This commit is contained in:
2026-06-17 07:15:32 +02:00
parent c023192113
commit ad8b25e70c
115 changed files with 6316 additions and 16971 deletions

View File

@@ -1,260 +0,0 @@
# DevDash Debug Challenge Series - START HERE
Welcome to the **DevDash Debug Challenge Series** - a comprehensive three-tier debugging curriculum for Next.js developers.
## Quick Links
### Debug Challenge Tiers
1. **Easy Tier** (5 errors - 30-45 min)
- File: `les3-debug-challenge.zip`
- Answer Key: `Les03-Debug-Challenge-ANTWOORDEN.md`
- Topics: Basic syntax, typos, simple logic
- **Start here if:** You're new to debugging or Next.js
2. **Hard Tier** (12 errors - 60-90 min)
- File: `les3-debug-challenge-hard.zip`
- Answer Key: `Les03-Debug-Challenge-Hard-ANTWOORDEN.md`
- Topics: Next.js patterns, React hooks, TypeScript
- **Start here if:** You're comfortable with React basics
3. **Super Hard Tier** (16-18 errors - 2-3 hours) ← YOU ARE HERE
- File: `les3-debug-challenge-super-hard.zip`
- Answer Key: `Les03-Debug-Challenge-SuperHard-ANTWOORDEN.md`
- Topics: Advanced patterns, circular deps, complex interactions
- **Start here if:** You're ready to master advanced concepts
## What is DevDash?
DevDash is a fully-featured developer portfolio and dashboard application built with:
- **Next.js 14** (App Router)
- **React 18** (with hooks)
- **TypeScript 5**
- **Tailwind CSS 3**
The application includes:
- Homepage with Hero section, Features grid, Testimonials carousel
- Blog system with dynamic routes
- Dashboard with analytics and user profile
- API routes for data
- Middleware for authentication
- Theme context for dark/light mode
## Getting Started (5 minutes)
### Option 1: Start the Super Hard Challenge
```bash
# 1. Extract the project
unzip les3-debug-challenge-super-hard.zip
cd les3-debug-challenge-super-hard
# 2. Install dependencies
npm install
# 3. Run the development server
npm run dev
# 4. Open browser to http://localhost:3000
# (You'll see errors - that's the point!)
# 5. Read the errors and start debugging
# Open Les03-Debug-Challenge-SuperHard-ANTWOORDEN.md for help
```
### Option 2: Read First, Debug Later
1. Start with `DEBUG-CHALLENGES-OVERVIEW.md` for context
2. Read `Les03-Debug-Challenge-SuperHard-ANTWOORDEN.md` to understand all errors
3. Then extract and debug the project yourself
## What Will You Learn?
By fixing all 16-18 errors, you'll master:
### Advanced React Concepts
- Advanced hook patterns (useState, useEffect, useContext)
- Closure patterns and stale closures
- Key prop best practices
- Memory leak prevention
- Context API provider patterns
### Next.js 14 Deep Dive
- Client vs Server components
- Middleware patterns
- Dynamic routes with metadata
- API routes and type safety
- App Router structure
### TypeScript Mastery
- Type safety in React
- Optional chaining
- API contracts
- Type narrowing
### Advanced Debugging Skills
- Circular dependency detection
- Performance issue identification
- Memory leak detection
- State management debugging
## The 6 Error Categories
| Category | Count | Examples |
|----------|-------|----------|
| **Blocking Errors** | 3 | Missing deps, wrong imports, circular refs |
| **Next.js Specific** | 4 | Client/Server components, metadata, middleware |
| **Logic Errors** | 3 | Filter bugs, stale closures, key issues |
| **TypeScript Errors** | 2 | Type mismatches, optional chaining |
| **React Patterns** | 3 | Cleanup, context, prop drilling |
| **Styling Errors** | 2-3 | Custom CSS, inline styles |
## How to Use the Answer Key
The answer key (`Les03-Debug-Challenge-SuperHard-ANTWOORDEN.md`) provides:
1. **Error Location** - Exact file and line
2. **Issue Description** - What's wrong and why
3. **Symptom** - What breaks or misbehaves
4. **Multiple Solutions** - Usually 2-3 ways to fix
5. **Severity Level** - CRITICAL, HIGH, MEDIUM, LOW
6. **Code Examples** - Before/after comparisons
**Pro Tip:** Try to fix each error without looking at the answer first. Use the answer key to verify your fix or for hints.
## Debugging Strategy
**Recommended approach:**
1. Start the dev server: `npm run dev`
2. Read the console errors
3. Fix blocking errors first (they prevent the app from running)
4. Then tackle Next.js errors (build-time failures)
5. Next, logic errors (functionality breaks)
6. Then TypeScript errors (feature-specific)
7. Finally, React patterns and styling
## Testing Your Fixes
After each fix, verify:
```bash
# Check for build errors
npm run build
# Check for runtime errors
npm run dev
# Run in your browser
# Click around and check if features work
```
## Validation Checklist
Your fixes are complete when:
- [ ] `npm install` succeeds
- [ ] `npm run build` succeeds (no errors)
- [ ] `npm run dev` starts without errors
- [ ] Homepage loads with all sections
- [ ] Blog shows 2 published posts
- [ ] Blog post detail pages work
- [ ] Dashboard page loads
- [ ] Stats load from API
- [ ] No console errors or warnings
- [ ] No TypeScript errors
## Need Help?
### If you're stuck on an error:
1. Read the error message carefully
2. Check `Les03-Debug-Challenge-SuperHard-ANTWOORDEN.md` for that error number
3. Try the "Symptom" section to understand what's broken
4. Look at the code example to see the fix
### If something doesn't make sense:
- Refer to the "Key Concepts Covered" section in the answer key
- Research the specific Next.js/React feature being tested
- Try different solutions to understand the principle
### If you need a broader context:
- Read `DEBUG-CHALLENGES-OVERVIEW.md` for the big picture
- Review the "Learning Outcomes" section
## File Structure
```
les3-debug-challenge-super-hard/
├── src/
│ ├── app/ # Next.js pages and API routes
│ ├── components/ # React components (many with errors)
│ ├── context/ # Theme context
│ ├── lib/ # Utilities and types
│ └── data/ # Static blog data
├── package.json # Dependencies (some missing!)
├── tsconfig.json # TypeScript config
├── tailwind.config.ts # Tailwind setup
├── middleware.ts # Auth simulation (has error)
└── README.md # Project overview
```
## Time Estimate
- **Easy errors (1-3):** 20-30 min
- **Next.js errors (4-7):** 30-40 min
- **Logic errors (8-10):** 25-35 min
- **TypeScript errors (11-12):** 20-30 min
- **React patterns (13-15):** 30-45 min
- **Styling (16-18):** 20-30 min
- **Testing:** 15-20 min
**Total:** 2-3 hours for the complete challenge
## Progression Path
After this challenge, you'll be ready for:
- Building production Next.js applications
- Advanced React patterns
- Complex state management
- Performance optimization
- Team code reviews
## Questions About Specific Errors?
Each error is thoroughly explained in `Les03-Debug-Challenge-SuperHard-ANTWOORDEN.md`. The document includes:
- Exact error location
- Root cause explanation
- Visual code examples
- Multiple solution options
- Why the error matters
- Best practices to avoid it
## Next Steps
1. **Now:** Extract the zip and run `npm install`
2. **Then:** Start the dev server with `npm run dev`
3. **When stuck:** Check the answer key
4. **After fixing all:** Review your solutions and make sure you understand each error
5. **Finally:** Try building a similar project from scratch using these patterns
---
## Document Reference
| Document | Purpose |
|----------|---------|
| **00-START-HERE.md** | This file - quick start guide |
| **DEBUG-CHALLENGES-OVERVIEW.md** | Big picture overview of all 3 tiers |
| **Les03-Debug-Challenge-SuperHard-ANTWOORDEN.md** | Complete answer key with explanations |
| **les3-debug-challenge-super-hard.zip** | The actual buggy project |
---
**Good luck debugging! You've got this.**
Remember: The goal isn't just to fix errors, but to understand *why* they happened and how to prevent them in the future.
---
Created: February 2026
Level: Super Hard (16-18 errors)
Time: 2-3 hours

View File

@@ -1,222 +0,0 @@
# DevDash Debug Challenge Series - Complete Overview
This folder contains a complete series of 3 debug challenges of increasing difficulty, all featuring the "DevDash" developer portfolio and dashboard application.
## Challenge Tiers
### 1. Easy Tier (5 Errors)
- **File:** `les3-debug-challenge.zip`
- **Answer Key:** `Les03-Debug-Challenge-ANTWOORDEN.md`
- **Target:** Beginners, first-time debuggers
- **Topics:** Basic syntax errors, typos, simple logic mistakes
### 2. Hard Tier (12 Errors)
- **File:** `les3-debug-challenge-hard.zip`
- **Answer Key:** `Les03-Debug-Challenge-Hard-ANTWOORDEN.md`
- **Target:** Intermediate developers
- **Topics:** Next.js patterns, React hooks, TypeScript basics, state management
### 3. Super Hard Tier (16-18 Errors)
- **File:** `les3-debug-challenge-super-hard.zip`
- **Answer Key:** `Les03-Debug-Challenge-SuperHard-ANTWOORDEN.md`
- **Target:** Advanced developers, mastery level
- **Topics:** Circular dependencies, advanced React patterns, middleware, complex type issues
## Challenge Structure
All challenges are based on the same application concept: **DevDash** - a developer portfolio and dashboard application built with Next.js 14, React 18, TypeScript, and Tailwind CSS.
### Features Included
- **Homepage:** Hero section, features showcase, testimonials carousel
- **Blog:** Dynamic routes with MDX-style content
- **Dashboard:** Analytics, stats grid, user profile, charts placeholder
- **API Routes:** `/api/stats`, `/api/posts`
- **Middleware:** Authentication simulation
- **Context:** Theme management (dark/light mode)
### Tech Stack
- Next.js 14 (App Router)
- React 18
- TypeScript 5
- Tailwind CSS 3
- PostCSS
## Error Categories by Tier
### Easy (5 errors)
1. Wrong variable name
2. Missing closing tag
3. Typo in function name
4. Array method confusion
5. Logic error in condition
### Hard (12 errors)
- 3 Blocking errors (missing deps, wrong imports)
- 3 Next.js specific errors (layout, metadata, routing)
- 2 Logic errors
- 2 TypeScript errors
- 2 React pattern errors
### Super Hard (16-18 errors)
- 3 Blocking errors (circular dependencies, missing deps)
- 4 Next.js specific errors (client/server components, middleware)
- 3 Logic errors (filters, closures, keys)
- 2 TypeScript errors (type mismatches, optional chaining)
- 3 React pattern errors (cleanup, context, props)
- 2-3 Styling errors (CSS and inline styles)
## Getting Started
Each zip file contains a complete Next.js project. To work on a challenge:
```bash
# Extract the zip
unzip les3-debug-challenge-super-hard.zip
# Navigate to project
cd les3-debug-challenge-super-hard
# Install dependencies
npm install
# Run development server
npm run dev
# Open browser to http://localhost:3000
```
## Using Answer Keys
Each answer key document provides:
- Exact location of each error
- Code snippet showing the problem
- Explanation of why it's an error
- The symptom (what breaks)
- One or more solutions
- Difficulty level (CRITICAL, HIGH, MEDIUM, LOW)
## Progression Path
**Suggested learning path:**
1. Start with **Easy Tier** to get familiar with the codebase and debugging process
2. Move to **Hard Tier** to tackle more complex Next.js and React patterns
3. Finish with **Super Hard Tier** for mastery of advanced debugging concepts
## Timeline Recommendations
| Tier | Recommended Time | Notes |
|------|-----------------|-------|
| Easy | 30-45 minutes | Quick confidence builder |
| Hard | 60-90 minutes | Requires research and testing |
| Super Hard | 120-180 minutes | Deep analysis and multiple fixes needed |
## Learning Outcomes
After completing all three tiers, students will have mastered:
### Easy Tier
- Basic syntax debugging
- Reading error messages
- Variable naming conventions
- Simple conditional logic
### Hard Tier
- Next.js App Router concepts
- React hooks and their rules
- TypeScript type safety basics
- Component composition
- Data flow and props
### Super Hard Tier
- Circular dependencies and module loading
- Advanced React patterns (closures, cleanup, context)
- Client vs Server components
- Middleware patterns
- Type-safe API contracts
- Performance patterns (keys, memoization)
- Styling best practices with Tailwind
## Key Concepts Covered
### JavaScript/TypeScript
- Module imports and circular dependencies
- Type safety and optional chaining
- Closures and their pitfalls
- Array methods and filtering
- Object destructuring
### React
- Hooks (useState, useEffect, useContext)
- Rules of hooks and dependencies
- Client vs Server components
- Context API and providers
- Key props and list rendering
- Memory leaks and cleanup
### Next.js
- App Router structure
- API routes
- Middleware
- Metadata and SEO
- Dynamic routes
- Image optimization
### CSS/Styling
- Tailwind CSS utilities
- Inline styles vs. classes
- CSS custom properties
- Responsive design
## Files Included
Each zip contains:
```
les3-debug-challenge-super-hard/
├── src/
│ ├── app/ # Next.js pages and layouts
│ ├── components/ # React components
│ ├── context/ # React Context
│ ├── lib/ # Utilities and types
│ └── data/ # Static data
├── package.json
├── tsconfig.json
├── tailwind.config.ts
├── next.config.js
├── postcss.config.js
├── middleware.ts
└── README.md
```
## Grading Rubric
Students can self-assess:
- All 5/12/16-18 errors identified and documented
- Code builds successfully (`npm run build`)
- No runtime errors or warnings
- Application functionality works as intended
- Code follows best practices (Tailwind usage, React patterns, etc.)
- Understanding demonstrated in commit messages or documentation
## Support for Instructors
Each answer key provides:
- Clear explanation of each error
- Multiple solution options where applicable
- Severity levels to help prioritize feedback
- Testing checklist to verify all fixes
- Common student mistakes and misconceptions
## File Sizes
| File | Size | Components |
|------|------|-----------|
| les3-debug-challenge.zip | 12 KB | 10 files |
| les3-debug-challenge-hard.zip | 19 KB | 32 files |
| les3-debug-challenge-super-hard.zip | 26 KB | 38 files |
---
**Created:** February 2026
**Updated:** February 25, 2026
**Version:** 3.0 (Complete Series)

View File

@@ -1,175 +0,0 @@
# Debug Challenge - Antwoordenblad (ALLEEN VOOR TIM)
**⚠️ NIET DELEN MET STUDENTEN**
---
## Overzicht Fouten
Het project bevat **4 typen fouten**, verspreid over **8 bestanden**.
---
## FOUT 1: Missing Dependency — `lucide-react`
**Bestanden:** `components/Hero.tsx`, `components/FeatureCards.tsx`
**Symptoom:** `npm install` werkt, maar `npm run dev` geeft:
```
Module not found: Can't resolve 'lucide-react'
```
**Oorzaak:** `lucide-react` wordt geïmporteerd in Hero.tsx en FeatureCards.tsx, maar staat NIET in package.json.
**Fix:**
```bash
npm install lucide-react
```
**Leerdoel:** Studenten leren dat je imports moet matchen met dependencies in package.json.
---
## FOUT 2: Typo in Import Path
**Bestand:** `app/page.tsx` (regel 1)
**Symptoom:**
```
Module not found: Can't resolve '@/componenst/Hero'
```
**Oorzaak:** Typo: `@/componenst/Hero` i.p.v. `@/components/Hero`
**Fix:**
```tsx
// FOUT:
import Hero from "@/componenst/Hero";
// CORRECT:
import Hero from "@/components/Hero";
```
**Leerdoel:** Lezen van error messages, herkennen van typos in import paden.
---
## FOUT 3: Missing Import — `TestimonialSection`
**Bestand:** `app/page.tsx` (regel 9)
**Symptoom:**
```
ReferenceError: TestimonialSection is not defined
```
**Oorzaak:** `<TestimonialSection />` wordt gebruikt in page.tsx maar is niet geïmporteerd.
**Fix:** Voeg import toe bovenaan page.tsx:
```tsx
import TestimonialSection from "@/components/TestimonialSection";
```
**Leerdoel:** Begrijpen dat elk component geïmporteerd moet worden.
---
## FOUT 4: Missing Imports in layout.tsx — `Navbar` en `Footer`
**Bestand:** `app/layout.tsx` (regels 17-18)
**Symptoom:**
```
ReferenceError: Navbar is not defined
ReferenceError: Footer is not defined
```
**Oorzaak:** `<Navbar />` en `<Footer />` worden gebruikt maar niet geïmporteerd.
**Fix:** Voeg imports toe bovenaan layout.tsx:
```tsx
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
```
**Leerdoel:** Zelfs in layout.tsx moeten componenten geïmporteerd worden.
---
## FOUT 5: Inline Styles → Tailwind (6 bestanden!)
**Bestanden met inline styles:**
1. `components/Hero.tsx` — hele component
2. `components/FeatureCards.tsx` — hele component
3. `components/TestimonialSection.tsx` — hele component
4. `components/Navbar.tsx` — hele component
5. `components/Footer.tsx` — hele component
6. `app/about/page.tsx` — hele pagina
7. `app/contact/page.tsx` — hele pagina (formulier)
**Symptoom:** Geen error, maar code gebruikt `style={{...}}` i.p.v. Tailwind classes.
**Voorbeeld fix (Hero.tsx):**
```tsx
// FOUT (inline styles):
<section style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
padding: '80px 20px',
textAlign: 'center',
color: 'white',
minHeight: '500px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
// CORRECT (Tailwind):
<section className="bg-gradient-to-br from-indigo-500 to-purple-700 py-20 px-5 text-center text-white min-h-[500px] flex items-center justify-center">
```
**Aanpak voor studenten:**
1. Selecteer de `style={{...}}` code
2. Druk `Ctrl+K` (Inline Edit)
3. Typ: "Converteer alle inline styles naar Tailwind CSS classes"
4. Accept
**Leerdoel:** Leren werken met Tailwind, Inline Edit gebruiken voor refactoring.
---
## Samenvatting: Alle Fouten
| # | Type | Bestand | Ernst |
|---|------|---------|-------|
| 1 | Missing dependency | package.json (lucide-react) | 🔴 Blokkerend |
| 2 | Typo in import | app/page.tsx (`componenst`) | 🔴 Blokkerend |
| 3 | Missing import | app/page.tsx (TestimonialSection) | 🔴 Blokkerend |
| 4 | Missing imports | app/layout.tsx (Navbar, Footer) | 🔴 Blokkerend |
| 5 | Inline styles | 7 bestanden | 🟡 Niet blokkerend, maar opdracht |
**Verwachte volgorde van oplossen:**
1. `npm install` → werkt (geen errors hier)
2. `npm run dev` → Module not found: lucide-react → `npm install lucide-react`
3. `npm run dev` → Can't resolve '@/componenst/Hero' → fix typo
4. `npm run dev` → TestimonialSection not defined → add import
5. `npm run dev` → Navbar/Footer not defined → add imports
6. `npm run dev` → ✅ WERKT! Site is zichtbaar
7. Inline styles → Tailwind conversie (Inline Edit)
---
## Verwachte Requests Gebruik
| Actie | Requests |
|-------|----------|
| "Welke deps missen?" (Chat) | 1 |
| Fix typos/imports (handmatig of Chat) | 0-1 |
| Inline styles conversie (Inline Edit × 3-5) | 3-5 |
| **Totaal** | **4-7 requests** |
Studenten op Hobby plan moeten dit kunnen doen. Tab Completion helpt bij imports.
---
## Tips voor Tim
- **Moeilijkheidsgraad:** Gemiddeld. Fouten 1-4 zijn "lees de error message" niveau. Fout 5 (Tailwind) is de echte oefening.
- **Als studenten vastlopen:** Wijs ze op de terminal error messages. Zeg: "Lees de eerste regel van de error."
- **Grid responsiveness:** De FeatureCards en TestimonialSection grids zijn `repeat(3, 1fr)` — op mobiel breken ze. Bonuspunt als studenten dit ook fixen naar responsive grid.
- **Contact form:** Werkt functioneel (alert), maar alle styling is inline. Goed voorbeeld van een formulier omzetten naar Tailwind.

View File

@@ -1,322 +0,0 @@
# Debug Challenge HARD - Antwoordenblad (ALLEEN VOOR TIM)
**⚠️ NIET DELEN MET STUDENTEN**
---
## Overzicht
Dit project is aanzienlijk complexer dan de easy versie. Het bevat:
- Blog met dynamische routes
- Dashboard met statistieken
- API route voor quotes
- Utility functions
- TypeScript types
**12 fouten verspreid over 4 categorieën:**
- Blokkerende fouten (compile/runtime)
- Logische fouten (code draait maar werkt verkeerd)
- Pattern fouten (React anti-patterns)
- Styling fouten (inline styles + custom CSS → Tailwind)
**Geschatte tijd: 2-3 uur met AI**
---
## CATEGORIE 1: BLOKKERENDE FOUTEN
### Fout 1: Missing dependency — `clsx`
**Bestand:** `lib/utils.ts` (regel 2)
**Symptoom:**
```
Module not found: Can't resolve 'clsx'
```
**Oorzaak:** `clsx` wordt geïmporteerd maar staat niet in package.json.
`formatDate()` wordt overal gebruikt, dus dit blokkeert ALLES.
**Fix:**
```bash
npm install clsx
```
**Moeilijkheid:** ⭐ (makkelijk, zelfde als easy versie)
---
### Fout 2: Verkeerde import path — NextResponse
**Bestand:** `app/api/quotes/route.ts` (regel 2)
**Symptoom:**
```
Module not found: Can't resolve 'next/servers'
```
**Oorzaak:** `"next/servers"` moet `"next/server"` zijn (zonder s).
**Fix:**
```ts
// FOUT:
import { NextResponse } from "next/servers";
// CORRECT:
import { NextResponse } from "next/server";
```
**Moeilijkheid:** ⭐⭐ (subtiel — 1 letter verschil)
---
## CATEGORIE 2: LOGISCHE FOUTEN
### Fout 3: Array.slice() met verkeerde parameters
**Bestand:** `components/BlogPreview.tsx` (regel 7)
**Symptoom:** BlogPreview sectie op homepage toont GEEN posts (lege grid)
**Oorzaak:** `blogPosts.slice(-2, 0)` retourneert een lege array.
`slice(-2, 0)` betekent: start bij index -2 (2e van achteren), stop bij index 0 — dat is leeg!
**Fix:**
```ts
// FOUT:
const recentPosts = blogPosts.slice(-2, 0);
// CORRECT:
const recentPosts = blogPosts.slice(0, 2);
```
**Moeilijkheid:** ⭐⭐⭐ (geen error, gewoon lege output — student moet debuggen WAAROM)
---
### Fout 4: Icon mapping mist "star" entry
**Bestand:** `app/dashboard/page.tsx` (regel 25-30)
**Symptoom:** De "Rating" stat card toont geen icon (undefined rendered als leeg)
**Oorzaak:** `getIcon()` functie heeft entries voor "users", "eye", "clock" maar NIET voor "star". De stats array bevat wel `icon: "star"`.
**Fix:**
```ts
function getIcon(iconName: string) {
const icons: Record<string, React.ReactNode> = {
users: <Users size={24} />,
eye: <Eye size={24} />,
clock: <Clock size={24} />,
star: <Star size={24} />, // ← TOEVOEGEN
};
return icons[iconName];
}
```
**Moeilijkheid:** ⭐⭐⭐ (geen crash, subtiel visueel verschil)
---
### Fout 5: Mobile menu altijd verborgen
**Bestand:** `components/Header.tsx` (regel 62 + 73)
**Symptoom:** Hamburger menu knop is onzichtbaar, en zelfs als je `display: none` verwijdert van de button, is het dropdown menu ALSOOK `display: none`.
**Oorzaak:** Twee problemen:
1. De hamburger button heeft `display: 'none'` → onzichtbaar
2. Het mobile menu div heeft `display: 'none'` → zelfs als `menuOpen` true is
**Fix:** Dit moet volledig naar responsive Tailwind met `md:hidden`/`md:flex`:
```tsx
// Button: verberg op desktop, toon op mobiel
<button className="md:hidden" ...>
// Desktop nav: verberg op mobiel
<nav className="hidden md:flex gap-6" ...>
// Mobile menu: toon als flex wanneer open
{menuOpen && (
<div className="flex flex-col md:hidden p-3 border-t bg-white">
```
**Moeilijkheid:** ⭐⭐⭐⭐ (vereist begrijpen van responsive design patterns)
---
## CATEGORIE 3: REACT PATTERN FOUTEN
### Fout 6: async functie in useEffect
**Bestand:** `app/dashboard/page.tsx` (regel 35-39)
**Symptoom:** React warning in console:
```
Warning: useEffect must not return anything besides a function
```
En mogelijke infinite loop.
**Oorzaak:** `useEffect(async () => {...})` is een React anti-pattern. async functie retourneert een Promise, maar useEffect verwacht `void` of een cleanup functie.
**Fix:**
```tsx
// FOUT:
useEffect(async () => {
const res = await fetch("/api/quotes");
...
}, [quote]);
// CORRECT:
useEffect(() => {
async function fetchQuote() {
const res = await fetch("/api/quotes");
const data = await res.json();
setQuote(data.text);
setLoading(false);
}
fetchQuote();
}, []); // ← ook dependency array gefixed (zie Fout 7)
```
**Moeilijkheid:** ⭐⭐⭐⭐ (vereist kennis van React hooks patterns)
---
### Fout 7: Verkeerde dependency array → infinite loop
**Bestand:** `app/dashboard/page.tsx` (regel 39)
**Symptoom:** Dashboard pagina loopt vast, continue fetch requests in Network tab
**Oorzaak:** `[quote]` als dependency betekent: "run opnieuw als `quote` verandert". Maar de effect ZET quote, wat de effect OPNIEUW triggert → infinite loop.
**Fix:**
```tsx
// FOUT:
}, [quote]);
// CORRECT:
}, []); // Lege array = run 1x bij mount
```
**Moeilijkheid:** ⭐⭐⭐⭐ (vereist begrip van useEffect dependency arrays)
---
### Fout 8: Null check mist op quote state
**Bestand:** `components/QuoteOfTheDay.tsx` (regel 22)
**Symptoom:** `TypeError: Cannot read properties of null (reading 'text')`
**Oorzaak:** `quote` begint als `null`, maar zodra loading false is, probeert de component `quote.text` te lezen. Als de API fails of traag is, kan dit crashen.
**Fix:** Voeg TypeScript type toe en null check:
```tsx
// Verbeterde state typing:
const [quote, setQuote] = useState<{ text: string; author: string } | null>(null);
// In de render:
{!loading && quote && (
<>
<blockquote>"{quote.text}"</blockquote>
<cite> {quote.author}</cite>
</>
)}
```
**Moeilijkheid:** ⭐⭐⭐ (runtime error, maar duidelijke error message)
---
## CATEGORIE 4: STYLING FOUTEN (Inline Styles → Tailwind)
### Fout 9: Custom CSS classes in globals.css
**Bestand:** `app/globals.css`
**Probleem:** `.card`, `.page-container`, `.section-title` zijn custom CSS classes. Deze horen Tailwind utility classes te zijn.
**Fix:** Verwijder custom CSS uit globals.css. Vervang in alle componenten:
- `.card``className="p-6 bg-white rounded-xl border border-gray-200 hover:shadow-lg hover:-translate-y-0.5 transition-all"`
- `.page-container``className="max-w-[1200px] mx-auto px-5"`
- `.section-title``className="text-3xl font-bold text-center mb-10 text-gray-900"`
**Moeilijkheid:** ⭐⭐⭐ (moet begrijpen waarom Tailwind beter is dan custom CSS)
---
### Fout 10-12: Inline styles in alle componenten
**Bestanden met inline styles:**
| # | Bestand | Complexiteit |
|---|---------|-------------|
| 10 | `components/Hero.tsx` | Hoog (gradient, positioning) |
| 11 | `components/Header.tsx` | Hoog (responsive, sticky) |
| 12 | `components/Footer.tsx` | Medium (grid, links) |
| - | `components/StatsSection.tsx` | Medium (grid) |
| - | `components/BlogPreview.tsx` | Medium (grid, cards) |
| - | `components/QuoteOfTheDay.tsx` | Laag |
| - | `app/blog/page.tsx` | Medium (grid, tags) |
| - | `app/blog/[slug]/page.tsx` | Medium (article layout) |
| - | `app/dashboard/page.tsx` | Hoog (stats grid, cards) |
**Studenten moeten minimaal 3-4 bestanden omzetten.**
**Aanpak:**
1. Selecteer inline style code
2. `Ctrl+K` → "Converteer inline styles naar Tailwind CSS. Gebruik de brand- kleuren uit tailwind.config.ts waar mogelijk."
3. Review + Accept
**Moeilijkheid:** ⭐⭐ per bestand (maar het zijn er VEEL)
---
## Samenvatting Alle Fouten
| # | Type | Bestand | Ernst | Moeilijkheid |
|---|------|---------|-------|-------------|
| 1 | Missing dependency (clsx) | lib/utils.ts | 🔴 Blokkerend | ⭐ |
| 2 | Verkeerde import (next/servers) | api/quotes/route.ts | 🔴 Blokkerend | ⭐⭐ |
| 3 | Verkeerde slice() params | BlogPreview.tsx | 🟡 Logisch | ⭐⭐⭐ |
| 4 | Missende icon mapping | dashboard/page.tsx | 🟡 Logisch | ⭐⭐⭐ |
| 5 | Mobile menu altijd hidden | Header.tsx | 🟡 Logisch | ⭐⭐⭐⭐ |
| 6 | async in useEffect | dashboard/page.tsx | 🟠 Anti-pattern | ⭐⭐⭐⭐ |
| 7 | Infinite loop dep array | dashboard/page.tsx | 🔴 Runtime crash | ⭐⭐⭐⭐ |
| 8 | Null check quote state | QuoteOfTheDay.tsx | 🔴 Runtime crash | ⭐⭐⭐ |
| 9 | Custom CSS → Tailwind | globals.css | 🟡 Styling | ⭐⭐⭐ |
| 10-12 | Inline styles (9+ bestanden) | Overal | 🟡 Styling | ⭐⭐ elk |
---
## Verwachte Oplos-Volgorde
1. `npm install` → werkt
2. `npm run dev``Can't resolve 'clsx'``npm install clsx`
3. `npm run dev``Can't resolve 'next/servers'` → fix import
4. Site laadt! Maar:
- BlogPreview is leeg → fix slice()
- Dashboard crasht/loopt vast → fix useEffect + dep array
- Quote component crasht → fix null check
- Rating icon mist → fix icon mapping
- Mobile menu werkt niet → fix responsive
5. Inline styles → Tailwind conversie
6. Custom CSS → Tailwind refactor
---
## Verwacht Request Gebruik
| Actie | Requests |
|-------|----------|
| Debug blokkerende errors (Chat) | 2-3 |
| Fix logische fouten (Chat + Edit) | 3-4 |
| Fix React patterns (Chat) | 2-3 |
| Inline styles → Tailwind (Inline Edit × 4-5) | 4-5 |
| **Totaal** | **11-15 requests** |
⚠️ Dit is meer dan de easy versie. Studenten op Hobby plan moeten efficiënt werken. Student plan (500/maand) is ruim voldoende.
---
## Tips voor Tim
- **Dit is echt moeilijk.** Fout 5, 6, 7 zijn lastig voor beginners. Verwacht dat studenten hier hulp bij nodig hebben.
- **De useEffect fouten** zijn een goed leermoment — dit is een HEEL veelvoorkomende bug in React.
- **De slice() bug** is een subtiele logische fout. Studenten moeten leren: "het werkt zonder errors, maar het resultaat klopt niet" → debuggen.
- **Mobile menu** is bewust moeilijk. Dit vereist dat ze responsive design begrijpen. Goede Cursor prompt: "Maak deze header responsive met een hamburger menu dat werkt op mobiel met Tailwind."
- **Geef de easy zip als standaard.** Hard is voor studenten die de easy versie snel af hebben of die extra uitdaging willen.

View File

@@ -1,212 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 6 0 R /F6 10 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /ZapfDingbats /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/BaseFont /Symbol /Name /F6 /Subtype /Type1 /Type /Font
>>
endobj
11 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/PageMode /UseNone /Pages 17 0 R /Type /Catalog
>>
endobj
16 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260224202513+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260224202513+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
17 0 obj
<<
/Count 7 /Kids [ 7 0 R 8 0 R 9 0 R 11 0 R 12 0 R 13 0 R 14 0 R ] /Type /Pages
>>
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1125
>>
stream
GauHJbAu>s&A7<Z-DsjA&V@iWKPu^L<1G@cCq+i_Ke("q>nX,uP2k9MO3[e?.I1PVX-/]HU]b(Umch^k#b4'qT&U^(]*.!'7mI*s/YHP[OqA+dhU`gO7[f]9,6UD4Z<.E-@D1uTrJ4bZf,dkq&Wm8E7&2X@Zbe1qV4.$i8aK7^$m[W\+@*jd!_l&_KP&@tD.u7u6%@s1O=,#0r`N"SoIc\0>qp><e,PM1g!.^,OPE4o$3lu.`8Z(r;A0ER94\Sf0bNmNrs'4f0Tgl4,Vq-Z1#pi.9W7Ki>V97^%sa;bMnZ[r,<][W!N4,@/ut#XB'_fEg1hBJDR1K@0p4kTQF?0.2HG2"`.Ha2.iLo?$4>.7^;c@((snnt_<i`sO4Ne;(cJ1Xd;^_eML9@Q?^'=8%4^6R(>[&q=*PL$1^^fQ'CbLj-I=t-h=MuR?p/qQd=1*HQ;Z/p4YCC=WI':+pUFW*29`,lhMi<pct@8[F[%=uBtZT>PO6t`\8%<Gl1?jGX3,@gX5H3E%IU)1OZMTO&$1-ZGn$0oc^2Q@dJ:gY6[3QMS\#]?N1Fk<#P:q,Z7*!`2jUh&.;kF>J&[F9#jX=+/<^Ynko(_m/(2Nd]K0@W_r?r#:GegA%'H<rWrA@Gm1o+(\GGgfj7"mVbH'U@n7%%-*n6X-=;`,iV0LV,i=NgK/;nN.&DVHMS+iq*>o;!C[U`Mu_ccf;R$MHpUZU(VEfbQ:%E<6B);J(@@K5K]*K@bm&spo/*CY03Tm4GR"h]oO2V9,6oN+<iIFTb/oZ2rmRW/n-+?r8-%[p(Qibe^EkT+]K$)u`EIRh]3+tN[qmBpTc8Gcr.F]$R62Nlod7GK`&(-YX<Qt_Pr[uI)bIjbe:]+3ND5mNUQ-Mn^_VaP)+[B]?^4Gjkj1\<JY]!Mm65Am$)Bk-d^BPV/<B+Z4uLKP^1N-%&X%`V-?7)EA*!BX'%BZ=U^RJ6EUW9aIP2=A,U(L@Y6[5(5uJaO64gsmchc-pMI/*8W;(LcfQ?>+gGmOsL*=qKa"TECNUbl*'mA`:[G!FFSEJr[pa'<JgH..RSuR[E6eJ6QU/M"^aT*-Dt.EU88`2g!il`T?"?n=cA^H@/4AMPfnnSP\hYHian+hZ<XDS/V~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 951
>>
stream
Gaua;_,B#A&;KY!MWJH;W@H#qCi_,G)/(0YEL@kgEK\9qEU4f0D9:7NjZ"Ru1A'"/1)%Wi<rW,;="AgM=#@\P3P\2p+8Z+mK_QUA@LD*&LuR9$:UE3+^g&H;po_dA,s;V$h/'2lORQQ?Je"D34SDkoC@2<l;m((f@+p'W0PR_`35r__RY<L2n:;Vh_Ym>mH+roM#Y]P,Uf^A&>hYR):>Y3!4">riZd"tb1UW5TSQu]pS;r]#,IBVNq1>hSju$C5E>/i^N@@R*Wsf@Q9'O5=W?J<MJhY&5M0W-t#DWjT5^[,"J('LhBueiD[.IHC`+rbMAP[$;'G'Rl;h:7K8&hC/4964V&op?T$^MkifB580M$`L]"!fIR`Z5Fj6sC5d4>#9#K&PM)T<:I"_o]mJf<<T2BQ+kUs6=r)+St7cU;754Ds/"#;7bjQIk"FRqS'$6l`>2bD,`Tp\0?"UZDKI[8s@\2'1#d"Z5TFXYQi%?JuVR>c(L^-#)YZ&<P=ln*F3!eW&"=b\u"cE+1L?(%^IOY[EN::)fIR0?90MR2*2#9S_d>@djMKoL8O)cZ`9`\,J+=7-<Q5!NaJ@:5X\-Ya`s,I#ertfW?K6C=_=uYk"0#5(]r9=b.=/1TdgGYiT"/c2O3)44j:12B[?SN>[RLQ4E?GE%aBqf9u=74U,4a<X/BkLC,\fun!eo[C,r:MbRs-<70u=O5`TuH#(BJ7N2KfB4Cmr,MW7i0W<8BN3;EqC/esFjpGPS\G3dX&mA3m+lbaj,XrY&Cs5*"&h!T<9H!H#k>(q&c8aYm%29B'Ll'eXWjl-7mD1N;*<VB6S;5R;RoX$=6.*"=`N3PMdZBZ4a8+WlN0h":),uHJj\JKG/?Ad4;"frOW*jnVp_(M[<h.&F`d5%Q2-$bLSh2%bGQC,e`E*Y/MX1NF4.oW5PQY!n&]M?nV83h&aL#*nW_R)u~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1197
>>
stream
Gaua=?#T!f&;KZL'si,VS38m;P;o7[/58@)MY<9N='BTR_>o-pViCec^O@QhX"C0M<n_DQ+;r5nqp%1IP^uIL<e%#gJ3O\AH-Uq[84LCBa+r^oihZ+k@%/N4jEQ8tqMcIZME"=Gd"UCd!"S1c+#%Y;]]IT\B&$5UZp9Es`ZT(X?Kes4(kg9q>.".QA+dR+G-(EQ::/]@inKH,4i'%_P;tIH8c':u.9tb8>kL.E0q@/!RD`C@^ZqE0h%N>VOF4+[q"9sLJ@pTkYLL<ImYTcp6c6#rf.u#3A$q7V>AgRSf%`C*-aKTc();dJ8`(==WBt+%q&STq)AjZ_R715OR9<?(=r;#p3\N>)WQK?(`l0[[6,^7M&'q*CNRkMS^#H.G$Mc>LW*7>p2qWfZ-JU^2TAW#-l4,OO/MXXNK%4%e-_s:*=%G:KAr^AS4I_YFIrbC#r?cV5R*M_U[#L3/0Ln4P6W>/j,bRbYJSBUP3[9\>cUnSVJ-cs3.*K^t9/A];H&lQrP_@cU:g.!^Xfg96aJ-c*p_0O8p^5Kdg@Q[_T'J\.,H4b&BBT&\\f*+-XibI:d-:4P-8NbGRJOjqpT%6;>lOTL':i7hE=[GHs1h">BsiU57c6\Ka1Ms';>l2Q(AZ9aoRh&4*--Xm#C_R2(:YrbldCd9G@9,NLEGH9LrK-T*F'.u)8s)pr1,U!(M2MX_iSEJ@88Ip?l%E(3X\l$6Re>Oqphqus%^0lB0Q6Bh*1p$RKG+iM92V>=OAp'KYO^"=I.8+p--4fX51<[%]f*b82\',G::/`0a86bS^:Z'IpH:J:>mgCG7qE&-V8^;h=QF4RK=;.,=p-XDfj@7j8ZhB4CqFp;VCD,+g"2.8f/0s#E*R.9/KhA-TV&4q7%ppcPODP\VrTAG5VB9K\3&>;.;X+;,\SiilFI:&S1^9;CIJFjG<DOLd=E[2)/\hN?Tj<^O;I]7Kkj8R#D!kEk&S@_u^ZrY4N<:h9*/`Q*[mu9T:f-[JsLt2dYpCr@Zfh2D_RX5]aBjkpS+sXn2,Do>-6E?W$i:T8Zaq$2R[%:CT2#\jNJdSjR4g`HTNRNs55TP8+ia>opN3@(B9qEHbZ,Hq*`UJGXfZ#/p,KV#Yl8Q3t?7)52um"K71sf2u)+3(&!(/#;;f.f+pshn5aS:,K(9F`6]Oi)5UdXATY]_-8qFPQ^HC6tlK#OQ!qg~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1262
>>
stream
Gau0B;0/3d&:XAWfR'XI>SKa8!IhZUOs\X_g5K`Y3o_"oX-bE1"QZr#?[iFfN,]j#*mU@^$sKcV^]!ki(f:.9qDZOs/j_P4@_asVhgl(X+M<EOE:c,!NMZ9(,<6"TEJQ9XMp"Ta]m3l$4GQ!@K<;uML]i:)Se&d6-2624/E0NelN@Am8eUWD_&5EN)SWs.&IF+ffCsPLU4!t*X^gF=&Md4sX`pW7X3DR6V(*Y4cin>naNDKtQ.U@'kP5F;J+)'MiQA5RaAWUAQ0B9[FC@l5O8=G-Qcd0;>9(i:_PgoM:TCe$0b9ScKoV?:<f&FOX).E;*QL5@XkkCP?R9]!6[@RJ6c^QtM9jfcnTNV$OpF@+C7^4cB$NQu18p9LN43d#d.RP#Ga/%6jK&Ee6^_"`eu9=t_7>=HqNJ!`NJNU-i?+[X9OJWb]=kcP!*>f,KU6m:+5c-nP&$H<k>[ep^a]T'a)g[k/`aNb&?7#SRH2QeaUVQ/TYNNQf'u*UM*Q1SLBumX#h!^/'>kh+9r0-O%^[^XX2:3[Sj9U'Y2!QS)/YKB\Pbj41taDL?<"('YW]XO;01U=5PB`k'R7mc9p",&gW]N4hW3)THB90VkWZ.p*l&aE"EA/lQog/I&f1<MBcaBLCGM:&Ae:f=/2?hnQpf$khgmng=J&#/[gJ6HjsQA/Q=K]iM^Oe1Yf,C4d2BE<_:g5M@\6lNa745TWe^*6s$q;N4i8nN+UUsVRF8*_M</+/`<(f8C@$R,2*$i@l:MYO4NWg[6#=6I+`3PY.\221m'(BA0>YM6NX%sQ+7G!g],l/bJ3r8D!7t+rT`8#HpXQb'>lI>UjA%^i.NBcGqga.BMdi7TJ4=K1Q\e.3:2Q,bIY;d0B$8Yaa@7S#WhUL,aC%)J'rer::QO.9o67mjmC*$ihcJY(m`k=JV'$gBX8s=6]q%B^>^VmM(EG06A$*bH2piF&T,cr07hMU.4+,C2.QW4&@j_f)^K[/s]jmtGn\n-J,k.W!.Yeas`R`h07m5kdf1ao)DO8L/@!r;KIDe!6p-@qH3Pm/f`S;EmWQ?2Zr@>-6Z+UnNb#Qpu%e#;2=ZMAdOi_``bO]M^D%KqN3YF`bnCj9)AP4*kF5OH1lJ`90S#*<6a(ri5qiro0:#;A9J*COMc?cp9kNouR]8Ht-%%2YZ-%nco<%OBq$T;Z1aW'>0cITU->MWcK3YV:p\rjgNV:&?+Yt!ib`oClaZI/EY:LA%ug;LR]<lUr)Q_,#LknJq[Qn\@Q+q_5s~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 504
>>
stream
Gat%^hbV*C&BE]&=55$`=OsYB=s"=c-7BcK?c7^$V9Ea+Yi1ZChE%\<JhV5eZ<Y0=rNpd(+`r;$mj&/%-IB<CJ-dJW^_BX:#&oh1b5cmc9B%oT&MPYr?m9W6p$m7-9Mg,;\,aN/P!ib=-N[<eB'fT?R*ulSP!"[,h9aViT75PDh<>VLpOCDMnWOs7hFE9B?_#'D:=bs\^2GY"gGf5$@7YEP9l.PJKeBC2o2qulI+4?0[0<SQO#'=5+_gD07T:MS@eSu(X/G2Ch_0-m2k:sa2m_XqV;G2Lh?;jJ%V$d=V*(hrV2)BfY']H?!QR81PVW8)G[*nk@aQ(*2fohXkQ]>Hm-k*$\aB?#Ao%"d']H*RMus#&VQ-/IX]eGg!dnkBg<K%:K82Lreh';,0-$_=mB$@VBcJl7LkRI4rIY#(3'e-(`3E-@Zg':/&u#ctl1*KCj]lAb/rYN^X7%Z(3eJ"f1#*51DP1p-/gk^UZ_-<l123G4D-Ra+0>MuDPQ~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 950
>>
stream
Gau`R9lo#B&A@ZcG&kZcFQ,p`2n+H:aHV'/\2eO`=;V3G,.=!us1W8Y'#MGodjaO;6*ONamn(-07=ejAeb:m(\-V$Y2[Yd80EZ&-('I"g"g+[<XZ[]N0HJ0;pC,7:(f"l*%u7\R(lZ"k9u6=eDrDU;glg2EgMoh)"kP\,QOjZ_%^T)=Zu"r,mE9IhC1#$Nl@%'ohAFjcBK\FJTfib.n%e?Vs!iQemI`>[a)H/Wlok"/HT*;`4@4!jY46!<H5jE*bUV$/*2hZJpsUPs7Y!M\rj78Z*>U(iPH&1KR]P5MEbW](bI_D(L'P[R."q"Rm9nNUm9"$-UBh8fY(kZN:n"729sHcQiFjQeFWf\-,).dCb-U&0VQq1.)53[Mk*]Nt6!GiF6BSnd)'+,bQX.gPTBp.Wr>Xb.kUqd@^?+4+EEg[1%Dr??_2B*>IA$nb"G:?d`[B#1*4A.ENH5o6D2`s<j\FnUKS4l2C#3"Q98$Qc(W`Klk;&Y[NIN9;T?]7_paft2LtqPT4>#R]$Vu]5#'!`)s!@InHP\pDclMbihhP:.THW&`QQ!e3CjYB5[eVh1TSkj,&e:*n/J^J/1;X,iL#s"8N/^Uu/RtC1<An0Bam;ehR]'=r]PPA?&SB7Z'k3M[T9uAX1]q@(HYC4h4Ii-P8M/5RU"2>iH&[$nZNVCn#o<bZ_Jq,A7dTo*7bfS9X]"AC7VaSr5'i0UmHg7Q<`1?Q8;)GGBbKPgc^T2_o[(7RKbPr%$!=%ZkcfpZ+]47ZjSCamq;:d\k!qW^M0!pXW7%'=QmC-7R:Fro9L;8p5jB@PGPgi/3p(#4C/.T?+$;jd==uPTe/"kValc_]aIDrD(A/\IH`TReZ$e<BJd/f?%4gd;(G17^'>#_(6bF\N"..R0UcZU%$C<J;9c/OdKX*;O8d./_%p(G]EEO'B8lisD0GZDHB*0?bIKOE)IM;~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1110
>>
stream
Gaua=D/\/e&BE]*;r%5lm4ajPPk$0Bj5SX*RT*KG/-Dd%[&mH-*(i[B55e6`jLDVI@'9b8#t"X[m`.EG0aoIZ/Gu$>!)=2PIE@"Z87oX_OcYO\+rNKQ=JO@X&XmdfED.RZ,;^@pFR=-AUsc-s-Sj-*-m5$!W>L_a`tDnWq7m.iV00Jk^r/VX8gEg@B!9QqrLkc]E?klrRR@T$Y.&u0T4X(*Z@_&c`V;U/]07>8HMeD=#=r1O#$n/WdR6\GpDl]B:]$4M+XogdNl;&p!2MIp%A091nD)l=k"o)BXmA0V7tDpr3!b:YMTZK)`l[Gh3S\6X0XY;j0.Zt^IGk3NEN-6mQ+8^^p@V:SJdp$5<u/?#:T8Q+S!Smr/ho<0U2CaUaMU*BoQTk<#?nT:H;EJMVNYa2!>[S#>PD&4c,e-bQQ^fa'^OdHQEu:9/7DK2MqQ=Kc`'?/;sPc;+S@qs;Ka.%[4[tl'hj!Y/ui6m`=.ri\H]ObF',:L^1Im.,hCRUI&H^u0/:pgMmS)Da&(<[q\Q"R/:I4G8)URFD3!PXFe14pJ,E%!,MM&0at@JVKrpABHung=Y?'I*1mWPk:ImsRf^Abm?1djMcdV3DgN3./Fa]aLrLSC]VO7QEKQ<d$57nA=Acrpc7(a0q5'JE0"(lbY?hhHoJk"0@%>g(1VY6sfM<(6Lh*:'9p&E"-da*7#5<_VBRV`H'_nm6c+sr!EVS^OpSkQ0.mM05-b698^1U5DXbhTT#<5'DdDY(En7OgdbR&QY$Ds:e.foEF*o0UC/S;J_gC/1jQmAFj&*_[!a["t<LQ-Xq$p[JPVUHQ)eBqJ?t_P8"m<a09$4j'Yr4")9sR>c=S)Ih2$+c-b+;InugC.5XhOV_B?=up\Ljt'b2Zef5+U9t];6,7a"LV\h)Y\RDoEqDo1_/_rb+lXV=p)fIC>VL"_Wt#^@o]LhG<:.$ud3HVWcQc@)Ns4dm-Gru7RIsh(>t#ZWTHHJ%81R,bIH=t+]/SX(>.PZ#(^=2)!dri6<A>%REM76j*BnCs0qh:/D=,BP"E=S!#9N>YSm+._E&YF%rUfM(gu1N9Im<oL_,W5!j^_4&q0):VMGJ[.Ae,OZrrQpK4+d~>endstream
endobj
xref
0 25
0000000000 65535 f
0000000061 00000 n
0000000143 00000 n
0000000250 00000 n
0000000362 00000 n
0000000477 00000 n
0000000560 00000 n
0000000665 00000 n
0000000870 00000 n
0000001075 00000 n
0000001280 00000 n
0000001358 00000 n
0000001564 00000 n
0000001770 00000 n
0000001976 00000 n
0000002182 00000 n
0000002252 00000 n
0000002533 00000 n
0000002633 00000 n
0000003850 00000 n
0000004892 00000 n
0000006181 00000 n
0000007535 00000 n
0000008130 00000 n
0000009171 00000 n
trailer
<<
/ID
[<ae7522970d3e617d2353a6d07b4c4975><ae7522970d3e617d2353a6d07b4c4975>]
% ReportLab generated PDF document -- digest (opensource)
/Info 16 0 R
/Root 15 0 R
/Size 25
>>
startxref
10373
%%EOF

View File

@@ -1,638 +0,0 @@
# DevDash - Super Hard Debug Challenge (16-18 Errors)
## Challenge Overview
This is the **super hard tier** debug challenge with 16-18 intentional errors distributed across 6 categories. Students must identify and fix all errors to make the project run successfully.
---
## CATEGORY 1: BLOCKING ERRORS (3 Errors)
### Error 1: Missing Dependency - `date-fns`
**File:** `src/lib/utils.ts`
**Issue:** The code imports `format` from `date-fns`:
```typescript
import { format } from "date-fns";
```
But `date-fns` is **NOT** listed in `package.json`.
**Symptom:** Runtime error: "Cannot find module 'date-fns'"
**Fix:** Add to `package.json` dependencies:
```json
"dependencies": {
"date-fns": "^2.30.0"
}
```
---
### Error 2: Wrong Import Path - `next/navigations` (with 's')
**File:** `src/components/Header.tsx`
**Issue:**
```typescript
import { usePathname } from "next/navigations"; // WRONG - has 's'
```
Should be:
```typescript
import { usePathname } from "next/navigation"; // CORRECT
```
**Symptom:** Module not found error at build time
**Fix:** Remove the 's' from the import path.
---
### Error 3: Circular Dependency Crash
**Files:** `src/lib/types.ts` and `src/lib/utils.ts`
**Issue:**
In `src/lib/utils.ts`:
```typescript
import { formatDate } from "./utils"; // Value import, not type
```
In `src/lib/types.ts`:
```typescript
import { formatDate } from "./utils";
export function createPost(data: Omit<Post, "formattedDate">): Post {
return { ...data, formattedDate: formatDate(data.date) }; // formatDate is undefined!
}
```
**Symptom:** `TypeError: formatDate is not a function` at runtime
**Fix:** Use `import type` for types to avoid the circular dependency:
In `src/lib/utils.ts`:
```typescript
import type { Post } from "./types"; // Change to type import
```
Alternatively, restructure to avoid circular imports altogether.
---
## CATEGORY 2: NEXT.JS SPECIFIC ERRORS (4 Errors)
### Error 4: Missing "use client" Directive
**File:** `src/components/ThemeToggle.tsx`
**Issue:** The component uses `useState` (a client-side hook) but **lacks** `"use client"` at the top.
```typescript
// MISSING "use client" at the top!
import { useState } from 'react';
export function ThemeToggle() {
const [isHovered, setIsHovered] = useState(false);
// ...
}
```
**Symptom:** Next.js error: "You're importing a component that needs useState. It only works in Client Components."
**Fix:** Add at the very top of the file:
```typescript
"use client";
import { useState } from 'react';
```
---
### Error 5: Hooks in Server Component
**File:** `src/app/dashboard/page.tsx`
**Issue:** The page uses `useState` and `useEffect` directly:
```typescript
// MISSING "use client" at the top!
import { useState, useEffect } from 'react';
export default function DashboardPage() {
const [isLoading, setIsLoading] = useState(false);
// ...
}
```
**Symptom:** Hydration error or "You're importing a component that needs useState"
**Fix:** Add `"use client"` directive at the top:
```typescript
"use client";
import { useState, useEffect } from 'react';
```
---
### Error 6: Wrong Middleware Matcher Pattern
**File:** `middleware.ts`
**Issue:**
```typescript
export const config = {
matcher: ['/dashboard/:path'], // WRONG - missing asterisk
};
```
This matches `/dashboard/:path` but NOT `/dashboard/page/subpage`. The sub-routes won't be protected.
Also, the middleware looks for `'auth-token'` cookie but the app doesn't set one:
```typescript
const authToken = request.cookies.get('auth-token')?.value;
```
**Symptom:** Sub-routes of dashboard bypass middleware; authentication doesn't work
**Fix:**
```typescript
export const config = {
matcher: ['/dashboard/:path*'], // Add asterisk for all sub-routes
};
```
And update cookie name to match what's actually used or create a mock auth mechanism.
---
### Error 7: `metadata` Export in Client Component
**File:** `src/app/blog/[slug]/page.tsx`
**Issue:**
```typescript
'use client'; // This is a CLIENT component
export const metadata = { // Can't export metadata from client!
title: 'Blog Post',
description: 'Read the full article',
};
import { useParams } from 'next/navigation';
```
**Symptom:** Build error: "Metadata exports must be in a Server Component"
**Fix (Option 1):** Remove `'use client'` and use function parameters instead of `useParams()`:
```typescript
// Remove "use client"
import { generateMetadata } from 'next';
import type { Metadata } from 'next';
export async function generateMetadata({ params }): Promise<Metadata> {
const post = getPostBySlug(params.slug);
return {
title: post?.title || 'Blog Post',
description: post?.content.substring(0, 100) || '',
};
}
export default function BlogPostPage({ params }: { params: { slug: string } }) {
const post = getPostBySlug(params.slug);
// ...
}
```
**Fix (Option 2):** Keep as client component and remove metadata export:
```typescript
'use client';
// Remove metadata export
import { useParams } from 'next/navigation';
// ... rest of component
```
---
## CATEGORY 3: LOGIC ERRORS (3 Errors)
### Error 8: Wrong Filter Condition
**File:** `src/data/posts.ts`
**Issue:**
```typescript
export function getPublishedPosts(): Post[] {
return posts.filter(p => p.draft); // WRONG - returns DRAFTS!
}
```
Should check for `!p.draft` (NOT drafted):
```typescript
export function getPublishedPosts(): Post[] {
return posts.filter(p => !p.draft); // CORRECT
}
```
**Symptom:** Blog page shows only draft posts (only 1 post visible instead of 2 published ones)
**Fix:** Change condition to `!p.draft`.
---
### Error 9: Stale Closure in Event Handler
**File:** `src/components/Testimonials.tsx`
**Issue:**
```typescript
useEffect(() => {
const timer = setInterval(() => {
setCurrentIndex(currentIndex + 1); // STALE CLOSURE!
}, 4000);
return () => clearInterval(timer);
}, []); // Empty dependency array captures initial value
```
The `currentIndex` is captured from the initial render (0). The interval always increments from 0 to 1, causing the carousel to jump back repeatedly.
**Symptom:** Testimonial carousel doesn't advance smoothly; always jumps to position 1
**Fix:** Use the functional update form of `setState`:
```typescript
useEffect(() => {
const timer = setInterval(() => {
setCurrentIndex(prev => (prev + 1) % testimonials.length); // Use prev!
}, 4000);
return () => clearInterval(timer);
}, []);
```
---
### Error 10: Using Index as Key
**File:** `src/components/Features.tsx`
**Issue:**
```typescript
{displayedFeatures.map((feature, index) => (
<div key={index}> // WRONG - using index as key
{/* ... */}
</div>
))}
```
Using index as key is problematic if the list reorders or filters.
**Symptom:** Visual glitches when filtering features; component state gets mixed up
**Fix:** Use unique identifier:
```typescript
{displayedFeatures.map((feature) => (
<div key={feature.id}> // Use feature.id
{/* ... */}
</div>
))}
```
---
## CATEGORY 4: TYPESCRIPT ERRORS (2 Errors)
### Error 11: Wrong API Response Type
**File:** `src/app/api/stats/route.ts` and `src/components/StatsGrid.tsx`
**Issue:**
API returns wrapped response:
```typescript
// src/app/api/stats/route.ts
return NextResponse.json({ data: stats }); // Wrapped in { data: ... }
```
But component expects unwrapped array:
```typescript
// src/components/StatsGrid.tsx
const data = await response.json();
setStats(data); // Expects data to be array, but gets { data: [...] }
```
Later:
```typescript
{stats.map((stat, index) => ( // stats is an object, not array!
```
**Symptom:** TypeError: `stats.map is not a function`
**Fix - Option 1:** Unwrap in component:
```typescript
const data = await response.json();
setStats(data.data); // Extract the array
```
**Fix - Option 2:** Change API response:
```typescript
return NextResponse.json(stats); // Return array directly
```
---
### Error 12: Missing Optional Chaining
**File:** `src/components/UserProfile.tsx`
**Issue:**
```typescript
{user.profile.avatar} // profile could be undefined!
```
The API might return a user without a `profile` object:
```typescript
const user: User = {
id: '1',
name: 'John',
email: 'john@example.com',
// profile is undefined!
};
```
**Symptom:** TypeError: `Cannot read property 'avatar' of undefined`
**Fix:** Use optional chaining:
```typescript
{user.profile?.avatar} // Safe access
```
Or add a null check:
```typescript
{user.profile && user.profile.avatar}
```
---
## CATEGORY 5: REACT PATTERN ERRORS (3 Errors)
### Error 13: Missing useEffect Cleanup
**File:** `src/app/dashboard/page.tsx`
**Issue:**
```typescript
useEffect(() => {
window.addEventListener('resize', handleResize);
// Missing cleanup!
// Should have: return () => window.removeEventListener('resize', handleResize);
}, []);
```
**Symptom:** Memory leak; listeners accumulate when component remounts; "Can't perform a React state update on an unmounted component" warnings
**Fix:** Add cleanup function:
```typescript
useEffect(() => {
function handleResize() {
setIsLoading(window.innerWidth < 768);
}
window.addEventListener('resize', handleResize);
// Add cleanup
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
```
---
### Error 14: Incorrect Context Provider Wrapping
**File:** `src/app/layout.tsx`
**Issue:**
```typescript
<body>
<Header /> {/* Header tries to use theme context but provider hasn't wrapped it yet! */}
<ThemeProvider>
<main>
{children}
</main>
<Footer />
</ThemeProvider>
</body>
```
The `Header` component is rendered BEFORE the `ThemeProvider`, so it can't access the theme context.
**Symptom:** Error in Header when trying to use `useTheme()`: "useTheme must be used within ThemeProvider"
**Fix:** Move provider to wrap everything:
```typescript
<body>
<ThemeProvider>
<Header />
<main>
{children}
</main>
<Footer />
</ThemeProvider>
</body>
```
---
### Error 15: Prop Drilling Mistake - Wrong Prop Name
**File:** `src/app/blog/page.tsx` and `src/components/BlogCard.tsx`
**Issue:**
BlogCard expects `post`:
```typescript
// src/components/BlogCard.tsx
export function BlogCard({ post }: { post: Post }) {
return (
<article>
<h3>{post?.title || 'Untitled'}</h3>
{/* ... */}
</article>
);
}
```
But blog page passes `blogPost`:
```typescript
// src/app/blog/page.tsx
{posts.map((post) => (
<BlogCard key={post.slug} blogPost={post} /> // WRONG prop name!
))}
```
**Symptom:** BlogCard displays "Untitled" and "Date unknown"; content is empty
**Fix:** Use correct prop name:
```typescript
{posts.map((post) => (
<BlogCard key={post.slug} post={post} /> // Correct
))}
```
---
## CATEGORY 6: STYLING ERRORS (3 Errors)
### Error 16: Custom CSS Classes Instead of Tailwind
**File:** `src/app/globals.css`
**Issue:** The stylesheet defines custom classes:
```css
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.card {
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
}
.btn-primary {
background-color: #3b82f6;
color: white;
/* ... more styles ... */
}
```
In a Tailwind project, these should be Tailwind classes, not custom CSS.
**Symptom:** Custom classes work but don't align with Tailwind approach; styling is scattered between CSS and inline styles
**Fix:** Replace with Tailwind classes or @apply directives:
```css
@layer components {
.container {
@apply max-w-5xl mx-auto px-4;
}
.card {
@apply bg-white rounded-lg shadow p-6;
}
.btn-primary {
@apply bg-blue-500 text-white px-6 py-3 rounded transition-colors hover:bg-blue-600;
}
}
```
---
### Error 17-18: Inline Styles Instead of Tailwind Classes
**Files:** Multiple components use inline `style={{}}` instead of Tailwind:
- `src/components/Header.tsx` - All styling is inline
- `src/components/Hero.tsx` - All styling is inline
- `src/components/Features.tsx` - All styling is inline
- `src/components/Testimonials.tsx` - All styling is inline
- `src/components/StatsGrid.tsx` - All styling is inline
- `src/components/BlogCard.tsx` - All styling is inline
- `src/components/UserProfile.tsx` - All styling is inline
- And more...
**Example Issue:**
```typescript
<header style={{
backgroundColor: '#ffffff',
borderBottom: '1px solid #e5e7eb',
padding: '1rem 0'
}}>
{/* ... */}
</header>
```
Should be:
```typescript
<header className="bg-white border-b border-gray-200 py-4">
{/* ... */}
</header>
```
**Symptom:** Project uses Tailwind but styling isn't using Tailwind; inconsistent; not maintainable
**Fix:** Convert all inline styles to Tailwind classes. Example conversions:
```
backgroundColor: '#ffffff' → bg-white
borderBottom: '1px solid #e5e7eb' → border-b border-gray-200
padding: '1rem 0' → py-4
fontSize: '1.5rem' → text-2xl
fontWeight: 'bold' → font-bold
display: 'flex' → flex
gap: '1rem' → gap-4
justifyContent: 'center' → justify-center
alignItems: 'center' → items-center
```
---
## Summary Table
| # | Category | Error | File | Severity |
|---|----------|-------|------|----------|
| 1 | Blocking | Missing `date-fns` | `lib/utils.ts` | CRITICAL |
| 2 | Blocking | Wrong import `next/navigations` | `components/Header.tsx` | CRITICAL |
| 3 | Blocking | Circular dependency | `lib/types.ts`, `lib/utils.ts` | CRITICAL |
| 4 | Next.js | Missing "use client" | `components/ThemeToggle.tsx` | CRITICAL |
| 5 | Next.js | Hooks in server component | `app/dashboard/page.tsx` | CRITICAL |
| 6 | Next.js | Wrong matcher pattern | `middleware.ts` | HIGH |
| 7 | Next.js | Metadata in client component | `app/blog/[slug]/page.tsx` | CRITICAL |
| 8 | Logic | Wrong filter condition | `data/posts.ts` | MEDIUM |
| 9 | Logic | Stale closure | `components/Testimonials.tsx` | MEDIUM |
| 10 | Logic | Index as key | `components/Features.tsx` | MEDIUM |
| 11 | TypeScript | API response type mismatch | `api/stats/route.ts`, `components/StatsGrid.tsx` | HIGH |
| 12 | TypeScript | Missing optional chaining | `components/UserProfile.tsx` | MEDIUM |
| 13 | React | Missing useEffect cleanup | `app/dashboard/page.tsx` | HIGH |
| 14 | React | Wrong provider wrapping | `app/layout.tsx` | CRITICAL |
| 15 | React | Wrong prop name | `app/blog/page.tsx`, `components/BlogCard.tsx` | MEDIUM |
| 16 | Styling | Custom CSS classes | `app/globals.css` | LOW |
| 17-18 | Styling | Inline styles (8+ components) | Multiple components | LOW |
---
## Debugging Strategy
1. **Start with blocking errors** (1-3) - These prevent the app from running at all
2. **Fix Next.js errors** (4-7) - These cause build-time failures
3. **Address logic errors** (8-10) - These cause runtime misbehavior
4. **Resolve TypeScript errors** (11-12) - These break specific features
5. **Fix React patterns** (13-15) - These cause memory leaks and state issues
6. **Clean up styling** (16-18) - These improve code quality and maintainability
---
## Testing Checklist
After fixing all errors:
- [ ] App builds without errors: `npm run build`
- [ ] Dev server runs: `npm run dev`
- [ ] Homepage loads with Hero, Features, Testimonials
- [ ] All navigation links work
- [ ] Blog page shows published posts (2 posts, not drafts)
- [ ] Blog post pages load correctly
- [ ] Dashboard page loads with stats and user profile
- [ ] Theme toggle works (if fixed)
- [ ] Carousel auto-advances smoothly
- [ ] No console errors
- [ ] No memory leaks in browser dev tools

View File

@@ -1,269 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 6 0 R /F6 11 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /ZapfDingbats /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/BaseFont /Symbol /Name /F6 /Subtype /Type1 /Type /Font
>>
endobj
12 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
17 0 obj
<<
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
18 0 obj
<<
/PageMode /UseNone /Pages 20 0 R /Type /Catalog
>>
endobj
19 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260225061542+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260225061542+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
20 0 obj
<<
/Count 10 /Kids [ 7 0 R 8 0 R 9 0 R 10 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R ] /Type /Pages
>>
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 897
>>
stream
Gau1,9iKe#&;KZQ'mgA57-NA<[fW81_h8`ZT"):j'#Go;YS1F>oj,\Z,p=.M3\\k#YuVt85.`="T_Pb<Ada-Q"HLbF0Fl2-Qm<<-9,Jr%6c>FUg(9r<LR8HYPS#kaX[F)P@g.PW==ONnE[u-pEZ3o"XPJSE8/A]=#B'GT\.EPn3>%E'MZ]+soIaDQiY]trn+@Ain'_$ej\lDn]pn]CPm<E+6ZNG`VF6e`b!fKoD\$/KI:bKr3GoV6W;d)dmsWR6reD#4=o0=n2j47-'!*8+g=?L@?rF(-/r.JM5/nE<-F7?$(N'DSl`r!D:\pQ%pWMZ](a6;R]?;gd+H1@*mDLKX*hAtT:l1so%:WjAE>sM30cZ0Re(DJ/f8.=CP_\prf%tIA!m*ta\:E#DaW]YP38+@@2&;qb)R@0mZA/@tJPNk/L4+lH1EOEj4"K+!M=8q-8eDTJZXU20]4Vtlk7tP2?s+9>/mAKg;jZLJbdhc\)R?RA!j<V$Qm+AaFrr:Z'QQHo4K\RUhguJF,/Pma1-*9A=cG<^i5dGld)>:(TNO)*;n,E5N.;tekkaTAi&$"cd9npNG'G<H9SAR<T3'h#f)?6.@,4La:;PEdgdWiN4Z\oKhn9rtp95U.'i4Y<8B3E.Q0=9W'`6R!Q1c_8U]!9U8+JE>L=dh*>uVblm8WN4I'3%4\X.u;<\1MDj]cl;?^cf.8AbL:8n/Sj?T\sb#!\)r036V4("i@>%3j@h_`sg[<eUkZ;6$qIBQhO,<d^F-&/QJFS,,?IX*s*F'Q*60PZ7I!JZ2VN&6ZKtE796HS8t1<fKF,oBhmo]1Mopdp0<s/je+#FYUs.L2s65IPc6W1XlH.1qGN&P4B,a6p$3Q%'N8(EG==6i4%JOWQD_OT#_hIQ\)dHm~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1081
>>
stream
GauI49lo#Z&;KZOMTu\.0[Y8],l&4r,4PllfiW7]JW#%JN"H[?8Toep^HEg7li\,/]2]5=P%m0arOb;lMF0XYm]WrS^h!`=7i]$hC]Rcq"kaa@D"(rMG:Rg[U_X"U.9W`?8D=>u*gmNP_)MsYRqG<eK.$rX+M!G]C%mP$(2cb6OG4sA.!dIDTh9"M[nd),c\'A1%CKf)816.1=X8\(Mf<r*?st<o3A;$L$&&od]bGY9&U4ejUS-"_RP^<U9Q6[K>Joq@KPaf!E0FmJH4+D'D&_+GXC*5?"Aah"X:[&.o*VAW&`s@kF$$9;gDG<^#2(53kO'oleY'j5=R3HG;OYD2p1c>tCCu7g,pF(EE9L%8_YQ]]-t+k<K%S2M32`G;^S_mDQD`=c50?[)lutMlm^!F2JR?4Sq!Q=loNQWgQ,;bQHU_J'Y5b6^A14-c]2^XI42Ir\A5H\5-7]T;o03G1TC%j)LXYINH;Qt(0YG7SJ_"+"A_GYCT\DSI>q$]@,#ebN5cbkr1q,&h9.Wa?97uq\I?%kbA&U:(qGu117,Q<4cW[uDpA9tuND]L^I\VN-O\m*Ji,26;R&V:;%9@POAj7pk-/M?[>NH%*F)C(l'KELk=gbKZF6fbr%Bk@DXCRAd?*VlR1B(YM7$%(mG"(4TPr%`#D-P/k?1rr`JL0<m<th$?g);LSg7Ok'c67rUgQl$j[GG_6b/r=N5!a@6P-6fsrrslu^lQL\\rqsAZ@LUa,#"-aj@4<gB^,&D>Qr<9X5Yp@k&kN(qkC-'"M_trG-l6t.\)-*(gIrYF9CP6pY#5;rk0"i"]Z$iM6iQ>W@eQ>/pOr6KVDBV7H1ErhuPj!;I2fS>Y:,Q-#kK3]*BKBGuG6#>>?I>1X0V%A,jd$!uFW4$Nlu61-ZrH"/5aKVnXXL?TXLeQE;6`HflF+0qdC9K,[kp&oVP,/#__7*n#/cHiM+:dh:871%tI^m8?T)f\k\s9G%@E/5n#*_W1^iqVZ(mCI,o@q@n\@O2on']RVN&Fp37uk%0Aa[JaW\heabTpZ\*9od.AdHangVFb/$NF^=]n?ZWNe*5mal@IQ4m_t%U~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 984
>>
stream
GauI4a\q.Q&;KY!MWP\Q)Bm5.'QY/u$-Q.r+eGYnjPi->UF`jYSNu`;dp<Zq:iIV]9%i$(qWn.2'#<`DeUP(33:Yt300i(tV]Z[Pi*SdrDr%lj1K&FaUSS)<2![+ne$R]p*IdAmVKD3<WhZD1JOsR?.tDon;m]%1[GkqiX*&+i?p*Qgc9%+29FkgantBJl>`p;2ihE!5\B)D;R+B`\RI)Z$4nOeR5T[b7pWmK=6Q"\P\ek='hQJ!mYQ+C2!K>_>nmXK&8,N2AcbManQeVnb4XNQeHj&aRBC!<Nn]G'+/[btO\dYX^0!?ZQLA!&[&SeU$0SUr:M]kkNZr;m(==6ZE"+2ZP/;N7BL"gWM`o%J<R:$&*N&!IfC$&]Q0Z7de):2H/@50Am9^Q`'9Tthp3BU\-a-aQWh$Kl&;i5oD!_]romfo't3kKnQqs&;*i[U=>H%A/qJq=SK#'pCeDC?XEEON1EJuFlY%a-!9?to36OG8a7&]A6ajp2Wi@9D/>_Dk\.*!BUk=.]C,1A@1f%3c_rcWI4fLIG%t+hdNF;aRE#D-df4Fl$0l$V^3`*DF%V3[K(*a^fr5!?L2SM5N6UJg.kFVGJ%$/J?O.P^'n\s2[fHk7Ej?b9A7.Do"3t8qi=?Oqicj<<e@h&ek@C6YQm,+28TqK%$BCGnASP_.%t*[!SG93MdFd.mH(JdT&t>gT4S[k@nfp^cKUMNWH$#T.0@t/h($\RC_]>jXQ<a\34V!G^.toKbDThdO@9Ia[(iqL:6WNFh?G(j`s"5duq39OujVgk:nDDNDI5+q`a,L)17np_%i+M-FiGlP9e?q.:qD8J"=>IF3`^F5!nbF)S_\VW?eMq-`(tSJn"24?8!U;g6a5Q%Ad,paLCmR;hFR7q,#4QQdXo715Im[e$:Eu@[nq+<qk#WOZ>pR8B.^/%#ECE7nAuoE0V'g3?=")I]^,rp"p-::En"MC3>HgGZ.8^H%?!us40R0$i~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1117
>>
stream
GauHIh/hR6&:`lHfR"j2)qC,5YIl^qP+-'HCZG,F(L\((0GZ%XiL7NqCE$q>#t&@Z.El13[?C:YTj8-/I<kQkC]LVZph@Tr$cj."-'OGJ_`/`c)t88fS?aga!b)`t/n3Nl?s+6M8I@"CA!'1"V_)U39gqUH==-=u*M:+&%Ku,&Fa%L5a".lA]iK0>KH^PWE.'`S0eaY:FMdQ>7ik82\e;ur5`-U4Uf`p;d2&'e-s-D0407`UaB.iU(rF3J*YN]eMqWGAXC7+5$XSBe^_9mTJ<AN$9oZQ9NETij\R!p^=**J']5slOF4stW\A<J#[JJCd/AH":r:L.1UEh%"6FK"*ZnA\`VkiEWmeISAOV*A71s%gWh@Q)c/51X<D2DM&d\6(&^muU>45;P`5@cG0qqXZtn1"D5.tguCs*4G&Bll:galDjcp]780&=d[iDL-:,U5Ta$VGVXPU<JY`hZcBUg=OK.mS%sVO-4X>%r^rkn<%N4<Jf&oHTS+QcrspN(?\YRXV0R!8!mMW[/WhB.185ECYpVM7^R71",%2Cj=*`R1cYjd#`e3mK+KD,]J=/]lCC/g=8km%9'WmQAsFg])cQbG"(>u8GGG[V?-j\<[d9Co2qqqFl(ZFj?8*3JYuh+)06H<teV1!cg2$V.QA.)Vk@#DahS6mp*H?CI?>iN><<\)^VK#^hgKImLSfEPGGEcU,P&;3F9+81(J<TKVK6H'>G$\/S7:f_6nUPYi[:WG_DEufZ(*,PE/?nX,r=Dm(q`j_M:e/G,Q)E%N<-h!XWPk3A1c\[iF8QNJEn%/hA8nYPHVoWkeec?Y(SrOB"GURV)C-BH9)E(SmQ=1*$5QQ53G!Oi"%LQ8bnuNc%hn7$m3h/=5UL_DTqq-Z9L^cF`'54D;'IEZg0PJ(FF;I(r@^XVM+Nfpm1ha$aUrVb7](?`8=L):>YHf$U(?I3`id.Y)1u;gC(36n"2i!#FI0Jj?t61`e/EbEZGLK;W+,SB=mlYk1R:o0jZVRLJ6>rYE,=-"Eh]#$OSc!q"Vfm5$lFo9<@&$/r2=DopLGH4ju58p$SttdWku>`_*'Y[!cY'k%aeMKiUMs^E+_db`R:-R%t7NTXqf#Z0OXAdj-'uI~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1037
>>
stream
Gau`R;/b/B&:WeDh"EGLC@boT8P%`@8(C43bM$d\,[O_0_'Q?Ba.]9CIMY@c]7FXlBi?8_5!*uGh=l>K6@]mFTApRB;[+=>-r:-$'IY=KZ?$jnSKAl`hAQ&Y(c-p(4i2P/L;u?"'=/qc_H=Z"$7'13a(O&;U]%GGNnZDu8P6iF#^Tl)9FpZem=j;B?A9>GhO*rdH#`d+0g77fldJuCh<V.,"F^L,pYk',q!)[__)pikcf!mTCGr\Q;3sT9RX%%W3.U?2ZGZBe?a`7W"%#"B=t!V8S8I7M3e<?pC8RKoq3l]s!Y<*Lk[r"16E06J\FE8DY`REQHS=MZK5ItoV1c)g29*_D,.Ek(.H*"BGEj$ld3cT@9GRVd[h\c0KKh+pqeH,G""0sc5UhY2ddUE9IcjU(*DD,"i1t41:`:S\]X+?4U]kg\/'%'@PX8K>+CJ,K7SE.&G2.rEbdH1Ii-0m_@S)*7U)J/E#;?meE,YGn-Xu=)6I/]r#70i38nH8,Ph&39Iir^>`-srpmr;n^<]qm+/dle`LIP)#<,"?q[q@MrmUbLEd@u0]9IR#bh@dosV[(6]c@^3T4i1MtJn,VrItEZ?i+gV'c=#;NZGG)1UKYPOW\m2P-B.Pe3)Qdgn^jbJO+R:+$&:(_2,"]V=-giYEJ@q/"-0R.!_7RcjNjX7Il8H],iB!`<ASfJ'AlC<X[U!f]AXuYRp$<!;ktHcN6GAAQ&CsO"nGB&<3A#tV-;WPdXbHIJZ3%J+b$"k`1hKHmO)l,h^c]nqg7j7KZlA5QH`qhLXn$+"C\dZ<Njc,U3L-L3,1F<U>=j8)nStAms1qg1^`:,D]9j\%A*@Zi*O)^]o3-pL&o[[pR5</S$+u&TnMS1&q#_"L5+O<)/(0c!e'E*NP%MinFeBt`>*FaG!A*X`$>UC\JN'X?Uj=]$T?j-3)*m>$ad/aLf,?mJ.)"Y5/ceRk9Q_qcL]n5N`Cd'3(h[I*7*?Dl!0s`>W;%#kZ$1@^.LNYMp$$OE/HhaO(ZHHX2UdRPnjoo!<Sg!b6LM(~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1231
>>
stream
GauHJ;/b2I&:W67\38A6:kkboMTT/`V+DFICM0?V!5HT5X!j7S<0EI=hsVu8ZL$4d"q6D$$RCeR]C51b-jCA-f).--JBJeT5;HUdiig-#&J.ar.e^T'XL".Kpl]][>S>%&md`m_8K''$8kC'.rOYaC8La)fPS>8MUAk,C[E%7F1QBjK8eUY"NtmV,551c#E-^^\$&$\+4nNZ7\IO`[+n@6NS6\X@F&>?8$SF'Y<c$HLN&cIanWf.II'fpm6\)7iC,N1NS\pEX&=+6+9)G,1FU;'=,W0*:o&M0ePIgSk7?Wo);C50sQqZ-$@5>B^'"H++^HlW-&r\V30@)OPD7S022$(Xn0(.&4TFJa<oB!ht]5LtC5mrmo??DQEU`s4)J/#'cC'WNJ,(<(^Eo;C07AoQVj2kTd%LYkX?:6+*FsEfXC&Hp'a:"BTInstNY@0?#20idj9^A=]Y+Pl!mPl63GTaW+h#g.Y-X$G3RH<er#WtG.`)hLN#`jf<p$Wr=<nf`>hsIF\NP5SM)"+YT8RAr2,mMPP-.?Fjl08>JF,b<\ZE2b2$`e&[g?Phn-X3kENK,G#;AC73T$i(9J+WY(pk)=[ds9L-C=qaUmHug?F3L%'WX@nb)7t7#WbPbRGWLOISlCuX#^k%]F(/$L>5N6$ogA`Y,SRo4EKcGE__dIP=?cluH9TC$<o"%?XhhU")YmWK-:@dp&8ZO'+YBjQ6rG&*+]HHB8U&*Mprj7G!Sq,8MIXS-J5]n"=X!nZ;>s%)$pp!:8Gn!-kao50rsOB?TT@]N?EUSPcuK#!RFTCVEF%^>GAn&P@X3,U41Tl6kBbOK]<KAPN>N);%A[D+S9KV^CboseC9Bk[*K^[EE"\iclkTqH2V)98,.F^9XZ.N%dbe$a4gs()O?+%^%SUh\b_3+[Qp`*(NUB?h/Ri4M(4484WV=5<j(Yk4mE;rQ#D`h'Ld24pHK27^SoXcSX*%GRFpo8dlQ27?kW2KIoONq>F`i/.9C$K>,9_.JDlc\->@Ni#>plT&Df?caoBdIV&,^qLOJV>g9%\Q9FR2FgZAt+#"i[>a36G[`EOBJHgPOd?rka+l[%>;:(sM#R]n"'D/E!I4i6#t:F8l#%GL#C6l)GIU`M3;?:B\T%:ch(3F?jhtm3o@&S`gAH;6="%-cedW!e/bm<=7akG<;6mUDus/:Is60Ur7Vm($,6V^YH\c9m*jgYdXG+qNReD8O*cC!8]r3_gaQ~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 630
>>
stream
Gat$u9lldX&A@Zcp6]BfMBmj=E_P3*mA+Sse!tfZN_ILo8c_`bo=JC]0m+IK(c/Mqp?fan]4+%q@GQ95!*mN_Ia*C_Lf_@p&J3j:V`=Gl0OKZ.VEu>UN""*r%[#AZm8KiIfiCrZOd*e'_r*m$<>JVJ#E/Es@[%Ra!AT`e/[k>,mOR1/WVF(d7cM[QTmTp3[eDH&c8mN@h5-[Sgj=t4hHU@6K"->pV(5kWC!3A;^U?_?Y&$h$qX5lOcOc^C=BldhU_q0/kL0<o&sSjYO)<]`Ke'JT5=WOLphCnWFn(nmA>UjJ->Db7.XO9E_QsbG#W@sXb[#O[<6$D3nmM]lnVP-/g/`E_>@k?.6H&KQE9b3uG-8+#0eRUmgE[Z/Fh<H:nFmn6/JLW$$,3h0331l>_N,Xhs)c^^U%!Dr_]1JMkdJ8<CiR#oclq52]YZ?a'XQoZY)WL@H2tEp"CT^]\;MR3UHs?1]bfimgc3i5_@Z>]GMbR<HVJ5f8ra^D@7519gM?2jhcS>Z/1'#R/.i<He3J[G3MMmKI`I50I+'hbKX+/E"1'k<(\#iO>,Q?4PiTA,;[\MIq>/'20Z*otSAKl!"lI$YI"n%^D7t=;0&IrCOu+[&:npC5i#d<_=A]~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 955
>>
stream
Gaua=92jS!&:j6F'g"Q4JIY#0j[ZgH80QFn-uh9/i]+deZ"5.A4SnBU2?lJpN8`]<4;0fWQ1XIHG-q`,5-4Si'7aR7%Xp*>*;ga`JdCj90^q/J*EHr:*jH^1na-qIGoW`)nAF4f%>8e(fF1<3pB7X-U5lCRfoB"R(q;_<\WhDJ,i%bFU?.#Be\3#IXS-dm=7;(<Q]5uJLXia0"M,9HkRuZ<3$%kB^GnHuf+&50UVZW]C,"KJrtbOGEfFG\kT*9_?uAQNF^4Ffn[ioN:8-C5<J6X@BZWrKa$_<g[HLbiX"0DnbutO<.Nk,)J\Y$WdG:ci@@AJ:g/$Y]NI!^?\X]cG#i`@a.#gem%SB+&b+sSW@-/"QE)c*nIX>`"<jOKUDe?[:DmOAGpIf+0X*i=9@XCl)/=1'WPg8i2OQ:Xa<,o@86*0\k^-`q[.<W?q$iDfEH+5Qnp0R!^R@JSV1!2^A4G%pu(%hO&`l[06pF5a\#5t0hiP]ZU"[OTBHIdk^@!VZm]VRk6i;n[9S1^/V83KHJUoQgsR!,Ur/u2Vq@glpY?n>Nh9feT$"hDl$cX`tlP[(Ar$=o,AB'gs:\arUE)Q/_7PJ,r%8D^9.A_i0aHX>Hor_k-kTi"s4E-+*fj5aPR4EkMoS6%HOZ!clam:S^^8>Ge1S+k@T,RW\YR%j7IlEBb&9qX_=5cs"Xf9gU5q'Zr.MBA)^KH7/e4?HVJ,s`)p?.S-^.$#mEEie(0o&;*aH&MANYP%83=TW33@CB)(*$1nY(@\4q8lK*fc&d,7\QP5[]S9'8/1+d:0/+IY/P9_5T[e?dT"['UOi6:cCb?HIDmrIfZh;=J"0$1CHdrQ$HCY_cH4Jfh6jT.54WlXO!S_"'G^Y&I6CEg]7=!^M0M'b<S5c%gs.p[Ql85K/`%u3BYtRls"+g!]oaoEsW,-\+1k$3H#*I=7G\`u!q$S^fd.d~>endstream
endobj
29 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1316
>>
stream
Gau`RgN)"=&:N^lHMt)0,aCY6NGcD=CqtdmX:qAqf%,/J/AqMih)pjjEk[!,G@<j<iFPXcdS+#nl'^f(O[6iScMnn,i6$ZN[1"eoCBZI<%'1>*XkluP/i>M=-lSqTR3jYeAtW;qlQJ9R#ZETDP7qPA<*+WIW\[WZ@T&C5L042%ge?8*Ceb2j,`43G"pb^>pZOG\:bCj!O\sCs,=]c_k:9s4VobdCjhpn)4So20gl80i([o/nK(V;`3(-b(.?S1u*XbEe=Y5-g8\)4d<,#5:EmT4$Umtr?n9i.jko/PLfEkosHiR7C$K,r:9g=VFiGN)GJTDDNp_"bbJ5Vn0\FA=ip@ajqjMnQ*\XfQO#KLn2OIST^_M-$Y*XlH%`A8;a%/hR_:`+g,Y'C"_NdmNPgr9'!&?3;%5rV$]30iCJ'i+&I!nS[V_3,&Kb5cDeA9`3YSOalKM>/=*,U1R1RXFI4`D#N,<acPp*f'3m#i&f.&)\@%?B3[Qf(O5.W2c,G='hP?PLp6EpY9qTg*:FDLB7G+*JHsYNFEg-gq^lrkCm>d0nA5;4$14[0j^tAEbUgZCa&/3\=8FtEg"p"%3/fsS##0[XtXuPF8C,BUDSU4oD0oa6LK4-4$.s7m^E!$1?mj>)5\UtU`=:=,YJ_68d95mh8)jrgGQ.^O"tPWn5&,u]417_J\P$2_%XQ+U>#+tiiJ??64G.'L4632;.^AF1B,%?H9;-'lL+4l"S9ak]f#_"brb2?`>4Pb]S[!lMQA=-!CO!N:L,]sMV:JkOm9+Pb&re[UEdO5qLDJ?6VLcqH3UG/0!Ih&PRa\]\^]jsGuRqE7n*;$O7L,KhJ@@TZK^,G`k$lYODXQ9K*BarPLe<no>E<hdo`Aokp10!cG4UH[Bf$>VQ&]AL5GC`gd3*;rpmsD#B4B5%6U]m0OD=\Qti;[NV?T?IFdAj[$btg.tTR139FL()t3BX]1QgsEn#Y>g4)l2`;Sm6<Mk:0L</E[EDg7?3`&G<CH#A4n(;6V7QEt+_M"ukqB\DA"/*$+_Y"h:)&@8*5oblWfK*i8?#,Yih-gP?&V%7+ctg+_XH%\FhmQ#`VG"F\=;FoSTU6L??GQLKMSIbC_f'Q?)gX1kMkj]0'Jm.[oJ;Dk<]-E_PdU`*/!7AT@Q$,H0j4Vu<<B62/HLk]!EOl>!(SpE5'dB-%Ad1ON']\+_YKWbAWE4)MFr"%+ABe/1k;fNI8pS$jB"JX0-<X?@RQAZ^"B\0O$OXKEUC`Jl$5BnF*K[:V\.c7-am,@]RpG?nTZqkEs`Sn=iLqX._T<^MiH=u)pHl^6gm78U\R.~>endstream
endobj
30 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 410
>>
stream
Gasaia\K`-'YNTZk1E,4[c8Vk8&A0(M?:1Fob(FeD3Y:Q<r>.AZ`"uVkUDhS]GY9m(oSJ;hK9=`BM@h3(l-t"#pQ&*\C9<4a',>D=laWGO\eFMd^M8]Y,=n*U1Qt"@7V)6$s.VO%(;r2:oO8JV0"o"o>+WI(@>pk#bZ5Ii_u,%Qb34i1nJ--38\i4&GcAiqPE4M&J9mqY0/ZH%-"QPQC5:Fe;7f24*X/NL^"4)^/At_V*Z(l]KY!>QO?0C2j#jH/^Oa'nUB`KoI!C\fJ<HcF6PWLCK\EZk8-'o?-9QDM/<&5Jm#8i7'mO-C&um$gfMUS+*%iu&B]U'.;!IXn?6hIhTmY1?'8cr8.AUPjnZenQmU?q28kCD#Z$Af7nh+U*7qg.bSot28bYY_c!5>tmi1FNN*-~>endstream
endobj
xref
0 31
0000000000 65535 f
0000000061 00000 n
0000000143 00000 n
0000000250 00000 n
0000000362 00000 n
0000000477 00000 n
0000000560 00000 n
0000000665 00000 n
0000000870 00000 n
0000001075 00000 n
0000001280 00000 n
0000001486 00000 n
0000001564 00000 n
0000001770 00000 n
0000001976 00000 n
0000002182 00000 n
0000002388 00000 n
0000002594 00000 n
0000002800 00000 n
0000002870 00000 n
0000003151 00000 n
0000003273 00000 n
0000004261 00000 n
0000005434 00000 n
0000006509 00000 n
0000007718 00000 n
0000008847 00000 n
0000010170 00000 n
0000010891 00000 n
0000011937 00000 n
0000013345 00000 n
trailer
<<
/ID
[<7a0979c2f1194bbadc3bbce6ebad8598><7a0979c2f1194bbadc3bbce6ebad8598>]
% ReportLab generated PDF document -- digest (opensource)
/Info 19 0 R
/Root 18 0 R
/Size 31
>>
startxref
13846
%%EOF

View File

@@ -0,0 +1,232 @@
# Les 3 — Docenttekst (Autocue)
**Fysieke les — letterlijk voorleesbaar. Slide-nummers tussen `[SLIDE N]`.**
> **Structuur:** ±1,5 uur theorie/demo → kwartier pauze → ±1,25 uur hands-on
>
> **Voorbereiding — accounts en tools:**
> - Cursor zelf geïnstalleerd en met Pro Student account ingelogd
> - Twee test-projecten klaar (één leeg, één met Hero al gebouwd als backup)
> - Browser tabs: cursor.com/students, cursor.com/download, Next.js docs
> - Terminal open in `~/web` voor de live demo
>
> **Voorbereiding — content:**
> - `.cursorrules` snippet copy-paste klaar
> - Hero-prompt copy-paste klaar
> - Cmd+K tweak-prompt copy-paste klaar
>
> **Voorbereiding — backup:**
> - Cursor Pro Student verificatie kan haperen — heb een tweede gedragslijn klaar als studenten niet kunnen activeren
> - Screenshots van Hero-resultaat als live demo niet werkt
>
> **Voor uitgebreide handleiding zie `Les03-Docenttekst.md` (normale versie).**
---
## BLOK 1 — Welkom + Wat is Cursor
`[SLIDE 1]`
Welkom bij Les 3. Vandaag installeren we Cursor — de AI-powered editor die we vanaf nu de hele opleiding gaan gebruiken. Aan het eind van vandaag heeft iedereen Cursor draaien, het Student Plan actief, en jullie eigen Next.js project waarin je drie kleine componenten hebt gebouwd.
`[SLIDE 2]`
Drie dingen die je vandaag leert: Cursor installeren plus activeren met het Student Plan. De drie hoofdsneltoetsen — Tab, Cmd+K, Cmd+L. En een werkend Next.js-project waarin je een Hero-component hebt gebouwd.
De werkwijze: ik demonstreer eerst. Jullie kijken mee. Na de pauze gaan jullie zelf bouwen in je eigen project, en ik loop rond om vragen te beantwoorden.
`[SLIDE 3]`
Wat is Cursor eigenlijk? Het is een fork van Visual Studio Code. Dat betekent: alles wat je in VS Code kent — file explorer, terminal, debugger, extensies — werkt hier ook. Het belangrijke verschil: AI zit ingebouwd, niet als plug-in.
Dat is een groot verschil met bijvoorbeeld GitHub Copilot, waarbij je een extensie installeert. In Cursor is de AI overal: in de file-completion, in inline edits, in de chat, in code-review. En je kunt kiezen welk model je gebruikt — Claude, GPT, Gemini, of zelfs een lokaal model.
Waarom relevant voor jullie? Cursor is industry-standard aan het worden. Vacatures noemen het. En het is gratis Pro voor studenten via NOVI e-mail. Dat is de eerste reden waarom we hiermee starten.
## BLOK 2 — Student Plan + Installatie
`[SLIDE 4]`
Het Student Plan. Wat krijg je: 500 fast requests per maand. De gratis tier krijgt er 50, dus dat is tien keer zoveel. Plus toegang tot premium modellen — Claude Opus, GPT-5, Gemini Pro. En tab-completion is altijd gratis, dus die telt niet mee.
Activeren: ga naar cursor.com slash students. Log in met je NOVI e-mailadres. Cursor stuurt je naar SheerID — dat is een externe verificatieservice — die bevestigt binnen één tot twee minuten dat je echt een NOVI-student bent. Daarna is je account upgraded.
*(Ik laat de pagina cursor.com/students zien.)*
`[SLIDE 5]`
Installeren. Vier stappen. Download van cursor.com — dat herkent je OS automatisch. Installeer als gewone app — sleep naar Applications of next-next-finish op Windows. Open Cursor, kies "Sign in" bovenaan rechts. En optioneel: importeer je VS Code-instellingen. Cursor vraagt dit bij de eerste start. Klik "ja" — dan blijven al je keybindings, je extensies, en je theme behouden.
`[SLIDE 6]`
Eerste login en check of het Student Plan actief is. Settings — Cmd-comma. Tab "General". Daar staat "Account". Status moet zijn: "Pro Student".
*(Ik laat dit zien in Cursor.)*
Als het "Free" zegt — geen paniek. Log opnieuw in via cursor.com slash students. Soms moet je Cursor één keer herstarten zodat de upgrade in de IDE wordt doorgegeven. Als het na drie pogingen nog niet werkt: er is een handmatige route via support, maar die hebben we vandaag niet nodig.
## BLOK 3 — Skills/Docs + Eerste project
`[SLIDE 7]`
Skills en Docs. Dit is een feature die Cursor onderscheidt van andere AI-editors. Je kunt Cursor framework-documentatie laten indexeren. Vervolgens kun je in chat zeggen "at-docs Next.js" en de AI weet exact wat de huidige Next.js docs zeggen — niet wat hij heeft geleerd in zijn training.
Setup: Settings, Features, Docs. Klik plus add new doc. Voeg toe: Next dot js, React, en Tailwind CSS. Cursor indexeert ze in één tot twee minuten.
Vanaf nu kun je dingen vragen als: "at-docs Tailwind hoe centreer ik een div?" en je krijgt het antwoord uit de officiële docs.
*(Ik open Settings en demonstreer Docs.)*
`[SLIDE 8]`
Eerste Next.js project aanmaken. Open een terminal in je web-folder. Type: npx create-next-app at latest mijn-eerste-cursor-app.
Beantwoord de prompts. TypeScript ja. ESLint ja. Tailwind ja. src dir ja. App Router ja. Import alias at-slash-asterisk — ja.
*(Ik draai dit live in de terminal.)*
Daarna cd in de folder, npm run dev, en je hebt localhost drie duizend draaiend. Dat is de Next.js startpagina. Standaard, maar werkend.
`[SLIDE 9]`
Open dit project in Cursor. File, Open Folder — kies mijn-eerste-cursor-app.
Wat je nu ziet: linker zijbalk is de file explorer, midden is de editor, onder is de terminal. Tot zover hetzelfde als VS Code.
Wat anders is: rechts is een AI-paneel. Je opent het met Cmd-L. Tab-completion is in Cursor slimmer dan in VS Code — hij kijkt mee met je hele bestand, niet alleen je laatste regel. En Cmd-K geeft je een AI-prompt direct in de editor.
Die drie verschillen zijn waar we het in de rest van de les over gaan hebben.
## BLOK 4 — De drie sneltoetsen + request-management
`[SLIDE 10]`
De drie sneltoetsen die je vandaag leert. Tab voor auto-completion. Cmd-K voor inline edit. Cmd-L voor chat. Onthoud deze drie — dat is 80 procent van wat je nodig hebt om Cursor productief te gebruiken.
Tab is gratis — kost geen requests. Cmd-K en Cmd-L kosten elk één request, dus daar moet je iets meer mee opletten.
`[SLIDE 11]`
Tab. Het simpelste, maar onderschat. Cursor analyseert je hele bestand plus je recente edits. Hij suggereert hele blokken code, niet alleen één woord.
Een mooi voorbeeld: typ een commentaar zoals "slash slash render team list". Cursor begrijpt: aha, je wilt een lijst van team-leden. Hij genereert de hele component eronder. Druk Tab om te accepteren. Esc om te negeren.
Wanneer gebruik je Tab actief? Voor boilerplate — imports, types, props. Voor herhalende patronen — als je tien velden definieert in een form, voorspelt Cursor het patroon. Voor voorspelbare code — CRUD operations, lijsten renderen.
*(Ik demonstreer Tab live: typ commentaar, krijg suggestie, druk Tab.)*
`[SLIDE 12]`
Cmd-K. Inline edit. Dit is je gerichte AI-actie op een specifiek stuk code.
Selecteer code — of zet je cursor op een regel. Druk Cmd-K. Er opent een input-balkje boven je code. Typ wat je wilt — bijvoorbeeld "converteer naar TypeScript". Cursor toont een diff direct in de editor. Tab om te accepteren, Esc om te rejecten.
Wanneer Cmd-K? Eén variabele hernoemen. Eén functie refactoren. TypeScript-types toevoegen. Een comment toevoegen. Korte, gerichte wijzigingen waar je precies wilt zien wat er verandert.
Kosten: één request per Cmd-K aanroep.
*(Ik demonstreer Cmd-K live op een kleine wijziging.)*
`[SLIDE 13]`
Cmd-L. Chat. Voor grotere taken en vragen.
Cmd-L opent het chat-paneel rechts. Typ je vraag of taak. Je kunt context geven met at-symbolen: at-file foo punt tsx voor één bestand, at-docs Next dot js voor framework-docs, at-web voor live web search.
Cursor antwoordt en kan voorstellen om code te wijzigen. Je krijgt diffs te zien voordat ze worden toegepast.
Wanneer Cmd-L? Vragen — "hoe werkt useEffect?". Grote taken — "bouw een hele Hero-component". Code-review op een functie. Een soort senior collega in een chat-venster.
Kosten: één request per nieuwe chat-conversatie. Vervolg-berichten in dezelfde context zijn gratis tot je een nieuwe chat start.
*(Ik demonstreer Cmd-L live met een vraag aan Cursor.)*
`[SLIDE 14]`
Request management. Vijfhonderd requests per maand klinkt veel, maar als je niet oplet zijn ze in een week op.
Tips. Combineer taken in één Cmd-L. In plaats van vijf losse vragen, vraag in één prompt: "maak een Hero met titel, ondertitel, twee CTA-knoppen, en responsive design". Eén request, één goede output.
Gebruik Tab voor alles wat eenvoudig is — kost niets en is vaak even snel als typen.
Cmd-K alleen voor gerichte, specifieke één-bestand-edits. Niet voor "maak iets nieuws" — dat is Cmd-L territory.
Anti-pattern: tien losse Cmd-K's voor één feature. Dat zijn tien requests voor wat één Cmd-L had moeten zijn.
## BLOK 5 — `.cursorrules`
`[SLIDE 15]`
Even kort over dot-cursorrules — kleine letter. Dit is een bestand in de root van je project. Cursor leest het bij elke AI-prompt. Het vertelt: "in dit project doen we het zo".
Bijvoorbeeld: "gebruik TypeScript strict mode". "Tailwind voor styling, geen CSS modules". "Server components by default, use-client alleen waar nodig". Imports met at-slash alias.
Het effect: je hoeft niet elke keer "vergeet niet TypeScript te gebruiken" in je chat te typen. Cursor weet het al.
Hoe maak je het? Vraag het Cursor zelf. Open een chat met Cmd-L: "Maak een dot-cursorrules voor dit Next dot js project". Kost één request, en je hebt een degelijke basis. Daarna pas je het naar smaak aan.
*(Ik laat een voorbeeld zien.)*
Belangrijk om te weten: in Les 14 — veel later — gaan we naar de nieuwere variant: een map dot-cursor-slash-rules met meerdere md-bestanden. Dat is hetzelfde idee maar uitgebreider. Voor nu is dot-cursorrules in de root prima.
## BLOK 6 — Hands-on
`[SLIDE 16]`
Goed. Tot zover de theorie. Drie taken die jullie na de pauze gaan doen.
Taak één: bouw een Hero-component via Cmd-L. Titel, ondertitel, twee CTA-knoppen, gradient achtergrond, responsive.
Taak twee: tweak de styling via Cmd-K. Pak de twee knoppen, en verander kleur of spacing met een Cmd-K prompt.
Taak drie: kies één extra feature. Een FeatureCards-sectie, een ContactForm, of een Footer. Vrije keuze.
Daarna: git init, git add, git commit. Je eerste commit met Cursor-code.
Ik demonstreer nu taak één en taak twee, dan gaan we pauze houden, dan jullie aan de slag.
`[SLIDE 17]`
Taak één live. Ik open mijn Hero-component prompt en plak hem in Cmd-L.
*(Ik plak en lees voor:)*
"Bouw een Hero-component in app slash page punt tsx. Specs: gradient achtergrond in blauw, grote titel plus ondertitel, twee CTA-knoppen met de tekst 'Aan de slag' en 'Meer info', responsive met Tailwind, geen custom CSS."
Cursor genereert een diff. Ik bekijk hem — controleer of het responsive is, of de Tailwind klassen kloppen, of de structuur logisch is. Accept. Save. Naar browser, refresh.
*(Ik refresh de browser en toon de Hero.)*
Daar staat hij. Eén prompt, één goede Hero. Eén request gebruikt.
`[SLIDE 18]`
Taak twee live. Ik selecteer de twee knoppen in mijn code. Cmd-K. Input-balkje opent boven mijn selectie.
Ik typ: "verander primaire knop naar paars gradient, secundaire naar transparant met paarse rand".
Cursor toont een diff direct in de editor. Tab om te accepteren. Save. Naar browser, refresh.
*(Ik toon het resultaat.)*
Zelfde Hero, andere kleuren. Tweede request gebruikt.
`[SLIDE 19]`
Nu jullie. Open je eigen Next.js project in Cursor. Volg de stappen op de Lesopdracht-PDF. Drie taken: Hero, Cmd-K tweak, en een extra feature naar keuze.
Vijfenzeventig minuten. Vragen — gewoon stellen. Ik loop rond.
## BLOK 7 — Afronding
`[SLIDE 20]`
Voor we afsluiten. Wat heb je vandaag geleerd?
Cursor is een VS Code-fork met AI ingebouwd. Je hebt Pro Student actief. Drie sneltoetsen — Tab gratis, Cmd-K één request, Cmd-L één request. En je hebt drie kleine componenten gebouwd met requests onder de tien.
Volgende les is Les 4 — TypeScript Fundamentals. Strict types, generics, utility types. We gaan ze toepassen in je Next.js project.
En veel later — Les 14 — duiken we diep in Cursor 3. De Editor Window versus de Agents Window. Agent mode en background agents. Vercel deployment met preview URLs.
Vandaag was de basis. Vanaf nu zie je deze sneltoetsen elke les terug. Tot volgende week.

View File

@@ -0,0 +1,187 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 7 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
8 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/PageMode /UseNone /Pages 15 0 R /Type /Catalog
>>
endobj
14 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260615075148+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260615075148+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 3 Autocue) /Trapped /False
>>
endobj
15 0 obj
<<
/Count 6 /Kids [ 6 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages
>>
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2079
>>
stream
Gb"/'968QQ&AI=/oOY'3fS\Oo3U@LmW+AQ*:iX<[6WIZrD9J)"g<h;Q%f>#TRuub::;B!/M)^>KekC9`pX`dHa<;KEa8D!"7#r^;qTL,'V9+Z9hstK%:/<5bs.24;bg<c7Yrd,Bk_@Y:Y^NUjA'5>;Tc]BXNGNP"Ou;t*&'=VRA3(Ye+\c!6iK?"F.U8aAGKpqKFQpA(3YO4a8o[l\]'MS[Q&'4i]Q]D`9:FYf[_0WC-@9QYa.>Gj^/jM9<CXBu<EjA</*GE/p7Y)=A'/590dC>`+S0]CdBsN@B<J+/,V?UKdt@rp]iq\B8C^_Z"W?W$T6[R&hEMh>cd/32bBN?+:1qnaFWWp%mN]Z1[eoi:b4Dt_'hr`rag%R":WnrR!j#Rf5=?&Pit2JU#%K.l4?`1(:@l/7\R)cd"r(t\#Z&/j$ER\"Dh[+n,9?M5f&Dk%&ri3#M6/)C\-.%m[hG51[]qWc"hhTF`Q<%',R8\$`qFY1rE<^_$,SF"%>4_P6H^EeD&cq+R:c1hCu_r.>m@RR^DaSRi"]\@\)2do$RP+@e[9eW7^:Fj"%5Jrkm)lsU_YLlb,5QL6jEk0Z_rBKV&P)[aCq2B[Z7.U=]--f+8P6S2jUt5.>t,qWduQ.5kb&_U^;I!08VXB^VO%Xb('FHR"M+4PJA/!]3l,^I5$H8<]]Y+QuVB+*,;.T95H3EcG1FfQp8CE#D(QL^PM#2@'M.R:6W&P2]NXQZ)A%1&K"1JO]R<'+4-3T<^P5)c=\us9.q+tQ&9SnU.k3r'e=SV"^S&f!HE<M4)"8ZNH6gM+DS2o-H25gPYs'k1+^alYUYGMJfXekJ9cFsVsRS:plhBpb$i(beE[7H'htG_X38\5&H#XfFbLf%0\S['c*=P/MT+kfC5f4i[.@iQejV3cUe`Z>]s<q=,pKM!SLdiV!Hk,iK`LJiJ#@(R?C=DN$*$C>c(st0pd>LAn88oa[XV2Lq8;#Vo>g`b(:[T5\CZRl9:'>%[C[`nQ?6brVX<"])?rDSd<+"2onaHB!a&M_V:V!_+B$d(4g(_5d]s#6@o7[lPYn9ec&dLu!M)3NntsGHe3@(^%pk?$F-PK=qfpWe,POr1lT7>3fD/Df^)FBg_1&7.m*bkjbB0&i&UG>;U&qGf$#)gQBu*_W?"?b3'&GlrcA@MB'q4Kl%VT0e\JiqT'Ub'57=k=a<Zk["EBk>FKX>J;:qEg'b@H@gWm4TY.3mo(TmK8]RUr@VLVGd^g[<,jI^T#re*;E3<pkF<2#=1H(&A</!c0p^]M4-AJFjROW4"Y;3Lj9Rj9g<.+lk,i*e@%U+><1(*q4&;(OC#G%g-\=bnKHkB#=;6YTKM;r;r$S3kj]<'.\AoDilVceY_r):Y>(`gp?280)#!Eg`D<CG=Ulem:5cFXEK\LmI$k9P0@\FMr#SD;)Ccjf6"0a7Fp8!QuNF!roj0N'HdZN>FVE\GBsNWk(.!9)&9QN[,?>T?_Zm0164[f)E`/2EFI6>c%+^k:6>[JU97]6(<eT0*%\$;X>9l6(o:L0c'jYi?ff\t+P5*7a0WKt\<h?0__=jK(j)<agJH?;/.)kiK)DEV@Mi3$)Ltg:MM>*H`N&7!Dgr_2NJ6\IGKekf@dXYc_U^:n5LZ+POh_0De<[c;\3?jiLRk>e+].<D[8U0!3TF'<>BJG@ZJ311[VRl>"We.0e6<259<*Sb-!4/Q[@9S55++=1\XZmrZnV/KQQJq[D:AcF;:DjF3ST4gdqgr]^FjiX%`Nh8X%cZBkZ'MVYLDT'IRn0b$7[#$kXnr,6%\`1K>oHJB#]UBdE>'n9(P6a,Y!\oF+i&/2i"DEMr>,b>Ak@?6Wl$08Q*?0aQ@7p$G(drRW;B+2>A(U3lV9ipN)XE"F`jqZMa3]n>fDDFDP72&E>oSDLA8;6iI*oQj4NS1EePA/8rt:`^s0_2APjP,RD1'"AB\58tS+7_n?,iPkI*7roV'kU[.tc#/.>WeVrCP\WAB>n9B!imN\UXH9Ts<!PN0]ddC,7BrYj??Xa>(=iSZ7,CAQNUV`88G`h,!6*Vi8fL+%1\A-pll,F0(3?P%%Oen9A!=#=BEW~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2260
>>
stream
Gatm;?$G&5&Ui!/R%l$I.l$n/m$Blg36Ab$ZVE$AjItf?6pN"C&E&/5PkD6K*KZHNe4uF/EGWf23#LG]F6@"#6[XmQca4hkAOS90NbC8HeV6QBA4iosoOddUNu+R7ja<TBYJsWg\f<>c`G@NQF8.^4;tJmZ0+_Z9ME=,&U'WksJo\qj)\GVBUm._"3u;S6I$NjJ:n[rmolfeo?Ro\5^!krlH;1SKWJq:63XF]LJ@6pkY.b$M/$2@s.L9OKnfa]29<W,<3KIHpU=GQ1J"[7Lkg_7FCaI:_ee?omBVPd2.nZRgG.,_8CMU:GFK%XXDqChr#k<'&U`+KcfBSIE)p;s^C)^iaeNM>@.'oZMCWpKsXN8oFf6S7so6>XSYDoKWlI3?U/,W4t[^o7P6he`>)GH+E$"98^aY=@ln997C;2b83]LshGMl1;E(ac$"BJR8qeU]>:!F8cg=%;Q\Nl?.6D/G545G')&&@/Hjs)S1aXV>*)&=.@dmbk`W2PH0U'qt?$,3r4%ieJ+"??@gB0^bBT&J4VfGKkkB!'h<RV7dZ:qq]:s;M.;LP>KFBajp,c:e4SAmS2OC7CcI5:^Sq17t`Mac)8Zo3hJ-S:G3P<:(%orq=ntkUL^YI:9K->eV*u$#MenTm;^'m6fWps^V%>YNQ)D>QH_/KK)p4H"_f36!JTg#oPjdXVe9a.D3C8KDSLZRhr!;'U$9>kq`_@^Yff5Vo_G[=p;9fUoEk3FH+hJL3S-),%V[/fJ1COqX"%(J.Sg5h,Z/;5:.#F4igZhIiRMbs-h*1-_ZbF&Ba6\h>4*G+Nm>J,=;@@KK['!J;!HAFFjY;s_.*uoP4$K(MHr60*W)!K)-IosqLM=&<#&hnhT@b*2D<-K:aV:S3'YL:JV:AWoET>aPm.\';tcY85o.^1Bl)?'8Q01gPolKq9'Et3=NX`\+\iR%91UmUEpRj0;#@bU".g"8bQ7D(9`pqK$TT`BLMe:H^!S:X\5IdZ;q3K(.VlF^Q$((bD_:!6R>q!r`q"cb)Afp8h"i"Z76\u6BlDj`o,Y2n5sQAk'SNrI'>Xbp9cYa)?(rZDaU[6)YJ-p&Arj4sHuZbC,EOK:</O?oP;DteO0I(rBUX>JWC&S1M]Zj(=bkS"Nk4U6J!+iS=u#97p=u>9].Ec])gAO+#tYsW%s`D/9Wn;RX+tHG/P4I.(#1rdSfC01'I.&3?'_IB"1PL#';`R'Z6B(FZ8@\]c;m%s/VlBB5;q_5&DrcF#m8?k5j^WYf^cZ#1V9C<;J/,`HcNfR&;T<)HngYVBalMr4PW%4Z&3#K9&9s`!uFh!%V#^ri[c1<YUQd]CdfI"g1H:5gJ*#J"F(GU_>`T5!/lIZU#1gc4e@PEXlpc[Za;W077\K6J3A\a'-/@'"Z2"HUpWM`91;blE_KcG8+)an?$]o28a*q2\p4P3:$/!KpU")6Ggg&4*86?IRcZ*UERjZa-4OJc!jmM*,d^Mo0N,%,(QtW-&.,JWRX4FeAOkBgA;&,Lc@XMleYid<>m;.7$DR\bh<QYg[`=-!h6bc7ilUP6+F[X/FGODC)W$RVPH7H45;ZeB'f#-ioh+o8<O:9$Aus+N^o6!FPJrf3>ou$fG&Q9*6VCLfG_30E8P['n#6RbF<fGVVdCka'f!YY;3csL+RnnD7$^mt&UB]OubT!9-4XO"IbX#UQQ=b,#T6<=^Peo<TMftcTE$;;U),-pF8;bHelB46CcR-WOR?joH3]_bV4,G;&`kHRnB,sNompn-n5OUeVZe9'/s2BC]>5,SV``+U0>["sk[Z+:5[oI/+OS35U.'*A:/hT6,`Fs8EAY3U;gROC[Jrb`uf-e29;=T)J9@ukDZSGiSPm3.kFpWVMDiFk5$p'FTIr"M"?dX#V7=c'4G6@*X)Y]f<rWiB?@\69cV&be>7*^=4'o-5So:6M\9C=4*APWF=;:4iooOM2!3;If?I6^cY97;B,iS"r,+g8:aR^UO=%+#3;[>p4,`ApBur7`QkDgBDrhOi>WJeo8j[L6Y:?SV-K,TLQ(Qe*T^maKpf&pVq@M'jI3Cu'f0)!;,f&"nQ9B/>#XO,<TR)gLKe$:)P/[t95q!,Hno>b4]1EP/Zu)CWoso9l40Sm;=5G9[&C!.5J]^o#UO#098q?kBV'hlH'!$Vk?WSY?h*nuRS]LFFeIro[TLn(ZS/9-*Hf#=tM`>+a=S/9)W]EfEF^N/A>6EmeBo;L`1e3HeZ*#oq&l^o90rM$<?']NQ$es+oG]^6T^anSb^c>6Y~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2164
>>
stream
Gatm<d<=8&&q/)-bfNep/$U9EbE?4Yg/p@0j3sO#[I(kD+!>SL5p?=FK^;*6h`Cg,`Jr@.g5R=],!c5'cCI$!V)6%b)Lgiad18nHp.\.n7a!ZcP@R#')d4e"r;ImW7o$C$3mZq;?"R4p],493*,[,A`W'iI!gBG9]]kB@<Le.F-^?,f/_DqPl"C"ZP?gP`ZTHGq.:k!:B9U^>RLLuXSmA1P.S1T51#;G9\@,tb/tqC)R]jf^+";E84:_mu)lgIrjf-1P`d^A!*A.:WY:@4A6Op.,o7Lh[PC^j?4tusD5EF`mc:?3rq#`/-n#^^9'eD&>g,F$7\%0tqVk$FnEI4HTI(N@h][s"d*u^JETj;pC4^e4t&"k.pHs>.PJZr%QV?<gqYUNSY*@ejH]%PPf/A%/:4`Fa$MWZ8%]Qa_R_lL7n.\'BTqZZ`jN[8"^HjX6OiL:m8Qop/iCSdOuPS``8;'lANU`t#E96UKtTV=$AYY1IdEbcVTSSP+^TT9N@!t;h6kSc5)ND.mfZm30Pb5#4XJ1RBuY=sMTMZHiQ",XV&4Pp6Z^DpuK+$YGUTA,jEf*e&I\&UP)aoAWkT4e\%-rE,R.G0%h1+8hT]&Z0$eMi'H.(HtQL7XoT(]BTn'c$Y$THV275Y')u$<f95m-CTo.ol#lGcn;iifA4rSLC;^70*\a*WrI^7;E$%][j'LQIaO4hQcCRY[r=FEfEqrD!9JTH>O&X"#HG?JN;#i$<,%V)f5&%*9UNZDhD\Os)qKp_i4Qn`+0H$&'90un9S@T_]/-6eM)atQ0bmfd(?M[;sL@ap-P/W-184g@YhL>LG82j1+A!86X#&l'l4[J6f*BDE)L`#\Pak0T'pnK)dA]6Q$9TD?AGF5Y$4X4"LAgBCIQjoV@U54g!^[c/T)\sJ1rqX<Wdpc!?&J0q&RP84*!SZG:L6YTmDh*D,A9k@ofO=s*Lr5q(rsbVAHPi`s5:.h#II3bq:@[P`07MnJndqA,5j&[V<[GKg]'BeeY'Np[8)D`:1?lfbc?TA`!LM5Cr%m"$d;GA^lGbBVu)gna+(%T42?qW/$R27ajO/IB>gu!k?$YM!ls@p]`H`k<jRB*Qg:>K=@I!9%&STgsZJi&k*\Ahh:<dM"@acNVCG2m6U%;Z!BPT?c:qZ)i:c\P+eVEqprGsTf95Kg$4ff(fq\tYB^RA6JANr619.LCj\6g#&D'ZG.M\9j%hLVWL=btlrG^_M&8-&'/DsZ3-akHnO4q2DY*qJ(;;9?<7tq*n;9]:jNO4jMA=in5E$DlPk!?<9RB<LS3<([KRO'd7TVI6i2H#]6Fgg)AD2PY!R"8u#kH=(8p(FAG@Ec\SZ"imi((IBpF(gAL0]$b5$HZ'j?bjhr1Q-CitFK(*b8oM^%2+P[;1=N.r0:KQ5.PAECKk**D.,G9e8dO3Edl101/6&n[IOTPWc&SKt["040DVsEI(]]i2`suKTh!?46INBP#490NA#!k:Tn<";X[fLQH=k-(NSd@Iuq'PBe*B8-2js.'P/)tK\"Kjp;&#9StKHP3S.pWA<'*Ba/c,W7q#94RrH+I\`<&@j9j<d*Dbg+dGL0S)rOJ'+gf=f<H+)hkZ;c/PsmLiC,mrdjOW:'d0q9IN'Hg&)VPON\*UQ'PUKA1]G"?*6O;40]N>N!(t?o1HE2YL)O_=CFZe<YXfDXl!Y#dYT)pI<2,#T6)qI+\=XhBW]lD$C^\mu?'0CL[YpX=kh[nLL9bOLUPc)38qQCAn>[_V*_?gq"&La[UXQT]PU!X='b\Y')I'D-uYN&J@ru3LAlX=dI^[uufHO=lq#Lq(Xj0]B9DC9#=Bsalk4_g'%4?gge&^L&Qo:?j2:@`j.FSactR7tZS/-tqbWLeHnhcXH49ugA.iX+bSfsZYF0$rT`\@0eNqqBO)5El6T*aPHe$ls<&,\R.4RF9Z6nZ01b9(+fRUmJVLkUFEhN7d.c]7[HeH"9QQ\^9[8UHS5%Xj18iFKRJ\],%Wm.Bch,Unc<n9&[m^!eK7V,*nl`jg83JmD<XtPm0'*h-1spOGr3WZt"aET'7'r^B,4!B+Z52m'@?oRtdQmIKu?0%X+#Jf=RftR3":sVn\G!C`lBp)PV<&**Y8d)DMQ]0AhD[=V0h.qP8Lb(E09R0=rX4:ZTXlB$Tr`LMM5+#Jj5m_Z~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2097
>>
stream
Gatm<>uM]_&q0MXkgIX#.C@L*4oi?nCeS;IP171DHlq?IAYa07,HYS/1F"@<lZ7Aa,sGe$,m47&[.D[nqXj#iEpDVKa5>?'dG$C(o3G+:6/rjEK5<g5/%QS4qYVg]<fW&RFB4pnFOD7BVo.?kqsZQq."/`'.ia]ocG^**S2mCXQd1g/O$4RiY,LEh2*O!38*B3Dqgg61A[40J)d67C%F\'*DtRA)H1pL'Y@tSEn^h6sJ+<Au;>KH4N9WValBgh;bBFbepO@%";6T_QMt2W>M6g8$SM*s`kbGPGk0=0S9L5f=3ki'uc.QWWDocVAb2`ElBhNt9Rm7>k1keNW=rHI[*)@BeB/I?%BushHRZj;Y7^^D&#H$#U];F-^r]SVu[kN/g1N7r\=\ikQk-_jil&,4a*6kR]>4r."4)Mt0:]4]S5DCNOK/2PnCb"i%dnb"'B`fr8>!$e?brb5lnTeBLj%4P4\KgQ*.lUb@JZ^#GT7Ut@4K_[2lu7@j,\ei@U+niN"o$-AJPV-,mWjbQ7c^(GcJb4`c+H0OeXSO`.aTa_j"E:qd":@>f?/k,:A"fEE9dJGR!h#%I7kY,$gCSeS5<j,"^e_G[./MuS+/=_gWAZ+Tl*6QZuBJ5]9pGCY*e(PZe_Tdf@GI`SV%;?k<Pp1GcQ,!chZ869S;Jd0(?j_O+:m^%@F%/JBemD',O-0JoC_D[/ac_;S3!o%i3cIbBHX&r-8CM;@TR`k08pM77`.b40P@h+5(LJ2=F;mBk.jXL@HSIE]!qFp3C_[QNl]8/RX0,r@V0id<hc)ZGX)D^i7q`!ma^*X-?nQh42Ci;P6;KPQ\+aH2+IenUr_0>"J8S72bU0-F@Q"_#6,=Mb*-pCgg&[Y2"fha4RSH6&A41%C0FNd6*_U+:=AMa[GV"W*OBVAn6)EmifV2jqUq(#jU=o'^k\#]Z<Tf[\KV&Y+b.DG!45-f2LtOYWI:m"mXN4>q#qi[j#WRJL]tK2q@0&+i8l6[6psLf@0g4X2l%gJA$HGBS#Y$kTFh5<1CW)d%L"#S*nGO\sDqj;U%Qh][3imYNH+QF7C@\O71mg[uB+JRo"%WABNh5RIZ._ht+d'oSUYgWB^e'j_:W%P&0N_eA0'O;-ZCS+s4HWV$0_I7r"n[.(7![E)X4qq1IZQC6gn4Z*gPk.,@IfNlfm8eMGH#^;I>4PiXNa>2"P"K[b!K$?>\eb*;???_rRp_apN6)TL(e@,4Nb#Q&/9R]@eUHt.at%hin,6p!qR9rs>Gs2R?A4(bS2`^-idW?tF$SjT6mjV$*6q*>Bgdb8`OQd;tNNIrUV=ar+DMM#8B0IRD="G'Ei1)Np?5OJ-iJAkYu^A\8V.,e#kA7edd4PjECr.0SO1r!VNW7YtiWrCSdd%^9MNdi>._\StPS=[1"k_YC.804n/?*L9BdP?"7q:T:dRO95tdPK63*D%_Pck&=)B6"bf=S*()%P_1^c,lD8LWr^8cmTt;51*$_$Mj8TZC@XCKA]<UE6pGh5rf[(2+PJ@gk^h?h=du<Zli!r*5)?@[sBJ+StJ'.Y\(Tc/SNE%eEL/j$D]ln*ni>^CM0_j[F9XNo%o-@<4DZX6iqP(?XLu;!EGFlUVgMa^uE`%H(*Lgb*gs_:hOm.h.`]s`+W+_-T0=SkT`j5`$U6kqpD*8IY!2Vq)6k>K7AmjWaZK[S.*"X8eO"A?0uD64hBHP;X>+TQ'BE$g]\86kP$Yu#M^HFVB\K42EdP:`_k=]'\^\&HONL];,o3<c-M7`&E,e+pF+\;8A<a3UNd.W)q1T=c04>Zms==;Q)JlacB%j[r]%Y:;aBmU'CHBK%Ik$El(e:67)pc!D479gZG`tGkp8$U.'L>JZ>rDH#,&tZ1.!**]g2nS#.g"!4Sao$=Sa/oQctgkEGB:>qW:3sEU+EJc=!f>R:B]2aZM#I,fam-/@tuY=;CL'g.tfWED,c`d[rm?,$$!KPCu<pVOYZUqDrr/<qRVNG@=3KfM0a.$EV)C;Ds.7NEcgghk`r+O>Kmq/t#WG][9##K(4\5rlJS,=T!m-dP^dK[s!)"g-de?AuC:@kq[R*(7uEC=rA>,Cu<-[hmMU/M=g'Dnlnn=~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1919
>>
stream
Gatm<?!#cM&:Mm.R)9;'O&7M)"65^h!hlHa8UB_jcY;X]>$B*bb?AJ$"bC%<4"JuD`$@)j"sJ=p]/-efhO/@SD7i2bNL*%Oa7Ssl87=pH.>?TN9B.OK2h(Gq<ERT67m[/#3XP&\dglYrFr/J53/FO02<cVB'!Bj*$;7JRTs.h)aXb+)B689Arf4kp^MddJVt!Qqae*fpgF+'.YYk2g7_`]+">n\i*qZQkPXogNWK!&AS`<=;/7_K^mcJ)HiEn3^;Ba0f8Z<7<dkVG_ChA1D-m%>TX.<`D(3J$iL&@8JBqid4P-F:3C%P9(Q\E^rmc[[X3a3q$=)^blRC,TKM);BHGBZs5g%b2QOmAa'H&GQFi*5VMp:hC5bXn!:4Ot]$/_7>p<]4s%fA5%E[Vs5gg`snkh.J35f1s!ikYj@u*EbIT0];:#4%bE)QA5$rCa!8:GUXX<C@.[$L^Jcr\L(YM"QO>o-%OC)(/!<$"!m]])*q1M>\O2GGAuN8H?1E,@ZRULSWYRS`+M,T,'FdV/6hHYIs.MCN[^W:UZRAc"<*ahZO^']KeU%S;Q:_9>e-08'1l*X!/!r+!r?3>5\$@d5Oett<Z8"JE1/U=Ze^)@R?n>D,uEn9m%CuoKf?Q[BQ5Q,[3u'.4cSo2Un6g+@8ubfc$<*+i%YmZ"7pklBRf,*DP2BjINuj\#FgcOadD.Bi+Z#FQ>6DYZrE:Kn.cf^S]N0c:lA$M>J%N:W;!^%2ghWS=6uMM0^2VR1VA"^@,GQul9nj/FR\uASV?7$-l<P>#2H!S:Pk.'r=[JUOKfh/V3NqaQ(OOVU:b!t"bXo)o3.W@XL-k"CelD/5In"Y%Sn0dH,u[>r.?/rnu4h.'Q=$$=K(R]6.OT!V<h8g>,R^/2Au`(5E9?bhag#(dJ-<;.$_>RDL,SXC7S*_n0W1LjqY%:U?EHbOZT<op'@BOK*rV>.I3fAcEI.tndB6<i$,5M_V3=N*qZ+a?&@X(ku?>\L,B#nNFRf-5U4,7rr@nNM:$NJ)@k[>43eap]A)(/YdT+phMViFp\-_F[6J%'?dJrCh9IY$ZUNda-6XARe/V&Z;ML)'XUVC';)HfmXX4q]\WhVT.OV)mM^Fk")t'G,ccG1?JP:5^p.aM;L22LM1.-5NFg\_VM(lQTEr%o-.TnCqU3&m=OY)Zo=^<&J\(d1h<f<q(BsD`l8+q\nm_0Od_7EMoN*6R".dKs_ip[=\lrpo&0J5!L`)#saW*+/j<Oc(Dh9VG+$Q_-ETYF!'YJl,CnL,Be(kdqPO0aq)EZ.Bo=A0!h#'grThGB\h?]''=].e">`BBW))(RM?=X-O_U-4\;h_L\P<,Ia^G$.hd*0;nJMnp3+\'t<uHFo@Fe;]L_kp\f\FTE<*8#@[/-P`otI6dTKbUlKB]S92c0,2ns9<3.X4Jb=Q]Xs>-(Q%*EIe;n&@^A!5Y4G=%jf6)b4cJi'(ogqCq\>BbQL%Ie&+R53o;TqU,h0g_ZpH5q*M<8;&KD"IAlEuq^dQ#1ANU0mAf=us\^JF1DE$d$q`VB+qh4ZN4b4X;j%Oe6F9_-X$!]LTnBX&moIGD8J$`/@ir+.Lp$k0c=*C32fg9e*5IN:7lb.#L9Ag_r)o+>t%sHUhl!^9]jXrqpD,r-9:4I75Q4jJj/_DRAMW<]R./B]j0i?H=QtIo+>&KVu!59MJ$Vf^0<1ASCrmON%#)\!&3:T-Rb]NL!BGrmKT=F/T<h%Z6Z+8)KQHlX0`tF@oj&!cZl-(6mN*;Su8mQj,JE"Ifg2G5A2G9O^IH+Iql.E,Hd2Q@UB-/WkT*RH8X%)"*S?t;Nh>YUYLo!OL7s$GN!t.s,'WQ--(NL3/L&U@h1[YmqM[]:Ca;G/46L'l8G^\\T@bd5DA]ih1!rtsl?.=L3qhB&+<C*^6Pb)RF;fCpNJ$`3MYl~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 387
>>
stream
GasbT4]B6P%#4NUMK^W,@:D1fA)sCY?/SCZ=PR*)V+L#_-dC%GO-11]Xog&Dc]Y+pdOUHaao:-t^k9YoZ\2<)431Bscmp[?k]8#qR8YE/$al(`.4+,#8]l2R/JNpQZ4TIb03?GmWr\Xtcjk>bLa8N:?(VnmV8JFo6tpBCpeY^['u_f5J(l;'aIsp4S.<U\*h*^h^dR+;YdA]#c?nu\D]'6u'4D4A)7_f"j'lu2T_Hgb4BL.Dnsm_\NS,(Mn;Xb.I?D=6_NlRTF=@)h`A(=kit1FA;iA-:<>7YKQKNthP!hn<+,SE=$B<ZW=^s4oVVbuq@c#@@SCt_939>fV.mbUMFB$h_lH$S6=V+2+O/6YJ!UXYBIE?7ZUa7MicAg41ajH,$~>endstream
endobj
xref
0 22
0000000000 65535 f
0000000061 00000 n
0000000132 00000 n
0000000239 00000 n
0000000351 00000 n
0000000456 00000 n
0000000533 00000 n
0000000738 00000 n
0000000853 00000 n
0000001058 00000 n
0000001263 00000 n
0000001469 00000 n
0000001675 00000 n
0000001881 00000 n
0000001951 00000 n
0000002242 00000 n
0000002335 00000 n
0000004506 00000 n
0000006858 00000 n
0000009114 00000 n
0000011303 00000 n
0000013314 00000 n
trailer
<<
/ID
[<15bd1c06a323579dbb461de4ce8768e8><15bd1c06a323579dbb461de4ce8768e8>]
% ReportLab generated PDF document -- digest (opensource)
/Info 14 0 R
/Root 13 0 R
/Size 22
>>
startxref
13792
%%EOF

View File

@@ -1,231 +0,0 @@
# Les 3 - Keynote Notes
**Spreektijd:** ~55 min | **Hands-on:** ~75 min | **Pauze:** 15 min (10:15-10:30)
---
## BLOK 1: WELKOM & MEDEDELING (10 min)
---
### Slide 1: Titelslide
- Welkom Les 3, vandaag: Cursor
- "Dit wordt je nieuwe favoriete tool!"
---
### Slide 2: Planning
- 09:00-09:30: Setup + Installatie
- 09:30-09:58: Features + Skills + Request tips
- 09:58-10:13: Live Demo nieuw project
- 10:13-10:15: Start Hands-on
- 10:15-10:30: PAUZE
- 10:30-11:45: Hands-on vervolg
- 11:45-12:00: Afsluiting
---
### Slide 3: Terugblik Les 2
- **OpenCode problemen:** token limits, EACCES, permission denied
- "Niet jullie schuld, beperking platform"
- **Waarom Cursor:** voor developers, meer requests, ingebouwde terminal, Skills
---
### Slide 4: Wat is Cursor?
- VS Code + AI ingebouwd
- **Gratis Student plan:** 500 fast requests/maand
- **5 features:** Chat, Inline Edit, Composer, Tab Completion, @mentions
- Hobby EERST → Student upgrade later
---
## BLOK 2: INSTALLATIE & SETUP (20 min)
---
### Slide 5: Download Cursor
- cursor.com → juiste OS versie
- macOS: Intel of Apple Silicon (let op!)
- Windows: Installer
- "Allemaal gedownload?"
---
### Slide 6: Account Aanmaken
- Sign Up → **Hobby account EERST**
- Email + wachtwoord verifiëren
- Student upgrade: cursor.com/students (later)
- ✓ CHECKPOINT: Cursor geopend, account gemaakt
---
### Slide 7: Interface Tour
- Activity Bar (links), Sidebar (files), Editor (midden)
- **Chat Panel:** Ctrl+L (rechts)
- **Terminal:** Ctrl+` (onderin) → alles in 1 window!
---
### Slide 8: Terminal Setup Check
- **SAMEN DOEN** - iedereen tegelijk
- `node -v` (v20+)
- `git --version` (2.x+)
- `npm -v` (10.x+)
- `npx -v` (10.x+) → **nodig voor create-next-app!**
- Help bij errors, CHECKPOINT
---
## BLOK 3: CURSOR FEATURES (15 min)
---
### Slide 9: Chat Panel (Ctrl+L)
- Vragen over code, AI ziet hele codebase
- Wees specifiek → bespaart requests
- "Analyseer dit project" → AI leest alles
---
### Slide 10: Inline Edit (Ctrl+K)
- Select code → Ctrl+K → instructie → Accept/Reject
- Voorbeeld: inline styles → Tailwind
- Voor: kleine aanpassingen, styling fixes
---
### Slide 11: Composer (Ctrl+I)
- Multi-file, grote taken
- "Voeg pagina toe" → AI maakt alles
- Preview → Accept → instant
---
### Slide 12: Tab Completion
- AI voorspelt code, druk Tab
- **GRATIS - kost geen premium request!**
- Benadrukken: gebruik dit voor kleine dingen
---
### Slide 13: @ Mentions
- @file, @folder, @web, @docs
- **@docs belangrijk:** verwijst naar geïnstalleerde docs
- "Hoe werkt App Router? @docs Next.js"
---
## BLOK 4: SKILLS, RULES & REQUESTS (15 min)
---
### Slide 14: Cursor Skills Installeren
- **SAMEN DOEN!**
- Settings → Features → Docs → Add
- Toevoegen: nextjs.org/docs, react.dev, tailwindcss.com/docs
- Test: Chat + @docs Next.js
- ✓ CHECKPOINT: Docs toegevoegd
---
### Slide 15: .cursorrules via Chat
- NIET handmatig schrijven
- Chat (Ctrl+L): "Genereer .cursorrules voor Next.js + Tailwind + TypeScript"
- Kopieer → maak bestand → save
- **1 request = goed besteed**
---
### Slide 16: Request Management
- Hobby = beperkte requests
- **Tab Completion = GRATIS**
- Chat/Composer/Inline Edit = 1 request
- Tips: denk eerst, combineer taken, gebruik Tab Completion
- "3 kleine prompts = 3 requests. 1 grote prompt = 1 request!"
---
## BLOK 5: LIVE DEMO (15 min)
---
### Slide 17: Demo Nieuw Project (blijft op scherm)
1. mkdir + git init (1 min)
2. npx create-next-app@latest . (2 min)
- TypeScript, Tailwind, App Router: allemaal Yes
3. npm run dev → localhost:3000 (1 min)
4. Chat: genereer .cursorrules (3 min, 1 request)
5. Composer: HeroSection component (4 min, 1 request)
6. Refresh browser → toon resultaat (1 min)
7. git add + commit (1 min)
- **"Van niks naar werkende site in 15 min!"**
---
## BLOK 6: HANDS-ON (75 min + 15 min pauze)
---
### Slide 18: Opdracht (blijft op scherm)
- STAP 1: mkdir + git init + npx create-next-app + npm run dev
- STAP 2: Check Skills/Docs, genereer .cursorrules via Chat
- STAP 3: Bouw componenten (Hero, Features, Form, Footer)
- STAP 4: git commit
- **Let op requests! Max ~6-7 in de les**
- ☕ Pauze 10:15-10:30
**Checks:**
- 10:45: "Wie heeft npx gedaan? .cursorrules? Component?"
- 11:00: "Wie heeft 2 componenten? Requests over?"
- 11:30: "Wie heeft gecommit?"
---
## BLOK 7: AFSLUITING (15 min)
---
### Slide 19: Resultaten
- 3-4 studenten tonen werk
- "Welke componenten? Hoeveel requests?"
---
### Slide 20: Samenvatting
- ✅ Cursor setup + Skills/Docs
- ✅ .cursorrules via AI Chat
- ✅ npx create-next-app workflow
- ✅ Chat, Composer, Inline Edit, Tab Completion
- ✅ Request management (Tab = gratis!)
---
### Slide 21: Huiswerk - Debug Challenge
- Download zip van Teams
- **Fouten:** missende deps, syntax errors, inline styles
- Fix met Cursor terminal + AI
- Push naar GitHub
- Post op Teams: link + screenshot + beschrijving
---
### Slide 22: Volgende Les
- Les 4: Effectief Prompting & Iteratief Werken
- Huiswerk MOET af zijn!
---
## TIMING SUMMARY
| Blok | Onderdeel | Duur |
|------|-----------|------|
| 1 | Welkom & Mededeling | 10 min |
| 2 | Installatie & Setup | 20 min |
| 3 | Cursor Features | 15 min |
| 4 | Skills, Rules & Requests | 15 min |
| 5 | Live Demo | 15 min |
| **PAUZE** | | **15 min** |
| 6 | Hands-On | 75 min |
| 7 | Afsluiting | 15 min |
| **TOTAAL** | | **180 min** |

View File

@@ -1,561 +1,155 @@
# Les 3: Cursor - AI-Powered Code Editor # Les 3 — Docenttekst (normaal)
## Docenttekst (Spreektekst voor Tim)
> **Totale lesduur:** 3 uur (180 minuten) **Fysieke les. Voor uitgebreide voorbereiding + context per blok.**
> **Totale spreektijd:** ~90 minuten (verdeeld over blokken) **Voor letterlijk voorleesbaar script: zie `Les03-Docenttekst-Autocue.md`.**
> **Hands-on praktijk:** ~75 minuten
> **Pauze:** 15 minuten (10:15-10:30)
--- ---
## BLOK 1: WELKOM & MEDEDELING (10 min) ## Lesvorm en flow
### Slide 1: Titelslide - Welkom Les 3, Cursor! | Onderdeel | Indeling |
**[09:00 - 09:02]** |-----------|----------|
| Theorie + demo | ±1,5 uur klassikaal |
"Goedemorgen iedereen, welkom bij Les 3! Vandaag gaan we kennismaken met Cursor, een code editor die echt gemaakt is voor developers. Dit is geen random AI tool het is gebouwd VOOR programmeurs. In de vorige les hebben jullie gemerkt dat OpenCode wat problemen had: token limits, EACCES errors, en niet echt ontworpen voor professionele development. Cursor lost dat allemaal op. We gaan hem vandaag installeren, je eerste project bouwen, en je zult zien hoe snel je daarmee dingen kunt maken." | Pauze | 15 minuten |
| Hands-on | ±1,25 uur zelf bouwen met begeleiding |
_[Glimlach, maak oogcontact. Stel de energie in voor een praktische, hands-on les.]_
--- ---
### Slide 2: Planning vandaag ## Voorbereiding — een week vooraf
**[09:02 - 09:05]**
"Dit is de planning voor vandaag. We starten met een korte terugblik op waarom Cursor beter is dan OpenCode. Dan gaan we Cursor installeren en je account opzetten dat gaat snel. Daarna laat ik alle features zien: Chat, Inline Edit, Composer, Tab Completion, en @mentions. We doen een interfacetour zodat je precies weet waar alles zit. **Studenten checklist (stuur via Slack):**
- Laptop met admin-rechten (Cursor installatie vereist admin op Windows)
- NOVI e-mailadres werkend
- `node -v` werkt (versie 18 of hoger)
- `git --version` werkt
- Browser-bookmark voor `cursor.com/students` ergens klaarzetten
Vervolgens duiken we in het inrichten van Cursor Skills dat is belangrijk, je gaat framework documentatie inladen zodat de AI beter kan helpen. We maken je eerste .cursorrules file, en ik leg uit hoe je request limit management moet denken. **Jouw eigen voorbereiding:**
- Test je eigen Cursor Pro Student status (om geen "ik vergat hem te activeren" te krijgen tijdens demo)
Om 09:40 doen we een live demo: ik bouw in real-time een Next.js project van nul tot werkend. Dan krijgen jullie rond 10:00 een praktische opdracht. Jullie bouwen zelf een project, en we hebben een pauze van 10:15 tot 10:30. Na de pauze werken jullie verder, en we afsluiten met het delen van jullie resultaten, een samenvatting, en huiswerk." - Test `npx create-next-app@latest` (cache kan blokkeren — `npm cache clean --force` als nodig)
- Backup-project klaar met Hero al gebouwd voor het geval live demo vastloopt
_[Wijs naar timeline op slide. Ga niet te snel. Geef studenten een gevoel van progressie.]_ - `.cursorrules` snippet copy-paste klaar
--- ---
### Slide 3: Terugblik Les 2 - Waarom Cursor beter is ## Voorbereiding — dag van de les
**[09:05 - 09:08]**
"Laten we even teruggaan naar Les 2 met OpenCode. Veel van jullie hebben vast gelopen tegen problemen, toch? Token limits die onverwacht opraken, EACCES permission errors als je externe packages wilde installeren, en eigenlijk voelde het meer als een AI chatbot dan als een echte code editor. **30 minuten vooraf:**
- Cursor open in een lege folder voor de demo
- Twee terminals klaar (één in `~/web`, één leeg)
- Browser tabs: cursor.com/students, cursor.com/download, github.com (logged in)
- Slides in fullscreen op presentatiescherm
Cursor is fundamenteel anders. Het is GEBOUWD voor developers. Cursor ziet je hele codebase tegelijk Chat werkt niet op fragmenten, maar snapt je hele project. De AI completion (Tab completion) is ingebouwd en super snel. Bovendien: het Hobby plan is helemaal gratis, en studenten kunnen upgraden naar het Student plan, ook gratis, via cursor.com/students. Geen creditcard nodig. **Backup scenario:**
- SheerID verificatie geeft soms 510 minuten vertraging → vertel studenten dit vooraf
En het allerbelangrijkste: Cursor heeft een INGEBOUWDE terminal. Je kunt node -v, npm install, en git commands rechtstreeks uitvoeren zonder naar een ander venster te hoppen. Alles gebeurt in één editor." - `npx create-next-app` kan hangen → check cache of gebruik `bun create next-app` als alternatief
- Internet traag in zaal → backup-project op standby
_[Maak oogcontact. Zeg duidelijk "gratis" dat is een belangrijk voordeel.]_
--- ---
### Slide 4: Wat is Cursor? Features en pricing ## Per blok — wat je extra wilt zeggen
**[09:08 - 09:10]**
"Oké, wat IS Cursor precies? Het is eigenlijk VS Code dezelfde editor die de helft van de wereld gebruikt maar dan met krachtige AI features ingebouwd. Je krijgt vijf hoofdfeatures: ### Blok 1 — Welkom + Wat is Cursor (slides 13)
**Doel:** verwachtingen zetten, energie inschakelen.
**Eerste: Chat (Ctrl+L).** Je kunt vragen stellen over je code. De AI ziet je hele codebase en kan context begrijpen. Je kunt vragen 'Hoe werkt deze functie?' of 'Wat doet dit component?'. Wees specifiek in je vragen, dan krijg je betere antwoorden. Vraag vooraf: wie heeft GitHub Copilot wel eens gebruikt? Wie ChatGPT voor code? Dat is je startpunt — Cursor is dat, maar nauwer geïntegreerd in de editor zelf.
**Tweede: Inline Edit (Ctrl+K).** Je selecteert code, geeft een instructie zoals 'Zet dit om naar Tailwind classes', en de AI edit precies dat stukje. Super handig voor kleine aanpassingen. ### Blok 2 — Student Plan + Installatie (slides 46)
**Doel:** iedereen heeft Pro Student actief aan het eind van dit blok.
**Derde: Composer (Ctrl+I).** Dit is voor grotere taken. Je kunt zeggen 'Voeg een product pagina toe met filtering en cart functionaliteit', en de AI genereert meerdere files tegelijk. Echt krachtig. **Vooraf melden:** SheerID-verificatie kan 110 minuten duren. Geen reden om paniek te krijgen.
**Vierde: Tab Completion.** De AI voorspelt je code. Je begint te typen, en het suggereert wat je waarschijnlijk wilt. Je drukt Tab en klaar. Dit kost GEEN requests het is gratis! **Common issue:** studenten met persoonlijk Gmail-adres in plaats van NOVI. Verificatie faalt → "log in met je NOVI-mail" — dat is non-negotiable.
**Vijfde: @mentions.** Je kunt context geven door @file, @folder, @web, en @docs te gebruiken. @docs is echt belangrijk: je kunt framework documentatie inladen, zodat de AI tegen de juiste versie van React of Next.js praat. **Voor wie technisch slim is:** Cursor heeft een settings.json zoals VS Code. Importeer de jouwe als je hem hebt. Maakt de transitie soepel.
Nu: pricing. Het Hobby plan is gratis en volstaat voor het meeste. Je krijgt beperkte requests precies genoeg om te oefenen. Als je Student ben, kun je naar cursor.com/students gaan en upgraden naar Student, ook gratis, met veel meer requests." ### Blok 3 — Skills/Docs + Eerste project (slides 79)
**Doel:** student heeft `mijn-eerste-cursor-app` draaiend op localhost en open in Cursor.
_[Toon de vijf features op je vingers. Zeg "GRATIS" luidop voor Tab Completion veel studenten snappen niet dat dit geen request kost.]_ **Verwacht problemen:**
- "I get a permission error" → npm prefix issue, gebruik nvm of corepack
- "create-next-app duurt eeuwig" → cache + slecht internet, even geduld
- "ik krijg geen Tailwind" → student kiest "no" bij Tailwind-prompt, opnieuw beginnen
Loop rond in dit blok — dit is waar studenten het meest vastlopen.
### Blok 4 — Drie sneltoetsen (slides 1014)
**Doel:** student kan Tab, Cmd+K en Cmd+L actief gebruiken.
**Voor elke sneltoets: doe een mini-live-demo.** Niet alleen praten — laat letterlijk zien wat er gebeurt. Studenten leren dit door te zien.
**Bij Tab:** typ een commentaar en wacht 2 seconden — Cursor stelt de hele functie voor. Dat is een wow-moment.
**Bij Cmd+K:** kies bewust iets simpels (zoals een variabele hernoemen) — niet iets complex dat 10 seconden duurt.
**Bij Cmd+L:** stel een vraag, niet meteen een grote taak. *"Hoe werkt useEffect?"* — laat Cursor antwoorden zien.
### Blok 5 — `.cursorrules` (slide 15)
**Doel:** student weet wat het is + heeft één in zijn project.
**Vertel ook:** in Les 14 (veel later) gaan we naar `.cursor/rules/` (folder met meerdere bestanden). Voor nu is het oude format prima en simpeler om uit te leggen.
**Demo:** ik vraag Cursor in Cmd+L *"Maak een .cursorrules voor dit Next.js project"*. Laat de output zien, plak in `.cursorrules`-bestand, save.
### Blok 6 — Hands-on (slides 1619)
**Doel:** elke student heeft een werkende Hero gebouwd en één commit gedaan.
**Voor de pauze:** demonstreer taak 1 + 2. Niet meer.
**Na de pauze:** loop rond. Voor elke vraag, beantwoord met "wat zou je in Cursor zelf vragen?" — leer ze om de IDE te benutten.
**Verwacht vragen:**
- "Mijn Hero ziet er saai uit" → vraag Cursor om een tweede iteratie via Cmd+K
- "Tailwind classes werken niet" → check of `globals.css` `@tailwind base/components/utilities` heeft
- "Git werkt niet" → check `gh auth login` of `git config user.email`
### Blok 7 — Afronding (slide 20)
**Doel:** koppel vandaag aan de rest van de opleiding.
Maak expliciet: vandaag was de basis. Tab + Cmd+K + Cmd+L gaan jullie elke les terugzien. Les 4 gaat over TypeScript. Les 14 (veel later) gaat dieper op Cursor 3 features.
--- ---
## BLOK 2: INSTALLATIE & SETUP (20 min) ## Veelvoorkomende vragen + antwoorden
### Slide 5: Download en installatie **"Werkt Cursor op Linux?"**
**[09:10 - 09:14]** Ja — AppImage download via cursor.com.
"Goed, laten we beginnen met installatie. Je gaat naar cursor.com en klikt op Download. Cursor draait op Mac, Windows, en Linux dus voor iedereen. De installer is heel eenvoudig: je downloadt het, opent het, sleep je Cursor naar je Applications folder, en klaar. Als je op Windows bent, run je de .exe en volg je de stappen. **"Wat als ik geen Pro Student krijg?"**
Stuur student naar support@cursor.com met NOVI mail + bewijs van inschrijving. Werkt meestal binnen 24 uur.
Dit kost ongeveer twee minuten. Als je al VS Code hebt, hoef je niks te desinstalleren Cursor en VS Code kunnen naast elkaar bestaan zonder problemen." **"Mag ik mijn eigen OpenAI/Anthropic key gebruiken?"**
Ja — Settings → Models → eigen key plakken. Soms goedkoper bij weinig gebruik, niet nodig met Pro Student.
_[Navigeer naar cursor.com en wijs de download knop aan. Laat zien hoe het eruit ziet op Mac en Windows (screenshot).]_ **"Wat is het verschil tussen Cmd+K en Cmd+L?"**
Cmd+K = inline edit op selectie (één bestand, één gericht ding). Cmd+L = chat (uitleg vragen of grotere taak met meerdere bestanden).
**"Wordt mijn code naar OpenAI/Anthropic gestuurd?"**
Standaard: ja, voor inference. Cursor heeft een Privacy Mode in Settings — als die aan staat, wordt code niet bewaard. Geen training op je code.
**"Wat als ik door 500 requests heen ben?"**
Wacht op nieuwe maand (reset op de 1e), of upgrade naar betaalde tier ($20/maand), of plug je eigen API-key in.
**"Kan ik blijven coden zonder internet?"**
Tab + lokale modellen via Settings, ja. Cmd+K en Cmd+L vereisen internet.
--- ---
### Slide 6: Account aanmaken en upgraden ## Foutmodi tijdens de les
**[09:14 - 09:18]**
"Zodra Cursor open is, maak je een account aan. Je hoeft NIET direct Student plan te nemen begin met Hobby. Je geeft je emailadres en een wachtwoord in. Cursor stuurt je een bevestigingsmail. | Probleem | Snelle fix |
|----------|-----------|
BELANGRIJK: als je Student bent, upgrade je NA deze eerste setup. Je gaat naar cursor.com/students, logt in met diezelfde email, en kiest Student plan. Geen creditcard, geen gedoe je werkt gewoon mee met onze les en je bent gedekt. | SheerID hangt | Volgende student helpen, terugkomen na 10 min |
| `npx create-next-app` hangt | `npm cache clean --force` of `bun create next-app` |
Na login zie je je Hobby account. Dan gaan we gelijk verder met setup." | Cursor niet ingelogd na herstart | Settings → Account → opnieuw inloggen |
| Tab-completion verschijnt niet | Settings → Features → Cursor Tab → check enabled |
_[Toon account maken op je eigen machine. Zeg duidelijk: "Hobby EERST, Student plan LATER". CHECKPOINT: check rond of iedereen ingelogd is.]_ | Cmd+L opent niet | Mogelijk Notion of andere app vangt sneltoets — check System Preferences |
| `.cursorrules` lijkt genegeerd | File moet in project-root staan, niet in src/ — herstart Cursor |
--- ---
### Slide 7: Interface tour ## Vooruitblik Les 4
**[09:18 - 09:22]**
"Oké, je bent ingelogd. Laten we de interface doornemen zodat je weet waar alles zit. Volgende les: TypeScript Fundamentals. Strict types, generics, utility types. Studenten gaan ze toepassen in hun Next.js project. Geen extra installatie nodig — Next.js scaffold zit al met TS.
Links zie je de **Activity Bar** kleine iconen voor Files, Search, Source Control, en Run & Debug. Dit is hetzelfde als VS Code. Stuur de avond ervoor een Slack-bericht: "voor les 4: zorg dat je Cursor Pro Student actief hebt, anders sta je achter".
Daaronder zie je de **Sidebar** dit is waar je bestanden staan als je Files opent.
In het midden: de **Editor**. Dit is waar je code schrijft en bewerkt.
Aan de rechterkant en dit is NIEUW vergeleken met normale VS Code de **Chat Panel**. Dit open je met Ctrl+L. Hier stel je vragen over je code.
Onder de editor zie je de **Terminal** Ctrl+backtick, dus Ctrl+` hier voer je commands uit.
Het mooie is: ALLES zit in één window. Je hoeft niet tussen VS Code en ChatGPT heen en weer te springen."
_[Beweeg je cursor rond op de interface. Duid elke component aan. Open de Chat Panel met Ctrl+L, sluit het weer met Escape.]_
---
### Slide 8: Terminal setup check SAMEN
**[09:22 - 09:30]**
"Nu gaan we even checken of je setup klaar is. Dit doen we via de Terminal. Open Terminal met Ctrl+backtick dus Ctrl+`.
Je ziet prompt. We gaan drie commands checken:
**Eerste: node -v** dit geeft je Node.js versie. Je wilt versie 18 of hoger. Als je error ziet 'command not found', moet je Node.js installeren via nodejs.org.
**Tweede: git --version** dit geeft je Git versie. Versie 2.20 of hoger is prima.
**Derde: npm -v** dit is de Package Manager die met Node.js komt. Ook 7.0 of hoger.
**Vierde: npx -v** dit is ESSENTIEEL. npx gebruiken we straks om create-next-app te draaien. Als npx niet werkt, kun je geen nieuw project aanmaken. npx komt normaal mee met npm, dus als npm werkt, werkt npx ook. Maar even checken!
Wie errors ziet, geen paniek. Steek je hand op, ik help je. Dit duurt twee minuten per persoon."
_[Open je eigen Terminal. Typ de vier commands. Laat zien wat output eruitziet. Sta twee minuten stil en help studenten die problemen hebben.]_
_[CHECKPOINT: zorg dat iedereen node, git, npm EN npx werkend heeft. Dit is essentieel voor de rest van de les.]_
---
## BLOK 3: CURSOR FEATURES (15 min)
### Slide 9: Chat Panel - Vragen over je code
**[09:30 - 09:36]**
"Nu duiken we in de échte kracht van Cursor: de Features. Laten we beginnen met **Chat Panel**.
Je opent Chat met Ctrl+L. Ik open het nu even op mijn scherm. Je ziet een textbox waar je kunt typen. Het bijzondere: Cursor ziet je HELE project, niet gewoon het stukje code dat je hebt geselect.
Stel je voor: je hebt een Next.js project met 15 componenten. Je kunt in Chat vragen: 'Hoe werkt de authentication flow in dit project?' en de AI snapt je hele setup en geeft een ingewikkeld antwoord.
**Tips voor goede Chat vragen:**
- Wees specifiek. Zeg niet 'Fix dit' maar 'Zet alle inline styles om naar Tailwind classes en zorg dat de layout hetzelfde blijft.'
- Geef context. 'In de ProductCard component, de border is nu blauw, kan hij rood zijn en dikker?'
- Stel vervolgvragen. De AI onthoudt wat je eerder vroeg.
Chat kost één request per bericht. Hop voorzichtig je hebt beperkte requests op Hobby."
_[Open Chat (Ctrl+L). Typ een voorbeeld vraag. Laat zien hoe antwoord zich opent.]_
---
### Slide 10: Inline Edit - Snelle code changes
**[09:36 - 09:41]**
"Volgende feature: **Inline Edit (Ctrl+K)**. Dit is voor als je kleine stukken wilt aanpassen.
Stel je voor: je hebt een component met inline styles die lelijke style={{color: 'red', marginTop: '10px'}}. Je wilt dit naar Tailwind. Je selecteert die hele component code, drukt Ctrl+K, en je typt: 'Zet alle inline styles om naar Tailwind classes, keep the same styling.'
Cursor selecteert precies dat stukje, geeft een preview, en je ziet de diff. Je kunt dan een diff accepteren of afwijzen. Super handig voor refactoring.
**Voorbeeld in mijn hoofd:**
Input: Component met inline styles
Instructie: 'Convert to Tailwind'
Output: Dezelfde component, maar nu className='text-red-600 mt-2.5'
Dit kost ook één request. Gebruik het voor kleine, chirurgische aanpassingen."
_[Maak een voorbeeld component met inline styles. Demonstreer Ctrl+K en de AI vervanging.]_
---
### Slide 11: Composer - Multi-file grote taken
**[09:41 - 09:46]**
"Nu het heavyweight: **Composer (Ctrl+I)**. Dit is voor als je GROTE dingen wilt doen dingen die meerdere files raken.
Stel je voor: je hebt een Next.js app met één pagina. Je wilt een heel nieuwe feature: een Admin Dashboard met login, user list, en stats panel. Dit raakt misschien 5 bestanden: nieuwe route, nieuwe component, styles, database queries, alles.
In plaats van dit in stukken te vragen in Chat, openen je Composer met Ctrl+I. Je zegt: 'Voeg een Admin Dashboard toe met login, user management, en stats. Zet het op /admin route.'
Composer genereert ALLES tegelijk alle files, alle imports, alle routing. Je ziet een preview, klikt 'Accept', en BOEM je project heeft een werkende admin dashboard.
Dit is échte time-saver. En ja, dit kost ook één request niet meer, niet minder, want Cursor snapt dat je één logische taak doet."
_[Open je eigen project. Open Composer (Ctrl+I). Laat zien hoe je een multi-file taak kunt beschrijven.]_
---
### Slide 12: Tab Completion - AI code voorspelling
**[09:46 - 09:50]**
"Feature vier: **Tab Completion**. Dit is mijn favoriet. En het BELANGRIJKSTE: dit kost GEEN requests. Nul. Gratis.
Je begint gewoon code te typen. Terwijl je typt, ziet Cursor wat je waarschijnlijk wilt doen. Je typt:
```javascript
const handleClick = () => {
```
En Cursor suggereert al wat je waarschijnlijk volgende doet. Je drukt Tab en het wordt aanvaard. Je typt verder, Tab, verder, Tab. Dit gaat super snel.
**Waarom is dit gratis?** Omdat Tab Completion niet intensief is voor Cursor het is lokale voorspelling, niet een volledige AI analyse. Dus gebruik dit VEEL! Dit moet je reflexmatig doen.
**Tip:** als je een suggestion ziet die je niet wilt, druk je gewoon Escape of tik je wat anders. Geen probleem.
Dit is echt de feature waar je sneller in je code wordt als je het goed gebruikt."
_[Demonstreer Tab Completion in je editor. Typ langzaam zodat Cursor suggestions toont. Druk Tab en laat zien hoe snel dit gaat.]_
---
### Slide 13: @mentions - Context voor je vragen
**[09:50 - 09:55]**
"Laatste feature: **@mentions**. Dit is hoe je de AI context geeft.
Je kent het van Slack en Discord: als je @iemand typt, tag je ze. In Cursor Chat doe je iets soortgelijks.
Je kunt typen:
- **@file** je verwijst naar een specifiek bestand. Voorbeeld: '@pages/api/auth.js write a logout endpoint'
- **@folder** je verwijst naar een hele folder. Voorbeeld: '@components analyze this folder and tell me architecture'
- **@web** de AI zoekt het web op. Voorbeeld: 'how do I connect to @web stripe payment gateway?'
- **@docs** DIT IS HEEL BELANGRIJK je verwijst naar framework documentatie. Voorbeeld: '@docs latest react patterns for state management'
@docs is een game changer. Als je @docs docs react typen, zal de AI antwoorden tegen de officieële React documentatie, niet tegen oude tutorials. Dit voorkomt dat je advice krijgt van 2019.
We gaan straks je Skills opzetten, en daar laad je @docs in. Ongelofelijk powerful."
_[Typ in Chat: '@docs how do I use hooks?' Laat zien dat niks gebeurt totdat Skills ingesteld zijn dat doen we volgende slide.]_
---
## BLOK 4: SKILLS, RULES & REQUESTS (15 min)
### Slide 14: Cursor Skills - Framework documentatie inladen
**[09:55 - 10:05]**
"Oké, volgende stap. Ik zei net dat @docs belangrijk is. Daarvoor moeten we eerst je **Cursor Skills** instellen. Dit is hoe je documentatie inlaadt zodat de AI weet tegen welke versies het praat.
We doen dit SAMEN, dus volg mee op je eigen machine.
**Stap 1:** Ga naar Settings. Op Mac: Cmd+, (command komma). Op Windows: Ctrl+, (control komma). De Settings paneel opent.
**Stap 2:** Je zoekt naar 'Features' en je klikt op Features.
**Stap 3:** Je zoekt naar 'Docs' je ziet 'Cursor Docs' instelling. Daar klikt je op.
**Stap 4:** Je ziet een + knop. Je klikt erop. Nu kun je documentatie toevoegen. Dit zijn de drie die ik aanraad:
- **Next.js:** je gaat naar nextjs.org/docs en je pakt die URL. Voeg toe.
- **React:** react.dev is de officieële React docs. Voeg toe.
- **Tailwind CSS:** tailwindcss.com/docs. Voeg toe.
Na toevoegen kun je in Chat @docs gebruiken en de AI leest tegen jouw documentatie. Test het: type in Chat '@docs what is the latest way to do routing in next.js?' en je krijgt antwoord uit de officieële Next.js documentatie, niet uit random StackOverflow.
Dit is echt belangrijk voor professioneel werken."
_[DOEN, niet demo. Zorg dat iedereen dit meegedaan heeft. Help met technische vragen. CHECKPOINT: vraag "Iedereen Skills ingesteld?" en wacht op bevestiging.]_
---
### Slide 15: .cursorrules - Jouw AI richtlijnen
**[10:05 - 10:12]**
"Nu gaan we een .cursorrules bestand maken. Dit is NIET handmatig geschreven Cursor genereert dit voor je.
Een .cursorrules file is een bestand in de root van je project waarin je aan Cursor zegt: 'Hey, als je met mijn code werkt, volg deze richtlijnen.'
Voorbeeld richtlijnen:
- 'Alle componenten moeten TypeScript hebben type annotations'
- 'Gebruik Tailwind, geen CSS modules'
- 'Nederlandse comments in de code'
- 'Altijd error handling toevoegen'
Dit is SUPER handig omdat je je preferences maar één keer ingesteld hebt, en dan volgt de AI altijd.
**Hoe maak je het?** Heel makkelijk:
**Stap 1:** Je opent Chat (Ctrl+L).
**Stap 2:** Je plakt deze prompt:
'Genereer een .cursorrules bestand voor een Next.js + Tailwind CSS + TypeScript project. Include richtlijnen voor: code style, components best practices, error handling, en Nederlandse comments. Output het volledige bestand klaar om te kopieën.'
**Stap 3:** Cursor genereert een complete .cursorrules file. Je kopieert dit.
**Stap 4:** In je project maak je een nieuw bestand genaamd `.cursorrules` in de root folder. Je plakt de content en je saved.
**Klaar!** Dát is één request, en nu volgt Cursor altijd jouw rules.
Dit is echt tijd besparen. Je zegt nooit meer 'gebruik TypeScript' dat weet Cursor al."
_[Open Chat. Paste je eigen .cursorrules prompt (aanwezig op je machine). Laat zien hoe output eruitziet. Demonstreer kopieën naar nieuw bestand.]_
---
### Slide 16: Request management - Budget uitzetten
**[10:12 - 10:20]**
"Oké, de grote vraag: requests. Op Hobby heb je BEPERKT aantal requests. Je wilt ze slim gebruiken.
**Hier is de breakdown:**
- **Tab Completion:** GRATIS. Ongelimiteerd. Gebruik dit voor kleine dingen.
- **Chat:** 1 request per bericht. Wees voorzichtig.
- **Inline Edit:** 1 request. Snel maar denk je in voor je tikt.
- **Composer:** 1 request, ongeacht hoe groot. Dit is eigenlijk heel voordelig voor grote dingen.
**Budget tips:**
**Eén: Denk eerst, prompt daarna.** Voordat je iets vraagt, bepaal je wat je PRECIES wilt. Maak een kopje koffie, bedenk je vraag, tik dan. Dit voorkomt vijf mislukte requests.
**Twee: Combineer taken.** In plaats van drie aparte Chat vragen, maak je één grote vraag. Zeg alles in één bericht.
**Drie: Gebruik Tab Completion voor kleine dingen.** Import statements, losse variabelen, functies. Dit bespaart requests.
**Vier: Composer voor grote refactors.** Als je iets aan meerdere files moet doen, doe je het in Composer, niet in aparte Chat vragen.
**Vijf: @mentions voor efficiency.** @docs helpt je beter vragen stellen, minder 'wat is het beste patroon' vragen.
Op Hobby krijg je genoeg om deze hele les te doen en meer. Maar als je gaat echt projecten bouwen, upgrade je naar Student. Dan heb je veel meer."
_[Toon scherm met request counter. Leg uit waar je request status ziet (rechtsboven in Cursor).]_
---
## BLOK 5: LIVE DEMO (15 min)
### Slide 17: Live Demo - Van nul naar werkende website
**[10:20 - 10:35]**
"Oké, genoeg theorie. Ik bouw nu in real-time een werkende Next.js website. Dit doet twee dingen: één, je ziet hoe snel dit echt gaat, en twee, je ziet de volledige workflow die je straks zelf doet.
**Stap 1: Project setup**
```bash
mkdir my-demo-site
cd my-demo-site
git init
```
Ik heb een folder, git aan. Cursor openen.
**Stap 2: Next.js project**
```bash
npx create-next-app@latest . --typescript --tailwind --app
```
Ik zeg Yes op TypeScript, Yes op Tailwind, Yes op App Router. Dit duurt ongeveer een minuut.
**Stap 3: Dev server**
```bash
npm run dev
```
Nu draait http://localhost:3000 met de default Next.js pagina.
**Stap 4: .cursorrules via Chat**
Ctrl+L. Ik paste mijn prompt. Één request. Copy. Make file `.cursorrules`. Done.
**Stap 5: HeroSection via Composer**
Ctrl+I. 'Create a Hero section component in app/components/HeroSection.tsx with a headline, subheading, and a Call-to-Action button. Use Tailwind classes. Make it look modern.'
Cursor genereert de hele component. Één request. Ik accepteer.
**Stap 6: Integrate in page**
Ik open app/page.tsx. Voeg import HeroSection. Zet het in HTML. Refresh browser. BOOM. Werkende website, mooi design.
**Stap 7: Git commit**
```bash
git add .
git commit -m "Initial setup with HeroSection"
```
**Totaal:** Ik gebruikte 2 requests (één voor .cursorrules, één voor HeroSection). Tab Completion hielp met imports en minor edits. Van nul naar werkende website in 15 minuten. Dat kan jij straks ook."
_[ECHTE DEMO, niet gerendeert. Wys elke stap live. Stop niet totdat npm run dev werkt en browser toont mooi design. Timing is belangrijk studenten moeten zien dat dit ECHT snel gaat.]_
---
## BLOK 6: HANDS-ON PRAKTIJK (75 min + 15 min pauze)
### Slide 18: Hands-on opdracht - Studenten bouwen eigen project
**[10:35 - 10:50] (+ pauze 10:15-10:30, dan verder tot 11:30]**
"Jullie beurt. Hier is wat je gaat doen. Dit is dezelfde workflow als mijn demo, maar je doet het zelf.
**Opdracht:** Bouw een NIEUW project. Mag elk onderwerp zijn portfolio site, blog, product showcase, whatever. Belangrijkste: je volgt deze stappen:
**Stap 1: Project initialize**
```bash
mkdir my-awesome-project
cd my-awesome-project
git init
```
**Stap 2: Next.js setup**
```bash
npx create-next-app@latest . --typescript --tailwind --app
```
TypeScript = Yes, Tailwind = Yes, App Router = Yes.
**Stap 3: Cursor Skills check**
Zorg dat je Next.js, React, en Tailwind documentatie ingeladen hebt. Test @docs.
**Stap 4: .cursorrules genereren**
Chat, één prompt, één request. Kopieën naar bestand.
**Stap 5: Componenten bouwen**
Gebruik Composer (Ctrl+I) om componenten te genereren:
- HeroSection (headline, subheading, CTA)
- FeatureCards (3-4 feature cards met iconen en beschrijvingen)
- TestimonialSection (3 testimonials)
- Footer
Elke Composer request = één component.
**Stap 6: Integreren in main page**
Import alle componenten in app/page.tsx. npm run dev. Check dat alles looks good.
**Stap 7: Git commit**
```bash
git add .
git commit -m "Complete project setup with all components"
```
**Timing:**
- Nu tot 10:15: je begint. Ik loop rond, ik help met problemen.
- 10:15-10:30: PAUZE. Stretch, koffie, toilet.
- 10:30-11:15: jullie bouwen verder. Meer componenten, refinement, styling tweaks. Ik help.
**Checks tussendoor:**
- Is npm run dev werkend?
- Hoe veel requests heb je gebruikt?
- Zit je stuck? Vraag me.
**Let op:** dit is HANDS-ON. Je moet dit zelf doen, niet copy-paste van mij. De waarde zit in het leren van de workflow."
_[Zet timer op. Check na 10 minuten wie klaar is met setup. Help trackers. Zorg dat iedereen minstens HeroSection werkend heeft voor de pauze.]_
_[Na pauze: motiveer studenten, help met volgende componenten, praise progress.]_
---
## BLOK 7: AFSLUITING (15 min)
### Slide 19: Resultaten delen
**[11:15 - 11:25]**
"Goed werk iedereen! Nu gaan we een paar resultaten delen. Ik wil 3-4 van jullie zien wat je hebt gebouwd.
[Roep studenten op om hun project op scherm te delen. Laat hun website zien. Zeg wat je goed vindt 'Leuke kleuren!' of 'Mooie layout, hoe heb je die FeatureCards gedaan?' Geef high-fives digitaal of letterlijk.]
Dit is echt professioneel werk. En je hebt dit in anderhalf uur gebouwd. Besef je dat? Met OpenCode zou dit twee keer zo lang geduurd hebben."
_[Echt naar mensen luisteren. 3-4 korte demo's, niet lang. Maak het positief.]_
---
### Slide 20: Samenvatting - De les in vijf punten
**[11:25 - 11:35]**
"Laten we samenvatten wat je vandaag geleerd hebt.
**Eén: Setup.** Je kunt Cursor installeren, een account maken, Skills configureren. Dit is standaard, je doet dit nu automatisch.
**Twee: AI Features.** Je kent Chat, Inline Edit, Composer, Tab Completion, @mentions. Elke feature heeft een use case. Chat voor vragen, Composer voor grote dingen, Tab Completion voor snelheid.
**Drie: Workflow.** Initialize project, create Next.js app, .cursorrules, build components, integrate, commit. Dit is de bouwsteen voor alle projecten voortaan.
**Vier: Request Management.** Je weet dat je beperkt hebt, je plant je requests, je combineert taken. Je denkt, daarna prompt.
**Vijf: Speed.** Je bouwde een website in anderhalf uur. Dat gaat niet slimmer worden dat is de norm voortaan.
Dit zijn de fundamentals. Volgende les gaan we dieper: effectief prompting, hoe je betere vragen stelt, hoe je iteratief werkt met de AI. Dat is waar de echte magie gebeurt."
_[Zeg duidelijk, maak oogcontact. Dit is de afsluiting.]_
---
### Slide 21: Huiswerk - DEBUG CHALLENGE
**[11:35 - 11:40]**
"Huiswerk. Dit is een DEBUG CHALLENGE. Je gaat een zip bestand krijgen via Teams. Die zip bevat een half-gebouwde Next.js project EXPRES met fouten.
**Wat zit erin:**
- Missende dependencies (package.json incomplete)
- Syntax errors (typos in components)
- Inline styles die naar Tailwind moeten
- Import statements die broken zijn
- Misschien een API route die niet werkt
**Je taak:**
1. Download de zip
2. Extract em
3. Open in Cursor
4. USE Cursor om ALLE fouten te fixen. Niet handmatig repareren dat is het punt niet. Gebruik Chat, Inline Edit, Composer. Leer hoe je de AI gebruikt om bugs op te spotten en te fixen.
5. Als alles werkend is: npm run dev moet geen errors geven
6. Push naar je GitHub
**Due:** volgende les. Zorg dat je een werkend project hebt en een GitHub link.
Dit is echt oefenen. Debugging met AI is een skill. Je leert het nu."
_[Upload de zip naar Teams. Zeg de naam van het bestand luid.]_
---
### Slide 22: Volgende les preview
**[11:40 - 11:45]**
"Volgende les: **Effectief Prompting & Iteratief Werken**. We gaan dieper.
Vandaag maakte je basale prompts. Volgende keer leren we:
- Prompt engineering: hoe je vragen stelt zodat de AI beter begrijpt wat je wilt
- Iteratie: hoe je feedback geeft en verbeteringen doet, stap voor stap
- Context building: hoe je de AI steeds meer context geeft voor betere resultaten
- Real-world scenarios: fixing bugs, refactoring code, building complex features
Dit is waar development met AI echt wordt. Prompting is een skill, net als typen of debuggen. We gaan het trainen.
Tot volgende week!"
_[Smile. Dit is motiverend. Zeg "Goed gedaan vandaag" en bedank iedereen voor de aandacht.]_
---
---
## TIMING OVERVIEW
| Blok | Slides | Duratie | Totaal (min) |
|------|--------|---------|------|
| **BLOK 1: Welkom & Mededeling** | 1-4 | 10 min | 0-10 |
| **BLOK 2: Installatie & Setup** | 5-8 | 20 min | 10-30 |
| **BLOK 3: Cursor Features** | 9-13 | 15 min | 30-45 |
| **BLOK 4: Skills, Rules & Requests** | 14-16 | 15 min | 45-60 |
| **BLOK 5: Live Demo** | 17 | 15 min | 60-75 |
| **BLOK 6: Hands-on (incl. pauze)** | 18 | 75 min + 15 min pauze | 75-165 |
| | | Pauze: 10:15-10:30 | |
| **BLOK 7: Afsluiting** | 19-22 | 10 min | 165-175 |
| **TOTAAL** | 22 | **175 minuten (2h 55min)** | |
---
## EXTRA NOTES VOOR TIM
- **Checkpoints:** Slide 6, 8, 14 zorg dat iedereen op dezelfde plek is voor je verdergaat
- **Problemen:** Bij terminal setup kan het 5 minuten extra kosten. Plan dit in.
- **Live demo timing:** dit duurt 15 minuten ECHT. Niet sneller studenten moeten het zien gebeuren.
- **Hands-on pauze:** 10:15-10:30 is VAST. Studenten hebben dit nodig.
- **Request usage:** controleer rond 10:45 dat niemand meer dan 5 requests gebruikt heeft. Anders raken ze op.
- **Debugging hw:** zip bestand moet express simple bugs hebben (missing import, extra comma, inline style). Niet complex.
- **Energy:** rond minuut 120 zakt energie. Zet muziek op pauze, trek grappen, keep it real.

View File

@@ -0,0 +1,149 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R /F5 7 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
8 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/PageMode /UseNone /Pages 13 0 R /Type /Catalog
>>
endobj
12 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260615075148+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260615075148+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 3 Docenttekst) /Trapped /False
>>
endobj
13 0 obj
<<
/Count 4 /Kids [ 5 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages
>>
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1720
>>
stream
Gb!;c99[go&AIV:ki-,\,s6L3;aM)u'GX0i74u<5V7%pjUesUE%=?07:QHcJrUu[*/m.S[/,/\rS*mJl)%>cKg>>a;n7i(Z?8Gt)N2FB1dt,nnpgnR=f+%$l4j3Ia.qIRBSf,%*LX+;fVWCspj+:tM6T_Zj$uPqH._-^nCi.1E;,/Gh@"C4E[0?j(YlYlaQK?$11;]cg0ro%+%go:VcGfSC;]*bfoM#/Q41u.!j+YA[J$t;N><.D4D7-g-g9t_gh^X2nlRu7q)1oZHKMPJqB[SrXM8D)==O;[,lM#"T^iP<B;<-gX8lRJV1,E//>3&l;"6^i.C^jR5!\#fLdWsDY'St:mEs5R>d^">4MK*U-i_"d8;Etd&D.<Ygj2\$_;1iI%mi!u(JX.CF[c=n:Xht4#[2/NJY'3/J$!=uBaQ6:f>d$*Sg;2g.(N_;%^^6AG"CV97=]>[UB;AJ@H4f$q,[uM2(O.k)rp:4V_-+JDoTVmN9rhLG2:^:+#%/JT6]KaV6P6)N&]Fr4)fh8)RA&Yo/+L(o/gqGnUkc)9as_o3\4tRnQ&K<''kQ*?LbY6/%/4DSQH.X[$VdB='>1?nN6:b]=V'_VoV%>Y\]6N&HVrolSeb%[`A*ol/,+c7EFm39;JWDnL=92dof\0]9OtK*AB_<5#pq(3_VS+dF7L@Mb\p/j"I;d(BDI*g1GF7"&`TcSI_@IA=K^(&In0_=:V^8*g'9.O[HX5X;TDDU[2bD+p2L*\#hB(W[Pg?e;-Q]08C/n"4-JUPo46j*hNgBA?JTe?=ZatF7-D?kh8I6GA2BZ_P_lanri4P9[7E=5#p*[<V_?B&L@&1aDX8.W:),s_V]37PM&d(:b^BGhP;Y4&`WESWlSM4M>PWVPf!9*JQH)rKL;IW$5oU>`C/dXH,qZ-c$c-U.l%QjuWYYh"pLd[JE`nssUB0=u4VSrR?%g(5+$R@A1if/p*\/-IQDcPiiY,2B_=aO+g"KN;RQ^")ZUZ0Q`%VsR1P?65f)jbT%4A`'jV2;^6$;`>Xl8ps?J2nDiZtYB0\X$_%c<3A<.J&/mp(,(:jB/XeJ]oI,%[']eIh'&6l37RCIU(/;0<j\m6Q:Q_4oA[QE,?q4ViB.+71kml[1iG^S;8FR$..0iV*p@Cq7HeUZ(."1d!l&9=H_D1!g2_<p/+&do5^*-)#6e5U9Sn'ihB[+jlkQ8Xa?W:3[4(bb7Ma;4VHV/tV;,16p.mXu`,;Jnht%kb7?"?XL&J%Plf4>'7%^>.A_h^F2<6Ula2A'XU:H]I\Q*^."cCq*,ogSAkWOGgS-IR>.i5;YIN\cj<2D8_c"s;FS*nP)BR(M0[S2Rj6AiS)PreO.+KFQ(2>`G?iN*$?)$sOpalL4n$B2$dImf";g]4:_L435:h%Z$Rs9fKldk!'XQgue^+Z<GNi+iQ6es@amt?e;RkB<oQW:%*+jM'BHJ8;XeE?Bp`G7]QmZuO"^MtH!^&K,ZbYF<k9BW/LT<;6hZ4caqeYJ7(qbCm^Os)'kBU2C+>co1=r4P)_GglrgUk=[EHdKC[R\`_Hqer02'k]#:H[<Oa`oMJ?)E,*k]_mXmh\WelrMmn4Id">h`o5+,9>9];S@_cUYG1u6e\pT+aJGC2!N;SM1.uL$CN.V'8EF4\2r)FLrGoUA(nUE'rHs%'t&'uQctUN14hi2TG@-.Ps:jGK2?WE'n-m%FU\n^#Rde5\][ulo+)Z?B(u~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2126
>>
stream
Gb!;dgJZcs&:N^l]):W*Yco`<G$\&]8-lV>Q4ttB_F)=@gE(\uUE,>1_Ui_P8@$(O=`Gmj(3;"-l06CUe#2`09/#1NO&LR),<5PZK-.mB!e03!0Y2e\UP"%+r9DC1hs1f*Tb%)fi!k"gLsFE&?2(e!*n1t:c9eaVIK7'S4Q6PCi&L"Q(Cn%80E?#.g5jC4^\3V68BGI?gN5WeDuAYUG;,K'kRW$VGC"*Y$%9#?qZHlT>u6"<1%%7_IPjLBkGPOPbpPVcL*KA(-O+UVk0U^HJj(2G\L\.]qj>cIb2j>-91N?`/SB]B^NsPY-?g4/jJGIE>%7jtYZ+6$aklLk&4UT)_k:5jot[4bYSKkn7pj-Um_(GE>@t>UCLdW2&oUL"A_C)og9=rtAkYrKmO)X"Smf9^R*e]+'<9A\(OT*7WEj8"$X("@Hei'RYOg+Q_P!4'jM*4\)e/Sf&3cF=$oC5jA5hD!mMHU5cgmIBM^%It>0Sc[TJD*KPKEUONoT$+!pBkH;]J;6)"i56NlN4I#O;V`kHr"Ll+f>"PR_&pL9XP_TH67N'f.O6TXnnC.kV7ciKV]t2sVETr9f!*r6gAfTDRpo\KhZT:S4^QId,5j'FU2CHh]n0nJk:2SLY..m9K,`F7:cjKK&B!gV_n;^5'=<eBJjnesrWF`J:!J+>R+t58ilMndZ*@dNP8O4XC<n166'aJHD\"^qJUBU@(<c8L-C-]6:,)H11o9%UnMppPbTF`>D?L`e?1P,3hR\H/8G],Q+3I:M.h@?&m]cLfK$KiNu9;JV!<k9iagF$F9*sWY"er_TX1!N+nBT=:q.%GSBe2WM-2Xk!EOH.3VM";q$:Z'U'm.#[\HF6&DM?EcAgj+=_dMkE<[q_G.*hVBUqtINqP^YDt&l#u]Jf<PV80eipFmZ7I[k"Gj%=b64-4*.bPR)l,>0#1%Mgg;+.p']dQBkEQD<gfFN"@/4$e5:JF3"J"-f2+9$H)E_MmirWoZ/2Wtp)mrAhhjaS"oCgRF:/mZ38H@$Dg^Rj?0N(McL'2ZG"eRI+/7i(.=J?*NK^;AdAo^t.Dgu,dHZ4Bdr!CUZoCO3S'$?1V\pE1Ne;;b*a55Z&Z(.%A.[;9u0NEk6Z+q]3$O/9*Rmhm<(k_?PEoK'O^.HG1h(2SP;*rse4"$%6[`lF1::\,<Ua4mpMYJVd&'iWtOh)bA+hF$kiY&:KLkF8MQsiV,`l<YG(g@jS*).oNF.;N$@>:LJr7tH:QE]A7"W&@I3`U/peFK"]6ZB2n^m\PhD*&/qYR:p4f4L?Vel>/I[9q)>Z@gHk:@(UIW;K;%fPi,,+a##/bf!3:<*nkMN4omAbHa(*7+`$=%)'^Ci,po*D2JYpC!B51h7-e/_JXJa`[@iriSa)Km,kGuC"UusNF!M:QEF-F2,5`X=tAET3X*Wa3*G9phj+[r\6^$51:@!HX>FN7G(1n9]5PH,4Z-eT)]&s]6<gH><dab&[$Bt-HX]CMaQ1lP6)&<iXYIej&BXrt9kk7?gjlj1]n?t,UD:n4B4S#SU'4%2O-W`s)"[RWU>;KjN"W$D&dH;^`]3'X5-g(4h4oQfrs@bljkkb9EmmXHlEq7,e_&u$m&mGLh8f`"aATCM/6QkDV8<E>(d(dr0-<V[l/SbO"&YdH$;F:`D&0"OP@IIR!>L36Ku-IfO;3Kp2EleX1jT,!*?Op@Md#.AACE)bh>U(/$n[nAde*VRKhee4AE=iJQe4k`<SgJ-hmhcP+mcP'AekV(rDjS`Ykt):RdlEXV-C+YFu2\JQuug(X#5V7.K:#tM=lbUK0@j0"o`N6JeWTQI5g"7QMHfV1rLZ)a[6G\(PeiqJ@&QTrDA>T8Ni4H_We?DmCb2&UogP!/A6A-`(^oS=)-8WPr7DDb%U87dP+8u[bWnp#8+eR$L1rtBF[$u8P8-p25N^K8Q[`B!o$1%,>M<@DqquUA![r8mN?'j'6ri=\_g\J\PAD/024dh^K?eqG.M!=(ktBpXdXrlnQ7H1rsjRUrU7R2)$]IJp.tBtWYIg]b.d@[IdbgB>@4?uB\=aF?KCm,1*MYSZ*eYlVAh"40POPjL1GpfXpq)\&Ej4OJ>iK_I+^#Ur99Jm<u1r0T/Bi)hqOhr3;=;'CDn#~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2205
>>
stream
GauHL?$"c/&q/*0f[8IZq:;62MY_:(3c4_Ueub.5'2A%l(Pm`X80l!6TU#<(hm4FS/!f6MnX[\)V',kKEVAbP!s[iH-ZFRqSEQk2?+^Lm#\h#I+S$_F?dl4MBE((%g6%TGRp:]7!DQ&tD`7;6ben9k%!Kt6X;nQlm_u5g!hZ+IL$emqV;X'sAhVGC^2B6X",9e.3g&.E<_E9%Q>Z2k.f'W^jN>n[1_0:^PL=QNes_KEXCZ32pSdn;3Rkh6F"CQ\3YcNdj*73nSj*N/1MhL:Gjt05Bk9cRC_!q)^oh7fo,#;KM,B\FZ<Wns.YPg]8./9m4)NJAoA[?_9R0LdRB/"N*kE2k9U<b_Eo<'Q=lR&hV6$cF!,QZ;d<fBG%KrDJ0[qsI`s<TV8h3j&5dVq>/kB`W<*mWScSRZ4b*&$SaY]9o#F:Qfpc+S#k?d<5#]L`o/VQa[B*SSBq/`-TGMQ[bmR@[m$aCk3/[VH:d-)5F:kF\>OWU^7Xu^;+*j4SO[/^bg`725o/Vk\FH+3$HOl>P,r'ZFt@LjRV]M6@*<T7'Q!AN4TUDJma+$KNQdj&Gc5`K#oc5>X$:RTn_KGIuUV;DkQp"X47VTMlT7%E2T,e.*LonY([=X)a,d5'It?!0*TbaI5.E[*kUqH]DA;RnXW2ITYC=*SQP*8+"sK#6aSWW*)]HLJp"W=\`n4PW@,`p1^4W'%B0^&X<1HLs\7p"1M'nam4co4WYm`$=5P`Y;AjSMW+KJF8<Z*$QQ5h;W2/`+-T_H=ptr935OrXkh*8`=l)H57AGWA\?VV\LS&d"9Q+0j]<4TV:;o\]qf+"hG;>OSk`/;D`WrS2M?6(iZ$Xlp>Y.R=JT(PRdHm:]A(S6pOI7)HdL8S1_.,'-fHm#P;es+UJ&OOq5%9M%sS[[V^I*:"l;3EB#3>-O/!;7rp[a>mWbmDm[/5i:aLd(ocA3q'g7@#*)@X&V7lK2fHRq4Q55Gr=]X`g_CBLq#q*T>T%qeR7a\sU>j-[BH@fYW1RJQ8D3[jEc9tYu()m8u%33+`]D+k8)`"IH-BVnP.C.*SX+MI50_S#k#.:M:V5WGYFK[A:E`l\t<b2V`3..#jdRTJk09S?nP2ID-aD]54,hT$Vi#i%Z,\hZ.8PGC5JF-IY&4^R/8u5Kg&9=0`.gN]cqin`^T?H!<)u6k'hj5kEF/@?gPCra\>'T]>2rtuW^aRV_X9C*YgK9,-_X[+QkG*o..LrmW5DVM0i?oAE.I)Pe2qIG-Of92!H*I`l.k2#3"f*W/k$j2Hm"DG<]Pd*1]$<(PRd6%$)52+ob4a/&VENp9p:fYE6=K&L$Mc;,<FNSi`ZB"qab%3Jd4+@:<`7Qg0_GDL[HW[Fad>@S4odG*#4sS7ac9sh`\?bJWm'*HDbTRM=OUIT<+co@mLet;g'd*pGrGHU$GYmi"^NWaJ,J9@WRDr`XbNR[aq5pGAud^4'%`Ni6gd5oq`sM'VZQ9p^BPdA@gG/g;GuqIDpa6q[C16WWc`V5HJG^ISb[Ek^8`dm;q`>^6[,^"c!TqCLkQ(:=N[aL=$%%8M()`L2d#.,98jeYi&Yl=aCqP0RK_jiMtOk/SGH$tiAHK%)!($OUgi3Y<Cm)ZlJeYK2l.s&9H3ea*IV(k%GT!g8UVtg`J1CrQi>S`OkD\;CBEYnplMu<o<6&U-EegT14e/QNgFL?m\D\N6G&M(f;D0/"P5VGHOT*?qe5\&qR)*(L_A>?Wn84b7^k/)I^qXoG;J0(YUEEgil>\Be6I^FJ'96X0t]_mmEVuK907f)=Xpl>XRlA,Tommod>&=g@L*X9ag;]p1to;nQJYTMo?tbT(-RcN-Vm&=Jn>t9`h6%)L'O5C&JF@V3EjD)a-=[R>)W"721s=-Cdc=llU5Q5[,'^6[T'^[roD/C8$*ff*Vii2cneoc"WG7Le(-tA"NaVq&FctHrT:2,.6pDu!Mg3<2]gNP]O6%`VS?%%'&NB8a(\0F;ZeTuUL_4]/+F/<d"Y)rOa8N$D86].5rY%r3qaE3`S!6bV(U1o[$eJ6_@iMP\-dtVR/*B$^Mlt_-P]dT1(I#M^O1D$SBG+/3KXNnP<-OP/BAc6B$b&@O@nP$XnhX[d\+7=p';q'-\9/K)=V#7b:o"3=@\<knB6/TcM)/UHrF;<h%G,o)I@`r-p6lH/k>@/<ur-;]8B[e[b-m4_U^ujfs0s>fmT+Wm!mf#eNE~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1134
>>
stream
GatU1a`?,q&A@B[\oL\((+hjXIEO06c'0T@7'&E1!eDq\&dWGAd'<QBe)2E#/h+(^U,EXt^@$I;_ji?oq\Bp^Y8LRuIIJ+b)t3q_JF"'Na<pp^r'<)s]I7>?rCNBCK_'`dkh'AWMh;SR75A5G%(p]@?=8YIEdCRa6/m5_OsVkm!>pL74#6*(;'lQDa`qM:-.)dcs,L9!IaN0SrIj-s?*kQ@9^748Fu%6nh6$efr-;?E2W.A,`r\ZeL\/*.kpN7X'!Z`jfab<3cfA1>H"dhc#P"'G.EP_<J#"deUdr0`Ud&hAdb`7bdJWf%Cq2%^-/X@#5?\i$EeMH@3@YD@PVoHkY\;a"Y:ESF\BDH5eS1Ke^c^tBWVRZh6#L\>nD;H8?,%qXK73uUM_0OBa;)p2)f5_0hS?^&K4u/sT8P&d4(H%5VU5TIM&&]`T[ZEu;V@dY/2ro))Q^$e>1.;]'>IiXa+]n0ARRD8Hrm!j]GEoD"Qq;cFA;;6h^Dk>QO7dda"ZqUbP6B1FB(u@V,hP*WJPK&,JPku$Q0F4IQ#(i'r*Y]C;$;n'`)T&WA2=4:iRDVr#@I[D-ch8r0LW>'46L5T_5dk4t1]&po$8>WUVqW#;LdFjZD):d*T.'NOFDAN5\.\X,"]("eQ3@86Q=9cGeCrSL6H49IgE7kKs?2)[dSB1RT7Mm0tU>@Z3p']a_Bs3tujM[&3ULk;9q@ha:nlFW]l#pd54Fgpe[$(O^H?cVG1^TP_O),Zsj"`Pdk6qrP`6$fTiK*E5g6l#%fK-D.p9Bcfc[gdc?XAprS(UXK9Ai^GTLqH8@Q3sbb'UE/;N-`dWQNj9>M7BB^]7!RFmV-V8b&%;[jE.l,s"G/K,c9:ggkPh!@Wo)8'NYicH$qmG2JaB)MPqZcd@G.ZN'>I^')\6!#Per$KoB*&g#.g^^XY"*u+8Q'ef$Q=ZE^=8k7g/+rRQ&J)=%oUY>GB<%`09&\)8!e=%nLI-cu<_.C'?&s/W!8G?5@sIN%0hFRuN^o4I-bmKSk()>BqlP.LQ.[<0UX'J=tnG>31:sNf!umlBUF>5?g3C[e9p^F9c],JfAsaVLY1@f\o@R["(ZBJ."ti6f#Mc=8artAogQe3%\;o[-ld5cOVB&!>N"bR/~>endstream
endobj
xref
0 18
0000000000 65535 f
0000000061 00000 n
0000000132 00000 n
0000000239 00000 n
0000000351 00000 n
0000000456 00000 n
0000000661 00000 n
0000000738 00000 n
0000000853 00000 n
0000001058 00000 n
0000001263 00000 n
0000001469 00000 n
0000001539 00000 n
0000001834 00000 n
0000001913 00000 n
0000003725 00000 n
0000005943 00000 n
0000008240 00000 n
trailer
<<
/ID
[<c26c46b5aaf48c9074c79fbcb3fd71b3><c26c46b5aaf48c9074c79fbcb3fd71b3>]
% ReportLab generated PDF document -- digest (opensource)
/Info 12 0 R
/Root 11 0 R
/Size 18
>>
startxref
9466
%%EOF

View File

@@ -1,231 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 6 0 R /F6 9 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /ZapfDingbats /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/BaseFont /Symbol /Name /F6 /Subtype /Type1 /Type /Font
>>
endobj
10 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/PageMode /UseNone /Pages 18 0 R /Type /Catalog
>>
endobj
17 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260224202513+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260224202513+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
18 0 obj
<<
/Count 8 /Kids [ 7 0 R 8 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R ] /Type /Pages
>>
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1236
>>
stream
Gau0Bd;mr#'Rf-pm_7(d&X"]e&oU?"h53h9d^n0L5HGZ#@KJHm@FFE,o\4_(C^1'>Ubb)CK!Fa/mQ'Iq8.6nq=RgmR!&tL^s-<]G&:=L*Lk&k=QU0f>_obNHM5N<<ht)X>>[gn"ma*q'p%W$P#d(TiJT1#baab4G"Kmn^41HQ:X6:u<(0CVCY$D>n&JcK,1"8aH!PnoM6^r]eJ9OjGaa5rQgajgU1qm>1Jm\f!CZmY9Z2D4d,q@/rp4R15TR9q(^Arl<5k'<'K#IcmURLu3=!`OkFSu=u$T=U1"I<1OZf;cOlj>Ar=e96?eQA8sX]VB]6o27Z"XR]na$#NUWQ#O-OfikH/86Rf%RDA-+;Ol)LQ!C7$8Mur?6aK&'\,J$"u/tQCG&6?7DI2^UlB8#_S%N:D33h#>ndDN$aho,d,L6`U&.>pkC\A=kNTT4MCm77Z<p[DQ60KDcE<$E8)3^;h&oGTq>P3"S2V#3C_8&Br!f0/ku2IUp^bO*HAWlKOY6Ckpg>2q2IGS8/Y49XY?flK@0Ig_#Wu7+dr4P3M5RbM[=!0M1('Wch`%[LatoOnC4mNjdZ@%_<_MZriqX9'guukM0XZ5^M<*/b7$mae8VQab<bA+9h2fD*/[,87XtDqeL>*4tmk6<q3'/'bTD`mmC>_3N]u109q5ri`XiD@i_^Q;C1VIK>[bEM7\"[WS,,C^p86*AnYJC*;G"X,DK\]qI036d(pO@AJ%U\T0\Hd]^`+Q)M5d!gErn+485D40gp6-Y+8'qE@KUK@;@UTj#!ImfgYZ[J)IL:WV:40)9<K[K55YLG[=pL^gB_2k^3&>Q_i7a8aAZ@Y!Y,dg1=u-7D:K3opI"fh*d4X*`C#LdDASJiCUoVMkVX(bZ:b\o?igO<4V!^8^6Q_&&ZMrS#&f8%[E=;co!h\L`JeitJGFLeLm,PdMc]9mtF7\`X[T$FUpl4W;<KjKb3@Ds4<;[e/W+5(K#t6kQ\p06Rg:=u34!g8kRkGg0E<b=tV.8]e"hJ9Kr)Gh%G<@l(F&\.JG`oq/H7EN&!GA<Nl68Ca!_/_mGe\KS!f^Y-r+#@pq6)dE?4=^?'-;4?(s_UhdS$<J&V_%h@FupPY>9,[ai>7U4L>s'&bYYnjI94B]S`J0S6NW-r#)\]S[()CK%iW&NU,*',CsUgSWcU%0A'$BMH<:fThOIFm1.URMsTR-0tR2X$JqUXRud%.#.&T#)bokD!k))JFFF4ST=9C~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1186
>>
stream
Gau0BgMYb*&:O:SbQ<33".84VP[Ck)_qAQZYS14mYWuO%17bA*,t1k'G/&K]ZL%?ib=.$`O\$%<kF<D"LB5@Y<rJ*A!.P-u]k"o<">aGK8-j*@&@A$4XL?W5#gGAS8e@bV^hpSRI)UNU!5I6$JR9ulWhFZNV81n^.1B&\*A\"UL"So\Y0fEZ?[!X('b">G&BrrK,4&Vcn/Z&V3)ePOYGRnR5hs^GJ'`WL%rhnq)OKn9p]A9c,Bd/)fH\E?@>6L,mbg-+lh..kX]?&)2Ep0j<GKrS9s[5;\&RgB!jc[nL.N*)Am$mC\LeuL^\(?ReqS$lHFntec$s"iO;UH?%85GImC0A7k4sc87uJ#$BuZ#C7>8Jq-H82Zjti+AX:6.,&%OZ7#3WMn9&Z-sC:FePKVX8M:I3$\Q5#bYGh>QgCgLC#8\WnoHH:Qi-T>]6o^teB>+XhQ*$hq[f,G%=pM'bN4MBdAiQ,;<#N'SaM2F8GIm4ie:(<=jnV/F\m1]K(MkcjaC8&pC4db$CBd:QU]Ut%JMP&6UN0BSfSO.f]BnEtE)<]uPIS_PU_-RgQ(T)1\UG=@62ZV^uORmFpC<&?E5NXtbZffaP%59_%i(U9!LO;82Z_E+,M8KLEk[$(XqknDNI=%'M1AC83::NA`je\n+duh[Q1;7(EX2h^Y;4X]b$4C'c[DhCN=;\@FlKoX&O-3lH)UG+%:*%g4%$a>Y=dFqtlb[#4595X$.L%E.=-78>Q;MNupp\=N0If5aNE/'kIOr[TfBjnf'IJmoU?i1Yl"O>(lWqCTP2TVZJ4S0qTkoVG"P_+D]h5J#A>Ui(Ip)P"[OuK+LqO8nZ@Ea\7+p?^*^7MVptJ@sguCe;WV8Fn8XLm8!%qRe&?aF9PKJ#+L(!aP$@f1LH&4PT2=W]87NEJu>lf)V@a0mOClXm#o^GL4qh$r&GNs_8\TZN5/TsH^:IG.sd/WudF?N$Y4kL5iPZa<<CLO,a(-X*W/SsJ$_DF_dLnfsD;1r]P=#CoF!nB_:\^T2:8p<5GibiGqq*]HAdq5L6X5p@bCIODR>0m`#d%=qCDNq:e#sR2Imm7!&GVU1)Il+gNrq"T`0HLY38Cio,VB9)jDO/&+f=<O@XbrE+q$)bbkeU8d:)BHuMO/0`K[F99pW+G2UX.P2Ps*bgoU'43Eq6XO+8?$qB`/Gq*$eP~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1317
>>
stream
GauHJ?!$#V&:N_Ce@snC[f)c8[i&5u3NJf?G(&<qL'AkGM+OK&^Ye;V0bHG2Shl;f+JGg6>ISKM2ZnqeXF?4s!8g+&5%DAfKJR#!8-gm,2!dBtfZ$ke6tjl^'8$"B.h(`L^L_&XYs7koD(Xp69X+3N'RU1Ql\)W?kdtrWB<q-acOM)1hf7u"i6K7KQpqkZ%K)@/ea[mHUhL!Vk17DF5>IEA_($c:-hT-G?X,&3Do1$*D8@N]"e,9QV"CSmJ1-:^YZD!)cs-E6ND%g3`l?^XRochFXZP)?=1YB/D1FDUfi;`M*TnpPCs:9([,eq^KHDOum3.-[P!12F'UH;7-3I0n),rMOZM0bc-kf+r*[,3j)uD9$_Oi/2V(j9XGZ@di=ftYunM,ks,*Dgho;UW!f]d>aC^XmFG?Y4B7g5(;P/,MuD%l]aXZs%$qYS5^d!\mU%8r`]<Q>nI[e`!EJ:+dE]:?SQH5X[=J6D8\nU>&lo8(`ji,s*'9pAIPJFm1g;"oP#JRh"I)Yb$"GFcl1,+p&)-_*d>Npr`!0`0:4AudG0B5+T]Oh9s(jc3ZXVH9lW8f225j3TMT4^S81O1qdmDE;WrJPN[m%E]Chr0ZXn9G7e&RGXLdP+!Uc;ZFbPgt\3rA:_Rd$%T3J!C-.?2.^P1#357493h6sQuQ@X"V(l/c;n(aP<=b$M">2?B-srbh>b)L?W=.\6SgT2MPr#b>aN`uTN]SK$(Msi<"A;Rs"$_N:0kDfh'+h3/6_$WWKUBV3Q9<TVrQBU3\OJuUrVT2Z':D/p0TYBQVk,7ZUsWO<\i&hpR-k#Eu[*mYQLi1%_GPmjpD#kA/7VWPF^rT=1pE.T?^<080hrW=P;"*O/#3tH_]Fd@lO\%=N6PWG?Z1P'St2oCB<CrJ9c_S&E:[FXcC2V/)Vj+R.,W>p[Ya#6rHibPCNlHNoYA7&ERmXER_P9K`d"f"6CW\."&dQS"k8`&Z/o,=*LIpFi-!M(W[f(X>Ol#RYMUb(FGZ!0[J"Nf!-sYh`a/^JdT@3ja5,1qS`KWoJQB=_EI8_>e'f5iMUn@Us<$uQPIA+^mY0DfbAec*1WW2rjj"r+,7R,\f8KA0eD6oA$`>3b]EYi>$7L(MJbqZN?'[G*aRNWFJL+IgemC_3-Wk0S@0j=![9aaf=s2HbRr@/aQqm/eOKHGO"I6upGCVm,@g[lq"5!4$%*]jI05,Ljje$h3Xef>SI0R\qi'_W8mU&GHg;%ZD:3n"P,e=IJW`j,Y^QiC!guPXkO*"E)J(TPVI^J!D'V:brUV&To=:i04OMUMQWMQsaW_9JjO!c?UaG2e~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1397
>>
stream
GauHKhbW8l&:WfGR+nOE$Z>>+7oJRm30q6H$d3QYHd\lH+`Z7%)i#[Bp:n@^P(kl3<C#iX;O_nncM*;]hb5FXpncMZ1^!M'JGp>lPX-u)apEj]!mZCMLBH8S`'LD.k(7'"Gf"OL&'`&eV5$-q*d=TaJ4TQ7OH0oMo]$De.>cAu$HICTCD_I^+@7g!I3;3C55I<bj"Tf*^u86PfEN%5[!K#V%FmYLV'@.Ni=]t3KX=DE^[RNc?CDp%E47a/#8_"R>l>MTWL;QhWRa(oiOqu08TFDS2m]Rg1IE&kF'Y"6PT#E1b]WbP[:&LO_S*tX6hPf>"C6>lQ&L:d>mqJ^QGl>*'!;+@Q(2L/;6Qk(JA-gAoR`9jDa4Qs<Th@Wa@rV_&r+*b\j/#<`Y>T*^`OW#JR/Gr"eei`X^LZQ/F4lp=U-PGU0ZIdYMPIT9BbXpI*UpLd+PSYBpha]STd2B8G1jcm9_.'r-<MmB!gtbM`84QL(QI'#.]V!aFr?#XUVAR0[`%X.=?Uo>+6#k,&H(*]fK?5Y70EdCsM<R2^H?J'l*+m.S5:D\-q)=R:+KL0V;:ZJ*BN@s!:a,>bnum):'1/%TdW90@G9:kg&O<2Oo+cX/!W!8-n^e5G=#CM'>9g!-&\dC,&_VE#FE)TJ:A%.bs6d9M>djRh;%473;-VJYKn9O-IU>WT%Llf=TNYN,sW.?7)M?*^MNqD6Cm((ajL?0Ue4ea'!,(o\t-7SA=O)/5:GDJLL"b7ImB##a=LOP/^=_^SXR`?@(0uUW3r3("B)-&;E(#0[';q37\l)Mq>3-W:S%`X^#@!$KokN^?asFg_E@@/J7"3OM"bi\3PV>FBcn/="MRBb4pJYA"oSHRk1`rhk$4a,[+9/P:4l4mPO$cg,m1aSqm\H9b^>MVC2\Pj#CtTfd6E#U>`7=5O/KP9@NPZiJML&G#p]+*Aa=7./!_kN%$B\!a$UR*W<j+V?4B#$7o7/m1LkW;hI;ASQuYCX]A\r])*j;ko>V$r0#1*+NZa_"Z@X\85c@n^SBMg!Sl1=%)J.1>(pDR4N.=1B3'fF>]H2&M,O?(p4,`gg@nQ6Y-=;\&BNZa>^,'ZDtF_EfcCTf@Y-O3$6P[<n&FRI-&fULH270Jk%0b24.DS5A%FHq<2k:a-[%*Epe[6kM8>&R@@GEW&f#7\!ca^qH:jh[n88,p)3a9S^u%W%<C7h\KJXPbDA,/Z+*R89rkA3Y`<Wm<$nHAtSden'NSE5NTe>a[A[RR\r\c4!PdU5B0C[S7>G@T6#Y)olbcoW4V6\"ABcX8"%jftGH:Ph$^45,m,f_fB?o'2N*2">!Ou6^pM,X)Q8>(1#$ZPWI.88d]paS__Mf[XoeWTn0lK:0T2.Ko['4[p4i/_iIU`o4RCiJl.B4%\"~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1209
>>
stream
Gau0B9lo;R&A@ZcHKR\.M6LMn[SsSh$k&>"11;s"RM-LV.)`s1Lb4e3r;(8]0d"4s%U5r"\@^;VpXJID"U>9<r5KVi[g9PI$C!jK#a>u4d4P!cek:p%Q6IHS?jqG7,lqX"/o0;3do3I$^U2?@Xg\W[b(']IFa(WO3d*;l0o?:EUW^h"E%dmagCt8)8t(%!@+=W/Co0j*^ka]e*>RjNY"CK24+l`7U9?K#qn97j^`ug']I?l)r*$iM/)(ArmKVDdn4L&EC$1C^es;R<r[m82W,sQ2m@GcT^TVi^[%[BE0H9JcWrumYWtFqk6P(aPNt^Z1)hb<>mUKjQPEQ<`8Q6#prZ/Yo*6.9)!^/=f]/Uk%,7"e][c]IfQ[6G1Vq/Vi8Ed=?;nuN=_SoYsh[pW!DO5VM(PIH=-K*[.@BMbB7Ak=q2=aj\6UDlC=kCO\"2844n9t"75#Jo3A<Z9fcIdnfL^HaWoD-nV_mXj*4E+Rc-P#fsn!IaHGgFL@^O2Ag$;0E\455t<\:iK]?*82MR53\eCo%:2ku986?U,7i*:LAZoQ)cKl[VQc8o[bXdZ@J9m@UCH5@sDc?rA8[N5APl3eL*j,hChk1b,CmcNloo8`,\Y9]27<'/1?2.j&9c\5J%#7"^nt-t:?Vj@^^:LeTk$-huFF"<Vf7Pi&(XNJu+I(YK;3\Wg5%</100n=YXaIPUDmd]psHX?^LVdj1P:fr59aYV^tp;=c^Mh0?JeV(0UK@A8A"o@,jUVh9GZVkMZGc[;3*"?nD!.%IsealLQcT[[s*;cJbj\]Da1SW"Ko,\::\Q-+;62[p(8hb^dA@t\mf2g$;3n65iV(#@%m<\s@C`pltj(92b6LI/bHdQcN;OT:;Z'3&%h>)stu#D1oK'C>&?Zh;gKCpjEe"kA19X6@J7]\*);`3F09?$P#:]`=ofEajWe78=<i"1(<Y.no_0TiGS$3Y%<miWoI<<s:dWW:)JX=Kj\KPSPN7PTf`If?TCh]2[;k348s#"eejSa,d7>37\kWQKQUk+>q@P)&4NA#S)X5\Ce?Y@OIG,S@L_"OVO.pX+'O"a7/pXZ>[/>8Y.&c.Bk\;GQZi&0uXI"b-Q[4bBh';WN_&6%3-![?jK9L:&orWbP&fAKl&)e<TgWe)@3BlMm!MIlUp="npHhBE&K>E/V]2(j5'4V2-fC&.cVnYe"uQSU<nJ#&L4[^"7biV1&~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1234
>>
stream
Gau`RD/\-!&H9tYQrO%BZsr'D[2nLRV3E7(8P#d5pOs+]ZN2H3-%<D:rqi1m_$(_[-^C9b=F4[npN`?uSM:9aJ$f-7jo?Jj34khK)2+&9"VD;P6RQ]_2!AtVnd/BViTLs6q[D:!pn4!%JM?3m8V%!q&@b4kBdBOE<6EK`n,gHifC^<;nG!JP[6InsT$-WAnm"W]jgd@c3Z.TiFQjgk_>+_PmQd;[AN,9cn)gJdSjN2TiB-@Lru,@'Hor`,i`GMPeb5>a>?WOjX-Ln_o'B6u[tIod4hD7EmnQbIfj<f+W7-$^$th85g3G`+rtWhP4WhH=3]MgQJTd/f-9^8EnL@Ed$0=;%ai($`rM;d]Bu>L!Oig`F7jU9!<RG7+;fJ@"8PB#_^t^$m-Nd&(g1O>4Bn`:UP".iHAK[47W<+')lhLVRL"`YK7r[>%,gKgX*Wt*=?XarP$@YYTU4+`2aNWg,2!=A*Kj:N:2)%_Dn>M+fJ]FF3@1o-lfphpqE0!i\dp6Z1Bi*QFPgBN1\&`%^XCmp;2oFT1>fQS>0g`tt#'9p),+J#HBG#L-o)cI\MXYn.5A%jEbC41u:FdH['Z6t[*^:sq7;AE9KIP8Q'VeeNiBq471t'.$E\<jL=%g*iC'Y_+/;ch<)2V1cM\C1_e1R\"UeBWFQ4`IhSD1]H(*eF1#Kng%NAUeb<?;*^"5N1VToT>_3&g*2s6BQLR8'pPAU.HIH*"@N]W"[h`Vo0T_+$jVc8]e3qZ7JOFsV]V.DS-dmH@0sZ'K5uXS&D:^/Q7'Y$K4d'a`n$UT/30oaP:nZpm^ub'%.#3Wa-*L"a+Rjh&EsC2GSETRG@)J%k5kDaW:Qg1IEpU8rT\nq2&Y4!`l$;o_M<j.\8'X,$_UXae'&[JIYU6`9s`dta!t5Q:$)T'Y_.'WVHTC1m44(>mPhUdFiaoE5d_!SV)AM`B;&gu,lt3Q;Hi17_mrSDgMO^lq6u?4*7bfu`/oDt6guf1<Q+I>)nLD8l*c&smp/(@a1s-(kn]7jWTeNjX>Zg"p5&_XgIAM="Z;Q31cHLWb,sm!^tJG!1L:j2[29MKUqH=UL]S2X$<&Q@Hf?#s?6-kR:VA<PW!nJ]-4Dn-@CY([?hOmntsi#jB1Y6lG'o/<kg0ZJBbl's+FPDLEN9lQD':98F]CQ!W,QSSJpoT>MCm_dq"u!klQ4*CeKt#$<_?[eJ.&.l5RU1c8lG:#6=,p^IXH%3%/'"Z1_aXT~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 941
>>
stream
Gaua=?#u_o'Sc)T'k_l)GK6'R&qW(@['d6C[U!OL]V-/W"_%Merqnn[ldW'I]lA4_JI*LAo=g!$&dB%'<o1^si,Stf)[;8=BF;s0/r]YR*AsE-D"FK&`0$RB-R9;JpMa6p@:-dm\,FV^iX>1d%.G^2FRU_bO%RK*N>/lCkrO\-JI=7M1(D_$jfF,T-DV0*8a7`@Li'/4*2:0NM%^.3-&=1hYK/;>o4f<\*B<:$%Bhfs(,4-TZ!+u==Vj+\KCap+Eu=,MJh6JV;]%)oo\IZ<fIKNc8R/+e+j!&rKJCG^5CP#k-P2YlY->dFIU3>h_ZW_hmhg:5EBHkD7Q$Wq=/E*K;qbX`8YS,:pn/ShPAE#njYsAuh0sPJQef^Mh9XL%NhAPF_BFkYpRiKrD#9hD[-[b3G?+I!/@=Ih.GHfL::obT.%`_>_B@:m='0$GTu-qD3Y-l-eAN"kF91XkXYqGVHH0X1>%bCfi:f!aj1KeWs-T2q42OY^,)Ba2ZUlaGMa9N^-4*>jC`R:!37FkP/6chb9n[g`_2]!#.nZQ^#\(Rh1<I\73\>H2\5-`8q?7hKF)8^Of_SZL$aYH;KL%^ZH-pU[V6ti6J+Su9N=+uW&f!=KB]cTaZAq%nQ9=Hq[Eam`O/'Vt1g<Jni`duQ=$E<!oa_c\:46$k\,TH,HenZa%#3AX[rqA.H%JR5%Oki'Hgff5K5CnhY.%L<+j>%)kZ3RF<@bj*Cl1e`FDBWC%f>7ST>N:*)9,q&6hPH%_^,^"m]A-'b9Phoe2CJ`0Q&3AX3V/P5K9T(mPFHf-)gun'V3:sapUb77G?I3#ds4WgDi]i<E[$r]2T"8\rA:]R4g:^`5NCF^nZRUpk!oL6LmjN\:e=eb2YmrO/9YV5VGYUH#&eaE&GahE*g<ss$6K2rnKfcfNi919O,9D%q\ZWJG4U8)Lhe(j!u\~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1266
>>
stream
Gb!#[D/\/e&H88.E@?RYCD!#28;bR!2G9C0=hU]dL&hH9<MCLFQK7>-)>`ZCP[r*YR\U@E#6?`Yj4ukBcU76@JE"Q5h_5(UmDJQ*"3/0L!u"(QIg]?Wg#k0BY#LiLMK<`j8$pbU\tPD?epMIl!SA`r#!]qK*#Pg<BcCoPXqJgV'I*,Pk!?4&0>7Vi!W3`ld<iNd,id1sA8EXHoSqR!rfKiRVqYQ=J]iBRrW1[l*Xot&*^!DA;&VuW'bk3jPQZF@_Wbp-<e"Gkl7mmOS@e8`S4;1AE<0JN?aDRE3<]/P8k`F^(8eH3,XV]:fMT9O2YN8=c6DjaP"!pL'X5`d:J6=rqHL(TZ^(&7dCQOIm]A4'C@tUHo*!A13UFP0?Frq'K9]'!@dfZ,]jBkn'^%/%qAoG`VCl:LMCoC7p,;Va**cNiOl\!mTU]>#aXSf;)n?7j`oJGAYo+[4WLT[8s/kqRZo!&C8pH185?F^1$cl?[el@ec9;XulAlX`ci!t_If$IAeqQ&P3BVrJg*`KbWO6c?1EB-MF6M%<Is,jt/JH_db%[C#pm@*"j@84aO:]dXCM$o+IdY81)#ftmQ[uZW:"TQa\Ga=_k5]&[MDh:eR\@bc1JoN!1EkC[uit/5UZ?@G!iiWE9gJ^WI`8_p8>P?M!9e'_H0lT;8\<L_B(X-5o:jY&Ff;sk@$)j']bh40Fj!iQ*,>"*.=22J@]t4(N5$ZG%r6(`1%m)Wj,3GIeW(RZo-OKC/QJb>Mf5@@ASt\1Z+I"!P1CqbOd8iXt<sdK]f$s6"r>,s0eTrs:DL'pI]g`to3YE@g<a'=AGLWnj].k`V'iXARONu5X*)PAQRNDqMbSL&.6g[lU`hl\kbf,-eHK&YDgS4qL:nLFP=WB&M'%223(-MEd,&_gH]aTo[9oM:A-MI5NpUEdh3hUW]%)e?1o;E.uZuW6?KM,;i!+($2I)p;Ofq$meD^,n7R8b6cTiJ.NfNYKOY[RUcJAnYBbaK!l11.c.*ROZs_b?eS5\Z'aoEdpf7te]hXg=OZXIB6$SJW]:eVt3H\S=6.2sE(m%9u>ApQ3/KBq=181qi:Sl,5!4V/DCIHbmb/jI88gdpP1\8$.uFOK%.7R6!&pA?$RoZWrG:.ej/?osZbhS.4CJT<\J'[\'>p*Q%RDeIEJA@_.u8M81h_LMJ(FS]%muiWHWU:@WN,(PFR.eu:%+ptb=B^ib`.`VD'=fdb-sn@i^&f\ar&^4GTd@>aP<bM'/p++jab9$gcZiTr4cO!+&~>endstream
endobj
xref
0 27
0000000000 65535 f
0000000061 00000 n
0000000142 00000 n
0000000249 00000 n
0000000361 00000 n
0000000476 00000 n
0000000559 00000 n
0000000664 00000 n
0000000869 00000 n
0000001074 00000 n
0000001151 00000 n
0000001357 00000 n
0000001563 00000 n
0000001769 00000 n
0000001975 00000 n
0000002181 00000 n
0000002387 00000 n
0000002457 00000 n
0000002738 00000 n
0000002846 00000 n
0000004174 00000 n
0000005452 00000 n
0000006861 00000 n
0000008350 00000 n
0000009651 00000 n
0000010977 00000 n
0000012009 00000 n
trailer
<<
/ID
[<d99cd050eea65d365d48b1f6682b5622><d99cd050eea65d365d48b1f6682b5622>]
% ReportLab generated PDF document -- digest (opensource)
/Info 17 0 R
/Root 16 0 R
/Size 27
>>
startxref
13367
%%EOF

View File

@@ -1,375 +0,0 @@
# Les 3: Cursor Basics - Huiswerkopdracht (Homework - DEBUG CHALLENGE)
**Deadline:** Vóór Les 4
**Type:** Debug Challenge - Je krijgt een kapot Next.js project en moet het repareren met Cursor.
---
## Introductie
Dit huiswerk is anders dan gewoon code schrijven. Je krijgt een **intentioneel kapot** Next.js project dat je met Cursor moet debuggen.
Je leert:
- Errors opsporen met Cursor
- De Terminal gebruiken voor error messages
- Inline styles naar Tailwind CSS converteren
- Missing imports en dependencies vinden
- ESLint warnings beheren
**Geschatte tijd:** 1.5-2 uur
---
## Stap 0: Download het Zip-bestand
Download `les3-debug-challenge.zip` van Teams.
Unzip het bestand:
```bash
unzip les3-debug-challenge.zip
cd les3-debug-challenge
```
Open het in Cursor:
```bash
cursor .
```
---
## Wat je tegenkomt
Het project `les3-debug-challenge` bevat deze INTENTIONELE ERRORS:
### Error Type 1: Missing Dependencies
**Wat:** `lucide-react` wordt gebruikt in `components/HeroIcon.tsx`, maar staat niet in `package.json`.
**Hoe te vinden:**
- Open Terminal in Cursor (Ctrl+`)
- Run: `npm run dev`
- Je ziet: `Error: Module not found: lucide-react`
**Hoe te fixen:**
```bash
npm install lucide-react
```
### Error Type 2: Syntax Errors (typos, missing closing tags)
**Wat:** Een component heeft een vergeten closing JSX tag of import typo.
**Bijv. in `app/page.tsx`:**
```tsx
<Hero // Missing closing >
```
Of in imports:
```tsx
import { Button } from '@/componenst/Button' // typo: componenst
```
**Hoe te vinden:**
- Terminal errors: `SyntaxError` of `Cannot find module`
- Cursor geeft rode underlines in editor
- Gebruik Cursor Chat (Ctrl+L) om de error uit te leggen
**Hoe te fixen:**
1. Kijk naar de regel number in error
2. Open dat bestand
3. Controleer imports en JSX syntax
4. Fix typos
5. Save en test: `npm run dev`
### Error Type 3: Inline Styles (moeten naar Tailwind)
**Wat:** Een component gebruikt `style={{...}}` ipv Tailwind classes.
**Bijv. in `components/Card.tsx`:**
```tsx
<div style={{
padding: '20px',
backgroundColor: '#f0f0f0',
borderRadius: '8px',
border: '1px solid #ccc'
}}>
Content
</div>
```
**Hoe te fixen:** Gebruik Cursor **Inline Edit (Ctrl+K)**
1. Open het bestand
2. Selecteer de `style={{...}}` block
3. Druk **Ctrl+K**
4. Type:
```
Converteer deze inline styles naar Tailwind CSS classes.
Verwijder de style prop en voeg className toe.
```
5. Cursor converteerd het naar:
```tsx
<div className="p-5 bg-gray-100 rounded-lg border border-gray-300">
Content
</div>
```
6. Accept en verify
**Tabel: Inline Styles → Tailwind Mapping**
| Inline Style | Tailwind Class |
|--------------|----------------|
| `padding: '20px'` | `p-5` (4px × 5) |
| `backgroundColor: '#f0f0f0'` | `bg-gray-100` |
| `borderRadius: '8px'` | `rounded-lg` |
| `border: '1px solid #ccc'` | `border border-gray-300` |
| `marginBottom: '16px'` | `mb-4` |
| `display: 'flex'` | `flex` |
| `justifyContent: 'center'` | `justify-center` |
### Error Type 4: Missing Imports
**Wat:** Een component wordt gebruikt maar niet geïmporteerd.
**Bijv. in `app/page.tsx`:**
```tsx
export default function Home() {
return <FeatureCards /> // FeatureCards is niet geïmporteerd!
}
```
**Hoe te vinden:**
- Cursor geeft rode underline: "Cannot find name 'FeatureCards'"
- Terminal error bij `npm run dev`
**Hoe te fixen:**
- Voeg import bovenaan toe:
```tsx
import FeatureCards from '@/components/FeatureCards';
```
Of gebruik Cursor's **Quick Fix** (Ctrl+. op de fout).
---
## Debug-aanpak stap-voor-stap
### Stap 1: Installeer eerst alles
```bash
npm install
```
### Stap 2: Start dev server
```bash
npm run dev
```
Lees ALLE errors van boven naar beneden.
### Stap 3: Fix errors één voor één
**Beginnen bij:**
1. **npm install errors** (missing packages)
2. **Syntax errors** (ESLint, typos)
3. **Runtime errors** (module not found, missing imports)
4. **Styling issues** (inline styles)
### Stap 4: Use Cursor Chat voor moeilijke errors
Druk **Ctrl+L** en beschrijf de error:
```
Ik krijg deze error:
Error: Cannot find module 'lucide-react'
Hoe fix ik dit?
```
Cursor helpt je.
### Stap 5: Verify in browser
Na elke fix:
1. Zorg dat `npm run dev` draait zonder errors
2. Open http://localhost:3000
3. Check visueel dat alles werkt
4. Geen rode errors in console (F12 → Console tab)
---
## Checklist van errors om op te lossen
Zorg dat je ALLE deze punten fixt:
- [ ] **npm install errors** - Alle missing dependencies geïnstalleerd
- [ ] **Syntax errors** - Alle closing tags toegevoegd, imports gecorrigeerd
- [ ] **Missing imports** - Alle componenten die gebruikt worden zijn geïmporteerd
- [ ] **Inline styles** - Minimaal 3 inline styles → Tailwind conversions
- [ ] **ESLint warnings** - Geen rode/oranje squiggly lines meer
- [ ] **Browser console** - Geen rood errors (F12)
- [ ] **Visual check** - Website ziet er correct uit
---
## Tips per error-type
### Voor Missing Dependencies
```bash
# Check welke packages ontbreken
npm install
# Of installeer handmatig
npm install [package-name]
```
### Voor Syntax Errors
- **Controleer imports:** `import X from '@/components/X'` (check spelling)
- **Controleer JSX closing:** Elke `<div>` moet `</div>` hebben
- **Controleer quotes:** Tailwind classes moeten in dubbele quotes: `className="..."`
### Voor Inline Styles → Tailwind
Gebruik altijd Inline Edit (Ctrl+K):
1. Select de `style={{...}}` code
2. Ctrl+K
3. Type: "Convert to Tailwind classes"
4. Accept
Tailwind Cheat Sheet:
- Padding: `p-2`, `p-4`, `p-8` (2, 4, 8 × 4px)
- Margin: `m-2`, `mb-4`, `mt-2`
- Background: `bg-white`, `bg-gray-100`, `bg-blue-500`
- Border: `border`, `border-2`, `border-gray-300`
- Radius: `rounded`, `rounded-lg`, `rounded-full`
- Display: `flex`, `grid`, `block`
- Colors: `text-white`, `bg-red-500`, `border-blue-300`
### Voor Missing Imports
Cursor's Quick Fix (Ctrl+.) werkt vaak:
1. Klik op de rode error
2. Druk Ctrl+.
3. "Add import"
Of manual:
```tsx
import ComponentNaam from '@/components/ComponentNaam';
```
---
## Submitting Your Work
### Voor Les 4:
Maak een **GitHub commit** en **push** het:
```bash
git add .
git commit -m "fix: debug all errors and convert styles to Tailwind"
git push
```
Post in Teams:
1. **GitHub link** naar je debugged project
2. **Screenshot** van je werkende website (http://localhost:3000)
3. **Korte beschrijving:** Welke errors heb je gerepareerd?
```
- Fixed missing lucide-react dependency
- Fixed typo in import statement (componenst → components)
- Converted 5 inline styles to Tailwind classes
- Added missing Hero import to page.tsx
- Verified no ESLint errors
```
### Checklist voor inlevering
- [ ] Alle npm/syntax/import errors zijn fixed
- [ ] `npm run dev` draait zonder errors
- [ ] http://localhost:3000 toont de website correct
- [ ] Browser console (F12) toont geen errors
- [ ] Minimaal 3 inline styles zijn converted naar Tailwind
- [ ] Git commit is gemaakt
- [ ] GitHub link gepost in Teams
---
## Bonus: Maak je eigen component (optioneel)
Na alle errors te hebben opgelost, voeg je **een eigen component** toe:
Ideeën:
- Een "Newsletter Signup" sectie
- Een "Call to Action" button bar
- Een "Stats" sectiie met cijfers
- Een "Testimonials" carousel
- Een "FAQ" sectie met accordions
**Hoe:**
1. Open Composer (Ctrl+Shift+I)
2. Prompt:
```
Maak een [ComponentNaam] component met:
- [beschrijving]
- Tailwind CSS styling
- Responsive design
- TypeScript types
Voeg het toe aan /components en importeer het in /app/page.tsx
```
3. Accept en test
**Bonus punten!**
---
## Troubleshooting Debug Errors
### Error: "SyntaxError: Unexpected token"
**Oorzaak:** Ontbrekende haakje, quote, of JSX closing tag
**Fix:** Kijk naar de regel in de editor, controleer haakjes
### Error: "Cannot find module 'X'"
**Oorzaak:** Package niet geïnstalleerd OF typo in import
**Fix:**
- Check spelling in import statement
- Of: `npm install X`
### Error: "Cannot find name 'Component'"
**Oorzaak:** Component niet geïmporteerd
**Fix:** Voeg import toe boven het bestand
### Error in Browser Console (F12)
**Oplossing:** Zeg dit tegen Cursor Chat:
```
Ik zie deze error in de browser console:
[paste de error]
Wat betekent het en hoe fix ik het?
```
---
## Veel Succes!
Je gaat leren:
- ✅ Debuggen met Cursor
- ✅ Terminal errors lezen
- ✅ Inline styles naar Tailwind converteren
- ✅ Imports en dependencies beheren
- ✅ Website testen in browser
Dit zijn essentiële vaardigheden als je echt met code werkt!
**Deadline:** Vóór Les 4 🚀

View File

@@ -1,225 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 7 0 R /F5 13 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
8 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
14 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 17 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/PageMode /UseNone /Pages 17 0 R /Type /Catalog
>>
endobj
16 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607080655+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607080655+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Huiswerk) /Trapped /False
>>
endobj
17 0 obj
<<
/Count 8 /Kids [ 5 0 R 6 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 14 0 R ] /Type /Pages
>>
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1821
>>
stream
Gau`T8TWWE'Ya0?]VCr6,?RJ:bcQlb`0iGrC*eX1/i8:'Zmefu.D+iJmfPLFX(A,!/ScL8]_-Uj),P^cjtiP1r&0,hk6r2_a]Djn$"<B7?;6$`J8iV1(]b5;MO4L&L&6LC(0HA<OYMn7BW?kh+bZ9rX^_e[cRHgIPQnic$Q'F$WR;q$bp'.K`d'iqkK#\0Od,D4q+a-@`/BK=UdHlkIjBb1=JsY6<J6%284>5#_o_cdT.f+s0f)gU>nf;HW_8O^fC4K8"i"BZRCP8%YqY8m0Y)bV1WNlk1mEqi7F`m82p8:K0]d.Ejc<r<f`]r`_FHFk:I=VI/h%W7E?rp'HgMbC4?!d;'t$N-OQ/c'30ipWaOjAum]Rq4_/R5X+>etC46tKD&W4O]:h)%oG7DcNPr'?6c@_/1YX1K1lC"IL758i\m.PX1DA=<2>R0Im_R/G,Fm"Phd9oA;2?#\n\Nl<El*9Y]&ZTQ@^$\#NFq)ZM'rUqJ1$Cg&:f!'sB&B`&,8QDt1!R@?+lO#k0f8`_Ej$8Rcd.`/FKpD4ia$M^Y_rP,Pi)TWMFa_()sk9p'o3X84cEI9hdcl@A-B0(CeS<`S,c+Qjg(tF'(1mNZ.@Pt@f`PWQT?f9`Z'XTN[,W'g]b'qh5cMjcpg2_jK&8B"ejZq.!kd*RO&M4:1._HN_#B+:-L&7033`I9,/[^S>M(iAui9nc($HS1mo:e-[E+`%Jl\<-+pS`Z?fdT6cj8h^F`/t#_/B'MgT.'e3Im]dGQDV*9P-So94)#F.&?IU>Fi$EK0,]egSDHUbQW9_!3k3@b@nfoDX,sM[rO0CRAU'M;-Cse,ksu#\#"A$/TOHYgpqOEb#\?3A\c6c`Ci_l&BZaT>(X\gq/6Z,Wj9<"i1OqZlmZFRmNX5X5sar?71#9m**6o]JXY4AiI%7N3R>k/;bA&S%i`Z;o,1ZDG5q3/BeK#__o/$ZUd\e\g>"(;\+.YQS?FLK*?Lt)oG2W)tp^*.s@!VXIDD-[ue5+F]hD)Bq:Xo,ia9jG_N^RlHRP]qE%'NoGpM(6%[K`<\R6!bfiFO=@`gY05Q0caS3];21p)ol<SXtfbtN(<`il&ef,Pl>kcN#WSqGcoYeA<FkY?U*2EEC-dcnP#>^6K[k<T+ZJM-BI$;Y8LsNp1771FF%Y_ssXS7Dt[8TUIkA;$nGs07XYKF.9!#1?+;637/:6leMoCMVF]UkqNR=U2DkLaV6M!V0_R:3E&pBAFX30d@gW_0MGhkR&%iBLYn,MRn[l`rHKof"FY@$@C>8Wr0GR@V4<b=de+Mg\Xn'&Xg"IBEP>MB:GPr16ngAnR4M&Z9d5Er=E<>Vfd:$rt'ggs:C0r^7K^Mo<e_QqcN_.4m#Q_3MRflF*7c#EO(0L<&WKCH(l4F`!esDPing(j.q%Y[$Tk$XO<;EF`eDG5DI`iVO)A><=M(A-)<+S.uPm09U$Jo`*&LVcTaqZ+:qu/f%D>c#0k)YGXP<c=!A*(l"eFD/.a<g:&`rT5:c@fR#0_'9gY@!t$*IX+ks6^-5\1hjC;tc0i_50P$k(%A_fi#Y&gD`0PQ@DZ/+Eih7GXh*eEs##[Bb;g6SAq/MqOOK.i.me;\&FR5IVok+u47,aq^'6$HHFdp#HrCZ?C<!*Sa"/?pnVOeXt]\JmW]aiQ8g7KIonNXYMI<GD+$+OsY-$jW)mY>;4HF[Zlc'5kWgpIqf)c*,Q6K;cASSfbEf[.kG*Ba@4W-uJ"g`S2T>r-I#eoX<e`**-?a-tprPR/VQ(`u!iUkb:MSgCqN!E-ct_<K9B8*_OtBZ9da@ne$92X2OB_pp)D6guc!'C1M~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1764
>>
stream
Gb"/'>Bedj&:WeDbZgXF=gY6[NRRU.EH1A&[2YTA*rJi;/@')sU8s"#aiT&$lll`E"p6#/H&1E%H*oa.q.<)1*;iY1Rg]=(p7Qh%JMQEuJ9&N#pDUWVepf9gHkpZ3L`8Bm`.17Yq#cnj,^;NC,8O,^SUc@o#9HbUL/A3bcNO=Y5RA8iDc%X.mfXd=&lR$drMB*<nJO^FS9Vai/Y90,$]TBCnLNj#bioq#i=9$?ePLBKEMs[ucl9j<l.KDkE*T359Wj9tLHi0ZagHjr`WGr2"F^prOV]79SjDfLHI!%Q3MP<,`"/Br+jo=i<k]=6G"UkW36btBClZ@O1gs+qB9c^&o1A.+*)43)_K5Vqn.!G3&K8c[JQX'8r#KR_&YMQBjD?X$%sU=ah;NG1+0a[X"gs##3&lX)@F\GUW3FW&;T4Vd1g!fG,IcBNOGhP8d2,mEoL3d@kIH\RW:n`J%cBrQ:u]36-fF'mf0/MlS=Oh1GG0A4IWg1fH!5S[G[NWp2:gc0d<N;Y*k*:6>2saj.MVOC7fm?/Ia?.lP:8u<8BAN,8LDRhjPap?3=V=C4kX?=7/(hiCp[a*EK^"6$ROe#05Z/BW4n!c>cD^dX!*["a-0sl.\r0;C`a$bS0We6]"D7CoGnA#=@&orFHF_"TiJ-X7r8qf[rEn=9j,P+&nD)c1&`<h)dBE7QSX=@l`]?c6on!sj.=f+dJs86aW*+W5+Ze`$#MB"_RfSk#]@.XDE]]@,n5Hk6FrD+P_O:?f6UZJLW%Lm8>.E\&0WD[DEf,A&u59ni)$KPgOClKX^mF\m!M"qC0k*gc1tIbQ9n`7L7lO!nRJk"]?dG2::e+On.$+J=[*9bLdX[]S-tF4L-[P[r=1AE^q<Wb<[F6U_iIR$6$B^if4M\"XE[TUGnK>gaN(%4>c/Vs7DFO^!b!.Q7kecL+IG%qc!&u`TjnSXQC=tn-Ot?Pq3A2@PI-c9QWYO*In5<'P1NZ=*u_#$O6=_5Dc%.#Tj;]-1QJ/o+E2uil(".UeWH8iZeM9uh6MHiq:(Md6=fKLOKcTl3B'>O@[-]5e[-,>gbXeCSEHpti"9=8XHBnDp/iPB#;5G@Npjb9QA@EpIT2P63NCMqjFt;43%`_^cqI8S?I75*[!D'*+t>JS&$CU'BFq[3&LnOL4KLquFn(M)'8cg$\6Wf&\nk3Z\/Xigo?6K4667Z*nS$DnRCYG[bVas(%C4<>3,uLtIEfM)A`*6Z>Cq%1Baj=Sq=1H^F#!]0Dere5#:;X30j/DRHC#3kia6!+8(Ugun/bnr[4W_PJ:T2)nBFbr)I#hiSj*%k7#6DS%a:M-)'CnKf09)m>qj1.)7)!/$f81QC)UJg/(/H.2(_q=?7j\7K`8sXLU"3l(YTXZ[L;uL]'(`^U`'S<>6e0U(P(nK:jFBVBjaF&1L6q4WgDR?_dB=*Da"(>?K:Z]bs8HcT)iOU.W(p_`gJ`APD_74Eq:oP@DVurA::21DK;#RI(*>ih0h8?Y3peL;n_poESAQ2^L]@5+O(i0oS4'Ya?biA=$kRoD"@2f..m8.*SWh@IlHc]1u?:"]+Wc!bPiDcpg/&^2pd9IVTWBk[463*>Vkb6!0oA=EbW@3*Eqeu2nji\CcC11Z9)9l=23D*ZF:>b]FAt%G:IkSnO+4LBT0=chN4g,39n_3#^`$Wh%#rG;ek5Whrhl#Mo3CA@29)oEL+AY6G(@4(g6rcoVi&6k5FP=4\I$q&+ABh69]d!,*Ip4N>abt`+:C?7E7d]J)=\WL&~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1892
>>
stream
Gb!SlD0+/q&H;*)Ylc(%[(4t]FJhf@#"!jI"U]86Q[DZ+Mk`\+<(V?cPQ0LigW=Ui4392'Bq-;fhL0SADP,as@mKS%l@4F(^oIcX^L=o;9TFZq,%:%(lqKs!NsKs?Oq&%sWUMkY:K0Q?:Ib^N/oa&B1!il3XX$H^Lt8.%Nd=;P"hB3EB-RUK\Q!8L6o2m6mO`iQ>gCa(,`_2:bPJ>j\oQ+ADX/=Sg&*$29!C3%M^g!GbY\jDAha5Rn]O,/9;eWm:=:tR5Khgb-RBaQY2JD*]J^QQ:,A(uKukc;SlH!W5c)Y[X/K2)I<Q_IQR4q4;#]s^;<!4q6hl=5fKm$[oh,UXBST3MHMi*7#R+AIZ?lAVhr&n/.Up9Lhg>dk=4-79JLH*'b4Ze%$B9[[QDKF0b$o_LcU1SG=05#K1Yu:(Co)&&oa#b\"jo9G..^\K48Wi7TN\r>/iF'r\-K@&<2s)F3u3mQVNkUTA<XnW<)PS<U-"cNAB,O.L_,q'nbeauNi_TSi/Dr@OS-DL+B8@RNu1_tc[AN70\Y7g7^R@d`Y[rVX5?Frs#1dcN9V[l5#!Y1lf-H*U-uKT3k*HWnMoP]GbUerHI^'NK[Br;j`PC2J,Qr>$TgMA&)g'T@AO3jq-b*ddiE\MBm011kOm!#8#4Y'\&q3iE&Ptio-E*cj"s8fa2bR`Cr11lknAYk3,'G&r3IJ7k66J$c&P>M\3fE`IFMf\'bk@%L%%&-Jg]k=KHNm+hLOD:]N?i30or?Od[JrcP7e2.?Di/$9=0[ooO8KK*KO>5h4*<j;?J3\^$*Ta4"Z"d7ppRWNq2f*%!Ktg,0aPai;6(c,mO([#RfC3\'3m)IQMp<aEh&>*N\/3a0biW9B=BCeK!;?d-2d?7AI/c:#(cDGIX&t0dD<'9%oTchNFG2Q-a=ZqI=+M3&)^!RA0>;E6o.oqjYO@mgr-)BfJ!&Wmj!^YmF$4EX9plo%t/l6HIErXh6ZOe)5%:m/3?k0('94Iou'@oCK:ml/!]\R]sLt<:UMr4Y/T@f9s2*P<TNLB'D;-oXOhh]ib^%;9*,:]ihq"0ip*aS#'kRH4RTLW]/ZKgRIR9mD3ISlT=k'KjK>$ikjb.p\lnWs/HHD9tCB=hp5`j8+C/TKWofB!(#rfKdk+!>K[hH?Ugr%6gbj/Z96A3bL]0tV@DMiItlc&2.W?`fn(hFT.9RbkhiM65E%=6h[AdkKQV:<(>p6%&rI80lT[W[A@,sY;"I(q1nbn=/<LpU's\n5Obu^'`=g:QclS/>[_=P[(:IoL-J.DNo1.K""%UhBC$o>H0oAP+D+%+.'i3Qn@Ja^`"Jq0.j;+p`=uq'9rL`lX1'gQSej[X?dIHlm3/,Q2cc@H>9*%cb,89N2]6uhUWbU;UjeCr^GMK$C5l48_J`$?-L?G8Tqd(W!K%"N'3`+/_4did`[WZj6/L,!J=0Ic1??PKU@/8Ji8\#_,dg%"BmRCTl19(enEb1UG\9r]u2-'E,<T\>@"f<21cDSWr,'2NmGZ&igV/O@RWL6[gVQ.,F][NCSQVQ/%4Qs/VYT8`MSn/E9=5YVL_\VWYYLXOSLi5'%!VCMVck^INY#ZOn2]6<eqZ8ok6aJcdG<O8V3t'cc`IGt)e!0`tT226C<Eb^QX>9-[%]e&@g6+h]m57NR'j9m1,OTPf2WD/;Q08csZY69"7>Z`,m8Jf^i5=WFn/%=u)P&mRWn.-+J#Bfkd@(FoC0YI%lAJc(AI^o=*>&N/D:P*JCoq#R3#Jgk9J@Y)mS'j$Xqm\I$=I.N9c[Mg%eg01Za/FdN;)l_GC7q<$02T/O,="\#lCqZc-'S#`*6;bm0S3nA^"M1H2A(`&%eUMCkYO+ck<5#]hAa=l6q`^VA_K0NiLp:p8Q4e)b0i/$W'G\~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1651
>>
stream
Gb"/'968iG&AIa;CickO-4g0?!9nYf9q6Hk),F2_8kutE1*k>C5QrE5HTM,o?19/_Q7U/*A9s&<,XGo0mI%FoL]PIR.t:&FJ@>XET.s^n#%M$^nR-;Gmg%->LN()+k6#]+l@p9Lkh6i?M5.m'7"d[5&5Yq"l%'X)&d2VC*9To8,T$^FQOX*A"dOWD,[e=l`2!,\&Y.n0*q=pi+3q(["VtE87#_[b<Z`$1:U"$%<iYHtb'`Hk?j=;/5(354+4?"g7Q5%M^usLOd7k*<c6^.!#k-80-0\[&@i4"<i2'_P,LdBc#YF-XcjrR8rN4;NP4E6BM0/r-8&>FuYRr%'ND$j7"?uM*oQ\!S#R/P3AYo5"B4qfj2($!8QjNEA`jZ<WZ%d:arg?tTe;NJ\r]HVtP%T"3]gb9UA-LCb:dP:Q=s"q:_C,cq3itPGcF6Pid*%+>3$,r\nYE6+hMrnB(Jm253Rk?$&F*-G*=80I7SZ&-qa_>hGq#a2$OGkhJk!6ledo.og(>%,]LX#sC'C0_F=E/$f,?Yk#q!KH(2H'_WQk!UjP2^@]C!3STlin.7M-q4c-q].6P=FZ"h61I0'>OB,'>h?9b,ci*#2$Tj$Jgt37Z+$R2YdRa7.E$:,]sK)Td_,.1@n@o"o)8oh7f!U.uqSSO*97kkALs.*b)u.Yij!]"X\;$qcrRT:TAj3SLn?h8XQqe!<rkQaAk]_tkZb/,o9,U^D,GMca=:]pZ]R\-pknN0^plVm&WID_kTR+-RFVn86Bl42;jl.5uj#)=\RP=D+b6D8B8,T,&bH%D#/kF"ljdJc`&`P$"auG]=X9SK`(#;P9eQJtoKDO'ZXUKt;b(e[OX%[P$GHPaPr\9gBSK(:]6?+([W!T7JFJ#12?AMMk4)c4B_0;f`r9mDM\*5*q\ph)fuSnk5u0&=9p0Y0r$P;5>@2eRP@jG(`MFn,!':_W.e`*%^`PZ":mo`Nn(Q\ZIW3B^6%tdrl5/ZG(BOkpGQH,:i#-Csj1@rSAWmGZ-,`BDn)Hn]S@OmPE]\C@<kU1reU!AVt^><A$AUi*hsm;8f4YSAe5"dKo-SY]lP*jC\PY*2=LnBorpeanDUOVj(#Eq%iPT#<^tQ&*t?MU5cLb,NVoV]MhnNj>@.W4ISU&8+Y>j1c;K(flj/4p4OG/XBPDXEHApB,sb.LoIb,P?mkG@j4[(V731lej^9aIm9&-d,Z9BrO,,o'7+rOo=8phfB.e'Z)Y5&($N(%r=E^&>aHTi/CiVFrJGI.H;Z%ZR@7d]+;-@CX*m3(3HWT<MV)!::If@&rRPIuQ<A)HN[YGGQ;+`A*WHUn+>_HB"hp+[b`$Y6`(N)@&rG&R9Ed>1t0JD5!4qB]K[gG(Z=9<=u\O686K\opJO7l;XLZmXJAj:RJr\L9OC!T!W3a&G*adUX>MmFtc':;:bBf&]?nG%k=KQdumq$nm$cVsZ1?K]_lAo6F4T#WmA"o8*ZI9KX"&67O,<9;G)org^F1%#6)HT&Pi&d@CR0uXU`.qj<923:W"Ih;:d*RoS9U\"AY<$f]p[8-U$g[-e?CA5hR;K(?q0NT;R.=*B23E('1%&Z$]?KK(-Wo>;<SZ]KlagB,k(YYHDqE%VL+#W.kj*gpJHo?9(^J[dR416_d$1M@U"m8*q*X?W~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1859
>>
stream
Gb!;d=``=U&:W67fU4p*aj_"*hL9/Pa(a=KP%Lf,R7;Ip8Q^R5!=T7eZ%%1gVeA`Q&Sp0&dYJNr4[/4d-",`%']7:Hi:RWcD[?s^_^ck<@DhRC:56H#4bL>qZQ1_Nhm<Fl7UY=2:ue,Ia#2Y&"pB>&1ORLQgF%aE#<ku>kBXuJ`Sg#mO+?3"VWZ]Apr%0#GA4g'\<J1QgMgCVGeCeScd0Q<%(DB0`@O5E$s/N_WJ7Ea)^]Pe#DP%uJ,"8.$Ld&CA^Nog"THo.72$+T8j.EA$?+Q+94Z2p$Q*3fn7Bn<7FZL;iHhBqOQa;:cYf/>Q;mhha'=;UZm^;g#\t&*XlOLc)c7d[6JjST8HK^uBq?Wr3BfS/e=[;3#;GJsSl]CUN$UXA2tN#eU#l>[_mE.A4lY(/U>B@EB6_@;m"/2"()R)FMSh!%H_aip8C3;<WPtL*LVJ5O2/Ot%R1!=ucWga>i1EKiCB$Vi`QtJAknSr;^WZ3i]X8k1%+[95as:.<Q?+D&"ktb:bO^Y`3h=,en`qpJm%+5V8ueZ:k?[Pb/AneB/[q>d)rN[i6>F$i,)2MEk6ZcnA019.*@_goqHuHXMEp@USWiDH<BgjIPNW2J47F];>lV+W^<cssqoV&=b0NF.0bI]e?+@RN\p]TIs*1\%WF3T+<5URL*\^gCNr!U9U^RfqT,l^$_&'Wu-eLZ8@1(9l>VO.#mXP\T<:Vqoi0@SWR/W.^r?2<09FI)G3a"=k`M%I5Q!$f42VAY=9an/uUMj;CFIc::"/NB0V#*u)Z\hL8Ve'lpObKG[UnIas`:#\8:iOK=XBa6kkmZ'HT]=g,;t^,R`$u2+L3k@YVFn&73[I2gh?sU2$P4)DFu"f5.2+D_3^"Ai&fHDF6tZ;:1/2hL<cfuu=^,Q.InZp'$&b["_m;@58TDaYP->Acr2&!n[MQXD/bJlo[=2V1=A$S537%/_&P8*VN67pp>BId*?L<OF7auE"=`QA%a45>R?B/>Cao/1h!'.tTnjJ+6XIRW.Bf3XgS8JFA;>eel=XH8[E"e+4EB+,hJnC+c/7W.1`3^Q"/^R7s2hBtW_5joKP;8o1F.r+`0T,-I_Pfn-r-*skKUDY@aV"'U:*D2u3/%';_/.._q?=UaQ:K[qP`&51j0cd84-gr)/+cH$nLW]EY>[+j`CrZ>4a"t<QK+8-),p&DVaW;2%AB6B*WS79'0?'*!LjA^43'gGis`;*@s3K3n^#!`WDkq!.?[<nnHOrV.Xa46C0l8&k^@"\Q+!538XpTunAgnP"Q`P+q,G86<+o*Ic`]cK;$EX&?odZHJQ;<q"'DHFiIX@mmkE:oJEf96HkqGC-6M)N\pMjBL-3#kDZlGTDOAR+-BWFRFiQW6'Kg/;Hbf76r/;#jThl'&_,Z:Lq04#^-nm_nhr];K:@96[2hMuP;p8_hoo,T8mOtl\/oN1]jjB'GN9bRk'IPErM\[)S#fYY^\A/.Zr@,l>PSGnM`@`s,29J'p)@F9E@ZcC*?`l[V.kQY^7D#P"d\=HUcTehc)Y,_;[(b*2lFW9t0!)#3[.llM6@9sH11#b62m$h&ZP:?Ss*faF$hD</l*NNGq9uXJdr^t)X'4"Dd#@$+VWL\'@;89U"Yk)7l^9h.Nb:>[AW`]go+R?)XkTSBOG;.>*+RWb6=23#I8H5JBNDO@`U/"OgU3SKb)]U]E=BoB#t6ra2'9$be8@mdMqKbKSqeM[Z*0Yn;oIC^CK)&/anhl%F60?9eK&NHs/h5J=2eU\Y\GJBK<2)iUSZ?7\=lhRK[4V6nD\]^l_#[DZbbgWV<t%,&^_ap=b;KXr4bg.RfN?ar,[:]=9e.<pYhOY<&J*NQRhLRAVu)r5Nff*cN~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1789
>>
stream
Gb!#]=`<%S&:XAW(uuu<RkQ*V8Q6H/[S<rupVtt,!e-7Xg/b>nV5-"8dJpr1D+ClV]34UV6'T`;IWLT<'>Wm-o&!1Bn8&6j0*r0sUF?b/(l[=gMp^JK!)\B;9F%A5XB?7!V.D<AZU"l.JIIH?"?8:O2]4;E&Eaf6Wq]8OQllQ+*t8Jue%d;CJ2kpcDRB0rA5K:*1SbtEFdKkMO([TXa=+/hAemU'R%b_";mfSa+\ZuVLJ\&hpE)S+_<Yb]@V6`N55^g>iQMrP;ej1u.sN\\#sVT#Glj`o0RAs4=:r[aJd`<^lU"sV?Mn*M73Ucf4nCuZN"lYk'SJ'M9T:7!A#>o#+63:Z_M()X3"H(m6W!;'<""^<0j1au"3YLCIn;/QGuTQ/@[_'JY1ffdU_CU:V5c,NOO?Zg[/?%B(g][?+sUG#$").0&b/;mWGu\Z,MVh)H_Ch-Do],=+c&N16L[<\e&b#QU1/h>=0$ReNq[]>SfAo,N`o2*]&S`8/&3lD!?@4'Y.N$)n?ErsT#::_MBlG\^)%-d:2YLA6"q]N-QH(@-pP6eU;gSN(i%5'^1MYCFf23L4jpOP7&JfBCd7I(\<YGp>Yer0n1p.*j#?"^MiC3Uq.a<M?bRH46R43K)bjkQ/B1<2k'SI\q0K0kbt5\cX^$djcp%9q6RCI4T<0A^,$/S;M?A2:^M+4n)biD*7M5PM*mb/^5jRA0h9U`$m8@3)O_Ql5KM3MF>)fB7*uakp`&lPf]D\7Hl8W7@o^?]ZVu([p)Rmo#YS4O<$^('p$;B>u'-dW%DB#XAmFW7>g90aqZh>HI4JR,4_.a,dlU/TpI:YO:Vnc%B8n1R9.+n*&I'P&XXTKUMXF!_GSNsoqV)f<uE=D^%(ejc+)'l&[DNIP8305Uq4+d+KFqukOV4lWJC,.mJ55,\057WQk-LKPDJoNI'M4]Y]]d3$c:,`Z<?);=<llt[$fn!AAh?/S(Y"psAPW?b;<nK6X37o]d42U]Hm<WKe)*?/2iSKVb8U0CDqU82<hqrV@38heApY-LUHh#OnDqhcrI;s<m&2WSDJ9KpBc=._fs._<Q';`mnO(:b$-/p9)TpnW=14$-9<`E\fd(1]Jj2$T"#9F<2H#]q2%<*%5,FQLE%sSN__LQiuauGR)0mlSVB)s[!Rqf/pitKKd-5-)cgP[,n3K<Ddj#tVfdq?\TF4PWsWt@<pAj`.h@iG\&,Isqf%Q:ue3Vkk)klQ_js4\bnT)igo-]Xe0/pMZ,lu8)^d9d`45"m-(_pnB=QU@m2RN1Q-cP7UTM.I63SctA3($:#I6W@.Q('F_OR`4r4^#fhB`u/Bn<8WfV#W$;"Y4.=g):WF?c%T-s.5=6@@3no0TN8)l<d`T<*tVt'ShK&k#A:=^<SP0k%Q@FQ/<-4FT?^h.aDZ()GL[EbY]=\_h^I6r8dm7];*p9=R9c*od>#h84[Tf.mqk.:WJgm<rr(WO<o`d/K&b`Hgnq6-8nF`n;I"pURio>K/ID7iKM`ORId9[c*4elmJX7WbMuPk2-2\M"br^&X!B*t1q"8LdXb_Q$87TM<(%48rfaA?8XL?l`2[j&=am4$M.V&mEedU<LYY=)&Mf-8rR\B8%E.g*Cs7_5Gl\H4+f?-rl`_lE/lVY,`U=t3F-*,;eT=RAT(#0)4NNNAMX/7%=oiRg;lc`#;<aC#o&*VrF$B+8oR[U.q$TJOPB^4DYW/Z"acG9/P@dB#ic7\A(Mn?L/6=7:<45-iT[A.9dlJ1+A,1K"c-dg5o`lBK&Z>V*[_@GS;)%2(H!,=D"-i~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1994
>>
stream
Gb!;d>?BQ=&:Vs/R+%t?]-rsr>;e-]-sIJ4[^7U=mR,G.j+p+]=C0[3dU0a6a1%.'$-R?)P(OE:)U149q.;Gt*.60)IlmgX4+tE^"j14t$'Z)5_tWY<f_`9NR*Pp0J@L0jL($W:g-79T?4*9"r/cUH!_8>;E8UaC5nF79E1IP>J<=gHVFQ1Ch[oko&l?mbr0?q-nJT6H3UqJ]]YD2/0<j_q`36O82&C0FL,3KHWfcRei_WEq0UtH/o],*J37\b2a&B)Rpp2Y0Sea"h9YeINr$lVqU,-Li_se"fDsRZKU"lQa(QMT5H9>:)XYeW;.A*>k8g=lf$"mqg'tsOl+bAfq1d1j)PWpfDPYQ0i-a:Sop%eZu]3)AX.34Zoi)[t9&Z]K/DXCB.UgR7mmSr-0SQ<]]KX=&PX57FirSLRo4MQD7V:XfM/Y5/%;.STW_H:-g-PJLpbigPp(E8XU;tM>[f`U`La5K1BNKe-@e\qmNRjb[XmHK_sfRC?O<?Yf#T,'l(58YY,NfpBeMga2sSC-XSG'FWU\.::2j/ha^2@/Zd7[?nQ2_(BbYF+?'R9WCH`b:RR<*i\]9VdeRKGHE)?WM81];MFMlKE4)$&)_U&"sIBI:S],\Zh$(QQFQe@31Ga0^E^%(<k9e,s<;/G%!J^d$INJmYL:%:sXuFc'SbgBfTKKWh+uAf4>dbQ12Sk-8c\u,:KSjP4U.76.#G\Bn\S7?83'^csgrt?*!WMReC7HS+]Gm[]c-7>R8O5JRh`"9ul]G#oR;<G_11qF_OEk!3c\ul5Krr9]\8q)NoBI`cBDH-n[3\1OI^%phi6Oej?o;HrgIFAJTKX#*je/AU'pl0;:eYA;VA_O#;ro(hs4R7uK$9h]FT!YC=%fX:P!h,$t\`24D'.&M@Q6eIH_J,Op/B_*d%?fd&%jQcL-R%Dt^_[jiCcS4%WToBjC4]$mOa<qsLUgp,tG1qEmP_R!o"8WeMW0!p'c1-\F,/es7<27u##YL%`VYhP8*R_\IsDZU<gL+^R/rc9mKnC$M.P93P2I%;[-f8\A$)@k5s6'*$GW6('iZ]5qcA4K7_Vr5Z/'k3r(/gVR8!UXdU0]?4"FBSGVp^.6t+sG3CKD[>RU2P\,jV9e)='$hUg47h=&J3L!6*9n8S/(8!XC./K!El6"":\t7,#R-sA;ILI,#uF&N=9:I*SG!3#l]2eB_U66f5$R57^l,E>rG\KXGtn>$[MaCiGJ>C)=Le)DfbPXXOCE%[bNm;D"P;*4Mbdsa0'Q;kC_LV[56;C.TNf'9!E&6:(1\:a%bD=$f=!DCo_`OOhTK.faDSCRo!bm:cWh99lReRLjqlnrruGNEC!,5#-'7CL,,4"*n617.EB%D(T\m?[1h=LBI5js,(=$ao)?fJ>//VP4Pto)BRO)X9/ViG>NfJMl)[_V=XI@U(9M2cmqB-Se"9)@fp#M`0VgKUn1RTYSPATKSJOM#'d+l)c&210n+KC<=B9hr0bdgZ56-8DCtkM@^r]^$OmBRBk)Gi+K_`9ed15ik$clH=ck3HVj_^U1U-KJY=-I`t]n.5FPEIR\4-eG.nY"UiqB35_nHpo27A3YiLs4&f7jYZd]J/H4Z]p-27&!^`QZ?lII0!KK"?2QjTk6>c;1]YFaQRn%PF%e`8m"RgB6AHa%Z=dI"ikjGGK;k)q`@X34bUl1fX3"gLHU[iSu.59OHsIidnX6ER%YH/D=XrCl_o;8R,k1:WOaX03r/66EY%D/4q?0lK:%Nrf5<]>TgM3jrWd9tlF<X+3@3K9O8,6W9j<T<@POk0TGb"O[H:-2<bk0p<g_A(ouq81`9#^e>1p>5CH,N)I6"BeT[P<XII38?8.Aj+Y*%q'K_$<oV8POldV[$Rh7e!1a1'YY[1:O/b:ChXgZAb>]KQ$Wk,<F*DJZL@`sQ:2Q5f7@Qc\jOW3!6%^g1,Y&*o@+[[$&Ug('U*-q[ppFpC=abt!Uq?mjnZ?k?o&?qi<BX9ZFg!'$N7gA~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1676
>>
stream
Gb!#\a`?,q&A@B[H?+k*8hgGS!h_2FYoQo9UrGtp=_brtTI?2%7=bdP3KCk')Vi$IVUl_Z1*mLqj`R?qJHT5*On+"/"PB<]lTG<&_W1t.Eu7S7_V<2rkK'?e$Kd!$8_4=u3.Y`B+HK#2I.$KS"pC_P*%;^Y8K9/]#o(tt5m66UNa@_O3.eb/12nN/\`B+.HOcqf<AV\Y4Lho8[SL)u!f,CYTpaFML#EG<44QsrR,\fdpb5jfpof<gqsZ4r<>!K^/;:8S=4,g=+f,7sm),te;/*I:q$WQ0_rRXockFP]EO]u,H(j]X8b#I8Mb;-i0MZ]qZ7"Te6?pU)iOP!@?&$_dD7@/`\PNLH/V?=C&2`]!Gh%[e@a_U8"_MV!]gj$(AQ6^g3G2^V6KZk5[>LW'DidZ<97@OCC1TYE0WHgn[!]T\NRJ(B6@3t4V"B7#gR2@aYZbh;oH,#8lZB\?j"]t+>useZS@'#b6d8iiarI7CD3I(d:K(8$_O3g4Qt+Tf$ub`"bDViajW<kp67U4rYFABu,tbJMUsupYcbB>.-9SD[IF[V)V';[H@'YL7cN`%_d[j1r'O+;T3\"F"HI7E96'e-siF8k;e"tA-%^_Yd$;Hm:R,a-8oa6/N1P;%\#i:(,!E#:#(.&\S:bQ`dN;&Lab9B.r`s^7&QikLT<NK(h[/I.>M95<i&7sSV=fT%R5H[rr#`ULiK84%de2fC)Piru9Ze7B>nGL>u!AX+m[OEQimNMB?7Z/s$TG(lZ<em8"FCE&9!]G[`4YXWPR>!J#Y-Qi!%a%e[\#%G!IG8[;$E<n6[IB7`Z-c+'3'JLKoQ8oh*:D$N[")V^daVlH#8P*he5GPS-*l^k'=rBdifGZ(k@goQ\_"c)YEnBaR3h?4Gl;:Gi<rq@2sG:<HYFipoJN@,A5=r*+(7sQ-M2L^R1n\Z)H>>&AL_R1a^baUfa0Rl3Js;8dgbtdJ9.<0=M/KgDs,fW6KRF+0!M@miHUF1_\WEV+n?[NYHT\4`c)fo^C_hEouKR2"DDG=(MN$X4>`PN;JVJ/&JEN\j:IX\*!pG.X`gEbYTm\k%eXg(JE;Rn;3BaZ/0-J0g4e?ZbE09p10=*<A2>SOFgH`G#kqINWYl27J;TM,=2G-?-aa_s,:KShOcJq#PeGgh-d:t"79G@WkC_OB>-ID-l+TW^q5@j#p=5CH%tZQ<ORLaX]GFqUR8=C;GIRh2''fE!S0\NsoGKRP-FL'l:[@A)WpJHNR\VkT4jO]]V3T,O<afE9g%hbcJ$33O&S-7\YY-q$C9CV:pX!e@A(cbhpS$E'\1s&-;b.[IjNe8?JhGkm:`c5eEDd^"c2IN;dVfJLd$HUFojRo*VO/eq`OBT3mhNba@[!fsklFb6#gqnJ"V\Bt0)'(FGD"oJSpt(7^/1oriTbeuX47j3W2`tO:^!CTS`?9D7r`pNng,?JdqXdVQnrAI?fS^FT?d+)f;#jiYE9hTjFk;<TNE4:-=N):ZGZpIh2+I=OC&C8s.9W/#G^?;Ke8uS[Bdh=*@2!7<6]=_iQ:od,=1g)9oB/OQ$n=;0OXdYHf&d+:&Nqp<I5N"dog^m5nsP]Zih8HLMZA]o5^/8F+k3W0J23"gSoUKAh-lG@<8i;_9@Fq6<LFfglh'lWc2Qob8%,Rd,U_rjR<cQKf*+]&Eo[u0_u=[OkO%~>endstream
endobj
xref
0 26
0000000000 65535 f
0000000061 00000 n
0000000133 00000 n
0000000240 00000 n
0000000352 00000 n
0000000457 00000 n
0000000662 00000 n
0000000867 00000 n
0000000944 00000 n
0000001149 00000 n
0000001354 00000 n
0000001560 00000 n
0000001766 00000 n
0000001972 00000 n
0000002056 00000 n
0000002262 00000 n
0000002332 00000 n
0000002618 00000 n
0000002724 00000 n
0000004637 00000 n
0000006493 00000 n
0000008477 00000 n
0000010220 00000 n
0000012171 00000 n
0000014052 00000 n
0000016138 00000 n
trailer
<<
/ID
[<523578e4abc2fdb4125b5ff1e456104b><523578e4abc2fdb4125b5ff1e456104b>]
% ReportLab generated PDF document -- digest (opensource)
/Info 16 0 R
/Root 15 0 R
/Size 26
>>
startxref
17906
%%EOF

View File

@@ -1,231 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 6 0 R /F6 9 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /ZapfDingbats /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/BaseFont /Symbol /Name /F6 /Subtype /Type1 /Type /Font
>>
endobj
10 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/PageMode /UseNone /Pages 18 0 R /Type /Catalog
>>
endobj
17 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260224202513+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260224202513+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
18 0 obj
<<
/Count 8 /Kids [ 7 0 R 8 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R ] /Type /Pages
>>
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1071
>>
stream
Gau`Qa`?,q&A@B[H>]19N;t(,'#(0SaHLU(ZCS4_9.l//;iiG)#3dG5q=nZCZR)*)aXKZ9"egM8If1<+$n+<@q(C4u7gZgkA\^8Nf>%M$OIX3>RG:P>[o13kOISZP"=+aF`,1^=fBeG*p=flcZ/eafK_%`f%#En8?ker'0I.UCfU%]$0a/%Lp*lXZ9L*V2`DNRQ6'B(V>ZVIV_#$2l4_T%<DDIP2QM$l3X<;e@5.6bep/eG0-t?$35fbY_189F"(D)B<5laS:7;NN'Jl'Dj"=O'#>n4ouZm`<+21.ijE,$G&WGB"82e?91fXPsu(\JjPY&,P]\3^pnR'Bq!a<lfH*^CR@hN"g>k]%^bWa[Gs*<NOV+dDaoO-7=Crc&jZhd@1dOu*B[>4b4;EQgjQKgn,q$2'U'R..FVd&hnFMUtS-c5YbI#IJb]mVA)"KsCKIWP_Yj.=D+5aM@fqGY&29#J)e+QYH#=o?,)/nuo=`a*4[9?*i#sqjEKiD9L/afhuJ:^3%qDgk/#,[BXGN#=kqEW3kl9)i2'D+\Y:_7$/BN8qGti:aNdQ5@eGsD$+L7,!eVZX-Pd..2[<hSds)1,]UlS1K)SlADM.aB):UCJ\M]RC^)7'Zh_3b@'?@Fi]m[p-lbMXqsC1,H<V)'H\_kd2+^Qh1u('sb%oAXK!D_l[4/6/7=/]UXrhGbWqrJXi5<>]j=./eGfo!V]YgjJDQ6FqE.IDbH.8.mTPQQ2Y2iB"kAi-L6>KXEJ9J);eI[0?l:sCB"m&idBo\''oFGjuf'=EXqKmdW2GG2;W@8mIc!XE&HZZ:$T0E/:g/O?B3LDZ.W%3o^di3.$^C,ZHmYr=f&%%k8K+TFJ18tt#^J1)fP/Bqc#B9M/ibWe.TKL!5cS#c(B(MX.PYk+$P%"i&*.)`<^Z\b3dq28<'?NVVH@%-MH+,jUURR"%o"c)QRe8foloPQ;*:.=P7I;c)(.E\>c&9kgT3CSBl+5RlZKoH/M+37]%_Z)&LuL!Q6AcFm8&':oG,LR$(*]Hdc6G8cHZ..^55u)\9,-`%=dDK<H(!fnBOK.aK?:l-aTj-~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 855
>>
stream
Gau`Q?#SFN'Rf.Ggq?C_;I*XZLUV+%[1f8V9;ar>,pb1J5sc7;V#L3r-T%SM;>/@S=Mh>Hm@87cnI5PtL=Q<G"n3[iP7[j'Li3;/;\5!u)#*HE\6EYeP=>h^.Ul.78_YZ9X(X6Rd/ab."U>;&W]lMG#J+m0a[eQ5pTo&TIt+o.&(fM5@L[CnJK%s6Ot7VQ$fe"4k`XX#<+m.1`.7,\SBE7.f40pYO[>_t3$YC]b&S/?A-"m]1kK7g_Sbcg'5fjM6WGqj&-_,'"_n]JDn'os=;\Vg+S<,XS#%e"EU%JNZfC:4=Y>+\o8EaAb`$ZQXV>WrN)<=IYF#X8?B!Vq4\2ZDPa"j1VDVW`FVB6NPF\n*#)`mRDMp8'\fIXmgk4+YZ]ZsiF+k3.2oqY];5R&h,n8msdNQo#1W!mQOg%F_.Y3_\.>2QlQnX;',:5#*DT@HqUfODRA5n["\f0^S7:VB0L0X.i'BfAXq#(BS!6uq`1`d12L(ee>oR%Aa-]dI3!7(fnBl"_Kku8=G-.?Dj'mYOKdW4$/$`VCcJ5^8?8H$kP3)h'#9ZX-s@9l&B#lA7#0R+:Z#g=`SdK25`B-5p&p?ZcmKA%2:>G2dW=D/c'?;:ST)QP^a`Nu`UGmHjN;B3moThM;u-P,qZqG-g[aGU\FOd.(a2R"SQbDTA`/Dn7qqiVYnf>,OqAMU$K=mjc%([A:p4i&@DRCL4_-omKSg])W*_uN9if6!3aFS<-65"12tUlnc"g1@4_7ECs"1506Ab,(j/VCApAA#P!?o4,_aA97oNgT&!\a/$!\_!-#>rpn$hI%;%pFM=`\1cbSV#,aIm@QMj,ANb=$bSF=_@LkHjIKP4>l1Y~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1260
>>
stream
Gau0C?#SIU'Re<2\F@CtX;rN`+-m?%@<3G7=L,*9cjcB>TLcH-7Nu,qG+9?Hn%4hGO#SlX,T?m]K3'8C4nm`!MLpU"D*IOui7AZ<cR;Toknn)!!_=tQO</g1U@"LK9`0]$kQV?M_q<#M0^:Mm4ZU@ck^(MkdV:e7f?>&cfoFCs!X]EI2)AKhOSIuhLUel^8f,-a!pO>gJ$c8*_!?iBWD>j<PMsY[`M4\``uW7R6Y$;$Q%np[b(99E9%:81'6qQq>S^T,K%Eb:;3W'3p^SkX,\*;)\(QGP*AV&2_FK(-Xen]0c?0.fQ>*9lQ5Pd9)bXK6!;TkM8_i5*Igb%A9M6WM>#t.Opfqem=<q=3=I*Wa^bN1uiG(VQ/'p(Vg%i@/UPIN`=;^G\g.%`VV2AEt5q6A5,E!W@e)pV.j3F'9?^L@d/.PBW8?>QhY2T=)Z`O#aKG_^ncmT=r#s+%CFo6M6C^HR'm'j"R#8l;jP:`F6\eQ7^h6#5"UZdV*\u8KU%&boA$3qiC7V3Bq>k<5QX9BH!bH5*PiX9I>W&NhL&i(tg2fr'4Y-E6E>L3_*$3eIm.3u*6_t@ZR<n`BfLr7hQh4qQqP")Jbm/ZE#02(V,Le9Kt,NAWUkI8M`l;/7=_m::t&<iL$aZfM(^:0-;fMDI*T.X^6(1,o&LT\66]`(N:-n];3pbh$^H7!OSdR5&Ogp'qi!<$fJ"mA/b@-:<Ljr/F\hD!N5^HpA/h\CNtat#N]I3,E9NAb=N6b>j?*u1ETJcgXL?E6(VT9F5HS@6ReC[nit[k.\(AcefC2uh9FhrsV?JQ(c6`>^QnE@4*u&p2f5Me#DlNk\\k5n>TDjVEU5^;PJEDH/c>6(3Z$%ZB\tLNV8chYkEeO,C9Gm?V2Y[C[C.2M%L8I3XDms8JsMJkX:VXO0@@Ap)?/Y[A570;Nkt;s:GtV2CT.5p;3ajN!Y^1i(<(gZ_l1&<GUffGO1BkLL11&bUFNk1H<O'PP?'%'i%!G&RfWoF.57G1^`cC4?P8H7@ok0flR6BcG>-?Y0$6D+CAZs$hp;f7OS$CNJA-0Q(N_,3i_I8o*$oU%X8ZPuuii5O,l!qJ,N7j>ihN5Y32H0tqj@6CX]#9_Q34`82j?]E[,0e509U-"4%!W[Y6`gKEoJ?ICrrRsY$q7fo,I?48(b\mCa(#sB9$3rgJU>&lkj)KnK+k4fQ^Kj4KN9[n8khl:CaZi%hYLp8T1rr1p]S1?^"jYoesbJ?EK$(e]Chgu&&rrOo$REG~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 696
>>
stream
Gat=*>>O!-'RnB33"c`UU_quu4$8+^5XO5:7=3`]8DFbIj5Qkdrqe\^TXL(A=I'lioUPBuc]1Wu]1)SBC]SL;^tEo5KL.F!_@_Ra(9WhY.3Pgb66R[("j%uW'Hq?FQu.6>L59B)!LN/di@.#s#q@/bPe'Ef.&J_C1q![)2b,0J8;OE@+ClInLA@eNmFGJP;u:l;/-V@\U;0[sNIK(^@M4O4KLqOjN/@:Tq%Qco[C$1[`gub$;2G+rk_I4n:bIJ>E*jMQaF!UfP!tEsE"AX'Lm+=>.VfKCg3'7,61P#tBnne)MOSU>8E>BqU.E_<H7nN2#C[>\gQ!Mu>kKdd[*?0Uo=V/_F#^%Aq/SU6Rhn^7I>Y,]``$6"]M#TO_O=Jq?^f,>BAdH`]/@EW+(5`s2I2(@j"W3WHU&O>C\p3<b>mS]W(B:"1%dbucGQY!o0BO`#H+.E?_B,RRDVmfAQ+Q_e%tMjJmkFja=0W"LLcP$U9$"AgR:Ro9M>f5Ms2kT,N?8SRENcc=f#o3HX>qc\p[+1kRlVla6[6b[,l[`C$JaNX](k&LUIlgqic^9`s7LT%b9<^c`t+NGq=^IId=t3Oa;F5,>R%]V>tTgE>/M4YUbeUAV(DRN$I,iE&(4,FT^tMY#f3+XR2ccju1KP>PRC\0YiaAIrF9!'Y;ESNN%ZlYLIF='K5[s"TJKS<V-b~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1075
>>
stream
Gau`R>u03/'RfGR\D%*9P@eXOG*9R50U3.T(u(cVPB]Y<dtT/gEDkH_lZ\ns=UU3m,g[7):9QnM3QKal@6j?kp$XjF3/ZYPQiZ4Yh#J*V5]dLRW-?j'bt&Q\:N;D35XZ0&SCYRo`I/&GF'hHB,ikZ72=.,?eC13k0#k[;M<@eUJCSW1*Z&r0%]f2AbZO%.,\ZaXI$ki';llB-jj=#C*F=:UJFCCZB?*5E&#/$lBHNsQLu,bC?T*J$8BL&3J_o&JWODhE2*%R2B4Ws)=sE7)de^3^%#H=<(t('P./uap@;*N`$29?\%X)/DU@02KkX/h>33D,".2$J`>AJ&c`c*.-)Q[m[!o-3+0t<c79WMnd3&o"'36nCuX9K`B'ImUM$@0F>*gLH9@;N$qj(^)R):6u)Ht:JhM&MCK;4:1rFoRj!lhq%Z7-aoU60LCF/1#d_`#!-Mk&HoB#AK<sKnco0S`t/t5\b`F24(=QV'o*FLU9U6-ITZ\[aNkVgH"9`B3[S`.PC"5g]QNErHCEa$p^HfKP1[`CT2F<co?PXZ%*A[B6!bSF^mQWP-RoXD/_/h9XT>r'&cVk#`XH+DY5HW8)gRQMo3afpn7$#l6Df9S\Rer8LJGJM".</'L<J-+VO/r&<A>nFWpaf.uF@tS42r3o>LkkHZ4.R5"(k[?>d#<*cg8h_!b`f4PKceiL?k1lU;Y)S#6Ejo@W,L>U8H;3S7Md>k4/%,a;%gPYnGG@F^$'pf&j+jDLh#LiQrV<*VYHZ?C/k3._c0LCMFn8JP^8W#l<#.-?h=G2Ajan&@p#A33EOP?Rr<d?rPZn%uq?52e.d_b#m5LE,1-Uq[,td%gC>i[em#%WFY*EJC=3<2R'_[p'STpaMl?OLl37=>nA%SbCHX=TN.+Ek7fpLG(u:SnuD\g]+q?^VUp&4WGXdk;uXk-sRgeSq"6A)1e!Y3h;<308uN"`3G##%5Vd;TAIJF`RYAOg3O,=kf$Z+7,Y5hYVkTd#`:Qi-O=;3YBQgmNeN*-A>f%+F;AY(q#)t><cF$g">2og5D)ljdH,Uf9XY45`=m=sO=S\NpD7IeB@d~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 965
>>
stream
Gau`Rhf%4&&:WfG\@p9j1:EB+#qshd?q"YRNN>pgK]!'DM9VPegAU]Vn=s_[:qB88Z4rDH^AI?d]/qgA\CgYIj8]u.[!E,tLZfM@93d8Wd#Ar.)f0k!"N1nBKC=O?n,`iuTp2^V2Jo+=h+:?bJ10;'UhbZMV67oHN"[_<0'9$aLKMGdg3]f3')tW5<QYu2mN=Qm/:YRFa#[iT[I+U91:EE$PsmI;lL$t&[#LBXHO8,(@ge[I.GRKcV3QrUOkSK6/L9:9,iBlF+RPA>!lO7/^QK6h&D[#1E:1=Z!tt1O`Lrt6H@=$OU=s,8LW&^3%DYTG4[l3tg3D?:ED4d61I6V0U0^4foa/(?NIbd:LP,"hpH*IL]:GugIiQ.BBb+U,'%`u=6&-;,QN(5#>!;V&:@oII+/sk<=XHEfnlG:VbP8?=ncbn.Q_d[<WlMD'0lGp`^ps^,%b/f#cB\.aghqJnm3Y.(]@8Nu%rZ>B@:nP%Q12s>hsK![H657hlr[mIM6j`Ab-jYdL!gfDmckAsA$&G]&SiF-)s;o4WraN4(gRuSa/%BuHuagTOS[elV"E:n^FHAV36o;?V)+qo']i7?M]Gm;lgkeXM$'3@=Cj"ZB=2&+rm;,E$SjXqH&b+g7/--8.H.f)5<sA%S,u1U*apX^_\dHNA=V7MJRbTHFRGhT`r_l0B-[F0D>Rd!r%U[,O058WB\;\^-7:[1Rs>L;g^scsEkH&\reqhcSQAr6A'`#CXg2\,ju=:*iPrqQf%$Zrr$"?@7SE(hV3.Bb'>S;V5r>>?>3f/=WdZX9G"S6poq;P[I!4)]Q@RtPUG0Y1:YN3IJkanm&Xb\EX!3a=M0R&je]S+1fN-PW#LE)](9XjhB9j*qK`Yt>p#K%)"QE;6?uJ:;^+2<4R>5md=>[h=j0[ccWJ]uGAJR7[P'I?IUbW?K2gr1mC%WT(-JN?Ca/RKAL_nF2nJh41@lO~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1016
>>
stream
Gaua<D/[lW&BE]&;]L-=_U)QBZ(5ED?n30`%/(C,Ue)t-gb(._4"#`ccQu:U\$hNp+W@=Pm.:%%s#Zhj:jcCBT9]J(1%Ytn0FmUaQllIi,M8'(2a,_pWf["5EsP0%65fX0$tj99lUF$Z0XKgS6AWga6eXR0)gD5_ETc(4cQN4q@-59J.13"*m&"oC%9t6_22o#5LedJC&f#'Z3i_d4W@Oo(#,TqkUE]_Fa)eJ3$DjmK8Q4q1'WP=YQ'=ZlFF[65cXXgpi,]tSqdj`q,>k7M&-</lchmd!C48GN@W7m2+I+,_Nu2]nLXRs4l4FnQ.3]m>dKnH&r1L]TT;1<P'uiWt\l(3hlCM=^'k5LX8N!*,=rU"i$GL5qnes9alR*'tS?>[XML^MEqY-8CbmSo-N4.]=U#LVk666ESTM[s.5*KPQPoq9"5\\kB_]$U#-$TOI@Z[hC@ZD6&.FtUjC@?-AkQK`a^O#MJ`>tm'U&'Af8B@&E\D_9'k,uMe<jOQGS]nEFJ@G'oAs5Q6Yr)sW`+1t!H/i4RHI*,"]B+@TV-6a;h=,5Fc\tN<9iX,Xi:GZkp*9FC3.o/V.(RHmS>&/pfhZHF[ROa,I6u$(7fXN6l1>moYqCphN0"KhgqqV-fp)89[PL(r:7G5O4(fQ\$M(2k>c*WbB9.T9dHXS!K&'VJV>[PkP$l2YQopS7a7I2/]]EdTBoho[SGc<!#:XgZMeUWLjMM:!MWD??a'dmV>X,_jC:Na;[4@iDX=u'/k=6?7eoK^=?^2t@I\.ut1];H"HdtGP`m.m+]#;>V@'XuJ]!H@[>RB/&#kND[Ao$IE_VD+p#c&!'n;O%$bC!ptmIfi=X$?7U><*ReKj*/e)-2a6pA3ZMn_=!qHcNa2YMf?JpadJ6LsO[pA4?`"6,9Bdh)-%W)q*IMM-tS_ie8gL5UG9ZP2ZJRG9@UgNXf)0P=XO5FG*9`4)d1$Wup&$gMuA=oEn5/<P^cWiu9tb,@.-IeeZ`NP@ToI"G42"U%]5uX]SF~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 612
>>
stream
Gat=j95g:b&:j6K'm!&HFgLNWE_EE#Z0FM(U%Zo%&LKDqJJ;ps5JI</,!mERd7PC5SSr[8<m(j.1dpqo1Otr+0(/oK$/kslbqt(]\.*)#BAtn7K#/t9^f\S&TV=PpbqrVg$ns-$2?Hu'"&5/'4j,9ME_QkKNXGQ[1mA;/>S+7`a#V$,^,[``<kFf6\O52AYO"64bEJ;/8!3*8:)\bJ$9&1N)h1D,ikZLN6AHQNnf\R]XGt"6EHWAkdc;Cb57h/N^a4uk7rcVK&N>^J1X0"?F*5#aoDQG?)Tl2p\-f+`p_(OdE]^niF2>(7XZ[#Kf*+BUU7I&m0=9u:GNJP9_F)fuGd_pR9eljD+J[#LMCL&<KQuJQKZlT49S/Ii@5`_DVu)kEgGD^aG*RC?&[)7a7QukPRD^Eq_e90):DXWjAX;K+G2.fg^!F^eE>t+oA7HI9i,_qAA1[Ihr!7N2n6,Nq_h[(rK8n'AJgM:PaidE_MbI8&5mN.<Zq[+QlVj0,4J'Ga$i3@;0Wc?S!RTOK+?Bn9npj].EFJ&L5cZtN,Sd7.\DO47F&G1;:TH7.2`pe'<s8<pmDg47Mfhq:S"eOVK&<=\D*l$=$S_GgCn]OE~>endstream
endobj
xref
0 27
0000000000 65535 f
0000000061 00000 n
0000000142 00000 n
0000000249 00000 n
0000000361 00000 n
0000000476 00000 n
0000000559 00000 n
0000000664 00000 n
0000000869 00000 n
0000001074 00000 n
0000001151 00000 n
0000001357 00000 n
0000001563 00000 n
0000001769 00000 n
0000001975 00000 n
0000002181 00000 n
0000002387 00000 n
0000002457 00000 n
0000002738 00000 n
0000002846 00000 n
0000004009 00000 n
0000004955 00000 n
0000006307 00000 n
0000007094 00000 n
0000008261 00000 n
0000009317 00000 n
0000010425 00000 n
trailer
<<
/ID
[<e1d0fc053ad55e2642c670c80d245c9d><e1d0fc053ad55e2642c670c80d245c9d>]
% ReportLab generated PDF document -- digest (opensource)
/Info 17 0 R
/Root 16 0 R
/Size 27
>>
startxref
11128
%%EOF

View File

@@ -1,493 +1,178 @@
# Les 3: Cursor Basics - Lesopdracht (In-Class Assignment) # Les 3 — Lesopdracht
## Bouw je eerste Cursor-componenten
## Doelstelling **Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
In deze les leer je hoe je **Cursor** effectief gebruikt om snel professionele componenten te bouwen. Je maakt een **NIEUW** Next.js project met Cursor Composer, genereert AI-gegenereerde cursorrules, en oefent met Tab Completion, Chat, Inline Edit en Composer. **Werkvorm:** in-class, na de pauze (±75 minuten)
**Status:** klaar = drie components live op `localhost:3000` + eerste git commit
**Eindresultaat:** Een werkende Next.js applicatie met Hero component en Feature Cards, volledig gebouwd met Cursor.
--- ---
## Timing ## Doel
**Totaal: 85 minuten** Je gebruikt vandaag voor het eerst Cursor's drie hoofdsneltoetsen — Tab, Cmd+K en Cmd+L — om een werkende Next.js homepage te bouwen. Aan het eind heb je een Hero, een styling-tweak en één extra feature in je code, plus een eerste git commit.
- Setup & Git: 10 min
- Skills & cursorrules: 10 min
- Hero Component (Composer): 20 min
- Feature Cards (Composer): 20 min
- Inline Edit styling: 15 min
- Git commit: 5 min
- Buffer: 5 min
--- ---
## Voorbereiding ## Wat je nodig hebt (klaar uit de demo)
### Benodigde software - Cursor geïnstalleerd + Pro Student actief
- **Cursor** (gedownload en geïnstalleerd van cursor.com) - Skills/Docs voor Next.js, React, Tailwind toegevoegd
- **Node.js** 18+ en npm - Vers Next.js-project: `mijn-eerste-cursor-app`
- **Git** (voor versiecontrole) - `npm run dev` draait op `localhost:3000`
- **Terminal/Command Prompt**
--- ---
## Stap 1: Project aanmaken met Git (10 minuten) ## Stappenplan
### Stap 1.1: Nieuwe folder en Git init ### Stap 1 — `.cursorrules` genereren
Open Terminal/Command Prompt op je computer (niet in Cursor nog): Open Cursor in je project. Druk **Cmd+L** voor de chat. Plak deze prompt:
```
Maak een .cursorrules voor dit Next.js 16 project. Focus op:
- TypeScript strict mode
- Tailwind CSS voor styling, geen CSS modules
- Server components by default; "use client" alleen waar nodig
- @/ import alias
- Functional components
Geef alleen het bestand terug, geen extra uitleg.
```
Cursor geeft een bestand. Kopieer de inhoud naar een nieuw bestand `.cursorrules` in de root van je project. Save.
### Stap 2 — Taak 1: Hero-component (via Cmd+L)
Open `src/app/page.tsx`. Druk **Cmd+L** voor de chat (nieuwe chat). Plak deze prompt:
```
@file:src/app/page.tsx
Vervang de huidige inhoud met een Hero-component. Specs:
- Gradient achtergrond (subtiele blauwe verloop)
- Grote titel: "Mijn Eerste Cursor App"
- Ondertitel: "Gebouwd in les 3 met Cursor"
- Twee CTA-knoppen: "Aan de slag" (primair) en "Meer info" (secundair)
- Responsive: stack op mobiel, naast elkaar op desktop
- Tailwind only, geen custom CSS
- Inline in app/page.tsx houden (geen aparte component file)
```
Cursor toont een diff. Bekijk de code, klik **Accept all**. Save. Open `http://localhost:3000` in je browser.
**Check:** je ziet je Hero. Responsive: maak browser smaller, knoppen moeten onder elkaar staan.
### Stap 3 — Taak 2: Styling-tweak (via Cmd+K)
Selecteer in `src/app/page.tsx` de twee knoppen (de hele button- of link-blokken). Druk **Cmd+K**. Plak deze prompt in het balkje:
```
Verander de primaire knop naar een paars gradient (purple-600 naar pink-600). Maak de secundaire knop transparant met een paarse border en paarse tekst. Voeg een hover-effect toe aan beide.
```
Cursor toont een inline diff. Druk **Tab** om te accepteren. Save. Refresh browser.
**Check:** knoppen zijn paars, hover werkt.
### Stap 4 — Taak 3: Extra feature (vrije keuze)
Kies één van de drie. Gebruik Cmd+L of Cmd+K naar wat het beste past.
**Optie A — FeatureCards (3 cards onder de Hero)**
```
Voeg onder de Hero in @file:src/app/page.tsx een sectie toe met 3 feature-cards.
Per card: een emoji-icoon, een titel, een korte beschrijving.
Drie features: "Snel", "AI-powered", "Open Source".
Grid layout: 1 kolom mobiel, 3 kolommen desktop.
Tailwind styling, matched de Hero.
```
**Optie B — ContactForm (form onderaan)**
```
Voeg onder de Hero in @file:src/app/page.tsx een contact-form toe.
Velden: naam, e-mail, bericht (textarea).
Een submit-knop in dezelfde paarse stijl als de Hero CTAs.
Form moet er goed uitzien — max-width, padding, ronde hoeken.
"use client" gebruiken, met useState voor de velden (nog geen echte submit-logica).
```
**Optie C — Footer (footer met links + copyright)**
```
Voeg een Footer toe onder de hele page-inhoud in @file:src/app/page.tsx.
Drie kolommen: "Product" (3 links), "Resources" (3 links), "Contact" (e-mail + locatie).
Copyright-regel onderaan: "© 2026 Mijn Eerste Cursor App".
Matched de Hero-stijl, donkere achtergrond voor contrast.
```
Cursor toont diff → Accept → save → refresh.
**Check:** je extra feature staat in de pagina, matched de Hero-stijl.
### Stap 5 — Git commit
Open een terminal in Cursor (Ctrl+ ` of Terminal → New Terminal).
```bash ```bash
mkdir les3-cursor-project
cd les3-cursor-project
git init git init
git add .
git commit -m "first cursor build: hero + tweaks + extra feature"
``` ```
### Stap 1.2: Open in Cursor Optioneel: push naar GitHub.
```bash ```bash
cursor . gh repo create mijn-eerste-cursor-app --public --source=. --push
``` ```
Dit opent je lege project in Cursor. (Vereist `gh auth login`.)
**Checkpoint 1:** Project is open in Cursor met lege folder.
### Stap 1.3: Create Next.js app in Cursor Terminal
Open de Terminal in Cursor met **Ctrl+`** (backtick):
```bash
npx create-next-app@latest . --typescript --tailwind --app
```
Beantwoord alle vragen met **Yes (y)**:
- TypeScript? → **Yes**
- ESLint? → **Yes**
- Tailwind CSS? → **Yes**
- `src/` directory? → **No**
- App Router? → **Yes**
- Default import alias? → **Yes**
Dit duurt ~2-3 minuten. Wacht tot het klaar is.
**Checkpoint 2:** Next.js project is aangemaakt. Je ziet `app/`, `components/`, `package.json`, etc. in de file tree.
--- ---
## Stap 2: Cursor Skills & Docs (10 minuten) ## Op te leveren
### Stap 2.1: Open Settings In de chat (Teams):
Klik op het **⚙️ Settings icoon** linksonder in Cursor. 1. Screenshot van je `localhost:3000` met Hero + extra feature zichtbaar
2. Korte tekst: welke optie (A/B/C) heb je gekozen voor taak 3?
### Stap 2.2: Activeer Docs 3. Optioneel: GitHub-URL als je hebt gepushed
Ga naar **Features****Docs**
Voeg deze drie documenten toe (zoek en click Add):
1. **Next.js** (officiële docs)
2. **React** (officiële docs)
3. **Tailwind CSS** (officiële docs)
Dit helpt Cursor veel beter code te genereren omdat het toegang heeft tot de juiste documentatie.
### Stap 2.3: Controleer Tab Completion
Ga naar **Features****Completion**
- Zorg dat **Tab Completion** is ingeschakeld (dit is gratis!)
**Checkpoint 3:** Alle drie docs zijn zichtbaar in je Docs sidebar rechts. Tab Completion is enabled.
--- ---
## Stap 3: Generate cursorrules met Chat (1 request) ## Bonus — als je tijd over hebt
### Stap 3.1: Open Cursor Chat - Vraag Cursor om een dark/light mode toggle toe te voegen
- Probeer `@docs:Tailwind` om een geavanceerde animatie te toevoegen
Druk **Ctrl+L** (Windows/Linux) of **Cmd+L** (Mac). - Voeg een tweede pagina toe (`src/app/about/page.tsx`) en link ernaar vanuit de Hero
- Genereer favicon + metadata via Cursor
Een chat panel opent aan de zijkant.
### Stap 3.2: Prompt voor .cursorrules
Typ deze prompt en druk Enter:
```
Je bent een expert Next.js developer. Genereer een .cursorrules bestand
voor een Next.js project met TypeScript, Tailwind CSS, en App Router.
Het bestand moet guidelines bevatten voor:
- Functional components en React hooks
- TypeScript best practices
- Tailwind CSS utilities (nooit inline styles)
- Bestandsnaamgeving (PascalCase voor components, camelCase voor utils)
- Folder structuur (/app, /components, /lib)
- Comments in het Nederlands
- Keine `any` types
Output het volledige .cursorrules bestand klaar om te copy-pasten.
```
Wacht op Cursor's antwoord.
### Stap 3.3: Copy de output
Cursor geeft je het complete .cursorrules bestand.
Selecteer alle code (Ctrl+A in de chat output) en kopieer het.
### Stap 3.4: Maak .cursorrules bestand
1. Rechtsklik op de root folder in de left sidebar
2. Klik "New File"
3. Noem het `.cursorrules` (met punt aan het begin!)
4. Plak de content
Save het bestand (Ctrl+S).
**Checkpoint 4:** `.cursorrules` bestand bestaat in je project root met goede guidelines.
**Request Budget: 1 van ~7 used**
--- ---
## Stap 4: Hero Component met Composer (20 minuten) ## Vastgelopen?
### Stap 4.1: Maak component file | Probleem | Snelle fix |
|----------|-----------|
| Cursor reageert niet bij Cmd+L | Settings → Account: nog Pro Student? Anders opnieuw activeren |
| Tab-completion komt niet | Settings → Features → Cursor Tab → check enabled |
| Tailwind classes worden niet toegepast | Check `globals.css` op `@tailwind base/components/utilities` |
| Diff is verkeerd | Esc om te rejecten, herformuleer prompt specifieker |
| `@file` werkt niet | Wacht 5 sec na file aanmaken (Cursor moet indexeren) |
| Geen requests meer | Wacht op nieuwe maand, of plug je eigen OpenAI/Claude key in via Settings |
1. Rechtsklik op de `app` folder Tim loopt rond — vragen gewoon stellen.
2. "New File"
3. Noem het `app/components/Hero.tsx`
(Dit maakt automatisch de `components` subfolder)
### Stap 4.2: Open Composer
Met het Hero.tsx file open, druk **Ctrl+Shift+I** (Windows/Linux) of **Cmd+Shift+I** (Mac).
De Composer panel opent.
### Stap 4.3: Geef instructie aan Composer
In Composer, typ:
```
Maak een Hero component (TypeScript, React functional component) met:
- Grote h1 heading: "Welkom bij Cursor"
- Subheading met beschrijvende tekst: "Bouw websites snel met AI"
- Een CTA button: "Aan de slag" (link naar #features)
- Responsive design: Tailwind CSS (md: breakpoint)
- Flexbox voor verticale centrering
- Dark mode compatible (dark: classes)
- Gradient background (blauw naar paars)
- Geen external dependencies
Geef alleen de component code, geen imports of exports statements voor buiten.
```
Cursor genereert het component.
### Stap 4.4: Accept of refine
Review de getoonde code. Klik **Accept All** (groene knop) om het in je bestand in te voegen.
**Checkpoint 5:** Hero.tsx file bevat een werkend Hero component met juiste structuur.
**Request Budget: 2 van ~7 used**
--- ---
## Stap 5: Feature Cards Component met Composer (20 minuten) ## Reflectie (na afloop)
### Stap 5.1: Maak component file Denk na voor jezelf:
- Hoeveel requests heb je gebruikt? (Settings → Account toont saldo)
- Welke sneltoets voelde het meest natuurlijk?
- Wanneer zou je liever zelf typen dan Cursor laten genereren?
1. Rechtsklik op `app` folder Dat zijn de gewoontes die je deze opleiding gaat ontwikkelen.
2. "New File"
3. Noem het `app/components/FeatureCards.tsx`
### Stap 5.2: Open Composer Veel succes!
Druk **Ctrl+Shift+I** in dit nieuwe bestand.
### Stap 5.3: Composer prompt
```
Maak een FeatureCards component (TypeScript, React) dat:
- Een grid van 3 feature cards toont
- Responsive: 1 kolom op mobile, 3 kolommen op desktop (Tailwind grid)
- Elke card heeft:
* Een icon uit lucide-react (Package, Zap, Layers)
* Een h3 title
* Een description text
- Hover effect: transform scale-105 en shadow toename
- Cards hebben border en padding (Tailwind)
- Dark mode ready
- lucide-react is geïmporteerd bovenaan
Geef alleen de component code.
```
Composer genereert het component.
### Stap 5.4: Accept
Klik **Accept All**.
### Stap 5.5: Install lucide-react
In je Cursor Terminal (Ctrl+`):
```bash
npm install lucide-react
```
Wacht tot npm klaar is.
**Checkpoint 6:** FeatureCards.tsx werkt, lucide-react is geïnstalleerd.
**Request Budget: 3 van ~7 used**
---
## Stap 6: Integreer in Homepage (5 minuten)
### Stap 6.1: Open `app/page.tsx`
Dit is je homepage.
### Stap 6.2: Update de content
Vervang de bestaande content (of voeg toe aan het bestand) met:
```typescript
import Hero from './components/Hero';
import FeatureCards from './components/FeatureCards';
export default function Home() {
return (
<main className="min-h-screen">
<Hero />
<section className="py-20 bg-gray-50 dark:bg-gray-900">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-12 text-center">Features</h2>
<FeatureCards />
</div>
</section>
</main>
);
}
```
Save (Ctrl+S).
### Stap 6.3: Test in browser
In Cursor Terminal:
```bash
npm run dev
```
Open `http://localhost:3000` in je browser.
Je ziet nu: Hero → Features section met 3 cards.
**Checkpoint 7:** Website draait lokaal en ziet er netjes uit.
---
## Stap 7: Styling Refinement met Inline Edit (15 minuten)
Nu gebruiken we **Inline Edit** (Ctrl+K) voor kleine styling tweaks.
### Stap 7.1: Hero button styling
1. Open `app/components/Hero.tsx`
2. Klik ergens in de button JSX
3. Druk **Ctrl+K**
4. Type:
```
Voeg een hover effect toe aan de button:
- Scale slightly bigger on hover
- Change background color smoothly
- Add smooth transition
```
5. Review de suggestie, klik Accept
### Stap 7.2: Card shadows verbeteren
1. Open `app/components/FeatureCards.tsx`
2. Selecteer een card div
3. Druk **Ctrl+K**
4. Type:
```
Voeg een mooier shadow effect toe aan de cards:
- Larger shadow on hover
- Smooth transition
- Better depth perception
```
5. Accept
### Stap 7.3: Typography verbeteren
1. Open `app/page.tsx`
2. Druk **Ctrl+K**
3. Type:
```
Maak de "Features" heading groter en mooier:
- Groter font size
- Betere kleur gradient
```
5. Accept
Refresh je browser (F5) om de changes te zien.
**Checkpoint 8:** Styling ziet er gepolijst uit, hover effects werken.
**Request Budget: 4-5 van ~7 used**
---
## Stap 8: Git Commit (5 minuten)
### Stap 8.1: Open Source Control
Druk **Ctrl+Shift+G** in Cursor.
Source Control panel opent links.
### Stap 8.2: Stage alle bestanden
Klik op het **+** icoon bij "Changes" of druk **Ctrl+Shift+A**.
Alle bestanden worden staged (groen).
### Stap 8.3: Write commit message
In het message veld, typ:
```
feat: Build Hero and FeatureCards components with Cursor Composer
```
### Stap 8.4: Commit
Druk **Ctrl+Enter** of klik de ✓ button.
Je commit is gemaakt!
**Checkpoint 9:** Git commit is succesvol (geen errors).
---
## Keyboard Shortcuts Reference
| Actie | Windows | Mac |
|-------|---------|-----|
| **Cursor Chat** | Ctrl+L | Cmd+L |
| **Composer** | Ctrl+Shift+I | Cmd+Shift+I |
| **Inline Edit (quick fix)** | Ctrl+K | Cmd+K |
| **Terminal** | Ctrl+` | Cmd+` |
| **Command Palette** | Ctrl+Shift+P | Cmd+Shift+P |
| **Source Control** | Ctrl+Shift+G | Cmd+Shift+G |
| **Save** | Ctrl+S | Cmd+S |
| **Tab Completion** | Tab | Tab |
---
## Request Budget Tracking
Je hebt **~7 requests** beschikbaar in deze les:
| Fase | Type | Requests |
|------|------|----------|
| cursorrules generatie | Chat | 1 |
| Hero Component | Composer | 1 |
| FeatureCards Component | Composer | 1 |
| Button styling tweak | Inline Edit | 0.5 |
| Card styling tweak | Inline Edit | 0.5 |
| Typography tweak | Inline Edit | 0.5 |
| Buffer/extra | - | 2.5 |
**💡 Pro tip:** Gebruik **Tab Completion** (gratis!) voor:
- Imports toevoegen
- Props schrijven
- Repetitieve code (zelfde classNames herhalen)
---
## Troubleshooting
### Probleem: "Module not found: lucide-react"
**Oplossing:**
```bash
npm install lucide-react
npm run dev
```
(Herstart je dev server na npm install)
### Probleem: Composer stelt rare dingen voor
**Oplossing:** Wees specifieker in je prompt. Verwijs naar bestaande code:
- "Voeg lucide-react icons toe" (voldoende duidelijk)
- "Zorg dat het met TypeScript werkt" (specifieer)
### Probleem: Inline Edit accepteert niet
**Oplossing:** Klik de groene ✓ knop of druk Ctrl+Enter
### Probleem: `npm run dev` geeft error
**Stappen:**
1. Controleer dat je in de juiste folder bent: `pwd`
2. Controleer node versie: `node --version` (moet 18+ zijn)
3. Verwijder en herinstalleer dependencies:
```bash
rm -rf node_modules package-lock.json
npm install
npm run dev
```
### Probleem: Port 3000 al in gebruik
```bash
npm run dev -- -p 3001
# Openen: http://localhost:3001
```
### Probleem: Git werkt niet
**Controleer:**
- Ben je in de juiste folder? `pwd` moet je project folder zijn
- Bestaat `.git` folder? `ls -la .git`
- Zijn je bestanden getracked? `git status`
---
## Checklist - Vinkje alles af voor je klaar bent
- [ ] Git init en create-next-app uitgevoerd
- [ ] Cursor Skills (Next.js, React, Tailwind) geactiveerd
- [ ] `.cursorrules` bestand gegenereerd en in root geplaatst
- [ ] `app/components/Hero.tsx` aangemaakt met Composer
- [ ] `app/components/FeatureCards.tsx` aangemaakt met Composer
- [ ] lucide-react geïnstalleerd via npm
- [ ] Componenten geïmporteerd in `app/page.tsx`
- [ ] `npm run dev` draait zonder errors
- [ ] Website ziet er goed uit op http://localhost:3000
- [ ] Styling verfijnd met Inline Edit (minimaal 2 aanpassingen)
- [ ] Git commit gemaakt met duidelijke message
- [ ] Alle bestanden zijn saved (geen rode stippen in sidebar)
---
## Volgende stappen
**Voor volgende les:**
- Zorg dat `npm run dev` nog werkt
- Download het huiswerk zip bestand van Teams (les3-debug-challenge.zip)
- Voorkom dat je project files verwijdert/opslaanslecht
**Veel succes! 🚀**

View File

@@ -2,7 +2,7 @@
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource) %<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj 1 0 obj
<< <<
/F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 6 0 R /F5 7 0 R /F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R /F5 8 0 R
>> >>
endobj endobj
2 0 obj 2 0 obj
@@ -17,32 +17,12 @@ endobj
endobj endobj
4 0 obj 4 0 obj
<< <<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
5 0 obj 5 0 obj
<< <<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font /Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
>>
endobj
6 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
8 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -50,9 +30,29 @@ endobj
/Type /Page /Type /Page
>> >>
endobj endobj
6 0 obj
<<
/BaseFont /ZapfDingbats /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/BaseFont /Symbol /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
9 0 obj 9 0 obj
<< <<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -62,7 +62,7 @@ endobj
endobj endobj
10 0 obj 10 0 obj
<< <<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -72,192 +72,78 @@ endobj
endobj endobj
11 0 obj 11 0 obj
<< <<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /PageMode /UseNone /Pages 13 0 R /Type /Catalog
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
12 0 obj 12 0 obj
<< <<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260615075148+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260615075148+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Subject (\(unspecified\)) /Title (Les 3 Lesopdracht) /Trapped /False
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
13 0 obj 13 0 obj
<< <<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Count 4 /Kids [ 5 0 R 7 0 R 9 0 R 10 0 R ] /Type /Pages
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
14 0 obj 14 0 obj
<< <<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Filter [ /ASCII85Decode /FlateDecode ] /Length 1797
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
stream
GauHKgN)%,&:N/3m,Y!`,/FD,GGd5kihf;2k#/2#M]Mq`O@5!0=HJc2I!lg,8C?#-9&5><>8.@7Zb&2T>u7cpU@nO7"*c_Xo+r)hKBXAS6QDK-J`!>WIg1!Ndpp<(=\-AA*XQ)p_>!FR@35"\(Pj)6G0#2e[eTs;(a(0mBKVYo8ATf\L4[qY_`+:i)eP')J@@"h234'b),.5BqUk:e^h]POe0;?]\]0B;'0P1:-9Tf!B<&OoCB6ccU(m+0dY6-087f>p6C'spDm*dY4`bL\)QF35DQ%;bb>8k\q:,iiIcRPfR\O2`qY:\o^)B3fM?GMgiDQaGWq$S#J^)+KB_X2U71ptgYm^*qh8`Ud;Ct9K/X8remnsN%rslspTiZ$+ZX&5PNh_gT`<[`E=?t04Dk[]]E&O4@O8:J>M6&C+Za1\98]f/UfEGTXS87m'@\!eg6<ujFX9GQ8`Ne/6;AJ_DWJ^8`[Oc2!mRb$8Mki1K=`T,84Q_gB@>@;6#%9ILTd%*J,/)kZf=oM^M<$RKbo2U#!0+REJ"Zi$8ls-7KJf)Nk6kBBV-tgq-9rMis&:h+7oddn6Ai-LU+Ct613LaC7drNuT=b@J1,j-?#:`**/fTO(-lX=,'lV'XP$Ae/Q\h4c"Q2UO@8[D'i$k!%I3N16\S;aO_FpTQW4je)Y:7V<>i#b#15n$DV<1PTibI??blqiR$P]E<M,Z#qOJc)UU[5'H3?d,#-ak%?#6j%=[%$P]!IWn'0jCY;=dVI=0H2oQ:nc,N`E6YcG';5If5$A?IA<n?EFV=2&8Z!/%hR^sr,=\]qBDD+h`keM9o2MW5^Q`@,jaktE$J.X0s88EMiste/o6LBrE_"APZ;P.5TZ2IQle./!'BsqSo2*QbL=Q_8^l\s;FO$<URNMI_-[9B)dsL/l&A7QbftiDj1kAFa3=C"*k,CdJ8LRHH`J,uB^=b>fBQO-ji,M7T-`*0ga)JNC+:sao4+Bl5f`?bgYV,&bVRV2`;Es1e1L$B3+i2)"g;k!LI6EW@keU(.roT)ZTYL]nBa<-X*^EGRA]t9*XVSB4u$o1mX?3`0;i-[THhIsF]J93)P-(A'uah@4/C5JSRN2u>P+J,I[?P`f^)[67G;FD^nn274.T2R7s>)4V`o"td?<OJmo-<<Z1q@0AU/Q@^CM#O'QY7/Hm$q5TgrPgfdnVm<"9me#]juJKP]TA4pcfpfT)d1-a:gQgE@r;bp8?PNog"*Jp'mGkf,#nS%=jXa*cl-L1^T`C"Q,E/EfP7CAT:t:uV^Gn$08(ZaO*N(;O>DK=/+0oD[=1m;4[^%d&g,QKDpTV[[&d0T-('g=[--84#e6#GI+o@8O"7pa8$!$EBaP^s.aG;R]pDosW"fTd&i)<8M!74Z"!>gN>1h%*]Wd?ZRrYFHW!C/EnE0+iEI*3.r(\`sDgu6F>;(q..p@\7rQEQnV"N3/jNMKB'JE=TdEU)r-:sCSCVGr7+kJn=3Qoq(k.5$C#_&i.FNrf5_0VVWFehT]8G#90Cr>2E\aG6\6!jMgq2bHYg'+inKF"n%P_G2@N4KZtJQA"GZbaT'mVO_mA&m6h@o$o@?@Zmp6=e?;1#-\nZk&\$Msf9?#n6Hb(l8h69Q=UIWJ?]2R<PM0W6>FCPi4f.'S#^7%bDO\8]nGdP@'0lpkLn0HdmI^^No<PIXhBE5G<ouR;[_ldeDZp3MbEC*T!D;+UYIjbu34k3rKdYmt[Ys$lP'>@4dF3$AiHuH-^o['V.=>K<![F!;]<mbu0E$Al`j_ec3F#cP<l32%=c-G`10/3/;nc!jm~>endstream
endobj endobj
15 0 obj 15 0 obj
<< <<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Filter [ /ASCII85Decode /FlateDecode ] /Length 2152
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
stream
Gb"/'?$Dbt&:O;VR$/nq)\YL3"$^Xk-Jb9%Vqo@j:O\F7Cb'mh$=UF>ofEcjgPHjsh4Ad3[V5dK(lb\1mbGV\Q[V'o2p),,:>l'("4)bE*5'&n@.Ff1-Cle1DtE&*G6X`_dYZ/;+;g+%]oM-`?9%F^HQ9RIoKb&iGpe-XnbY-a6IlHt6YA4_8)(;U'9$2ficZ^eH1aN,`sph_m`;>P-L.sJ*X`uA#RAp[1DY3F)n7`N`jchhqOuJmG(`P+0@GmD$qI(`g[<X[]:CJ;\6ETD\nHNJ]Y@asp,)%c47a8AV>29R/DLY#Mmhr12JCQY"*`s^^?6$Bo7i8eFQpmU\pr!oMW7T'kI`4g$bekrQX<NsEXS'Ih_X3a*H0:8N.LF^'f'ee*8.7$T2D_8^47&/=c$63&4F6s<=Ba[5g81?<3d=^XH5I)g4>^pUN6:B,IBh>+#%<)keV&R@mMaMO-U^76HM?EBt(0U>%!4A96mp7=J8?`/>f>+)]s_k-N2N*e-K5k9ZM,C?JO[^KrUrRb=\$t_*q4k4DqDgFH!]c`o;b97b&?'NAF_>@mU>s/:gr/C<K,K#(:."L80aUaVr9ra[Il)APli1VD!13E%b/(3$C6g,r.+&oFqT(PP9lj-CmeNX2LsZ@sdh;3>lmmbkISu5@I.<<f.WPU!u1s5SfI')52K`3Th]*ELLjLb]o(XU1Nli"?tq=58Xm"<H4/("mr22dr/m'0EDheV3W!(hV:52W]AXrhW1ZW:%5u:]RpMZZ8q;Ue%FUr2R\T70M/R*akqkeX?Z>\)Z-i+$N4/,4Ni;A'j[$$NkbCn(=:ljOd7c+1EmV+\H=K>&\!VM^duIi#"^9WYb%@lq\qWQM^ZGoi]^'qGFBD\PXp1U%XapOBO52nc)'"1q#!7>&Pnb!e77XPmX5HIX'9DkhOQ++/l=WU?lXorcb9WSE"%`tlX2MP5Ln$I\[<<(JAgO^ULa:1%CZUeJhi4DL1m*jONfa\@'_I61I;Zg!F7&a`A)*g8<^/M(fQOfr$`3Pr>c.`O-tK:s&cW%4`p%Z!kVFTjkUm#$fZl>mo*7(O8Ufgk\h0\Mk46"%8=Aa_;R/-s*/lD=:[G/K`<B(Dhq78?*hS(^cGRnG5$X+=W=5Q5b<-nA[!FcBeAota>qVqP27aT8V6d1e:C]$H5QL%\IYJ7A'0unAn/J@-krMa2Vp&8&8"%!DK5RWP_,T-(YmoM9pi\r8f3ud86e#Qfo@%\[1Hb.&qh.mdCeKX>JF9_m*[Se!H-@82VV]a;&p"@==?gk"eCm^BIZ'.X\'<Wp'.[(5?+$.TIUZmmnGj1&kDW\(eFs]f^@pD=m7)"Mn5G.<.-Ga-[bV1qPe:.H/Y'r;.8=CoI$.Z/=5jJNX\,>LKF)R3O[&k4/7f%rgcnl-+*nl</H(@C=n@0XQCnWA["OgC^aBP[Gil1Xt'i=U.Zf2+=((Fh667%h&G^k[tXCG5PJ!%.nJ5c=l2$M3NQsm(+V]J:?#&L<7VMF]edlF8Ze$E2:j]oU!];4BrS!18YY_=T)/>UIOI`>_aP$jFUcnJ=#%8CAJ!NNfqbgNVGG%R-S]!Dj):jC@H\,IhsKlqWAu`rpKKPP5cc&B4_R=oZaLZ%^U>BTesO(?lPGcP?Dm<riaRDT9G'DBpfUK>)Dr,"-/'u)2&XlLHga'L[s-ShO#3\YB5Gh%->u=K6q!VZG/3f8ok>V0i2.?PQB[R`dPOIkg$:n0;&2#aQIPH[jHuO/;<RPJ=%FMWN6RWQ^c/Ps&Y9uI^=RhH<[*9_J!6-=dkchSM8,PJL=OnDdHbpbF2[]g*GjK!ZIX#.,Xlj_`44JKk?'V5=hd\3+&)<nFeW5@8`PrIQ7AXlT::Im)%sMVa`$d-j1k*jkPtHDQJYQQeNLOdPi^64kg,X9k+`!CVMm:>8(*B[@"q9A9;BolIaq[H-WTl!r4Ok];p@(km:Qge^6.dHguq*M,Y6X'ELTjY>IJ=[S6R&oU:iClO_pOI+J9L5P,o:bR_l^ts,;HpX[T=XB/F0>gMA\gOJjan8.EWrde]a`LSN?)f+V99[2k+q%%5Nq;C9>).k0k7XXp1L>qQrdLCH(IY8TN$\Id.O3"ICpI]N8h9Q7Nkq,3A*[3T-UN8(3E/)_!GS`6+OMktm<$./JQJcpLP~>endstream
endobj endobj
16 0 obj 16 0 obj
<< <<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Filter [ /ASCII85Decode /FlateDecode ] /Length 2071
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
stream
Gb!#\=``=U&:WfGf^Y/q6o55,Rd&mPS"soL-F)h`4VT(`&f+%e,\X+Ooj.'QCdjYRn\gG4#hSKnq"=\]0_!\qrspo9=*$?JD1Y>e^&ld7D4&(jhr`X*c"q%(7!A_G@)K<nh_tXBVs1'e#-J9MVf,Z<JKiZcbm%B"%e^-hBt!3`Wk]_Q;U+nE[;b?8IC#@&l@s/#iuYt)'5GE9:\RQ7Z0SO'^-E3bkFq6cF3eYMoc!Hc\CL'peZe04UL@ZbbSXlERP?4Sig4!'G5Ygncj[<M><P8'eLuMQ_eh\9UM]4DM`,a&E%B$WEPZg?fCElI#*V]r@ULaBf0W#/J.rD[*apR.OKatcL;n&Po0cOin/Pc9GfjW0&Y@Cpk"H6LZM^Gh_Yac':\I>d/D[W<Bghr(E!C*?MC`95$r/u#,-!tT!fMm=.F6W-iq++YHt&qNMNF+8"6Ukp[$f7;)<E;0KiE_)EjdqjTEr@Y-*HF]MO]ULIg(lH3A"@Wd`-/sPth\i)2NhohXdcfIUbeq)4Xbs.11=1fpj%DdsW]#n#6^/(+DN+Z9h5Xafj-*&;beZ$3i=\&GWb#l#84nq[i-FBIUtG52PN]Nn\p*B2'c#r+ifMp>0I5/S\'<)Ga>VR'a:mJ^P\#Wtj.Dc<`Teo+25;*SK*H?A9WXaQ(m;oU6f8WaMlZQ,tp6GN30.,@#+rdHih`(a4[Jp9rb(O#h,:BtGbU#n,@kS]MPT@_f_9EPdT1=lF@eP#A8!.B6[3kO]X&G;8/BL+5D0#[gj$#Bi?+n:B)dTL!^rQs<1n!='BnGjpG\A7:dDGut;!0HO!+^Gd!T<:iHea:O5.!RifF0fbRhq@ro4Z%@6[IPbp@@Q48NmI7@++@:2-oRDHtj>a)oBom<eEdZ[cps\CM-YOn#)Y?0B(\M/3>h&Kb_e-n2l]H89<b@/unr,0O*K&fj$&p,g+s+cf]4%/EabjSELThrXZJoRT_o,U^bUW1<d<1;*/JgT2'UKiXmMYWsQ]PJ5N28>_<+Q+kV1is2\(1$jjL&8\G?jki+Kr1A>77npc%39g47F!>MQ-BuEtm@u^W')s4YeUupl]%Qfa%ik4F[PQ('IQ:(Bi3A2,gT0?om+CbERe7B^1mjoX_5"%ai8(8Uk?mVQ7gJEf!3f0DdSBFf>VrVNjiZ1r:NES!Wcdkrm*u)7nd19!:=sXhaAE(!U%Dfad4H__OBb48ZO9p@4CpUglQb!(bd#)Nu8objrhWnkfnjH_#K#"LIT,11rG]WcGOL,aMQ78AcN;)YIdaW*TJ7Fi-LO*f84)S]=?lqgIAA6S]\.kp7@lTF9g<bKE:E\>Hed=E^+.MX*)2V4:\C@GE>P1+)=mAN*B$=,s-E''%SMh0gD1"<MRoGf[ednu!mtR?F]"I(3VL)Xg05U)B-Wjk6]@o>N.jMku"WAo$oOCRMbia!u!:f(3f^r^.+'mU>n.llP8^-MpL:q=G/1=t3Iu@YsfYkf0?$)LU?'[Nl-#a2,d0LdG@ITCBN;jd!ofZDhJ@Xg+md5[]DIofqg6VE`Yo#bQ=r+*"^se+<8_BplPBR_CeFFk#qfi^Gfo8o9^@7<h4Yoi\:I`/k:aO]',;b3C>TFFe4YQfS+M)%#[pT%0C5,@fPb67aj6hhlA++q^-I3T]K7c_F\VY/Mc#'I..E)^%*skF$X::s^=O#LO^7j.XrD3YJ"_/I:ZKemD5=o3Y17+mWHbkYbBcIg=\]%\k9:Vqu<A'm1s`#Zb&Q[o_C(!VV=o4ii?6YT0\YACHEBo2+M8s"G29Le8$+S@6^cs6+-BNc$i%roe$!5sO=TUZ3XPA^"&'(\%ApWfMfM#aO>c8!5C0r8D6A7<)E9N/MX4L"P:Q;Faip)du6;\`ue[C3/L`.:_&e[m+?6H=5Jn2u=e3.0d-os"^%X)qT`@6A+FRYR-bC)i4%V-0-$RTI%@)o.54amXQY/()KY:T#]s^S2$2<rL,G^:bQ^\b-;N"71<ik>f\C(\U4l?e_=YlV$Um8h#Im1hlWa*\c./0=*QOnI:D.9'$n$=]GZ$uO%0PLhIsJ&2r,'W_]e!J'J<SWK(_D6^m/7~>endstream
endobj endobj
17 0 obj 17 0 obj
<< <<
/PageMode /UseNone /Pages 19 0 R /Type /Catalog /Filter [ /ASCII85Decode /FlateDecode ] /Length 1205
>>
endobj
18 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607080655+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607080655+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Lesopdracht) /Trapped /False
>>
endobj
19 0 obj
<<
/Count 10 /Kids [ 4 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R ] /Type /Pages
>>
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1720
>> >>
stream stream
Gb!#\?!H&A'Ya=F3*1ITNiJ7Y=Yb1OK9eZK=M(Hd9l<Y+"G:3\[KGiErJpe!4!]B1DQG*/8=%SKhXbS$:jfPEF1M?r1PXLo$lFci$s5ar(r(jTZ1lcn4QJ7/5^Z$!L]`K]qZFgmb<^e,N-N9+;+Zu\+Ae"F'e/'cT9N#9TjdZC@mFQqV$_`$-:/Gbh!^GgF+b$Z#fGm*rN1OkWY^f%eIL,GhC&e.Ya1N;l3Dtt+U*f@0Y7&Xb;m3kF'VPEN61"!<Tr[I"V]0-r,N:^l<BgIV(Q)ip_q.*/\jVb+=IhN+P2=]f;2Us7I1/*M!&In!uhOALfWl<KY1;:oGNdRnBq(CRb/#<^4d@odFa^NL5Uk,)VQ\DH\]c,rAoV2k8'$PKF5Q^^&;8.I*!ROdMRn2;\%%=9YJ:(b\P&M%dg+?*c1+=<$T<,p3caKG;8Bq.h<XnQr;^0mMpksYJWc%2?#c,gddL@<($(Y&uqm,4a.sTV];)YZ/"0PEHaLOQUZ>+n)eMA#pL3bX="bp/m*e[d3Y'+26m2X$o()<*Z/"lc&Y0c@SS`DEuu[-8?%QZn--Q5gd*3h-U:rR=Bua@#:2kcilp\<JkoE9BaRCP82MlUpngZeD][.0'I@T2_42>`E;;%Ol4/47BRRFdO,D:f8a]2PG<Z7<27K7Xc$R-3?n>H_XUSJXFo<8-M/SF+PTFQS@H^h)qoWO:ZE`DZXn!`Dke)eZE[M:8!I^.%eci,FEW,VH^T%7AMRfGYdgU0#\]upO1;+3N-P<Sf+#L5erjL=KVFA(t2R#C%_fW^E"L]_4fm>ASqZDE<%W&Sg5MkD^?(L-c((c-rPN#\Ri6uk#5pW#:YNlV%Z67,@=AO$H&/^tm<;aA:./rK5Xu0H%D80i6]ariXD0J'M,LEcbSlVh^_-C=_X7W7jZ[ag_).pCHl*<tl/i68H@YoTrQ-Q)OE+r?s3[aEdm]8KR`)mU:j7W<%([%]gs4i.=d!o'V]!OHKVunSm"`9)VZ!aC\N_lc](W`\pDZ4CR]-TqoZp%ZpOS]XRgo4`S%(315NT82c<2M0VE<IXjEHMEIH'[S2:6QC"boISQ)3V\>6F"^:9Nc#AU4b6McuF9TEI=89:=D<SFLfPo33J9#9QQ);h<Epr:7'7$nT(%odhnWUPC'28dsK%h3R_uU_CfjA(CAgVemt1Dd^f$%H7S7T;6/NO\+W#(0eXeI2EULS^epdenDJQ%B!fq8M0]Be9b*657@MOE=3?SU29^]1dFb,RZXZf5&8bG?!OMmk3Y`0\J9uJi4*&`d%'s7&$'TD-Q8g$EK7sE^Pk\W<q4t(A&q5GABm"6qUKMbl.>[cj1$YtQ-$5>_=c&(AQ!t89h'DP2b^-@C\m1g&-SG'>UTT>G?6%_Ns"H_N4s.T^lQCdpQ0_=rrTs78o6]Je3,ClloaM^YN(iaI1&d,3jG<sa((]/`(:Jr[9'7)F4Gh:O'b3aQ/g<B48<+.3b9leh+5kWhptfWY`L,Wt8"qinX(S_o&#!,.FoQftl%@\PW\KR[%?;5aTp0.ih8P(s]S/+g)XQu)BVGGrdG!Dp[a&tV-LAifXqPlCB-pC?PW%,,?c]mu#=LYZ-ZC$fD2TU*3g1@E=D9kD$5Y&:33@(cJZ57nqg>sL_(+5kr34%TEUbWX+l)@W9_Hor<FIZ]THes!g38$1ecci&3.$XeG7j!EHml3(+7L]'W$(d5HL!$dhEPaM)PR~>endstream GauHIgQL8M&:N^lHJ_7C0hB#dmn8h7A?snBW_G7uk(OU[g)qq>`0):3YMZcCf8Q+O>8>AYYkrJ;W;AE(+^M2F:()S:`\=o6^c79/@"B-0phtC9mX=K-ehW2s.,bJbBGc#73"3lh*d^$/UEB]cO,&2$_+X_&"m*CpCVM!B+D+q"-S`9p!L*lCEsc)X3u^A2aMpli.#r[7s)#H4kg@0jre.dIr+K>a\Yb*S%W'd:a]g+VmZ,GGm`$q5T`;8R;h\=9KCKSocd1s&&:$hY,^],ENCm&'q<c73fN@V51.X$n*JR:YZ'`7!9sIU[UsY9n_L\$:*eVkOhk>*hcRh!IKA5lTB7Ie9W-rUdV@U-a>ZKe6IQ(8@9aQLL>0h;ABb%'FYrmBC\d2crOs8FGl5?Bi=A(&$VOX>tL]M.-N[nN[USs/pe)[&M1DS&YWO6,7nCm7b9SLGOI/la3G'6\BWMG_k]-)j'-L=s)AKWS+Z-3g'Nf*O<>-E-."]Kq=C=FQ,%eU33@%!FgW2TMa=Hs$.JR)\61Pn,X>rZN%l\M`#8Sts\'L1,,Xk+AO/f]OP0a0/%M'7O)6r#:/qX#^JL?KtIn/I[l-dTAs,YcMW8A6C!XjB*:"a32h@nD]Rd$YIAC4f=oNnFdo)6?dpLF!#E3CWp[L&0&L_2T)X$mE[[KFHF0^hs:8ZIMkK(O_T4O2WjJ8JCnJ%X\K;L]iHni:Hi;(W'c;HGr0>lG!\oHCpYlTXk`mX"L0ON7Fs*TKsFlH\5Lf@JepH_TnV2XffF$qQ2k;6Jim.!gGFh"JqgkS8^g,/k&LN*&iU/_R`os[!,Jg#L[QJoV)q3D^SFIfGe(#+=Ft"/TB.g\-C6f^cu>+W21e<"&VjNXKLQYBt@+rIKJLY]&'1,lP<eGC=jihUWS&S9Y"N#6$QZ/\SRn?Mh?KfIXE/9_4IZ:ckA;UFb97R(G]`tUKgkTnu';]U,n7m!iP].c#Hof13]qcI"#<>Vtc9Pr8\0s8Q/RP>(mE6>G5Zk_AUD0LVJkR6a!Hqqgp:fYdDbk[1H\\K8Lpd6/kBaAk?'<=&u/\bP]ne8q19C:TIE-?14/-;q:i>,kVKBUPT$GS^\Bd6LP]&'!;fd:!-8cs'slUl59<Ds-REWi*J#B$FX^4B`$+H;[DlOVOJ?T691gtKt/=91>'Z1a.LhuJAk=)p2<)n[RsK*^R_3QhLG(bhkg~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1731
>>
stream
Gb"/'968iG&AIa;m&?bU'b#U'JA/CI*N*U-:/\(YFN0*1'J._-8;;fgkb5c)$bi`5/31/:LIrVOKRh%^DnfR/^jtF2<WAg?_"/upSM=LlVO,(\ikb7E]U1bK*Pqn24<KA0W>fk#TkckhbafIGJ-7#+)+Z65'HVL^&Lg'R*6/B%";Db,T/mC:#cNh+&BXi8"rlT.&o`k0Hc-GHE.*+d:Khud#o[mkZi3=%fRl%7eQI#X6CBKSQnDfhroU[kn7+B.ATX2K$2eNF*F0Pmi*=((=)b0(kTn>onH>`@0R[[`(`;pn8po5(2iYQ&AX:XLeJW\6k&IDO%ue.g(0`9iDkICX&F!+(g&EhL!sfSB46b-1nl.?[.lWG77chFGiGF]_lc=TSku'*T$;56oAW4]"N5@>F=%9)(:MrGP$8)VG&0.aeXC'SqGo\lJ;NVrL,7*a#euOkqbq,:I!YCrW(8nFj"-C"n#RRO1Fs'$jdosd,Ji>^82:VjCTTZ(6Cq3O0b`]BX%:hJ(CP_I$aXN=N1tEEWS?.T'.j3+!(Jg(c5fb>c2?FF5@?cHH9"qpg?F>r?0Lc?BYs!c:isAS<9@oIhStpO;hpfVE5c@2lYXQ!K,,]5JI1.i"H"F!mPD!uS5\o-cRnauUXp%Pm%uJ3n[H-7Yg)Hgc>`hC#0F9pb=I\jZg[qt;F6-B3_1$0,*hBhQ<kb=s2GlYWEC`FAGQ=m>KZgp-@W8lF`<@NuFN78?ZuKCW_Y-64D/eRUk*X:\1csJXHaVnsgSDJN-GJQ39^X@8pb>_TmkfC'=FE-D>iu%k`9Q3@.Ag.k=KbU]kk0f,<ge3s1l:t6)aga08!`+1!OZrugo:?$473VSAZ)fX=?8;tBdJ).Ti#RnIBnY*.31\+S@5#jdT\U)!$mLuY&q%lZPYF$3\U1T8)4dlQPIe3,.22`Kt,G.Hbf7V+#7s\Z/QhB8YMEsE3;DCEa=:>o)ZpUQ.Q2/chHF\D<%jXf9u'(>GJ0+[AA@p@Z'7$5<$*d)#^;*Ko)SD?7V710f#Qjru^n-(\DG@$)1$gitMYS7Q(.+i)=??^j'ZZk?)R:1"I6;T]5$BN0VH9G.T>#\0:7PFg/TOCR(?L=]Wa3-&I?eKpjs^i4?Arcm9%`F&Ne`jL&2h?$X\K*+5SmcRkDfio8chU)blfQ$-!WgW/q?).#!?G(OfRnJROlS*N<HVs(]`jgsnbcFgRL\K@o8f)2h5jIJO^L]]R:?c.W.>Jb\@H&W.1_3seFa((j7lVLlo;1(-e4?+FcHosUH\T[T'DW@KCU8*^5$0uR^!j^hW4YUuLI=WM9+-3(<Xoek!$k&#$1:t)EE4J)?<kV^84k5m6Q!eaEbd51Eg(^UYJQR3,Wsn"(E+-FIrB4Ae&Gn\>m']D6"FdNaJ4sS5$J3p!U/Z[in/aG>mU:;ko1U3'Mt>1s]Ie-p^*Zo2Sa3+M$U`i"%=cEH7F4Nu#`Fn+M#M:eQT[(F;V[+nHZ[Q<:;mPC_.;eBDf$\-/)Zh5q&3PY=LFM_l[:0l4qT!?.,<O-M6Q=8I`m!TmjXP[`Ok--fgjt\];a@/M`=:#p]aL"W''jBNVRAjllJ&Eg`Ee%Ru<+]-;Na9ZUGDAA\6U*4eR:"ie9N^]@s>;O3[ZmZ'2&#4kajmYsKFAS,.X\:)BiJHY.ND-C7U(^<&gY5Dr555pI*0nCp7M)M<AaEaL:eD'E+YGA"GC=;=QU)`%*:M0-p6Jb(PL]R-j~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2057
>>
stream
GauHL>BcPr&:XAWR!m\![oJM$8E;?sYIj$VhY*I&I]*=*6];T\OZ*123s(@\@OI2*ZaLB&6CR`45%=p@^1hYJk5`iJE:B"lAM?:QapEjn$+q+9^5WC+RG\)W?[@1>J4.+u5rftI&;2<f=@>H$Jk3_/91l"=,VV>.";OaJ3<*JVJV_YdOoph'#\A8H]MCl6O>SCO(e-I1GPItd(QkphLG7Hn'#P`s'<7tqF5kS4">HFbhRiNShnX"`eQ%!*9EtOL;TBRh-B&6jH3;LdQ-N'sbC(47_Y=Y^?pKq]*XR3H<f*F3aqRFHH9r_'1sHd1"!SgaM[M!Xl_"PK,VnjKC;3V$[%C>a4Q)5m@i;JXN0AYXZdD]BoM5ofRGu3(oj$Z\k?s:(akfggB7P2GQGt^?8s7<qi<l5$Ug@9!?irr+o=2aq!5Np$=Ib_nrIkq)?"KR'CfMlU2F?J2Vt("hgZ2N^9;2NJU?V=]9.m9"r_f%Fe<DsX:ZHQV#Cb7fZ't#t7"J,3."\`V"D,1OqFRa?rj%oP,,N&.Pe.5^<b_7jbnE'#4l&udO#IXC@2T8spTT%:>.Dp^@H"^K.oX<MeesulX%S7CU;:T3l<K\NcG@^<jn;mpZr<5cn[C]+G8=[-0'fmMbLfiReXJG-`6VK4$>hA<o6[eIGpA>c`&Y6*^c^s*_Qh4"BekZgN7Ag&cAPBNeDsg6nqMZQ>FM_$oIC*U;C-Ec??jBE9<E#9lIu?q/aucP9NVk_f\/L!7<o9gqKs/O^0A<^NtG_4.`=f84s^6+OtKj";"+E&]K5MoWLbuu+0%Q-+O-AWX(FCc0GkH3$!a$ekeGYd%uE^@!k*->[41,16H?FLUq$[/n.s6C/6c0#$@\k=3F8qCT/gOWF1B`hgc_Xb7%`F9djS2VL@[LCFKWnESSSn+!G3+>LOkY%r<=bK%GNs7fK2UP)_o&8>n#Li0&%dJ,B8p/au[udbVA3!?_DNhHPApG<pRob;%Iu'4S"4,Im(:9KHA#-!cqe:0-.&EE/5Zt#J0X#ZC]m4H<Y!2Suen;?a.bAn(#6GRb'bNqd@]fT:I?FO1nWaA:G!%huR%:qh$BD$eOH";\gk';M3dUHG!.d.j4',1R6^3+>EfIL1Q/0Tu9L,E+RD#ADhrAi:6U1.N)S0D/3C`r)#h<ZhY!qO3>dRE6k%O5o>e[CI4siUC_()rjrV/2nP#ffHDf]oa'\)/jqeqoP<-99@"V`0(m+Z83H<siJLn,M+@L_'j+g0A=F%["S%07@6pCjfl$'eoIEIIH'=49&Q0)[HV7i)P#mTr9J0oGQ<:cj950\Vj3biE)=lR0;$)0%)RFZVFu=5H]nf(8Z4JF1^Mnh#)lEV_fIW]uCUBN/JWlITY&d*kG/G23F.-l5gpWndiShqZ4BXn`oc176P`eKFa2U+[SHU*WRS+[U-:adXl-m.9$i:']4m*LA;b'7WT1k5.7IK-T,`+qA89i0-l:g0EW*U_?:)939]U@>QLpAjUA@tgk9mR<GiB=9UJ5GTG)kp-eAY$lEDQ+&Vd<T/@e?u>bcqFn!6h.7hRp^o#*COp6E2fK7/h.CrUp7Xh$Mq%ME6K^NQ%<\D[V;%:X'9!:c&H`!6gb)4b$Lj"=3B#2dU>O'Q]cKeT=%:mY_elGobI0I8G7\9@[.t79"2?FWJ`,8X1996EbOn&)$o5tTaNHA/4&I#Cg"feY"Ri#TSh_8*"3]*AEhcqKR#gL?c\6[p)WbE@+_*]U#DP32OTcsDCW@rKT?GA,P/BfgAIuAl`X^DIeT@ER!7=-0mA!5inL__?e_;mkF*oRNE23;p"D*R=m"Ei1O!>+Cl7PVKu/Zo?S,Aq1,Oub5!qQE4FClBW\HroEr95Ef#[;nltq&..d2uc@hd't\gjjg_)@;8[QkGOh<")dX_BS+@\;uQ'Xo[]H8BE5(A+l&!M/Ka'<]OAb-g?US31LIZ6SPuGh[="X`goR_h9m'7I!Vjpheqt)LQ(rnO%Q%eEjp?ZXIeeXoAS9*ZY3hs4MQOjel]aRUta-"W//\1rdCXRh/e&B$TeTZ"h0-#c%D"@1Rap~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2020
>>
stream
GauHL8QXq3&AI=/e1d69bm5unQ5^:X1iI\obk*1ta&[eKA%?/2d$ORq;j&0U=TF5t2"2-P86):^^=mCTJ\S(2NKsS5`c-6<Jn`&G"stW1`ZO1<Gjo3./D'Hk"2smB40X5<[e^"Z+(X<>`!rf`"Q/^)mnoal8UNbl_8:8bB6k[;bh`b%+J*@n4pBr8NpF;=kAB3^(947MESY%"I#ZHRn,hV#kR_A!LVe"c=^S1kC/\T/Np"3&$,b(rrjdUkI*RW5QLbgiQ@_Q7&giq+_:]Q.YSBfVcGVGrmpQ&_.Hh&6N^4YSHCI5cZ@_8E#TH':_g.E!5W_<HTbVdE\Ud%oa.R*or7]M)=1?P*0XMq'`XD,(5#AjPnq:@2W"#C,kNShNQDm#/[)`He/jsI(pM2WPp%O8UF&V^<BsN8acg2]*PE[(NWWtQT.T`m%>\1$O='R&I]gV%h8/]^=E6BM?%B#P9]kL$3;A<Al11`oQh-.T*-Gq,Naid@E(hWXBPT_Q+RiFY$N*!7fm9tp>H1bqk/g+a;nP$5^m\oriV9ZL"7/*bmd1=`Q'<YGS^d<cYN4(m9,(4eo3I^n'X_NW7<'TetCS1(ADcsHgH%K@uUE(Eu7#3^Fr>nR%1:UhU$\$,?$%d4)MD91td(o6.M-69XXQX^mgHE?^>@b9V5>"<Hn&fn#j9Tt4OKSV0:_kVMU"c<[VQQ\9n(lpsI^>_4Z/#Vb:A.^_"<$@c#Pc(;LNC2RH%mm"B%*@Z-BK14!Qu`=Oe>L'>)U]a](jJVch@7r[\VWCIKprL8+7tG5`$D41X(E26><G#%;6!h0<YTB!S-,D'RHlrCp?eoJ`eM6Ce%6fk;r]#j(Z/'L]=!hdAI7R02%YFlJF6FJ$3,Uct0*a_i%G.P=-/srG)K-g^`-NKJaWR,T-=oAlrD5ad8\pYbg*pXVTt-`JS\>Z)g`uT[\JY9OWq=9?sgiHh@AIq(F\\-Zl,(jk<cs04@8Sli^cp0%Qn(PH-=B\&h=X^"UroHr%&8h\W141#5#eNe,N",qS(("FLQO?8\7A)bKn1.(ir)Fi_IZV>A3;LK`HA?JFN6#l\@NL=O&#U5WX"FF2Z4!4H[Vg_<hlVA#%Uo?m1+bTJ$qOE>P09Z%9DSDp"3.T`SuDEnr/*F'+_3M;sF9n:Jl3hX3\%^o$TR-508)I@X,?gigUc'mma`,H\pMe^gZ9Ur6pCZ2/N<1=d/bmK>C5GSCFGeW)2g1Z0XXf_rqYi"<RBX-.OgY9+87YBHH*d?0'\q8ZJBM[R)=-rE:_ZCHQVWD94pIX;Rg3Z3C6nh:F]&.56M90`71M8Y/89/tWf.=l>EE6[B-D#p*]+H8#>GJ?^pLuHI@X(A82?!W+$8/U:2DJ!\EK\R:E:f]t$bh:.EX1d=^HHm4U>ZXs]C&k^9f]=eksfNi-?#%%X]U=g>-^SJ:$9nc?J`R_Rhgik"V`FWe`O3=B64=@0dlJB]&'Jm3^/11.4*;:mMX"[W[ZA8IY;m^Nd:_/(^g%d`^3f8<bu=G4W9Dr[R2dn.h1+35iWUd:*_PEOuY!0LIlfQR1RY^DUd;TO'jG>=H*d9M0B&RFiCh)nWt\OR.:JaE@tY<KM8*O;fm]L'MP//T9eVa6t%rgBc!IF^ln#7D4+mfS>*+,0ZN2d6toa;cek?U`3hbCTa,mAEGugcMn.cBT#RXAM!i`8c10muRC5gPgTh`kMW=%h183nNZWp?PMirCBH;oiW8O,E\;IZ5j8H#]5l-pZLWDZi&V$a1-NH>5C[@Z<FOG,/rm%O7l#<'\iWe75i1G+`dfk(9<QTC6jldU3!WieYt+_=>$,_@Z(%H&;H94bGip=C^!3-q,a&^."+NaOZ5:OD$(]/U9A***C>ctXb:P4M@//S5.Xo;1hdUBYq.VL*UuA1Q@H()bFuX&:g_eI]G9c1)Go^D1DQD=?o-RHa@TflKq)SD>P#RiU4IX[9!;_*&G>N/m:_TQ9."R1tIkC+IG)oXI0E.K$XK.M!#j_]BfmPW1.t<Y#[Jn/ku"B*J~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1769
>>
stream
Gau`T>BALf'Ro4HSB2GiAZ`X:]AbD^il4nU(PfFT*?MeX(/PR*+t\!/dbioV$^/;5+QI/F@u3X$^@0k'6G^JSXo8PuJ2^st^SF+4N2JoE&MUqRGN6NK!F[%5_?IfA_gMa-kN*fHZU"Yq7m*F>'U0i!Oog<26n)ID_gPE\$+YWOo)T"=4\$t&6rDQ7hLkcsG`3fQ+Kjo'g\Vq=WheekY@32ClSf0g-YT8S/!UM%:fk4^?Gc3La$6`bI6'/Q0Mg.7I^ciojq];&8U,XeDp=i0e'-lc0`DF>k9WAhW,Nd:EZmAq?CN.`_HaR%53Z47BQ/;b5,/kE;EWN,P7Ah[FTbnJE?k3+35D'tp&#F3<LJG=IN![?3WjR/QU]^E)XKfR@0%2/1.r:E=BHJGIk",%P9G"um"W\3=]pq^c8PIcAF)n8B2!)qgXg;^U*V@jcr*$DKT=tLXVVPU9W(a(p/5W'l8bk7`MpSSJZBo:.O<T"n9VhN_,%C]a1djBS"#SS'SY!=`u=39\6;EnPd';@cX:urL1IQFUBRG2<&!L%]QnOrZ`I;5D]ZbPNPgaCh'@ZAp`qo,,BW@8>)d]_VOO<PI5(D7M2m)ZL_`d\"@#50TC^fme1>$gBstojkA>G[k=p\3#tf%(r;iP:qpNejc;UV_)o9S=,].6T>Q\67:Z=b]^!0W:[=-Vd).(rXIdptj:$mk>#I,9q&4RFuaK4e=gmZ+F,i%k+N[AC7D(@g8.VP..SNTZ!$VhtNZ[u#/qB!)]p0%]?f`h]62[OPFh1t)W86NFKFtFa@F?XU/QJ+Og&P"gd^S9<*"G:lWQF5I7fXS[CM'F=%[;[TqrD:\1>sV?DLNl(@<3#]#dg:(VX8jcWl?$u+Y0Hr@S*i0ObQl\2:qkL<SV(9p"G<m,l932``+P<[5[B+RA`^m>eQ**uG!Fr3I:b5Ss2`EN+HJ;T&oNX<m,SRAD+D4MIg^jN67M2$+Ks]D,0]U+KKG`4jd7t)2lP)>RO7`Y32VKb4jf(t0a7+uU,t0JLDFTo0$?/W9Q.0s^-mKdBo/Lm)Ob,DnVX@1.)(b*YbJGNb'-/"dSioA6+j/l@eDM*KBbPNZotn\$h1ea5SU"#X>!H1<=jlH<F/iBBmnFG6I1bf?TNN?fD:9EMaQiYNe-,h9n55/)qg4e@)l<,C[MPRli'pA;WZpE.9c?AJ3,lc#b_rJD"Bj(_e_9\W<qnsCp&t2Rc[6k!Zdb3BF@(Y@]mW@*h@-\]f\R-Q)%q])u4VQcHY`.b.17<YXD,,dpX(>Z^tY;=\ZEEj&.aDhs,T?0bYIqSTX_fnZBGR;/Epi_hCqg1Ge!m`2q(_0c21FBTbdLjDgk%92%@[QU2eaF`T-sJ&1%F*QP&J=YEVSe:S_ZVE&_P6@8:ud?aHn8PG.Jg8^:E'TVB9ba3bUVs$'K"L!Hn*7JMem%^3*P^b8Tc0_B5,K&h,hJUHU(dn'oD%&P\>UA&#j*2e6O5RnQT+?m]"MWkuC6]TL%V(^_=\6Z$Q.<tHpZ-.4(nW;M,W>N!IfeQ0htupPb:&sDel''121<+jgHAls7Z[:=]>l@3rPuUb?Rh4TM1ac<B\;DS:U[ts3>VUq:A?`d@2.Z:Z.:q$a!7m@@Q&>,qid629$W@*#o6Y^@3+u0&#SCSZNT'QI"BbjmM]D9X=WAOYibQ\>A]q:DToR)Np?+G(![4G:0pXbs%$6,-9M@!2DYqSmsIC*np@N73tZf9Il[&Anm]kF/H4b!j*eKq),;fOlRqQ$K`hV[!^`H1JH~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1810
>>
stream
Gau`T=``=U&:W67fZ?eIF9I.;#a23WRF[3B:,5C>.qp*t&;l&b7_D&SZJA:UHPa?VMmDY_7?'lif\LK7Kaj]")9#rI!r-=6j&6$g#1Fk8S8ak0LY("kpAn]Sg"ZX<3n/6h&D,DRaB!p1/4"s4>QYs\'?B7N*J6>3lMs:5gGWlh,V3:2$m<!O[_mYUL*[gZKAIl*0bhlh-%pW9^;B&HGV0RC3eUXbOW!R@.]"&d-7@<IC^[*P$pT.;EX$HFQ-B2LP(`=-A^Xp9Qn`3jcY)`>mUV]\1-:*P2jh>+!]j#c7>YSrH_9PLR4%5Co,:*5HE/1^5]iB@67^Vl"KS3K-6nO>3Efc/fdMi+rP#HO"g3Q>:UQ`#7lf97_fOq9+G_b'O/\)sX-]^De6>M=o#n4`5]2?dlp3oR(Z.;bA!#%m8.+%R!X1k"h^#:e8/>,#,moDl[IS,8T@8Ll,V1598ZZI(%KI^Jj&Q+'Z^ho9[(s5rQ06,gBUB6m-0u8T!gQ<lGHdC5g_VVU7hA6Ll,Uh).tULU,6g#1JVhn3bCnB%1r.<[beeK=U&haj1'sGXE53Ko$/;:';[&kbg>*LAhW#.#Ho<\V*=VGA]>fZoR[UNmcEM='Ri2FS$F4DrKEHkZNmi8J>@t7-jUZ#4-H$u8er%2lVLA(AlTnBfoMp3XHtn7;G`EE7WZ<)b]>R/&![Gi2(>?.:ll\oa9Yh[9-*YUV#bI6bTAX1;m`idurV;M`Q\pNi/VrLL]Z]bCH>8qk'/*SN\+/k"n)]VXX5cV>^LrmeL=N\7UQCfGMM5;]gJG_,)uD*U@]YKMS..2*s6:3<d=RBURL$,W,M3ic]n_J*`3fcPVuVi&08RFT]fc>Q7Im?h;%Sf>UYO1kN1[fMiF^_qg=0uL+htlgj$#eE&OT[59:3oN!$*WTeVI(iY=>S:],Ffd(1+G;eA#9]qZ+2S`a\=ZW'87[@+U[<$O%G7Ae87.]WFdK]c?Q+esLc\`N_CG=X3Mqlge*I;N:H_#YPjeRO@%qUA5(aHh"tn5a0m-QW:!Ga5]&C&M%,'\`9p[@eZV-chs)'1j!6b/7Z%[%jAE<0Q!`R[W1E<*_Om,fP"D\H9Bc@f0dN]^?OmWU7*+qjC*Jgg]-\Wr1*;SUd)BNS9m>Sflj*qn;$Vgd/gB#F@B9d;m]_L!`SGqJ/LJqP)F^+@_)r%/KV+g=UZ\4/5J$cRpi/__?3sh"r`MlVXshs:6Tgi-B=/2^f7DGR\K28poO?<hO4q2ca9C>Rb*&E'YD1%V\*Io(u**60?d/.Cs;qX(+5HOD-bs(#oFFfC`u)s/2n!h8u7aA3)"]Q>UR(NR$?$6;pAmPK+_hE.P;7Q&*@%9S.?%P[hAZKE3+u,PB'Kui:-\^1M2JZ5cZj:306M`LN"jK'7=(^%mc8=F,6+7D,ri'97N-P!D]]hjG"h-Qh*>dj)jF/39,e9`q)M^^D%SrgPVj(L;:9L3WuCcMOpbthS+QOc%^QUSU3tdVG7jR6cRkAAP_5h?%i:hkeG4c2VL]_jk@@[0s4,n]a47tDHME8AW&qQm-"F4n=87MHsg'+fYU8+ZYcp41LA-`>&*4/42>aDUFQ-/E(&G,=:$p[e9gdZ7<Kcebom[/fR%f@p\hnYkKY:Hk?Fc:Jh'B96>93Xc'VHqTuH?/1c9QqRX[`ToN6:]LIFdm^70Jhh:BFd[li[%P+JCsku7Dc",&k2h['>lq31$a%a_PIoUu'T/dsX?$<Aq-FlCjUiA(VoT)Wek?L18U1(V`i4jJP\ZL-dDG*_rJO'5>R'gFsaa#Rp]Q-,S;e#22W_*?Q%S;7~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1595
>>
stream
Gb"/'bBDVu&Dcq.k%(=$?iU=$:qOZFe"!4.;lNoF6W4Q<La!ZKFQtFRm2`q*LMYXDe#/udM@aai@V.;1+9RL>epN6WJ<rng3aof`/D%Z?&[6Wfh^ttV(?$n?KEK/YeFkm6hl^<_6rjMP93e+d$mbM%oE5HALm5UVNoN6_Gu,nk5Hu%:c_[3I5U:DUl!><I':3bs/VrGurmgNZ5?ZJ,M;,:&RdC?rYQsJ@25&ZT;!*!4/@R>1nbpKen@ai])9,jXn;A_p(`PHrgk-D]i0??)WQnN6(m^e%q4oo&!>Q(jN\.oeT^PjZo2QA25X,ELOp0c<5X(hWHPDfje6GeBUR7,![g8bQGL8js[<6WZ+q&fk\:a04Cb=UkXa>i3e.Rqq"378^"tlU$HIEY_pBdt,QUa1#3hh2jgO/"%YDHm"m]iqD^gLhAW:;4Bn<]B'^Zc"n7pDZU7kuOM$s^F7j(u`-TbUd+0%KDKj@:RUpeB%3,.jqD"Kf,V\;S0JY3j=XA084:5cG0$IBEoRla7'/(p+nd10>tbX,PHZr8oj"&T+"^2Wl[QF'+)]Wh<%L-oU;nS0_$cfX`kZ3tt2_f?)l8*QLG]+c@Ju7"qeL'neWu"dE5n9q5PF%b=ukX;@p;2\&LnA7]3_SaAtm@F\b5L=H-;dH9oSgZWq:\=4!4U)u&)DF`J[0d_Q&7M\Q:Sa[]+WAub(0(GDd'XV(iK-=nm4-8-,jIatJU;#+h/h7?+BbL(I2JKIJ(La7_F3X-./,%$&bMA#1#]&UIot#LaU1)U5lYL!]*'D+$E#]Q/XZ0GI0-t#WS113?Q0i\6&24>0o:UWEh^i6MV<`(Pa@LCc<1(AFELf#Oog3RYb^qrA$M?=/REA3KFJ8h8'rpt68PJsG:,5pN%PhD*a*bkD;KSU1/4QWl9/uIG:5DnjI`m]F6"XZr0dH\%#ehdg9cq4.lKm.;l-m,N9^<h-&q=(2=^L0(^43[)Xrop7`W'-AQYATt@"PGZXfaTrI_F7Rqape!TSQ(Y#>+mR(2:K]gKuF_qLIXJKYVG?/Ra")]+AI-rgVF9^)AVk47U3S1u+Z)<X'QZ5osCg9>PI->WaD_LJbF,U*:)Ki->m54665lijOnQs7=0G+3FL/il3;4KcU"K&3%WQOtk+WioWcu3PG];F8W2i^gLiT`GN+&`,01],8_G<I:I9/Ud73J_58!O8)4rjHj$\]<_4&-SfDNc(mOJ!BtL*4YM7H?SJ`i>YNh^bKJa:d<N7O1#1C=nqH]i6h(accbG)q/SG;("kM%lV5Ra8!S/$:'`!Ds[:7TeRTXW/eVg5LCJl2"PK<fjP,9<7Kkj0p^r2/Iu*34%BlND9"cFY:R1S2G">blM:+qk("1fCkZP>A$+.P]7%4e!nZM^gX^&SCq^ro7)YpUO0mi+9<RPaR/Km_Ci#%tF2D.b/8El]`#d\5$o>`XWCf!e#k&PL^c,2NL)5)##"j%^-NJa*PA:bN[6%D3@mgAZ(UXVTWe"b3"cRJ0\QEBEWgY/)610YKR\Ah1H'(;E:*O0B-MZT"_q)Hq=O_<Di$jO-K3HbtPVX43q:)"6;bF;UFVN".9%Vnu@,BIL"?j86u~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1742
>>
stream
Gb!#\?$k9`'Ya=F39q;r2q=(.=jC;FRl5,mAI2RZE\V&,\/:^VVHJ)IhnC?2!s<^?\m?pQK_:jr=%>dL3J)jB05CJVCOk"MG]c<'176::]#9<E-[7_XmOZ47o3Dl_,A\`85QXIUJQc/)Gb"8R"VcXW'B-R]$;,sW">J?sIHlHJ0RmM.+?m8_i;sV@j+8B;4+oDEY$L%,*ZXHHpk4A<&72%tR,o9HT`hJ5eQ?s!@T1VI@'YO70>IL+$1G3SbbJa8-1H8TA@*oGQ8'K,0B%og.[+ZneT7+*J]S;eTOlXGCuRIX^Mib.bd5PfKoMe,E"SukLmF1\0T*7Qc73MA_hdeu(Se0d9Zs(MNr/.J_:;:f?0F89N=n8`URNI_J0=1enCp8rQm<<A1,$Ld-IY+'h,n>0bHXELZGGUWXkaUj^"!0R+-UpQf`mbJ!9;5;1NK7WeO6N!JiLoY'W1C:GW&l_.;m-]aQ>0TgXVG[\5:;jRq4]8?Ci5W,/^J@J_Q4S%Wc8RCWkgC8*>4BK_nI-+kP\D_<kp017?u_56V5Rhg:ud1\e'26:-p=f5Uh,RHKCl"=u2*p4utNArA0PJ7iHe,8:ZXZP38jN-Kn-IL3Dr^l]+qZ(r+/#8Zk8@33]!YdRfo0<(b89.X/s>&cLOn-pbRC]!K:;9M@.1ZpH%UjbWd'Ig5ol>UF,icio$6XipP$Xoq!7`X)g,0SiR8arE>;5ro$m_20AdP?c[?Y/S2>rWC_=PH5Cgt(2BJ-:fdhMdnJjt\L5i:T!'B(0-,$ntQ;![kHLN^G\IF`qT,<2N(opMmHknQ+b>K!CompK4S>dlcN)2XNRPY'[%ZA',:ZN9@U^5EV,coWlTn[&@$^-7'q1Q"I:hXL5_>gij6-WbIi<M]tei?ieU4m9i-FjF(]:HCVl>Y/>3Yr,@6`.S^<olO)Tq.APE?Mph`ZZ_N&a6S(;iq)4+@rY8'U&u.j`PY,L:5D^FE)im[Cm0/+.'QAK4&U:Z#fioS;da3(EX:4/[q(U(_Cr":N;an(4PAr2;;TUc\B@!HC?nOYQ8W%hQnf8DBK`:9qW&jQSL'F_PL'5(P_BX4Xell;e>B\h@Z-;Q#b<b<s"q:C"leJi4FtROF0'cd(5-`\lXZP9)?KJt=gT]pf<5c)Sn"X"#3=CF.p2cbLZMV:=g0m29]^C8"l(>kl-#"=')B5eL^CqOYI:JqIm:BB.!,OJKdsW.Mf*JQmIBn[FF%W.dRaDG7(>gueQU<HA'nl+U#(g97&\Ul%30iuVL?rtC]%b6P$Kgkc\'KiV<VcpUa!"]q0]MA[77;<E$(+8k`hks<!`4^m!.PIZW.bgKO":N%lg7j@np_B$Z7(Iu\5q=#da\>`>,0cG*X.QuK9<gg[ORGBm*e:46XfN_6TjfAn9Fp5SfS%F:75BHiRad%3:539nF*`)[a0#_5*Kr#)D9g33(jK!1b&Uq]B"(IqLG1kfVo1n*%XblDqg;_G7,s%E;e'+*o&]>B>*>#rKtB[.=@[TR(5+pRE'WS1ioV`9q>Sk)L.7WIkB^4nns@E1MhMQ$bVTqnFjPQe&?:46%,X7ie^AUG%"$(4hftUUOT!lJF]I!BrZYuLf`%Z<"Pt[?iDW#Oa6tlWG+6l^ZtJfBf>t*p!kQG.i2VXFT*c3ahsuGEd.@Qam4,sT3P[&,93-2Wj]DdfNG+MKu8=(IJS9unf^fUa#V#lhT#5"m3I^:]H/.+"oW&%^nb\L"?u3D!j$kPQ4KP3~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1847
>>
stream
Gb!;d=``=U&:W67f_W]]`WuSd!<(KY3S^JleT@\KLX7:l;l0g)K]FJp^<<!,W5Ea5'3hnUE-Lj>@e\W2.9Q@>bMV7ui7Ss=^7[_/N$gk2ikY=H4<KWuN!T"lG?c9!Y\]p4na5<DoQ6=LlOhHU4p>A7,.I`8TL9&m!(/op.0hG5np!QRE6mO\!@"K.$#`2g-6?[,[6n3Fo@NYNI%l/S'<al\0WQ!.6O?pXeQT@$@SrK(0V62HS%#"BiK]kS4M4N@J\R/0iJiC=`]3mB$,=TY9N3A3,8ea7r4(CPB@LoQin=lL2>UM=.Z8Dq)L<2nj"pbNBB23FE<DYBGso0.K@/QmA?1MY(YLV,Zp?:X)]<7)P9GAeD&_'`CG"GuCRDGTfQG,=]54rPjb[J6=Cjt-qS[^cio-mc&)fs=bO('S4cD>krDn%<2&d+7)(3Ws/</iuq.1^jPP<"t;RQTr_;BggWY4BH%*V&]Ma0cm>c;qF;ONu%BPZ^-4s.qg%WO'q\''%>B_hSZ_AJP)DS@H;3X'RPKA/O[C<ap92'b;FiW86pQ<F<)AI+7</)N$)7mX-I%Ymq4m!s[&<[WTil6*_7H4Z0AlYmuS_@@];I6NjOe5,2@7L+8o<!m8b2Qik.b.\#qrJj+K&YVNF)bGf.0A@d?$`"s@n8^p%\8,5YhUQ?1B*'0;db_1SR`AT8UM,tAK%@@;\AE+e^@X]9B,##nfm10O-`hd].B^5k+-][$C$A][-E:X%V\PRY40KaTqXH#IPh13nhnki=kr?1*M_gJB<p>@meL@ANM=Y)W*l1`'ID3Sd>P[)dDU6XOi`K]u:*5Zgltm&u;;2Am($0fSUeQ\=&R97X2TT*^doGFpa@b1I@$FnG3L/je:,iJ\5L,g;YG9mhCrp_5WBeM1j_e^[TJ-%[jb(c2K2%<"%"p*nR(;*b1;OK0''/kX?nQN1OnpCqA:!o^"3+_S]+bpJF#I.PNj)]<_^"6M&Lo.i<d?FNC2ofc*60mgh-i!>h<$?UDbd@Xf&)7Da!jA(RV]dQjnp)nr`%8hOcV$p'=CB+F%@'T*6BugK@$,.JBM98[HB*mo&OYRW'!TMLY8Q\aQ=Q+NR*NB#dpW"+e?+2eJT'2f*H"AC^moB_gLNa?I=)2QAg]B7)a&CjaN7mNlClGU6,aPm@%Muhf<"+RVLD96E/#+,Un`E>,Xg8XFpaFhV+WU$dj<;FiLM#Y7M"@F?WeIejdFIVY_3jDFPQIVd7Vj((m$7SPOW&R0O0!.W-VFG>5D(`+a+@,[!^"mB*oRdC_CRoHJu[/dH8'h)W*A4m/T^iTg'#e-K"mR)rer^sR0O2;inZkJ\3?fYh3&I2ibsE6YnNA"cR)kQ%dh&JfmjbH?0l/D>l%gi0N)DuK\8[m\ro1E"2r=Pb7;3%[IaB`r5XI$4=YT&gg05f5J9)1iuj.KVeA;II-JqM7`%-YTV0T5Ztr.1:r!Q'S2_#r\]X8g5R>?ptG;8`JFjNqE2T)Xtj5$?IX'%!GbUF8RJ)S0(*=B*Re"^,:W!9e&__9Fk_Fq[Q'H]>d)IL,(Ojb<Qt=ZbPE8A)F-@r^_!!a/kYCbRW<blYFta]r'@Ss0nuM5YWBLMd*\F3dL9Y2=^bDTYC"<!rTULn"4#<jhu]!q@Gicgc9uSf@'Sl$*m7%#;?^cpLGDeIY`<GA.YJFec0E#FrF+1\h1lR<,/eWF3nJm,%ns83P]GC@unI*/r=+)fr6uMP0@g,0Y<6d1!tW`c44farGU<:i[0285*+g&S)qPoZh!Y;X7'::Mc'!I^;[EqGD=u)ocZY2*tT(^?JJenl/KKuTYO2s4n"WWJbLMX6>7:[I'[317Y]<?)%'=m~>endstream
endobj
29 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1944
>>
stream
Gb!Sm=``=U&:W67fM5e<:.,,ql'G0)`(ai!3?$G=8_*(@,V2e:!>^@L1r$a48j$W)[N(?kf$[)98F5m*gVkd5J`)GeT5bA1Hj%=p$IjgX2,MnF@Isb1Nd?3L/DIW9f)s2&M%eQM7?70G2<bG:I*7T;LdFW6.h#[%6Y_<`'S;>9Jq+&u3*cL5UL46h#soH0OEuB>Dp<JWhmQCiCO*W9h4DBi0/Vf[QG9j<c!7qZ(d:7;:,Poif-8]P4;d2,J,A[)28#(c?H,nm1\aZp*`"YPD**8\YMfXlnk9g(4b:uh7R%ao*MKY%>9rin9qcKM.>pU!7aQK?8Hf2g3`8-aN3sM[?JC2O=Fl#Sc?g<Q2"D6Rk'Mfi_5N-^>.04H&`c)6#Zg^Y@p.+eM4*!+22cu#]-QU#K_7-@Y8Qpo>SB`r8lRqh>/\EFW.fTGqOto\ActQ1(Klfn>@H\QBn_[M=55%&0c=:S[]\f&hR*=&]kOYoPBj:]I!FU8<0<JKiL1OQ<K!s"VR@"q@g.m0LXi+"JBD;nY!L-^s-:c<Oms%k(=1Ze%l,$KX][*lDBdRSD1p&NZkb*J*)]XKQ=Yh!f&*$DC*=_Yb<%_:8Cj<IaJ>k-]`#M]$j6T!9u@>P>SU'M9`=]X?=fYiBCK4W%1XNa=G@m4KO:]I3o;W'[lK+LcplElVm,Jh&b/!;V(fn>V1(ZK$G4.NU"4gA`)*tSBIMYpOB*34Fe+o:)gpt5"pP/H'P7C1rH\,^]IUMMiXD\f(MCW/P+H5`=mE=tJp+<0(YI!T59:Dd8N#:B2:isKY4(MFkA==PHW0UoYEl85[n?%u'O"(-XNpNSC,Q\`E]]^\Fh:sB>0F=C4gU_%JeQ_TmG15"@I'9b_-S?,C3c%u[*c95E$_A=[0%09T5t#hD?<&]0O(?L`Z8,m$!0>YV:>!U26UBB,jV.-<$-/&[1Q4`3TIANc\4%:9^K/)lN;/g?trt;","^ckq*IJ8ZK6rfS<R0_B<@8bh7Wg$,Lit(8#P'bp$L49LQbB/toQ:RIatDBClr.0tf!Ebk\IrQ<c%s9QEJ[G2N$bIK0:+q$g)!iFoD>J'-esL-14l>c(tjDW.1H"TZ/hV3K-t3R8O@kcNLPC/&`QD8.sqLtlC%PK!U5?^dd[df4h^X1Xr0r4lm)'k`sG"TPP.X=.J=l3UfiY*&nZVVqgfhGJI'VrnFf+osmQdgQ>&gjb6"@O_'dIcW-3:LV2ilm<Ihp^-L58(&]u_fO]uU$&hh,D4`q^6Rd=$4:>oj/lk$c2rg<"H*Os0JM:t/OW10EO`gRdH.'\8)5D"q5]%FPs#1&8GZaGo0QpZUs%^IY,%o;cW_5@-71if%',d92op%rV;,`:[ihqO==c;O*ZpOQbRb0$c^@DJa+B&;0NE:\*M#I$7@Y]PTM0CA3#F_`Y\FKF(g3`[[!N#tND>ihUPG*9<M$9n].*rBQ[#WSjWnGh>m)U\[;OX$g\Kc\\MAEB<-c*,mnu;GN8_eP71)s,F/>\d/rEg8]FuX2<nG_Ms.*M^.CKOiYY@K_?m9l*.KPXMc@1orE;<^[8.T\8U])W6`<;K3?"5IV<13'[^bH<P'6JZXc`osVXHm:ON>\m,AVh)1(dIiAg7S[OJ=,<ee0MF[F!h>hG$-kqF1a%sN:?;9a*E31cS/:ARN0ep6HPNamr<io\LDL%9.k?j'WZh`6j0Z[g9J_G>901;eQMX\<X1ddmsf2TF"&nU?tD&K(>jP;6G9=ur2:j*9@H!TCR&!]hW&.#P)cmKYCi*9&jMR##7-)C$m@"!1U-n&dWcpR6mc%<;bRkR79UV%0V"#tG??U$,L1>YAg(0E!QNMr15Mj*=Mh^biPUFLkP(QD9K:7-?;[AB5*hYD#2D^ddMWf(H74[agEf^/k3&]?Vie4[`Jf$P$WYJ.-l6mIb9(JJHCKtOp$h+fehlUh$QBFubl~>endstream
endobj endobj
xref xref
0 30 0 18
0000000000 65535 f 0000000000 65535 f
0000000061 00000 n 0000000061 00000 n
0000000132 00000 n 0000000132 00000 n
0000000239 00000 n 0000000239 00000 n
0000000351 00000 n 0000000351 00000 n
0000000556 00000 n 0000000456 00000 n
0000000661 00000 n 0000000661 00000 n
0000000738 00000 n 0000000744 00000 n
0000000821 00000 n 0000000949 00000 n
0000001026 00000 n 0000001026 00000 n
0000001231 00000 n 0000001231 00000 n
0000001437 00000 n 0000001437 00000 n
0000001643 00000 n 0000001507 00000 n
0000001849 00000 n 0000001802 00000 n
0000002055 00000 n 0000001881 00000 n
0000002261 00000 n 0000003770 00000 n
0000002467 00000 n 0000006014 00000 n
0000002673 00000 n 0000008177 00000 n
0000002743 00000 n
0000003032 00000 n
0000003154 00000 n
0000004966 00000 n
0000006789 00000 n
0000008938 00000 n
0000011050 00000 n
0000012911 00000 n
0000014813 00000 n
0000016500 00000 n
0000018334 00000 n
0000020273 00000 n
trailer trailer
<< <<
/ID /ID
[<459a20e2e948bef44ee56942063a15ff><459a20e2e948bef44ee56942063a15ff>] [<615b7d5ff26d2613909ae4f44e88db16><615b7d5ff26d2613909ae4f44e88db16>]
% ReportLab generated PDF document -- digest (opensource) % ReportLab generated PDF document -- digest (opensource)
/Info 18 0 R /Info 12 0 R
/Root 17 0 R /Root 11 0 R
/Size 30 /Size 18
>> >>
startxref startxref
22309 9474
%%EOF %%EOF

View File

@@ -1,240 +0,0 @@
# Lesplan Les 3: Cursor - AI-Powered Code Editor
---
## Lesgegevens
| Item | Details |
|------|---------|
| **Les** | 3 van 18 |
| **Onderwerp** | Cursor - AI-Powered Code Editor |
| **Deel** | 1: AI Foundations (Les 1-4) |
| **Duur** | 3 uur (180 minuten) |
| **Spreektijd** | ~55 minuten |
| **Hands-on** | ~75 minuten |
| **Pauze** | 15 minuten (10:15-10:30) |
---
## Leerdoelen
Na deze les kan de student:
1. Uitleggen wat Cursor is en waarom het beter is dan OpenCode
2. Cursor installeren en een Hobby/Student account aanmaken
3. Cursor Skills/Docs installeren voor Next.js, React en Tailwind
4. Een .cursorrules bestand genereren met AI Chat
5. Een nieuw Next.js project opzetten met npx create-next-app
6. De vijf AI features gebruiken: Chat, Inline Edit, Composer, Tab Completion, @mentions
7. Efficiënt omgaan met requests (Hobby account management)
---
## Voorbereiding docent
### Technische setup
- [ ] Cursor geïnstalleerd
- [ ] Student account geactiveerd
- [ ] npx create-next-app getest
- [ ] Cursor Skills/Docs toegevoegd (Next.js, React, Tailwind)
- [ ] Demo .cursorrules gegenereerd
- [ ] Huiswerk zipbestand gemaakt (les3-debug-challenge.zip) met:
- Missende dependencies in package.json
- Syntax errors in componenten
- Inline styles die naar Tailwind moeten
- Missing imports
### Student voorkennis (uit Les 1-2)
- GitHub account, basis git
- npm/node.js geïnstalleerd
- Terminal basics
---
## Lesverloop
### Blok 1: Welkom & Mededeling (10 minuten)
| Tijd | Activiteit | Slides |
|------|------------|--------|
| 09:00-09:02 | Welkom, titelslide | 1 |
| 09:02-09:04 | Planning vandaag | 2 |
| 09:04-09:07 | Terugblik Les 2, waarom Cursor | 3 |
| 09:07-09:10 | Wat is Cursor? Features overzicht | 4 |
**Kernboodschap:** "Cursor is speciaal gemaakt voor developers. Geen token limits, ingebouwde terminal, gratis Student plan."
---
### Blok 2: Installatie & Setup (20 minuten)
| Tijd | Activiteit | Slides |
|------|------------|--------|
| 09:10-09:13 | Download Cursor | 5 |
| 09:13-09:18 | Account aanmaken (Hobby → Student later) | 6 |
| 09:18-09:22 | Interface tour | 7 |
| 09:22-09:30 | Terminal Setup Check SAMEN | 8 |
**Checkpoints:**
- ✓ Cursor geïnstalleerd en geopend
- ✓ node -v, git --version, npm -v werken
---
### Blok 3: Cursor Features (15 minuten)
| Tijd | Activiteit | Slides |
|------|------------|--------|
| 09:30-09:33 | Chat Panel (Ctrl+L) | 9 |
| 09:33-09:36 | Inline Edit (Ctrl+K) | 10 |
| 09:36-09:39 | Composer (Ctrl+I) | 11 |
| 09:39-09:41 | Tab Completion (GRATIS!) | 12 |
| 09:41-09:43 | @ Mentions + @docs | 13 |
**Kernboodschap:** "Vijf features, elk voor ander doel. Tab Completion is GRATIS - gebruik dat voor kleine dingen!"
---
### Blok 4: Skills, Rules & Requests (15 minuten)
| Tijd | Activiteit | Slides |
|------|------------|--------|
| 09:43-09:48 | Cursor Skills installeren (Next.js, React, Tailwind docs) - SAMEN DOEN | 14 |
| 09:48-09:53 | .cursorrules genereren met Chat | 15 |
| 09:53-09:58 | Request management tips (Hobby account) | 16 |
**Checkpoints:**
- ✓ Docs toegevoegd in Cursor Settings
- ✓ Studenten begrijpen request kosten
**Belangrijk:** Benadruk dat Tab Completion GRATIS is. Chat/Composer/Inline Edit kosten elk 1 request.
---
### Blok 5: Live Demo (15 minuten)
| Tijd | Activiteit | Slides |
|------|------------|--------|
| 09:58-10:13 | LIVE DEMO: Nieuw project workflow | 17 |
**Demo flow:**
1. mkdir + git init (1 min)
2. npx create-next-app@latest . (2 min)
3. npm run dev → toon localhost:3000 (1 min)
4. Chat: genereer .cursorrules (3 min, 1 request)
5. Composer: maak HeroSection component (4 min, 1 request)
6. Toon resultaat in browser (1 min)
7. git add + commit (1 min)
**Doel:** Studenten zien de hele workflow in 15 minuten
---
### Blok 6: Hands-On Opdracht (75 min + 15 min pauze)
#### Deel 1 (10:13-10:15, intro + start)
| Tijd | Activiteit |
|------|------------|
| 10:13-10:15 | Opdracht uitleggen (Slide 18 op scherm) |
#### Hands-on Deel 1 (start - 10:15)
Studenten starten met STAP 1 (npx create-next-app)
---
#### ☕ PAUZE (10:15-10:30)
---
#### Hands-on Deel 2 (10:30-11:45)
**Check [10:45]:**
"Wie heeft npx gedaan? Wie heeft .cursorrules? Wie heeft een component?"
**Check [11:00]:**
"Wie heeft al 2 componenten? Hoeveel requests heb je nog over?"
**Check [11:30]:**
"Wie heeft git gecommit? Wie wil zijn project laten zien?"
---
### Blok 7: Afsluiting (15 minuten)
| Tijd | Activiteit | Slides |
|------|------------|--------|
| 11:45-11:50 | Resultaten delen (3-4 studenten) | 19 |
| 11:50-11:53 | Samenvatting | 20 |
| 11:53-11:58 | Huiswerk uitleggen (DEBUG CHALLENGE!) | 21 |
| 11:58-12:00 | Volgende les preview | 22 |
---
## Materialen
- **Slide-Overzicht:** Les03-Slide-Overzicht.md
- **Lesopdracht:** Les03-Bijlage-A-Lesopdracht.md
- **Huiswerkopdracht:** Les03-Bijlage-B-Huiswerkopdracht.md
### Huiswerk zipbestand voorbereiden
**les3-debug-challenge.zip moet bevatten:**
- Een Next.js project (App Router, TypeScript, Tailwind)
- package.json met 1-2 missende dependencies (bijv. lucide-react gebruikt maar niet in deps)
- 1-2 syntax errors (missing closing tag, typo in import)
- 3-4 componenten met inline styles (style={{...}}) die naar Tailwind moeten
- Een component met een missing import
---
## Veelvoorkomende problemen
| Probleem | Oorzaak | Oplossing |
|----------|---------|-----------|
| npx create-next-app faalt | Node.js te oud | nvm install 20 && nvm use 20 |
| npm run dev werkt niet | Verkeerde folder | cd naar project folder, npm install |
| Chat/Composer reageert niet | Requests op of internet | Check cursor.com/account, check internet |
| .cursorrules werkt niet | Niet in root of typo | Check bestandsnaam en locatie |
| Cursor crash | Memory issue | Restart Cursor |
| Terminal commands not found | PATH niet goed | Restart Cursor, check nvm |
## Request Budget Planning
| Activiteit | Requests |
|------------|----------|
| .cursorrules genereren (Chat) | 1 |
| Hero component (Composer) | 1 |
| Styling tweaken (Inline Edit) | 1 |
| Extra component (Composer) | 1 |
| Vragen/debugging (Chat) | 2-3 |
| **Totaal in de les** | **~6-7** |
Tab Completion kost GEEN requests - benadruk dit!
---
## Voorbereiding Tim
**1-2 dagen voor les:**
- [ ] npx create-next-app testen op eigen machine
- [ ] Cursor Skills/Docs toevoegen
- [ ] .cursorrules genereren als demo
- [ ] Huiswerk zip maken met fouten
- [ ] Upload zip naar Teams
**Dag voor les:**
- [ ] Projector test
- [ ] Internet snelheid checken
- [ ] Cursor fresh install testen (wie upgrade snel?)
**Tijdens les:**
- [ ] Iedereen npx create-next-app laten draaien
- [ ] Terminal checks KRITISCH (slide 8)
- [ ] Request management benadrukken
- [ ] Active walking tijdens hands-on
---
**Succes met Les 3!**

View File

@@ -1,354 +1,309 @@
# Les 3 — Lesstof # Les 3 — Lesstof
## Cursor — AI-Powered Code Editor ## Cursor Basics
**Vak:** AI Fundamentals **Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Vorige les:** Les 2 — OpenCode **Vorige les:** Les 2 — OpenCode
**Volgende les:** Les 4 — TypeScript Fundamentals **Volgende les:** Les 4 — TypeScript Fundamentals
--- ---
## Inhoud ## Doel van deze les
1. [Wat is Cursor](#1-wat-is-cursor) Aan het einde van deze les heb je:
2. [Setup + eerste keer opstarten](#2-setup--eerste-keer-opstarten)
3. [De vier kern-features](#3-de-vier-kern-features) - Cursor geïnstalleerd en geactiveerd met je Student Plan
4. [Chat (Cmd+L)](#4-chat-cmdl) - Skills/Docs geconfigureerd voor Next.js, React, Tailwind
5. [Inline Edit (Cmd+K)](#5-inline-edit-cmdk) - Een werkend Next.js project opgezet via `npx create-next-app`
6. [Composer / Agent (Cmd+I)](#6-composer--agent-cmdi) - De drie hoofdsneltoetsen (Tab / Cmd+K / Cmd+L) actief gebruikt
7. [Tab — predictive completion](#7-tab--predictive-completion) - Een `.cursorrules`-bestand in je project
8. [@-mentions — context erbij halen](#8--mentions--context-erbij-halen) - Een Hero-component, een styling-tweak en één extra feature gebouwd
9. [.cursorrules — project-context](#9-cursorrules--project-context) - Je eerste commit naar Git gedaan
10. [Debug-flow met AI](#10-debug-flow-met-ai)
11. [Wanneer Cursor vs Console](#11-wanneer-cursor-vs-console)
--- ---
## 1. Wat is Cursor ## 1. Wat is Cursor
**Cursor** is een AI-first code editor — een fork van VS Code met AI-features verweven in elk onderdeel. Geen plugin, geen sidebar — AI is overal: tab-completion, chat, inline edit, agent mode. Cursor is een **fork van Visual Studio Code** met AI als kernfeature ingebouwd. Dat betekent: alles wat je in VS Code kunt — file explorer, terminal, debugger, extensies — werkt hier ook. Het verschil: AI is geen plug-in maar een onderdeel van de editor zelf.
### Waarom Cursor? ### Werkt met meerdere modellen
- Vertrouwde VS Code interface (alle extensies werken) Cursor laat je per chat kiezen welk AI-model je gebruikt:
- AI features die écht in je workflow zitten - **Claude** (Opus, Sonnet, Haiku) — sterk in code-redenering
- Multi-file edits met diff-preview - **GPT** (4, 4o, 5) — solide allround
- Background Agents (later in de leerlijn — Les 15) - **Gemini** (Pro, Flash) — snel en goedkoop
- Werkt met OpenAI, Anthropic, of eigen API keys - **Lokaal** (via Ollama) — privacy en kosten-vriendelijk
### Cost ### Waarom relevant
- **Hobby (gratis)** — limited, Tab completion, basic chat - Industry-standard in AI-assisted development
- **Pro ($20/maand)** — alles, onbeperkt, Background Agents - Veel vacatures noemen het expliciet
- **Business** — teams, SSO, audit - Gratis Pro Student voor NOVI-studenten
Voor deze leerlijn: Pro aanrader. Hobby werkt voor leren maar je raakt rate-limits.
--- ---
## 2. Setup + eerste keer opstarten ## 2. Student Plan activeren
### Installatie ### Wat krijg je
Download van cursor.com — installeer net als VS Code. | Tier | Fast requests/maand | Premium models | Tab-completion |
|------|--------------------:|----------------|----------------|
| Free | 50 | beperkt | onbeperkt |
| Pro | 500 | volledig | onbeperkt |
| **Pro Student** | **500** | **volledig** | **onbeperkt** |
| Business | 1000+ | volledig | onbeperkt |
### Eerste opstarten Pro Student = gewone Pro, maar gratis voor verified studenten.
1. Sign in met email of GitHub ### Hoe activeer je
2. Cursor vraagt: "Import VS Code settings?" — Ja
3. Kies thema, snelkoppelingen
4. Opt-in op AI training of opt-out (privacy keuze)
### Model kiezen 1. Ga naar `cursor.com/students`
2. Log in met je **NOVI e-mailadres** (niet je persoonlijke Gmail)
3. SheerID verifieert je via NOVI's inschrijfsysteem (110 min)
4. Cursor upgrade je account automatisch naar **Pro Student**
Settings → Models. Defaults: ### Verifiëren
- Cursor Auto (default — Cursor kiest beste model)
- Claude Sonnet 4.6 (default voor code)
- GPT-5 (alternatief)
- o3 (reasoning — trager maar slimmer)
Vuistregel: Sonnet voor 90%, o3 voor complex. Open Cursor → Settings (Cmd+,) → Tab **General** → check **Account**. Status moet **"Pro Student"** zijn.
### Een project openen Als het "Free" blijft: log opnieuw in via cursor.com/students en herstart Cursor.
Twee opties:
- File → Open Folder (zoals VS Code)
- Terminal: `cursor .` in je project-folder
--- ---
## 3. De vier kern-features ## 3. Installeren
Cursor heeft veel features. Vier die je dagelijks gebruikt: ```bash
# 1. Download van cursor.com (kies je OS)
# 2. Installeer als gewone app (sleep naar Applications op Mac,
# next-next-finish op Windows)
# 3. Open Cursor → Sign in
# 4. Optioneel: importeer VS Code instellingen (klik Ja bij de prompt)
```
| Feature | Shortcut | Wanneer | Eerste keer Cursor opent vraagt hij:
|---------|----------|---------| - VS Code settings importeren? **Ja** (theme, keybindings, extensies blijven)
| **Tab** | (auto) | Voltooi-suggesties tijdens typen | - Welk model kiezen? Voor nu: laat default staan
| **Inline Edit** | `Cmd+K` | Korte edit op selectie of cursor-positie |
| **Chat** | `Cmd+L` | Vragen stellen, code uitleggen |
| **Composer/Agent** | `Cmd+I` | Multi-file edits, refactors |
Deze leren = 80% van Cursor's waarde gebruiken.
--- ---
## 4. Chat (Cmd+L) ## 4. Skills / Docs configureren
**Wat:** sidebar-chat. Type vraag, krijg antwoord. Net als ChatGPT maar mét je code als context. Cursor kan officiële framework-documentatie indexeren. Vervolgens kun je in chat schrijven `@docs:Next.js` en de AI antwoordt met actuele info uit die docs.
### Wanneer ### Setup
- Code uitleggen ("wat doet deze functie?") 1. Settings → **Features****Docs**
- Algemene vragen tijdens coden ("hoe gebruik je useMemo?") 2. Klik **+ Add new doc**
- Debug-hulp ("waarom werkt dit niet?") 3. Voeg toe (één voor één):
- `Next.js` (URL: `https://nextjs.org/docs`)
- `React` (URL: `https://react.dev`)
- `Tailwind CSS` (URL: `https://tailwindcss.com/docs`)
4. Wacht tot indexering klaar is (12 min per doc)
### Context erbij ### Gebruiken
- `@filename` — voeg specifieke file toe als context In Cmd+L (chat):
- `@Codebase` — hele repo (gebruikt embeddings)
- `@Web` — search het web
- `@Docs` — geïmporteerde documentatie
- Sleep een file naar chat — toegevoegd als context
### Agent-mode binnen Chat ```
@docs:Tailwind hoe centreer ik verticaal en horizontaal met flex?
```
Switch knop bovenaan: Ask | Agent. Met Agent mode kan Chat ook files bewerken — vergelijkbaar met Composer. Cursor antwoordt met de juiste Tailwind-classes uit de officiële docs.
--- ---
## 5. Inline Edit (Cmd+K) ## 5. Eerste Next.js project
**Wat:** edit-popup op je cursor-positie. Beschrijf wat je wilt, AI past selectie of nieuwe code aan. ### Scaffolden
### Voorbeelden ```bash
cd ~/web # of waar jij je projecten houdt
npx create-next-app@latest mijn-eerste-cursor-app
```
Selecteer een functie + `Cmd+K`: Prompts:
> "Voeg error handling toe"
AI past selectie aan, diff zichtbaar, accept/reject. | Vraag | Aanbevolen | Waarom |
|-------|-----------|--------|
| TypeScript? | **Ja** | strict typing voorkomt bugs |
| ESLint? | **Ja** | code-style consistency |
| Tailwind CSS? | **Ja** | utility-first styling |
| `src/` directory? | **Ja** | houdt project-root schoon |
| App Router? | **Ja** | moderne Next.js flow |
| Turbopack? | **Ja** | snellere dev-builds |
| Import alias `@/*`? | **Ja** | `@/components/x` ipv `../../components/x` |
Lege regel + `Cmd+K`: ### Starten
> "Genereer een TypeScript interface voor een User met name, email, role"
AI inserts code. ```bash
cd mijn-eerste-cursor-app
npm run dev # → http://localhost:3000
```
### Wanneer ### Openen in Cursor
- Snelle wijziging op één plek ```bash
- Code generaten tussen bestaande regels cursor . # opent huidige folder in Cursor
- Selectie aanpassen ```
Voor multi-file edits → Composer. Of: File → Open Folder → kies de map.
--- ---
## 6. Composer / Agent (Cmd+I) ## 6. De drie hoofdsneltoetsen
**Wat:** de zwaarste AI-feature. Multi-file edits met preview van alle diffs. | Sneltoets | Functie | Kost |
|-----------|---------|------|
| **Tab** | Accept AI auto-completion | **Gratis** |
| **Cmd+K** | Inline edit op selectie | 1 request |
| **Cmd+L** | Chat-paneel openen | 1 request |
### Tab — autocomplete
Cursor analyseert je hele bestand én recente edits. Hij suggereert hele blokken code, niet alleen één woord.
**Voorbeeld:** typ een commentaar `// render team list` op een lege regel. Cursor genereert de hele component eronder. Druk **Tab** om te accepteren, **Esc** om te negeren.
**Wanneer:** boilerplate, herhalende patronen, voorspelbare code (CRUD, list renders).
### Cmd+K — inline edit
Voor gerichte één-bestand-wijzigingen.
**Stappen:**
1. Selecteer code (of zet cursor op een regel)
2. Druk **Cmd+K**
3. Input-balkje opent boven je code
4. Typ je instructie: *"converteer naar TypeScript"*
5. Cursor toont een **diff direct in de editor**
6. **Tab** = Accept, **Esc** = Reject
**Wanneer:** variabele hernoemen, één functie refactoren, types toevoegen, commentaar uitleggen.
### Cmd+L — Chat
Voor vragen en grotere taken.
**Stappen:**
1. Druk **Cmd+L** — chat-paneel opent rechts
2. Optioneel: gebruik `@`-mentions voor context (zie volgende sectie)
3. Typ je vraag of taak
4. Cursor antwoordt + kan diffs voorstellen
5. Apply / Reject per file
**Wanneer:** uitleg vragen, grote multi-file taken, code-review.
---
## 7. Context via `@`-mentions
In Cmd+L (chat) type je `@` voor een autocomplete-menu:
| Tag | Wat het doet |
|-----|--------------|
| `@file:foo.tsx` | inclusief één specifiek bestand |
| `@folder:components` | hele map |
| `@code:functionName` | losse functie of class |
| `@docs:Next.js` | officiële Next.js docs (eerst configureren) |
| `@web` | live web-search |
| `@git` | git history / blame |
**Voorbeeld:**
```
@file:page.tsx Refactor deze component zodat hij @docs:React useReducer gebruikt in plaats van useState.
```
---
## 8. Request management
500 requests per maand klinkt veel, maar zonder discipline ben je er in een week doorheen.
### Tips
- **Combineer in 1 Cmd+L:** *"maak een Hero met titel, ondertitel, twee CTA-knoppen, gradient achtergrond en responsive"* = één request voor een hele feature.
- **Tab is gratis** — gebruik het voor alles wat eenvoudig is.
- **Cmd+K voor gerichte edits** — niet voor "maak iets nieuws".
- **Cmd+L met `@file`** — bespaar Cursor het zoeken naar context.
### Anti-patterns
- Tien losse Cmd+K's voor één feature
- Cmd+L met te weinig context (*"maak iets cools"*)
- Vergeten dat Tab bestaat
---
## 9. `.cursorrules`
Een bestand in de root van je project dat Cursor bij elke prompt meeleest.
### Voorbeeld ### Voorbeeld
`Cmd+I` opent Composer panel: ```text
# .cursorrules
> "Voeg een /about pagina toe met team-leden in een grid. Match styling van homepage. Gebruik Tailwind. Maak een aparte component voor team-member." - Gebruik TypeScript strict mode
- Tailwind voor styling, geen CSS modules
Composer: - Server components by default; "use client" alleen waar nodig
1. Leest `app/page.tsx` voor stijl - Imports met @/ alias voor src/
2. Maakt `app/about/page.tsx` - Functional components, geen classes
3. Maakt `components/TeamMember.tsx` - Geen any-types; expliciete return types op exports
4. Toont alle diffs
Jij accepteert per file of "Accept All".
### Wanneer
- Nieuwe features (multi-file)
- Refactors
- Bugfixes die meerdere files raken
### Terminal commands
Composer kan ook terminal-commands runnen — vraagt approval. "Run pnpm install" → Composer doet het.
---
## 7. Tab — predictive completion
Cursor's Tab-completion ziet **wat je gaat typen voordat je 't typt**. Vergelijkbaar met GitHub Copilot maar slimmer (gebruikt je hele file + recent edits).
### Hoe gebruiken
- Type — suggesties verschijnen grijs
- `Tab` — accepteer
- `Esc` — afwijzen
### Cursor Tab vs Copilot
Cursor Tab kan:
- Multi-line suggesties (hele functie ineens)
- Cursor verplaatsen naar volgende edit-locatie (handig bij refactors)
- Suggesties op basis van wat je net hebt geschreven elders
Verschil voelbaar na een dag gebruik.
### Wanneer uitzetten
Soms ga je sneller zonder. Cmd+Shift+P → "Cursor Tab" → toggle.
---
## 8. @-mentions — context erbij halen
`@` in chat of Composer = context kiezen.
### Soorten
| Mention | Wat | Voorbeeld |
|---------|-----|-----------|
| `@File` | Specifieke file | `@app/page.tsx` |
| `@Folder` | Hele folder | `@components/` |
| `@Codebase` | Repo via embeddings | `@Codebase` |
| `@Docs` | Geïmporteerde docs | `@Next.js` |
| `@Web` | Web search | `@Web "Next.js 16 caching"` |
| `@Git` | Recent commits | `@Git` |
| `@Definitions` | Symbols | `@Definitions:UserController` |
### Goede prompts
Zonder mentions: AI gokt welke files relevant zijn (langzamer, soms fout).
Met mentions: AI heeft exact wat 't nodig heeft.
```
@app/api/auth/route.ts @lib/supabase.ts
Voeg een endpoint toe /api/user dat de logged-in user returnt.
Gebruik supabase auth.getUser().
``` ```
--- ### Genereren
## 9. .cursorrules — project-context Vraag Cursor het zelf in Cmd+L:
**`.cursorrules`** = markdown-file in project-root die Cursor leest bij elke prompt. Net als AGENTS.md in Les 2.
### Voorbeeld
```markdown
# Cursor Rules — QuickPoll
## Stack
- Next.js 16 App Router
- TypeScript strict mode
- Tailwind v4
- Supabase (DB + Auth)
## Conventies
- Server Components default, "use client" alleen waar nodig
- Tailwind classes inline, geen CSS-files
- Geen comments tenzij gevraagd
- Volg bestaande naming (camelCase functions, PascalCase components)
- Throw geen errors uit RSCs — return { error } object
## Verboden
- Gebruik geen Next.js Pages Router (alleen App Router)
- Geen any-types in TypeScript
- Geen inline styles
``` ```
Maak een .cursorrules voor dit Next.js project. Focus op TypeScript strict, Tailwind, en App Router conventies.
```
Cursor stelt een goede basis voor. Pas aan naar smaak en commit het in je repo.
### Effect ### Effect
AI checkt deze rules bij elke prompt. "Maak een Server Component" wordt automatisch "use client"-vrij. "Voeg comment toe" wordt overgeslagen. Je hoeft niet elke keer "vergeet niet TypeScript te gebruiken" in je chat te typen. Cursor weet het al.
### Beperkingen > **Vooruitblik (Les 14):** in nieuwere Cursor-versies is er ook een `.cursor/rules/`-map met meerdere `.mdc`-bestanden + YAML front-matter (`alwaysApply`, `globs`). Voor nu is `.cursorrules` in de root prima en simpeler.
- Cursor's parsing is niet 100% strikt — soms vergeet AI rules
- Hou 'm tussen 30-200 regels (te lang = vergeten)
- Test of rules echt werken — soms moet je expliciet refereren
--- ---
## 10. Debug-flow met AI ## 10. Hands-on — wat je gaat bouwen
### Pattern 1 — Console error Drie kleine taken in je net gescaffolde project:
Console error → kopieer → in chat plakken: | Taak | Wat | Sneltoets |
|------|-----|-----------|
| **1** | Hero-component (titel, subtitel, 2 CTA-knoppen, gradient, responsive) | Cmd+L |
| **2** | Styling-tweak: kleuren/spacing van de knoppen | Cmd+K |
| **3** | Extra feature naar keuze: FeatureCards / ContactForm / Footer | Cmd+L of Cmd+K |
``` Daarna `git add . && git commit -m "first cursor build"`.
TypeError: Cannot read property 'name' of undefined
at UserCard (components/UserCard.tsx:12)
```
AI: leest UserCard.tsx, vindt waar `name` undefined kan zijn, suggereert fix. Exacte stappen + voorbeeldprompts: zie `Les03-Lesopdracht.pdf`.
### Pattern 2 — Onverwachte output
```
Mijn fetchData() returnt undefined ipv users. Hier is de code:
@lib/api.ts
Wat gaat er mis?
```
AI: leest code, identificeert (vaak) async/await issue of missende return.
### Pattern 3 — Tests falen
Run test → copy output → chat:
```
Test "should return user list" faalt:
Expected: Array(3)
Received: undefined
@__tests__/api.test.ts
@lib/api.ts
```
AI: traceert, fixt.
### Pattern 4 — Refactor
```
Refactor @app/api/users/route.ts naar gebruik van Supabase
in plaats van mock-data.
Gebruik @lib/supabase.ts voor de client.
```
Composer doet multi-file edit.
--- ---
## 11. Wanneer Cursor vs Console ## 11. Verder lezen
Cursor is geweldig maar niet altijd het antwoord. - **Cursor docs** — `cursor.com/docs` — features, sneltoetsen, rules-syntax
- **Cursor changelog** — `cursor.com/changelog` — wat is nieuw
### Cursor wel - **Next.js docs** — `nextjs.org/docs`
- **Tailwind docs** — `tailwindcss.com/docs`
- Code schrijven, refactoren, debuggen
- Nieuwe features
- Boilerplate
- Documentatie
### Cursor niet
- Git workflow (gebruik gewoon `git`)
- File-management buiten project (Finder/Explorer)
- Terminal-heavy taken (gewone shell)
- Browser debuggen (DevTools)
- Een quick lookup (ChatGPT/browser)
### Hybride
Veel pro devs: **Cursor voor IDE-werk, Claude.ai voor brainstormen, Terminal voor git**. Gebruik niet één tool voor alles.
--- ---
## Bronnen ## Samenvatting
- **Cursor docs:** https://docs.cursor.com Vandaag heb je geleerd:
- **Cursor Tab features:** https://docs.cursor.com/tab
- **Cursor Composer:** https://docs.cursor.com/composer - Cursor is een VS Code-fork met AI ingebouwd
- **.cursorrules examples:** https://github.com/PatrickJS/awesome-cursorrules - Student Plan = 500 requests/maand gratis (via cursor.com/students)
- **Cursor changelog:** https://www.cursor.com/changelog - Drie sneltoetsen: Tab (gratis), Cmd+K (inline edit), Cmd+L (chat)
- **Cursor forum (community):** https://forum.cursor.com - Context via `@`-mentions: file, folder, code, docs, web, git
- `.cursorrules` per project voor consistente AI-output
- Eerste hands-on met Hero / tweak / extra feature
Volgende les: TypeScript Fundamentals. Veel later (Les 14): Cursor 3 deep dive met Editor / Agents Window en deployment.

View File

@@ -2,7 +2,7 @@
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource) %<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj 1 0 obj
<< <<
/F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 6 0 R /F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R
>> >>
endobj endobj
2 0 obj 2 0 obj
@@ -17,7 +17,12 @@ endobj
endobj endobj
4 0 obj 4 0 obj
<< <<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources << /BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -25,19 +30,14 @@ endobj
/Type /Page /Type /Page
>> >>
endobj endobj
5 0 obj
<<
/BaseFont /Symbol /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj 6 0 obj
<< <<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font /BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>> >>
endobj endobj
7 0 obj 7 0 obj
<< <<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources << /Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -47,7 +47,7 @@ endobj
endobj endobj
8 0 obj 8 0 obj
<< <<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources << /Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -57,7 +57,7 @@ endobj
endobj endobj
9 0 obj 9 0 obj
<< <<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources << /Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -67,7 +67,7 @@ endobj
endobj endobj
10 0 obj 10 0 obj
<< <<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources << /Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -77,7 +77,7 @@ endobj
endobj endobj
11 0 obj 11 0 obj
<< <<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources << /Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -87,7 +87,7 @@ endobj
endobj endobj
12 0 obj 12 0 obj
<< <<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources << /Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -97,95 +97,78 @@ endobj
endobj endobj
13 0 obj 13 0 obj
<< <<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources << /PageMode /UseNone /Pages 15 0 R /Type /Catalog
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
14 0 obj 14 0 obj
<< <<
/PageMode /UseNone /Pages 16 0 R /Type /Catalog /Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260615075148+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260615075148+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 3 Lesstof) /Trapped /False
>> >>
endobj endobj
15 0 obj 15 0 obj
<< <<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607080655+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607080655+00'00') /Producer (ReportLab PDF Library - \(opensource\)) /Count 7 /Kids [ 5 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages
/Subject (\(unspecified\)) /Title (Lesstof) /Trapped /False
>> >>
endobj endobj
16 0 obj 16 0 obj
<< <<
/Count 8 /Kids [ 4 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R ] /Type /Pages /Filter [ /ASCII85Decode /FlateDecode ] /Length 1689
>> >>
stream
Gb!;d>?fiA&:N_Cb[\5R\<6/<%W<&-@EVa+-(DX<,4(.2/6cH6%]-ph7nRT\OotjbBU<ZE[PBXO1iqe]%/TU*p32`%]G\WQG79iHj!c\>nR6eLDoW+_*jFbK^ib6k4(9mc"",)\IJOr>G20kRq?(]LYj[QVE(6Gp!hC<m:"Hl^@%<>e(Kb?U\CNt/$qp*JdWL=1Dp(aI?buLU`d\i_?g+o>5O1D&*t4,.N=1?3h_'.kJB,&MQ%h61'N+[FCk3D*\6cL[b\!)CA(J6U)`Nehr720r-9OGu53u>&q].XWm2_Ig-nC88"Qq15O^VS4Ea`IVF'_BOZ,i.+-9dtr:>pgtY`*0d8bf43(sVK<QmaO!"Gu5g)eQ#'`D2s?Qli@GRrk`7O9t(>%NGDa`q[!XMb&c#Jp2>YEs%9Ba(3gC32,eYhu,i#C)9!VpJ2JqR.D;,r,+5P*8_kUY.8J[GMSn+nCQ`5;&5]IE](`A:m`6*3i:)%E/fWW!uM0a!.8]BJA/_1n@mo-^";*a0\5,7B7?>V<=G4138>FKa.*0?+^9)t=,*%>R,Ff")!3-pN'u/M?r#<u</J0a2kqX),@"2jCjP3LjS@Ao66M4?#5lcu?FVXd+_CKG6q&C&NDr(V8ZaSq\Y=b)_Jb]\?$>1FPD(WJ\WAaL76=<VpLNdVTPf/52qSZcgI-j9Bj5LPVaMsC67B4P`i&r=,h*bJbQVp@b\NRm0>9`JO]U2Yl!lk_'KZp^#[T+qGJ<e)rY're$O!3mBjBdK.tptpft$I.q%^Psj"13$j7;ZljXW$eJ5[q_.p\(?g4A)g?OhMElNq.lita_=AHH-rXLp<%H<3Y]5W'd,_ZJ#=foZC2"e!\b)Y'48HpFf[Oie#r'<?6Vkuq6G8fIQ>l3;NKBTS%4R@bItnhl1O[/i"9.0W`/llq*!AAP1+i*T$:2(9]A,\3X$S9#/.ktQaaha$^=[S:Jn*BB]-#$Y)\2tGZ+cW(S$JZl]f;8Qdg4Y.d>J(=Fb$dX7*?'[:0r8CrAZk@5UFD@Yk7$KQqG")fBXe8l5ChAVISH9*R85\PE.8dc1MTLD_!d984&k,lW@&@oM2r"i!#%`RYE,oCS6X%qm[eanl,]2\Q67Ru+$Phr#jBQk"n6FL8@]iT(@U9BsBKoqf5Qrrp)O*i7C'N[HWeP16jIqE.R)t/$&Te@_-TuLAR3FAR:aE&_N3/lXN($0lqMoU]4CV7A@'q94U',rC]=gjb>KNQjM6@\R8T/3Q3)WX18e@h;]2!&BECXeq4@qe0DDqk/8nTuUpSjg"C8S+,bp/"'`[kdMa<QqIb=4"%m@cXl`;naYJi5f]*D9^WBTPl5m&EX"?)TkU@cS&)Uc"-=>HpRqe7qs6&LFc]OeaC?lCIt"0]&1RpRfJFdbo>c='ZZMfTd0#@71B)eN7@+YF*$#6:N.HYlXN19pjg0?Hua0Us88DLr),OC7":7q/O"olnaoI/iUR(P5AKVT(*#.TuENl[g%,XW:'96SE)0iXVBCOj2WnoZ(FOk7/JG9N=q*9\#[90^6ls47UNp)C%l0:dlaeV9&K?D0':k[A"n8*S1MX'-VM3WYO76hZC(*C<@T<U$452@:kdDRL]m,Kpq'2WpR5//FGP1q_i;Y,s7t`?2V29gTHUafSEi50kAM0R@:8R(PiFUP=Z_as`HdHW(G?d+#?n*4:&~>endstream
endobj endobj
17 0 obj 17 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1919 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2024
>> >>
stream stream
GauHL?$E&%&:N_CbW1*nDBfo'OMnl?g[%FO]'\oDDYBdg"2V91N55nShY,QFKJ6dqFpRClcERo.0JdopB/C5)#=#TlNog=+NSqc[$C&A?!n=P>(qk\`,O:8!1X:[:KC):O%2d`KX8a<7=t07%joGi<84V<1Gi2/.9,U9.pmr$r5d"McgnJ0amKi'./ZI&Bhg=7g3FcaVZ[L,Oh=k^bUinDqH0lW`C&UsI9$eJUUXf$RL\elCd2^G\qf$ri5A*np7!De"^`R-jU:V%U(_cE(&mhW(9!X/nT>B_47aJJnN`(p2KYYR8deBaib%tOOHjlom)?T:Spk077B],t8Mn@ISJa[(/O+u9GptjfsKPt>b5_9MtboEm!3jIhqAaM:/aD)bEF`#1nWID;sj0gIO!0&m/"!c>5CITMK1S%XUZ"Cpg[5k@oaE#Xe"M\aJqF?Sjj/b<M(Y,B\1)I2O,_fN#6t.uJLRkuSdM9'GSI+&5N\=W'/?#B`A'KhLhE3XIRh;RKk-W.B)7rr3gHMHs[_P"pq)#n2fHc^\(uE#PM`/(?%3u^iF/)*.b\&D1k(5i7*Cg5Ol@Nf->I/RDpf0JM<BY;^C(9N[;/ID??7b[lb;VFM5%\SqX"dji@g-ejELGpL-3rfN\s-TCF9S,/p:8WXI'K2VH4pPUb,IL8:&WeYmslq5.gX/mDc[AnQ%kra=Y$J/i7W64Hb&6[f;em3Up9[E9t,7Tfm12'a5L:"AXF/:boe1"6-efh=;HtphcKcjrstC>4m.S(5"hb&7H,Sm%t[(IF:rhQY+r`Jl70s:7Cd'sduM\-XM0bmPf,E8+_rUd\W+E(*r,Uo**cPO04BlF9OMl]CCOMh:l(BFU[ZG/#S!ppQCW8BW8,DO%!FT9[D564/jg[U4,_I6=,2j)n9nE;ZqBrgpNMsD=2WkroLk'"-C!0b2q3J\i!J6G*:7FJilN-?<c&'tN4DXNZK:kS;b`ZhT'Y+r?)DT4ESRJfq/H%(qk0"A;g0o9;btQ)5<joL`!)NCs4>NKGEf2HP/IN\WhGnp#E<5++\r0$o`3W!oe_2#Wa%17HVc1F8d.$KKksVN@:#s5"(n@U,(pd>U*W>TE?%2-$%B>oIQe>CCB^aU1!`gjP)0OhAZcB"gEg>uoG#uI3Y'e($"0qI?uI1GM`[a,c.U'`r;HM^B,[a>+rhiTI%20Ha%h63mt6W07!G%GEG@P=)WWOUUefubMC>"C=D,[ZSu+F95$3TkVMf9)a9q"r$[@@EcN`m8!R+,7cP$rVBam*e$MlU@9(]n]Zs&I+>I0#9Yh>&310uEV0G@I+=]MU-.Do@Vg3M,l*sI._=s\NG+'P[n,e?i<*/058Pbk5Q>(Xn8j*S9D'2u+]C#fQ!AOn+7.[d&/3)'D]H:21TrNa\fRkM%ig2J4I=&HNEWacU%Ng+.nQGh/VS>OA)Vb^ALGgEs,:<=d'ehC/Op9dn1io#R!)+Omq,Wq[cVnq&aq\n7:V5\?&lD+%[gG%]1QDb.nDT&RNHE##dd-Iaq++Se/@)L>a-@C9HV.Pb>#^gD\9l8Nt-m.pQ.@2(@>:-`>7ufkU/)?s+g/fcSf+sPeHTE]'G\Bt)V1m#>4ZgdV)6C[hY-6'OL7qf"#f0-pCHN6-&>u1a).\!aai@""%3s0TBgmqj3K;L/R-KGN7ZrMN]npnY6UbGS&p2rIC\9^OVW8`@Y$H<s%b3`IF7eTKle9^.<YcL3RYBJo4\Tq#ihELimW06-ft:Y!+!*W[ai^t44S_\#?E\`L<o-Xr%Q`k&qSmC1<R/"pb<0"3OV/UJ`)p(38Jimm6,Fn`jnajIK-EdNXAqH2AC"tnAH$r"_lnP.hN)0"l2"@dW?_*)'s',iWbVH->U&TG;#a%]K7Ns49Ze3q6=NiqW3Ujl:F<ZJ_UHm.5\1,b?c\/TM#~>endstream Gatm<=]='G&:XAWk[GX",VWn$2cX(ojFAPA9f@of]_H'"a7e.H*$$BJYMUC8FPm8SJ.39[HFglZl1qn0$3n4Yq&)R#Dhi:T8V&W6:4QNdK&J*P0H>H^:bCociqU`qi0oUL[UJ,UHq^K<?EelQVZYF?)`+EH65'cmQR4h+i:n8;@?J_.'^"Bi%-K<lXfQujGAGlTZt79s\sAc%>g!=\HA:Z?+F[f\<pLS@odK>mhh+j>5q6:5)O7>3QLUM_,/gr2cTtX`Y'Q5RHM(Iu'D/$S@sZ<=_XJFSpmBp1:5g3@<^un0-q2jHAk.S2LKH/YY=1*iQD:;FKuiiO/E/c5DdUU;L\R1s2_4o&73mcsU1>glD#5F*EJ[+iZUMn&:4jfqL*DsUgf\0P.r%!lpq+63BSAP`U;ohWBcB2fBt6=1'^6h1ECC6_nY>(RfWG]_dXtF*g.!/X/\"Ei:lOMC#H*mU`JGs3j+No7(lNWoA:mr:kf9_-pVa,/Hh$s/f\7U6Z,h+L?%]NtVM8NlklL)lK%8`b<O(+Fr1,&eH6+l)Ss\hH-3`Dg@:q4jG;UL$gt2H;[>,g<KlBtRhq)_`lPL>$;QAr!X+9T*NI^"YW.&nd0s/!^jSE`q^`P6FpIQn9Dk6s76422+k;%ZiP,,;pWLQL6L9-OS1"f.T/NDMQJpUPOpcB)'K3YR48GB,(QK*1JYER3LVmbnS5^MiK2EUDO@nV$da"iH:D:`YVD5sY$Zp6\l$cc<g@^SFsJ[8!Jb8^aURO'CKZ9?[KPjQD>e`oTYA:(h#4jD5IaV87oOiVOWCM.GSa.L-%?@t4s\.&0m'q'MWCH@3tE@e/eG[Rj'M*j:1#e3b_7^!j>%"",PR!St5[<0d#]p[gs\:.eW8Wf<FXs=5ePi"9U=r,>d`L:=%ZWr:l@mNVD3N+6enY=CEM!rn9^-=1shN]N"`=f^q/<nmO#a,8bM\gXO.D?.$%o,WY0\`X6Kl/:<=r)N@#.lIrCLl.2.cPpJZrkrPJT:Ol'J\fZks/>]pLqK]BC3q,-ZC=$>fP5$e)jpmIWI8jFgqqJqDNNT9#Mq8iPnGP=EuS6_.#e17?n2Fl3Zl>c\i4D<htB@77WoU38-Xk9@!:2F-c?T,('u70LKA@Wk1EG4/AiIIL[O,?a<',/++,[WmeY8hZ:33L@gdQ,_9^FpV:S@#'!lfC3F_7VJS0lqicT[UqN](@l4k8Fc6=*bM"K8RI(Tn;[/F^KDH'9;U)U9TZ3l1c;'&GGi+9i@(C1*;)S*bJ`Rba@=,%h],Y9^L>0E%eM=t6\``h-@EbrK<OEfQ9p9!_e&nrqWe;8nboKd6IL-7D\=V;tCp_?DelI='MV%]4Zrm"teslBZ?n`O9%$%3VW!q[&c;[!t)7IaK1(`1qc"b&i%;ioXj?=%e5)'f54"5qqY`L&-Pl'S)5mc5=eZt$A\t2sFg'kdRNFT]g<g,2'W&DQJbDNVZQHN)/>RcQ.Aj?42K9O>-R3loJ`bJHIMT(YS[/?<4=Cs@ckF7FNaaM_F)]@7;R.jc%B'r\@I1Xs^:T[&*R8cpk4M?e[Xfn!e9,U_1&0a4A)(h%/=(/_SJTj(sX4j/'0LU@ZA@O"\>eii>GB+2gFID-aSZc&gD2/PcE.,e;AulN#E!.^+/013jWFgrdb5!lLR@KoDpZKca69(W&B?!U:-mS#g4$f="fG#4:Z#sIMXZ%c2/#hAq,euW'9$g)BV0C0s+qc7?j.CO_]1U*M?quV>;hn0aM3W=uP<4l=T,"1MV9q46/ujCI!A_hO'q/>aC;pEX5L+>ok6WfY*(NcL2[d[OSrNT*0]1bnh'hDn/b%Q\L<&t!+[b=t1M"cRK-6!uEa#:9'g'Qs%[74lW4-\(]Ki2d-5.8]c"/;2P4Akr2I2i5PG!a$BHj9&l55F$$5Wa079<':'U$;,L_Zn!NtKSikgeauC;G)ubeHt[G5`BNON]Wg0'"6Nq29%HEINrf3R5h$%>XXDKZ_(UJO8A)+r$keO%)S,jGUD&f[Ub\23T`h+$W55)u~>endstream
endobj endobj
18 0 obj 18 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1629 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1752
>> >>
stream stream
Gb!;dgJSt\&:N^l\m.fAG/U[BHPJ1_Y`0fG!\Pq_KpRqO&qa4@l^:l]rqc9Tl3?fg8QPWg.X7?:Sj(u.:Nq_J:VX"-gReJ9FQ)_R!TVNI!Xl[Q]'nTpGOOR`a((]o&U6V,#AjrW4",ToN:mrRYnslpRmH<e?CHNYVT_]^Fpabr9+mP@5o@>iA)JZ<T;3WT.?!1B??nd)@a=6'd#6nnrUg&fIqe.RgP_udOdl$!N6r;T$>OH9#X+eK8HdiUm/MKRFUi&1KXQB*FU%k*KDc_of:44-Hk()a%PsZNGmW;2ke8N"=>Cf7.7&cRa%"s][8%6J,F;jbPlSO/hucDe`N=dpS47ruOf)^(3j'&o3Uj'hn9G4Mmo*']qNY]6ZK/8$@Hlh2.A,:pZFYP1P2qa`b>&8cM\o+?dTo\!)N;#C`_T!P4>OKE"#q?qEYbhP1Z?]2<%pSY&5=Ki<?3e$+0au*AYV>?q&;ZW$LtlE`XX?Zi$([6I,9Hbj_g%&A\SC:%?qP335.M10g=5)00VMg-K\6m:m!<6`+([]VuN7VLR!p7jl:,n>pK>/04Q&Aa3,V(G"J.Y[;)p(ZS9"U8(^&T^"rj2EFa`dG07;uXSnTKR@Hg'K,/Pt&,LU8BBZ-l/a*Kkqp<(ic!gN6jgee:/rc_Nr5/EMM%FFK'F0b3BSjF&LNuJ[(9H$Y%oonS7cC/I;NZ!L*kXip+n2&J712L<OK0gKY$!tpY1_'Qj@X2T')?5bP"/@2M\9U>!0%4@M=2t2\LX;td#b\4.Lk/W(a8*n#`]-)Ofd4P^q%K_48+7[N_5?ss$5qs+':b*nN<^LbN:ZKmQh(`Oi)*&#d$IW7-Y1A[n[cqC(5@h^?j*GM;l(?CGT^#'%aX\4h>mEbeDL!.aqi,G="U10NK-Re>9%3XPG9,Tr<g(bed?b?L%WpOhNLYG8?7KF\9B<NQ$:q:7#kUTOGdT/gQ$+#:T5B6e^OsNAB(I,?7JiAR!33OBL8Yhar#moUZZ/E$qmb)R.*G@(i_Q#BusX,Bj8Dc6p.\DT4W>kaYTsqtl;?RX^.V<.63&K/LI8mD/]MBtACFPeIo8L"TSk3G*s5)/@VOZO9ak)Q:(HFcj9<iharDQ,OUa'`ff+WTcYa[*J%$7c'"$")2Q@#sU4M2g2AInTQ<,IhA;.(e\UVCWLf5q:hrcB?FDF[e&G!+MjiJ8Y_oL5ing7B_<=%,'3JAbE>AkfM;"3ptJM3Mn?DpLZ?7:6fOaK9!&6F,Kcp,d)3.b9Zl9V]=NEO[9uqS),5>]*NdK@)g_#en7oi*'?]M4diX&eIs9R/L^9BhBFe2+]JKJr=)K#i)M>ol9)\%Sh1uZ&:t7+5C&FNpfgP^.,dpdIX$U!&E.lolN(Oh1gA\:_6j5@nQ0[L\CCG?UV`Pm`']_a@@c(%@5dcUjF$JJE(>X<gB1O6"3c`C&/LSW3T^5EbL7@q5nk15feJTrd7.O-"Bcm,dMRIfpSmcrPFEc,+U_6WN;rlnRmB/Aln5e+[)K]YpS^R7?=9W:7P%ISG91.RKg+j_n+8FR?2CPI/\<E/b0o>85&;;VC2LeH,nA8h!]\')mh]ND#F-&kofu5";Q-Kd`Q8'A@3+h`)Cghu[;ol4j>+Dhk5E;aoF8~>endstream Gau`S968iG&AII3lqB')14ff$m7ZfC8LCF7do^@*d=hcJR5.48+Cs.goXB$9nRY9K--,%LQ>(sJporChG=fWd%k[eO'u7"O(k88F),03Y/RlQg&jB%qIh-3ABfj9'R8:Zh%H?&qKmNOH-m#G.TX%r>(>,4m#F;o#+j=AtAHOYM_t4fXhopRB,IBpp(fk=6<Ak4Y6V?4NF:$F*\bU1=Bj=aE0rXKn[ASFo>9aSR_TH5nJDin0*@6UP$4l/memm#^Aio)fqY3XY:dH+N&qO@>OmRr8J)9a7P%U<dn:82CN7edtB$ZD*mr<H]\#FRj&VcC'%>9m/Z+Ch*S9`'8C.C/PK9g]klami=cr.Yq3&>E?IhT>U;F,^mHZQXYrS%g>L2!OV4JnP'9*B!!?+2Uh-7(RH*j'j5kCIn,&,m.7ROn1>1)p48Fa7#!8>SA[d5oOt`o@3PQ5Pg"0b,]\[+&`,T&PLpH;UYhSJ[VB&TE/59N8/_6?.3Q1gVmRc\f[uLuM-nK^`D*3Ai(neE'h&\6L&D/)<mpcXl*O[Ck[&X<.JXd9*\2@'IS_:tYd*2@]cl;2.B61PWtZ'd\qKPum,nU?8[8C:<<5U.4?icb%fs(g$r>Y<_qe>cW!h/!A1S1iAjSDGQ23QNTLR_KV?VH%$Fj6hPc_+g[!lH\FgR%I-"hRd9[cd<dXQ^`q"BFWXWdbB8[)q-LBC%kQ&J'q6MmUK\ms(ma-h=1h.[&*c$9'h52b1d`uL/$#NL[8ZuFp$ta<TZ$N9@`bQkN8,2h<;.;Q7Y@F7BlH!1o"iRYrYD8nIk$VKi"n]!dBR4RlR[GC1Wl6aMWZ`WcRF3Af74h,Wa$fQ#u!^V+RXp2&J'I17"PqLoT)[fI_-42k-;FGs6/)F*Lbs:LN[^(Hs!EXgNntF%CZ"-(N)S@bB9%`!>Xr5O+Sts)6BUmZ(0,b*oS5L`Zcd/ekPKLVr_.$n?I-ZcGK^l<sp!TBq1W'?G8)$h3UjoUJUb%b5MgKP"S[GUo/4$7N9?COaY*!q-:qu0l/dSML0IFJ*pGdnI=6MD$F/RU""'r9gQrSKc1*E8l!G'VC#lf@Pl)``n)^sPi9?Dj15C_GmOG12rY_UI_jNg:#7dod1Vb>8t1F&l`cpD4hOS=Hf-m2BJIYB]hslgk:th)aK]EHN'rf",@1p8J(hqa,[6NU-e%uh-!.(k\m%E_+CG^tHg0U!P,nD[aeTM(:C1\V7j6o;LLc4%c-8O8m9D.nbNcQqUZq$/IAb]$HZ3%LG--DOHt8R,]r*'"g$T:"3a;og]?RJH.L6fF;is+cP0EZ9fo$u`(3_N_<.\9\-?G;E#cU_sJ_;\X/`GM7d=24jD&,%,&#kq8'rCbT+3[XCCX<<i'[3qA30&:qBg1P<0;Z-uV;Za6a:;E><6R!p5>Ei+U`WG_h2eKKj>%FE9Kuo8j>+jJ`gQ=O:,%F9%8lGnTGLM*dgrDl>jEeuoYY>`@?H*\)[<>N&\cm3JQ7(+@AL-]8bLF8WDr.O<6>rpZRVgs-#lc-pl1LY8a1c=/^5)G$<M8ML$Ab>.eEclj]6V>.VZnTGq:m[=NiP"fBQP:!Pe#S@NLrcV-K=B3#5U*nBH0!]oM*V1R#9SpLTU^V'1Yi+G9:UgHI_ik4;X?I#RN^WD%8#c,TJhV0>2G(k:;`/PC@"q24&U:+`'X9^EP=odgcS()I?(F5cEUJCq**gVKm=KDB?mPO:@_C,8c[l)p_Bi[[YJnC,jT%[>fheR7kg:_XFG~>endstream
endobj endobj
19 0 obj 19 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1971 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1872
>> >>
stream stream
Gb!;dgN)%,&:N/3m!'jU2mEqr]P"+j3mOR>S2@3FkJ])"[A#'HKZ]5'B);%'J<WIEU1HEX['aGlZkgI1KAD/cE\Q^rok-)dGWCH?D`D\:__H+g=YMsnf>GEH""##H=uJ(`1JD-Uo1el[Gs'0`G*q:tT7^ddb]>A?4O-(8pH[(bfB"XG#ihfb%a9&(f<^!QgibgC`d!\-6I=-rZQ7*D*oGoI'Ki!J%6B)D^M,I/i/L\$=\XelgidNOcJe91YErnkZ])/F-;p`1H^SA#.H*l)(J`96'jbB"(RT[__^Q"dWWcRi>)l**3,WrgF"E2HNpB8##'#e0@(%EW6k%q&jd68DCr7IZpCU*ZKX"tY=BK?*eZ5tFSR!Sd=%&O^I;K2!@dgsqpOIOCFmH\d-Y]=b*GM8$aVN86I:m87hdB8]3UY`7mLYr+pXdt4':G1a<UMb(L!,-DOB1M9K.OpcAfKHG"d+GpO3lp5NaWLe!S8/p*<oLmYJ/nHXg,U)BdmV6>Z<FHCRJQ2AMP977b>BjR-gKa!Sc/Bca#CL&71E\*\Q%-RK9>XdBe%DTJ`V8LgpR`WBi/0^_A$1#F#6dPnu2??H==as7AUQ\CN0*Jpl9PQA"XQ&%9LM1(Rj<TI;Nh0fstXNY&4d9Ta#T--`W[O`NjLq=>7Th$;23,<OY4XL<L`V.F[0:=>TAieVVa*(6XIMq3,!'Yi#`FMPEL$'Q&hOC4M\3^(msq!II!fs"_Xr-X]@FI0iJWQh?$i](DD35VOa3EM*EgOSpCAY::Tl??<28J%+NqOje9=!&*2YODodME-h!""F$Z\.18Rea`-7!QuV,Bd7'qo.$/:`]E\#'92B`N7;5,_R46!D/cP-G0i<.H4h<JNeeUe`B],%>h-+18^]eMVUJ`J-:9XD)jtL3WJ%0sTpOF$GR-_n_D'&l,=97T86HGSGn@na0[#Yo$pdoHV9DF<5%"[_>%l,u57V*soYUOK[!;sVXFji-+3CV&5Kg[CD03k[20ms.Skp!Pfs.FHI$^j^g3j0/<dW52l.nB@2WH7>^&)[C#%p#.YINolJi#Ko3FtQnXaB0IBh*CrYRZs5?R&-(2c?V1XtliB59ASmCK*S;hp0Wo>5*(0,`Qf(R>t;/l0=L26)aaRaE?LCi2d95O)qkXnSdXc"8+:Jio<Uq0'^+ZE]Jn$jtk^u3Z,1+bFL9Zb-NCQ8'A]s3q!+@0U_@T6W7(tb<pK'q>+dZZs'M;BrtZ5qia']ZXAePjAqW5f+MRA()S@X"D<sbl@:Ku7m:ht;qq+k"K^1c0*+\%_P$*9?p`roPp-T-@GGBB`Q^N`d4!,rimoCR2e;]thLEOa\)RVrGr-]E91mt.5Gl9kp%"B,MVW0i87d?FKe:jAW+rQ!N%39A1?_d&s#U7q8.cYgFN3O7bu=u`ko*=-9dq'W9((*/RG=j__KR%qNHI=&4BD\PQr%L]_,fmNLI(mh.WBau;60'CB:4tuhm88fZqt(m"pW`g3%8J/2eYSfhG`e,5='pk"Z8&sEi)D[+@L@JWH2=&-0?qZa`J<`H5"moXBVSa<1:Z^(jF7(\mB>kD=FrU^!35##rguE]=X=%g_VOVO&=c7[Z=VP7C>s/ZRXEch(jVLlDYItqI<?u+rMqqZoh6IV%u?oFrG3I0<4-6ck#Qm:3!2Ll;n/]-$:@S@QNdre9q^/et%WX]84,H5*Ks9M-efXn-g4U=(9g.HI64"`W"#8)spp[&n7A-"WN-k9T2T"dU]$m;:6lXZsBTtZjgcW/_=WqD5s*!o1lXb'[DGHF$o#cXZN$KH##2B^"9TkPE*Z2-#I8DX#*0P2,cmZS%^d!KDO'D3$Xg+kGOCNSmXIfi=#5dh_QfU,Je6;Ve_i(c#YtQrNM?#lK<>7XpA_pq8:_[i)1;!R--5[8W\o[B4E7I#BuMNYDtF1@9q*#P#57#;'eFJ_j;X;PY1J++G_4HPg=E@P0nI$_;HEY\8bX~>endstream Gb!#\968iG&AII3lm*Cd[3uT6HR=NtM:4;Ll-rbgM_>bN8X)es+X(E[?f)%EP"CRTeP7DnUqd0'mI%Fo=9jFr^%`'MJ=dW3^X5?FYZQHoC*kLG^k]nilU7p%D5u81p$ftBM`[Ffd<)+0[LV]T-&+o5>>P%4"B2'2NdHj]9ZPOcf5o]OG[e6P9l,SY="f8DNL7ZKNjuJj0PSO#6h?+Fl,?QnR'#VamGO`3i_CfL,,e!5"jq&8co&jd4-)tF(hr^O-DUeYj,XC[4hoCA]aZ<Xam&qEWSV<Y_V>??Kscd"<n>:Lg>&:^;4ZE+;(lZG%P/6["sG8>QHpDkElhq00$?[Ao@*=@/@Mdm(g*QIW[2nY5do\aZ983#L19<C`]V0p]r.0%4Z\0TKoM>]eU/Er^lLDuQWFA7Uk@V"?Ih*3@Ehm7NM0ZtHnA'f2rle1k4oHT8er79"M^dl1Cb"PlbW].HhZu(OjYTP+)of(?+(Z]N/g2*YA2]A:%;O%'JBnQnX_RYLg'+/An.0!-2Tr[iq42mH:>\_(PU8*_+MWhb6E@Dk94:n*^Y<P-pIfe5iAH^<$`\g=)\hlXC_">isqK<-A%s)gltp54o$713<g*AI]At$T]KSh0c[#ts)ZA_T.:#re1iQ%N09_+Uc1R*-9tgiN0_P$%GilHZ\ZFeLfKg`(/)JScM/MWbH>fOQ8&^Oa(_.$Z@.IsBB]+#Z':&3qa<9eZ<\COs6*W\1>S/[ioj;b"fH)%0`%0b2RoR@nQg"%rZnMuBGg#[[J8",#;Tce_?P-:aAhAi_OiPGFsKX&<hn-EM!G4WPo5)102KB`"Q'@`9-q7J"TD/R=WikaAhk"s"OLo[a39mtJ-BaSLF](a,lHou#rn46-QYdJ!=L-/M#hW4ZOp6=-DL[rf,Zq,h#SS#Jb.t+S4[h^b?=+O`o81:Yu=]n#'$Slq%`q<=@ZV_)AjCBED6Q$Lk?,5%buN'BGPurae,ps`Et@T'lhip!DF/*\Z5<i.DhmV&br7m'U].!'_*Ze<-2QHZ^B_*>mNfC>&D]OW68?E554++3Hd+i[[<eN*3_geq`KOY_pUR!4htN)ngE&IQY6d&DS9Xb0rLNQMh_O2hLPsc-1H^^nDfnpLg$NTVUfAU9o(>?OeN<.lmUJ\:oQpR=??;roM=7RM%g?$Np=RtjS'?j!B*MG1#HoLND[mCkGU7>F\"8PJN0ZCf3Lk1X2GDSCkc<%<`N4',H4>rKV^sRh3DaZ=/D<r6EQ<V+ko00qq%8nRH@nl"MoUqU_[4,?TsqO+V+chEMhO1a&q0%LTGBb2#:QCa0<i1RsZZh+ir*aQUm/Hb;OgNbR:C'1M)V?fV?4PPf69;CPSVrgeY+ls*0#r(r,W2IfF#=$H,)3?kunR%*b`qU8K_Bf,YNmr>h-^/s7W@JdqRZM4dudQEtMWp.)KMpj//pWC*6XKcWRkPB`a/r9AC%U75HJ*#I5%dbi>onWE@S2QIq!5&:RiBLK<LX1Qog*-,+[9Or=k9X00kR,b3L]?17F'"EEI#Ql$.ZY2s$BNjj]*5Hl-!;@h8j%eW:/,Ll$)'cMo?fo'u4Pe-4dZ5KS^m-:BV:_Q`[UlIa69"Ke\sXdb1LI$6!fIqr&b0+<44Vke_8WH?n3*'Z79H_8)[`j^+H'.nlJ`+]kuj7Y7OUo:7aJ*K8g..;70al422kQd#UA\<UcRmtA\*?MT+]f=SjR.;K[(rFmObDdS\SGX%iF=fJ@BL@[V1am#R=R@S\"hf&$Yf7aBtrb&K:^&=JEre;qtb\*p#)6Y5A9*VOh7pL51(;&+0V`<ZL>1CqE5IlSYgJlK!>pH?%:\3s'Pf3PDt-mS.'F$LBPjo`Y2o1SK+ZOT*.-`F8<rUEh@q~>endstream
endobj endobj
20 0 obj 20 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1727 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1894
>> >>
stream stream
Gb!;d>B?8n'RnB330TcUUmDW(69sGVm^AV@[_$Bjnrae,+$1b'(1a?1YMURU0m;mlp:?f/@N,]NF2*+C%bUFBR*R_oGSS'J)[@>ed0(G,EJWJfn'TOt7huc^k8m`C@(RQa.s1G+<7@T^4CO).Kan6oDZr<0*]R13KE3bfkPL8M'uK%bOTUn&Cd6ic"car3n_h&q$-%cY[lbhNp%,;5JJKoG6n9:7ft<+Xm;Q7*3iXgACrc-1Lb^scnFu#Jr)$.mNc0e!KeKICpdW(W@m6Di^i)3QZm`2*$ILFncSU7q;(_oJ627KKhdKfk@aYh`7o#7Q!q-`r8/>H,&?HViGD-cV2CH\siuS^M\>JO.lPk?+>%*>sYA1't:6CJj`>"nriH4dJ[i8+LBB/0)[5So":L:3Fe!I!hPR)P1U*h0276nslW`d:Ji\W*l]BE2ug_Ys2D+JA,mdOJpkkj3ObG>I6E^>hOp/W6fPV7m?X.rGI@ld8L[&%HGZq"q,.Ai"nO6q*Xo4].(A%7)jB942u_0l/G(Ful1JFp697c>34[>Ac7Q*ufb+lb"'RNStsfpZ#Ig/mH2Rk"lYOossOZ";7*)a)rO@XNWFr"a@T&1^#i+o57!kI=,V"\Gl$R`H=S?51Z50/[Ui:dH]iHi]VV,^f\nOlGm5O+1F+Hl%^Xn&OFJdSeXA3D#oZLl3uk>6A`aNX'uU:/ag:Y?SLm_j@k<LAR7[F'M;g/^`qhIAVY1fS!Z+GR`FPBAZ>5Npgm$dWI+eE<<&YCF9pGc7u0:^/&X]-^:u:-q_!m4\*8.-r[e5b*isnrd">lH0qEbCPjX0lD0N!@U@=ERP#hdQ'D*SXb?@W3rVM^p[]N)<0e.";Q,g`^#@Z1q=ag@H`7BE2G7%j3_:`SE1B`K\)"r-1B@$b>$l!9_!<0p$Ru2[ZJY6TbN6UiibmVg=LV_KcU?qPK+D'/R'8X!i!dm[L8`^n%XE'mFF^I$h%O@HP@g;QoT`B/f@quU[!ocVZDnWPhDh(!<3lo`Ge`YJ?.6t(k6^"_>im/OL6%P?U`NoRm]W#IIoTSk+X=pI4FjQS#+:Bf17*"'"+(Uq,F<O:/1$Rm!Ed>N78K'gM.O3Sa"fb^`A/K=;Q&t%bKsgOAa$2.3+?Yq57G_K3BA]YM][I\FLZ,g1RA8?%9,?OULH\Yh#LtcTS[ZB1-83M9H2&AHJc)%`fq$4`A^VoDlIVf3Wj8W%-]Glb?GrdZ_VAO-P:p$q]n+ujIIG<93G,L`B%sY*C9rb).jau,H=^)i$6ha<[uOXc)Z]W!E%#GJM6icJ5LuHL^*$YY:SIHkFDdSSHfpFedj)B=dE^?UT4=i+S1T1C6dSOO#2$/&B3<&`VFt?l[[cl\B0J*os]I<KBVM/d#f>Ha"I&MZ\[&tI?Sf7X*P=Oq#F.F]i$j?$E>Ap:-?1@0OO0R62jPq6,kXFMC'>nT[X2U:adZ0HuAAiH(#cb^]fZ%#K$^t-SHhQp8_rC^uY/3_:_!EdK[_AKIrNmq@S8R92&W`E$Adl<58'5okHCU,[\YLj$_o:XN%3D&%STm9&AqjB0!gm=Rnj^AV>Y5%8\gK@CP!9adrqk+^8>VLS4)A8JT2b6(pc"dtIp$bp]:Wk#]cP-0:@`?!14I>c&8VJ"#Iqk%1E>lqp^`O7s_/@9fJj^tT<u!L:iJ>V"`Kqpaj,k4t#<\Tqr^/VrJdYlr<Zj+KnnGloBl<(X>91iW(/<6n^;~>endstream GauHLgMYb*&:O:S%/T%^[45)K!"60']l2:e;e,]fLXOVlAh6TT7)a#WIYX(;q52fd#*"i!VTH@gc5[moJOpAQrsr025>U7_D[[J<Bai3DCm_ql?`0[&,M(.^\`!`T@,P1jiB*o2lf_i26h=O+7q@9qG0.UYCiWoq@oHSOIo7K:0thr@dp&2GBGVcTdCXDRer=J8/N7;?ET<f(Zn]]6_Xt[R2NbQ*ZQ1_Nhm<HB0sL;rR=R,'0LXYP9]gRpkNU>uA#'WW$RHYi;H-;fXnQ/B=0(Q8qfZqbC(*%kHOqf_RcGY]9HYSL'GMB:<&keg:Z+A@#5el<go"TM:Te6*1V/6"cAG5;Lfg/-hsR6o=]+UoWe@u8.j3&6L#3U"N:/(@:bp'J67=Qf&oV)?J<'RZkZDJjdsHDN9A$E3l1Z08.AoLg3Fjim+dNZ"_k/4sqi#pte+1&LMNW-VUhWUc(FaS499DOdj1*dd;IP_rqEU(eP+m"eMGMFb?(NG$i=0&P/_(s(=?nDN4E1%7cY^_&/s1$IGJi7TO*/oaO]!hIQO!Fc8X84=(D8UGCR>p+6<I6EOqSiW("Q9DLKT:o!q.D;LCrf=27[/KY+n?I3l,glDSl3Rk&stklem$jXCo=KgZWiZ^mDN;3Z9suWFO[eJNT*ir_0"4AKhu9^c*o4IEtdbj[l=a#RW9i;5l+j<dB9(Fl:df-_KMjoAE:73g]WcB85j[5"l6h![59#U\_6`A)#&d(kL`D`8CI<RoS6+BfU.)@+aKemFk'o3an?sc*FZ[.gNel*4CaA"m0c0(?;B]Df*tS'Q1NP@,!t+o<V8t5Qt#>C2:r*>R;J@ab/<q>MYC-EL%KBr-$50]1/Z+;O%l)\?R$Ns&(7d$tUu0#r=,t8>G4RaKb!:NjBuTo$Cr?7rl0c/<5$p(G:ddA0pdi#<lqiCNd7IX#/=3qa%=QK`-("P*I?\@(d'C.2XD+*/MiCa(q26=]f!Z+M'7E:Z]tVal6EWP/+CPSTZ;m1Xu1fWhn-9,F7r$^]Mt*@BgIHojf)Z)J:E;[ke\CLHr0n-&NmO%,#L@@kQ:<Zctn,#7F\7XO-.X/D8!He'27lG<7RImrOD3KmUON0eb`T,uU5a^Q/d.efAb3qPM\Z'DOD3Lfm.DY"/FRF>?t,hbA@fh;M]beJUruBUD/67jd&#\5PIER)oDr1@1SO-IoIX,0hmP6fS3K7^hJ;R<E)^aS&?AYTN/gE"18S^$QD]1FF/b@hu8-Cu19l`\[]S<\5NBNDk-tlh"+2@YX(CooRk/0t"-F\Adp31]&TS-%2<^bAag**DiH[ZBAqsLV+<<!GC3TSpQ478P3ZR,,l66ONItU]LV2<"7EKdPG5^DVAZS"VEn5A2+CS:_Pe4I$=jKh,/i>4k/_H2^sR.(K6>/(TUYG#'o-^(C0rJ8:Em94ZIO9]FN&7CgBlh9o$K2VZtPtq[<Pp_@W#9p1*rIYW9@USTn:(V3t=P/b>@?n.2'h%>*I;U_5Sa$)N">9lBMOd4%%;5EsFeX@($"sgnJt_LYl?AA3MQf3e>3$2H5aIg3_t'g]DA\k7%W00uIGDf?kX:NB5;t>5TjSc(nD#H9YLV+V0;&`=(DFA\DH#@$/9_1k_#amrQ1[m;#.[$!5T"P@^3ImnL0B(QmK&aKZ!e$4Bc*<%hda^lnPuUnd%j"oPa($ek1:V.k@*a!_WBCr9gN*9@,Zco8Z[H2j:i)9>]cUL\ZJ99d]1$sKa_&.#jdQEV^9+u&Gj.["kYnIQ90#?Ka%f->jneb43Nh]*$'9*6l2C&jJtDU<(>OkuCMYZdAH*^num!qBe@hUbJEE*)A.jTfI\?HtFK[bk-L9"E900$'.>iV*/G6X0iWOM<e=T7L/&d@GKE$UqaXE:\e2Ilk@b$N~>endstream
endobj endobj
21 0 obj 21 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1831 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2145
>> >>
stream stream
Gb!SlD/[lo&H:Nn1$TPV>dsF\?E]Jdl<St,'6$3'O\Y`33#cL/gDfKR$iT`WlaJ3T>c.b!dksjofX!B[bg+D<[_]hI?%,6l^n2i2IVj^3@5J)@,qb/5_Q2)#f)n>Woj&)a__!7/5Q]"(5oq+Yi%`(M)VDhWT5`ZkQeR2bJ-3c5^2Nn1$^a(=Y^uSno!o-]gE,DAc?b7"9$t"=4N<!?oeepNi!"eakR_A!O31;9Y+9=83\I[RchCh3_VkCM(N85:`M/1N8gWt0k9?@1(fT_;+)g)6\.XX%MYno^(,1&H%'1n#)+PL1^T$k--jm(gQ')Yd/@,o#2$G'VJrKt(CKNcsKR9#COiQ3l2XntN>.Y2m\KR:/R*b[oO(!"=apXJ!.:<ibNZs`@';;8Fi'n%_V<R$lZsG`%%Q,d?`$'Tqmn8eZPu@Fb])&58VX$+!YlTC`OiJZ.Mp"[/k)c=o1TA]%'0&ubR4&Ln?>mIr'T#L^Oi)DTMb1XGQ_j8`89!`;IQYY6N'sO/9VjA]:WZ(RG".8kmTP:"?)??MpZAV_i0r;`cF[X+EtcO/\O-<H?m=nnh<#,eekKB1mE3dmLeO5djjC0mi:%Y%GJSBoX`TkU4rE%gh[.`9M.bSu?F/0/`_PXhVXb=25aA@g.`Og%;A='rEi]PWa=oX6>V:n/o2sPoYOf$(F]W!M*q$3%^oH$^kfW!@MSrDNNV^BLh_0*?;;Su9U(6X(f5bl;,JcTfesOF*>)cNFa`Jf<.[i!;?fiEeJFKdR?F+npAhX!2gS=[r>M8IGM3^Ygbf"gANsAZ$?=a`J#.uHACr$(U!e9">A(t0637\FQ>F?@-="9%q2AP>@3D*6b)P8_e@5aB*9]Q9<cn(-_b'RUebF?9L,;?dd@C:b[=@hf<@0ZD/cZ!4:`C*'bF`O98)2Mg\D1;NIi7Z]A/+]L(-jadJ84j5?X9_`8VIA8=i]Th_bNVf<:g_'JP<Xhfe2K_[H\fJ>Gt&ktJtYdEA6ie/MG,9%R_DXdS2l/LFS^AEQS\U<6UTIqXk]TkgZnA&*B:_!HA<#<X]6>g@\;"o(3*UA);fWGi#/4#fY:Bf]/M$uV\LaXi8c:TJqttdW2<^n1+g3`A'@[#cGO1b%7A<SQl\4h:[X0AR6`[MMF)K9MC_sH\;.i.a=a=)BtHk`*OZQZ+3NRdEqfHO[iKTLA,C+=B%Gq;p=shES'i;0bc?k7I$Y6JbZPG3f_TJd\Sf/o*!6`'*3`bgr_BYCoX[-?gsA^_pSjr%FIQ%,D(YqF0+24XY0-Rrk=r::N!;Qur?]t*,ou7al62TcncI9N,-^14a`#Y!i#[-7;lVj'NMj6LE4,%O.3^d9ij(W:o`8n\KZiKI15lCa`OFZ=&.T.a36DZ75!ZOJGtEDO=`FhHd;R=Y$;Wt!3:=^o<QG*&;`,:LG^I>,bC:-t"]l>7?oenKWQa@r&(Q,p(a+_bj^+sc6YD=S(-N=@a7I^1gmj+\0MOWq[W!-8n$2VL7>E]%c`b:$Le/,C=qV"4&7HX)HN:-]L;Oo5C4dp&VNJiQ?&5"3e';JoosBNia\UHn$;?1RlFsLpC1g?jf1Ri^R4Lj5%utD@n9[2n8Y>snb$TP93H^3H.T_@IXK=Eki\,[4E!UGY*LmAakH7`IRc?@Ni7$?[n_guqpuuXj1HuNiFq[?7=bQT7m:6X66X,*`SV(QI@+m5S/6La,\=ti$E)Jhl]V!;Gcee)oVLM4p"q<kD@@e4Rl;GO-8EtA"i"/E^;K*((Efo=S,71^O-;6l-)\e#6cI>IIT+ZC(gjDK<>_j9gS8WX)Bcj8k*reVqVTcTI'\MOcE:>NN66#I~>endstream Gau`TD,]1Y&H;*)YsTm;6L^[1]r,5+;FoLgSYgYcl&7k%/>%ne0<QQfkcaGrVcOOj+e;1Z[]Is7mbYZ?]5(.W?JKot.FAY>3]ZBA"\k<$*Tk`04e5;:IuehAl$CP]7EERY4VkPnK7.u\RE*9]e8hc%#P$LHJF&rc_Ls:aa!G=&Q5p<ATrn>5-G7e93i([_>+S3]_^Y?YQSc7IacRA.&[,Msc'cb#^^%0DrnkP^D_n$(.\e7B91>cCimf^>Q?8>R"\ij#lZYfk-*'b6G%qSMYeJsKHWeZUVEWa]8l\<FU0A@FnVX&'X44&)VM@YHWKc!KX/a*QSs0IMe!H+9pa_s1MiR[i.TWR$B,@kJM4E%"Q'ITaBC\7CPO"&`k(A3;Not0I%hlII,C$dK]Zq4B<`df9L5]^qg(\+)'UqLUZ^l:(`S8t0ph-#X%$iU`aVGT.8;`9eYNSGfFJWYVjB!h2N9sB";DaH<80umM=h:aI81IJE^n.3t[:L^lgZAEWGK;a5n"=hS133rQMu@**?SLA[6S4UDMh`@Or`VAi6F2eA%MLJG6V9k/n$=,BK75Bi3J""cp.J/rCEegBNo^dQJsI`%F_dMZbdIW"Yj0eejuQ[@_jp>&-L*GO<kt`M8Of6G5qmP"ousLD<Wnd"qD26eY0HtF2#Z$>F?*MG_3XrJ!k_7$nUST1#2ofI$<\den0kZ[,7KO_%_n]G/.Z"Y^d]0ZFr^\c2X[I56KU6n+:S-%]iFZ5'495LDQPOc,"=h`<=pHddsRi1([,U<ff!bN9k9dL`^g16qIOf9`7aFC<XK^bH?#VrmQZBb1=,ZHL?4+=DH$WV:"OZf1-k.&%OniNS##n@%Q:7B;1bfH*KWr-/a-6XK!Kb9Y9'$?gW[-\BEe>;!<q+k<<)q#b[T6>YHisZ*bQc1Lr/c^L=Q)HMlJb_A2;ND0gh0r<6%-KBWnOjYYSkTisHhW^E`DcS\u,_$\1^?QtBkM:qkMAoHCXUqOr"SS1^B4.uLu>R4@^KSq&"o9C+Hm:a+r#7F4[ekoZ0A2;Xd2r-H,"e.UmFWW%AUZc*KhCp;l3pfaio!4M54Yj;Bifc$[?4dP7'[F&\"S0)jJUVh//;0CG@iei1R6c?.M[K\KJc_HI+eBbqm\-%e%!l$\k6J0;eesk"Shs)U>/!X('rDg!s[carTC[E#<L?t8*i0d&udPQ&,Hh0)5\TIVcVRdRYSNifagMke1g]*LWB?MT;q#m,@q/QlAXQVE@bDJ(59\ldT/)]p5NU?sMZTdk&^h?^a&sXIo`ec=EH(a+F@r:YjlL!;W1>-NTDH[[un.[P<C'EIZS1!%aW)3QpodD%$]2I^L=6GQ3$#W)l\[K6:E_S6]e#U/N)`>]BXuCKIW",EJ1\&,L42+5;MbDK;oQ3th>:e#J_SsK(X!+E+/YcrHQh9GQAYU%KV=OUg=D3BhVF-nc/eXG'4Z$m0"80^E.23qbT0,QZ]t>3a[\CPqh*CB*I^U'r:#.F>ibG'X=-t&C8"Od7qQ^=^?KRsT4b@0bMji#&I#O^I@r+DR+boR#`f.>t5;B*V1)m-3D9=O.iF8MW+n9NkdR>u32bc3=^7rC!I!AH1!^dd4<>F.]%qdZ.Q$;>ZofBXQ*RehijR[CMIE>%G:>&)eYWWm5)YQq[\qj:aZb;=H2*1?go.\u\k\)X<RW4#RbfTS;nHS2NmpLljS7aYn_FMXFl#^-<,boVpb<M[9F8sE1r#^h6:lo)I7:&V4H^!b5r3fL7,aZ\:=`e0"m^h1b\A/&V%3>6&0^rM.3q0cXV+H()&.&_M0CXLtpTDaf-eQTYX,iC1S.kX<DI?*ok*@H_'9nW74$W/L*$Peqb!3A4lN*\2HnubLKGPM@=K])Y^ADRTmT.Q6HWIjNX+ou3.e7T2dN[_O(a\gAb0@Tp-KoRBApg02CHt'ehEii:F++OeQ.T*V2eqZfOG?T^#W$nb_kakh2`,!)h@8.,4c,,u>:$I[lgW`'@6H'k1;^>be.K!GepFc>=IPkkG%ha2&po[[2:^BXWfZVo@Wf;&XZ$3Xf5t?&S]YD=dKuHGlXWtck699HSQ$*GgS8Q:)j9b<h:GdWj3MsWTd.B9BI!8)+#k,#:ri]K,Q7Ub`*BljZRbG7!-\`T1dFU,IfVEZ&Y/~>endstream
endobj endobj
22 0 obj 22 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1936 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1164
>> >>
stream stream
Gau`T>BA7Q'RnB33)aFE^_0Y1jkBknP]E='=eMHaI;p7GF^nWY=_WGXrU5(Yhi_</Z7p+D8K(_mp[aqOAO,e)gj<3+4J0LL.gCsMF9,&H!Rl8^E]X&47!QL7i</TolGX`6m+EK-fr+s:+@44]"q_;9'FGB`,>f.F_!tYHZ4OBr`T%O4&dC1O"H\H85nr*6mu`-A<%WnJ"S)0VXaQ<h0.&u#*Lb[ZU+kB=>,Q\GSfj9nTb#Tb`p`piI1'sm-L:;/W<,7&/^nbCB:O`roG1cS93J3Ye;n!`!Ro(i_1kbu7-HrKD@:k?le8:+,NM4Tj])AuL4G=%_+m>4>I/oX@$"KLOWP0)=WCgoqTgM42EW4C-fI7la&%da(cJ)Tf8>]HW'**kp$iOu'f=]NM7Pk7B\4&_O9N@;U:Rb"`&r.PCkRSFh:,ZUYFqiQ9/4;uU1iRA*JDg+=$!(.0a3$jD/IT0h@+n2U[sVK,Pu6(IJ?E"m-9k!W3(l8KNZQY#9\C,G[][]odj7J%[0nAR&nR.TiPqRM5fH:Oj=O'odZ@U[9^;U!]kn(7m`8X-h6V1*5cJ`f]bP!1'8sUOO\`To#cJ[&tC96>&'EP#3HO*Q"=u"aX_>2>+2k%%,#JmpM'<5Q8?5"QdGO0ek_H#o,6Y\NR&Z.]sS^\`'t,-oCh>gm@NdbL(-L)_:H-6=4lB4J[n.7ED1nYCL&Hmf7g;q*a39#+]C0g<Ft5-ZXrR,#5PM@%,0.P>_\VEAo!)tX.8\Q=(a5DeF-?s&9Rfhn.a#50B_W#ROn"r!iDg#:+M#)b9\.?o2%t\)jo_'+H*)'gWGd)n6R#(MAhL]^O/7/+ls\TF<4TK5a8m9K0r*^iiJ"+p!;q9(Oa5UlO_Yi=GBGoUb([*[<g?3oc*l0(r\p/G`:pFc*`B&N"-]DIG/N&/a-7e7eL,7N9XL.+H-fAIPF<3N^>^BB$,b/W]gl/nSt+tb7CU$G%1Qe&Wca7OGQjZBIk'5&)bAOl#O7<(1E"QkO-ColhZM_(H&DsN`I\*A9%7%Z.VbU"6.@`BI-?eY2i0So9Fm\kWC_!Wk$n0+qUn:3tY%j>ijKU0#aE'50'1ISa)CK)pcMWT#:#9KFiG&BTEEbRu+g7Y`)'t%YHsLa/Z_8Q-&-ZJ\kM[BLk5*:8GTloKPeR;&,\+EAT50F^^lW!R:j>M]>e/&6T?%;Qc0N6`&pj[RU^K[%"h^_Nu$0?"9Ge<KUAR$"R*#n]?Ui]fh\%*nL=Pe^sM.WliamD.N;K&M]K93_?XQDFrL6ShCYs)G-j,aC'aqRNrUX`[WG/.`74A8L]R-S%ZeP8klIgq?c_q3_c'nZk(m)PThmq,)UU]NcV>*)W.CB9%U;m%=C2sDB#gJ`UJ*C'`J+jZ]ee+%R25i-apX'2@0^kc)K2Km"Oa(,fE-c)>qd2Y!%o.@U>Pl`7VKj13Dls&8b:32S2QRF^hS#D7sb-nbjn//#T@)D#UWm7<[uNiYL%u@J$<NnE",aS[UZb+;`n3c@Ik.7PY2%d*Rb,hC$`mR4^(nq^/![3&gWK_&l7SFM.*hcQBrfdW8poRjo)8j.0b^q'VLi^Z`eWpqRKc[du&Zm*7,GMFC?L,ujWs)P$Uf/^VfihPHOj[TfT@Xgl-F0n3+VFcX-@g4u!gQdY;L"Q"?#dD=DYFIfZj][KE4+['3FgX((50o0%LTpb$RCKMmo[P4$!:ut4W)5Y*Yn[E=QTsZ@cE#HQk=@**C^!//T1%a'X5NqqrkMN3q4=n=MEQ1u!.OW;$&54m:[.nHRnBR5*7%Q;Cj2BTeU`9=46?iHn[o7V_:lDd>h7jc/YR/\J+hskjjBbB=?WKUeg,dT$+Z6uOp;t@e)O6/@3;C8ST#MW52uMl.%Cn"%OL''f=VNh@937Y\_'JTA"l@Q*P7NMdJQ:Y=(J&(;[DFG^)ogJ\J^M6Ck_-n~>endstream Gb!;chf%L6&:WfG)"#4uhEm#38>+C&\(>K)VlNQo2<qmE@doJ6:$gAQ9"TTO9u;rh9'D"n\2uqu*8<:,bkV(T$s\+bl?es#r#.liM?3;kRK;_9I>G&b128tZfP5$V3PicU0aRXYLCG,rFfh.H'>/9?iLr\%Z]-]n2P]Z_bq!jQC!Q6L+!!`G$g1]63.km>;uG11G+;[m6@].[X%2Q<:_$3"QI/JeK+G:V)]C-3;&UcMP)9dN:"FLA:,NU]<!'\2n_G3+n,)2[T3;A-H(Lda:[k<ISfjmSf.+tG3J[&0^;4uP`q?t9X`C-2o\MX>QlY.40FF<m%)(B5"G+1QnBc3g^nY.dit#FF6m+sJq'%)fE<dfXhN\=^=F/t,&SNu_^`@0@)H-KnZIZ]TYd-G0DHh=ca%KVi0['=IWOj>@d*1JJ`o6$N98CP=$u;F+R5868pPl<Ob^94BJ$COdTr)E\EA7+!n4h2pFm;g%p?EI=OP<*sl%"n`>(tP#m!p]d1gT](/Usch1^&;Q]!/;R*>c0K4dRn#.W-6<G?-?+KuAL8YZMm$PinVj;JTJG,P;:*=_DUj<1'HrF5nYh^Jn;Er"Geh$@KdUFHK,A!52YiR2Y\nODVUR;ob\dcuW=Qf/)[,%lP+#.?iWZ%-8_C2NdP,,X3DkhR:''e(u@Tf1ER6;K*\p4\fCF5UQen_U%-&m_8"(=`(t1UD"Epkd[:e8;MKl8[Rr(iBdk;cBS;4)-Zh5)c1!_U5FXaE/;C1l%$M#C'=1[(':=d5>s2nB,J.C6[U</=OYGQPm50q>Dg>.rKqQVU04g-BuXcj,4KNlFMeB3dF<V^C$@WGHpVe=\Ar5&\B)Udhe=?uBd+-c,^)=u9uEbcp0\I2>GAa=9>+0MOu>Rnj'CC:AX?]7`(`-eP$6I?P%"Z..]&`?6_1]ZIjM4?@"FM:foH)N,hCih(M?n?<Hn+*A9-sVWJR8-e5fj<iKklGQD0""&RADh]JpaYPDa7pZZ1hDImTf_8slcE(&*,FXe5oBJc$Jc;UoHpmRht6:&WH;&]lLkEM$jB1e&pUXr12ld;t>W;k;tbXpPfJZ93DIb.@qVOE+!>)OEB4>!>,3LD@As+:rT*aWjj%0'QWiE">.Q-*OGU[/Zkd5qRV/5[^1`kApIURhrI2#$77Y8c~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1809
>>
stream
Gb!#\?$"^Z'Rf.Gh!%IG-P_pu96OXMh6c!]VVA;NgD=HWMClW[f[L\4fjDKq[ZO:Q"Hr._)_tfSFMDqCnWbPJpnIG.]EJ"X%&;Dg."h%(-mp"Sn)2`@n81FX@$1a[r6Y$["sid,+-a)t%Y5`H@M+=@P9cA4GXtCC7NjHj-RG$SC7%1*:`rY\1?:\e)Mh5fYhgaZ0m(Z3+@L<oI2cO,_i'=aUVedTIl)m9BUb6ml>tt45R&oK_XRMZ^Ycnt@]$2N?5+9)0r9on"t#:#GjuPUlAa)HPqJ]Ep`.:LXb33B/j`5taBbS0FB+`<g)0XaAneD5p'c>c$>qF$eqPK!6,LFt=S]PDd/<>Zi;pD/l<WqK?*RNC:Pue71(CfGM\9c+UY#M5S-]Qkqob8TYUEPOf(W:6jq,k(JQ8Q_$_8C.,^F6q,4t.NRjOgMN<GukI5YcE-l=(C#RbT6K&qMUfc\@qAHRsYE+(]d?m^]M4kUn`[r^P[)rj,=]HElTWU6&qPR<7:\&Mu@WJb.G'&*toZU'EV_qr^GB?][h`<3:O-GqJhf'RdM9m7Hck(B*Tp=0.PoU8-Hmt=3J(AL%_6mQ6+$"0-eV+_?An`<BXZ2cn>':%a75oQ^*<i*dE\V/#6>3a.Y^g6,[Su0%nY"/HAqY&AV#=',RWH<E[aoj'/\6,nk>j%t&D+BG^^)p93H*WJ<U:k<?n0@iMoQ=j6OTgImhB#%]Fk&ojr>rd%bnC0q8i`:1G59MII0JQHXp2+,3W,)4k=8@NB/<g;;[c@Kn)OJUUFpYV&DF)De^#0ocK[f;MsCfnIT4>N1'cCob(kA>b6s#5V4h"\e_9@#-fBdr)ZBg!PEAjg0BeVGIel59*;Mq9S`Cu6,F3`FM^+i5nN(/dkNd]@(J#0Xhkr=rr^6b&<1A.3;Ah>0:7`U*bdu"m%r(9C")C;A7GpmtHCS#tBS_R7+5b@3*Y@;Rd!E9KP4'(K1KMg?50qZ`O]nSPNn[;&-0-a/pkhZt;tu(b/CWmnI<5u:F/ZbCSZeg#/^k<-$#33r:#h\=PMo!1"kHjWQ[(\k(Fh:EeS;4gb[GIU`o=HhE<)VV`;?8-'L)C'T?!<d"]L]Nr.$aP^OO?hYB)D+@X#QBa2R>L'q9P$`^\p;#/QXjTFgYn?_\f;![\#sf;Qc2.$VS_<"u^'>Qer_C6\VT1s1f&SM@>>otVIl,/SUBZDKplTQjG\Mfk>mS>bNTZEuBJ&=Y.*H\?'&o3QiPl.G4\(g`H'\ss3-SU:D(3]?o;A"@N(l3#e'^:kVbB>`o1Vj7Y>l"QoHGeI/iqbhXOMI)A0^j!<&Qcb^oeY74tHOk1J=Q/)>n+!<3'lV%UZ_"2L']o<:(q./MoR;@W6V&E##^Due_JZ6PO/S%X'VB!#Mnk<S3kI&*c>LSdN-Utt#H%)FJ!rNPkA%E1+<B"7`Mc0*f*GL"D(e$#BB;0[^]p:`^XaoXVCI!Rc%^HC`3Z(&CY$;,L_nAMlu0tkmOA-K_lO/G[;jnG2?d]+S"qUS)aE#r6=r7*_XS\FCcp0K4\Q3"@(FQVqJ;VS@s=(W6:f=tH&dI$YFRqTE"d,;aKcJ\E/^okKWle*8dTmaA"Gj8T<-3CbI"7AI8I.4<Z:Iu;!qA_`ZMXm1OYt',b;d"Jh9d'DKlgdcP)]#bM6@\rZqg!%Y;ufk<Ff+)]hIlUYkJW<)X"9na"^iD^jqVB(!LfG'sY?]A"r]U$hPO<6?/4oW`,'1o*9AGJl9MVi'YKHG7dO]Id@,Yh+^qZ\RA+HVr#V!>E+IId^4K^<h,i;V9.Ul3B8?l5'm."oGsZ6N~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1122
>>
stream
Gb!;c>B?8n'Roe[30-h4&Vu>VH/%K4lu]i'/DDPe#V9J#5ctZlq=cCW%Sd("4?FUfit.9JF8=+*oGn*fWIJmYN&#oZ!>ma4YQ_iMY_*o$^;+kFq`."ZYrp&1A5gtN/O7$U^HffF**e)ODtt6Kkoo=Pi)#@\odO"scL\7FP/HS&$j6'VktqZs`@dkFKtWJ%Aa.POEXD9.gNWJ@3^"RI/j#V9qps'b%buWhH*nZ'@b5ZEaC,#?YO?])=i@t/ej?2%O-2V/.p,o>#CkOF'n>/@fQ)6A_%-f8e,]bK*C;UdipAi+_M^a;<U8_;`C\@*%NKlF'mu.0V[H1(e'XH4b@\*9,%rqi)\/qi"22B#WnEh7E&,Me"K0Pm\?U@P%BK((p:S'QLIp2:\JS7-L^!0m`&Z[ur^e@aQeR`R4rQcj3?jK9?sJPeb]p:X8m^NPp+-ua3b!5=%u<Za7J$HG3'*tZJ2S9MQEh&7nq=&@OLpNnIIj\'nsn_pdDpRtY]]Q%l@:FQh^/[[+ZHH$<3Vb14HsE_JXBM*jNrQ;DOf!m*[dubWcp@.S5"+"99*)(Vc`EQ`>a6"c81cmR+Enr@@8O8_cDX:4OX)&2rmUaNU's+<(JVhn_3V[Gb8HY$ZEglVKF^Y?ZYr1#`<IUg7%e/j;a%7:X_mkrs+gRU+3[+,nJ0YDW'dL'Bl]#9LCmah7V<@I[3R4JQ.G"\^eJJf1b#W#=d?TIKeW-T"=dIJ#0'8+%)GIa+r)A:l>g8mWks@rC3p'F.UrQ;cEQi>kIU:1XN3!K@#[kX6]A7bKWPe9NepeLaDFT-s;`!CPC9Yr?LsTTj<"6h[nRdRQ)9&o7cSkqDSXZ?[!E(QDV;[Dl<[`aEEADfAP0g%Q/GLhVO<(a%UQ:ZZ_*Ic[#Ne.;'<,&G-uAc9L=!l!o]K3U=)eko+%\U>k/m_6?sT>?i"7]o&Or-]1;M+V^h3f;B/b/;+aNWg*GKa:6,*'-$4lTt_;p<bPK\47]>V33aA$:A>hR=_^)$rek@BFGbOLHL!>&nD?#JomHT,DhD'&&/d\S\2pm!_-oJ=X]Z_2ImrDfH;mnPf+Yj:8R3>H6ba8>bk/;/j3#M,;3-XAnN&LrV\HDL>/9K3G#IMW~>endstream
endobj endobj
xref xref
0 25 0 23
0000000000 65535 f 0000000000 65535 f
0000000061 00000 n 0000000061 00000 n
0000000122 00000 n 0000000122 00000 n
0000000229 00000 n 0000000229 00000 n
0000000341 00000 n 0000000341 00000 n
0000000546 00000 n 0000000446 00000 n
0000000623 00000 n 0000000651 00000 n
0000000728 00000 n 0000000728 00000 n
0000000933 00000 n 0000000933 00000 n
0000001138 00000 n 0000001138 00000 n
@@ -193,27 +176,25 @@ xref
0000001549 00000 n 0000001549 00000 n
0000001755 00000 n 0000001755 00000 n
0000001961 00000 n 0000001961 00000 n
0000002167 00000 n 0000002031 00000 n
0000002237 00000 n 0000002322 00000 n
0000002522 00000 n 0000002421 00000 n
0000002628 00000 n 0000004202 00000 n
0000004639 00000 n 0000006318 00000 n
0000006360 00000 n 0000008162 00000 n
0000008423 00000 n 0000010126 00000 n
0000010242 00000 n 0000012112 00000 n
0000012165 00000 n 0000014349 00000 n
0000014193 00000 n
0000016094 00000 n
trailer trailer
<< <<
/ID /ID
[<3aca6d13cd74b57d5c4397c35a55e608><3aca6d13cd74b57d5c4397c35a55e608>] [<1f4d23edf2b4a956040416fb7e0799ac><1f4d23edf2b4a956040416fb7e0799ac>]
% ReportLab generated PDF document -- digest (opensource) % ReportLab generated PDF document -- digest (opensource)
/Info 15 0 R /Info 14 0 R
/Root 14 0 R /Root 13 0 R
/Size 25 /Size 23
>> >>
startxref startxref
17308 15605
%%EOF %%EOF

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,6 +0,0 @@
node_modules
.next
out
.DS_Store
*.tsbuildinfo
next-env.d.ts

View File

@@ -1,103 +0,0 @@
export default function AboutPage() {
return (
<div style={{
maxWidth: '800px',
margin: '0 auto',
padding: '40px 20px'
}}>
<h1 style={{
fontSize: '36px',
fontWeight: 'bold',
marginBottom: '24px',
color: '#1a1a1a'
}}>Over Ons</h1>
<p style={{
fontSize: '18px',
lineHeight: '1.8',
color: '#444',
marginBottom: '20px'
}}>
Wij zijn een team van gepassioneerde developers die geloven in de
kracht van AI-assisted development. Onze missie is om het bouwen
van websites sneller en toegankelijker te maken voor iedereen.
</p>
<div style={{
display: 'flex',
gap: '20px',
marginTop: '40px',
flexWrap: 'wrap'
}}>
<div style={{
flex: '1',
minWidth: '250px',
padding: '24px',
backgroundColor: '#f8f9fa',
borderRadius: '12px',
border: '1px solid #e0e0e0'
}}>
<h3 style={{
fontSize: '20px',
fontWeight: '600',
marginBottom: '12px',
color: '#333'
}}>Onze Visie</h3>
<p style={{
color: '#666',
lineHeight: '1.6'
}}>
AI maakt development niet makkelijker het maakt het SNELLER.
Je moet nog steeds begrijpen wat je bouwt.
</p>
</div>
<div style={{
flex: '1',
minWidth: '250px',
padding: '24px',
backgroundColor: '#f8f9fa',
borderRadius: '12px',
border: '1px solid #e0e0e0'
}}>
<h3 style={{
fontSize: '20px',
fontWeight: '600',
marginBottom: '12px',
color: '#333'
}}>Ons Team</h3>
<p style={{
color: '#666',
lineHeight: '1.6'
}}>
Vier developers, twee designers, en een AI die nooit slaapt.
Samen bouwen we de toekomst.
</p>
</div>
<div style={{
flex: '1',
minWidth: '250px',
padding: '24px',
backgroundColor: '#f8f9fa',
borderRadius: '12px',
border: '1px solid #e0e0e0'
}}>
<h3 style={{
fontSize: '20px',
fontWeight: '600',
marginBottom: '12px',
color: '#333'
}}>Contact</h3>
<p style={{
color: '#666',
lineHeight: '1.6'
}}>
Vragen? Neem contact op via ons contactformulier of stuur
een mail naar info@debugchallenge.nl.
</p>
</div>
</div>
</div>
);
}

View File

@@ -1,124 +0,0 @@
"use client";
import { useState } from "react";
export default function ContactPage() {
const [naam, setNaam] = useState("");
const [email, setEmail] = useState("");
const [bericht, setBericht] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
alert(`Bedankt ${naam}! Je bericht is verzonden.`);
};
return (
<div style={{
maxWidth: '600px',
margin: '0 auto',
padding: '40px 20px'
}}>
<h1 style={{
fontSize: '36px',
fontWeight: 'bold',
marginBottom: '8px',
color: '#1a1a1a'
}}>Contact</h1>
<p style={{
color: '#666',
marginBottom: '32px',
fontSize: '16px'
}}>Heb je een vraag? Vul het formulier in!</p>
<form onSubmit={handleSubmit} style={{
display: 'flex',
flexDirection: 'column',
gap: '16px'
}}>
<div>
<label style={{
display: 'block',
marginBottom: '6px',
fontWeight: '500',
color: '#333'
}}>Naam</label>
<input
type="text"
value={naam}
onChange={(e) => setNaam(e.target.value)}
required
style={{
width: '100%',
padding: '10px 14px',
border: '1px solid #ccc',
borderRadius: '8px',
fontSize: '16px',
outline: 'none'
}}
/>
</div>
<div>
<label style={{
display: 'block',
marginBottom: '6px',
fontWeight: '500',
color: '#333'
}}>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
style={{
width: '100%',
padding: '10px 14px',
border: '1px solid #ccc',
borderRadius: '8px',
fontSize: '16px',
outline: 'none'
}}
/>
</div>
<div>
<label style={{
display: 'block',
marginBottom: '6px',
fontWeight: '500',
color: '#333'
}}>Bericht</label>
<textarea
value={bericht}
onChange={(e) => setBericht(e.target.value)}
required
rows={5}
style={{
width: '100%',
padding: '10px 14px',
border: '1px solid #ccc',
borderRadius: '8px',
fontSize: '16px',
outline: 'none',
resize: 'vertical'
}}
/>
</div>
<button type="submit" style={{
backgroundColor: '#3b82f6',
color: 'white',
padding: '12px 24px',
border: 'none',
borderRadius: '8px',
fontSize: '16px',
fontWeight: '600',
cursor: 'pointer'
}}>
Verstuur Bericht
</button>
</form>
</div>
);
}

View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,23 +0,0 @@
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "Debug Challenge - Les 3",
description: "Fix alle fouten in dit project!",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="nl">
<body className="min-h-screen bg-white text-gray-900">
<Navbar />
{children}
<Footer />
</body>
</html>
);
}

View File

@@ -1,12 +0,0 @@
import Hero from "@/componenst/Hero";
import FeatureCards from "@/components/FeatureCards";
export default function Home() {
return (
<main>
<Hero />
<FeatureCards />
<TestimonialSection />
</main>
);
}

View File

@@ -1,85 +0,0 @@
import { Zap, Shield, Rocket } from "lucide-react"
interface Feature {
icon: React.ReactNode;
titel: string;
beschrijving: string;
}
const features: Feature[] = [
{
icon: <Zap size={32} />,
titel: "Supersnel",
beschrijving: "Bouw componenten in seconden met AI-powered code generation."
},
{
icon: <Shield size={32} />,
titel: "Betrouwbaar",
beschrijving: "TypeScript en ESLint zorgen voor foutloze, veilige code."
},
{
icon: <Rocket size={32} />,
titel: "Deploy Direct",
beschrijving: "Push naar GitHub en je site is live op Vercel binnen minuten."
}
];
export default function FeatureCards() {
return (
<section style={{
padding: '60px 20px',
backgroundColor: '#f9fafb',
}}>
<div style={{
maxWidth: '1000px',
margin: '0 auto'
}}>
<h2 style={{
fontSize: '32px',
fontWeight: 'bold',
textAlign: 'center',
marginBottom: '40px',
color: '#1a1a1a'
}}>Waarom Dit Project?</h2>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '24px'
}}>
{features.map((feature, index) => (
<div key={index} style={{
backgroundColor: 'white',
padding: '32px',
borderRadius: '16px',
border: '1px solid #e5e7eb',
textAlign: 'center',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
}}>
<div style={{
display: 'inline-flex',
padding: '12px',
backgroundColor: '#ede9fe',
borderRadius: '12px',
marginBottom: '16px',
color: '#7c3aed'
}}>
{feature.icon}
</div>
<h3 style={{
fontSize: '20px',
fontWeight: '600',
marginBottom: '8px',
color: '#1a1a1a'
}}>{feature.titel}</h3>
<p style={{
color: '#6b7280',
lineHeight: '1.6'
}}>{feature.beschrijving}</p>
</div>
))}
</div>
</div>
</section>
);
}

View File

@@ -1,82 +0,0 @@
import Link from "next/link"
export default function Footer() {
return (
<footer style={{
backgroundColor: '#1f2937',
color: 'white',
padding: '40px 20px',
marginTop: '60px'
}}>
<div style={{
maxWidth: '1000px',
margin: '0 auto',
display: 'flex',
justifyContent: 'space-between',
flexWrap: 'wrap',
gap: '32px'
}}>
<div>
<h3 style={{
fontSize: '18px',
fontWeight: '600',
marginBottom: '12px'
}}>🚀 Debug Challenge</h3>
<p style={{
color: '#9ca3af',
maxWidth: '300px',
lineHeight: '1.6',
fontSize: '14px'
}}>
Een project van NOVI Hogeschool om te leren
debuggen met Cursor AI.
</p>
</div>
<div>
<h3 style={{
fontSize: '18px',
fontWeight: '600',
marginBottom: '12px'
}}>Links</h3>
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '8px'
}}>
<Link href="/" style={{ color: '#9ca3af', textDecoration: 'none', fontSize: '14px' }}>Home</Link>
<Link href="/about" style={{ color: '#9ca3af', textDecoration: 'none', fontSize: '14px' }}>Over Ons</Link>
<Link href="/contact" style={{ color: '#9ca3af', textDecoration: 'none', fontSize: '14px' }}>Contact</Link>
</div>
</div>
<div>
<h3 style={{
fontSize: '18px',
fontWeight: '600',
marginBottom: '12px'
}}>NOVI Hogeschool</h3>
<p style={{
color: '#9ca3af',
fontSize: '14px',
lineHeight: '1.6'
}}>
AI Development Cursus<br/>
Utrecht, Nederland
</p>
</div>
</div>
<div style={{
borderTop: '1px solid #374151',
marginTop: '32px',
paddingTop: '20px',
textAlign: 'center',
color: '#6b7280',
fontSize: '13px'
}}>
© 2025 Debug Challenge Les 3 Huiswerk
</div>
</footer>
)
}

View File

@@ -1,53 +0,0 @@
import { ArrowRight } from "lucide-react";
import Link from "next/link";
export default function Hero() {
return (
<section style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
padding: '80px 20px',
textAlign: 'center',
color: 'white',
minHeight: '500px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<div style={{
maxWidth: '700px'
}}>
<h1 style={{
fontSize: '48px',
fontWeight: 'bold',
marginBottom: '16px',
lineHeight: '1.2'
}}>
Bouw Sneller met AI
</h1>
<p style={{
fontSize: '20px',
marginBottom: '32px',
opacity: '0.9',
lineHeight: '1.6'
}}>
Ontdek hoe je met Cursor en Next.js in minuten een professionele
website bouwt. Van idee tot deployment in no-time.
</p>
<Link href="/about" style={{
display: 'inline-flex',
alignItems: 'center',
gap: '8px',
backgroundColor: 'white',
color: '#764ba2',
padding: '14px 28px',
borderRadius: '9999px',
fontWeight: '600',
fontSize: '16px',
textDecoration: 'none'
}}>
Meer Weten <ArrowRight size={18} />
</Link>
</div>
</section>
);
}

View File

@@ -1,59 +0,0 @@
"use client";
import Link from "next/link";
import { useState } from "react";
export default function Navbar() {
const [menuOpen, setMenuOpen] = useState(false);
return (
<nav style={{
backgroundColor: 'white',
borderBottom: '1px solid #e5e7eb',
padding: '0 20px',
position: 'sticky',
top: '0',
zIndex: '50'
}}>
<div style={{
maxWidth: '1200px',
margin: '0 auto',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
height: '64px'
}}>
<Link href="/" style={{
fontSize: '20px',
fontWeight: 'bold',
color: '#7c3aed',
textDecoration: 'none'
}}>
🚀 Debug Challenge
</Link>
<div style={{
display: 'flex',
gap: '24px',
alignItems: 'center'
}}>
<Link href="/" style={{
color: '#4b5563',
textDecoration: 'none',
fontWeight: '500'
}}>Home</Link>
<Link href="/about" style={{
color: '#4b5563',
textDecoration: 'none',
fontWeight: '500'
}}>Over Ons</Link>
<Link href="/contact" style={{
color: '#4b5563',
textDecoration: 'none',
fontWeight: '500'
}}>Contact</Link>
</div>
</div>
</nav>
);
}

View File

@@ -1,73 +0,0 @@
const testimonials = [
{
naam: "Lisa de Vries",
rol: "Frontend Developer",
tekst: "Cursor heeft mijn workflow compleet veranderd. Ik bouw nu in uren wat vroeger dagen kostte.",
},
{
naam: "Mark Jansen",
rol: "Student NOVI",
tekst: "Eindelijk een tool die écht begrijpt wat ik wil bouwen. De Composer feature is geweldig!",
},
{
naam: "Sophie Bakker",
rol: "Full-Stack Developer",
tekst: "Van OpenCode naar Cursor was de beste beslissing. Geen token limits meer, gewoon bouwen.",
},
];
export default function TestimonialSection() {
return (
<section style={{
padding: '60px 20px',
backgroundColor: 'white'
}}>
<div style={{
maxWidth: '1000px',
margin: '0 auto'
}}>
<h2 style={{
fontSize: '32px',
fontWeight: 'bold',
textAlign: 'center',
marginBottom: '40px',
color: '#1a1a1a'
}}>Wat Anderen Zeggen</h2>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '24px'
}}>
{testimonials.map((t, i) => (
<div key={i} style={{
padding: '28px',
backgroundColor: '#faf5ff',
borderRadius: '16px',
border: '1px solid #e9d5ff'
}}>
<p style={{
fontStyle: 'italic',
color: '#4b5563',
marginBottom: '16px',
lineHeight: '1.6',
fontSize: '15px'
}}>"{t.tekst}"</p>
<div>
<p style={{
fontWeight: '600',
color: '#1a1a1a',
fontSize: '15px'
}}>{t.naam}</p>
<p style={{
color: '#7c3aed',
fontSize: '13px'
}}>{t.rol}</p>
</div>
</div>
))}
</div>
</div>
</section>
)
}

View File

@@ -1,4 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +0,0 @@
{
"name": "les3-debug-challenge",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "14.2.5",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"typescript": "^5",
"postcss": "^8",
"autoprefixer": "^10",
"tailwindcss": "^3.4.1",
"eslint": "^8",
"eslint-config-next": "14.2.5"
}
}

View File

@@ -1,9 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
export default config;

View File

@@ -1,13 +0,0 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {},
},
plugins: [],
};
export default config;

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": { "@/*": ["./*"] }
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

Binary file not shown.

View File

@@ -1 +0,0 @@
,sharp-zealous-planck,claude,21.05.2026 07:44,file:///sessions/sharp-zealous-planck/.config/libreoffice/4;

View File

@@ -0,0 +1,427 @@
# Les 14 — Docenttekst (Autocue)
**Online les via Teams — letterlijk voorleesbaar. Slide-nummers tussen `[SLIDE N]`.**
> **Setup voor de les:**
> - Monitor 1 (privé): dit autocue-document fullscreen
> - Monitor 2 (gedeeld in Teams): Cursor + browser + terminal + slides
>
> **Structuur:** ±1 uur live demo → kwartier pauze → ±1,5 uur "Nu jullie"
>
> **Voorbereiding — accounts:**
> - GitHub ✅ — `gh` CLI ingelogd
> - Vercel ✅ — CLI geïnstalleerd (`npm i -g vercel`) + ingelogd (`vercel login`)
> - Cursor ✅ — Pro of Business plan, ingelogd
>
> **Voorbereiding — voorbereide bestanden:**
> - Een schone test-folder klaar (`~/web/demo-portfolio` of vergelijkbaar)
> - `.cursor/rules/general.mdc` snippet copy-paste klaar
> - `AGENTS.md` snippet copy-paste klaar
> - Twee feature-prompts copy-paste klaar (hero + about)
>
> **Voorbereiding — backup:**
> - Een eerder gemaakte demo op standby, voor als live demo vastloopt
> - Screenshots van Vercel preview-URL's voor het geval Vercel traag is
>
> **Voor uitgebreide handleiding zie `Les14-Docenttekst.md` (normale versie).**
---
## BLOK 1 — Welkom + Vandaag
`[SLIDE 1]`
Welkom bij Les 14. Vandaag verlaten we localhost. We gaan een nieuw Next.js-project scaffolden, koppelen aan GitHub en Vercel, en daarna gaan we daar in Cursor features aan toevoegen. Steeds in een eigen feature-branch, met een eigen preview-URL.
Dit is een live demo van begin tot eind. Ik bouw alles voor jullie ogen op. Stel vragen onderweg — in de chat of zet je microfoon aan. Niet wachten tot het eind.
`[SLIDE 2]`
Wat we vandaag in één voorbeeld gaan zien:
Een vers Next.js-project. Naar GitHub. Naar Vercel. Vercel CLI gebruiken om env vars te beheren. Cursor in detail: chat, plan, build, inline edit, rules, project context, en background agents. Dan twee features bouwen, elke in een eigen branch, met preview-URL. Eindigen met PR's die we mergen naar productie.
Werkwijze: ik bouw, jullie kijken. Na de pauze doen jullie hetzelfde voor je eigen project.
## BLOK 2 — Scaffold + Git + Vercel
`[SLIDE 3]`
Stap één: scaffolden. We gebruiken `npx create-next-app`. Dat is het officiële commando van het Next.js team. Het stelt je een paar vragen — TypeScript ja, Tailwind ja, App Router ja, src-directory ja — en je hebt binnen 30 seconden een werkende app.
*(Ik open een terminal, draai create-next-app, doorloop de prompts, en start de dev-server.)*
Je krijgt: een werkende Next.js 16-app, op localhost. TypeScript en Tailwind preconfigured. ESLint klaar. En de juiste `.gitignore`, dus `.env.local`, `node_modules` en `.next` komen straks niet in Git terecht.
`[SLIDE 4]`
Stap twee: Git initialiseren en pushen naar GitHub.
Vijf commando's. `git init` maakt een lokale repo. `git add .` zet alles klaar. `git commit -m "init"` maakt de eerste snapshot. `git branch -M main` zorgt dat onze branch `main` heet. En dan `gh repo create mijn-portfolio --public --source=. --push` — dat is de GitHub CLI die in één commando een remote-repo aanmaakt, koppelt en pusht.
*(Ik draai deze commando's. Browser naar github.com om te bevestigen dat de code er staat.)*
Als je `gh` niet geïnstalleerd hebt: handmatig op github.com een lege repository maken, daarna `git remote add origin` en `git push -u origin main`. Werkt ook prima.
`[SLIDE 5]`
Stap drie: Vercel koppelen.
We openen vercel.com/new. Klikken **Import Git Repository**. Vercel ziet al onze GitHub-repos — we kiezen `mijn-portfolio`. Framework wordt automatisch gedetecteerd als Next.js. Build-commando en output-directory mag je standaard laten.
Klik Deploy. En dan wachten we ongeveer 45 seconden. Vercel klonet de repo, draait `npm install`, draait `npm run build`, deploy't naar het Edge Network, en geeft een URL terug.
*(Ik wacht tot de deploy klaar is, open de URL in nieuwe tab.)*
Daar staat ie. Een verse Next.js-app, live op het internet. Vanaf nu: elke push naar GitHub triggert automatisch een nieuwe deploy. Geen extra werk.
`[SLIDE 6]`
Twee soorten URL's krijg je gratis. Productie-URL — die hoort bij `main`. En preview-URL's voor elke andere branch.
Het formaat van een preview-URL is `<project>-git-<branch>-<scope>.vercel.app`. Hele lange URL's, maar je hoeft ze nooit te onthouden — Vercel post ze automatisch in je Pull Requests.
Voordeel van preview-URL's: iedereen kan op je werk-in-progress klikken zonder lokale setup. Geen npm install. Geen `.env.local`. Klikken en kijken.
`[SLIDE 7]`
Environment variables in de Vercel UI. Onze app heeft er nu nog geen nodig — we hebben geen database, geen API key. Maar voor straks: je gaat in deze opleiding apps bouwen met OpenAI-keys, Supabase-URLs, Stripe-secrets. Die komen allemaal hier.
Belangrijk: drie environments. Production — actief bij deploys vanaf main. Preview — voor alle andere branches. Development — als je `vercel dev` lokaal draait.
En de `NEXT_PUBLIC_` prefix: variabelen met die prefix worden in de browser-bundle gestopt. Zonder prefix zijn ze server-only. Dat is het verschil tussen "iedereen kan dit zien" en "alleen mijn API routes kennen dit".
## BLOK 3 — Vercel CLI
`[SLIDE 8]`
Naast het dashboard is er ook een CLI. Die wil ik even laten zien, want voor dagelijks werk is dat veel sneller dan klikken.
Wat je krijgt: `vercel link` om je lokale folder aan een Vercel-project te koppelen. `vercel env pull` om productie-env-vars naar lokaal te trekken. `vercel env add` en `rm` om vars te beheren zonder dashboard. `vercel logs` voor runtime logs. En `vercel --prod` voor een handmatige productie-deploy.
Installeren: `npm i -g vercel`, dan `vercel login`. Klaar.
`[SLIDE 9]`
`vercel link` doe je één keer per project. Het vraagt naar welk scope — je persoonlijke of een team — en welk project. Daarna maakt het een `.vercel/project.json` aan, en weten alle volgende commando's bij welk Vercel-project ze horen.
`vercel env pull .env.local` is mijn favoriete commando. Het trekt alle env vars uit je gekoppelde environment en schrijft ze naar `.env.local`. Eén commando — je hoeft je env vars nooit handmatig te kopiëren.
Tip: standaard pull't hij development env-vars. Wil je preview-vars? `vercel env pull --environment=preview`.
*(Ik draai `vercel link` en `vercel env pull` — laat de prompt en output zien.)*
`[SLIDE 10]`
Env vars vanuit terminal beheren. `vercel env add` vraagt om naam en waarde, en welke environment. Je kunt meerdere environments tegelijk geven — `production preview development` — voor één commando dat alles dekt.
`vercel env rm` om te verwijderen. `vercel env ls` om te zien wat je hebt.
Wanneer is dit handig? Als je een API-key roteert. Nieuwe key generen, oude verwijderen, nieuwe toevoegen. Drie commando's in de terminal — geen dashboard nodig.
## BLOK 4 — Cursor in detail
`[SLIDE 11]`
Cursor. Nu het hoofdgerecht van vandaag.
Wat is Cursor: een VS Code-fork met AI native ingebouwd. Niet een extensie. Het IS de editor. Werkt met Claude, GPT, Gemini en lokale modellen — je kiest per chat welk model.
Wat krijg je boven gewone VS Code? Chat met je hele codebase als context. Plan-mode — eerst nadenken, dan doen. Build-mode — laat de AI autonoom uitvoeren. Inline edit met Cmd+K. Background agents voor parallel werk. Cursor Rules per project. En tab-completion die meekijkt met je hele bestand, niet alleen je laatste regel.
`[SLIDE 12]`
Cursor 3 heeft twee hoofdvensters. Belangrijk om hiermee te beginnen, want het is in versie 3 nieuw.
Aan de ene kant: het **Editor Window**. Dat is de klassieke VS Code-stijl IDE — file explorer links, editor in het midden, terminal beneden. Daar schrijf je code, review je diffs, gebruik je extensies. Dat is je hoofdwerkplek.
Aan de andere kant: het **Agents Window**. Dat is een nieuwe interface, een nieuwe interface waar alle agents zichtbaar zijn. Lokale agents, cloud-agents, remote agents, agents die je vanaf je telefoon hebt getriggerd. Alles op één plek.
Je wisselt tussen beide met **Cmd+Shift+N**. Dat is de belangrijkste sneltoets van vandaag — onthoud die.
Binnen het Editor Window heb je een chat-paneel — Cmd+L opent het. En in dat chat-paneel kun je kiezen welke modus de AI gebruikt. Eén van die modi is **Agent mode** — daar gaan we het zo over hebben. Plus Cmd+K voor snelle inline-edits in één bestand.
Gouden regel: code schrijven of één feature bouwen → Editor Window met Agent mode in chat. Meerdere features parallel → Agents Window met background agents.
`[SLIDE 13]`
Editor Window in detail. Dit is waar je 80 procent van je tijd doorbrengt.
Stap één: **Cmd+Shift+N** — als je in Agents Window zat, switcht hij naar het Editor Window. Je ziet nu de klassieke layout: file explorer links, editor in het midden, terminal onderaan.
Stap twee: dit voelt als VS Code. Cmd+P opent de file-search. Tab accepteert AI auto-completion suggesties. Bekende sneltoetsen werken allemaal.
Stap drie: voor een snelle inline edit selecteer je code in de editor en druk je **Cmd+K**. Er opent een input-balkje boven je selectie. Je typt wat je wilt — bijvoorbeeld "rename deze variabele naar firstName" — en Cursor toont de diff direct in de editor. **Tab** om te accepteren, **Esc** om te rejecten.
Stap vier: voor langere taken open je het chat-paneel met **Cmd+L**. Het opent rechts, naast je editor.
Wanneer gebruik je het Editor Window? Voor je standaard coding-flow. Als je veel files tegelijk wilt zien met split screens. Voor VS Code extensies — linters, formatters, debuggers — die allemaal gewoon werken.
*(Ik open mijn Editor Window, doe een Cmd+K op een variabele-naam.)*
`[SLIDE 14]`
Agent mode. Dit is de AI-modus *binnen* het chat-paneel in het Editor Window.
Stap één: ik ben in het Editor Window. **Cmd+L** opent het chat-paneel rechts. Stap twee: bovenaan de chat zie ik een mode-selector — een dropdown of een rij icoontjes. Ik kies **Agent**.
Stap drie: ik typ niet een specifieke kleine wijziging, maar de HELE taak. Bijvoorbeeld: "Bouw een /about pagina met een team-grid in onze huisstijl, baseer de styling op onze hero-section."
Stap vier: ik druk Enter. De agent start. Stap vijf: in het chat-paneel verschijnt een lopende takenlijst — de agent vinkt zijn eigen stappen af terwijl hij werkt.
Belangrijk om te weten: in Cursor 3 heeft Agent mode **geen limiet** op het aantal tool-calls per taak. De agent kan dus meerdere files editen, terminal-commando's runnen, en zelfs web-content browsen. Hij vraagt wel altijd eerst voordat hij een terminal-commando uitvoert.
Stap zes: aan het einde verschijnt een samenvatting van alle wijzigingen, per bestand een diff. Stap zeven: **Keep** of **Accept all** om alles te accepteren, of je accepteert per file.
Wanneer kies je Agent mode? Voor grotere features die meerdere bestanden raken. Refactors. Nieuwe pagina's bouwen. API-routes opzetten. Als je het plan vertrouwt en aan het eind tijd hebt om goed te reviewen.
Belangrijke tip: hou je prompt specifiek. "Bouw een about-pagina met team-grid" is goed. "Bouw een mooie website" is veel te open. Dan krijg je een gok.
*(Ik laat een Agent mode demo zien — kleine maar concrete taak.)*
`[SLIDE 15]`
Background agents — het derde stuk. Dit is wat Cursor 3 echt vernieuwt.
Wat het is: async agents die in geïsoleerde cloud-VMs draaien. Niet in jouw editor — vandaar background. Ze werken parallel in eigen Git branches, en je beheert ze via het **Agents Window**.
Hoe kom je er? **Cmd+Shift+N** — switcht van Editor Window naar Agents Window. Daar zie je een lijst-overzicht met alle running en done agents.
Hoe maak je er een aan? Stap één: in het Agents Window klik je op **New agent** of het plusje bovenaan. Stap twee: kies een environment. Local — draait op je laptop. Cloud — draait op Cursor's servers in een geïsoleerde VM. Remote SSH — als je een eigen server hebt. Voor vandaag pakken we cloud, want dat is de meest interessante demo.
Stap drie: beschrijf de taak. Wees specifiek, alsof je een ticket aanmaakt voor een collega. Stap vier: kies optioneel een branch-naam. Stap vijf: klik **Start**.
Stap zes: de agent draait nu in een cloud-VM. Je kunt **Cmd+Shift+N** terug doen naar het Editor Window en gewoon verder werken. De agent babysit zichzelf.
Hoe haal je resultaten op? Stap één: **Cmd+Shift+N** terug naar Agents Window. Stap twee: klare agents staan op **Done**. Stap drie: ik klik op de agent — ik zie de diff, de commit message, en alle stappen die hij heeft genomen.
Stap vier: **Open in PR** maakt automatisch een Pull Request op GitHub. Stap vijf: Vercel pakt het op en maakt een preview-URL. Stap zes: review in browser, merge op GitHub.
Wanneer gebruik je dit? Voor twee tot vier parallelle features. Voor lange refactors die je niet wilt babysitten. Of als je vanaf je telefoon, Slack of Linear een taak triggert terwijl je in een vergadering zit.
*(Ik open Agents Window met Cmd+Shift+N en demonstreer een lege agent.)*
`[SLIDE 16]`
Context — hoe vertel je Cursor waar hij naar moet kijken.
In chat type je een at-teken en je krijgt een autocomplete. at-file:foo.tsx voor één bestand. at-folder:components voor een hele map. at-code:functionName voor een specifieke functie of class. at-docs voor externe documentatie. at-web voor een live web-search. at-git voor git history of blame.
Voorbeeld: "Refactor at-file UserCard.tsx zodat het at-code useUser uit at-folder hooks gebruikt." Cursor weet nu exact welke bestanden in scope zijn.
Hou de scope klein. Geen 50 bestanden in één chat — Cursor wordt dan slechter, niet beter.
`[SLIDE 17]`
Cursor Rules. Per-project instructies aan de AI.
Wat zijn ze: Markdown-bestanden in `.cursor/rules/`. Worden aan ELKE prompt toegevoegd als systeem-instructie. Met YAML front-matter kun je zeggen `alwaysApply: true` of een glob meegeven — bijvoorbeeld alleen actief op `**/*.test.ts` bestanden.
Voorbeeld inhoud: gebruik TypeScript strict mode. Tailwind voor styling, geen CSS modules. Server components by default, `use client` alleen waar nodig. Imports met de `@/` alias.
Effect: je hoeft niet elke keer "vergeet niet TypeScript te gebruiken" in je chat te typen. Cursor weet het al.
*(Ik open `.cursor/rules/general.mdc` en plak mijn voorbereide regels.)*
`[SLIDE 18]`
Project context. Naast Cursor Rules zet je ook een `AGENTS.md` of `PROJECT.md` in je repo-root.
Verschil: Cursor Rules zijn instructies — "doe het zo". AGENTS.md is documentatie — "zo zit deze codebase in elkaar". Tech stack en waarom je die keuze hebt gemaakt. Belangrijke patronen, bijvoorbeeld hoe auth werkt of hoe je errors afhandelt. Hoe je tests draait, hoe je deployed. Pointers naar belangrijke bestanden.
Mooi detail: AGENTS.md wordt ook gelezen door Claude Code en GitHub Copilot. Eén bestand, meerdere AI-tools.
*(Ik laat mijn voorbereide AGENTS.md zien.)*
`[SLIDE 19]`
Zeven tips die je van Cursor-amateur naar Cursor-power-user brengen.
Eén — Cmd+I voor Composer met multi-file context, niet alleen Cmd+L. Twee — at-everything gebruiken, ook at-recommended laat Cursor zelf bestanden voorstellen. Drie — plan in stappen accepteren, niet alles in één klap. Vier — `alwaysApply: false` rules voor opt-in conventies die je per-chat wilt activeren. Vijf — Auto-mode laat Cursor het beste model per taak kiezen.
Zes — Cmd+Shift+Enter geeft je een diff-preview voordat je accept. Zeven — Notepads zijn herbruikbare prompt-snippets per project. Handig voor "schrijf een React component met..." die je vaak gebruikt.
Anti-pattern: alles in één gigantische chat dumpen. Hou je chats focused per feature.
## BLOK 5 — Live feature-workflow
`[SLIDE 20]`
Nu zetten we alles bij elkaar. De workflow voor één feature, van idee tot preview-URL.
Vijf stappen. Plan in Cursor — beschrijf wat je wilt. Branch — `git checkout -b feature/x`, en Cursor kan dit ook voor je doen. Build — laat Cursor de code schrijven via Plan + Apply. Commit en push — Vercel start automatisch een preview-deploy. En open de preview-URL — test, geef feedback, itereer.
Cyclus duurt ongeveer tien minuten per feature. Inclusief AI denkwerk. Dat is wat het bouwen versnelt — niet dat AI mooier code schrijft, maar dat de cycle korter is.
`[SLIDE 21]`
Live demo één: een hero-section voor onze homepage. Via Plan plus Build.
*(Ik doe live:)*
- `git checkout -b feature/hero`
- Cursor chat: *"Plan een hero-section voor de homepage. Gradient achtergrond, een grote titel, ondertitel, twee CTA-knoppen."*
- Plan verschijnt — we lezen het door, eventueel kleine tweaks vragen
- Apply — Cursor maakt de wijzigingen in `app/page.tsx`
- Cmd+K voor inline tweak — bijvoorbeeld kleur van de gradient
- `git add . && git commit -m "feat: hero section"`
Tot zover binnen Cursor. Nu pushen.
`[SLIDE 22]`
`git push -u origin feature/hero`.
Binnen 30 tot 60 seconden begint Vercel een preview-build. Het verschijnt in het dashboard. Als ik nu meteen een PR open op GitHub, post de Vercel-bot zelf de URL als comment.
*(Ik open de preview-URL.)*
Daar is mijn hero-section. Live, op een unieke URL. Ik kan deze URL nu sturen naar wie ik maar wil — een collega, een klant — en ze klikken erop en zien exact wat ik heb gebouwd.
`[SLIDE 23]`
Live demo twee: een about-pagina. Maar nu via een Background agent. Parallel werken.
*(Ik doe live:)*
- `git checkout -b feature/about`
- In Cursor: *"Open background agent: bouw een /about pagina met team-leden in een grid. Gebruik onze hero-section als stijlreferentie."*
- Agent start in een eigen tab — ik kan in mijn main editor blijven werken
- Ik laat zien dat ik bijvoorbeeld een nieuwe Inline edit doe op een ander bestand
- Na een paar minuten meldt de agent zich klaar
- Diff review — apply
- `git add . && git commit -m "feat: about page"`
- `git push -u origin feature/about`
Tweede preview-URL is binnen een minuut online. Twee features tegelijk, twee preview-URL's, en ik heb intussen nog wat anders gedaan ook.
`[SLIDE 24]`
PR's mergen.
We hebben nu twee branches op GitHub. Op github.com open ik beide PRs. In elke PR staat de Vercel-comment met de preview-URL. We klikken erop, controleren dat alles werkt zoals bedoeld.
Mergen naar `main`. Vercel detecteert de merge en bouwt automatisch een productie-deploy. Productie-URL update vanzelf.
Mentaliteit om mee te nemen: main is altijd deploy-baar. Feature branches zijn je experimenteer-ruimte. Niets in main wat niet door een preview is gegaan.
## BLOK 6 — Recap + Nu jullie
`[SLIDE 25]`
Wat we vandaag gedaan hebben.
Een vers Next.js-project gescaffold met `npx`. Git repo aangemaakt en gepushed naar GitHub. Vercel-project gekoppeld en eerste deploy. Vercel CLI gebruikt om te linken en env vars te pullen. Cursor in detail bekeken — chat, plan, build, inline edit, rules, AGENTS.md, en background agents. Twee features gebouwd in eigen branches met eigen preview-URL's. PR's gemerged naar productie.
Wat je nu kunt: elke nieuwe app vanaf nul opzetten met deze flow. Cursor en Vercel zijn je standaard-werkbank voor alles wat je verder bouwt in deze opleiding en daarna.
`[SLIDE 26]`
Na de pauze gaan jullie hetzelfde doen. De opdracht staat in de Lesopdracht-PDF.
Kort: nieuw `npx create-next-app`-project voor jezelf — kies een onderwerp dat je leuk vindt. Push naar je eigen GitHub. Vercel project + eerste deploy. `vercel link` plus `vercel env pull`. Open in Cursor — schrijf één eigen `.cursor/rules/general.mdc` en één `AGENTS.md`. Bouw twee features in eigen branches via Cursor. Push beide → twee preview-URL's. Open één PR, deel beide URL's in de chat.
Je hebt anderhalf uur. Vraag in de chat als je vastloopt — ik kijk mee.
`[SLIDE 27]`
Volgende week Les 15: RAG en embeddings.
We bouwen een PDF Q&A-app. Je laadt een PDF op, en je kunt de AI vragen stellen over de inhoud. Antwoorden komen uit jouw documenten, niet uit de training-data van het model. Dat gaan we from scratch bouwen met embeddings, vector search via pgvector, en context-injectie.
Voorbereiding: je Next.js-app van vandaag moet live staan op Vercel. Je hebt minstens twee PR's gemerged met preview-URL's. Cursor draait, en je AGENTS.md en `.cursor/rules/` zijn ingevuld.
Tot volgende week.
---
## Bijlage A — `.cursor/rules/general.mdc` (copy-paste klaar)
```markdown
---
description: Algemene project-conventies voor mijn-portfolio
alwaysApply: true
---
# Code Style
- Gebruik TypeScript strict mode
- Geen any-types; geef expliciete return types op functies
- Tailwind voor styling — geen CSS modules
- Server components by default; "use client" alleen waar nodig
- Imports: gebruik @/ alias voor src/
# Bestandsstructuur
- Componenten in src/components/<naam>/index.tsx
- Page-specifieke componenten in app/<route>/_components/
- Hooks in src/hooks/<naam>.ts
# Algemeen
- Geen console.log in committed code
- Errors: gebruik next/error of toast (geen alert)
- Async: prefer await over .then chains
```
---
## Bijlage B — `AGENTS.md` (copy-paste klaar)
```markdown
# Mijn Portfolio — Project Guide
## Stack
- Next.js 16 (App Router)
- TypeScript strict
- Tailwind CSS v4
- Vercel hosting
## Belangrijke conventies
- Server components by default
- "use client" alleen voor interactie / hooks
- @/ alias verwijst naar src/
- ESLint moet groen zijn voor merge
## Hoe lokaal draaien
\`\`\`bash
npm install
vercel env pull .env.local # synct env vars
npm run dev # http://localhost:3000
\`\`\`
## Hoe deployen
- Productie: merge naar main → Vercel deploy't automatisch
- Preview: push naar feature branch → Vercel maakt preview-URL
## Belangrijke bestanden
- app/layout.tsx — root layout met fonts en theme
- app/page.tsx — homepage met hero
- src/components/ — herbruikbare UI-componenten
```
---
## Bijlage C — Feature-prompts (copy-paste klaar)
**Feature 1 (Plan + Build):**
```
Plan een hero-section voor de homepage van mijn-portfolio.
Specs:
- Gradient achtergrond (subtiel, twee blauwtinten)
- Grote titel: "Tim Rijkse"
- Ondertitel: "AI Developer & Docent"
- Twee CTA-knoppen: "Projecten" (primair) en "Contact" (secundair)
- Responsive: stack op mobiel, naast elkaar op desktop
- Gebruik Tailwind, geen custom CSS
```
**Feature 2 (Background agent):**
```
Open background agent.
Bouw een /about pagina met team-leden in een grid. Specs:
- 6 team-leden (mock data is prima)
- Grid: 1 kolom op mobiel, 2 op tablet, 3 op desktop
- Per persoon: foto-placeholder, naam, rol, korte bio
- Stijl: matched onze hero-section (zelfde gradients en typografie)
- Header bovenaan met titel "Het team"
```

View File

@@ -0,0 +1,289 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 6 0 R /F6 8 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F6 /Subtype /Type1 /Type /Font
>>
endobj
9 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
17 0 obj
<<
/Contents 31 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
18 0 obj
<<
/Contents 32 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 21 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
19 0 obj
<<
/PageMode /UseNone /Pages 21 0 R /Type /Catalog
>>
endobj
20 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260612063152+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612063152+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 14 Autocue) /Trapped /False
>>
endobj
21 0 obj
<<
/Count 11 /Kids [ 7 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R
18 0 R ] /Type /Pages
>>
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2066
>>
stream
Gb"/'D/\/e&H88.E@=Z-Bk'U#[ejJugLOTp/6s'qr@f$A/R;o,&ue`m^%T?mqi6p=(<9/'2+<'RN\+-SmuHK#g`>X19-;LZ-3_2k+WUOCJ`jr?'"lQt@<`!S_h5]6]XDJ>47U*.GgQ%lV]WGmNMC++O8WB%JJ7\3@,QYn#g=Bhm!rVm.lB$i$W>r9f/G1,X.XNg'kr;_n+Ya.-7H7@=f#QVH!5!Oe2TKOK]*XS*uSQpe)r'1gi10(/Zk_ol<.Cn[qg(IIf$<(ke%Ng(C3#=1?T(#aGR"&-V#p]jnritF"(+L=A9W2%UA'W`NH.t!`]E8Xst,nbrktSBQ\Fr]Jt7`EXM!ug4lHpNjEpr;;=np%f71YH8ra"$d`^Q,:H5B,WRLg"@P)HJ?VPud3&"[P#GrM`)0nl@GbkWqi2c:lY6RE\e=_\TN,jeo\sgA,kBR8WLV=;HK;1r._<eHY*"sq,GJeZmEkdBf$4DqnFdHMK81l'S[g]^GL38`3Wn8tiW21]0G0+o&`Bj0lr$7q>Wt2fmBST/Ze2\OCCkk&\[;D/Fr),&M79/<!d.f&m>'2Cj@QtS96D9=_QQjV9g(UjD5m;X'L^gAn-Pmq")D<":$K,0Gf+*`k>e7<+L<-Re&Vqr`&n2UQ2p/O0H*O/CK46@a"UA2;5#EN1^bp<:;tB6Z.acZl[=/;"*][=X81VfSW/>?)#N@\n;[9O$bk\72W,NBg[G[KC["eVn>4/<^/dlg`&3_G%DKiB'n!"OL#@<iDnFAr:39dB?.=o4f^jW^+QmekE#3QZ_SY[L+8Jc]D0Xe:ZNX9r'-'YGJ&"i%DiH`6X;MPoCUbR+E-uVK,Y,Op7^<hZGJrBl,CW!4G_-9Jb!jn"B9jgK(A^Ler.=7,lcBd%dItS<]l56rj(pc2k@ZHi%qh9Q/1#"K:aMKYN\ht/6OF)^;/)=p%\Zekoj^i7^fQ!!S&*m[(lo0KT<C(Ma6#N]Pmc'p?,hKB0]k;?6!hF7\tY.]Wh@S(3*L%.46saLI'9UZ[h?p^A6nP=Z7ZWq=Vo+oku9RBUW8O?Z!gTn`&3_GNHOcGq-C"J+k?hrkHJj<HA+%=!jtQrgdJtWb&F@k.OhpZe?=3Jo9;S\/>jKA+T%ek"\t(Pi9?P*BkldN9__ktJtPo\V"EDPh%ois-aDSo<]-^_`u,4`/9ct.)+,]p=X#Gr/DkYE<bZAMdBKN%:="\3e8NlSV<!C!_ZeHi,La';DQ./Dl=sDeW&$m%dplc!=mI#e.JrAip@"8160*M+>2O)AR]p='Qi$Pe_?4d'_h,.]DUU@DJu./]rD@VK@<o8C[ET#B\WiqFmL%J\p4-"7,p@KX`%lk.$KiBFh*6?Qqk7b89o1VCcKQ=rp<ec&%/;1i-\<JR=$6A4YD)0>gMu5]^%/Z,b.nm0XtuS)rdRP=bZf1=i!A@!XLtdLEKOaZm\hA+.N$Em>^FA/FLZZ&Z56eq5BMK2Nf:q9G6n)H%fc2_lIjjk?d(7]0_I$[1KfflIm^Of:>C:/j\t$AC!"C32qVHlp:eUpGInp"n(pK/'An!Nf?S0KYFrFSF-\U=gQOEfIV=:sE@*6/9%lU8$-3r>hFW7U=>/SPs45IqAYS4MJSg[V9,ld-dF.rqlaTgE,9%EFR7Ras3=RoVg>![7DX#4V8\Yn>d6lV^Pqpg"ZJ.k/b)Y'dk7T6=4)gl*e&lbXgQn:6WtIGc=G?:%@Nq3]5saqa9VZ=Xn;$mZjjqV(%;:*S]nm+YbC7;cGu^nmdru15rL"qK\4@Wb3<RDP(19lFXcmYJ`?:/bZF&*+@BQ!";uWU/fS13*Pi=a7R5/V7rXUjO.tiL%8mY,(\&!W::9PHbR@5-TqFrNF2V_^mJic9fUJU^R!d2ih"?kA!:9u<!c!,;=g=VlTqMI1j2>:r'Eoapf,ke"q1rKd^c\$b)nAYQ:V[mWuJU>:m[aJ!i=[qN8*'$kRT:%hqLba%?R(7Z(BfMr`L(4Ys<nhQ/TGc(JJ3h%LHf1CC4AX54cX)!/b,N?,eC:2RF!?;b)_Ej?`f_c3Br>>=V^i!I$k##"jQjnh*;g1WpCae~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2400
>>
stream
GatU4fll+>&q0LUeB*W6%#JrTEm?f"YnCiH`#_Qk*l^3#9qhnO!H]QG#&3Dhk>h[i,Z6?2Bq[qhQWe:_]%qf"iO^1<B0"B*>Pe?>54o/&4/`'+)U[q0?/E<jlUAQ+3>k5Ko>,^E:5>%sCtot<*ZdfRWde:E!fQ][lXGR\?F*_:.4^u`D7+!)+jNt4D29(E3l1.3.5P1M-tc>0j-AbLpU:[b&RB,bLCs+<rJ-^c`L:McGjj^'UQiY))cni!3Vo%ZQZW+q)#mrj+*4*)p<OcSSR&hXb<*E\SW^M3Tk0L#0IbPqPYFQ\.Z0W$<S]CK()AgS([UTMS)`2E>";5'4GW;sT(t:1pEn:,1`;557gD^_HeSS\g15.`)H/ITlOD2!jS;Wd%Qp<3Sdq]K-Ct]^:L@JDqj@Z\BeVNCS!f_kD&1U8kU5cfqN9TmZ`dRq)4fS>kjN"E]H?J>h-==#\YS1+/b\E<p@c&+p2D.4f1YP[\Ru8?giuiGVr>-=^ZL-j`a-^Kmn=$`?)<Zf$ARp64>FffRQ4E?]7rQIXt#<@aC_'.Lu'OU\s2iNS^6t&A!^WD1p_J19PeF>N',p@H1EJOMO#Q6<d@)O0sC5>q1'<uMZA4fP[GkLkOqQb2`%?>$rJ%V<B=?ZBYp6?76r#3jk5?sV42U;m/T*2^>P3Z^ij5(Wo/!aBr[0/gL6.8Sll_J?)qbVTCIs"Ba__:cUL]$s5\Y*5Fl&UA;b&4G`Z^9Z4=iK.K(sep_ZGb=^-R=F#G\!$(`l-ST8%#N-Wr*fr`mqT>#/NqtO#Z8Y(54;Z?b>35&#``Vm;`End59<l<e<c;_6VV2t=+M+QGiJe*ur\P=Wp0c9HC=rU7O"s@`GE,o[2a^q:LQ33dLM=rtbf'u+T,&gP-31eAE$AKR]5:?RPo:J&<2rjJfBKB$JOlYk:?u#:p/>ppZN!ALn*e\?kGb-M@d,TBG'XSl.Ro'R!)>>LSf]PO&$!cNBN!:K_BnO@&9EUX`FB!Ha/iHuE"5[)-(7pT,PK0mgRX>jN+8'ZgAYYT'FHEc'1%ZT&\PfC;!7,J]^hJf:H[NPb@u:a3\3lif#2h=O4uu\;_k1h\`VMN&br:]N'+7MqWIiQsU0LfMI+_\C;"kdMe)o"6lOk`Qo!San]G^ZeTqbO&bh,i$;B<V==_uoUBJi1I\R(_B@@]pKd?_B"U7,\?L4;g"U"TE,$D3TAKAAj)M!HAiheUTc&(alK.%uO,SSc"/[e32V*9S)2)N7P9KHPG7O$t3dVh&BJqATm9dd3%"I80)`hsiil^34[Z!i_(ZWEZ#Dn=9!#aA$s!V%6HrU(GLQ_133a=2#F&O*Zj&:^aRYQ'KcW+3F/(XobjSN,5"&=9monMaPOuTEX,q'c`@5YifRG02Cfl.Wst1\Oo:[`s@Jua3_MT1!+;]5O3E3"Kb0B>094<:>Mq<FgOV+*B@)h[4-Edc;b76%*tNK#feMfHMB`45FnT[Va8b=dV[,-K!<5>K@)K^..jBM*[G;3Fqt@2Dg3;Mc^@Q&7-J$BKr\Hl,BT+[>D*unWc@)p#Kj/?XlbL;qIRnrCYg6_eYpAQCtpD<_gQuU.d+3^.H)Sm?Bqu$lOrQPE6KB_1SA'JC)T*c'!HBmGB^^,j!ol:U@NZ22-5W6)p62,raX*dT5>2s3.Hs_POR$?/i*<8bO''KAJ4&EigG=P5WUlDD'\^2k\4W<#ATDu2\O[`3DSYnlbEl5Z>1D?^R=C;S>.Bl7mpZ;r2$SW<6c\SE?]GQ9<*S?:713e1WR%1A65&'$0pbr-%6+DH/"-sZ*RI5BP<t5!.Y;`'5.XS=s;U+9j@/a?o2P,?(Lrs)L*JrKI0$SOn?45bH6Z*-%)Rk;R7OFd,jVQFT7EEW&^:2U;#q!Cq;"ZaFdM,FuuGTY7jJoj([9km\b#(neq9`*%'BMB(11.4DDX=rF_DG/Yk(c34nu)GV@8:i$9V?3/L-nml.^;E[0)t*P#"/=ZEB5>H+o)[!O`)fI5Om8:#GN5hW`.fP;)ZrhT8shrSN9[k8JIaX'23h<um/62/a4/m<<TI!F*;M4-VJo^:ZiH5?8fgf2>&,8rSt4Y<a74*lF^2Oq'rKPUM&<q5<qTqO/^LbO2uVE3KK]W\ULS`d0`.]dG"Zbb<N@eLBoM\u^j_!\&j!_"TlKAe8NTd#gCC8IuYYaeE-QZCBM]BGe>[+h*T9(l94'H*9[)iKH1Rj[T!ah)FBf$kLg/L&YnMInkTZ)90'p,"#p-c:GPMO.qOSM`C0$Yl55;`VVW^sWZoI_1L$SHoXS;GKU-=!6BNJ1ZmIK-8nc6FW's,(QS6#MSj!BR)I8-$SV\YMNV`arnQ._Y'6pDLGZ,&XXb][&K)r[`DRkg*EtP8bJWq\3>@sB(G5eMfm1fK.t]2rr\<o77.~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2042
>>
stream
Gatm<D0+E#&H88.Z'-h\3^uC@0_S0f'>L?>nYC%sl;$Y?k!0:oS94&\J0_nD]6?RG"LaIM%LaTMp@[*OG6ss7J"_32,lm*1GkEtJYQ]Y6K^GEAK57t84T"kU=Ie`dK'm@=1k_'k)tZ292Vk.3K,47^B(Cn6KV_q:OEd0f09[\'h(CH;'KJ'8r-k1Y_lYdO_a]Y2K/O7u;Bj>-Jggu%0G-Xf<2'\*_I*S#+5!=Fk\m(nc1Zll>F'q*2T6X)@*jYeE-.+4U_=tco.u13)1h$M)P*$;Ct=TnnR?%*:>rq;3WVO=E=>2pK,?D@5Nf6bW>IuHABMr4.<4Iq0"!U_)\?_B#1p!t%?3nK;e3(OM+VE^XH9W+Qm&u7YMMa&`CEQX,?1IZ=a[RLjTq*BF0K*uKX<VRea;u=Ke$n$)6U$)N2W-<rp%hGa.!V&fDUVg'a$7EA,5AJiaei%U7r>LBHa6TqlaD(9h\?ZP<CaKQ(r%%(MQR:Rp^,VAcqg$EtHM3(0A8l=UJLIkQ]#)Ti:WPU+._8*2]Nc*p05s<+$Mf:-+t>jAaL(\KH%COp"IN"+S:h@cLSOS!uSY]s*lj)X!KbkjCR:'BXHsLt^YpLO&oNYZW%*@d:Bsg)UHn?8FX=E1]VtY>SS1?X#)em4ktqD4P&6q@4^1TKbP<0!>(I)qjKK[VU3$TVLn+rqlE#IsD42Iqn33FR//\j]Z)QpHmrlAMY+tFJsnM3PB)klT&q,aB?h3/'>5pB1mQLKV`lfmkLmH'pu&B=A2Og[HFb\1M8_Q@$mT:':l$l8Dp7r&gc_*p_jHWT5OM8*P_$+F`g2D:u0HSLaMi^;#Mo4c7ARa'>DRF5ijcPMXV(,9]s390JkP[B/m2)R:ER^CG7`DKD5t_Ki1!Y>\$B*<Y-H5k[9$X3epZ?Z_eno?k']/4f1K_EQ&GDGP?h`p3GtgP+j*pWK\jNgWWKGjIcZK:\'BNLCUX]X\@*4#iM^t;'DXL+g&?6A8p>%`;#1nEe56`[18^F*-EBHdQ7`6-P=m)F3BM3&ZXh>bn?,bR"4pDf9'QurPR$Hl0aN2hC4%B*N-Knn)g.h)P#9R=lr!=[ej=:07&Zo>ca&<mQTohr$%:h._1LF^<QfTR9L2EjGEQ[YsL='I&&Rj,YcnTI'0GU&JlVSZ%J$)OgkK>C?VA'>=uUTEN,UN6d.\@ih!^f]_Sb^[q<1RA;iP.(n>tX9O3<Y2*i^h;Z0-6#sh_/aPFqt9\JA=C;*V`C/JNk4c&:t.9Z6U"S=->H6b!0AQF"\Hn:SPAieq/[3Ijm9MsBQbi0V9/>Qe6$$q?]#f=SLaRQ!MJlm?c4EnoL7/rog6n'03CNE*)9Jqj$rmIB;CU0la'#MZ+,bd(dEh]""Nt+(F)Y<JaAnOA&P/`+N/k>mERH<SgLY6h+$Ndi5%Qnfr*=p$TiTj[LL)k%d2IcVp+eo[g)D6g<LpO_>j85VNglKoAg2Qekp9hZ15$W*L-ebpipJO.#R0+:cS_:jjVSW5S)W!BeT2EL<%8(J[C/X>JeY,5d/PKDc(/5uORMguHrm_*<6*N0X:`1U?ar$mUYaV#!/TW4SNddZNM#'5N+_Z*<fNS*l/3b6i'CZ_#EjTUap958I+Pj,RMP1R8@+d=c%-to0AgA9cM4A=2]QL`egL!=FqhH`^9KKMCZhVYtW9Q(u"YbNW-]ZfU33;4@Zi(o6_O[>7NVY>)o`]W`a_Q.]QYPrQ[+@1tpmH:e9*_Gg^6c?3&Kp9T[6F<WQjPP(o)9J(GH+?*e[X+^'8jsZs(8L_-N+Q]@b[`._O;QiQW0/K2.@/hL-`$!3kRe6SPFIgIp!rLhVO.oN@)ZW@YIc=9T>bu9>677_AX+&L7PMEZi^j-(Zg&'-E!ioj*3ls*_e5M'#1BgeO`K]]A*0_R(D0&]K'VR;7hMi*FiT\Bdr/98)ElpeZ<""V."sC]i`5`XFB*F:GfE>')af=^LbV46(Yd%UV2u8c'E=^:h%8Jps[K<hN3gJR3IQS0d45&Xf7>I:l2Pto/o+'P,3'[F,+o@"4-rKpWJ!H~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2580
>>
stream
GatU4?$G$]&q/*0R$3lJ>DpmqA]HGe:?$/sp%G1#Sb?Ku%j20V&4o+pJl^o=^NTZMU-@qjlf6V071p\dmbG@*&In7dnpcj\khVccr%b"oR-#9GVU:XjGB>orp"riGp;tuIR_9`V`J:qOkiuV:`I#*8N@Fe3q'uk/g5\7cdC]g=%`=r%jOp0B;.8!1Vkp3Age1AP>T59ZO"^.^V"//E^VDXjFSs'DQBDl.*K+*a-u3($-i\3!I4g3p<4GA(a5]gUdamhVRP/CS2`fD3Q+E(RS5Q%02,Zs:Y#/caH(u(@#8]=k82SV*@k)5X]/S1/j.CFmYUl3BYaY;dqU;Vb73k<>9r$2)A0RAoeT>_gV3S9Z/7hCgBo@f(2n$`8s(Y//dn8W62+s?Fq6uf_L^/d9<i'o\@$O>n,$CjUZ+p<@ekYH$I()1k]0$PL_aD)!g.AS*QY!OL$IS_k5Go]M3U7Er&AZD:!<<#ZmaHqq0]bM^-G$^5`^C&*Bq_!r".;/iCC)tZa\m'^]<sM*+i/^LCao^q>:=3:+#ueA='B\?f0VQXi(2Co1u71T^cWc*fW=sX2&^\IYd`u@R+E?Z]3B\rQZRDgj9(1QUB1h6p+,Zs^A2?q^2S6'L1-j,.\3#NHZM!s=[X?b?Ta&l8l9*k`AUd%[8Y833$+[=R0$UR?`VUW:_!%Kl>oa/%'T;h5.l\DV((F,+=j9W$-9JIRJ.eVYZ)tX^m[!i3;_";Xa;AGI%@8g_m$\1D5.I;#3gr*-s:I)]9KqL3+SNfTR"oen;Gudja\R[OMjq&IM5WQ4.()<H(mjF#Ga,;!/a_4JnI?.;t<a"*`TFa1#ICU9>Gme$@sUZ]B>ECVF_#^.k<.1FuS/!=;V&0^!OVd`'G(m"3"B`GUIdIFB7M6ZtVoe-0pOndFZLZLjq;TKn"FA)#VkOUAkb^T^#^H8Cn(GCTpF-dYlde"B\F6/6(b\qX#`Wa4h;@Kr[)N2Nl#pJ];UX6lrk!ZEO=n$s'!;%UYCMn%?/T@dPHp#K.tmDC2;kK9adr[D4GW2ggCJa<4$f!^MlGCpj.VdHGKE'M:Ps['_HIp8D7kh3P-#s*1%F?q/)$&<eRJmj.#jXusWaX=n.Fk!JQJIVTIK:nMSp2WC)-icBrFphCX<*XAP&>i9og@(Gf*];&\:^NtQEH9(=j0Lf+k/&pHYIphB]i3h1=<l`j(R&mWg9FBBR2B^(M]n'q05MXl\`U!T)p<GtRRGL#IH1)a"M#I]V_E,%mYar5Z\V+VA]I-s5k'1Z4>.s-Po>ULd%Ud`&!DFm>bi6`hm\A":,+VOVj?\':n=#8W],IuW8iQg@"K[]"#pfR3@p$:sog.YY5mV:R;@pQ%dF$\6kD3D75'FA-:S%Fa56uI.oia?i&K%#aC#8HDn3dEDI:--T)bN3WZ#pJOpgtEY_l#'fS[8TUX!r^q#E4i_-*W/eNSZOtl\6'+SVO*84->l#c`N!IeTa$*=pg`M,+=FW7$>WV6f,A5Xm\79/M)$s3XP7#]n]"FOEO6S='LS9I6"Kak[US>j66s:M;NMl(&%#0Q0ZJ^WeN,/Kb^]l2Rfr&+X]2,_b6tG]H]MW^L*&Fm&b,u`.XUC%YiAE1nD:Uq3oTn?8ZU3MmCuYC\SIlJNCuUF5L".RHV8J[6hMgT4<[0V]RBl9!=A1U4RGS.78hn-":b-4I%^V[d?$/II1CsXCCCka)l/7i*^bV`7]RuO2V1oFrqal)0@j\209.@Y6%9HPPiL\f?#7U=ilYmm$.al7m6559p@"-bdMB1_#B,6X:rfLN`Tm&pMIfX(@1BJl6#P0#WkjfYg5]_C*15,bO=n=4R89qJbpf)!T9S8j%^,),\]Kl+1urtS+AhiVTa=^o0HE_inXaZ]]\+W%b(as$9pJ%SqiEF?,eZ_or@'u8hAVT`:q0Y_Qmrcg7n[em"%iBCaE?]SB@4A_m[mqf@hX+5Ju[j9G!:L3)6)ELh?rNiTsJT#Z7;'fSApc%m@Re"K-u\J1n@a2aGTfkj+9mQ8j=g$%KlD9RD#<nOp0"kJ+J!_#*1YnB>_bC]HSU]n8PloY[b.-VSuMW/`JPXTkKi6"Gb;_qB"Q1A<>4JS]lD5J=:M!jHH+p9kmH+JMU?p4=qa8'R+_M_jp^=2V,>r)n]V$<U#L*66UNXKX>G^%iRAS5d?t^?!EEneRa8-QA<qg$4-7^Kc*-H\<;r?(n9\5@sG_GU4#gGoKbJP0<B!/K[q%/NTeeWu(j5G^FWBN>;[K7Aoode*D3k1Aj+M"T"*`_nHa#6$VMR]2"5uJ5/&bi#7SQ\S0>`o*N`'naWOLo@31Ie1'R6%M)-HJ:59=-[OSNV9t2A=t!-2akMeZQ:`E.HYgN=D-b>[HaV)#2e;<]NR-Prpt=RgW+8Z#D2j1$f<($'%usRBTIua!8Kk'RNX_jo"V_jOeMSG(Yt\g2mUQ,IrFlMlcMqJQpWjr?&_7S+`,GG/oWlpe*n!4G!2p6@4-?Na3TQi#Ob!fg#75-?3(aZkqeug<_$LLkX4m!J/WO)i]<7`REJ;8EI]1@HiNO"(H&uTlc:[ZL1]iU*@VaOPp\c2anc2L4ofiLrj1P~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2519
>>
stream
Gatm<gN)%.&q/)-FQI's3u1=f?hBLRSBSpXSkM'9,ZVJ0#De$]L_k"1:eLBQHQ&P*ag)S+B?Nlh!C<AuE6d]c%]GG\Jc@Y]LgJob:HoA'0Vl<@%E?e_mV_75h6T><%@W\t_s$H-GWDGgd91QGl&Y?U*ok4*],'6Qn2A&I=6oHmdBNFV;E@qu+]:gGR?P^_=sbMkZ&VjgRm]5>W$Oo*EmbZ%'R;@_73TfA;m"hI9B2`gk\`t9J*6%pM4]PIm#N6HE,b@'&9",<BHuo/KICOX*D'6XP#_iF7XV'(&R&&QE[@5SAP+Js'R;<FXd0ok@Ruf_<(TG5BcZ4-Y9[jHO,2EFT&F3)=0e?Zs8PkZ&"@f=rZPk'N,<&e+[&&YpG.b?j`p+OfnGL.!_O)SRPYuu<`ujU^sbe6=1RYtm+LH/&(j%p%RD-F)<>'W!Uc.IU+EPoAI;9XL6hn^;5"[!Jj/2n.5dQHUq%2B2OK^W8HiElGdKaL3#[q$185;"/n1`<NTcproief&<WZ>FraolA0oW(bhd?,:XrZ*CeODU_#BLskhf+k@!cc$dPLd8$o&/rX'6PI2N_rNY`5lgP,?IM./fjF#Soqk&ORrgn<t\B-7P-EQLoMmUFNk&hN%R?s>KL!lQ-:FurX-^8PS6<to`?1bdNNR,oWUmWS<W\ZfpgiiWR)R1YN<tX-iM8SKOd@p>qpH)JPLqj;Vn&?8TiVGr;BCg)2I7E*?P7Pa`U(`&4(b,nk^M<9cJf&cbn^%ANB/e]Fi"WXNT6eK^FH6U<c&O5K(ZTrs9j/%R^0gmEP=i2`-p"Vo)!e_^B8BT*cdfTU$MO'F]X(7q][c//PUp"qP#ja*4usAllCFOP#A"]PpR;OsSSr9df!#\)"FCO89ZZUJ&1&J(n*jZ,''C$/TF3QLQkXf!tj=jQFqOh!3ZfT&O#,8ak\XG`Jl!R!6ofj-`P9fL/%(iT)]NKJ.`(!n3aBcZ,NRMs5'9)cR-]`gH?Y;BZ&:"Ye2[.-$#gV_%fn]?S4)is*N,KFdt$HpdCqi"13_hg`6<H_?+kVQ%%.;?f3l!Lafp_-IetO1,KSG<`f'8dugr]2m3U!E+@[O:3M1P(>_T*"*ftcrd(;egPRH['$=CViB]dfOB4P1j)rAB&p=s+ieQN*DdW7+%9aged3'>57=[u$P_\1T6(D+)Xq)Z>SZ&mi080jj^6Nf&Y/=N3ZR+PX;iudD4*;4!r2/G5q",9>0:PMHlNb+#A&"6!l@BJULTFA47PFW]K__)7HJV+d1sj_rpq7FA2l(K(FG`t]6dsXN\AG:$d\cu]))1@kKX.>Q>UWuL6Y3KKg%K"M6O-YH;Vc/6"2;8PHg(tNBg9J05tAQhNU1Q>iN*!A]8[r[h]X0?$G8Di6a<dCCumM"k6C.`!bY+3HbmXJ3G-V?+rG_dtb6MQ_#\PR)Pe1,)+Ip5DV1QSVg7[_%iaP=5$ugY+,RF@4aT)3(a]t.,%ago4_UcI=C.c3T='EPXZJhT2IU^'Y:,C=Z;:S>r\Qc@5rCTm"=3$)Vf\1_?$m&[hY4OJ_RQiE=jAG>5L>h8Z]14=O/n4R@&Ck4[SKl)k#$a^b^;d9L=m<>r,UhL_Iu4II#Yp'TS:4Z(F-!PD<Cdh^`_U[]\:mKE>8?&)#EId-c]QZmPoOcLVP7[TDjMO"n(c/u-daSFS>gE[eG;#W@r3+aQf%mT&B'(s.20)\j"Igu7fr?$*)u8,^3(lla%>./_B<J[Y#3*+9na/8D'ji=l^:F8LBVkHMffCGmV,K0nL54ZcL+7qlE<]&M>0]U6*6-i7J#W@G7U]g"^92",csKi&=pB(R"=H4!nO.ee,`5%);"nR#rK[V](qB;Q.F%K^HM/g_Mtm6%+[BpA(4Yf?UOo3`.e*LGph(+Qj9>H?PeLbp=K%G(">D-fNr&DgfcbiKfg5Gd*SV&F!m)cZ,I3%VZt3MY#e9/#K3mBfr%j$dpA<59H_Yh5m,`TqKkWT3hEa2&52]fL_&S3;H#)@&j9[*HaikWkkUACdaG(lk39(d6;iS9=$VL;Tpr2tr\]V3/O-1rcfIrf[U?PV8tL0@o"?eM=X=T`J-4rG!-Q(j'9`,9I`)Of9+p6/:(,>GRF$4XW$mDt/p-=G'f1#[h^RNnuJLH2I\R$=ZKMIL-n,2>?KnLL!Klo5tNa:3d:N06<m?',I6#r20&N`b82Ho=Bmk13b/R\XV4%*tXp6+B].n0!%4g.add0oe2C0C2&XV>W]gE6M+s@GO+C+j]hJNKrj9fT9-(MNo==\b!O!QH+-q:OLj5l\5Ta1L]ao7bYT'$;gQ1VBX7l5rdZG($'TVp2`bZ!`!/<'XlLAtq2\_75sCjmXeWGjH,IB42Ro3Z@H85@`a9[krU36q',s<jn;q3/Ae$mWo3p%ZeMS`)YbD(T&^`7Z:Qtcd83=\6,(#GhAYi,<,rXc.\UWd!iiRD/rgGGekWd#j+7Y]h%fUM-I;F$$^\LM^m>!ac]^oHu?=ib,`nWhp>AMfGoj?h]kf`;hkGDnl"_UhVrr~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2440
>>
stream
GatU4fll+>(4>jXgnQ79-6N?mWT@i)KbGu!*QMWZPDn[u#rK^=!Ap\$&0lW%e89`*83)cK=ZT&>06aC"<FA(?Hj/qNS.iT,d)j$)$::2W'L[C<P8bE9?N'=md&n@BrocI,&*UBMk+<+[&bb0<2?rKR#2#MW4)];_p%^R3D#]tB*WI964fZ,G*p!R=(M8fnA`6?pAOVa&JlG$#d,Y\k#OV4p5H[_Wc&&DU=e2oqKCr1>6Q=l26p]k&?cH%`SX5YDQ8<fI*sme*En_qEam8i?+;bSaBsOPplCkL]k+4%PC[0P%l-junl-IQI2j>4d=>(KA8jeNJTKA"/B/@(?U<&Y[Tt2eum,<P8P'e/ol'*GOc8%PZ>]OBMm7S:fXodq$J>VW"?>D?^1?5SphBP"_P]XH[mH`@OYC2>_q6o"hEEuLmFTqX;)nlgSFEBQiY,B+h5PFa@3au+5Fe)da9MS<a(,U:bj_\fJg49=+fM\jce9E0:[T#[^S1G^EA?)$iYRTi'h;7]PDRN?MdbIL%9&MZuB6>@*>LE(o&t+V<Zb=&`e<Qgh;cpM1Q+!P_+Uf)H;)=iIEMgR/3?"O2]u0iW#<<cu7+J-8_sL=SWnD9H,HgOur2/&cj#!%+]jT@7[7i.e$3A:c+_`*Na<MB'"`+.P<G<sbop<W0%hqcpkJBBd=B>3=jX,kN]R>fYO`ebml9>N:BH&1k6Q#W>I(]N5&'W>eG[n=\5ra-&[gP^lZ=@sDM$<"b+3g/iWR*DQH-.W:b7>VV#f@IcA?agOs4lI9s#A"hr3sR4rMWZ&%SkMDU[QQWMSVRKDsPjW+Y(*=gF'-<W5s!l[9bh)H!8eBT>hs"\#A=1.XJ]$j/676*a_VOX`!W<A>q_e[I&L.@2*i6n`V+^5>+qoF-U#nad.:fU$9^>-QOL:3WMD0,G3O9PH$%YELh^_"BN!i+#T);)1RlTN0;8]!+-jEbSFjm#>J_k#:kXU5qQ1%S?%`"T"Xu_n!mQT=Y@Voh0CM,;9Y!1_ebXU)Q'GSD0,OL5)_7Rg<MF"Amr4dr"kkTNR=UIOj9B%1S"<EH,0?<FGpb<<7e9JA=;ZeRJ?DO.prKA$`:;q-Be%e0+ieEctPT\;0^eVf%,9eN1?'Ef30=k/%Ck6c7C-ieq"S]MaeCo'UjE7M&M"oKl1kOIo>uG4iOV$b()JjIi/ZI@O5_O<`!JDOh9UuH0nb:ZS'HASThtbPNHNpU5mDP!CkmTU.qACOIo:,'^]T&2_rg,#=aTb7udLgL;g0uE`2rDhSX7h,O,T&,[$T@K'hGf%>9()@XunJ@b(j7KC``hAO@8@A^(u:DV2/`jNVgW0<WqaE#^Fu3=LQ2;O,?=\b6pBD2eSb])`"!)OlA4ooQb1Y]Ep2NNi--j2G-?+sUMk5WFTo)JleQodI.gj2G7kn=6us1AmnME>*H=H!`*Q9BZ+[=^nJ#8(QY$:$aT8Eir-NMbK@!I9+G9Sb6!(R_fI$]2PW@9so(W@>DBQBXhR+[biCbl"n:9A_*9tH]F/*^.[(?\Hjg'X$(WP4-a3Fk!DUO!]*$%/-qIUMM[u64@Yl2jY/as_USi0[r[hg2e/^[[[jDH;?fC\daTe,]N9.<.Zt)3XQbO:J$#8'IF)#\,&,#1/rdcg<,?gD\9Sn\W^<D]TQ/8fCr@=s%nNX%;t_Gl*b`029DBcd'E?YM12q[!5MAf&=KIV:7?@c%G7=Hs5i@pq-)e=e?q<BKS#Mgk)T:OebbWOTaaC#PQ1_4,KcF/2%5'mqKrp@$')>qr&#.I5,<ri6hd8%pe4/D21aIQiefUD,an#Q5gWQsH*hbqn8n!qJgRDQ"TjcF()h<8MV[(IMUmYQgF1gJrfhONa_W>4@".+e<rX1?)daAX`Rn(ZQ7^a=k=)%%4=@ppjis)!B?FL[63V%s^_4>74mBh,QJ%FmkdkHTd69ju8!S`ED0T62el<f&2jFM"o'2GLfd$AQS+2Ko'>V2F"B(CO^1STE=4%P\o=@?OW?Kg++2dPNmo(Q)6m'0K'0&-%B$c+[J\/\R82JegLHs8,6-+ds%R5&,)_N-$Ek*LE@RXEtR,As,R/B=1V%THO$SW=951WLV;<5Ma\QPt1o"6mm;eXa]8_FkWgHPl2*C0VDdHVO5QSg&$>9]:MKUf`DJl-S/B/F7u2BDD;f+jOiD9jJtO?1:?0X:Xkun6&3)GhlsB\h84jIl)1kNGOIU>ACqiFbTkYIQPNroo][iMd4%umhX2?.hb$?aC/1jr)P%?e08Nu_^i5e#S8Whg9>7h/iGL&7GlDr5mKRZU=$IAJKqcpk'AiJ(CiKf2!n;i.]&'S*>JGBUN>YU9X=<"0J0hV%c(Ej'#'#F&".?bmE6(6`E*KAE4!3Z^Km>CHedDa<Jb\`"4&b1?`#=#>#fb*%8K]kNH#O)_]UoH2HRQt+jX"#pMKBEhqT$]qEO7?"gJ~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2037
>>
stream
Gb!;dbBD?*&DcM"l=9o"@`=Ml=*EPn6\(9g%#\O0/ZE3goc0rB6b\#L]&dR67h0/o%"VejEd--;Wa#5?L)0a?qR7OK]H"fQ>8qA`L,WMPQ4(T'GV_JYqoGr.lu]At^_teOFh:\hdrO]"FLe5$kG+gJ$8&rCF6EFKkkE>'/>qh+g^[HRI]GVN^`'TU:UQ"l3#S81VDXq?Ref^;Dg&>Y%#,E453EBhd*H`r[V^/']5L'_SaQaEn)iVQhlL_!YPtQGM_kZg/CYP"5JE"C!f./A0i+8BqF[Uk9N/MK5T$CRAn$E/]ZW_d0i9(kZ,1mQVb(j%^IU!mJs$J9PPMI&F;8lg)5nDs]GdA#_^*J^9W[:;>Joc*((Q,YEj%&3](\Oi0q2WYpgZ<-8A7)Z%qZ(ZhJ+/J"G@kj1F9h/`!F3cAV,ko#Rd$^"DP.DKD#?3BYN*S"jJgGkZ:T4`d*k=5>:-<7E%THMMl'n-b"+A'8dB4!r<2#Dqiel%?Y5"\u&HsI0TsOlYm[lpY)YX?H'a=&#NE=&hO`4;"<V"YBZ<2JFd.10Vu&uHq%*dm0`iCYs4KPp3l0I8Rd8o!"5C^/K.E85P!8_VZ3)>*8_kP%'p:?XE&^O0OH^JeTL!_m.=?rGgE`*;['WZodAiD8Z2nhJQhQBKWH`E`'@O8=e<EHm;[aOq?d0!mIK90)fV=^*o<hOE)nlC6lJ$1BU4bBgiU?AQT:Wmnl>##nS-L/]@3kspQje/dK&-AJYFE3>F^!\"UTEELK![Ik!&;Yc]'KrWPhrg.qKDsn=F<7Ki;POUnS]QWQ==Yo;DX,6;fFj9T4iFidY%]OO@g9?gpgjpSE'8=^#K"10ZY"XpU,*pnQ`M01!s*>cuS4F^T3[4j'J1d>3d?clA,?Sqg$QYeE9$Z#dTF]K[RaQ=\g'::gVG))PY-5tk9D9F.iHHODIf'MfQ/#.$uKL)M?GkiAG7X]km3;F84mPsE3ebp7=-@e2W9e;)6+f$YN7Bb]Ms(qgL/oW2XO]q]/V8;8;N1$AN",KXl<$X^_D-sh@]*fSH=b?7Xd=70Ig>]"32G)b'qHg]osM@h4<j&G?>1C]>:&64pKUJ-=E*uS%pHRa``qGR)3M6/N>JG"Q#i(E'^OGueYjC'WDF$&3MXQ(ELTgNHWgJPTk-1),ra!k`W4l?TNfkdiqd1^GWIRmUoVRN4)S]2]!h0.QU#%p.GDRON[`I)X1:&)s!j_Y^i)/r76g,(O:.:"'*#)+7=7MlsSBT.Ng)LmNK?HR6+.1cZaR1Eol6HMf`iscXM.#GPf_r-'Q18n>*)do6n?5/[GeJo'`/<H.d$l;N'Zuf0oF"I*p5UmD5?:7t[mPS:a",Pe7lfpe9Gs;kY+H[dIf-DFTH=)lY$k:IRP5Ng`&_2hlVO_Ha$20Z1-j\'CpjYBI:PROYB)5UVV4on'TqI*tmP+dc+7m\jq(H=spC-`&]X\'<I8%HbG*'J+!W89hd"38-Xp=eV:#@2Xh%mJ>s2`s[mjQ,HE#D"U9^><><Gc=@\=^9r#k1gbLBW)*r1jK*V@aH7L=LpUPuXCk-`fOli*6[=Z7TWb0IrtE/P%c!'I63O6%kf+@1D&S\GJh9/Xk1kTh-8;++^os@-dY<\HC&G[Bu_mW"mdI,_"jSms0k-BenZ/+sAsH/5;W<T/5o60tjB[1*2;X#"m&<eTF1WKJFU&r._,M@,bF%Y&\uVbL<J%_goSTFo-/F]pkr_NECDgn\k:8d$'e<.^1c"OCh?ha,&C(Xk)?ZZL\CW8"BlpZSnGLYr]C)mcW^n+C"2r1X:-Q3h9O,XOS_j;PgWpP'hH7_UG/'h1a)RZRGdW#chsQ\AA\UAqUBZ,:kb#Fb4Q3b_(S0PV<\@Kg[qcpmB3VG#+24H"ODqFhEN$,mI`ehXh(%$f7=1?P_*BX/QCKn3+ZoEZkuL9p20S_I]sD,cX1h04(-[S2?%INk>.lJp/5tP81M%_sU`G\(b<om_,=_n9&qNRF5C1I\-X^W[BHdT0L[K4^"u9(E'mDk?X9,MBm)0'or7oA<fR-~>endstream
endobj
29 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1624
>>
stream
GatUs>Bedj&BE],.H[TQX\k1ifVD;<ls*_d1i]Jc;"pJ2dK#3DY.@e&i_SJ380AllC7H3$D=-cHk;V^g3d?A/B=o@50qr<a64+qU9do?34W7VS`TZNXUN!nf&-[\p1^rWE3s\UYSK?'b3JaV_]BKo\KT05-=?pJb_G2[],e!^KGkq(2Jti8%:MDY`dEeQJ"('k`'V7KT;>@8f:Wh.DBrOe#e<Hhh+,4Ai*jZ.oeii6SebuVaJLIVMCt7AsO'3?9BZ[EMGPoDbo:hd;46P86N$;;dM9QZ:Yc#X(D2_XFs0@*\&_dZV[J&Z@[fcr5]ML*N"Sl!Ab@8Ht)5Wj>H;fhCa'.*]@a=E*9l6IXeD_ak1=)2(T%U:JeQ_;"j]cO?]2D#1>nD0ig8)#]hFiM7%cM,.Af5&K02#AKf4`Y<'"u^2NB(Q1G8!Cm'[N".VE@f>R)YuJP%2oEJseUG=E.ol`A*ps^3DU5(bDnMP@G?*;eJaeOIEKF76mU_F/*Nfb*S+r1"_em"<r]q<),JciYbcB]_/&TnP\tYrTD_m'*ro6ogI(WfM+<@6^3`hp6lL@N9Hr"%o;q#jdaGWPa9<R-dGWfV7B!nW]ZVg<>-orAFj%!*p/A*$/[L41:u7.?%TH/gT@%daD><apo<C\dU'1Oa3(3bZoFb@[=VGe!@Z6H*D,`_<LJI@RqJf9D5JL_6l:&;5WXtjdQKQ,*.W\@Yk-m7Gc0IEjf4!+;^&ss<NI''n@+o^Ya/B?l>m8!i`&&3KLi+cFQemGc1CZ+o2c18V&p3@&S076O<D;BL[qAnj*R#`fV&JYD8;Jr$>5\D:#f<ST<n;ppLC4#"cNRgK*jl#j@FDn',d,b'l=SE-37;=MQ1t*)sVTMP@daB)b%O.@V+0VA\DH<Z>2&]:_rp<#^QV6pVHVp%Wf!kls."hj^o+NS=[X]DEjBS%3KbY"EASJ=bWP^fYL<o^efF)7KH[;Cfa;_YPVA75`_$4]r7XU*Lh8O[FAsIiH5XG;N9sG9C7.3dl1-RYbQ-M;X<'<?PT`G$h(9ZOXn`..]Q3cYrBZ/]+:k4hXH$=l//9Aa.AN[j%Q.?b7`*-4.4fFmthpt=&FNA'gAr^pN;pIMLqFtJE.m?m[ZOr+F?q-EWE+cq!Eh]Ec!V^"T1_!;ZRD_rTA9Fp:;0\M_QTrki*/'pEQUU4c#b,>g8&h_LQYeImkG/Q*$\("?F<%K\U-2-,/&?o4iXt:GUcPG@ti.l"#O$VC_HGZKQ(,d:R"6A:%fH1O]L0-.nC7"[5T7Hfia=M2b.j^b@kd,5Q$X'"h/s+;AYm9ZcK4Ra],#S,q]LR0rSRX:o4_g\D[Jjl>%6\*BR3"SEUq1%p,g_9-2MoOC(eT2i&5(3Y5=g'I?]\hS;&Hi(*Fh\,oE$eL3VQsDkBT1`iVGKTT,Tt0/8/6p<hP,lEQ5j)oP#IJ3NKf2JS.;D&f9rJ@kF!Hium#Y]d<fB6h=110-%J&^KXPRH$F_?7i]I^DMO:VmCo.S-Z5E2b'_)jf&Lr+Tokk54/;>ArVd;Z]`//go(]flVUdnY)mgP0#0\n>E[;d1)=R@d;T>PLP21\:sDePQ.UG:7m:RoX@sYO*;Hgi>GE(V6*64)WHn^[Y(LKE~>endstream
endobj
30 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1092
>>
stream
GasanD/Z1/&H9tY)!ub9XqV*["/`\6?!DGYUsnS.jC>#jQ8kjJA$a1^c+i-jOH>ZL_Z_L)0Ri/Pa"nbD0j_6eYA/UR2(UshTFBCUTU]t'rromtkXS$2c]"!f?j-_*1W"iR\1Ss^VF<s_\r/Ns(q<j^+qC-I'^Z(R&^PMB6m3nHNjsn__TB_"*Lnkto/JtBLkAJ<*Gm@Joe^8DeBu9,_C3DF_-,%gV^$hh"ehe0*Dqt-EUl,]^u?eD'u^h(NA!f)j8(3ol*6*eU?t%l10c5&06[IdYBEUXW&C(TEYkNZP\%Y?+E9_H=P%Ms==i=Gq@V<IIcm84,i"%D7^eR\NE=\9_Nj!"=rI^,Z]DE_MoqK$BtTT\4Bq-E<f^k?=Qd.o.m"Du@:Cc/30j?:gp^?ZU\C,5>pj:9;O$g'V\@+(`s!"1^\=HhO;,m@a8?$Wlpek7n+@)&RV*lt"<XSXBDLA+l3Z&=7]q'rT_2&[*bkhJJ]`5p*(MDkTJZP"8$)DIm%iEgHu66jPk5TYoURebIg+TB[PLq9M4#J8_ZJ!lI+P4C0?S*cm;"F`ATi%==<?H>\65p8o1mWkE\'IA&*-g?4\H$FXW)&[6<a/uLQ1)TSbDO2:"N"9[)*>lT[Z"jH7;Jl`bs:D-4gidaKn58?gCO+m3U1$PlGf]LoMF&Z?=]<#*7mlcJrB23oZCN/ELAa>g(]bB%,brG*VMWG<SKY?jfb!EcY%\doN@/3!m"j@*5eS\AV+(YJdoRkFqFa8lD6+,P44^:+k6i<be\rPFOO=#ZWe-nq49>QQMRoT#<`5,:a&gcfu[n(MfYs"7_0913,jmkCM'O"KqC0eiY6]P)E3&iQRGH4E?UH4CLpRp'r-=XjrSJ]*iF0h-,(W2u#5]M@AYJbOU%6]62E1P8Z7JeLtJfK&)$fAaA=SU2QDs9BYd$cIBaV+4.4F]r<XS9U/guRI[1R*c1$]iZCK*&#D%Z_VY63Sn8;F$;85urNuaNNt+Au_D7WX.?J'W[A6@1Dk&;4`Aj.ba5n\'a((JlXIn\mfBQ4CTu$D@;LH+QI/EYa7I&,VPbGD-IaOf6&tUp<omaoi_[E32gas"m~>endstream
endobj
31 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1699
>>
stream
Gb!#[gN)%,&:N_Cm+=+rP"#u`Ji>hMG3Un?Bq&3sTCbs9JgF#7_2t<0pZoHL<H/YVDJ)"q4I3@m/e3(AB+HHDpnm`a-j@+a_X*!YA[&fLapEjb>PMoHIC*\"dUU3'=\-C!IDRtAk[tKBe'OjDHj7"m1+9S3AoZa^\S)Ks[5`I95o:$=`8Z>b*![,fBX>c)2RiW`9@0etXPW)a/ob.ae=h2X:L)4gTaU:5@C)n&cs&3Rn0>1dnUoc\Z98>sLV4nT*l'&rUW;*b%sKgWNNA[npDe6ghO'foh%#*\[VZYafg^%LmZ<*\1(PAm&E^SG/5qp@_CtFCK6O?BG:JFaX;LQf?(psIddXi*2t@h;pTT&EX_81jNfR>ZBP1AnODp8iHX=QUMs)i-fZ$Uq@3'SJKjWph:%fl0%p"m\Gn.<`d]ut<4!0010(E2eD5/X(Ek.!`37h!l,*);OGMj%)#G&2WPP[>o_"D.;7m>qtA,C5rD>_!s,+65APrd4!,H`YJF8YVIANV*D6esAi=#(B@[YXY_$'EAj1=NKZ7lGBM@Qk`e$X"H\J^.u413>0Xr;[d^-`U].9aod>g"j>J<$c!E@Z5:-,"\TSi2BTUEJ>2A&H[S&VEu<Cn52Zd>F`Qk1CP%W&eO/Q]Iu-Q65363d;1(eD%gfB]T)O3i[k4_<(RPR66Wj'bn6ldVR"j71cu3KiZ]\>0+[_=ie@L&b4Ds&OJmdC$#Bql0K15gGe>BDg4-O,U\S,PE]<"@1?8kYff.)Qb^$1m"s[Fo)'bg!DTAVSATia>8eN9AN?fu?kQ$rG5%M_Ck3>eX,6ako+JJ65BSn:WQ9)l!Y=mM^S^1rTH6[K)k)O<5,N5UtCu9]+U_+^'-.I#3q=951OcR\pOa3ZB`X7K"_iVIY2B+F9VD9/VDgJJKfi5m-e8](Zr,^/TXI/=]QQFQMUgoMkanH]AV,]!e>XfrTJHL`"+CI>^H&Uk`*"-$>)G#'Mkc0`K/jpKBrWTf2LkCGpfjXa9p?d-90![U:CLhm5A9#X#^.*9]M%-%n`p2P+P<4_G>V>SpQA>d9+[hZhR]0@:V4fVQYr(5b6\<AiH7./R<aa#Bbk(dsn@@2o+o"g/dTGeU4'ANRn[d$nSDgu/QY1/'=fdN25-+pRmDGE-^YlFt]`5"incG=s2D]sNlO+]`0DK2mjtWL?5b(<%iH-iY<d&T1W*9\cfaU</1p+T/aZ$bfpPHHNlN[&WdqI4C5eNKAeKe&g__`1+Q>fC!(I+DRn%Sl7qsG.@1k1PunK*hKaNti&$NX>1-L\1&i!6cliafBA2BUhEiaDe9LfMT$fpnSdn>*\!du.5kZuC?p]u>fXr^SVBH](#iZ\Q6nA$L>Pf1iB+RP<o;>,"?C<BI]LE]@Jt>a=;>TB;]E,pS34K^JddZS->:Q1HAcmd!2UN$\T<m8+%]oKXk9RZu>@a$]D!oY!XN913p+NDRd;V!TUPf&7>b+<>6cbi*"KVf7Rl=NZQn;,3O%$GRaH(0ulcDr?mCb!b>>$bq<q.PcnL8Mn%9pN4)r3B^n&cXhi.gLgn^?/Wuj7?"Pa:V<CSMeaSCP_\B&(:VFEoqa#Jn#\.'"O$Rr"1L)t)5)#7HL!W039X0rb2cq7*niQY4D:!EKo'0[RWJ@=JJYOJ%2i.l:1Eg&0Lib*FaEoZa2GqN6W\MRfL)#P^o#HMQfJ>$?N]?4;Z~>endstream
endobj
32 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 644
>>
stream
GasIc9lldX&A@C2m,XCAC^#4eP)l#FRmUiB:418S^tMg?&-\o$k1<[B%)0XtbolE&gUG@10hC;5j0u)j*k1Y.Pl]pLh#W^=_Y-'=fok6G:!ARF9QN?:Elq=_@NM3o@6.o&lm`U96anEPOWYl'iYlPUo:.9FZY%DnWKJEEJYZa&;FV=fjHkZ`F%S2tN29JZQ&SZtQIAeCFXD+JoSGDK0I'G0#omA1=6ed+=i?K"<_8IN)&Iq9f-u4FS)mb8"52L#08C:e)=8348%0/3-FY4I[Tc\T):(R+dHiiR)K(&3fK`lfj1b,'9a,-i9qZ^7I>.(e#6N9iO\-c$JWEs%VG\n_a"i_Tm4ViuojA.,(8*O3gDHkD5X88O*EM^Y2Buklfo!XfMe0Z6hFTbq.0LT*X<G1iHb$8,H%a<C"1EunZaG(h]<3M[RYb9DEo1=JRs9;PcI^uYW-r+%XagpJ0SREK!\jfmp;c+Glfpg`>]>.Xs+qc'f_+)[)qauEf<$hT*k\PX><uam]n4PMH:QirKYmTOqE-rDT&odDruCC+gH]UBH,'c7o8[e*cEDRGKCsjan*"p07`.r3j4o!;^4h'=O20'#B5X:7;n484Z#;&g+,m<\VqKdS%W7l81#H7"r?=N+"1W=U#Q~>endstream
endobj
xref
0 33
0000000000 65535 f
0000000061 00000 n
0000000142 00000 n
0000000249 00000 n
0000000361 00000 n
0000000466 00000 n
0000000543 00000 n
0000000626 00000 n
0000000831 00000 n
0000000946 00000 n
0000001151 00000 n
0000001357 00000 n
0000001563 00000 n
0000001769 00000 n
0000001975 00000 n
0000002181 00000 n
0000002387 00000 n
0000002593 00000 n
0000002799 00000 n
0000003005 00000 n
0000003075 00000 n
0000003367 00000 n
0000003500 00000 n
0000005658 00000 n
0000008150 00000 n
0000010284 00000 n
0000012956 00000 n
0000015567 00000 n
0000018099 00000 n
0000020228 00000 n
0000021944 00000 n
0000023128 00000 n
0000024919 00000 n
trailer
<<
/ID
[<ba0bcbc3b3271fcc4856f7b379add7e5><ba0bcbc3b3271fcc4856f7b379add7e5>]
% ReportLab generated PDF document -- digest (opensource)
/Info 20 0 R
/Root 19 0 R
/Size 33
>>
startxref
25654
%%EOF

View File

@@ -1,455 +1,166 @@
# Les 13 — Docenttekst (Autocue) # Les 14 — Docenttekst (normaal)
**Online les — letterlijk voorleesbaar. Slide-nummers staan tussen `[SLIDE N]` zodat je weet wanneer door te klikken.** **Online les via Teams. Voor uitgebreide voorbereiding + context per blok.**
**Voor letterlijk voorleesbaar script: zie `Les14-Docenttekst-Autocue.md`.**
**Lesvorm:** online, demo-driven. Cursor + browser side-by-side delen.
**Duur:** 180 minuten met 1 pauze van 15 minuten.
> **Voor de les klaarzetten:**
> - Cursor open in lege werkmap
> - Browser tabs open: github.com, vercel.com, pokeapi.co
> - Terminal klaar
> - Cursor Pro/Business actief (voor Background Agent)
> - GitHub + Vercel ingelogd
> - Webcam check + scherm delen ingericht
--- ---
## BLOK 1 — Welkom + Terugblik (10 min) ## Lesvorm en flow
`[SLIDE 1]` | Onderdeel | Indeling |
|-----------|----------|
| Theorie + demo | ±1 uur klassikaal (live demo via screenshare) |
| Pauze | 15 minuten |
| Nu jullie | ±1,5 uur zelf doen met begeleiding via chat |
Welkom bij Les 13. Vandaag is een belangrijke les, want we verlaten voor het eerst je laptop. **Setup tijdens de les:**
- Monitor 1 (privé): autocue-document fullscreen
- Monitor 2 (gedeeld in Teams): Cursor + browser + terminal + slides naast elkaar
Tot nu toe draaiden al jullie projecten op `localhost:3000`. Dat is prima om te leren, maar in de praktijk wil je dat anderen je werk kunnen zien. Klanten. Collega's. Werkgevers. Vandaag zetten we die stap. Tip voor de online-les: zet je Teams-deelvenster zo in dat slides en code allebei zichtbaar zijn. Cursor + terminal samen, slides klein in een hoek.
We gaan drie dingen leren die je voor elk professioneel project nodig hebt. Eén: data ophalen van externe websites en APIs. Twee: je code automatisch online zetten via Vercel. Drie: efficiënt werken met Cursor agents.
Aan het einde van deze les heb je een live URL die je aan iedereen kunt delen.
`[SLIDE 2]`
Even kort terug. In de afgelopen lessen hebben we gewerkt aan AI-features. Les 11 was de Vercel AI SDK, Les 12 was Tool Calling. Allemaal mooi, maar steeds lokaal.
Vandaag stappen we even uit dat AI-pad. Geen LLM's. Geen API keys voor AI. We gaan terug naar de basis: hoe ziet een professionele Next.js-workflow eruit van localhost tot productie?
We gebruiken een eenvoudige Pokédex als demo-app. Waarom Pokémon? Omdat de PokéAPI gratis is, geen API key vraagt, en je krijgt direct visuele resultaten. Perfect om je te focussen op de workflow, niet op de data.
`[SLIDE 3]`
Dit is de planning voor vandaag. Honderdtachtig minuten.
We beginnen met theorie. Drie blokken van een kwartier: externe APIs in Next.js, Vercel met preview-omgevingen, en Cursor agents.
Dan vier live demo's. In demo één bouwen we de Pokédex met Composer. In demo twee deployen we naar Vercel en zetten we environment variables in. Na de pauze: demo drie met de Background Agent om een feature parallel te bouwen. Demo vier: GitHub Actions om CI in te richten.
Na elke demo bouw je het zelf na in je eigen repo. Vragen tussendoor altijd welkom — typ ze in de chat.
--- ---
## BLOK 2 — Theorie: Externe APIs (15 min) ## Voorbereiding — week vooraf
`[SLIDE 4]` **Accounts checken (studenten allemaal):**
- GitHub-account met `gh` CLI ingelogd (`gh auth login`)
- Vercel-account met CLI: `npm i -g vercel && vercel login`
- Cursor geïnstalleerd, Pro of Business plan actief, ingelogd
Wat is een externe API. In simpele woorden: andere mensen of bedrijven hebben data of functionaliteit, en die stellen ze beschikbaar via HTTP. Jij doet een fetch-request, krijgt JSON terug, en kunt daar dingen mee doen. **Communicatie:** stuur een Slack-bericht een dag vooraf met deze checklist. Als iemand morgen vastloopt op de installatie ben je een kwartier kwijt.
Een paar voorbeelden van APIs die je in projecten zult tegenkomen.
PokéAPI — onze demo van vandaag. Pokemon-data, geen key nodig.
Open-Meteo — gratis weerservice, ook geen key.
GitHub API — informatie over repositories, gebruikers, issues.
Tavily — web search. Die heeft wél een API key nodig.
Stripe — betalingen. Twee keys: een publishable en een secret.
Voor APIs met een key onthoud je één regel: nooit, ooit, in je code committen. Altijd in environment variables. Hier komen we straks bij Vercel uitgebreid op terug.
`[SLIDE 5]`
Dit is misschien wel de belangrijkste slide van vandaag. Drie manieren om een API te fetchen in Next.js. Static, server, of client. Welke je kiest hangt af van je use case.
Eerste optie: **static**. De fetch gebeurt tijdens `next build`. Eén keer, en daarna is het HTML. Snel, want geen server-werk per request. Goed voor data die zelden verandert: een blog-post, een marketing-pagina, een lijst van producten.
Tweede optie: **server, oftewel dynamic**. De fetch gebeurt bij elke request opnieuw. Altijd vers, maar trager. Goed voor data die per gebruiker anders is, of die altijd up-to-date moet zijn.
Derde optie: **ISR**, Incremental Static Regeneration. Een mix van de twee. Wordt gecached zoals static, maar elke X seconden mag Next.js de cache vervangen. Het beste van beide werelden. Voor onze Pokédex de juiste keuze voor de detail-pagina's: snel, maar elk uur kijkt Next.js of de PokéAPI iets nieuws heeft.
Vierde optie: **client-side fetching**, met useEffect. Dit doe je alleen als de data live moet, of als je user-interactie nodig hebt. Belangrijk: bij client-side staat je API key in de browser. Dus alleen voor publieke APIs of voor data die geen geheim is.
Kijk naar het code-blokje onderaan. Het verschil tussen die drie server-modes is letterlijk één optie in je fetch-call. Verder verandert je code helemaal niet. Dat is de kracht van Next.js: je kiest het render-gedrag per fetch.
Vandaag gebruiken we static voor de Pokédex-lijst en ISR voor de detail-pages.
--- ---
## BLOK 3 — Theorie: Vercel + Cursor + GitHub (15 min) ## Voorbereiding — dag van de les
`[SLIDE 6]` **30 minuten vooraf:**
- Cursor open in een nieuwe lege folder voor de demo
- Browser tabs: github.com (logged in), vercel.com/dashboard, vercel.com/new
- Twee terminals klaar (één in de demo-folder, één voor ad-hoc commando's)
- Slides in een aparte fullscreen of klein in een hoek
- Autocue-document op tweede monitor
Korte sidetrack — straks komen we terug op Vercel. Even Cursor: twee soorten agents. **Backup scenario:**
- Een eerder voltooide demo-folder klaar voor het geval live demo vastloopt
- Screenshots van Vercel preview-URL's voor het geval Vercel traag deploy't
- Pre-gemaakte `.cursor/rules/general.mdc` en `AGENTS.md` om snel te kunnen plakken
Composer is de agent die je al kent. Cmd+I openen, prompt typen, hij maakt aanpassingen in je editor. Synchroon. Jij wacht, hij werkt, jij kijkt, je accepteert de diff. Pair programming. **Copy-paste-klaar materiaal:**
- Repo-naam (`mijn-portfolio` of vergelijkbaar)
Background Agent is anders. Die draait in Cursor's cloud. Je geeft een opdracht, hij gaat aan de slag, hij maakt een branch, hij pusht naar GitHub, hij opent een Pull Request. Allemaal terwijl jij iets anders doet. Asynchroon dus. - Twee feature-prompts (zie autocue Bijlage C)
- `.cursor/rules/general.mdc` inhoud (zie autocue Bijlage A)
Het verschil komt straks duidelijk in de demo. Voor nu: onthoud dat je twee modes hebt. Eén voor naast je, één voor namens je. - `AGENTS.md` inhoud (zie autocue Bijlage B)
`[SLIDE 7]`
Nu Vercel. Dit is het hart van de les. Drie omgevingen, drie scopes — en het is cruciaal dat je dit begrijpt.
Productie is de live versie. Wordt gedeployed elke keer dat je naar de `main` branch pusht. URL is `je-app.vercel.app`. Dit ziet iedereen.
Preview is wat je krijgt voor élke andere branch. Push naar `feature/search` en je krijgt automatisch een URL terug. Iets als `je-app-git-feature-search-jouwnaam.vercel.app`. Ook publiek bereikbaar, maar alleen mensen met de link.
Development is je lokale `pnpm dev` op localhost. Alleen jij ziet dat.
Nu de belangrijke vraag: hoe ga je daar mee om als je verschillende databases, of verschillende API keys hebt per omgeving? Want dat wil je. Je wilt absoluut niet dat een experimentele preview-branch in je productie-database gaat schrijven.
Bekijk de tabel met environment variables. In Vercel ga je naar Project, Settings, Environment Variables. En daar zet je per variabele drie keer een waarde. Voor productie. Voor preview. En voor development.
Een paar voorbeelden. Je `DATABASE_URL` is voor productie je echte database. Voor preview een staging-database. Voor development je lokale Postgres. Drie totaal verschillende waarden, één variabele-naam.
Je `OPENAI_API_KEY` is in productie de echte key met hoge limit. In preview een test-key zodat experimenten je niet failliet maken. In development je persoonlijke key.
Voor Stripe heel concreet: productie is `sk_live_...`, preview en development zijn allebei `sk_test_...`. Een experiment dat per ongeluk een betaling doet komt nooit op een echte creditcard.
Onderaan staat één killer-combo: Cursor Background Agent maakt een Pull Request, Vercel ziet die PR en bouwt automatisch een preview URL, die URL komt automatisch in een comment op de PR. Reviewer klikt, opent in de browser, test live. Geen lokale setup nodig. Dit is voor mij persoonlijk een van de redenen dat ik altijd met Vercel werk.
`[SLIDE 8]`
GitHub Actions, snel. Waarom: voordat je iets merget, weet je zeker dat de code lint en bouwt op een schone machine. Niet "het werkt op mijn laptop", maar "het werkt op een verse ubuntu". Belangrijk omdat Background Agents soms lint-errors maken — CI vangt dat op.
Kijk naar het code-blok. Een minimale workflow. Bij elke pull request en bij elke push naar main: checkout, pnpm install, pnpm lint, pnpm build. Duurt ongeveer twee minuten op een kleine app.
In demo vier gaan we dit bouwen.
--- ---
## BLOK 4Wat we vandaag bouwen (5 min) ## Per blokwat je extra wilt zeggen
`[SLIDE 9]` ### Blok 1 — Welkom + Vandaag (slides 12)
**Doel:** verwachtingen zetten — het is een live demo van begin tot eind.
Dit is wat we vandaag bouwen. Een Pokédex. Maak duidelijk dat het anders is dan eerdere lessen: je bouwt een hele cyclus voor hun ogen op. Vraag tussendoor of het tempo klopt — online is het lastig om non-verbale signalen te lezen. Laat ze hun hand opsteken (Teams reactie) als iets onduidelijk is.
De stack: Next.js 16 met TypeScript en Tailwind. Geen Shadcn vandaag, we houden het simpel. PokéAPI als data-bron, geen API key. Cursor voor de code. GitHub voor de versie-controle. Vercel voor de deploys. ### Blok 2 — Scaffold + Git + Vercel (slides 37)
**Doel:** in 15 minuten van leeg naar live productie-URL.
Vier features die we bouwen. Een lijst met de eerste 151 Pokémon. Detail-pages per Pokémon. Een zoek-functie. Type-filter chips. **Slide 3 — Scaffold:** ga niet door de `create-next-app` prompts heen alsof het triviaal is. Studenten hebben dit vaak nog nooit gedaan. Beschrijf elke keuze kort:
- TypeScript: ja, want strict typing
- ESLint: ja, standaard
- Tailwind: ja, we gebruiken het hele jaar
- src/ dir: ja, het houdt root schoon
- App Router: ja, dat is de moderne flow
- Turbopack: ja als default, geeft betere DX
De eerste twee bouwen we met Composer, op de `main` branch. De andere twee met de Background Agent op feature-branches. **Slide 4 — Git:** veelgemaakte fout is `git push` zonder eerst remote add. Laat zien wat de foutmelding eruitziet en hoe je het oplost. Wijs ook expliciet op `.gitignore` — je toont dat `node_modules` en `.env.local` NIET in de eerste commit zitten.
Aan het einde heb je: een productie-URL, twee preview-URL's, drie Pull Requests, een werkende CI-pipeline, en een geverifieerd begrip van static versus ISR. **Slide 5 — Vercel:** wacht écht de 45 seconden af. Gebruik die tijd om uit te leggen wat Vercel doet onder de motorkap (clone, install, build, deploy). Het wachten levert spanning op — gebruik het didactisch.
**Slide 6/7 — URLs en env vars:** dit is nog theorie, want we hebben nog geen secrets. Belofte aan studenten: "zo direct ga ik dit ook live gebruiken met Vercel CLI."
### Blok 3 — Vercel CLI (slides 810)
**Doel:** student begrijpt het verschil tussen dashboard en CLI, en ziet één concrete workflow (`link` + `env pull`).
Belangrijk: maak duidelijk dat we GEEN env vars echt hebben — we tonen de syntax. Als je het wilt demonstreren met een echte var, voeg er één toe (`MIJN_NAAM=Tim`) en pull die naar lokaal, om te laten zien dat de file gevuld wordt.
`vercel env pull` is een eye-opener voor veel studenten. Het scheelt je manuelle gepruts elke keer als productie-vars wijzigen.
### Blok 4 — Cursor in detail (slides 1117)
**Doel:** student kent de hoofdfeatures en weet wanneer welke te gebruiken.
Deze 7 slides zijn de inhoudelijke kern. Niet door doorheen jakkeren. Geef per modus een micro-demo:
- **Slide 12 (modi):** open de chat met Cmd+L, sluit met Esc. Druk Cmd+K op een regel in code en typ "rename this variable". Toggle Plan-mode in de chat — laat zien hoe de UI verandert.
- **Slide 13 (@-mentions):** typ `@` in chat, laat het autocomplete-menu zien. Probeer `@web:Next.js 16 release notes` voor een live web-search demo.
- **Slide 14 (Cursor Rules):** maak live een `.cursor/rules/general.mdc` aan. Zet er één duidelijke regel in (bv. "altijd functional components, nooit class components"). Test daarna in een nieuwe chat dat de AI die regel volgt.
- **Slide 15 (AGENTS.md):** plak je voorbereide voorbeeld. Leg uit waarom ook Claude Code en Copilot dit lezen.
- **Slide 16 (Background agents):** demo de feature-2-prompt al hier even kort, om visueel te maken hoe een agent in een aparte tab opent.
Vraag aan het eind van dit blok: "welke modus zou je gebruiken voor X?" — twee of drie scenario's. Kort, voor engagement.
### Blok 5 — Live feature-workflow (slides 1822)
**Doel:** de hele cyclus laten zien — niet alleen Cursor, niet alleen Vercel, maar de combo.
**Slide 19 — Demo 1:** plan + apply. Maak het visueel: open de chat, plak de feature-1-prompt, wacht tot het plan verschijnt, lees het door, klik Apply. Dan met Cmd+K één tweak. Commit.
**Slide 20 — Push + preview:** push, switch naar browser tab Vercel, wacht op build, open preview-URL. Gebruik de wachttijd om te zeggen: "kijk, vanaf hier ben je niet meer alleen — je kunt deze URL nu delen met collega's of klanten zonder dat zij iets hoeven te installeren."
**Slide 21 — Demo 2 background agent:** dit is de WOW-moment van de les. Plak de feature-2-prompt, klik "open in background", en demonstreer LETTERLIJK dat je in main editor blijft werken. Doe een Cmd+K in een ander bestand. Dat illustreert het parallel-werken.
**Slide 22 — Merge:** open beide PR's op github.com. Toon de Vercel-bot comment. Merge één naar `main`. Switch terug naar Vercel dashboard om de productie-deploy te zien starten.
### Blok 6 — Recap + Nu jullie (slides 2325)
**Doel:** overgang naar zelf-doen.
Voor de pauze: vraag of er nog iets onduidelijk was uit de demo. In een online setting moet je actief uitlokken — studenten typen vaak niet uit zichzelf.
Na de pauze: laat een gedeelde chat open zodat studenten kunnen tonen waar ze vastlopen. Wees actief in de chat. Loop om de 15 minuten een check-in: "hoe ver iedereen?"
--- ---
## BLOK 5 — LIVE DEMO 1: Pokédex met Composer (30 min) ## Veelvoorkomende vragen + antwoorden
`[SLIDE 10]` **"Waarom Cursor en niet gewoon VS Code met GitHub Copilot?"**
Beide werken. Cursor's voordeel: tighter integratie van plan/build/agent flows en background agents. VS Code + Copilot is solid voor inline-completion. Voor agent-workflows is Cursor (of Claude Code) doorgaans soepeler. Test allebei en kies.
Tijd voor de eerste demo. Open Cursor, lege werkmap, terminal openen. **"Wat kost Cursor?"**
Hobby tier is gratis met limits. Pro is rond €20/maand. Voor de cursus genoeg.
Eerste commando, typ mee: **"Kan ik mijn API-key in Cursor gebruiken in plaats van hun subscription?"**
Ja — Settings → Models. Voeg je OpenAI/Anthropic key toe. Goedkoper bij weinig gebruik, duurder bij veel.
```bash **"Wat als ik een typo maak in `.cursor/rules/`?"**
pnpm create next-app pokedex --typescript --tailwind --app Cursor parseert de YAML front-matter, gooit een waarschuwing bij syntax errors. Open de file en kijk linksonder voor de error.
```
Beantwoord de vragen: geen src directory, gebruik Turbopack, default alias is prima. **"Zal Cursor mijn code naar OpenAI sturen?"**
Hangt af van je settings. Cursor heeft "Privacy mode" — code wordt niet bewaard door OpenAI of Anthropic. Check Settings → Privacy.
Stap in de map: `cd pokedex` en open in Cursor met `cursor .` of File → Open Folder. **"Hoe kies ik tussen Plan en Build?"**
Plan voor onbekende terrein of grote features. Build als je precies weet wat je wilt en het kleiner is. Bij twijfel: Plan, dan klik je Apply als het bevalt.
Nu het magische moment. Druk op Cmd+I — of Ctrl+I op Windows — voor Composer. Type deze prompt letterlijk over: **"Werkt Vercel CLI ook voor andere frameworks?"**
Ja — Astro, SvelteKit, Vue, Remix, alles wat Vercel ondersteunt. De link- en env-flow is identiek.
> Bouw een lijst-pagina met de eerste 151 Pokémon van PokéAPI. URL is `https://pokeapi.co/api/v2/pokemon?limit=151`. Gebruik Tailwind cards in een grid met de naam en sprite van elke Pokémon. De sprite staat op `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{id}.png` waar {id} het index-nummer is. Gebruik een static fetch — dus géén revalidate optie meegeven. **"Wat als ik géén Vercel wil gebruiken?"**
Alternatieven: Netlify (vergelijkbare workflow), Cloudflare Pages (goedkoper bij schaal), zelf hosten op een Hetzner box met Coolify (zoals Tim doet). De Cursor-workflow blijft hetzelfde — alleen de deploy-step verandert.
Composer gaat aan de slag. Je ziet diffs verschijnen in `app/page.tsx`. Lees ze met me mee. Accepteer alle wijzigingen.
Start lokaal:
```bash
pnpm dev
```
Open `localhost:3000`. Daar zou nu een grid van 151 Pokémon moeten staan. Werkt het? Mooi.
Tweede prompt in Composer:
> Voeg een detail-pagina toe op `/pokemon/[id]`. Fetch van `https://pokeapi.co/api/v2/pokemon/{id}`. Toon de official artwork — die staat in `sprites.other["official-artwork"].front_default`. Toon ook de types als chips. Gebruik ISR met `revalidate: 3600`. Zorg dat de cards op de homepage linken naar `/pokemon/[id]`.
Wacht op de diffs. Accepteer ze. Open een random Pokémon. Werkt? Goed.
Belangrijke uitleg terwijl studenten kijken:
Kijk naar het verschil tussen `app/page.tsx` en `app/pokemon/[id]/page.tsx`. De homepage gebruikt gewoon `fetch(URL)`. De detail-pagina gebruikt `fetch(URL, { next: { revalidate: 3600 } })`. Dat is het hele verschil tussen static en ISR. Eén optie.
Open de browser Network tab — F12. Klik door wat detail-pages. Je ziet dat er geen calls vanuit de browser naar PokéAPI gaan. Alles is server-side. Dat betekent: als PokéAPI morgen omvalt, blijft jouw site werken tot de revalidate triggert.
Optioneel — en als de tijd het toelaat — voeg een derde route toe met `cache: "no-store"`. Bijvoorbeeld een "random pokemon" pagina. Zo zien studenten alle drie de modes naast elkaar.
Commit alles in de terminal:
```bash
git add . && git commit -m "Initial Pokédex met Composer"
```
--- ---
## BLOK 6 — LIVE DEMO 2: Vercel deploy + env vars (20 min) ## Foutmodi tijdens de les
`[SLIDE 11]` | Probleem | Snelle fix |
Tijd om dit live te zetten.
Open github.com in een nieuwe tab. Klik "New repository". Naam `pokedex`. Public. Geen README, geen gitignore, geen license — die hebben we al lokaal.
Klik Create. GitHub laat een URL zien. Kopieer die.
Terug in de terminal:
```bash
git branch -M main
git remote add origin https://github.com/JOUWNAAM/pokedex.git
git push -u origin main
```
Refresh github.com. Daar staat je code.
Open vercel.com. Log in met GitHub. Klik "Add New" en kies "Project". Vercel toont een lijst met je repositories — kies `pokedex`. Klik "Deploy".
Wacht zestig tot negentig seconden. Je ziet de build logs voorbij komen. Aan het einde: live URL. Klik. Daar staat je Pokédex op het internet. Live.
Nu het belangrijkste stuk: environment variables.
Open `app/page.tsx` lokaal in Cursor. Voeg ergens bovenaan in de return een regel toe:
```tsx
<p className="mb-4 text-sm text-zinc-500">
Omgeving: {process.env.NEXT_PUBLIC_APP_ENV ?? "onbekend"}
</p>
```
Push naar main:
```bash
git add . && git commit -m "Toon huidige omgeving"
git push
```
Vercel deployt automatisch opnieuw. Maar de regel toont "onbekend" — omdat we de variabele nog niet hebben ingesteld.
Ga in Vercel naar je project → Settings → Environment Variables. Klik "Add new".
Variable name: `NEXT_PUBLIC_APP_ENV`. Value: `production`. **Belangrijk:** vink ALLEEN Production aan. Klik Save.
Tweede keer toevoegen. Zelfde naam, value `preview`, en vink ALLEEN Preview aan. Save.
Ga naar de Deployments tab en redeploy de laatste deploy. Of push een kleine wijziging om automatisch te triggeren. Wacht. Open productie-URL — staat nu "production".
Nu een feature-branch. Terug in Cursor terminal:
```bash
git checkout -b chore/banner
# kleine wijziging — bv een spatie ergens
git add . && git commit -m "Banner aanpassing"
git push -u origin chore/banner
```
Open de Vercel deployments tab. Daar verschijnt een nieuwe deployment voor `chore/banner`. Klik de preview URL. Daar zie je "preview" staan, niet "production". Twee URL's, twee waarden, één variabele.
Voor `.env.local` zou je `NEXT_PUBLIC_APP_ENV=development` zetten — dat doen ze in het huiswerk.
Belangrijke regel: voor secrets — echte API keys, database passwords — vink je in Vercel **alleen** Production aan. In Preview gebruik je een aparte test-key. Dit voorkomt dat experimenten je productie-data raken.
---
## BLOK 7 — Pauze (15 min)
`[SLIDE 12]`
Vijftien minuten pauze. Ik mute mijn microfoon en zet de camera uit. Tot zo.
---
## BLOK 8 — LIVE DEMO 3: Background Agent + preview (25 min)
`[SLIDE 13]`
Welkom terug. Nu het stuk wat veel mensen voor het eerst zien: de Cursor Background Agent.
Open Cursor. Druk Cmd+Shift+P voor het command palette. Type "Background Agent". Eerste keer: Cursor vraagt of je je repository wilt verbinden aan de cloud. Accepteer in de browser, autoriseer GitHub.
Nu kun je een Background Agent starten. Type deze prompt — letterlijk over:
> Maak een nieuwe branch genaamd `feature/search`. Voeg bovenaan de homepage een zoekbalk toe. Wanneer iemand typt, filter dan de Pokémon-lijst op naam. Gebruik client-side state met useState. Push de branch naar GitHub en open een Pull Request met titel "Add search bar" en een korte beschrijving in de body.
Klik start. De agent gaat aan de slag.
Wat je nu kunt doen — en dit is het hele punt: iets anders doen. Een tweede Background Agent starten met een andere taak.
Nieuwe Background Agent, prompt:
> Maak branch `feature/type-filter`. Op de homepage, naast de zoekbalk: chips om te filteren op type. Type-data moet je per Pokémon fetchen via de detail-endpoint. Push de branch en open een PR.
Beide agents lopen nu parallel. Klik op de eerste in het Background panel — je ziet wat hij doet. Bij vragen kan hij je stoppen voor input.
Wacht drie tot vijf minuten. Pull-Requests verschijnen op GitHub.
Open de eerste PR — "Add search bar". Onderaan zie je een comment van Vercel met de preview URL. Klik. Test de zoekbalk live in de browser. Werkt? Goed.
Open de code-diff in de GitHub UI. Lees mee. De agent heeft toegevoegd: een useState voor de query, een input bovenaan, een filter op de lijst. Logisch.
Als de code goed is, klik "Merge pull request". Push gaat naar `main`. Vercel deployt naar productie. Binnen een minuut staat de zoekbalk live.
Hetzelfde voor de tweede PR. Review, test op preview-URL, merge.
Wat hier net gebeurde: in vijf minuten zijn er twee features autonoom gebouwd door agents, getest op aparte preview-URLs, en gemerged naar productie. Zonder dat ik één regel code zelf heb geschreven.
---
## BLOK 9 — LIVE DEMO 4: GitHub Actions CI (15 min)
`[SLIDE 14]`
Laatste demo. We voegen CI toe. Lint en build moeten slagen voor elke PR.
Open Composer in Cursor, Cmd+I, prompt:
> Voeg een GitHub Actions workflow toe op `.github/workflows/ci.yml`. Bij pull request én bij push naar main: checkout, setup pnpm met `pnpm/action-setup@v4`, setup node 22 met `actions/setup-node@v4` en `cache: pnpm`, install, run lint, run build.
Composer maakt het bestand. Accepteer.
Push de wijziging:
```bash
git checkout -b chore/add-ci
git add . && git commit -m "Add GitHub Actions CI"
git push -u origin chore/add-ci
```
Open de PR op GitHub. Onder de PR zie je de "Checks" tab — de workflow draait. Wacht ongeveer twee minuten. Groene vinkjes — goed.
Nu de demo van een falende CI. Introduceer een typo in de code. Open `app/page.tsx` lokaal, verander een opening tag in iets fouts — bijvoorbeeld `<dvi>` in plaats van `<div>`. Commit en push naar dezelfde branch.
CI draait opnieuw, faalt. Rode X. Open de logs in GitHub — zie de TypeScript error.
Fix met Composer:
> Repareer de TypeScript build error in `app/page.tsx`.
Composer fixt. Commit, push. CI draait. Groen.
Laatste stap: branch protection. Ga naar GitHub repo → Settings → Branches → Add rule. Branch name pattern: `main`. Vink aan: "Require status checks to pass before merging". Selecteer de CI-workflow. Save.
Vanaf nu mag niemand — ook jij niet — naar main mergen zonder groene CI.
Merge de PR. Klaar.
---
## BLOK 10 — Composer vs Background (10 min)
`[SLIDE 15]`
Vandaag hebben we beide gebruikt. Wanneer kies je welke?
Composer is je pair-programmer. Je zit erbij, hij maakt aanpassingen, je accepteert per file. Goed voor: snelle wijzigingen tijdens je werk, complex refactor waar je wilt meekijken, onbekend gebied waar je niet wilt dat een agent vastloopt.
Background Agent is je delegate. Je geeft een welomschreven taak, gaat iets anders doen, krijgt later een PR met preview URL. Goed voor: features met heldere specs, parallel werken aan meerdere features, tijdens een meeting iets voorbereiden.
Mentaal model: Composer is "samen werken". Background Agent is "uitbesteden".
Twee waarschuwingen voor Background Agent. Een: hij kan vastlopen op rare config — debuggen is moeilijker omdat je er niet bij was. Twee: hij verbruikt credits, dus Cursor Pro of Business is wel nodig.
Praktische tip: te vage prompts geven matige PR's. Wees specifiek. Noem de bestanden, beschrijf de UX, geef voorbeelden van wat je wilt. Hoe meer context, hoe beter de output.
---
## BLOK 11 — Lesopdracht + Huiswerk (10 min)
`[SLIDE 16]`
Tijd voor jullie eigen werk. Twee delen.
**Lesopdracht — doe je nu, dertig minuten:**
Een. Eigen Pokédex-repo opzetten met dezelfde stappen als de demo: `pnpm create next-app`, Composer prompt voor de lijst, Composer prompt voor de detail-page.
Twee. Push naar GitHub.
Drie. Verbind met Vercel, deploy. Open de productie-URL en stuur de URL in de chat — ik wil zien dat het werkt.
Vier. Voeg de `NEXT_PUBLIC_APP_ENV` environment variable toe met een aparte waarde voor production en preview. Verifieer dat het werkt op beide URLs.
**Huiswerk — voor volgende les:**
A. Eén feature toevoegen met de Background Agent. Mijn voorstel: zoekbalk, type-filter, favorieten-systeem, of vergelijk-twee-pokemon. Open de PR, krijg de preview URL.
B. GitHub Actions CI toevoegen met lint en build.
C. Branch protection op `main` instellen.
D. Schrijf een `DEPLOY.md` in de root van je repo met: productie-URL, één preview-URL, en een reflectie van ongeveer driehonderd woorden over Composer versus Background Agent. Wanneer werkte welke beter? Geef één voorbeeld waar Background verbluffend goed of juist slecht was.
Bonus voor wie wil: vervang de Pokédex door een andere externe API. Open-Meteo voor weer, GitHub API voor jouw eigen repositories, of een free API naar keuze.
Inleveren via Teams, voor volgende les.
---
## BLOK 12 — Afsluiting (5 min)
`[SLIDE 17]`
Vandaag hebben we veel gedaan.
We hebben externe APIs leren fetchen vanuit Server Components. We hebben drie rendering modes gezien: static voor de lijst, ISR voor detail-pages, en kort genoemd hoe je dynamic of client-side zou doen. Dat is een knop die je per fetch omdraait.
We hebben Cursor Composer gebruikt voor synchrone wijzigingen, en Background Agent voor twee parallel-features. Verschillende tools voor verschillende momenten.
We hebben naar Vercel gedeployed. Productie op main, preview per branch. En het belangrijkste: environment variables per omgeving. Een variabele kan drie totaal verschillende waarden hebben, één per scope.
En tenslotte GitHub Actions CI. Zekerheid dat je code lint en bouwt voor de merge.
Volgende les is les 14: Agents. Nog een stap verder dan tool calling. LLM in een loop met tools, autonoom twintig tot vijftig stappen. We bouwen een research-agent from scratch met `ToolLoopAgent`, `stopWhen` en `prepareStep`. En we bespreken wanneer agents wel of niet de juiste keuze zijn.
Dat was 'm. Bedankt voor jullie aandacht. Vragen?
---
## VEELVOORKOMENDE FOUTEN — voor mezelf
| Probleem | Oplossing |
|----------|-----------| |----------|-----------|
| Vercel deploy faalt op build | Check Build Logs — meestal type-error of missende env var | | `gh repo create` faalt — niet ingelogd | `gh auth login` opnieuw |
| Background Agent doet niks | Repo niet gekoppeld aan Cursor cloud — Cmd+Shift+P → Repository Settings | | Vercel detecteert geen Next.js | Build directory leeg gelaten, niet `out/` typen |
| Preview URL toont oude versie | Hard refresh (Cmd+Shift+R), of Vercel cache invalidate | | Vercel build crasht op env var | Tijdelijk een dummy zetten in dashboard |
| Env var komt niet door | Redeploy na toevoegen var — bestaande build pakt 'm niet automatisch | | Cursor verliest context | Esc, nieuwe chat starten, opnieuw `@`-mention |
| `process.env.NEXT_PUBLIC_*` undefined | Moet beginnen met `NEXT_PUBLIC_` om in browser te werken | | Background agent doet niets | Eerst zien of het Pro/Business-plan vereist |
| GitHub Actions faalt op pnpm | `actions/setup-node` MET `cache: pnpm` — anders geen lockfile-aware install | | `vercel link` koppelt aan verkeerd project | `rm -rf .vercel` en opnieuw |
| Branch protection blokkeert PR | Eerst CI groen zien voor je merge |
| Composer 'kan repo niet vinden' | Cursor verbinden met die specifieke repo via Cmd+Shift+P |
--- ---
## TIMING-CHECK ## Vooruitblik Les 15
| Blok | Cumulatief | Duur | Volgende les: RAG met embeddings. We bouwen een PDF Q&A-app. Studenten moeten daarvoor hun Next.js-app van vandaag live hebben staan, met minstens twee PR's gemerged. Anders missen ze het fundament voor de RAG-stack.
|------|-----------|------|
| 1. Welkom + Terugblik | 010 | 10 min |
| 2. Theorie Externe APIs | 1025 | 15 min |
| 3. Theorie Vercel + GH | 2540 | 15 min |
| 4. Wat we bouwen | 4045 | 5 min |
| 5. Demo 1 — Composer | 4575 | 30 min |
| 6. Demo 2 — Vercel + env | 7595 | 20 min |
| 7. Pauze | 95110 | 15 min |
| 8. Demo 3 — Background Agent | 110135 | 25 min |
| 9. Demo 4 — GitHub Actions | 135150 | 15 min |
| 10. Composer vs Background | 150160 | 10 min |
| 11. Lesopdracht + Huiswerk | 160170 | 10 min |
| 12. Afsluiting | 170175 | 5 min |
| Buffer voor vragen | 175180 | 5 min |
Totaal: 180 minuten. Stuur de avond ervoor een Slack-bericht: "checklist voor Les 15 — productie-URL + 2 merged PR's — laat in chat zien als je vastzit."

View File

@@ -0,0 +1,143 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 8 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
9 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/PageMode /UseNone /Pages 12 0 R /Type /Catalog
>>
endobj
11 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260612063152+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612063152+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 14 Docenttekst) /Trapped /False
>>
endobj
12 0 obj
<<
/Count 4 /Kids [ 5 0 R 6 0 R 7 0 R 9 0 R ] /Type /Pages
>>
endobj
13 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1841
>>
stream
Gb!;d>Bedj&:XAWR&:_?7U^G+hs<0oe!%4Sc,gVPM_p6DQ'U\:4oY,F>tJ.KM'6SS1$0h-c>C>lAekRqH8+q/B)m-cGkj,tB#AitbdC[m^Ot.uMn^oI/*YfS+5%6Y_"OnW)nE>9DHjPf#He;t'98U("tO`d*u*rCl""!S5)_9Q@06nF$/6:6]Yfo6_8NbapdrGl<u!@2$7:C1'cH1TEd7uO"cQf%BE"^I'+XicW9;Nf<Ga;9(<Djt517G&]DXB\)#TLhOX!EfA1]d"O_:Ko+-\7J(0-7@!_%^FV@->PT1,?g'uVT*n=L%g=MRn9V.KQ]3A`VucsGpp%ZF#:'O`0(@bJ^Xd$c7i3T[o(1+pMF\q(*;SH>]f5fmL'Ya;6GD-a-N7Oq.*NEIX)/I3rKof\kSVZg=P/+=WsoU9q;0Sc&qS"nhcIV[_[_UFJ,ZnCpc%@Irc;$]Op=<PgoOA>af70<?8&M*GMd!&mt]j9]Rcg%@-*tpAOea86s/lPc`K_Z1>FN+TA#o3iAm,b)lB=kkm\SfoR^Emk$DVC9q):?_XKBTg>0/-#<F&W;dm0XKMe2)j62&"_W%-kESV;u2:Q;j\[Y>VILBJELT0Lj:03A5?LJp3`XrR2,2oD/K[Z/(9m%l0:!($<PY7Rlh?)A]`Fi]4Eo@mnq?M<P'&$kbu_c*2dKVC:2^f3uX>?M.94f`"Lh`J):Jcuo\eFqD4p""PK2&Z>U#k/-[sdigDl`!UpVE,X,'AFXpTI0rO1=ESr.d[bn@O=d<rQD;&D`s.,i3Sn7GYFlMdKQ`3\:"/"Eo0qmJI@L+YgB793q@N#@GpMsff+So&8"FahjY7OVSun'OK^C8h:$&)nL1USN<?]p,a((=V)rM1G%MO05Lj<DuaD13me/&Je$c`'HiS"u`MOJ`%&:)b(FP*e^U86OF0J1eO7/_r\(KDPWq\>?YH=@1@kL?S1&dK*UNZNn_le86Z\*;]RNfV0%1h2MKAU=)IK9n'sJd"%5%!=[JJi:.J%n[(mn>c)KME=kGRS/K9'\$pYH"FSUK373c2Y:jAh?I;,6GR`AB1^U7CY]*sQn)F5cu`Q+A?ip1?Ihm4D?W55l$h9Q=j2u86!TlNn_*NZ3e>!)b?5O#)onu(X--!ej!A8M1+92lpkIGUUqR`8:1,)toi#l8lT!jiX2<05Tu(^DA5>plDaa7k4WX)?=f1HMO+$9j@'k=SmOdCXHBUr7b3S!nc!#1]gEPN^r/lj4lgXP7)>eqQN,g:UP`D5c[,n=s":V8LRg&;6&K(GmI=Fr9GKShq9r1CO.Nkb?2+82>27tRVW#=C@-';\?_FejK\@</2]ACEeFLnou4q!m-]R$E[XNj8Yao'BaOGOQ_kgKBS06t2M&mK'*2HLN_[S16j3Pbr:\3u4[ZJ\cL+G`GV">WuQZsZVQ81M%$NbAO&dTo0rajD0V7$m3lU%L&t]V%nSa)8hi3e=n[5I,Z$EfP1Lm5S=$s+.dV6>.m=EU<mY);B/o/GaNZh5K.I_?iWT=O8jK##PV<rg<18`V+!2;lWQoE80r)Jb;M$77q2V-0b35*DH0e;u>A0q-Pc27pBWFHe4:+'q`onQ&9<_:T[`1qP^;iRM[I]PKW9X:%b'D#qtI?`AIhlY3(!,[/Y8PoU2*ae',1iIJ)*f-cO3CS.Fgti6ZB;Y^sVJ4PhoNi5or@@[]>6bua2NnsaF3BBJ9hR=O$Yi%&87llD%,q`1%(^ZSpM]71*^c;G6,B]$/Y+E_upND(.nUJIWG46huNE\CqHGp1ZDU`9McF6B(H^\(<HN>c%j_n33P9'hjP>H4P%k;5K#\>D&emt[qAH!tX[i8S*;LR'I~>endstream
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2280
>>
stream
Gb!;d?#SK;&q/*0)"aZIN?sM4VZY]Z4*B!dNmYkXBcd`t.rlo;[G5?8V%rmZh_V(B@S2[9"E#1!+Ta9?B?m$83O8aonIq/e7L@JqLlQrn-jXNDTqL0*dcc;.7kajh&M.#WqQ9`gGS8@'YD9f7(Z41o]6p/D?`\EaQTKl.mtVUcX%uo-59+cleERIsSWf.=-QW#hga$!+"re3A)6WZaD/*;\K>C2!h9b@MEknmRfc8EH>.l$q#$Y_;olnaE_<7:p72`GVB?o[^X"8\[/7Lt\`j_BQc2W4%p`;)Yb@nneVL5GgC5pQ(RSqA/EHg=;]bU[(B1/FSWkMiJbJsK4R&KOJ`m#q[9$KLtH-#N:S`R^X.k7j[A]Iq^3'X"?j\2(C&kC-!WuW.t[eB7Ug[iV9'irp%]7b2T-o0$TC[l7Ve9oQMk2`]VSF)))?M71k>J0l3i#gnQ`hdn(<99^@F/)](M=R0NV9J8=0X!:,+1D2Q7eHQ'a+`&H:F*g_:0l<uQ-a[6@5npKA"0I@r59K?l7njV/SA!?qIVnqVUmICabnC5q4QB1oq^S$=tnEYPqS4AC$(Qm<T:T0YF/AK>uWMKko6Cp9Q)!Fp$X;^qnq:D(&#Po>hbYsi]T8k5*!NFW"SMd@=Pt:eEaF]?LKOO6.kIEq`QZQkCVQ`^Ktk==T(*Z6a8VP_"ra@)B1W5lS'=*6@$.Ni_ScD[*ZhA4\qt;C2Vk)mVppXTX*P8F+42%AH7L^9kRp2:g*E0RlOZg/,H2\/H,4POW,<^:''5G_aiptYq=b>D1BLA>nR+QnZq.m7iQ#UTq[t,(?b`Dn*qe8()`cdJnQ:%D]/L<EM[[mo2Jrbc4at4\%Z[%G8PcEjuoQ?'CMqrn'S6d/c_/9>Da"q0b#OOQA^GOCgt]Z?RDRjcM"ZaIdDrG0?p,o@>aIs;lg[.@EFA@/oB!+;)^!Q)SV:B@TeC/0*cbt;Fbfg;I9IbXO3?qA9i%==nPb*]QLXh36)B(ja(\3[*nUVhm71IKi5e)o'dNu?Q\N1o]E^"hAELiElqKQ?M!h_;?e0tC9b)a"E](uk.U2lV/]TtIhephH=@:,^XZZS>\V,6RleLL_'*X_G.QS"c/<0[UI<j'BIm[ObYtekHV+pG\&F&O.t[L,(u+"5)[2`b(jREh;!2o\l;g[q=[\ZMf#;c?$KVc>f!MAf9pMT`moKsF-=d?m<lE8TXiNUJ$siZQB'VAjYVG2.)!b["nQ&Q33WS93(@dFe"id-R*mHHg1Ek7#W)kT:h`4*P!Q$QRBl=%H"Q=UrN3./Bf$i-s?-4"^FQu_:e.J./oooRH#b^(O.&DtQK0f8-r#6V80g+sor9?W'V/NmtgJo"RimQ_Xoq*-XV9K(tC)J]0a2_>Vk@Jl,<JS)8S,[@DT4cP+&j!)+/00A-o9;6lCZMdBqZY?]iY(9.:$I9tEF6:*@R(V-;rZ1(d?Q*!/cJVYr2E(9ZE[&&1`$D?rZC`"'uh>HZ97YR?XAE2&&03h_o(p!7U'T0".E?$C1?E&]/1e&orZ@>iK9KiCgp="QF+_G[BK%i-ok.D3LC/9M+pqhY6:%YMMdj`X0!$nnQ(3X0XB?g9D=ZMFtCd3M!n7%Eo3VKoF9:`oF$.@NK/,VWdaUCl_)m8`\m?[\$;o&o(\Ae%mRLqhB1[Jb`b,kMH6Tl+34)`AR)$)D>iS.o9%*2\6C-YiTcRo$P4N,&!/>NnF_s@=1MUN]6_99M1Mfk]g/1+NZR:%8K]0A-7sOJ<EIZo!2jaEOiN8VV1rOk.mpn4Nj4e@#RT'Z/JHjMCp$YabK)Zd7mI7&aI"1j(YZetb6uGXQS&%?)jE8&XMW?BW-5/G(.=9uQbG!YIS#Q%a.fe8&J)Z5(q.0tci3I-Z3%$^(T)ca:gI41a&_gLdRVfST<ZQr0*T?^Lg<V2GrHJ?#L"tt#GT_,Rn2D7NP<S;MXNENGOM=Z0_>[YAf2Cl-:Onj2m"dmF\pq"]PDq>!'%[]gQ!/BD<?ZiS&o+fP=<h^A@+T&red*1Sh67Qb4l?=S5rQ:\-<^_6e0tJqN0%C%WY-m%-ru8=dCGW(AY@/BIdM$>(GPZYBJ\c@`"Ye/9k/F4eU1`hJ$D5jTN]qA[$pN,trO7-0jZ$mCYjkoop1Zd'8c1W".Cm4[6\d':r;u`feEn<K5L/\J@],jEK1U:G"Urhcgk:\\-Uq$].4caNsr<[Rq=VN\$YA@CjLdLM!gmgS6(EiI1aokK?(B:J]mJGGLE`BG*.QZ`INanEB#%UlM728I\V4G*PHKrXWCoWfm~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2455
>>
stream
GauHLgN)&g&q'FT9^*jF6G`kMQ!P)-;B3`7N\!!VW>&\T^_0m]JRrdDO8/CGB:$)`PftVfCtUWK?uOjW4[&;6N?@&,7N'Uc&\\"j-U49OK`&")V9o`XO'_eVb$RBd&%qmeE:22Je+\U1Fr01X)[$'+>Sr`71lo&N[<XY`=T9rcC93ZD.2EZ;OsWdo9&u_t0eH*3=5I^2c"Q_o0Dl,@`tiT;T3C"lm]YJ-[e`<+OjkZJ"jLhbm;fgu2Fs)YYs8%,>@oL/=jY#&Hq4YqX#cPl-$,,rP80A1'NZfR8]jG-95W!10BrmckYtG00jNp_F/Hsi0QA+59"#M0-u#Ym/!XMRY91>?fLO`g48ajTs'I$AaE[;l?m1q?G]'JM:7Ds,`^);ObIT*A49\2b-'Y9HUXUcZ-2B_Gkkg#&lP"Hn4"VM*@uOY1W@na+=CVdKEuQQ,@j^_6MQ/;53A@h)TO95>S3^Ib]IUud/8CHAL1t;1O6LOX7mYpHc(1\N-!-66Zj(<BArIjOS/U`uX-og!5c!0blndc@if>Mnf1`!Ma'nUHapH=4J.."f+tlkl@9Tq[bf,c9!8QuJh0s_>#]4qCm=0&l_*K7UamTZta6j1bDNDHUYh_;E!9[\sV0s&tra>6V/j$f!oDKj+dJ\Bs*Y"+TN%Q2[g]0TSr;&*B3(Y"`BF72M!;0n=s)c]m0su$<Y04n]X.iAVXcF<-E@tdRPXaS$#/bGSHC/UaoaL2C$kY@N*["OL#dledBOm8/=am8;hTCd'I(Qo5-S=i4.brNE^@gXoBLa6:=;K5=)[HcHhiSI8q@Z+U+QSkiL=B0L09=K^0PPrT,K#LJ%`L?8cnuh[;ii<jb![SGCKX58TXJ6SFB=ik'E)=aV$o"pjo6fK[Zak/%sG>pV52mGZ927@8id3.<XT6(#[=,>75Q;c]B<9Wms[KaqY_WYFb3OPK"s=rl@*)UH'G.!Lt#kK?p)H[MsTpl"V6*HE%r'\bMq_n*[*erhk1JgNB9:qruj+3[?eZ]1Y\]E2o:8=B0o[7(=T)),7Z#nM9.\:,-,O34G+W6Zma,2lY#80846TI<J;iA_:.rJF!^.KbJijpCRakf"3!m8;gr.snn]e!^S<W/?D`ZE!;Z?ep$Ls!GWEe5>_?H%'TVr"22=]4mo%1Ih8k7O-^R*HA7T'V0"!qJUW^%p?CJFQAe.a$X\nte.BjAin#0eN;mJReHmbXF.86;Q,SH-A@mI=T=NB\X81SAib[kiDQDUWJgrflJ8kf=D"-O$lenc3Sp4fdr>Ptu^E4">a2PDY`U;2tX#]2^d&tPYd1,7VC,ST2L3H-^&'ga19^/fWX!X&o/_BanUM&19rS<^E..oUL?D*=36dJs2nk%\6,91EM[EIh@F,?3#VR3,%^4u+Tl"J#8L#nY1<T(VT3mIn"H^HK'b)cjZ^TkH[=M)NO?#<ZN%*!E0!?j-86G/.p?;8jr0Lbup!!f_uA(3]:0qauF2K9OdlFn8tlrj%MYP;U(Y*lV[K1UV(DP4%i8p8Z[:gAShM?^<)$fX]c+'k`VX<2+F0#rs]k%j(\c-&I7oSO"(lEd1;+BR>$gYOet)%?%(u<Q5r6brOOkabF(&2@LTFN&phF$%AdfA)="\X-DJP%:%l>NK="I=3V<4,5%Ua=f+q#g.j_S$HJ?gGgLW\h::U$&BtTn:i^ga6JR@X?)e5Q->135_CS?Y&kg@o&fa0@^L=6dgiU?7kA:*BJ$V:;j1WWT`n:j-72i01NEXF,aou3E;:qA>(1Q8+0]^MK\erXrkA(1IScC^tg3.LMS$3[EfteZp#pWq$)%9*T%jfCi$IWb3)`H%9N<.`a_Xo.DCR3_4%<u%G6JP>,I@ZY]kt^nn<rBX]%Oc^(5)s//j:m"FL48sE(\A.fHVS3"+qXm%pF)t!`FUEc2F>_@A#L9W--r#jZQk)5Y6mbtk'Akq^;'L5qaV%Ibk<HQi(AWM5^Ge"Z3a.;/@\EO.p%h@/%N1I'IF39#d]5'S/XGDL)SaAS7>(R@f1@a4lH\e"=Lq!H&dNmEU\`7')=ps(g=CiGL,5)VaOP63qOPIgr:3>^<;I9BK5W>>E.EsH[0PYl*3*X4<H5f9:sN_&M%#6M&Yn!![6Y^1nq4rK_9QXK46jmk.rA].m3;RWft6jcbK".TtUX&?:JB6c+-9bYI-hghq#c>A*[UmPe-&h^A46A"!LH.^.*8loqW=le$/+\?fI@;3uj>/lBT#TKsa\&.3qH%&uSBaW_LqEjXdOCNhpMAl%@/.PA?H$+A'X(:6?E.ZW(l`9XZ[/O^KEiki_Zi,Bq`*]XUX=WT>F,.[i+(VdSEf0>QLHllfc+G!8bI@r<[Fmm1dXXUL-6DV#I"S2\bGSF&4"*"Gj*OE4Hr03jS\6*P^FJk!^%KjE-"l,cn2o;oTL1mLql;I#C;>9c*m"]Pm(IQjGJq!aT?DSO5:I!S5brX[!X3hZ~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2475
>>
stream
GatU4?$G!n&q0MXW8iXR1,1Ns+,9."0$,C/b]m_^2X<B8a"J&T7"_;dZs$l8lh=FQ?mgR/]+2Z6;0Q%5X]nf7nYq/-ms\"fUA#<^s5]@cct#Upcg0!>MEXd1r]K_?Q%QjkN.R0f(W!m+p,bG(_db>:";ea8H/2o,3ss%JFla.%j9.C>SXTXS1:RV2+,Yul3G@5'V^nmo;J2N?9Igc'SC:[LF1]HT1$<IgZCo>6'$Tfhm0RHVs#gPXL"Do;lT8b)Gt4BO61`\Re2mOQ%#rQr6Y/l,#i9XAQ4erPSF1:ZK195d55MAIAJO1u6D`E8j4$N[:ifZAoUZe`Y1Y?dpDS\AQ*bQ:iUkt"-nVK_\Re,X\"&i4.\&WFWCXr)4K;@!T*l'nAuT+'&lKE=Q\j*i*g:qKHFq8TD?D(_;Q3S04,5n).Sj'imA\BgPc`Ailjh?W47+o0YJ"=f"2TCYcfd_7<j42/mb^j"*D%+1idhJ4fm#@4dScI0*3l694LLK49CI\<om/9'm?j\P>&"1:a1Nc;QTC!^"DEc7c(SCR1o;a$!oUK6o#,3lH675i60CAJ&`X';*.A7L(%u[N3B?4mrej/YefQs%c_,el;Pk3iNN[hJ06mYSCpT:DA1@apa_,!RBM1jaH'M<27HN$t6ININMP!3T0Y!c[[T;K]8^3-!Z_@r1FW^5\?gV^%cnD%XqNTO]*<;c:dr,EQiucUf\Xa\DE(UbLea1[rc1+[J5X/-Se:btEZ9?)C`DqVTW1U1g)Fo>4+*LrmXq2pR9*(E2U"pEOORAs<fc7T8"jEGsaNmSGEd?M(h3r3J!Ot]u&Xnm9=Q.Q?a1km,%-4_]/@6i!1J5#T<kRm/>bgtVds8>j<ZDn:-F_q9Yqas_U&qM8S#MXK%4p%=$3')1&u5\YX+mQjr@o/_L9JG_5@M,dW[$b-qG3,-1d%GZ<eg?'n1r3$i'2:A"`MP<M;'XSF68medqb^ZiH.F)/>"uOm]^`Wd&ph^2s?&$^"B`iXafuF*>N"/cI:l*8DaPk%'KTf27Qt--L$sc:0Wu014>-Z+;oY^dpN]Zm^Z!*c6HZ,i<bYbe<ut[=^aT8%OE8`fpQjX#Pa#3.D.gKO9+M_ompakQ!3]>a%+;eem/**!Kbd5eRF7fk+&3$&]$4kaOiC'MKInFcWt9d@;:k=XZ(WI"rpXlR'`1IfM!1$]L)VjZ3DV>nc/Y:bZI>>q2MO:0!3N6FM$P^o$>IMd9X_(FMjMm3-,2)FQh5'G,VqY/1jr6p#lX(A_@j_3+'()UrmY-*lp'fQH*C\IBb]B_>i$BP5B.SQZp0_#$HAC++'Xtj5_O'*O=]&*W=,=<O3-f^ktk'.Cr7g)HV>^/DS!#K0IX^4A557%ZtNtM@uDA03UU:>8X6CjcS"0bqZR+OtKuY^1@*h'T;U9S'<%^DEL-u3R's'-ucWhDZo*kVn-U/`!]!>&a]\U'-AV+QWQgbh*5C%;S?e^F+J=@*'92?-_tXh2s<n0p+GQh3kGR!e\[r#A^U5d1s5s-.4+KUWD*=)AUi&0&O<NA'gs\O#"+WGmgY&I+gp94b2dmdQ.L1RYIAG02sLp.K._TNmrhdZ3G<[ppcf0o":+>fp)`LLh-f\#GBVf-BNo:n0le))B8IUihfk@=Js;50[Hq&!SuiBH")l]TNHWK`Es=KVeK:B"kd]/I^HRXkiAMN5438Hs6cVk0)EmG\c>64'2^'pK37UTJ.&nY1@:eVT/M]^$fVmBupEG8ched*H9[&g#enLaEC@5/PYhl*:jLI!C:T%<'/m3$L?p0EK9Q;K))0ZkG`jpo60CFI"6o=P.#NTc"!%'8[ehrqE"mo_."Ou(k8-)?7F$2"UZ>>0UASnQ_A(Z_sUrQ,joIl`"7bOHlUe7d^;eSJ4EZg$Io>bn::0t&B%@O!#QSpZ1N[?LQcZUYA7DB*6C>OO=e^`(?;[;T]oJ?b9b*\qo`D0RWHjAKRhcD,B6!:W(VaN4U<VaMb##l7P<Mr:j-15XSY)sQ(p#[R!'18/N"Xn=4XP0&`W,43k)l(',Z-k.P_L$:^q86Xf78jUIL:s`ch?=eulK%+!M:\ZuY3TXp*C5-Kl..`J]tHYo%)'I\RaXpI!I%/^^Z`DE^`W2l$XWd+$=lr/Ca=W!$=h`W+!,@7s4mW5,_s-1Enon9EOlfNl5e]>5J%6I=i>;bC\p=5K,kBG>od5Nn;2U<6+-\MDML[>rLIhC,mjrS1U'c7%@Z-&3*^6Cj1ab)ZH$5nnA`$B9Nj.V)+E26q@HLR+_3&=NDW$H1=Pp@GB/D`7*4A)5Ba=ACRiUtPl3\\[:ZA#:s*NsZbisi)c/jkW.e[<d-PZ!\\i0SZMjX2dI)7M?l4FOP;O\?`<BEBLF`1HJnn$_`S2*CZm.JD3k+SAF=NWI'X_"t>c5:W![stVRR5N3s)iBOD^h=tKGkK47(u.C*j"BaUVXKij6F=g0W`.>4t>lhIY@fnr"jG/O>-~>endstream
endobj
xref
0 17
0000000000 65535 f
0000000061 00000 n
0000000122 00000 n
0000000229 00000 n
0000000341 00000 n
0000000446 00000 n
0000000651 00000 n
0000000856 00000 n
0000001061 00000 n
0000001138 00000 n
0000001343 00000 n
0000001413 00000 n
0000001709 00000 n
0000001787 00000 n
0000003720 00000 n
0000006092 00000 n
0000008639 00000 n
trailer
<<
/ID
[<9637415b07eac78105f5700cff5859c4><9637415b07eac78105f5700cff5859c4>]
% ReportLab generated PDF document -- digest (opensource)
/Info 11 0 R
/Root 10 0 R
/Size 17
>>
startxref
11206
%%EOF

View File

@@ -1,313 +0,0 @@
# Les 13 — Huiswerk
## Background Agent + CI + branch protection + DEPLOY.md
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Deadline:** Voor Les 14 — MCP servers
**Inleveren:** GitHub repo URL + Vercel productie URL + DEPLOY.md
---
## Doel
Bouwt voort op de **lesopdracht** (Pokédex op Vercel productie). In dit huiswerk:
- Voeg een feature toe via **Cursor Background Agent** → preview URL
- Voeg **GitHub Actions CI** toe (lint + build)
- Activeer **branch protection** op `main`
- Schrijf **`DEPLOY.md`** met reflectie
> Niet klaar met de lesopdracht? Eerst die afmaken — het huiswerk heeft die als startpunt.
---
## Onderdeel A — Feature met Background Agent (verplicht)
Voeg een feature toe via **Cursor Background Agent**, niet via Composer.
### Stappen
1. **Open Background Agent** in Cursor:
- `Cmd+Shift+P` → "Open Background Agent"
- Eerste keer: verbind je GitHub repo aan Cursor cloud (volg flow)
2. **Schrijf een specifieke prompt.** Vage prompts → matige output. Voorbeeld:
> Maak een nieuwe branch `feature/search`. Voeg een controlled input toe bovenaan `app/page.tsx`. Filter de Pokémon-lijst case-insensitive op naam terwijl gebruiker typt. Houd Tailwind-styling consistent met bestaande cards. Push de branch en open een PR met titel "Add search bar". Voeg in de PR body een korte beschrijving van wat je gedaan hebt.
3. **Wacht** (~3-5 min). Volg in Cursor panel wat agent doet.
4. **Open PR op GitHub.** Vercel zet een comment met preview URL. Klik die — feature werkt live.
5. **Review:**
- Code-diff op GitHub
- Live test op preview URL
- Eventueel: comment achterlaten of nieuwe prompt aan agent
6. **Merge PR** (of laat open tot je CI hebt — zie B).
### Feature-ideeën (kies één — of bedenk eigen)
- **Search bar** — filter Pokémon op naam
- **Type filter** — chips per type (fire, water, ...) om te filteren
- **Sort** — op nummer of naam (asc/desc)
- **Favorites** — localStorage-based, ster-icoon per card
- **Pagination** — 20 per pagina i.p.v. 151 in één keer
- **Eigen externe API** — voeg bv. weather widget toe (Open-Meteo, geen key)
### Eisen
- [ ] Feature gebouwd via Background Agent (niet Composer)
- [ ] Branch `feature/...` op GitHub bestaat
- [ ] PR open of gemerged
- [ ] Vercel preview URL werkt
- [ ] In `DEPLOY.md`: prompt + agent-resultaat reflectie
---
## Onderdeel B — GitHub Actions CI (verplicht)
Voeg een CI workflow toe die lint + build draait op elke PR.
### Stappen
1. **Via Composer (of handmatig)** maak `.github/workflows/ci.yml`:
```yaml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm build
```
2. **Push naar een nieuwe branch** `chore/add-ci`:
```bash
git checkout -b chore/add-ci
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push -u origin chore/add-ci
```
3. **Open PR** op GitHub → tab "Actions" → workflow draait → groene check
4. **Merge** naar main
### Test je CI
- Maak een typo in code (bv. `<dvi>` in plaats van `<div>`)
- Push naar nieuwe branch + open PR
- CI faalt → rode X
- Fix met Composer of Background Agent → push → groene check
### Eisen
- [ ] `.github/workflows/ci.yml` in repo
- [ ] CI draait succesvol op merge naar main
- [ ] Minstens 1 voorbeeld van rode CI (screenshot of beschrijving in DEPLOY.md)
---
## Onderdeel C — Branch protection (verplicht)
Bescherm `main` zodat niemand direct kan pushen.
### Stappen
1. GitHub repo → **Settings → Branches → Add rule**
2. Branch name pattern: `main`
3. Check: **Require a pull request before merging**
4. Check: **Require status checks to pass before merging** → select `check` (van CI workflow)
5. Check: **Do not allow bypassing the above settings** (optioneel maar aanbevolen)
6. **Save changes**
### Test
```bash
git checkout main
echo "test" >> README.md
git add . && git commit -m "direct push"
git push
```
Verwacht: `! [remote rejected] main -> main (protected branch hook declined)`.
Goed — protection werkt.
### Eisen
- [ ] Branch protection actief op `main`
- [ ] PR's vereisen groene CI voor merge
- [ ] Direct pushes naar main worden geweigerd
---
## Onderdeel D — `DEPLOY.md` (verplicht)
Schrijf in repo-root.
### Sectie 1 — URLs
```markdown
- Productie: https://pokedex-XXX.vercel.app
- Preview (feature/search): https://pokedex-git-feature-search-XXX.vercel.app
- GitHub repo: https://github.com/USER/pokedex
```
### Sectie 2 — Background Agent prompt
Plak je exacte prompt + wat de agent uiteindelijk leverde:
```markdown
**Prompt:**
"Maak een nieuwe branch ..."
**Resultaat:**
- Branch: feature/search
- PR #4 — Add search bar
- Files changed: app/page.tsx, app/components/SearchBar.tsx
- Build time: 2m 14s
```
### Sectie 3 — Composer vs Background reflectie
Maximaal 200 woorden over:
- Wanneer werkte Composer beter voor jou?
- Wanneer Background Agent?
- Eén voorbeeld waar de agent verbluffend goed was
- Eén voorbeeld waar het matig was (en wat je deed)
### Sectie 4 — CI workflow
- Link naar `.github/workflows/ci.yml`
- 1 screenshot van een groene CI run
- 1 screenshot of beschrijving van een rode CI run + hoe je het fixte
### Sectie 5 — Eén observatie
Iets wat je opviel deze week:
- Preview deployments — wat was makkelijker dan verwacht?
- Branch protection — hoe veranderde je workflow?
- Vercel logs / Cursor Background panel?
- Iets anders?
### Vorm
- Max 600 woorden totaal
- Concrete links, screenshots, prompts
- Mag wat informeel
---
## Bonus (optioneel)
### Bonus 1 — Eigen externe API
Integreer een 2e externe API in je Pokédex (of als losse pagina):
- **Open-Meteo** (weer, geen key) — bv. "Pikachu's geboorteplaats Pallet Town weer"
- **GitHub API** — embed repo-stats van je eigen pokedex-repo
- **Quote API** — random quote bovenaan elke detail-pagina
### Bonus 2 — Environment scoping
- Voeg een `NEXT_PUBLIC_APP_NAME` env-var toe
- Production scope: "Pokédex"
- Preview scope: "Pokédex (Preview)"
- Development scope (.env.local): "Pokédex (Dev)"
- Toon de naam in header → zie verschil per environment
### Bonus 3 — Twee parallelle Background Agents
Start 2 features tegelijk:
- `feature/sort` — sorteer op naam of nummer
- `feature/types` — type-filter chips
Documenteer in `DEPLOY.md` of parallel werken meerwaarde gaf, of dat het lastig was.
### Bonus 4 — Custom domain
Voeg een custom domain toe via Vercel (gratis subdomeinen via vercel.app blijven werken). Niet verplicht — leuk om eens gedaan te hebben.
---
## Inleveren
1. **GitHub repo URL** in Brightspace
2. **Vercel productie URL** in Brightspace
3. **`DEPLOY.md`** in repo-root
4. **Updated `.github/workflows/ci.yml`** in repo
5. **Minstens 1 merged PR** van een Background Agent
---
## Beoordeling
| Criterium | Punten |
|-----------|--------|
| A — Background Agent feature werkt + preview URL | 3 |
| B — GitHub Actions CI werkt + bewijs van rode + groene run | 2 |
| C — Branch protection actief op main | 1 |
| D — DEPLOY.md compleet met 5 secties | 3 |
| Productie URL werkt | 1 |
| **Totaal** | **10** |
Voldoende = 6+. Bonus telt mee bij twijfelgevallen.
---
## Tijd-indicatie
| Onderdeel | Tijd |
|-----------|------|
| A — Background Agent feature | 40 min |
| B — GitHub Actions CI | 25 min |
| C — Branch protection | 5 min |
| D — DEPLOY.md schrijven | 30 min |
| **Totaal** | **~1,5-2 uur** |
---
## Veelvoorkomende valkuilen
| Probleem | Oplossing |
|----------|-----------|
| Background Agent niet beschikbaar | Cursor Pro nodig — gebruik trial of vraag credits |
| Agent maakt branch niet | Prompt explicieter: "Push branch en open PR" |
| Vercel preview URL leeg / 404 | Preview-deploy nog aan het builden — wacht 1-2 min |
| CI faalt op `pnpm install` | `frozen-lockfile` flag aan — commit je `pnpm-lock.yaml` |
| Branch protection blokkeert merge | CI moet eerst groen zijn — wacht of fix lint-errors |
| Background Agent kost te veel credits | Maak prompts korter, beperk scope per agent |
---
## Tips
- **Specifieke prompts** voor Background Agent — vague = matig
- **Lees PR description** die agent schrijft — staat vaak goede uitleg in
- **Vercel logs zijn je vriend** — bouwfout? Logs lezen meestal de oplossing
- **Houd DEPLOY.md bij tijdens werken** — screenshots maak je dan in flow, niet aan eind
- **CI failure ≠ slechte agent** — CI vangt typo's, agent loopt soms net naast
Volgende les: MCP — Model Context Protocol. Een vector-DB met PostgreSQL pgvector. We gaan een tool bouwen die semantic search doet — handig om in een agent te gebruiken. Tot dan!

View File

@@ -1,194 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/PageMode /UseNone /Pages 14 0 R /Type /Catalog
>>
endobj
13 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607134605+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607134605+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 13 Huiswerk) /Trapped /False
>>
endobj
14 0 obj
<<
/Count 7 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R ] /Type /Pages
>>
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2335
>>
stream
Gatm<8TWXr'#*s=oOTMp+p88i0S``km&nK)4H:aeYK@Z=KI#7cWK<@8RD\phY5ps4[FioP*2b%o"M4JGo^W"k./ksmR4\Ns:>ke8#)pH1#0\GY9YS;tM>+n44QMVH!"kBOQii2!rWi,O.j50i)"X,GU_/`O&&)RBb#e_cF]$($W"mN_GRR+WdRa4",RZpVm]@9Nj;_1s,RD;4GJ1C[C@bmI>3jPZmQV&r)9d\Q;M@mp$_dVJ9,RX$p%pasY3)`U(KFJN+)S..7131e7rWW85&6V"BiPB+eZC=$eP_\"F-p4MjFu/BORJ*Xpjtuio"V+-R"3NK]X^u69dfZZRHJ,_C`qqcFQu=naK;;4?mLU3JXc2&a1W&9/Nl<*3]Btcn0'-KLAK01rLjq<Ih!Xpg]M&<clc_BD*JdK&[<1C1reNmQ;cp8;K)'9n>nB(6@3FR=-e.Hbp`)1cF7/,'SFb:Gdu(Q8]_-Fk85_d7#aTQ4<8St:o&p.&3GpYPr+RcFroC@:73'Kf\M1tmRCkCNV:No20LQ&+9>u8otYsl!u>CPCiW5LFfFm,,O,PW[U<K_RkB=)`Y<'JhuY)ZOjYZG6eLiOG8&$O.:A#91IXVm-oWJ-BA*D$q_h#ak&n'a1H:k6<l%FRGE*co3[rM]XQlfcBq&0MNkPI3n`B(daYGrH0rQSM,mQ5<35Hnji,^j9LI]VTNH<HCC-Z`>k;MWT9'DQ`^@)#EFhpVtW3h>^&5t,MhijNg>=K.*MD;?B4&2'c[UgO6:YY._#l,k[mo>McH)ZfKfdPJhg55)L$qro><E0JTc@R[o0$c`?[]ALhAQ`?bRg1WeqC#&#AUuJ7<I#S6W>]rgk>U5d?;E9b<u#2\Gg=kWcO0Z]r,Q^Hmm)46Z:TM9SH.qPR/Y-4"_/&.FhO^2pDl[T-Ne1@>[e#oaiSu5(9u,;I7eJS#m]omZ'2Bp5E*38LNOBH*DUY4mfi^[I[M[%\'QJN6K)gX/YkhLdQBm@%P6b9lPueQLOkQASV)j)Img:7"*XKLn._SdIP4+;?98'\ENR_o3f-a=D3n'N.Fhu6?W0%[qkV6W'Gg3$$Ri\q'Genu/0q*tV!'TcY5sr<&pGV;2@2B/1WG3Q1Rq_JE:4Z/Ph3`*Z+4]PpCe8m,aBiQi+RQ<DAgg[e/L/h>HR?t\AcnC6FDD=6(s`^Q.:^7BVUKcCI=jI&TguePc::$l??7+Wp!CLl8aFaQ(!>'--T?:Y0d>gV;O7-i8(t^.MpBWD<Ve2<B87c\4"kTF8F*;V?S!j.nK1(Xa[b2\6]396XEpYf<bF'+"^KN4#&8XA498>-XVNP6<\VP5)EB_J63khN<2I@$:"/MA5JYHh+ota49eprp#TS?905eUoT0SRK$Bh8F(=tIabc^DD@#i8T_2&<EY1+J)6auLdD4#q_INA,oe39X7%G)fW.NlPg)F`B3uA(\Q=oUU,\Fn,)6;QU1GM0rbIQcL6jSXN:(s)&_%m[7\U!pr+Q)ZB,f^/b&os4s_JREpBEM>VboV/A)IYF+'fi/2eam9%8VEcFO<6u/4k+=0f,2Pe^9u'tohZHI<LInhd!Ba*5qd;aBias4o*Ki-mH9jX34277Vs5?:R'5_*]hNbS[ap:08UIqj?@H,>ZhEL7.tdO1N;j"+%Z5OTYM&+XqBW6&VmrU*ooCN=#?<!9Kc;S*DZGE3RNo%Pea]@Z.;]+*7TQY[-UZNOT*2,Q!NCGdcu6A@lR()^<1;-RFea%C=K_'>j`hDd&n(\F^r"+qpRe>AMK&VhjACZ(hc@]9Ejp7nUpQ-[0LYq4+9j2Yo`%AZ?<h79?sC9pBo0dc=E2f)W=N(-qQ=RUS#\Wqj<"C#1fZ%g!Ao:2^g;14dOJ2U/>(2V<)Oj(ft_q7g63Y;:-eM4@)&^fY4QmDNN*j18YQM:DK&gf](3KqLP;2bp;mP"(Uj2gl^7OQ4&r<%`^`XRdiK`X65Ir("#>b`!09_>];PLshr"Fs/$WdN7S)ES;8'1UlL2Urok?6&Ode@`M_]^d_*/nK0?Wpr&0,c;$CAV)EYc.s'\,E]:)?J^GYNNn[+D-gSa8)XhH%8&8(7K#8F$DsAg?MJ<nCVP"VD&g+daEY^F%'^WT[oreotjro'IU[Feb*\>mpi2J3Z5VReo9#cQ#=A7+'Rc;5an=k\AqBZcKMb<Mf,=aJW@JP.;cRoCIcK+\3k8&N0J?03UJmi3>g)X)_J+f+"%JUf$0bCfK*KqOF91*,8Ut&^556oMrL8r#$G09Tm&u_q$tgg:rNX1a(#uPHB[?e[0!QT$l&_cC-!KLDPLXlXmEl.lt8IV>/_HQ9j\@QmVL4m:l[YrrGKPSRq~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1035
>>
stream
Gb!#[gN)"%&:N^lp5u.>Z$_i6Q8D0sRC"+ha\6>l&S<aI=E7sRmGD!Z5Y8IrkQ<8Y^k8cFF3^r^(^c:Oe*8a2!%rS:G"mP!cl3#YM%@9]\E^JN[YPm6OWOSnR,c.<OOu'jT[T3[+=(C^`Y=dR&Jk3#1J/LB)7Xiq'[[1C_W\?qJOH1F0cp[r6--p&oc=EFf;A75&;K_uoaO)c_M_"<TX-?<_ZmGkd"^%"Wok%N##PB6%,,(VT5NUWV-#!QV5E`q1aZ]7IFjJ:r3gZQ'V[9@KM.iq<94uiErs#scoWh#Xi:%JJG>]L4cb)J"],_IAtiD#T%%bl4;F$3],'VHMRn`\\dd*/EsW3oh\O+9?H_G\?UgrpUjR>k.TC.JBO-=Yh`?Jb20(M%fF^Kh.W;rf"^ZU(U#2FIMq%_o<u7>^ffsb:EujL;du^jmUeZ'CQG7/WVeR>ueNV1L$uGB39D13l-LZh3*OBM&G+V7O:66MC!MtaXEbuE:?O9kh-#Rdgb'cX[P;*KUY#>0De?5i4&lXE$\n^e6\kG[l3CqH%S`gnYrp_7iG5'EK$$s@-Cj9jL+;p*MJDA:Tb$.jMB"7p'*pu==4RUBtLR):]Z^l2@g0'_EdVADXOZdkMU<?dH'#!FKV1l`p456`,q"1%QR]"dUn,eC6_+mJU&W'E_C@""%UV=rp+SC%')q"0-i,6Gu]q*br>EGB!$.+3SOP=Cp9X*2"4#Asl,la[]Pt4aRriu]n#j:$umD9:5%ei!+c=\u4?sucACkZ&K@>\Y!gVXDX/#*J]B?`V`gXn,Y.eg""clt,>IX'\Sr7M$.bBf(l]iLW)_<LRLNS-[V"#X9k@4jNEn77\s23UBe\$`W)3,L+k^dj[OW^+8<k20cG+3=7EF(oK;<)Z"+KTlT8P;"r%!=Z1dS8WT3gCEEL)0Wbu7Wqj:[n5!$:5ZshD!W36kQuB`;ogiA"YSa'lQ)3#ans5&r5!Y$VcsIo=5CLcmj&j$Ygi%-1@Pcu\`GjjMqPIR7/I4$1r[B=gbC(Jo`l3#&Dm~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1666
>>
stream
GauHK=`<%a&:WfGQq&HQ<dSh1BbWl$-9r'=:kKO+;,GbG@a%4%+ZKAmSL#SFFsJdsN(AOkX`hGSB'1EAMJq'n7k)7F3Qtec?l\<q^a,$rIg<1>hR,k`i`c1DKV9UJ5Q]nN:oq;<Hpq#M!sXKp@^H8P(bm?k^emjBVP155q:K1B[%c8F+GT^7W,L`#;4OCr8UR@d]CJ%4`mG0Mg9F#6m0CIH#MU-HTaF@>o<f,#'IFncIXVZCns(f%Fe0uMHWA^3?,6^-)P4X54tFgdXR$R+g9"*?9kq$"&^8#*r3=^.^mDnc;G_c\s.(`m$$]Chjc(jp>28^g'k\\G!RN2Ve3/uS4/SGb+rk-XSih!gi5lfZPuNikgaDK+\!D8D'2#)=XDtT$,mi%X%p/]-T#g6Kn:hh#*)sB]i4d!^%U6/\:Ieg-&0\mgIH!!ZQh>jV%Z#:3,*2FK')^.>g^/1:$nRIP[FYPHHag_]]lq6@7'@eH(EWaO"ba.2EruhjL%.mtZf_G*&Cqooh<P&J2>@42hr7:(Hkp,JhST:MY27Y9GSX;!SsAg)/R1uX[b)i[7c)+ZZ)CJ"m@rTdW""g0iNjk*S&D.`)A6gM`<mrait,PXkpA'#!Vn%C_eb`q<P3&k1C_5P+KN"Uf8J<V?<gb_q+Wl-@e)BNc>G?ua@uep:aMQ\AJ@kZ'^r2Ai&1Uo21b8jBMqW@h3Nh6/b,X1:9\*jC'Z;N<"E'SIS]%tQ,`;?$QH$^%FKDFfn+:?g.`T?D?Za1+"h050n[5T%n:nO/o47#"7<YejEk\=gOiob+D@L,*fes)1QG&bj.4Ok9P%XIcS1Rnf;;;0)F=UC92hFQNU.F<\?"?mYc3iIT>Fpd[]IIO\N:bqmj%oL7@6MZ>FrZ<Bc(K4O,,Sep*?3cE]p:\;Bfftn#;?c6[Xhe-[oNk<l>MBX5lpf-q?f_-(C^_O[;+VeX20=0huh/7o<p\'`A1-U<]cbRY2EIrWn=uTJ8"]5d5ZR7`f-$%CGCX"&,C@h$JgQR@5u;jE:-;.n?f\2?Za,"'D%/PKm@IBkaVoo5lKP^o1%rnqY-`DQl<a(_d8"YJ%E<HXW<o@s=T^BlP[A_O"BfO.Sq[J"#Il4*i)#+As,qJ(+a2'1[Z10uE1FG*[)n)FXEi$#T*<6m;E5Fi\9:+c$o:71N)pj0Fr(3%M$`8ROTm/FrgW2GNc+l7u-$=GddP7_'bB8UP?slMS9+*gU0_EO$SMUI(AsYIYe>o637^@'"%ao43ka$"*&dqKohCp>[O^]is.f]<D).Nm2f-B^`pFGO1_=*s@WYD)%NZ@)Bu^L0-Cg\[C&=%1WW:fkoqo/C]c8@=bg=0C15p/uMPTdjK,6S,:2cB3""NF8)Xc<q[pTCpX^^/H62ipR6PMNdC-uV<[@na4Y=7-1FR&BsQO6mELjgEq.4RlWT[f(7;W$`sK'o`+nf_<o;6@YN(,[F&b.acCTg=(&G;_Q:!GI0AEqqI7*,FZ-a.8I].@9'pgg?5-9"-aI0\B.LPbYI_&oj+6A;S7m=h'Tha50p_8MCo@OK/..#3J?ie<'ktQUV([98P[b(1>JVg47=bL3g6!6^?/[tL#T5WF5e!;7<#+i-j-^o95$fAQcC[l,$_)BVtKsf#8[UIb<'XSt\q!uu2]8qH)FEo.r_##32XoWG~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1812
>>
stream
Gb"/'gN)%,&:N_CD#KGDWJqH38:l:]a0D2h1JZsi8iiNS%ho1=N-UOW88A1=$^(SUMe(L<1MNbQ`h\p9m[hC,pOEror6X_YPQJM^_lL6\."hL)-mp#2B5lY8Xt@LQk&V>p,A\`85QXIU5r#f'E'<1a/>+,%T*V%k(R,1;#[fX<pSe[WK[EGN!qAG.(EA7(Xs^kDo,ZgFL8HtN4YO<.^[Y*5pB'>3hP$G3(R<qbg41q^*DUG9qC_D=kl()Oo-p3j@p)9b4AXn!.V(A5E64IX#H)nRAJph,%cKt(AWfhfRAh"LrrW.cg/^^.G?i!*gV9`f9U7pa317bI65EY1hXb\+B-Hc)U(8*jV'_6\T),l<N`I$oCEm^W(`D:OO*cslbU)qaOX1e7M/@=]OYP7Bie7.LlUosoR)h$<]V0pTXicG$*#H+mF??iN@iSSX,'7dLT3)f"de9AO'\UJuAD+($$2uVR2NnT/%e@]0=B/8`:VHJ*hWj1$ZI^=qacF`9"bb<S0``!]3l2W^RN6S&L!Vm)\$$hN?DTk/m&kf_Le@hDg`-+I'O_bO?s;V`7Ol\>\EqIO0hl-$]ZV*%AVDLJP82[[3(V;Sk1Xo)*`Y%C+&t:kb&%*4c)t'QCL!3MXP24bAIpQRG,:Fk!jS8VTs^H2e(#>K_UD\&<HWVPXRqY0c%\$Omcpj\['&D'e_agE]q8eW1)jUuRHCaJ)Ce+V"20anWAki`qH:LTZFW7;%\]79Ei!7I`)a+K%C!=-%'Y(Ye!mA,>g1&a_DG4a(>1mI@@XG#j_Y0cZN2KCBqPq`1:l1p$[FmnYu?<0SL"Rpn+BA?$3kO8UGS5><oNQeS9uhHq$&hS<cF?S<3m2]/M09M\=dDuW231ld(o8<an%BGfiAZr)^&a"L%m'O/6<B08\T_k?Q(:E-)_,&p)ou;.%2a"#4#<_H5f+t)UaZi_MBOIL=G:EN=WL7RGQjWDem%[O.0],3b+9s2fgfIdi=(qf^4dNR;3hlMPD:',cpEgWPs;#AqWX>=<.2V:2E@R2L<=([bm@+jd=,(8,:*$T$&Wa93FL]k#W8(<pnFKOR^+W:f[B[dhH/Fc==IdB",3*;\:Sc]CiS"hkFh'mq!sY"BFsYqh[q\-pt<&'$p)`)DR7P^";kD\m;W1RHccMG;Rq!F-5MFYbQ/kGMr.0\2ZZ3H?W'2iHc4IUT+X10csj0,k1.<<5.F-MO2!jG(s+ZYi&2fj^`#;b2mcF;,;In!-+Rj:!,W1f70+*,"/$1^MNMeZH`CL^DAXo:55>!$bA!HIoKk,'m+:_1laSIkW&9Y]C.^32r\C>.m#X2_93h'N<4>rI=J)W8F]uCKYCs^.HI9lPXE+)&`hDr3"!",*P0hQUX5Q=4>-_\^XObCj!'hXKYp`UkBTq0PBFg,I7&6T6?gqF!+a8U(`)dV>3*Y!_j(TIO(C'$]XP-GO+a%6s.>%\l"TqV+$L,U*M8t;rsa>$j%dEap[9FJ*hAh!=`rBO/+Qs4_E@fQ6H5rs9=_Xn[[)3cZ$]@E&U!tR#((L>o[-Q@`OXNGThcJ;Wu7t;pf)A&s0jKWH'&VM<CeeZs6fIW0$['8ZP&$e$TX:0iZV"QHV*\?-FjZ0b0!FA]'UNUl5fICN39C+cE\T5N+JkC=%K5.**Xhh:G3gQ8U(F0:F%0j0aL"\,#g87o4R/S,-dBkYKPdqi\7u`I^,6a;Jb[20T$NH2J9Y\/du?31'A>io('cbj(.I9Fg`;aV[hi4,0<J\^qB"<[ZI8[)Y\2bC+@]malmr@qnIkg-8@@R,sJKC$.V9j=o>X9.tI:O'q_3^~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 972
>>
stream
Gb!Sj9lJ`N&A@Zck"*pDn_.c*l(q##7#_ngM*fZ!(Lmuq@!:e;J`P^4CkqJ0R^PhXJd"JQBAT,'o>VaiHLpDX*WqpUTQS)%]?odE(JI*W$I1_mmg\C`DCBm?SohWR$`r`\0/.*-JKM+hmC##G-Y+tH+=(St[O&G*/e!"2#,-P%i5!4F$)IVK\KAn#kIeh:f;6uKq*EBa56$F7QLNTZGXP%Ul\gAB5PQJ&AQ2GZL\mbUSM;rcJ%<1E:HCo`&%'X@CLJn<'[V!Y&5Fk8SQMB7!Yb?b1X,N&N2ejhc4E%WAZiH9_Xj2bS8^:`'jBN:I%"YjNb282]oLN77Gt?Q\tPkd+:[$*PJ/9?)0FW4DV(\a[Q%8kggsD"k9YAY5'!;jCWIM--.^Y$]80TI]%Q1R\,gRiUrSnL\`@P:TYC+&kF9<&77n^A3/uopC@kIOE0=+=5O?Wcno7uregf:XBQ9DRD0h"`!])[HI+n$6ED&/9LR>atZ/=\$Nf#BADA*c/UB6/U$"D=Gg<"cLKctCO_@GXqY]nQ;=XL6VM@p'PFWIp*9a]-LN/J,/>W#\tQS@$'eBdf8D^e`YjiIIF^904K!Yn-.=IKtWBs4qCMf$ENHPkqLHdHY?K;e-%(GLa]OG0@EZMeC8?LshlGdVkZ;DQpEQDq58BlnqQ5Bm84NTK(-Y^h1G?f98VShHX!4aVW@Hfn\\k6c=WU7%-6m"3rZ5i6R2D$'[ojVk\NogGoM?)n.Y[9c5lg>FO"3LSnQf+6tqK<=H=RcZ`<jJ/I^k1O]ACG;IUd_;)=o^Hj6"o\toGp_uY'<-3V';LI`*a7*q9>qO]80qr49"YO!HK?[W%S_mBj>s]qK!VG]HK28B&h2$8Er=NPkn6S!<T>V:[/`?5@6[%lYNH0?%XX4<HHr[bm^*kHYB$aq2/.='Bn&2i,#*)U%*l4(lRWTfg3iTGfVO_Y%hdoI;HU@2)&b^>F#s%m~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2107
>>
stream
Gb"/'>BcPr&:WeDbY,Fu[S`+t+M(??R^Un5[Vj[\m[fV`H8&B9@R((#h<_?&&R`EK1n42QA&mB#D_BIc1BRdo5IC=t@XsY+*5/16)>%itEl+N"!G?i@c61>>0"6C6_R)E0`3](XA0W<"gEDTt4:8rIM/YIo6&?/E!/C)MQb&8M\9VLZ+Kf4cJccV7G(d1[h]`P?.%1h(EP0it[=1HCk7OWC%b7Yp#_jR;D,c.&EFMi#[@n\[_8f>YcJcGTaZLCj'u<n4a^AAh2.J6YT-#-;H'fCg#]Jct7OjC/;Z$n#>NiiKZ9%d9Z3e!6G8^EIk!1MC57R4pbU+'BcQ@jt%o*An1HKZ0jKMfdf+c0OO?pP.j+`8Z&JfT#"0o[Y;Oo`?,(mGE1qDi*A"k$`$$@9>]ji_8"@dGC:T0pc6T)>8*9Z#%?sBuVdjVH1&YO`6o>mU"PdV+I#0:VV"GYTE&`H0RA3IIXULSRWN/_jVkte_\7rS(_.<hs_7(kfGCZZ5`jGXD<Lhd<$P\YHn=VjCT2m48apeH\7&[m_G)6p/r)g;Vg6m^;%Xq+K8KIW*>])Hiq:2'@&@l_e/\22A"nNmC9f.9p09mA;Q$Ugqjm+=KBIF6GG_pYUnZOHY^C[/K(.X%)qKsW9]>9do(Y_$Rtd9"jW)_Ki29DmA0K.9jG_N@NB8W_g:R3`K+HfsV(:T7"t5KH0k=7@gnhG4B[J.T$GaNmM^Yf+k/`Br.N>f!kY,+4.Q8i-W9*iLnh7d,\iB7KThp9%.Cc;21rpNg12*e-/%REUc=_C1Sh:r;#^1H_Hg/*G$F?=N'Co?otVUV(,Sp/6Amd8W@%d<PT+M'#D\R@5Hi36&("keOfmIPK+.;6I2+?Gb^-\5-\MgGqLG2ug]OI'[T`Vg4tF(ol4!':=J;I$%PSUGZUHY:.pH,hoXraj:"MUE>_]K]fGuUUNHI+.Xbk(,m-%lq9=#8g.>*81se>'AKc.S)J7G?CO9UE^T)E<eK"."/AGAh"FpVhiS5!ob>58a]8k]=kA)(Ua@iU6ITN0.Z\"PRjtqZ]E!o<P:R]5M`gY7\V$0;-aAUn^bY(mQ;oaDBr6X6TEsZRRE(r<4R3%1n].'lk,`dsl#.1F[7tuC%Psr=?"r@k/E.2ELcs%XX*!RkVMC,I@QYYNbuH[4M:=XP$:8bTY@iehQ1Yigfafe(EL)"Y'5ZhEX>_;gfa)H]?,Ff]5U-0/ed;p`N#]/o6V8;<[V0])8UK4F&lX%u^f[V0nnHp>c;ZW"AO5Llgejn)7*08S_p>q5G+@]N[S&hq]P5aYgf9BnI+&qll-:;d=?/!G@?J#0flJ28m'==7I%sPcE7m]2f<Y0lU?O3hec[l]oBL$*@a>Z^j"2nm9Ipp_s(o^LV?q.bcJ[7>k3\8P]Fs6AIYii/55oD91G?mU9_)mEB;(i2)W%Aq%;Tf%LN:Z)4i^,=p[fHtRgr_+&*,jcc-,TC:!3;6R;o`4Adukjai*JSFDkr0jsRuIkEeL>\u,a>VjX#;Jnc::9oeV,cegR">+uO,S46CcA00BbCS<8L_^9XdhH!um30r<,.8?,Bg]LeWghKBfL4*(sI^B?[)G89"0MO.6j8U\HGsX1m8VFVU),J#\'1aWmq9C*@bSuk(gBc5o&cAo)3O+Aegr26?RbC;Be.U#V46,!ZN\JWlrW%;W$lojE'W1`'+jNEbY#EG";(?cQin0OsF6O.0eR9C9Jr["2V.[3&2B'Y@(i"q/JA);.^-5"DX?JS4H"I@"i6fOVAj*$f2+r?"(rMt!+>KH2XfpE2rq0N.Et:h+p6JV$0!FN?MPD4t$r\2!)fh3ZXYcDe?aBMPAMh;P2,S@]R:^n)R>+S`@Tj)Pg8!q=eS.a#2n[F98VjW/cnAtJah[YW70B#fb75@3OJlSiFXS036E-j%^5q>2W$n@X.HeilBbUbr.C7Xq]Jn*UM_W:G.`J%:jms5&VJ`O"P%/m(=S7ZkThH\R%mKR==R1m2%qY;=k.Z:u9Fi#Z@BtKAoQZ!s=J/>YP;EJLOSD[P+X=2/@8T3JN)VJSpO6Cb[0Z)72i,so,%XeS2,3J>F,1QGL%k25k21[*gZ7<^%E2'\f@m8l'9%DALiJ>9~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 830
>>
stream
GarnTgMWKG&:O:SS<li^g2(rC;lQde2JF@<c)#<X8pS6,->N]V!BX2n^9cbMNh[N2H=5mZR@,i<OtY?Mdh4X0iq,Q'3LJ*c4Q0^0_>/3FG9i]o#sBBINMILT&@$##B_#"(57g-I`!VV8721(;+sis3No4k](oe,V@=C=:&7H^!aFf;5+%3r7GZP$._l5fX#=3_S^You3HEd`6m$QpS1DkJ;9i5+of+V'XToWoEUEA+^=1C(I[HXG@"b(S'V2lW<*%/]"l=A\Y]'Uo`2d0-glB8184f/T\O);$_I*_J>kK<9Ore+H'(RHZ.5#JX+E,UnE<sc`,TNc=!)lA2LLrbo(%td*]Rh:FL94e?I"4@ZnTkiC9N[j"A_9c_LJdq.'moj59q/3DmIAAql8_6H!9V3]mm9l0;FpY74EkFPn%BTL][rJ\Y9_n>66Z.:+VsO5VGf9&\<24Y:AT);rZ&Bp1>Jq$;,3Es.=YC:GY^,2R@]_p,Z>\YTp0P87(Tt*0m7W5"BA`Dgh4('*kh;`0?K\4&/3IJEr\&F79m,:cA$l'E_0^7pB04lhOeW-rLA4nMWooh:KXrmLh6g?(e)2cKQ[*(Agl?C".N>1h:@].Y1WlNBH(W/e%S.7BES]CnA+;Igm^/sTBgU7'Z_/8NQu?u--#u?j@AUSf%^GH$8Z=s`neTPt;.2am2qeb>O.Vije(h%90:5k.JtAq[5T.u!L#G1o%RDbmMAPIRAodi?PSn(ZJPIjRW`n%dT^!aO9c]Cm!pM0rjCAV#K^fFb5N-MX*?RQ.N3\C@[/s]03AW'85[Q5^L1/uXX3Ir^Fk^7?r<"3n44O~>endstream
endobj
xref
0 22
0000000000 65535 f
0000000061 00000 n
0000000112 00000 n
0000000219 00000 n
0000000331 00000 n
0000000436 00000 n
0000000641 00000 n
0000000846 00000 n
0000001051 00000 n
0000001256 00000 n
0000001461 00000 n
0000001667 00000 n
0000001873 00000 n
0000001943 00000 n
0000002236 00000 n
0000002334 00000 n
0000004761 00000 n
0000005888 00000 n
0000007646 00000 n
0000009550 00000 n
0000010613 00000 n
0000012812 00000 n
trailer
<<
/ID
[<9e0017c2fae42cc449cacedd8dc86476><9e0017c2fae42cc449cacedd8dc86476>]
% ReportLab generated PDF document -- digest (opensource)
/Info 13 0 R
/Root 12 0 R
/Size 22
>>
startxref
13733
%%EOF

View File

@@ -1,186 +1,248 @@
# Les 13 — Lesopdracht # Les 14 — Lesopdracht
## Pokédex opzetten + eerste deploy naar Vercel ## Bouw je eigen Cursor + Vercel flow
**Vak:** AI-Assisted Development **Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht **Opleiding:** NOVI Hogeschool Utrecht
**Duur:** 30 min in-class **Werkvorm:** zelfstandig na de pauze (±1,5 uur)
**Status:** Tijdens de les afronden — daarna huiswerk uitbreiden **Status:** klaar = productie-URL + PR-URL in de chat delen
--- ---
## Doel ## Doel
Aan het einde van deze opdracht heb je een **werkende Pokédex** op `pokedex-{jouwnaam}.vercel.app`, gebouwd met Cursor Composer, gepushed naar GitHub, en automatisch gedeployed via Vercel. Je doorloopt de hele cyclus die in de demo is gedaan, maar nu voor je eigen project. Aan het einde heb je een live Next.js-app op Vercel, twee features in eigen branches met preview-URLs, en één Pull Request gemerged naar productie.
--- ---
## Vereisten ## Wat je nodig hebt
- Node 20+, pnpm, git - GitHub-account + `gh` CLI ingelogd (`gh auth login`)
- **Cursor** geïnstalleerd (cursor.com) — Pro account is fijn maar niet verplicht voor lesopdracht - Vercel-account + CLI (`npm i -g vercel && vercel login`)
- **GitHub** account - Cursor (Pro of Business) ingelogd
- **Vercel** account (signup met GitHub kan)
--- ---
## Stap 1 — Nieuwe Next.js app ## Stappenplan
### Stap 1 — Project scaffolden
```bash ```bash
pnpm create next-app@latest pokedex \ cd ~/web # of waar jij je projecten houdt
--typescript --tailwind --app --no-src-dir --import-alias "@/*" npx create-next-app@latest mijn-portfolio
cd pokedex
``` ```
Open in Cursor: Beantwoord de prompts:
- TypeScript: **Ja**
- ESLint: **Ja**
- Tailwind CSS: **Ja**
- `src/` directory: **Ja**
- App Router: **Ja**
- Turbopack: **Ja**
- Import alias: **`@/*`**
```bash ```bash
cursor . cd mijn-portfolio
npm run dev
``` ```
--- Open `http://localhost:3000` en check dat het werkt.
## Stap 2 — Eerste feature met Composer ### Stap 2 — Naar GitHub
Open Composer: `Cmd+I` (Mac) of `Ctrl+I` (Win/Linux).
**Prompt:**
> Bouw een Pokédex-startpagina op `app/page.tsx`. Haal de eerste 151 Pokémon van PokéAPI (`https://pokeapi.co/api/v2/pokemon?limit=151`) als Server Component. Toon ze in een grid van cards (Tailwind, ~4 per rij op desktop). Elke card heeft: naam (capitalized), pokeapi sprite (`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{id}.png`), en pokemon nummer. Make sure cards have hover effect. Gebruik `next/image` voor de sprites.
Accepteer de diffs. Run lokaal:
```bash
pnpm dev
```
Open http://localhost:3000 — Pokémon-grid zichtbaar.
> **Tip:** als sprites niet laden, voeg PokéAPI's domain toe aan `next.config.ts` (Composer kan dit voor je doen — vraag het hem).
---
## Stap 3 — Detail-pagina (optioneel als tijd)
**Composer prompt:**
> Voeg detail-pagina toe op `app/pokemon/[name]/page.tsx`. Toon: grote sprite, naam, types als chips (met type-kleur), stats (HP, attack, defense, etc) als progress bars, en height + weight. Link de cards op de homepage naar `/pokemon/{name}`.
Test in browser.
---
## Stap 4 — GitHub repo
Maak een nieuwe repo op GitHub:
- Naam: `pokedex`
- Public
- Geen README/license (we hebben al bestanden)
Lokaal:
```bash ```bash
git init git init
git add . git add .
git commit -m "Initial Pokédex with Cursor Composer" git commit -m "init"
git branch -M main git branch -M main
git remote add origin https://github.com/JOUW-USER/pokedex.git
git push -u origin main gh repo create mijn-portfolio --public --source=. --push
``` ```
Check op GitHub: code is daar. Check op github.com dat je code er staat. Controleer dat `.env.local` NIET in de commit zit.
--- ### Stap 3 — Vercel project aanmaken
## Stap 5 — Vercel verbinden 1. Ga naar **vercel.com/new**
2. Klik **Import Git Repository**
1. Ga naar https://vercel.com — login met GitHub 3. Kies `mijn-portfolio`
2. **Add New → Project** 4. Framework: Next.js (auto-detect)
3. Kies je `pokedex` repo
4. Vercel detecteert Next.js — laat alle defaults staan
5. Klik **Deploy** 5. Klik **Deploy**
Wacht ~60-90 seconden. Vercel toont: Wacht ±45 seconden. Open de productie-URL in een nieuw tabblad.
``` ### Stap 4 — Vercel CLI koppelen
🎉 Your project has been deployed
https://pokedex-{jouwnaam}.vercel.app
```
Klik de URL. Werkt? Klaar.
---
## Stap 6 — Test een tweede deploy
Wijzig de titel van de homepage (kun je via Composer):
> Verander de h1 van "Pokédex" naar "Mijn Pokédex".
Push:
```bash ```bash
git add . cd mijn-portfolio
git commit -m "Custom title" vercel link
# kies scope (je persoonlijke) en project (mijn-portfolio)
vercel env pull .env.local
# trekt eventuele env vars (nu nog leeg)
vercel env ls
# zie wat er staat
```
### Stap 5 — Cursor configureren
Open je project in Cursor. Maak twee bestanden aan:
**`.cursor/rules/general.mdc`:**
```markdown
---
description: Algemene project-conventies voor mijn-portfolio
alwaysApply: true
---
- Gebruik TypeScript strict mode
- Geen any-types
- Tailwind voor styling — geen CSS modules
- Server components by default; "use client" alleen waar nodig
- Imports met @/ alias
```
**`AGENTS.md`** in repo-root:
```markdown
# Mijn Portfolio — Project Guide
## Stack
- Next.js 16 (App Router)
- TypeScript strict
- Tailwind CSS v4
- Vercel hosting
## Conventies
- Server components by default
- "use client" alleen voor interactie
- @/ alias verwijst naar src/
## Lokaal draaien
\`\`\`bash
npm install
vercel env pull .env.local
npm run dev
\`\`\`
## Deploy
- Productie: merge naar main → Vercel deploy't automatisch
- Preview: push naar feature branch → eigen preview-URL
```
```bash
git add .cursor AGENTS.md
git commit -m "chore: cursor rules + agents.md"
git push git push
``` ```
Check Vercel dashboard — nieuwe deploy verschijnt direct. ~30s later: live. ### Stap 6 — Feature 1: Hero section
```bash
git checkout -b feature/hero
```
In Cursor (Chat + Plan mode):
```
Plan een hero-section voor de homepage. Specs:
- Gradient achtergrond (subtiel)
- Grote titel (jouw naam)
- Ondertitel (jouw rol)
- Twee CTA-knoppen: een primaire en een secundaire
- Responsive
- Tailwind, geen custom CSS
```
Wacht op het plan → Apply. Eventueel inline tweaks (Cmd+K).
```bash
git add . && git commit -m "feat: hero section"
git push -u origin feature/hero
```
Check je Vercel dashboard — je preview-URL verschijnt binnen een minuut.
### Stap 7 — Feature 2: About pagina (Background agent)
```bash
git checkout main
git checkout -b feature/about
```
In Cursor → **Open background agent** met deze prompt:
```
Bouw een /about pagina met team-leden in een grid.
- 6 team-leden (mock data)
- 1 kolom mobiel, 2 tablet, 3 desktop
- Per persoon: foto-placeholder, naam, rol, korte bio
- Stijl matched onze hero-section
- Header bovenaan: "Het team"
```
Terwijl de agent werkt, doe wat anders — bv. tweak je hero. Wanneer de agent klaar is, review zijn diff en apply.
```bash
git add . && git commit -m "feat: about page"
git push -u origin feature/about
```
### Stap 8 — Pull Request openen
Op github.com (of `gh pr create --fill --web`):
- Open één PR (kies welke feature je eerst wilt mergen)
- Wacht tot Vercel-bot de preview-URL post als comment
- Open de preview-URL en check je feature live
### Stap 9 — Mergen
Klik **Merge pull request****Confirm merge**.
Open je Vercel dashboard. Je productie-URL update binnen 3060 seconden.
### Stap 10 — Delen
In de chat (Teams) plak je:
- **Productie-URL** van je app
- **PR-URL** met de Vercel-bot comment zichtbaar
- **Preview-URL** van je tweede feature
--- ---
## Eisen ## Bonus — als je tijd over hebt
- [ ] Repo `pokedex` op GitHub, public - Voeg een custom domein toe (Vercel → Settings → Domains)
- [ ] Minimaal 1 feature toegevoegd met Composer - Schrijf een tweede `.cursor/rules/`-bestand met `globs: ["**/*.tsx"]` dat alleen op React-bestanden actief is
- [ ] App werkt lokaal (`pnpm dev`) - Probeer `@-recommended` in Cursor chat
- [ ] Vercel productie URL werkt - Test of background agent ook op grotere taken werkt (bv. "voeg een contact-form toe met server action validatie")
- [ ] Tweede push triggert automatische deploy
- [ ] Geen errors in Vercel build logs
--- ---
## Tijdsindeling (30 min) ## Op te leveren
| Stap | Tijd | In de chat (Teams):
|------|------| 1. Productie-URL
| 1 — Next.js setup | 3 min | 2. PR-URL met Vercel-bot comment
| 2 — Composer eerste feature | 10 min | 3. Tweede preview-URL
| 3 — Detail-pagina (optioneel) | 5 min |
| 4 — GitHub repo + push | 5 min |
| 5 — Vercel deploy | 5 min |
| 6 — Tweede deploy test | 2 min |
--- ---
## Veelvoorkomende problemen ## Vastgelopen?
| Symptoom | Oplossing | | Probleem | Oplossing |
|----------|-----------| |----------|-----------|
| `next/image` klaagt over PokéAPI domein | Voeg `images.remotePatterns` toe in `next.config.ts` (vraag Composer) | | `npx create-next-app` hangt | check internet, of probeer `npm cache clean --force` |
| `pnpm: command not found` | Installeer: `npm install -g pnpm` | | `gh: command not found` | `brew install gh` (Mac) of `winget install GitHub.cli` |
| Vercel build error: TS-error | Composer: "fix de TypeScript build error" | | Vercel detecteert geen Next.js | Output dir leeg laten in import-scherm |
| Cursor Composer niet zichtbaar | Cmd+I — als niets gebeurt, herstart Cursor | | `.env.local` lekt in commit | `git rm --cached .env.local` + check `.gitignore` |
| Push naar GitHub: permission denied | Setup SSH key of gebruik HTTPS met PAT | | Cursor rules werken niet | Check YAML front-matter syntax (geen tabs gebruiken) |
| Vercel deploy mislukt | Check build logs in dashboard — meestal env-var of dep-issue | | Background agent reageert niet | Vereist Pro of Business plan — check je Cursor-account |
| Preview-URL geeft 404 | Build kan nog lopen — wacht 30s, check dashboard logs |
--- Vraag in de chat als je vastloopt — ik kijk mee.
## Klaar? Verder met huiswerk Veel succes — en wees trots: je hele cyclus is van leeg project naar live productie.
Het huiswerk bouwt voort op deze setup:
- Feature branch met **Background Agent**
- GitHub Actions CI workflow
- Branch protection op `main`
- `DEPLOY.md` met reflectie over Composer vs Background
Zie `Les13-Huiswerk.md` of `Les13-Huiswerk.pdf`.
---
## Tips
- **Lees Vercel build logs** als iets faalt — meestal staat de oplossing er letterlijk in
- **Push klein en vaak** — kleinere commits = makkelijker rollback
- **Composer is voor pair programming** — accepteer per file, niet blind alles
- **Sla `pokedex` url op** — die heb je nog nodig voor huiswerk

View File

@@ -2,7 +2,7 @@
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource) %<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj 1 0 obj
<< <<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 7 0 R
>> >>
endobj endobj
2 0 obj 2 0 obj
@@ -22,7 +22,7 @@ endobj
endobj endobj
5 0 obj 5 0 obj
<< <<
/Contents 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << /Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -32,7 +32,7 @@ endobj
endobj endobj
6 0 obj 6 0 obj
<< <<
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << /Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -42,17 +42,12 @@ endobj
endobj endobj
7 0 obj 7 0 obj
<< <<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << /BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
8 0 obj 8 0 obj
<< <<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << /Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -62,7 +57,7 @@ endobj
endobj endobj
9 0 obj 9 0 obj
<< <<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << /Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -72,85 +67,96 @@ endobj
endobj endobj
10 0 obj 10 0 obj
<< <<
/PageMode /UseNone /Pages 12 0 R /Type /Catalog /Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
11 0 obj 11 0 obj
<< <<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607134605+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607134605+00'00') /Producer (ReportLab PDF Library - \(opensource\)) /PageMode /UseNone /Pages 13 0 R /Type /Catalog
/Subject (\(unspecified\)) /Title (Les 13 Lesopdracht) /Trapped /False
>> >>
endobj endobj
12 0 obj 12 0 obj
<< <<
/Count 5 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R ] /Type /Pages /Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260612063152+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612063152+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 14 Lesopdracht) /Trapped /False
>> >>
endobj endobj
13 0 obj 13 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2127 /Count 5 /Kids [ 5 0 R 6 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages
>> >>
stream
Gb"/'=``(P&:XAWkb<s7i>A9+Q=QRp\Z=?_9,:jr`UP@5'9@!H%tWQjs1U',Ou"o)fHj=(;RRlE?[#`1?amt(ifA66F9V0g5!4/5J8s4+";[.3p><`FrSU=6\E#,uJ-\k<b^m^Lr]trH'G*h[@idGQ<"Z6!#W3i1`u6q_;i3$&%3448(_A!mdNK1H,,s\THpK%@-orW9Pk!o65sK5:MhrF\6T+aj"N(gf0Zp)HOAH^l#kh=RgO&\Z%D4<S9TpNaAIa3u[7QE`=E(G$l'qLtVTG:X/E+/8C@\Z6RA.A"q^8Utho_DGLHHhE%b5k&\#'0Q!,lGVX:kj;4upcC"P<kC"+Ji69_(JP.2Yp%,]Gn!rTJgNH)i&e;?*IAjOf7pVp`;IXFSfPqmM/7E"0eiTi&bp>i-oc:b2g+``V#WEl)'W)GFK#_'$tp:le)ZYV`#O7ZQ)/r,B!Uk_HuoJ4iV(em+m8*8O&*Nhc,[r-u.rJWN4_WU3q&WbJ9Bm5uFZ3FT)2g?om)G1T)E)YLuHZgAMZ+q\=RH,Iu@,8Oe%N+$=JI5F'4M)(k9(j%;m7*NT-1!5ms>_#-U.93YIiir14M(>2/KT7Na7ht#]ka9;Y=X=XTI<iC8&R1]B:f`(6[P\4N!+TaP3`qh#0>Bl:,W0s)F_^It_Zb`B90!hAdfD)r,>$^A0*1HHO'`)kUn.A;n_?*rK$aH1[&j9lcS"EnefO^6#EXrsB:k@9a6/\YC)*LFfai(.Cj$lsC3hoo%UFefO:fgjlhY?HXp_sPKtu?cMR?abXI,o2dPR:nn3[D-Cm0%u>f249I2fH3n[_NW_e_P!=dl'NGD."=,rr'M!^$6t=ebL3d3#Quk02>QYg1sTg19u-9:HG2=uidH,#!A&).:@_rI@_S!S^3=O&stZeo?Cu'ne@1B`_uU54/oE:alYE-+J;00O$i4*97;a-#FgMZcZlp9>!5d3o]P*;FX!F-XJF$D^+J"KUJCl>*^!m%t^&3d.'-8\Tl:&`/`]0.F]Q<G`2&K*<u.;g?G$B0mp\G7<$imb:Qph9OgfeRisXXaIFODF6p]IEh`OQ)a1H4Rm,2IfIl&J17pNB+6`=U4HH1<%O>`LKq#5Yh8q0&H^YXfCg&M1qBJ0E^kk?p/\l\,Z$f0X*SS`#E<"Ed0i[86_8lB/-ft15)0kfbq:\\"8ZM&!*16oq?c'rF^6q,gf1fDcZZ9'2NN)<<C>BjS4*:u2.ViCp%itep:Y'Bn9%j]3/f`Bi4:_7W,m2JWlN]p.2,RI">o4P)Q^A:nI:W^2csO-cALOgP^oZ6S\j"@&R)*L8-[f51UMOh3I+f6nA(&0ci<(FlXu(0)(lSO(GM&:L].m'3Yp2[@a6,Qmpm1`Be,`Do`>08Pf:I:7LWRi#M<.+LYurlecI]:HdlS1(7BU$cfGWZUN>7Jum*jCKYl6FshC&)(9nJ)YNkut#Ub?B+iM3.bS,8'NC'%c28"j/e)l.lB.6/`E$_?Du\S&aC\hN[4o(BD[QZ](a\Q<5OF8Ff<<(443Ls%DGs*@RUr-X,d=Vp7!^+5QD*9%fQ+>P2K7oR+f=jaTJSn"J(KZ,M0As'H*/acnR^TUI_^AXh.)qs25jHfa9R[3848iuDJUK]G>LFeo/n2i2=7;T9)@U7ZG18G7:9o9tWH!r$2a:Bbrr"mc<.adj[9_;H"=89)%VY;jP4u9EU^:!#)9C8ajC.F(!B@7e<=f5XAiNYXkG@0D7U#.c_ZNR\@Q+"^7e5#XA^1nrNUh\VYa"`bk#-!Df1('p,%+=Qt<Hgj^K.AhgEZ2G/K^oT<(J=#8C[/a7QXsGiHQ/p8FdVX"nW,b;"8D3=b0aLd0lH6nLiheuG26t(K![EBmc4a\YW8l;P8me$4'%]P=R84F)t=%HpO_uX&7@\?^K7:(l`=i<,K]L4XDRJ>Z12"^o;5:GbH/]QL1P%X;Y)GRi6INVp[;?>MrU8Yj2Fbo\f>k*I+%X<UX)7lY&[)Lj)-IF78pZg;rT]KMYAt#U-HCR7,MA3<mdeOEE@7YF/WboNZ0'.]W6!i2%"X<6`q/6Y'm`^5'XE!J/>QLP8(p=4PZOTf(mT/T<mP)gT4M,>MRuiWGS+=.<!WD:N#%@BMQ0bEWa.m?-mRY~>endstream
endobj endobj
14 0 obj 14 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1391 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1626
>> >>
stream stream
Gb!#Z95iiK&AII3b[jX!88,_*8HF\mg[94e/@sNcD(iFR&oB.b7'37E5n<eH4/<ou*_gN@,KCL4br;En50f5j`JaQ\ZikWgK&oK,N5KB>9G.Z4cgY?XHj^3]4H^j!&Q=g2+Gk&ectn:@6:Fkj'*^1N8/t3_0dC\.`5'bQMHC"@Jb=#uJ4,8aYlk]G:rV^f]c`2RF=g(_<:\OfLbEr1,t&3i@@6uu_=.ah][VTa,bZcJ6<KMWq!H73(\#Oa9Ufq%/`=sX1$Q-!/']A!^GnApgUuoI#/?P=.;W:YW]Pg_%BPS\Y8P(8P41k/O7>H((75(ACn3G0K"fa(][FT.=C3ed,FIIXYY$0oWDFc\!F_3=$BmB-II^[l!mEFY+g*1h"u;)4KMQAP+Cj[F%ndQdMGQm>q+<b5P6TJS6SbGKbX5*?+]2,E(r7(FmN7lAQrXd`0I[&mU,\bC9FkP'i%#9#+d`<Y;lkWMiGB(6Bs8&Z]%kho@/3Ybi`/ZD4Qp=q>s]1L\,q%2&Y`c2h0XKq#KB_^Bm1m[pSVl+S$)19ZSgsX?K01jY.Z29CLY4^$'@!'\]=ZEFTImrkQ[:U/7dU)Be.j%hV7kf`S'R5@73GS+bp?-mCKkl>:s\Jeoa_c]>S-gkC*T.Ik("Ems[Z;YN&795X_l4eO9jqQtC7Tpt:t@X\elt<Ybdc0T]@"3L_AAI,(]=ZM&E[+\3k<n'S<S/"IK#H'Or9G6p-QC%<40fZ^_7cIQ&e"?mM]j,mbcbdr-G.3)p`$A?I'ZnC<PO^]@m?/uMC]/i!(+[OK+:#;9O^c*)c"P%?lRcY&+a3bQ7?#%T0D,SU$YNEF,c$*!Pqehgg:)IneD@CdFgodM@s*7kY-qYGlq*IdP6L4if3)<IOG:4Z5V(>uW>6;Rs<`%Eh$LI,Amh`ZmOWk)Fs+A`Dpq3P5rT"B6cA3t"TpjoQX!9St'a4!iA4%BgIp=hmP,PFm6pC<_0cu/?Pl#APYhaAJi-_C2=F@)F[;O%FL+dACMlgj.1I3H1HAgU)s,/B^D"e+uqpb)aLE'OF[FPf_=+O&)&hDNaaEQ5?.j&m0\hMcddgjeMY?eb0WtHjZ4[g%lS7"DgKq$;5^LM;SL60\k_OT"1__RqH!CWmh3o6cJMaK3A.`_Qs66/s]6\mtUiIUutqD_F+_V`C1"n,-M4aS!pI1l'H-?pSg\Hmu^%.t3kd@K9m5CQG$YYcq"p>j[OC!8!2M6"t%m`Y@G24elVkF2'VWMcMdI[/NV2s3q"'o$5iPe+mUjP;Aa`As7T^>u[ujedYsB;R_@]eu:aj8@7jAkVMN[K\RSFB?VhPuR'sK2%Bu?(f/oZbmlk]L-bp^^ZSOMi)W1)b!2ds2D_"H@qKp_uK;<i4f$qJq!d_"+6s~>endstream Gb"/'D,8nW&H88.1"&WnTYu*,mmnf@B^\QI$AF^nad6SP8K;QM]3Zk]+7?VqfSQ/LPCa;OWR>4jahGtTo??RFfE=m\8G/T&!%1,nr/1QK0H:2"%3(&bn6suHhBT(d/iDF7"p@'R)ST_8\.>JHl;GrO^p&48$7HaB<W7N;oM?lK?sFNq#XO%:,m:=MqWB%m1`0*T!Ig>;W#2/l4W)IP\6g`T"g;c8&C[k"f.!!;]"n"*B#.-J4H[SaAcb@W&Lj4c.[LM!_MqAs`%[55ib=*PbMU\0!GI=TPVaL`Kl*..#$q2-$1r/gh,/l'3N8J9_suF<37"@)M\+a'\VOg_LfD9,@4eF-Oi8LOUNFp"nbM2,$pMgjaSAfX(qs`2OT1W&DaN:)V+TGSIYrB?CR0_&"B#GtJ;b4-786=&e/qPSO95`F6B1\BP-6al7Ga>W(GhrH:WSb/$a(^/pj.(CUgIG.+/6,qi_u`E*fKY.%\Sj@gN7YX3.B4*G\P+/QQgl^--(=ffJrdPKLM4E2*&iNl$j<W8uZ8KZ]YHY+qMKXH!KDA7Uee`-kZh`%MKF0Q6&:hd[Cq*a0"Zp7[@\=CBKT"MLmYpVj5>->grIGi6"$J$hu9%BOd>==I+N^<X7R`oKig#$Zr>aF,frfiO=]@I#9-T$I4m'7q,=V0g>Z.*E`k$s&-?XiY\i:pHmq8#3\iN+quMf_1db*d9o(h?0W;njh!SpD?lt?n5>$k_a$UJ;d#[oUht;_F5.\*"rm&D28'.IE&DLo@37&8S&TWHnRhQTM!+/)dNXE5Leeqj[_Oq2O_spZmmqpZD?l,A=^X^F>9A3m52VQ<lboEeT4,r;0TAdn)"lWFJ/*A;3$"iV2imZURc4E,?]VB/X!:up'=>SW8Of2%1eSsC_$&oDdds>>d!nuDrt!nt+uN/l3I>P->?0ETs&)FM@lPq.-l%#fV5+Te(A*r!.D_mYiF<VZR1_(.DAKlA-"(<-16H0'HU`.`0J.)@P9nT9qegW1'kMgrN71d*Fh(34OE3<$b;alEV5/Z\\=Qdtb9/,<>QX6gOp]\amFbkdeA:YTP+M&dL@5BHH7\L;^0,>Y&(s-L/ohEM2SI?I^L"g)c'W>cJa#.i&lk"'"sL.)6>PQ:C=-4;HA%V7HAr>BGeUpID6AS\CFkLVfO*B@Y'$l@2qG&po%p`4hFB3AX3T]?"I31FkZ2DF_g?qH:dSH\BGX+K93:h4#lbksYj);f;+CGW/:+X9<n<R:NQ.,APsr2c@(&V.n"nAfqZ0r1=VcW($[5a]ho5@Bf8rD,FHB(j0fC6O+>gM1"u9Ki7d#DO,$:i]fQDjIPGi#TkuHX8b;)YZ:!aA]P)rbhq[HZJ`':o\`&1UnVN4o_nL+$g`#ZL"k,tsMg@jM%4A*6>j`PsRI#2,d<`bou-sg;2%&__/*NaBD<gK(S=(CX+cIul9Y4uG+2[^jX0%<eTGI7*'BN:,`)<OO,>,[Iir<@-#YY_eaA,NL1Bs6A.Fu8P:.>ntG3+D\\lf$jZiVrFq6>UgG-2dd\kOlHXSINZ(4\Z!X.,T!fTB]UA\Al@U"76.Opf;c:[EadB%qE^a4I*H@Z:r_`j\4INVh3elaE7)MAKfQoIf/1,HN+%L-0RY~>endstream
endobj endobj
15 0 obj 15 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2009 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1392
>> >>
stream stream
Gb"/'=``=U&:WfGf^Y/r=qE;mIV?c]SYM$_b/nbj50sufdH21,ZBJnKhqeMIOGXRj)3<4$=;mB,!daq)Ib5qKMB>n!%hjKN-SU"fJ8ps(!%*PUa`BG3=<B-3*UZN=M"%A43=oQ]5:C,E$FW;ATDK$Zl4VF.+?`1VchDtM4P+-s'FI#dYS[r'TroboGg(=nkK80<*6us=,dub.hp-0=o<`j.EE_hXc/sr,E!0s/10TAWPhE2KP6A=$]R9GfSn'WWL@ZsSiOC@:33V7nEu4PBNqUXO.P!B1"*L<s/5K5KmD.ck-4dJcJUY]eiP'E#oflM(0EHF?nnA[QeZXR-PQi67\mFF.G01f]3pT\)#XMbOO'<FV'"BomOH+E*Z+*Ms>9f%XmLi-g2ir$_*@2#5*Bo_?h3cPE1;%$:@p5-0q)H[/_<;Z9).OZM"<fhYh8^;KVlYA=N?W,=KGNsgM"-7?ZCPgK(doEKF0!<Gr\W@oI\cL4"5"c'$^&960hSsr\,_80KkGc*[`QA"&CrE(\(?1[Vq1;MIT=/D`+D2BG=hhh.`g$-)@?_L$DMd02i'HHcLNI-;PiB:SQ4lmHtq$%P7j>Q1@T;J[*@<%g17L4F8KC3jB8Gha:#Y]p6LYCD[mRVk_.GnA/)qu$oZ]APVj:4jU#kB:+YY@:%fN/Ce9]QM7pu,KL;]6mSMnc@m[4<jV!n?[]EJO18"?sXm)][m]q"tn\@[l<W>GHdIk/gA(iH.]e'[bYE+8.^:*Y1B_pJ!+Xt412hG@k88Xc7bY"?a_JSf5/lOQn/chfk'P'3,Gs9N[F-U;#pocMFeDFaUTL`(3,1dK.VdB_h$I0RmP:TB$?rgg'Jhf!R9'nq`"N`8qT6'(t+>+h-7sTpk9iP+c8*@-\'IPuS\(flO5FT#r4(T.+NAa2\XAEI7Va42J_H]Mc,fa9MZG'K#HZuS@2G2;8D'07EBrRF%n)miJoJ5VI$8;k[75*"hZ7;tmgi?22q^:WGP]Z:eOKNCjpMj.q=e&qAO_62l+FSlsO4OqY6`5(#<7C/`L10s-#gOTQeDaBV`\]jugNua9mV-rNUNEM"[Gjs;p+GEadl7\Y@A[!2D[uLPNXj)X:hcoMC=\[`H--^`T!Wo8D!p$&OL0F16D!K[de<'OOR^+W1hiF$L9uqnp<%&eCVus-kD#4ma(rK8U'dVO\tNNuVV!N+V%CjE$)UX'_10A/5u.fn,f.YPJZYh`0FD96eY!4$UM:8L(Ia`,clMpdG;pJIqU6WZ_`<[9+:4e7%Wnm%(J)5aWp$Ce,/UYok#I\5HfHhESM*_E3^<W(jmi5qn_:r?j#.:dCt6*+cN%f0?^0d6U,2A"Vq7gN2Agc-IhM]LM;HngdG#,X^JdS"@^o7RBp;BC9p"SN_Z;#H>-rb6q9i+qj/*FVjs/oCXpVoU7r&h#lt/SN[-]A*96WLU*X>CsFA]5tcFRVAiiGlk/F&[Vksbo>kO0]d^.+'pV*"?,qa`Iqe0U'&Ki^]dVS_VaB]Qip,M%Z?Ic$/KMm2b9;dH(><YPmBX-r'oq7X]sf8,cC?bC6Y@\88lVc6OP?@Joq=kNj6#k2el@tc-k"H+*9$#Cu@oTkU--";4\Lqe"`j66D5)0hhF3;\*BjQi<#47:c(``eW]CcSruodd"k*BNa?PUam68!8li/*mVpGqm,)KW3Z!LY%nk&>.Z7)qLBYs"``Hbg`$.D$T_TF:qkhRNJEH1_SLmUV3c=f9f2Get:WiIY4E+%d!f#e%/,<eeh<6odcuD->U/'o]7W.>j=6J?V,TP"F7_]T&TS`DK0W..+ZLj^&.>hk60YBik5*sSmfn\C\0U1Oi2;PD*JE=9bqd;-42c2@K206/'uqO/eoS;L%m,c1<e>[(j@@Jkj4p4^NY;[WfR]d[P+Aj1f5V^CG7!.GfSnFi0C;"6V\>oL1)+WBbc8T>;WIj(c9o4pXJ"=Wc`0Go7g4uLJr`D<)LEo@rN$UYgmp/>>9sdFVLCC>mJ$MbTC6k#FUDDE<~>endstream Gau`R968iG&AIa;m)0]#1P,oEHtB1A\oI#7j%`L[^%_YkYXF>OJ=tHdf69N<Ua59fN2AHqU9qkQG<^NjCqOHQ).`e4:>k8p!bF'2#0I7-aTHp\=Pk[@53Xf]Ke0%X4,a<Fbu"un'C(rLfY+>RKf3Z6"q>2.Jt__S7:7*oIab"o##C3Rn0isA:Po(]SmK<F!L*7)J1l07$,U19Z$e\j5nIe[d%`;M"5=-M#pHc;E5GkELQGZopIPpL_&uB&*nWr%&Fk:ZLHK$egY\4uh3l@LAK`?Po3JOSe]Afj4+ZidB0::fL\_:&$(;c]U_Y8*&8XJ;#@mJc/$eLbQkY&BZ**bTCkf-U27K17@kGsUp?1S`J["Bo9nIN22qf%1OAY.k(D>#GJ=.I=Ar26f:\7,?p8GJbEOk,V?:DdeOQ/CW-(a-%]h277);`@N]JC&T*S5I.SJW<qVb6$t*6+To'1mXXR:59GkPIaEn>Yj4GaO,HAH@pV6;,C_76W=3V4WZ&.:>7jlY2eUpn]HT(!.Og,HKs2Zb::_mBE5LDgQY`2QPL^X)0mff_St"@!"Jsd1V<L0`QD]-!3HhKnb<"@`B%VJk?EE$/?<,<^eK8@Lb4lR;$>oNeknd86SD*>sIT/NSs@+irfSe@bEX&T1"ml/*l2<%#J6i2k1XBrfC7='M;@VA;#"Nnk:TGIP;nr>6i)pG9\6BH/,MjQ`^A-*?[`eE7R$'KH]abEE^.$iMdf#RnX,XoPi'b9l0+kDXZhLA+3&TDU<L]]?]s1V(%[tMORkR%_>0o]1@\CN"!DgbOCiT=h[Ms4u3R[hBDA'=0kM@U&I6=-,3!:c5BdUn'!PLeZ76U7[bRdZ&>EucU`!De-Eb?1u1'<VaMk5Kj&qU8,8JuM[H%FU=R:CQ5XuCl2!qo(J2n8,In0lkYJKXMq8X<jUbsCfAD8*[oN/u&^<1"Udq[PQ&LS_Q8sM'9A*]3ial@b3g&I[^HNJsQRL/n=g()Kkqj.F)48]PGZd`'>F9m:E"C-d/7j,1chh*PJ=OO?_>hgf.C)ml3SibL'MHDNfBG:=F_H-#O._gV:>#U13FOSG#@mJclTaNbe92/kgnpF5.#*AY[#[^;kW]JZh=dZJBp(-1CLJM0_^QP,.p*^/XJ2GRL`/g'8u\hbaO=\)PY]n]e(dfPV]F":_NbdVpIp=R:'+Q3P]`G3%Y$Ntnm6lJEiD@=aB[=Vo8U9sTaWL72<;^bOKcu)Ktg34cXXVWiMY4_hJI5i/#7V,<lc@E@X3je@b^c#]a/#U/R[AbfR/C+1k/84[?Bf+^i6`6GoOCS34b4MO<K4a5nNfB`"M]78si5PMSWRc90\pY&U%`AkjU+Cg%WW4;<.[GeZ#WfAho]FUa1knh:L"k+CP-U-IK!>~>endstream
endobj endobj
16 0 obj 16 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 627 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1554
>> >>
stream stream
GasId9lJN8'YO<EN-Zt=qJ&*P5IctF7AU?i'RJaO&pL[DCmH@6Ol)2jSHT/$'uqtVj70de!"Vd4n#QTI,e1E#&/*(uKHc-2*q'a8K9=[3W#-4O*]XOI;?*<BYleH0g.<Z"\KP`&PSI`2Z]H?V(h(d94C'WA17B/_5ZYhWl9FhadSbh%'ua@ght&4E[\r:Z:[A/-Ym7M;6;3/<:a8D6`$RrOiV^U6a#gr;GJ;=Z\h<N`COB0)p`cQNp"e_Pg[<hAkBQ<"Hs%OAJ*:2^q1dt)heRkJUa#&f3Q8c-/OY7GeAo3n([+ZZDLY3N2HUJUf,79*M/3`W9Wql*0bR_N*CO,O;>a`W(/uUuLU]^jPVJrk9<;[Hnih%&/QL3:'fXm'S_%CgM8aH_P2LA2RU_Yn$kHt>fM.8>#"p4,Xcnp)E%JWsbuZU5mZfN@Ot":ZiB$)(Vcc5?<j9DGI2bn"FW8.+0'6[lT589*a@RO4U52JgV&`1;gNQ3[*;&uOcT*aodCXG@am,l5_e"P.S*.5\.s1ZkoOn2YRE$3,ptQYb22AVu3VkgoZ(Lla#AF8'E09s@Oa@,SD[N+ba.mQ.?Qr;#ibP)C3T4Vh>EijbS(B(_0:kp@4>=!HCW,V/~>endstream Gb!#[bAu>q']&@2mPp-Ikm5:e06^@H:#*t&))kU.d\8E!9=4L%8BTK?pOZ2_j-Y!+:*kljK##4A`PFP7",^I:r'r3O0+0c8aFJ84g]E[-_Vu1l/=`mRlY:a9GTdUfL'f,9&7@ST0Zl_C_0?Ef#W\9[P;s7$i@^FOO?1ha8J$Qk=I(p%TXNf],QlUund(N1h_,<43fu4_0bG$F6a)(+b9S=LUY&?IcD5,(%1s!p#W4dk%O6E6m(K8@(`iL2*'+:fi56+Af7"^aGT/X^kOq($dXEo29tCL]9suJW\nkkrTC.eoS8f&Vo*P(=ekBL1T?iR,E<hErhl9CV_E:mX[9'qu9O(P:St3`$ZIZ##1__:Odnqu`nnbA5.b5n^o@uSLhduC'2q;c:`;0#\KE;Ao#&Q:KGu2-DX=07f.9*1qcaUlM'!;r.AHVM?L25SqrcTmF-PhlQQuJa;9HE$.@TVdZ-3[4H-B9QHo@66RdNtLSK)nPVTX*Cd6E'JNQ9l-iE8ENZW&T'YZW3LNZJ$UE[Y?*k5--@$q=V-)VbX+.3AR(N^>ImUCU;OqU-($u$n&94RiSU&Xf@TM-TJ,!%8dbuM^)%i@XEDtb!;8riq:6kG1X0p:T5X-$)t)ck7bdkeUF0@4=(GSi""3)Fh\:k%9HXV/!g!i>3ZdXrQH0lB,;XR0<RmXC3jUDW2VV+3!5q6HuAMf3^&;XaXFY@'(3BgpD56<$O(T$p'$>/*asZ4K0\tRD^u8/$merIL5/'fL(ZP!dBTl6(+AV2^a+YBC%Dg`7i?lY?:J,4?aZ%!(NhjJr(pEb./`7p4-ouAJotA5KBl,upCk%<'/ab+Zi6jHG9npRQ8;Jmpla6o2Tt+e&<?SGeEp?4@sOJGa.2cfn&^44JZYGU8h#lZP[3%Y[#&l+S%Ph!;^NJ-#`+D'Im0XOpW+K@R*kNA*%'#qfds]NB[[*GbE8<D5Jj=/XO5ET_g,,l7Q,fFOgG/T"7$+!0YE29Y,ibPU0*lJA-.Mk,kO5[OhQ7-`q3R!pIZa91.pL:?=DO1jkoIo7[9HEG]6]ZR$t=t<5$.s[k3Y_[Y:GN&g2EhOuuVU+T&%kFD^:OVg8/eWrnP,#5VdfI\H:@p4(U]P4)^gWi/apg9TuXrQ_Ke993&Go5njq&%-AbjK@+JH6e]\IXSAf3.@ekeQ&3@.P@Kq3B_?>/#0SYIQ5<g.5u*3[oue>d;WSp(2a%DCcYn_=t3_>q4D"HV`^M+QS_!VG;jYM&hlfN^72^t"<9XYS"bD>Q%fR!''lF4_r.CZ#p$kGf[h(SklI.&0lgp7!YX%\7.E@o+h`ih('F4h`QLAtUM,i1,^BW.m-YO9"LY[INPJ%;kGJKl`15ft$iGQ??]dXkIc0b(:"#g(ilO^IC`+sA/UPn/$h2kE`Q)jFYZ(<kRiG&U@Gj^iH@'>-+nY!uJC*;+aoYug)jr8NNi\s<XGX]$Wiu`X^(%JPDbS^%A;E-)]#T45b\qfu3n`H89G3$M@6Tbqj,r7k7eEU1p*_ZmKEnKBbKJT77TE"AX$gOD6#V&HpojbD^DiU.3W~>endstream
endobj endobj
17 0 obj 17 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2100 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1865
>> >>
stream stream
Gb"/'=``=U&:WfGfZ?fJ:1>FNG_,9Ma#WP<9q4aoV]EP!,Xg$,$&4-"35<08?D;7-X?DQ8i^;]+I(P;Bm'd?0-N;Hi1X#i*:#Y1q$C&AK#1OtX9TS0"eG*bijtb8+J6:U6Hj7NuoFI1kOb;[,f4R8NDL-t_2V=N4c@$02kBS-ATG0),DGVIICO4b?_PuR9pWe/E\;[+pRlIS:hk&`ja0g4WEEhg,c/oPQE!1$;l;9.&R+1XGQtESoiT'/7*6MA_iEXM>^8Oq_Es638H#R`0G>M:2V*kH)`0El)Obm=+KBWP5T5.R3I%/fBq6mbiIk3(B(>tN1U,(r+:P(RJ")nHHT5\'t0UK2iH6t#*AU<R$Xc%(!\2`9L?/((W?k-m'qe)nE4CXa;+$=&G*G#oL\SV&Zl,JBGrsUP/Vc6%VO*hr144%JAZl.=CcohY3_<s?A.))P$QY$b=p/45p>\]D\9s8(h\Mn#+H"bE]3j/*nj8PD6FSsVhlk800aM;ZB,`GZ6!C^7O41o_U+8CQ:AsmRe&+JBnh^@bC^<MI$I>ltsr3o6DndW>%"4]9RhY1@dZ[U^:eV_sF=hc>rq&D(Hl7#p=AtU;"Xk'I0Uqs/,q9BlN]G20:8ttVkMN/UrK'$tR3*c^f/.)];]G-M'#-rdRM)nn3mm=9=:Qoam=:U]n*mM-iXF&XoB9FWF&>V9r-3CTjA>Q)X`>*hiO9l)oAQHlEP[!KrgrHaB.I1;`*Y\c_Mt0b3.PJ3CB0/OgA.XTuZY*K9ZhWRK[ZknfdnW=LMA1qG7Z1(h!ks%GDJ\-<(Gd/ZY,/4HfepGY\DqOp<HCuJ'$$_G&1j'i!gOl3$?TU:b+!6K5,+/MZLPU>3Jau*AHbX8XXh^68K)$1`ml*?T5Md>ak(O:IaMEmPC"C6Hi5&1^!l$dWffIWBp-N(ZRccq,eoVLf$L\@r@T!X$=KCdnn$@`a6^OuD&9)kA)$=h&78MUB^2_gDAM[<0kB0i!3jL5EUC/'NSq+M<o+.HeS8^S6ju,3eW-,tT\*(fRi$m?C%k!&NsA$jf1u;\@Ebne./]5s5rJZW5)=-7>Z=N+[2>1dcF.gcXWNHFbbd=,]nig4!$Ou$2S-2&9N[3?c#(l_O#@o!_VPP5,jAX_F<G3XZ;?D%Kh,:R^cI#uPd=)Sj4.Z+?<gF^U"cj1HVlTFLm/Z`Wta?'c'V6uS)s+Cf/`s2j=LEO2nD/$9:QGT-rFjH!]qQSlf(n^3j&=?rth'U3+j!R&;L.e<Hl;";[Ro$D\*/Vq[AT=GE9I4@Bu<)/DJ&X'\gm)K_8g\bJi$()<7cN6HpUr=JrYhYG<^&AL;G'^6S7O("mg:a\8`GLiL^DcUY%+[@Ve9-t#1[XT5P;T+TlOI'eM&5HkZ6S1qLq$708lIc"'GCDeCiDY-HC',=OcnH2pm:$jr]cV&($@f_p1&B"q:("1#,2U2OT7hB)oCs$2"7P3+=G+VSu<MU=s<8o0"8sFUt]2MuaCLJKQq(.AZW^cbOGC',tat*qk#]cnP-5Us7?'f`;DA&/G;F-bg?47`eKMGR*pq!okLZr?Q^=k3F"m?eH+.g]>D_SjR`$?mt68`FP7Yp4"U9%n9jQSeC0)g21cII1ON+gR(AC6=1SknL;kQ>Y8>3$g.E"SijhG-TnR;*MnLI.5.JjC`)j<`;hAFUUlM6:;q1-("\E<C4cLXYM0Uct&'3LE"#W\?.9W=!RQ;AJjRDIupnkA+!4798UVHo7Le$`mAo"do%'%u1M/1b&'"1":5,[um_fg7D)Fa5UL0*'@GX,ih5#]Q'pBeDFDBT"P8Hh'G*gLH$*bdM<EJc#/E<O[Atdn?ppZBKj[EcOf7.f5o#mVjPHlfKsu7k?'\TT2>h2JG;j)DA/F"bb9:1,dRGt-qJJtg8sC.$Z4EUZK[nF9Lq%VAu:*L5;3V5^Zg>#_Mt5KB"gecI5Xt.O4M0c0`%pmF@\"6gGp6IJoeK4<hS\Ol%?cBHl#MV^#,HRSX2V`c>-RdK((NJFf'_B=QF5+X"OuM?,]k6qIm>Gigk66d\RTLlWk&^]QnjEr:+]g2+MY_R&0YU+Ri/NoKbXJpC:Eh8.j+fL@YI#rWT?e_sd~>endstream Gb"/'gN)%,&:N/3m.__K[jFH2:;8*HUhQ#fS60W7quW%C"H*Cr?E@kT^'e6n`Nh$Rj(>0rCf=T%MoF]Bf`P,+nO&fGE<boY"u"k3TGGc65TSk"liXng9l;>c]@4*B(Kqu2YsUi@f>6VsKUOai.#c3^ZpP4Y`C*H@E/<VM4bRY2pc]"90GnZk`!K-9i+R-4g>:m&dkUeq9@2Q0:BhaqD;s2+D-RN.Q["O#6gpP6IXBNT_-fQ1j%2;*`".HZbNA$4*63pajqpdL-UXukoAaZ,YOQ-`RK%rNe/q@ZPAV>V-JKjR\nn.P:No>rMcq1[]PJAhL04`0bEPTV>f)X?as_O?#pnP*W<*\B99o%;/NA<^SB'ae.El[;p<t)'?:Z%`#cu%N!P]="&R8APiap)e7A45nf@.$c.`'U"dtaL)5&m(YoZgFTTb8)9dDq6Hf,tsS%XE%JgU82G:-QgR(L''1FYFB]-GF>"]oUj8F1@UN10.<73LAo'p@:O\9GAIkRnZRe/JDuHfXZ\QZTR.6S:B[4Y%0!L%Qr;a]9&CNf0UfsYg?n#F]k[P0!;?8S:%oWmVqs8mVXDjK(cZB'-6AkdGm'kCppaQYr2KSQPEJ[aJg<60PZ>hq:=65n(Mfqm`LOpGa7IV?Nf6@+\HL0X!2((,VJ\WZW2itFHIk6^V;rY>8+Pk.^%qIe+CP-T0aNeKVn'Q;2F6E8j/dV']SoD9k+=>78q9H1/Q&"0h5m=37]laa;/S.!_YXIJA@'ZTMK$^AU#ma314hgSN#=**OhP$*nf7K?C+;=C/BUd#RT3l?1=37iurNVR'RhH8#,rjal=,kE;5V50d8D^jYB`85R+PG?XLk^IOFpEpuCrN\Dp!q0?"B`>pQ6480ASOY,fTnQ\Lul;Hs1:mVu%\<YI4mhQ5?$Wa\!`D3StnCk6^=Y9HtV2Amj(@:?i]nHoR?3=:l)A[OsYH_PUE`.Vq*!+#Fc=^nX*Ug?H*2"J.#)BLc1Tn8X9`1[fa*,A[IgO%<!(<]fB)ast[?R8t,7>XXrkf1u;Xj0'-LlE1E*R*q64,"(^;8U*5dSs@USKETnNPa0:bo`ToKTs!4ms:Ib%^kj5HuFOWk]Slo&I]Eal/?0'oO'CDBBD2)1IHI]+26=s9=&/e`1EFi)HNA>7`lCW-s@s''I"\jks]O>bOm\jaQ<f0fPuM*_FgZ'V(rdZA3C#&.Mekn3Wg=`<j!p;+!hs]Gl]r!$DgQXc;g\L'M^AiWYC0O=efZVZmIrRE=::.O\eR4a/_hfUdu%3<dXs@@P)E]qI;qT43fN,L_P_&`\?23GMU];*+B?r[l]"<4j\FfE,Xp1nRAV5J_Q6_b[JbS'kTR**7bOpL)0;jgYeefdY^KLOS)9RHstZWGqhLs8f9-EPaL+#@Dc15du]C]*6!4gPppLEk[DK8>a'ijrkAd=LcTTb')m;Yk+d6uAQ]*Z^5I4D>&7QsBo3kHgR=ITS&H\?O+=.=)9e3PqdZeX\u[-.R75P'o=bgkI?.YbZ+32+@"L?T``uo1L9]mgDDT!jh;<ZeYD\]-c:50QF1K?3^^AXOmu7CQK:Z:0bBI26G$B5;4I#4b!CD3aDick88P0)r1"K2@Ldn[TgFh$+&hZCY6<Ac*88%1EpCPfdKkuGn]G;qg4'j%B4Wq`H>iFk8qDd%>h@*A`l8DF-UpMi1g+h[rkKMA[.H4[Xm@!K#:AF'H]e_nO*i@g]p1p4:^Xm10pp@5hOaFhrYCnL2r8(iJ0U7=9n94RP.X@YDeMetl)&]t#3MU85m2HB3O*:pI9$Tkf[?\,brXo+b%lS)!'Hl3&c@E]Kc))bc;24TF+*F0Z7c6.B$Z$KC`q)n&P!l"XOh6amjDXFVHFX~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1839
>>
stream
GauHL>Bedj&:XAWR,]R7%5Ouro$Z)`Y:"$&S*$64\ds]9KL"6kV%kugopo@p)$9s,jOj6JN-=AJhg>NY+ha9=*TmFS,ImT,!OH2k"m/u@N8DC3UU,Hr=F8K*/O-Nc#(^8gTf7)X*^-s!UVC46Djd,X9\-0dJN9^68[qV0C'>E_MAO'_E+do+]Yt;u.i?N>FK'D4Ut7g/m#c`9RdV;O9T(hn)25g4Qls='Y2J_WKKQI+?9s_D"jHL2YAV[)Fo.cBTmLGo2i9ONffQGH_$S`UqKCeC?od2mM9,2M=L^8k^LKRVCS\j!O0Odh\on68@NNI*":m-a.-77b<JlDnDr)?`%mF:7Op_U/n=o,(+9[&dEaGnk4k7`F^$b!\FCBR61<+,aQ3q)hk9q&BQc:g=k.Eh;pC"S2bhV25`RU[+WYH^H@#A4>-12W@Z*,0rf=)aOpU"6'&$TJdi:Il^?&V*eVVT2S>)Kn+d"5nq0ftq%5VB<p)338;%ebYNL,Dcji6Zn_,_s\J[ndVM5SOd]Q'+.$A<*Vh-'?!G.;`bImr"P)Hi@&SZqoTqPtFJC4oOMIRIaAh^`M\Zhg`28HM`"YQe+UrY^Oc;)pcZK4uJI>];5,P=Z;HXf4?8OX0U$T]%V&,_&+]A`PeFnp=1FPj6@*OI=?HeF_Ri?rqrCZ(7ig^i`W,fO<+=,g`V[.=uJP')SDu0<X7XO]O2JXFnK7phJgp`)C+qHG+cPgPh:NW"=H8SU,r=jjM22u!teoo=E4%s2K"CjVfP=l;NOfoo6?C^ZDPe'M4R.6/uSc#hG[Mhp@/<_I>-cLZ^L@3K(EOY!E(=O*`H5("p&i5<EKl3Fp4ZL7LC%6=\9Epiq]0HmDXK4EidWqqf1NdK'pF@ir4]7'LIObr@&H.FhC5E^R-4:5Rj97D<QAa?u_a@;39#e2:3FJ6fnR/L8H=%A="/,Yb82ZLjak"p+m^Dgs:)Z0$u``RU<rck6/PkkLQT"8];Ic4/n,Yhm".KoR"4&Z%MJ[QZhmH?E"&`=ePQ`GXJ`72%+hnhJu!K^b?+(5l(a-VjAZ.^Xd&qE8MN`o)-k4c#r@@qNgFgQV>TDdj7N`;8L(TYE$\HpU^/kY'nqM"'YCVBPh;4#8W[HRqYU8,i?]+`]9NSK?/21meDP]P>t&!Eo['U<^m>$HB&R^ML1.P>#fMan6QQ;/VP&YM)o6<HU"*1]HGKb@oT+IXbUc@dLK.8&9Bl4EJCh=%J0pcEPGneaGu4tAG4"cM5GQM:h?ZIp+^@:]AjN*A.8"</;%J&A*6"9mp9_/<YR$8X7aB&;tWdH[c/k*[:e\@R4[6eTYafn?O@E?$\#!e=nCpG@BGV%[45opN73r%0+j,$!\Q=[9(PB8C/@cpb+@,[5QjUcYu!!R9ZB1>+Ud[q#ul4%=7,Z+<Bq710JH(d^kT@;TZNrtB@92hZ8YE_?t?YRdBI`1S&cP2i\=*W[uI].^46.mVBsWj6-^2[&6,9YC>Dh>9S=-&<tst?=f3PE&YjY_P1*CioesjcW_Q7%:,&c76+P""-mSDZY*nUE)@WZ=J$()dA\!X2XijEA#!@o;AWM'XU:.JAJYJ'8Wb>5>7kACm?IrmLJH]a(P,^tRIUgEUEi?bk<j,_k-G5e,,qgWf.bQ@G4V3(;TD=no_U*H3q<];H*<oP0?q7M?IQk)#=EVu^5<t\:2hTlaZ,^Zmq0qDsN]f:B\cYac_b88g?0>uEfmZu,V"p?K=f<YaklMXbHpN,_qmf"*;<V_H#Vho^&J>jYFW=ogl&"(4kT<6o"DscAg^H!Ep2cbS's3S[;^'W]KSso'&.k?F$D^<h1g3)QD)Ij^%nGhXcN~>endstream
endobj endobj
xref xref
0 18 0 19
0000000000 65535 f 0000000000 65535 f
0000000061 00000 n 0000000061 00000 n
0000000112 00000 n 0000000122 00000 n
0000000219 00000 n 0000000229 00000 n
0000000331 00000 n 0000000341 00000 n
0000000436 00000 n 0000000446 00000 n
0000000641 00000 n 0000000651 00000 n
0000000846 00000 n 0000000856 00000 n
0000001051 00000 n 0000000933 00000 n
0000001256 00000 n 0000001138 00000 n
0000001461 00000 n 0000001343 00000 n
0000001531 00000 n 0000001549 00000 n
0000001827 00000 n 0000001619 00000 n
0000001911 00000 n 0000001915 00000 n
0000004130 00000 n 0000002000 00000 n
0000005613 00000 n 0000003718 00000 n
0000007714 00000 n 0000005202 00000 n
0000008432 00000 n 0000006848 00000 n
0000008805 00000 n
trailer trailer
<< <<
/ID /ID
[<6d65c6032a302e19cbfdfbf2e67cdcbe><6d65c6032a302e19cbfdfbf2e67cdcbe>] [<d68dbf84e79b8d0b36eb66a375a9e544><d68dbf84e79b8d0b36eb66a375a9e544>]
% ReportLab generated PDF document -- digest (opensource) % ReportLab generated PDF document -- digest (opensource)
/Info 11 0 R /Info 12 0 R
/Root 10 0 R /Root 11 0 R
/Size 18 /Size 19
>> >>
startxref startxref
10624 10736
%%EOF %%EOF

View File

@@ -1,517 +1,447 @@
# Les 13 — Lesstof # Les 14 — Lesstof
## Externe APIs + Cursor agents + Vercel deploy ## Cursor + Vercel: van leeg project naar preview-deploys
**Vak:** AI-Assisted Development **Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht **Opleiding:** NOVI Hogeschool Utrecht
**Vorige les:** Les 12Tool Calling **Vorige les:** Les 13Agents
**Volgende les:** Les 14Agents **Volgende les:** Les 15RAG + Embeddings
--- ---
## Inhoud ## Doel van deze les
1. [Externe APIs in Next.js](#1-externe-apis-in-nextjs) Aan het einde van deze les heb je:
2. [Server- vs client-side fetching](#2-server--vs-client-side-fetching)
3. [Environment variabelen](#3-environment-variabelen) - Een vers Next.js project gescaffold via `npx create-next-app`
4. [Cursor agents — Composer vs Background](#4-cursor-agents--composer-vs-background) - Een GitHub-repo met je code
5. [Vercel deployments](#5-vercel-deployments) - Een Vercel-project met productie-URL én preview-URL's per branch
6. [Preview deployments per branch](#6-preview-deployments-per-branch) - Vercel CLI lokaal gekoppeld + één keer `vercel env pull` gedaan
7. [GitHub Actions CI](#7-github-actions-ci) - Een werkende `.cursor/rules/general.mdc` en `AGENTS.md` in je repo
8. [Branch protection](#8-branch-protection) - Twee features gebouwd via Cursor in eigen branches → twee preview-URL's
9. [Werkstroom — feature van begin tot productie](#9-werkstroom--feature-van-begin-tot-productie) - Eén Pull Request gemerged naar productie
10. [Productie-overwegingen](#10-productie-overwegingen)
--- ---
## 1. Externe APIs in Next.js ## 1. Scaffolden met `npx create-next-app`
Een externe API is een HTTP-endpoint van iemand anders waar je data of functionaliteit kunt ophalen. Voor de meeste apps die je gaat bouwen heb je er minstens één nodig. ### Wat het doet
### Wat valt eronder `create-next-app` is het officiële scaffold-commando van het Next.js team. Eén commando, een paar interactieve vragen, en je hebt een werkende Next.js-app met sane defaults.
- **Data-APIs** — PokéAPI (Pokemon), Open-Meteo (weer), GitHub API (repos), TMDB (films)
- **AI-providers** — OpenAI, Anthropic, Tavily (web search)
- **Payment** — Stripe, Mollie
- **Auth** — Clerk, Auth0, Supabase Auth
- **Email** — Resend, Postmark, SendGrid
### Drie smaken qua authenticatie
- **Geen key** — PokéAPI, Open-Meteo. Direct fetchen.
- **API key in URL of header** — Tavily, TMDB. Key beheren als env-variabele.
- **OAuth flow** — Google, GitHub. Complexer, vaak via libraries.
### Basis-pattern in Next.js
```typescript
// Server Component
async function getPokemon(name: string) {
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
if (!res.ok) throw new Error("Niet gevonden");
return res.json();
}
export default async function Page({ params }) {
const data = await getPokemon(params.name);
return <div>{data.name}</div>;
}
```
Drie dingen om te onthouden:
- Server Components mogen `async` zijn — `await` direct in component
- Errors moet je zelf afvangen (`!res.ok`)
- `fetch` in een Server Component **wordt automatisch gecached** door Next.js
---
## 2. Vier manieren om een externe API te fetchen
Next.js geeft je per fetch-call de keuze hoe je data wilt renderen. Voor 95% van je werk komt het neer op drie server-modes plus client-side. De code verandert minimaal — meestal één optie in je `fetch()`.
### 2.1 Static (build-time)
```typescript
// app/page.tsx — Server Component
async function getPokemon() {
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
return res.json();
}
```
- Gefetched tijdens `next build`, daarna **HTML voor altijd**
- Geen server-werk per request → snelste optie
- Data ververst alleen bij een nieuwe build (= nieuwe deploy)
- **Goed voor:** marketing-pagina's, blogs, productlijsten, alles dat zelden verandert
- **Niet voor:** prijzen die per minuut updaten, user-specifieke data
### 2.2 ISR — Incremental Static Regeneration
```typescript
const res = await fetch(URL, { next: { revalidate: 3600 } });
```
- Eerste request: zelfde als static (HTML uit cache)
- Na 3600 seconden: volgende request krijgt nog steeds cache, maar achter de schermen wordt de pagina vernieuwd
- **Goed voor:** Pokédex detail-pages, een nieuws-feed die niet realtime hoeft, productdetails
- **De sweet spot tussen snel en redelijk vers**
### 2.3 Dynamic (request-time / SSR)
```typescript
const res = await fetch(URL, { cache: "no-store" });
```
- Bij elke request opnieuw gefetched op de server
- Altijd 100% vers, maar trager (geen cache)
- **Goed voor:** dashboards met live data, user-specifieke views, anything per-request
- **Let op cost:** elke request = serverless function call
### 2.4 Client-side (`useEffect`)
```typescript
"use client";
import { useEffect, useState } from "react";
export function LivePokemon({ name }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`/api/pokemon/${name}`).then((r) => r.json()).then(setData);
}, [name]);
return data ? <div>{data.name}</div> : <p>Loading...</p>;
}
```
- Fetch gebeurt in de browser na hydration
- Goed voor user-interactie, real-time, infinite scroll
- **Cruciaal:** API-keys staan **in de browser** als je hier rechtstreeks naar een externe API roept. Alleen voor publieke APIs, of via een eigen `/api/...` proxy-route.
### Spiekbriefje
| Mode | `fetch()` optie | Wanneer |
|------|----------------|---------|
| Static | `fetch(URL)` (default) | Data verandert per deploy |
| ISR | `{ next: { revalidate: N } }` | Data verandert traag |
| Dynamic | `{ cache: "no-store" }` | Data verandert per request |
| Client | `useEffect` + fetch | User-interactie / live |
| Tag-based | `{ next: { tags: ["x"] } }` | Triggered revalidation via `revalidateTag()` |
---
## 3. Environment variabelen — server-only vs client-exposed
### Twee soorten variabelen
| Type | Voorbeeld | Waar beschikbaar | Veiligheid |
|------|-----------|------------------|------------|
| Server-only | `OPENAI_API_KEY`, `DATABASE_URL` | Alleen op de server | **Geheim** — nooit zichtbaar voor de browser |
| Client-exposed | `NEXT_PUBLIC_APP_ENV`, `NEXT_PUBLIC_APP_URL` | Server + client bundle | **Publiek** — iedereen kan ze zien in DevTools |
**Regel:** alles met `NEXT_PUBLIC_` prefix wordt ingebakken in de browser-bundle. Zonder prefix = server-only.
```typescript
// ✅ Veilig — server-side gebruik
process.env.OPENAI_API_KEY
// ❌ NOOIT
"use client";
const key = process.env.NEXT_PUBLIC_OPENAI_KEY; // staat in de HTML response
```
### 3.1 Vercel — drie omgevingen, drie scopes
Op Vercel heb je per project drie environments. Bij elke env-var kies je in welke scopes hij wordt geïnjecteerd:
| Scope | Wordt gebruikt bij | URL-patroon |
|-------|---------------------|-------------|
| **Production** | Push naar `main` | `je-app.vercel.app` |
| **Preview** | Push naar elke andere branch | `je-app-git-{branch}-{user}.vercel.app` |
| **Development** | Lokaal (`pnpm dev`) | `localhost:3000` |
### 3.2 Per-omgeving andere waarden
In **Vercel → Project → Settings → Environment Variables** zet je per variabele drie keer een waarde:
| Var | Production | Preview | Development |
|-----|------------|---------|-------------|
| `DATABASE_URL` | productie-DB | staging-DB | lokale DB |
| `OPENAI_API_KEY` | echte key | test-key (lage limit) | jouw key |
| `STRIPE_KEY` | `sk_live_...` | `sk_test_...` | `sk_test_...` |
| `NEXT_PUBLIC_APP_URL` | `je-app.com` | preview-URL | `http://localhost:3000` |
**Hoe in te stellen:** in de UI: Add new → vink alleen de juiste environment(s) aan → Save.
**Belangrijke veiligheidsregels:**
- Productie-secrets (echte API keys, prod-DB-password): **alleen** Production vinken
- Preview krijgt een test-variant, anders kunnen experimentele branches je productie raken
- Development: `vercel env pull` om Vercel's dev-vars naar je `.env.local` te syncen, of zelf invullen
### 3.3 Verandering pakt pas door bij nieuwe build
Bestaande deploys zien een nieuwe env-var niet automatisch. Na het toevoegen of wijzigen: **redeploy** (Deployments → ··· → Redeploy) of push een nieuwe commit.
---
## 4. Cursor agents — Composer vs Background
Cursor heeft twee soorten "agents". Niet hetzelfde, niet uitwisselbaar.
### Composer
- Lokaal in je editor (`Cmd+I` / `Ctrl+I`)
- **Synchroon** — jij wacht, agent werkt, jij ziet diffs
- Multi-file edits in één keer
- Terminal-commands met approval
- Voor: pair programming, snelle changes, exploreren
### Background Agent
- Runt in **Cursor's cloud sandbox**
- **Asynchroon** — jij geeft prompt, gaat iets anders doen
- Maakt zelf een branch + commit + opent PR
- Kan tests draaien, dependencies installeren
- Verbonden via Cursor cloud (eenmalige setup)
- Voor: welomschreven features, parallel werk, PR-prep
### Aanroep
```
Cursor Composer: Cmd+I (Mac) of Ctrl+I (Win/Linux)
Cursor Chat: Cmd+L (kan switchen naar Agent mode)
Background Agent: Cmd+Shift+P → "Open Background Agent"
Of via Slack-integratie
```
### Mentale model
- **Composer = pair programming.** Jij + AI samen aan dezelfde plek tegelijk.
- **Background Agent = delegeren.** Je geeft taak, gaat iets anders doen, komt terug als-ie klaar is.
### Wanneer welke
| Scenario | Tool |
|----------|------|
| Snel iets aanpassen tijdens coden | Composer |
| Refactor over 5 files — wil zien wat hij doet | Composer |
| Onbekend gebied / leren | Composer |
| Welomschreven feature, can-be-async | Background Agent |
| Parallel werken aan 3 features | 3x Background Agent |
| Tijdens vergadering PR voorbereiden | Background Agent |
### Hoe schrijf je een goede Background Agent prompt
Een Background Agent ziet je niet — je moet specifiek zijn.
**Slecht:**
> "Voeg search toe."
**Goed:**
> "Maak een nieuwe branch `feature/search`. Voeg een controlled input toe bovenaan `app/page.tsx`. Filter de Pokémon-lijst op naam terwijl gebruiker typt. Case-insensitive. Gebruik Tailwind. Houd het stijl-consistent met bestaande cards. Push de branch en open een PR met titel 'Add search bar' en korte beschrijving in PR body."
Specifiek: bestandsnamen, gedrag, edge cases, output-formaat (branch + PR + titel).
---
## 5. Vercel deployments
Vercel is hosting platform door Next.js team (zelfde bedrijf). Voor Next.js apps de simpelste deploy.
### Eerste deployment
1. Push je repo naar GitHub
2. `vercel.com` → Add New → Import Git Repository
3. Kies repo, klik **Deploy**
4. ~60-90s later: live URL
**Geen config nodig.** Vercel detecteert Next.js automatisch.
### Build settings (als je ze nodig hebt)
- Framework: Next.js (auto)
- Build command: `pnpm build` (auto)
- Output directory: `.next` (auto)
- Install command: `pnpm install`
- Root directory: alleen aanpassen als app in subfolder zit (monorepo)
### CLI alternatief
```bash ```bash
pnpm i -g vercel npx create-next-app@latest mijn-portfolio
vercel login ```
vercel # preview deploy van current dir
vercel --prod # production deploy ### De prompts uitgelegd
vercel env pull # download .env.local van Vercel
| Vraag | Aanbevolen | Waarom |
|-------|-----------|--------|
| TypeScript? | **Ja** | strict typing voorkomt veel bugs |
| ESLint? | **Ja** | code-style consistency |
| Tailwind CSS? | **Ja** | utility-first styling, breed gebruikt |
| `src/` directory? | **Ja** | houdt project-root schoon |
| App Router? | **Ja** | de moderne Next.js flow (server components) |
| Turbopack? | **Ja** | snellere dev-builds |
| Import alias? | **`@/*`** | `@/components/foo` ipv `../../components/foo` |
### Wat je krijgt
```
mijn-portfolio/
├── .gitignore # node_modules, .next, .env.local zijn al gegitignored
├── next.config.ts
├── package.json
├── tsconfig.json
├── src/
│ └── app/
│ ├── layout.tsx # root layout
│ ├── page.tsx # homepage
│ └── globals.css
└── public/
```
Direct `npm run dev``localhost:3000` werkt. Vanaf hier ben je productief.
---
## 2. Git + GitHub
### Init en eerste push
```bash
git init
git add .
git commit -m "init"
git branch -M main
# Met gh CLI (snelst):
gh repo create mijn-portfolio --public --source=. --push
# Of handmatig:
# 1. github.com → New repository → naam → Create (leeg, GEEN README)
# 2. git remote add origin <url>
# 3. git push -u origin main
```
### Veelgemaakte fouten
| Fout | Oplossing |
|------|-----------|
| `gh: command not found` | `brew install gh` (Mac) / `winget install GitHub.cli` (Windows) |
| `Authentication required` | `gh auth login` doorlopen |
| `error: src refspec main does not match any` | Eerst `git add . && git commit -m "init"` |
| `fatal: remote origin already exists` | `git remote remove origin` en opnieuw |
### Wat zit er NIET in je commit
Check je `.gitignore` — Next.js zet er standaard de juiste regels in:
```
node_modules/
.next/
.env.local
.env*.local
```
**Belangrijk:** controleer altijd na je eerste push op github.com dat `.env.local` daar NIET staat. Een gelekte secret-key is dezelfde dag onveilig.
---
## 3. Vercel — drie stappen naar productie
### Project importeren
1. Ga naar **vercel.com/new**
2. Klik **Import Git Repository**
3. Kies je `mijn-portfolio` repo
4. Framework auto-detect: **Next.js**
5. Klik **Deploy**
Wacht ±45 seconden. Wat Vercel doet:
- Kloont je repo
- Detecteert framework
- Draait `npm install`
- Draait `npm run build`
- Deploy't naar Edge Network
- Geeft je een productie-URL
### Twee URL-types
| Type | URL-format | Trigger |
|------|------------|---------|
| **Productie** | `mijn-portfolio-<scope>.vercel.app` | push naar `main` |
| **Preview** | `mijn-portfolio-git-<branch>-<scope>.vercel.app` | push naar elke andere branch |
### Wat je vanaf nu krijgt zonder extra werk
- Elke push naar GitHub → automatische deploy
- Elke nieuwe branch → eigen preview-URL
- Pull Request → Vercel-bot post preview-URL als comment
- Productie-URL update vanzelf bij merge naar `main`
---
## 4. Environment Variables in Vercel
### Drie environments
| Environment | Actief bij |
|-------------|-----------|
| Production | deploys vanaf `main` |
| Preview | alle andere branches |
| Development | `vercel dev` lokaal |
Je kunt env vars in één, twee of alle drie tegelijk zetten.
### `NEXT_PUBLIC_` prefix
| Prefix | Wat gebeurt | Voorbeeld |
|--------|-------------|-----------|
| `NEXT_PUBLIC_FOO` | wordt in browser-bundle gestopt | publieke API-URL, Supabase anon-key |
| `FOO` (geen prefix) | alleen server-side beschikbaar | OpenAI key, Stripe secret, DB password |
**Regel:** zet `NEXT_PUBLIC_` ALLEEN op variabelen die echt publiek mogen zijn. Twijfel? Geen prefix.
### Via het dashboard
Settings → Environment Variables → Add. Vul naam + waarde, kies environments, save.
---
## 5. Vercel CLI
### Installeren
```bash
npm i -g vercel
vercel login # opent browser voor auth
```
### Belangrijkste commando's
```bash
vercel link # lokale folder → Vercel project
vercel env pull .env.local # productie env vars → lokaal
vercel env pull --environment=preview .env.local
vercel env add OPENAI_API_KEY production
vercel env rm OPENAI_API_KEY production
vercel env ls # lijst alle vars
vercel # interactieve deploy
vercel --prod # productie deploy (handmatig)
vercel logs # runtime logs
vercel logs --follow # stream logs live
```
### Typische workflow
```bash
# Project klaarzetten (eenmalig):
vercel link
# Bij elke nieuwe env var op productie:
vercel env pull .env.local
# Bij env var rotatie:
vercel env rm DATABASE_URL production
vercel env add DATABASE_URL production
``` ```
--- ---
## 6. Preview deployments per branch ## 6. Cursor — de IDE
Hier komt de magie. Vercel maakt automatisch een preview deploy voor **elke branch en elke PR**. ### Wat is Cursor
### Hoe werkt het Cursor is een VS Code-fork met AI als kernfeature. Niet een extensie — het is de editor. Werkt met Claude, GPT, Gemini en lokale modellen.
``` ### Cursor 3 — twee windows
git push origin main → Production deploy
(je-app.vercel.app)
git push origin feature/search → Preview deploy In Cursor 3 zijn er **twee hoofdvensters**:
(je-app-git-feature-search-user.vercel.app)
PR open → Vercel comment in PR: | Window | Wat je er doet | Switch met |
"🚀 Preview: <URL>" |--------|----------------|-----------|
| **Editor Window** (classic) | Code schrijven en reviewen — als VS Code | Cmd+Shift+N → Editor |
| **Agents Window** | Meerdere agents parallel managen | Cmd+Shift+N → Agents |
**Sneltoets om te onthouden:** `Cmd+Shift+N` switcht tussen beide.
**Binnen het Editor Window:**
- **Cmd+L** — chat-paneel (kies modus: Ask / Agent / Plan)
- **Cmd+K** — inline edit op selectie
- **Tab** — accepteer AI auto-completion
- **Cmd+P** — file search (zoals VS Code)
**Binnen het Agents Window:**
- Overzicht met lokale, cloud en remote agents
- Op één plek alle running + done agents
**Gouden regel:**
- Code schrijven of 1 feature bouwen → **Editor Window** + **Agent mode** in chat
- Meerdere features parallel → **Agents Window** met Background agents
- Snelle tweak in 1 file → **Cmd+K**
> **Versie-noot:** info hieronder is gebaseerd op de officiële Cursor 3 documentatie (`cursor.com/docs`). Cursor 3.7 en hoger heeft alle besproken features. Sommige UI-details kunnen per minor-versie iets verschillen.
### Editor Window — stap voor stap
Je hoofdwerkplek voor code:
1. **Cmd+Shift+N** — switcht naar Editor Window (als je in Agents Window zat)
2. Layout: file explorer links, editor midden, terminal beneden (klassieke VS Code-stijl)
3. **Cmd+L** opent chat-paneel rechts
4. Voor snelle inline edits: selecteer code → **Cmd+K** → beschrijf wijziging
5. Cursor toont **diff inline** in de editor (niet in een aparte panel)
6. **Tab** om te accepteren, **Esc** om te rejecten
7. **Cmd+P** voor file search
8. **Tab** accepteert AI auto-completion suggesties
**Wanneer:** je standaard coding-flow, files reviewen met split screens, VS Code extensies gebruiken (linters, formatters, debugger).
**Tip:** kun je Cursor laten starten in Editor Window als default? Ja. Settings → "Open Agents Window on startup" → uit. Of start vanuit terminal met `cursor --classic`.
### Agent mode (binnen chat in Editor Window) — stap voor stap
De AI-modus *binnen* het chat-paneel:
1. **Cmd+L** opent chat-paneel rechts (in Editor Window)
2. Bovenaan chat: **mode-selector** → kies **"Agent"**
3. Typ de **hele taak** (niet een specifieke kleine wijziging)
4. *"Bouw een /about pagina met team-grid in onze huisstijl"*
5. **Enter** — agent start zelfstandig
6. Cursor toont een lopende **takenlijst** + voortgang per stap
7. Agent edit meerdere files, runt terminal-commando's (vraagt vooraf), kan zelfs **web browsen**
8. **Geen limiet** op aantal tool-calls per taak
9. Aan het einde: samenvatting → **Keep / Accept all** of per file
**Wanneer:** grotere features die meerdere bestanden raken, refactors, nieuwe pagina's of API-routes.
**Belangrijke tip:** hou je prompt specifiek. *"Bouw een about-pagina met team-grid"* = goed. *"Bouw een mooie website"* = veel te open.
### Background agents (Agents Window) — stap voor stap
**Async cloud-based agents** in geïsoleerde VMs:
**Cmd+Shift+N** → Agents Window opent.
**Een agent aanmaken:**
1. In Agents Window: klik **New agent** (of `+`)
2. Kies environment: **local / cloud / remote SSH**
3. Beschrijf de taak — specifiek, als een ticket
4. Optioneel: branch-naam (`feature/about`)
5. Klik **Start**
6. **Cmd+Shift+N** terug naar Editor Window — agent babysit zichzelf
**Resultaat ophalen:**
1. **Cmd+Shift+N** naar Agents Window
2. Klare agents staan op **Done** in de agents-lijst
3. Klik agent → zie **diff + commit message + alle stappen**
4. **Open in PR** → automatische PR op GitHub
5. Vercel maakt preview-URL → review → merge
**Triggers van buitenaf:** Background agents kun je in Cursor 3 ook starten vanuit je telefoon, Slack, GitHub of Linear — handig tijdens vergaderingen.
**Wanneer:** 24 parallelle features, lange refactors die je niet wilt babysitten, taken vanaf mobiel.
### Context — `@`-mentions
In chat type je `@` voor een autocomplete. Wat je kunt mention'en:
| Tag | Wat het doet |
|-----|--------------|
| `@file:foo.tsx` | inclusief één specifiek bestand |
| `@folder:components` | hele map als context |
| `@code:functionName` | losse functie of class |
| `@docs` | externe documentatie |
| `@web` | live web-search |
| `@git` | git history / blame |
| `@recommended` | Cursor suggereert zelf relevante bestanden |
**Tip:** hou de scope klein. Veel mentions in één chat verslechtert de output, niet verbetert.
### Cursor Rules
Markdown-bestanden in `.cursor/rules/` die aan elke prompt worden toegevoegd als systeem-instructie.
**Voorbeeld `.cursor/rules/general.mdc`:**
```markdown
---
description: Algemene project-conventies
alwaysApply: true
---
- TypeScript strict mode
- Tailwind voor styling — geen CSS modules
- Server components by default; "use client" alleen waar nodig
- Imports met @/ alias
- Geen any-types
``` ```
### Wat krijg je hiermee YAML front-matter opties:
- `alwaysApply: true/false` — actief in elke prompt
- `globs: ["**/*.test.ts"]` — alleen actief op matching bestanden
- `description: "..."` — wat de rule doet
- **Reviewers** klikken op de URL in de PR, testen de feature live ### Project context — `AGENTS.md`
- **Background Agents** maken PR → Vercel zet preview neer → klikbaar
- **Stakeholders** zien een feature voor merge, niet alleen screenshots
- **Geen "werkt op mijn laptop"** — alles draait op Vercel infra
### Belangrijk om in te stellen Eén markdown-bestand in je repo-root dat beschrijft:
- Tech stack + waarom die keuzes
- Architectuur en belangrijke patronen
- Hoe lokaal draaien, hoe deployen
- Pointers naar belangrijke bestanden
- **Environment scoping:** preview-deploys mogen geen productie-keys gebruiken — gebruik aparte env vars per scope `AGENTS.md` wordt door Cursor, Claude Code én GitHub Copilot gelezen. Eén file, meerdere tools.
- **Database:** in productie wijs je naar prod-DB, in preview kun je naar test-DB wijzen — meer dan een Vercel-feature, eigen architectuur
### Comment-bot ### Background agents — async cloud
Vercel installeert een GitHub App. Die plakt een comment op je PR met: Tweede Cursor-sessie die asynchroon op een feature werkt, in een eigen Git branch, in een aparte tab.
``` **Workflow:**
🚀 Preview Deployment: https://... 1. Beschrijf feature → klik "open in background"
✅ Build successful — 1m 23s 2. Agent werkt in eigen branch, jij blijft in main editor
View build log 3. Agent meldt zich klaar → review diff → apply/iterate/discard
```
Per push naar de branch wordt deze comment ge-update. **Wanneer:** voor parallelle features. Drie of vier agents tegelijk is normaal.
### Top 7 tips voor power-users
1. **Cmd+I** voor Composer met multi-file context (niet alleen Cmd+L)
2. **`@-recommended`** laat Cursor zelf relevante bestanden voorstellen
3. **Plan + Apply per stap** — niet alles in één klap accepteren
4. **`alwaysApply: false`** rules voor opt-in conventies
5. **Auto-mode** — Cursor kiest beste model per taak
6. **Cmd+Shift+Enter** voor diff-preview vóór accept
7. **Notepads** — herbruikbare prompt-snippets per project
--- ---
## 7. GitHub Actions CI ## 7. De feature-workflow
Een **CI** (Continuous Integration) check draait automatisch op je code. Minimaal: lint + build. ### Vijf stappen, één feature
### Waarom
- Voor merge weet je: code lint clean, types kloppen, build slaagt
- Cursor Background Agent maakt PR → CI draait → groene check of feedback
- Voorkomt dat broken code in main komt
### Minimaal workflow
`.github/workflows/ci.yml`:
```yaml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm build
```
### Wat gebeurt er
1. **Trigger:** PR open of update, of push naar `main`
2. **Runner:** Ubuntu container op GitHub infra
3. **Stappen:**
- Checkout code
- Install pnpm + Node 20
- Install dependencies
- Run lint (faalt op fouten)
- Run build (faalt op TypeScript errors)
4. **Status:** groene check of rode X op de PR
### Tijdsbudget
Voor een kleine Next.js app: 1-2 minuten. Voor grote apps met tests: 3-10 minuten.
### Caching
Met `cache: pnpm` herbruikt GH Actions de node_modules-cache tussen runs — scheelt 60-80% tijd.
---
## 8. Branch protection
Standaard kan iedereen pushen naar main. Voor productie wil je dat niet.
### Branch protection rules (op GitHub)
1. Repo settings → Branches → Add rule
2. Branch name pattern: `main`
3. Check: **Require a pull request before merging**
4. Check: **Require status checks to pass before merging** → select CI workflow
5. Check: **Do not allow bypassing the above settings**
### Effect
- Niemand kan direct naar `main` pushen — moet via PR
- PR kan pas merge als CI groen is
- Force-push naar main geblokkeerd
- Background Agents werken automatisch via PRs (zo bedoeld)
### Voor solo-projecten
Beetje overkill, maar oefenen is goed. En het voorkomt 'fluitje van een cent' bugs naar main.
---
## 9. Werkstroom — feature van begin tot productie
De volledige flow zoals je 'm in dit vak gebruikt:
``` ```
1. Idee voor feature 1. Plan → Cursor chat: beschrijf de feature, vraag plan
2. Branch → git checkout -b feature/x (Cursor kan dit ook)
2. Background Agent prompt (specifiek!) 3. Build → Apply het plan, eventueel inline tweaks
4. Commit+ → git add . && git commit -m "feat: x" && git push
3. Cursor opent branch + maakt commits + opent PR Push
5. Preview → Vercel maakt preview-URL, test & itereer
4. GitHub Actions CI draait — lint + build
5. Vercel zet preview deploy neer — URL in PR comment
6. Jij/team reviewt: code-diff + live preview
7. Eventueel: commentaar in PR, Background Agent retried
8. Merge PR → main
9. Vercel productie-deploy is automatisch
10. Klaar — live op je-app.vercel.app
``` ```
Tijdsindicatie voor één feature: 10-30 minuten end-to-end (afhankelijk van complexiteit). ### Eén feature 10 minuten
### Wat als CI rood is Inclusief AI denkwerk. Wat dit versnelt is niet dat AI mooier code schrijft — het is dat de **cycle korter wordt**.
- Klik op rode X in PR → naar GitHub Actions logs ### Anti-patterns
- Lees error: lint-error, build-error, of test-failure
- Twee opties:
- **Composer:** open feature branch lokaal, fix met Composer, push
- **Background Agent:** nieuwe prompt: "fix de CI error in deze PR — lees de logs en repareer"
### Wat als preview niet werkt - Geen branch maken, direct op `main` werken (geen preview, geen rollback)
- 5 features in één commit ("WIP" commits)
- Open Vercel dashboard → project → Deployments → klik op preview build - Plan niet lezen → Apply → het deugt niet → terugkrabbelen
- Build logs lezen - Background agents starten zonder duidelijke prompt → bagger output
- Vaak: env-var mist (Vercel preview scope leeg)
- Of: dependency die op Vercel niet beschikbaar is
--- ---
## 10. Productie-overwegingen ## 8. Production checklist
### Costs Voordat je een app "klaar" beschouwt:
| Service | Free tier | Wanneer betalen | | Check | Waarom |
|---------|-----------|-----------------| |-------|--------|
| **Vercel** | Hobby tier — gratis voor persoonlijk gebruik | Commercieel of >100GB bandwidth | | Productie-URL werkt voor alle routes | Anders krijgen gebruikers 404 |
| **GitHub Actions** | 2000 min/maand gratis (public repos unlimited) | Bij grote teams of veel runs | | Env vars staan goed (alle environments) | Anders crashen API routes |
| **Cursor** | Hobby (gratis), Pro ($20/mnd), Business | Background Agents zitten in Pro/Business | | `.env.local` staat in `.gitignore` | Anders lekken keys op GitHub |
| **PokéAPI** | Volledig gratis, geen rate-limit issues | n.v.t. | | Eerste feature-branch + PR gedaan | Bewijs dat preview-flow werkt |
| `.cursor/rules/general.mdc` is ingevuld | Anders weet Cursor je conventies niet |
### Performance | `AGENTS.md` is ingevuld | Anders weet AI de architectuur niet |
| Build slaagt zonder warnings | Anders begin je met technical debt |
Vercel doet veel automatisch: | `404` en `error.tsx` pages bestaan | Voor nette foutmeldingen |
- Edge CDN voor static assets
- ISR (Incremental Static Regeneration) voor `revalidate`
- Image optimization via `next/image`
- Automatische gzip/brotli
Voor sub-1s page loads: meestal niks doen.
### Observability
- **Vercel Analytics** — page views + Core Web Vitals (gratis tier beperkt)
- **Vercel Logs** — server-side console.log zichtbaar
- **Sentry / LogRocket** — externe tools voor error tracking
- **GitHub Actions logs** — bewaard 90 dagen
### Security
- API keys NOOIT in client (geen `NEXT_PUBLIC_`)
- `.env.local` in `.gitignore` (vooraf checken!)
- Rotate keys regelmatig — vooral na PR's van Background Agents
- Vercel environment scoping om dev/prod te scheiden
### Rollback
Als productie kapot is:
1. Vercel dashboard → Deployments
2. Vorige werkende deploy → "Promote to Production"
3. Klaar, ~10 seconden — geen rebuild nodig
--- ---
## Bronnen ## 9. Verder lezen
- **Next.js fetching:** https://nextjs.org/docs/app/building-your-application/data-fetching - **Cursor docs** — `docs.cursor.com` — features, shortcuts, Rules-syntax
- **Next.js caching:** https://nextjs.org/docs/app/building-your-application/caching - **Vercel docs** — `vercel.com/docs` — alle CLI-commando's, env vars, domains
- **PokéAPI:** https://pokeapi.co/ - **Next.js docs** — `nextjs.org/docs` — App Router, server components, routing
- **Cursor docs:** https://docs.cursor.com/ - **AGENTS.md spec** — `agentsmd.org` — community-gedragen format
- **Cursor Background Agents:** https://docs.cursor.com/background-agent - **Pro Git** (gratis online) — `git-scm.com/book` — definitieve Git-referentie
- **Vercel docs:** https://vercel.com/docs
- **Vercel preview deployments:** https://vercel.com/docs/deployments/preview-deployments ---
- **Vercel environment variables:** https://vercel.com/docs/environment-variables
- **GitHub Actions:** https://docs.github.com/en/actions ## Samenvatting
- **GitHub branch protection:** https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches
Vandaag heb je geleerd:
- Scaffolden met `npx create-next-app` — alle defaults uitgelegd
- Git init + GitHub push (via `gh` CLI of handmatig)
- Vercel project koppelen — productie + preview URLs gratis erbij
- Vercel CLI — `link`, `env pull/add/rm`, `logs`, `--prod`
- Environment variables — drie environments, `NEXT_PUBLIC_` prefix
- Cursor: chat, plan, build, inline edit, rules, AGENTS.md, background agents
- Feature workflow: plan → branch → build → push → preview
- Top tips voor Cursor power-users
Volgende les bouwen we hier RAG bovenop — een PDF Q&A-app met embeddings.

View File

@@ -2,7 +2,7 @@
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource) %<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj 1 0 obj
<< <<
/F1 2 0 R /F2 3 0 R /F3 5 0 R /F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 7 0 R /F6 12 0 R
>> >>
endobj endobj
2 0 obj 2 0 obj
@@ -17,22 +17,17 @@ endobj
endobj endobj
4 0 obj 4 0 obj
<< <<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
5 0 obj 5 0 obj
<< <<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font /BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>> >>
endobj endobj
6 0 obj 6 0 obj
<< <<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -42,17 +37,12 @@ endobj
endobj endobj
7 0 obj 7 0 obj
<< <<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
8 0 obj 8 0 obj
<< <<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -62,7 +52,7 @@ endobj
endobj endobj
9 0 obj 9 0 obj
<< <<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -72,7 +62,7 @@ endobj
endobj endobj
10 0 obj 10 0 obj
<< <<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -82,7 +72,7 @@ endobj
endobj endobj
11 0 obj 11 0 obj
<< <<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -92,17 +82,12 @@ endobj
endobj endobj
12 0 obj 12 0 obj
<< <<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F6 /Subtype /Type1 /Type /Font
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
13 0 obj 13 0 obj
<< <<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -112,7 +97,7 @@ endobj
endobj endobj
14 0 obj 14 0 obj
<< <<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -122,7 +107,7 @@ endobj
endobj endobj
15 0 obj 15 0 obj
<< <<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources << /Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -132,140 +117,153 @@ endobj
endobj endobj
16 0 obj 16 0 obj
<< <<
/PageMode /UseNone /Pages 18 0 R /Type /Catalog /Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
17 0 obj 17 0 obj
<< <<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607134605+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607134605+00'00') /Producer (ReportLab PDF Library - \(opensource\)) /Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Subject (\(unspecified\)) /Title (Les 13 Lesstof) /Trapped /False /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
18 0 obj 18 0 obj
<< <<
/Count 11 /Kids [ 4 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R /PageMode /UseNone /Pages 20 0 R /Type /Catalog
15 0 R ] /Type /Pages
>> >>
endobj endobj
19 0 obj 19 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1911 /Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260612063152+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612063152+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 14 Lesstof) /Trapped /False
>> >>
stream
Gatm;gN)"=&:N/3oLl3SA3H?=Y5<se:?#GYiZof&#?RaT(dLQ(La6rF?b\b!)]'aB/3j4\c#i]Jkru#aJE!&dmq;`@0n_K()))Q<#r%k$0YDt;U%QkYguY-p!"hC0!='5CrISj#V;Sl%%=#\(V]P@k)f2,Yh[b+m\Ba]5#FU7e.uOGu>jD,C&>(tcn*[:o`8qtEgVe*f@!@#6T<H-?Lg*<'@?qulL,2p8Wj+$i6CHjo0O2EHLQC>qiOt]&4IR^a^t0codB%j[c93"LT+CKTc3lXe^Z8QFC:TF?nIgTN0RT^FD:l=7:)j[\g]!2VJI0,a]MOg?ltn_Adh(/dSPQbn[3:l<J\$FF05q&c7H]D00!8D\[c`me9Hh5<5WEoZSW2HLIDO[C$^&sJ(__Q`%b#4JCG$2?$_XcE=A[:XS2F@lCZBSI<R.$`['`A-B9>V;N!-nrq:1P?ME,@p/3IO]I%Lebc7r=P8K&[pj)iqa5a0(V2mIFb:nKdtMk(+'XN"Ca7:U6?%JJokD6;4:g&K'pNB+pcb\&L]RrF,d==\NNl$$8:Dr.3!<u25k(9a`L,D38<R4#KE%n!/B$T9fnAFA!O6>!3M_<l@TYMjMejXS/4hddMEA@jH&F"kKOU=3=3!mYmmkj2N@6DC2.RAmiL1^l0BAnnu9MHH/f/7_WT%B!<e3QMotaE9$U?n@Y$qOKiR=QnMg`.WiP3ME,PLURqG>1D?1pCJRI"r2(/Y[1,!?@m^NcSc#:6>Dna+=\Uj;A&X#c`Nj!UC&^To`"Kjc*hIR9k)lF-uQq"+?hCk)3<g"qD=GN*d41r+R9Lc_ESCMhas2"NM<Ur[Vm,iE-EX:[E;nJ4-/M$&'Wj`3Zi#)>,HKs4!8Nh>`-X>Xcb-3jjV$A]%@$qR"CZ93n3>6)(#[P6Pl-MYTOLFN[5sVa"d8ZnJE+T"S6p.%S?)'0p6%;ht`ke:HP]uVK#H(\<$R$__g``Cm!uhqsDbnGBiYo0YIu)YhbrtGn_.kni1auV`"7uH81T8CHrJl&rDJW'bD\];YO9s2GqTO7Q-7Q\W60gAWmM2*MV)-^p"hN2MW?.Dms>G6X'eA&E.s6RoF"mJ(h!!`aGV<#A;Cu:AIUY]FiBGq^Y4>(O5_%Qq0IETAn_$RQ.6q,UbaN8h!IkFfabP<!m/SElV]u<@Vj^%qDKc/(;M6WY)q^%@bWpR>mJ*A83kk]=#g>9o=PJY;VuY46GqOD]#@>P"Sb=4Q$b@=$c7qpjuKu1UpDBV+Q_.C;#SCBNE`Y[Tcp&7R/#S'?JFmF'.UO-ZR[^*b01aA7:e?>c)3MC:j<l?,r@*[?SHfbi3T;nN`m*F!Q%,FD,l2WIbX^rsXD4I_E8%^AJ"IL!#t0.$I)+nQ]k:&p=pP7\us[,)k[59Wdg&#?pR.mD#qTn;^\h%)Lc'['me;26,6]X1'B[0!!h/f9re9lr`!(@V=Ze<^X,G41W:[-8I,QQoLil9VWJ5F_1*V#5T78Eg3]cq;+TSMi7>1&Id$E8oqMTL'O!g-]*.7";R051ZZXZNJpnX1>^cOMp`OK>=`6.$9\,<cc]TrKQ5_<P(`39%j*;3;NbG#RF(c*[oEWM_pmggN0c)AS^,`"I#t)W-U$4o'[LU*OJ&Os?C[',$6odcZt]1m\)HUT9!VI)D64k$Lh,bB";L;*C;\T.5^hnZN%T);8\g0Hi7oPQlqqZd_IT8APMHIBm:tNb/O;7_9fY5gIV(?P9gc73Lb6kp_;ltpl1BPKO#sq1f/Ird$dWP._sN*>8Fe_c7&9m+?ongOGWh]?aWPrAfE(+kPi;?:F!P8^.<Gf^dr7b&"3cm,nQfbu]Z?=@PWVf%4mOeUl?P]240?ul:f"tFN:p-3A*^0^ENW-a*]1HB-e4gn;4i<tfM#7=\%^`1K(U3Nd"HW~>endstream
endobj endobj
20 0 obj 20 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2006 /Count 10 /Kids [ 6 0 R 8 0 R 9 0 R 10 0 R 11 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R ] /Type /Pages
>> >>
stream
Gb!;d>BA7Q'RnB336UYU?s:O409TI]B?IXsB"8T]4QQUo;%g@HAS``2l1DaZO`,5\*>jO)9i='1o]_n7*1`!:=SSWPru_Mj5(p`[$C&AG!n8A'$t$ooYkPOqR)c_s!jRGjq[A:!Rm4uk]`_T$GWt^J#`BA4*H2--6MLU!Y2c$ma:q=>oI(:==^3pO"EcldXf]'h`u&^]&D-OOeb-=t_MI@gYEf[;XPO5;1MEmiMeY>lU+UXu(dn>j]?d\=V4O\LQt[SWV]o'UXFLmm*0+>G*p%[7(MXB!C7uiB`[WDY_sd_^:X!,cI)f[67Ifg+^K*?k)t9",H;_SNa_@,5?lbd3aCtb;Z!'tA$"DX)7pG=*]OrHD_KJ&^K=Js,;Veck;3,^+=cV^)0i?8;#j!,nNk!?(T\#4_%2G*GW-68&H+jY<[S<e_XT6U2J-%Y';30_?FSV]^!a4nC6-i_,&`K'WaEhj,7S!'C2sZ_mAR:Yd#;;&)Pg8S,;7f?=ACMes:hBeu>[+_41`>]5L!Y_>eS>EI/+J&)f("_i:`oZ0*ra9(Ts@)&dFkQ?_?A_ql[iC&qn0c*?L&&-]=["!"K(;N/.\gnA3u-QG;dE7<<?pG0n_P8*-sUbSfOhASE_dW?m-0h<@j=gi(%?=>Y1@"Kn?Q\,bY04k"/C&3g!9AW3Cfq3A/eF9L^KTN<]@->%]Ni&1`HI#@s:c0Fj?K3nNu(!bU`QHr1d"@=hF@*(IA@&`6sP-'7>13$n<tfh@DN3ooY9g6+D8d+33"dR?dmc]sC-.bVCQ)baLSFZ-\NT,Zs,$pe#82k0IA&SbIX+7(Sb<g]_gq]j8Z<[%!h\J,QR]@T)Oj"kM8cQ&_-%ulpA4"U'hf3q?[Xr7]r+2GKf-&6D<U%AAUL;h@33L&RA2oj7Rast1B^Q+CfYO<_>o0Ji07;._$Akn<Q8#Y`N;TpneL&n;+(L_El6ZH`:3lRT%^/OPXH.++S24ZrI=dZ2kGb_#^<4`kCKmN`[RO>?KUWlD[:cX9P"*FCPLh1aT!$Cb.Bbl`\KR(<a8@b)uITDOkc7kjPZ+S!trcKhZcANQD-k'Xk_Zju1EWl9EYk7ukl)@6jQg3^'UDAF@&<"=*-E7>`S\4YMc`YC`aG-ZN5(.ZmP5%^BN*r.>WM3B-X9nf[ld"$SA]Ug/Y[g9?LC9N(nm"Uu-SN73;1EeC#6lN;^=pfKWRfNh2$o5W^H,+7FfhqOXHMWj/4'Y?Gb-&r.UQgtCCG/ekGQ:Lk7W-*_c.DKXP7),1!_/)ZEb5geAB':qlf]s)5q@!Y[JcX.U;#*_g0eE[:<([&XHS0*,6ETO'%F]]-F&<'D1!5arMZgO-WW0)"nQ3gX(IL/uhLF"RQI]/\d[5Jd^!%fiiu1bIpSJ+dP/k93LTR95U!()k.0Rr6br'VOkXPIRu;;)8bsmErEfSdm^^\HIqqS::<'Cm:lmB"ng'h1;?D4[e,<R.PQ[X==9km,%L9[Em2>@FS$DfpRkuJj`@Vg#p@SsHXeRs_quT?A6;@7;1B)`2<7qiN<8T\<YT9IF>-6_/=D5<ci^oh(9AKA5mMnr)G0D3lp8'b69Vf?DtNh.h=H%6mGs1;IYdrFrt!V7kY!2=<+&dhW%0jL17aF$O5>Vk5l)67S"&Kd??gQdgNE!F<q)M,k#l0g_J[V[^DF]WM;nlX8q=P0F'VPb?I[3T50Ir&BCi-a<7e+p$LG)GEZPt"i[&5XW^cqk87a..*:ENILM[]B1FDO88(mYpma:^?$C;U9mcB#Y))i(DmM#D4*%q6$2&hc.GL$J*@%YR,lRJ[!,G)g-LhDlJ)uhid=*d+.PSMadcP>S+*<Z#?)N/o)8(W$t^VHS\mqhr1$`o?6&lqA&%^@oCeX=p)126!8C[(LrGd0D7rGEA#k%?+iS/A(WRTq99VP4C<a[2;r;Ff%C%<Y'B-"?NAJ^dKh-4\0lZ"@4A0hq2Ol6(&BZZ0E"'HL\5Op<cO-%X"oH44<TXD$'-Y[n[U^&J-0#X3V~>endstream
endobj endobj
21 0 obj 21 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2085 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1961
>> >>
stream stream
Gb!#\>Ar7S'RnZ;3%.hO.:SaA,`9am%)&A9ZDL((p7X*87A4QEZK+W`QQjM#h]ZQ.E\;Oa%`+;N&h3B5(S8et0qe<bbl;nln9bLof*iLOi>@qP4;]-UX.f.0+^FNPo7=E7\fh:k](uYhM3EKDL^8:I5gk2(^]R@f,%W0Rj1ZXD!>Gm\:E0Qa&2]Ii&BT;C`JsgK&Y,V0O8)%pfCuK@P?PY]Lm<.^B)RKm\:UiAZDaor..[N_L,8&,4oT[3Dt8`7j(Y*_n97m2GZqq>o)P@&p7IU];HE&s?E0hooD)'keIE#6@/*.5lJR"1-U+Q>Ndmis?nUB6XR^??q_"<_=TiUIk<8[K!>9dVBL!k`>oMX(Cim2IU1:"b#uA@\iBF5Zm9JrGJ5GOQc5`1O<?>S"i8#9c=VEl?%W@_F9LIL4&D)V"=DX>SCTIT^7#VI2pSl(".;L#'$Uh+]fV,D_.1WGBX3K4SNodN%F\GKnhtlOAYBB3!i"\Y__P;0_N"R`\GC^p?.t),lN-ER$YhI@RY8r"!n3b'2ip2D.+i`mfrWcFS<RHS5$#uDA4S?nY']W=rnMJ?7gS.#*C89eC('U+#BMXQSpqq?O;+8Z8'76cfhkqah-o^,+-lRj9CThk_RW-2u-bM?bk'00gkJ!K[*=kl[NU;r^&;!`>#2L0MXGIrZa-a(/.1DH3$]+7c3@f''B4;WZ%I=Ki[0%G3JWu_GZj!5,/00t\[=PctY4f?U[(!LKV$itW"+"0FL*Yi=$OC%V#c0PjhI$?2Hp3q\kA$;qK?m[W;dTb#BVJ$t?JJkJ)Ba-`l['H])CR)MB/,>]=^37u"qt"MX"d+-NB,^DbKX]E,b?A02eW22Ss:$kit@qMoM:]NFVp0cRo+S#?Ira='S*C9Wg]9$"(Pds:Ipudn1n*dC\2P3>/RDE]:5KWT*RB;(ASRuF]%Pb[8>$p9hqHUm=?c9Ke*'icc/gE<J[k]'Ute%QNSr(%JFJuO3QuNL8OB?X7_1H(.G*k7F"NmnZ;f9<U[]V<Yp.Mg[ru))X>)L$N[BA\g[b(o=R%_WQOr&X95n\H?e$KU?6G/g8okeQ7=`g,pG1Wh:]u#5h-GSJif79Y2V8(+WX^TrP7"#T=m=>F7gPKdc>4L5s>OTbb_;%GKQ0.b(hdSQ$"at?::[$^ncddUkE:l_->+kN5Q0KEVaWt[Bf43f8!GJrFLOc'VdTI4,6]%c1%n]R&LEHdj(ejZ'^Jn2Au=@2ZB5'R7^D3N-@7/rVVD3pNO#\c]t%X]d]jKDl%T`K&3CRbEOLM+N]cr0g^43f<6W=03-$&]7;M(Eh)N+Q;g_Ep?.1`ra(cTCr0\h>HWF3`k5u!e98YJbIGk#nNW[$W)G,.3fO_8Ea@7i0gaBJ=rPl6(`Lj3=rPl6ck_SKZ6#CQ0Fo6u(7H<<BMkcEkC'_6oeCh1#QmrQT4Bk+M[EZJmXI)8X16m6[i[*djk.Jk76Z>oKo4mWYXn+o5`IfD<`/LYn8%"J_?dW@kpT[a9:CrJ+=VlBmDN^ucS`Z*F.GdsWa=]0(F2k;=ieW!BtBQQXH>SXrIa/4klj14\0mo$L;/-oo]nR4=t?C5_Wq1m2-$+2oSQhE_NLO.@+Ug:`EKffX)kH6[B!T3M=XLU2q(S%K`0A1H#T_f`&M=XTI4+Xp?)ZNH$"pLQ4U>dQll6mm#L+A85lqah*Gj7a7q(Z8VTqOF!X\VI2NdiF;MqgMR0&u?.5,I(3@`a2q2ij#+=(Cm.#qL%KKLNJj#jD5h,$^fAC[hf*lG^W,!6('F,cn^8hLp%n*R6LH`YIU<<*T42SnSnmFb:B[Q?6`k?Ug=,920_poO&O^buupmA3"eGlMq1PR?+H30N+(hpqu>HgbR!QVZ:5[bXZ@f=]3)@[VPqDbFg;>i15`j9-`2=!;mRVn+UO-sm6B>]DZF-T5JUd-u:oq&,3S>$ee?#TB3Cg4"[qHLhkj5\$#2ZMnI7@]u"!r=JLU4pn0.Z#DK=U[<d.P_1;ZeU19'V0kSR3)'[I"CAs1%_NIXsTMO&>b\3p?"emnbm'Uh8s?S>Bu\?^IB+594'oU@ht;JP+:'Fn!Yler!Q#deee~>endstream GauHL8U$Xp&AII3e8:ER9K.;N1S2PF,gS-3=Gh1i+UD7,'%!ET.@OU.e]jgo`:82_Ca5BP^08+J)?L+lM#&3j!.U'giZA5T_KZF+dOc<n^t!]!@mCr)L['mqI0)$DFO)irjMT#!Ff9*/TUVN]q@n&/PWp:&"-cA<2%GRD<'c([;?^&-L,Z2!D/`:M$i%C`q=FiLk:h?6Q%-Wr0`]/=:!9dfJ>37-CNTbU7&Uta+XV27(p@?B[Lr`[n-\TN#[-^&Q9b]]@l)10T<kbT/-lC8Y[u$Z>/]ubYD^sU6cqB*Q=G5FW-=n+7&b8B,#S[UM,:+pYF*/q/m,b`MGtGCo-%@fcn:5#/kQnq418Z+cVp-pck*X;L7e'j`DN/M%RkdAI.mZuo,0'q#L4"X=pc$/*/_8d,+^N27WBhD7G4c;HoXOoSP+,/,krcK-c3#Tgg4;s#d2S^M^7GZ5@Y[Jl^gO@B8l6R*PkZqqAj4*8rST)Vl].'B05OAqC\q_qQ*k-l]jLQL6nk(JX+#[l+LrQ\3,=&HZKgMm5+eoEuTY'*nT,TeBro;huP"@j*YH]523O)*d?7.PU=dp69MPNFssWO)s#Z.\7-=,R4-u,qcW4UU>)e*X+TZ$-P7oiTZq2Wc+5J+$)(`mag;I:[33dkK(K&&IPsu<F?M*RUa*&&npNCC"Nrg$Qo9!i+ck7n_q[bJqPP1=dIh1jlC+f^Z'DcVpS\GBQt]oGDrBKZ8_+Vh>knIXJI.^"+:B::35B^/cN1/N8#CNJ4M8B'!"!!86%ZY\i-EU%[9kB+C[2cF%GoG&A^/H)-0/'h*#.9JMh3'5iulft4pSL,I\R+KQrp:&ZgRlW</piC<*2"2YRN<!,^LA_#Hg<@qokDY=.dJWUcoo]1V(-Y:-7JtqMYT"U[(dhCV4*bVH='RJPB^q-G3O4'sed6cqoJQ5AR\?&iID-p/"ZAg%/,5"A5Y'B5aC5fe^5+pZbVhg]#1J"Lm_f`<t:7;C6?5[528jRLUHf3Ai*beCRFDF&#IbVJgV\:RtsN%\om[@U*J!rI1O_Id`nG9ZPuo7;<D2f@(nLPZp9Sl<@)b;((A)qut=^`/Wn8"1<EU?lWt[4.EiY;##,NLG=P<9Gk.cKGO,8%C1ejED2P'P=N`H</2e`>,p2id)S/D7;LC,c:Y0(1-_'adtn&`f5"N:Yb'cL="8s-S2J/09GONNa;>D<e"B=U6LT+4oj$>B*9AT>(rUV(Cp%pS@">P6"dVo#>k]B^=>%mXT'pOIP(D^4P&[g=H))d-FMflhm1lh$#m]:T8X5D2%`Ju!4'S\]".`?fNdUD\%#F=tSm!m1*^e'WEKp,Sk_ot.L1Q!Yo/.LM%&P]*9CgOA_'\q,%h7?1VVnS(no^Q;l?$*$X^e]t?O>Rt+\:X=VWD'IQn%WDCH*(Lg[BaceG\u6Q<9a@AH>e9c<.qq;?,UjZP<Jg>?3N$kBNI-CKO75[Z2qX.N6Jk7\fjhj#Lf%hR2I^maW]<GCDo`97,c(7ucer9938eRoa_3E4$EL#>`!3-.ACjK%X1t[YT`l@A:KndGf.b_Nm;mfl74[\sV^fWFbjZFbcl!`;@S>rsV0X.iIAic:4L`oCC3kQjq*VTFY?r=Hk#LEP>]@Gh.Wrk@29V'$kVYYth<ie,M$W_cH\:UDkb]]s&F?ITR&Q4>U\?\PD5U.9#\;Z>b4!$k<om9gP;Nn<a](8#&79%8/@IL9<#d,VI^&1_s2&,VX>lCE1D4:)l7!M(2hrVr66Gm:m5>5SOSg5&mjq"5#=#!c7'AGSQM2a]fFK,>.c(ksUt$fY6[!`hmq&.r9uhJ/X/K5Re_lc4kmZZEAbq1uTsp*u7Q.O[uqDS7hD8ebtD)+Ii#,`.[;>\EJS?Mn._UI`@P($/*Ifg8'UiBKOmYU&KjtF6j4)7np)+eFb@+>IKGi8A6I`*p>M`\rrP#b)s>2@+<Dh0_c_$&3BF~>endstream
endobj endobj
22 0 obj 22 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 704 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1867
>> >>
stream stream
Gas1\fl!<6'Rf^W*6hCpFZd)e?LjD^N>a6<2qjTn+"6Y1/H[G`-rQ=[mq!B(1hcquI(B!AEu.IT\n1W]G/3DHMfT3(OK9Q^f17Oa2IX?<=%6=5K1A[)?@i!P]hkh6J_4r%Q'ZI,Eb@8:PMA1eTIA)hBi,&8:7)0n7TcS)SW&Oa:Z<U:PTjke@q5GOnUj$Wb4FBsl:>Oe&/]]6+fQ&2dTjlG`^k84\pFDL!q49SW3]3$k4mA(+knhU.V3UC0gD`)D;Bk\]Erd*05l$-BTA5Q6up^r*T1_8GYIZLj'RNZ1'0%M;m(H!&*\CEUsK?++/u,a+JT.7`bHumo[F8gV3'P1_8`l9Hs89o($6tm]>oqGC\qS%[[_%Zg;(feG/S?nKfufQ]hcWp0?^ISZ0n:Y2UB(B?$CX7.gMtL_.Y!jBp1@LEphkpR)$YYPqb"Ma_<PG*TEu5VU(3<=>sCMh%.<\4G*#K=E$4Ca?e]eAuQ2\bY;VRf<nUlZH_t([`Vt9&$e%HKN$)qAX.sp\!u>bH&Uuk/8u9&?!>U"@!Qi7<p=JX/<?DFPs&[mE>pA"*+olORo<LkE0o_Ala.f9OV%X*'/PbWI/\dZ2PD<JcqI`=%b]%$MYbqrU)+FoauN13od%_3Zma+7dlS@!F7%TZkLZNqq@qh!2s%6I.uW*TK*7$gIa<M>24_Le&DOL8FE#%p!m'UG&c~>endstream Gb!#\gMYb*&:N/3$sK<C\huiQpL*H,g?kVKZ/)($$*^(6g/]D[Xs>C>If0bUS"82oSH?daNYPJK_o(G<NlZb1S([6U#Pe6;NaFSM*6r344Q=3Q4#fBI^,W3VoE,`E4D#.&:*Q<VW/%hg%s[=65hq1,0aA?'jor`Z;5iYHpgGG*iX!QZK0mHA3SH=Y7Kc4''!]M;0PZa?"rE<.^]JIp1l'JV6AYFLS6s//o/e]Y5kI1!>LD>]R`K988[Vj\jWWSH!c$d\$W6,89pSu!(*8*Q2nIXFGag@/;1Zed;>r<NZI\gDq"Xp"<\Dd5>Y.q\a"sh@?![UVa8"?#Z"D6sl_!rrkn"V!gr96P@H=s-8/Ob)U[[f56[ZCXG:;qS#_-FT\+>r7p[Jjb-9d#@7&,^/pt.21`16l4&?>oqm^;a?lf-DMMm\]*=^O\=f=3OM>Y;LUd`ZpD[_Np9I-QkC[%U,0AT<5.m]L[4S#CE_,gjRj*^f@?@9B_G)p93SU.2C0lJ*$alHsR!,\gm'V]0,0,LlIU2:[\sQq`,>X`<M#[8;XO)aRF$O3D#XN2!`7Zo:JO\Io>*W*]YoYr;IWCANDnBAqXh8%/eL8tu2++-W'J*ah#s,RspgEMGY0<+D*n1S(B=G^EtFROf^@UiF*0roR`9V<Y.q#@dmISlY)\BriK6lR=5FQ2fok]#nRZ!f3"H/%eDq9er8q%/r\3R$fXpSbdF.+S?$KI.(HnAo<cF2,!,..Nb6-G&<Zi506\J=(3f(AZ9n'[rr)e4kKE^gCBdK.[rj8c[jd;H!"SeStdj"JN30e@aMG%VKWm^B^Qi?7P'l.?p(%aDAXCQ"Wi@F'=W9f@17QA&RK-V2+%n,FBoNjS:<2V9,!$qUHuH;\MPNA6f_>ZGn>_f$0"(r'9SP*.>Q?V\K79#X%AUp7.Sp[j@+OPR_EV&I*=?$bL%jS,/!^cEu;S_$^ju@+fIh!C7.Go;D=Bfc)k#neB3(hZ*NL^(YfPhLc0JF)7QG`U!)`>V3KiCIj9lPMSLU5`!dE\/79ZeC":B.$ZT(@E^kf\[26Em%I%a_iONAEn/rV)n7^0gM9)@Oob'992e`F[R.!7?P<I:?G^].$+u4'O:5p<XZ7%6qd]-O\"5kdZbRa[P!M;YOEBo-c4qh=f$sD*O)'l^mW95u(LugG[O.O\),IBC!qmjF"TCMDo`+mk_9EA&HZN6Z%:4\@]Qqi2@kcBmgX+Mrea3-Gt-YC@Gqbn0/4t2.W(,.q)>bhJ8HC]*C`u7ECiCm9+?bjj/DrG/).[cRP`R2g,UgSkuKj8@`$7R\&2lS![a)qR)InIC(')<3G@iG_05S!MQg:3@X=K@+DjoT8T^^AXS.;CU>kj69'K'Y,YX6X@N==^TLg;J>`A%b^9PqHqr@mLFkW0q#DDSI$";U/XbS(#j\nF?.[=(eEI8,Kc1oDuEb^fBG6m?pf&7643Zr4"5^TD.cVBZ[A.R8&G+W\niFI^bY3l?+EC>ANdW.C6S/Kt,"]b`H%#[Bh4Sa!6_7hi%1h*atQKSr^h+2A$T!pn3Wt\oV_Q69](,qpEW<h/>NZ@C.X]YBP"emU=Z?]XSXRYfq2lVJ?'KkC<Z%Lc&2p`0O<FDgWb1b_E,<O*Mat7WhU:Ei,k<7$a6=G;2,lai+i3_[W?'<HkWY%OB.oPkgiLWP0g*-'>0'ojQ@M>"DYi$!&(Kd%t)Ml-:['%U,WdBE[TfVG2e=[4X5Jgi]]0Wg;1u7A5VQo7VhHc,ukVCiZ5`/?Nut!b0Iel?3.rA+.pO(?cYV4F7;0GHUXqCaRMa-Z/ZmEaE9YSYik=^S^+q#NcLjh_Q2rk&T;26@,Jp.[@'(7j!Fn19MXOiNN5/%%R=t/(F,j~>endstream
endobj endobj
23 0 obj 23 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1519 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1726
>> >>
stream stream
GasIg?$Dc-&:N_C9_5'U8*g*TQM0seBp;)dl_q8OIN5!?K@!f=7Nd=7fD5*1?!tJY[OfP91,F8ipYM<L5k2bYs'u6$IL\ji)))QlCLE;rR%M0)mbN/A#<\57&>?%Y306[u5:BT>%XpZpLdqjf%>bE<8B$B_OTLPl@E:b`2-dof(m$GEToqYTo)f_Tk:]BA3DM/_*sBu4rs#)Nn0/K%kR_A)`F*AqR\X1f+UM_/kehcMUXLk4;ts=nFHrqc,(m)2;CCcN>]Ln&nT9.`<GAki9DG1S3\.WXSaYFPr!)<PbZqjV;KCbA]Vk9qF9P><?E.:N?&+uiX#,mRn&>)9f<bu$n-3Ps'bQ%P\/2>$\0l6(P`UG)@,l7!e@0<Ho6<eSQ)Zd_<F[<]$No6-nQVL$F(9I4'OrJS)iDupo"i9\XL<*Kg49A-[eH31n1Es4;AY/#=V?d=-o-0h+<+s[7PFAO>C2Z7k#63sd7;(p9AY2tL*"#[A!CrO\,_8'L%:Mb1==hm_Wk]]IdP)bD#B+Yn)G?VYR#OO,lT(HVe(u0-G_kuds.QBSKB%C&5brP]Da(c3[QDaij=N?6%jLJDn1JIR9o.E-Td"s['^`:S7P7;LNj%S^"2EPd]-pC[j#2E&l'g(c`^qfhk*Sl]n1+#YLENm':a\GrD8YBqDE+-`<AMt]5(,J93WqUK\$U=nRNKf28Y>aSlF3!G!iEo"GBAcp.&(tPB^u(@Ju9S0H8/oq1qcQBS/)l;V&o4E$H4"HSEYQQ0Q&[>qk!WA]Up`*,(,8naEdJD?n96+L9ds6emYl`#0'co+F5^CoMQ!"R!AOlZ-7kfcuGaYL>%.%2Z*(64a_XQ/X^=[O`&m<CH"snms^>4e>t#c_adY:]>W&L`[8eVN4#L[Z\.*,#g/Fe&fSi%$njGp(+Y>5siD5mJ#bWdZ@ugb3.$qYPW=pF42hE?W,t8%W:qaquZgJq]p)8`bZ/RDQcbYeLj<'V)`pls)EY8WS6?Bq?NC&P`aNWHqX,Bep9grakW56'@5Kco(/paoW"JDbNM9ZM9\B\l:mNXgGE9N-t:dRV3HBJYI/]a;:+QHbbaXn%@N2IaG3G[A@atep$CBl8Tc^>Q`ljb7G8M>2Gg,F5h,5]3Ne<i@FH+1X@:d1rY=V,f&a+j(S',&8n36G']h[ICtUVBf!^:Q)OV,1o5!pBb$oXgm*al>V6gdQq[\"B'os+'QTW!:9q8rUkKl,9'0Q/*5fF\9%(d?:2OO,#eIMlb29*\hC*5E?7DkjYo43iOk=:o1<<n%3rc=(Y4b,%t;Eo+sF3n\!PN"K\1UnFXC600%S6`il!6WPC!6L&]1]D8g]i\8<WeTOG=4kEn663nqD]\H-U".,Z@k^)XE,INn=<41C#a<K4EnGn7B4,99c2^Z+"]KQ[BTE5fiF+R\Sr\d```!3!VRSoNg(^q"S_\$b3cJc'hS@!uQIFSWN-8o(i84ST9t;\@dp&nrgR8q_CcIfOg3;SaAQk&SZ6%oZ(VBJ<M#t?i!QG3#BE~>endstream Gb!;dD/\-!&H;*)1#J"l`5]E<YH!'829ket<Gdf/I]7F0li@l\9p[/?oBSJRr"^shE[XgQLr#\=R:g]7GmO_F?G-3*/-'"9i8P1N/mpL?mP5SJn)8:tIC"/EkmQWD,2s0D*oeF,[*JNt/&6d,i1%]q@t%h_W[8K1jc(:VEBuJgdU(W=)@HaV@L[NZ""O*8p2U9kLD`EV//D.`%\jo#Sf'=KoipEG>t]@knIfF?gbqcmW.*8"#(K<$R2^">Sr5O&,rB!CWT>BY'NS#pjTG9-6i\=u]4SAaggOpk9#l2qa!ucW@CDIPNM(6Bfo"-=56:tu;fL\7&<,@+ni=)/cupUCYE`<E\4h-h5D"UN"p#+L-uA?`Q5eOi@=-0,q*rG<[VH-(4(0_)=KPQ<!q(DQ\Q+&2@Z^otdTp]2n4KXaTXdEo[9k\I5)!G.Z/';0\%33qDN2d%94MbF^.$M"9WY3NIR_%.Y>(g\",tCDZ<:9uG:+SnB&dD/DVkae'-2_<8FaG`OfgKF?$$RVB:7ea^_(%^No^rU%kJ:Q5N)j`Ac_$Q/1ZlC41qN+en^lE/F&3;0D(\=g7eTY]0$XQ4EuQ_S55B>c(FICHW&p.'cBo:a-i7"'_9mQL2NA4?`mI/.$d,ORBE_2d2#V(oF@S__s]'/`7Zi:BG>4dQ^HBD#K.dUPF<45$MA@u5<uB-ATsN$M/-ffJKhipRE5b^QtK_J=C?p#hRqu2INq+':&'<<+Y[Or5o0.V28@7KTn+I]<t?8Ts.W+>gSBd-8Mg^>9?bM3KR'OFmLh,A#fH&6CY/U/[i\U)_aK,^/.8:O[GKcfn+i5tf+oD*G58Z1>^_d4Y\Y$gat_1UY&$hhm(%*^hg`);c"ueO?DHc'&o\S(%\jBS>4LOJ4?=/%(HL@?<DV<</-s3,#lVdeI$_tN%_T:4iPQD\h9WS^<`0b!`rVFm=\q.LTQD94;W=^[OtYlI*u_6@,*Tu@nkfPh!,QWjf^9?U`T(@@;=O^+$T3]aXc$VFR]-14kI@RAjT*`8!;2?PHH%(VNQ\EUNS72*ZD\GJpTf.PIkN^/+5<,AR;,@U4t$)$3@aS6EsALgXjVT#T\eSm27ZZhO3FGB*+q7+Y<3*a)X25'EGDC5\k[:o]<%ZK_=6pp@Sm7&A6<#@-Xh:+Ef&Ki%HEmg#7u1[din;YqRhSToNK5=h!NAWjatJdC/)##:kAtF%UZ@(I/)2PL[2@ldR2.5IQE.aXqP<nAA$glVJ!G5-V1#2INUD<0Prp)CKr$Cok()BgTGercfb&CkB>gqBBtXL[tm<'L\f%(pD;($UN?IVW?J0^JS6G#+cQg9Tf^74&NB%i@?K`.e,sSqX$slg\8PMV`--Fj35[[X`CugI!o6)9)P"NtU0?!TBO%K)XS_*"&^[m+L!@n8:*uo#_V<Tog2A!Wd-U;-B5$QdYnFh/rXE59P%'8$=k2?W0_U%YeMcNR>B4TnbIi=aDlbTD`#Eu?AE^?Ql8+KUki%G8($E^2jls3C>Gbjf7u4-=ThsBXg=a8pnR(p[&QchG3#&P?#[N2f$Ri)i@/YIa0b80fLg7=G7GflqHTr=WcP_GN]6DIG2DtX"q"Gr.Ngi"ha,mK5Y2jDniQh(.jFco)`Gg_?&nFAiW3k8=]eTBf$HK!"Kf1c$me_'R>usXBb7NY.>D#iHU`;.GmW+Rd&^p[Pq\L_EV,q\ole@jOVn3)nUo[`Gkmu8pY^QXB=^$7~>endstream
endobj endobj
24 0 obj 24 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1952 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1609
>> >>
stream stream
Gb!;$=`<%Qpg(Yo6\0,h'/QEoMX%DHBt@d,dbAPEr5)"3X]U?Oa[t)k^Q5sS[OI)M[1/%iaR;=7JDunqs'Z9(F:>(n))*#L;d_4t@=/BrO0+HYZ+986*Y:h<-a8.)Nqn3s=W)ISnA#MN#jN)^]AO#cKEq_5Gg?&O%pHgAZ,M7gSGL)+,+-CSh`C3;DVL!$,p)6ck4s`q@E8f^Dl?#YlQ62G*NW\LKBft"1=,]&SAN[s=77g#O*mg^E1Z5!r*=%Pc:aPW[L[!eUU-\NMc*"59\lWm?`#;'+2R^j`fCS3!IWM,M21/o_nT.S0F.)EPkhZjqc_;)9EH<OH%Rkh!HG^pe>ImX%eW[8#6f!*-lCpQguXW$[R]Uk\!C.):FLA)@*KJ3/se8agVXc0h%NuJ)@i-Bb^euE!o+T:d(sm-]r**J>mQS.G+;YChm35-m/YND95)_EM?G:IOj2jFN</4927G')1>c:.VpC@i<6!+<BZstZ1.I*PR-al@\;p&HV)!7f(W'oADa"EqGm-DTp,7;.&M-2Z?%[X@dRC<)cHqd_paM251&hF&o5pB:9#F@(:M%&'l$Sab?SW37d*>%<=$lji[sXF46!UrUX^0W,g(H/BC0ErLWtZ7nY"P$;Zk7O#WtO<VT2dY+e-TYt^5C+g\0t<s-)Dr('^hh17fsY?8p$!P:4],d\WP<HXIY\f]GP%Qea$7OSK-uKTXEqd@(^6-\_#m8&3?'CK_bUR,16*IL6g]Tg'1Z3kt)no(5#5nO>;AHK51W?MG)IYjJYChqV5d[*1a"TnHGH"H)["fMrnjamqD9*06mVQGCMA]ESXIrn,CGG\^'fJNiCY#2;Cf_gaBN"_QD5oS=&uq`bM8n3\:iGm"kOK\]&\]Q=$_V1*00MUfMeM$t/(>X?q0]O"sh9""o1>R,/pg4g$lcVQnPu-HTd4k%Q<OW.5SI.$mSthoOjiDth?,2f\O4A*ak2b#ea$1Xe*J7V=)FA1BmiL3&bk20&c`dk]%QdOO"GjNYD8lT*t:L"\]s_:`Y<lIchk?/,=dO;luMdTTn"oY%^aN%*t/FjBoqJ0pkr1`V3@^5ARK;n^=<*O*j3+MF9[gTbm%'a\QXkn9cV9M1P(_@VCu.U><(mkE5j2P8dP/RM&Z.)?TYTWeL/dLp*ka'dn&>iHl5R^DY^mJe_mgl]jrA-Sk[:ihbb103'76jbr4<#3KrUm.U<(l*9gSKB>4]N=qZ2f-<)//u'Pg.<Y^W8U]&nI-pS<N_VmCS;gqU"j+$RUQr2YtrVPWarZg-B0RQ2Yp+IK'3@)GI5k7q=Q2U$2A^;.;(Ebq$uK?8/Z"Pe+oKYQVm+C)"c-X!EJ=K@V]Z/4]Z=.^0b5'FDH`p$>L(E9ANkV-e,!lE(.-\6EB#*';'9jX[':0!=<SW]%<m3qlJK#;:)EfW.&3E?XJW*CYAlc4YG4`MOk(,e(U>\\`nl2qd0S[9)`p6qeIlARtL#=meCSBC"m8`!W:H,_urG<3-bZ&[-8uXj[5Q/?H1[`e!^O/5iF]@GAMUHa'=oFr"d=!6<moG.W49%S>-g3fO3:L#GcX@^4aEorL!JU$HZoUnt\SBDB;Tsj4?!]%3E1!^^$>J*umbu4q/"SnTd!o!m\C\_2dNV<*8+EZ.d[JGI<Y%MU(3G=q@gd%#^*C?=?:$<EN_kMO3I?$r<^5B"U#X$40H/_QiSd?8oB,F*KqB+<9#s1P`OZ.YbPu&C,Xd3"GI05*?*o%V^9C)Fu#84FBkm%J;rfkR:l0+!*d]4iHuu`<uuc+K,Z;S0"+P$$s'=YQs6ME)L'<I<A8@@>F5"oGfcjIP<mh])((rgT`@rPE8>G0u0T#U>=(galmnE%<f3@^M['JA?ur)5I%p`L9h8h*`I.UJh@V6qT8u-M-ACV2DAs9/Rh$Je]_Rl*@Olc_3J-bm&Al;@$.@s6g<-;qc]mm&7GGE/[u]h~>endstream GauHKgN&cS&:N/3FO`RVP?8K1J77)s/N[rM:9!6k;7ueaWW\0EKS=pVOuE=9m6TNL_6W+'Pq6>QI6#cC1CXSAhRrV)0*':QnG4.1/mpX>DD;NR"1T3AIe7k6BhKdYiGro$[^$@#o]W_\geOc\b(<Jm&:=6\KX"B_641c=JXOY7>li/,orBpdCLi*,5rM/CZbU['b8oaL%.@U!;EOYIn&C4+RE`;%/RPaXlS-VnNIP,d+^F&g-"].O?=(CQG5M_PQ?RM:i<IacLAau>6P>T6,fNc6li,N%!I/#Wj*7iE..9Q"+o39*ZuNKlDe#Ws>q:RBTJZA/j#k+d;p3,REeK\oh.&Eq;/caJ9W&/YkG;2VbgOeY_@*)$>b?608Ptb@+dm%9>JiQO+<@3mG^RSC*AXHWM+u&B"rGZ:8E35g#]@:3n4"R5&4C)13s43pYs]NAhtZ*BG,N\:;Q'pbDpn[e)ami,qmn@rl]L.^8C*Q/.F+/u(%`pZ.Dk-lg!oIg`"@bmQto>@BfeE.;TVg,mF(8e+pEs^>oIF29S,$AbYD=JCIP-I.4iSGbAo+e1L7IYQ+#atq9/1!CHI6<WWhDGLtbhaa!?UnB"CXJ$1'LbQ35:U`^)m08$&&o?5Ol2OcL3s;<gU1).m1Jh*U-RXtYfu8&T6h^FJLjHsArScTSJf$G$^3d-$GS>q'2[_$NV7-3r[0>mL\<htNKIl`-`r:%IOM#[b.L^c!bk*Ck2aJB_XWn78ErJ1t>.X&G3ZB%!F&DbZ_e:NQs>)A1s8']J_-2s**=0Y!Ui4m"aolo6.j9""1PM\*UP]_HjKmF0\Y_<%/)ht>)H$2uq[$5PS/6M)BL*:EU(juqMW7nDV7*H0-$0t);^'&o)J/6oZIrQEAmMBcB'*E?taLC'FmPmTHiS;n.Js4>>cCr^^fhHaD-_-/=jj4)Ct!KPTQSRL4o/L-nQDG!huS/fFR)G-6i0Yf]cfr%Jk31n'SA`A]^9[c?Kp#l,A@9t(dE-j?/N6:_&I;oD(>;HiT&8ks7IMV]H7$rsH_0eXL^_-&T'3>K0B9tjp$$DgD$'Mi=MYS^64bpu!1**)rjrLJU(@TVR#C4?FCh=NEgW;OSEb^/\"t4J)J@a;go#K#UH(mlADdI21d[n>/?uU,U&`Q3b(C_l3;@\(;22l0)H#AK?(@dWTmt86._/LFM9ecJYhDZm4"H/\VgY2&d=R7C(?q*6N]kfTa]rdNB\Mq"Q5E4a=&6/t_1jf'IIs;lZZC*N+B5k0cog$0?=0SQ^nF74T!Ej3aGGgOJ.st#OKOd!GG@u%*f;JkE3u6SPS:9GAi:8ico$qp^XAZTfZ<T1YMYk^.qi2,dSp_J8\1<CAlK`'FqCq3^8OU3YlRj`7QK+3g:p#F79,Up<=fQ1'1;bl;p55k\`k@+39]d8%bmRJ#RUhp]7h5a!EMjLc&6(/i%FM.lE98S!\ns:DN5jJ:%P$#q_.^&RqXn`pJ&f6*DsnN$b9P;?CB'B:R4Ge/+D>,6VM]u:ULXB+oV\qII7u\p=^>6AAJe5-i2^5"&YnT^.o/K6dA[Q217rCu_1<6WNr74p^<SJ*U1m6;?:I`"qPg"im.g>2LG7rj$`ui#Qi~>endstream
endobj endobj
25 0 obj 25 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1559 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1846
>> >>
stream stream
Gau`S9lo&I&A@[:m,ZO'R0k9+O9F=%e&H8dC#do^Ep3`O+UJbfW$GrnrUjIc9<p.;DEH<NZ63Tlmk&]!D]oHO^1d,+('&9NE.1B0176@<El)8]!T$Pa5<Qe,(@r).+T*?j5a&q]6+QInKFs%RQ;A-A9LR*U$VH(C">F'sa#&NK_>K9_J]*Zm$P#SO0G;cso`9md6Y]B5-qYPan,*jE]nAeEGN`Z97PM3Bh51D+J.nQ0E<t"pc@5TakrnrX(TL)of&lI+?)i!L_s`Q'E:JBt;,t3-<<b[d4-ePpThPDB9NNorj1#Tg3G0U*pE&=D:_h=WE2[M5Ddfj265p7$!R@kcdiKoHSYL2?L^8Eh\Sr^iD299p%sR7?GJ*+*/o",7KF>uVB5*<&-1"UF<Y'usWJ;=ETfAis0&*lg')NA=(a$'L?G-g2a)"<+dZ0fB2"mu;5Dkt!rr>#jUU^6cVFGOXd9R6l9u1:Nr$F5$/(fLH76j5k'[CQqN%DA"?>KY'85j$3q%Sb]^sm;hH:A7`1P@;n$'!(nX1A?]bska!1/R::VG%/m`F6uS%d/1CCi#rjj6"GG[jU3*c"1)5''pXiAI*@Jj&u2XaCM^V%X'mg5;g$VP;Ooa$8SYW*5&ri*V,R&Ji73_btP7)a^CfS)Gg,gjJj3UB"*#D,E*SJc_q]b8kY$+@:66K]e.bnO@h<sWJR_E9fg/C!POc_H;WBJ\pL\AWBh-SWH@:SZBTWp$/MQnpZPK^6-int:j*\l(O.BES,$c"/XXk<bZ(Rd!j6J"cJ!;nOk&JLP`5"[8_$W3^HsppJd&)E%/WQAF:s-S"ZKL($"5cK:9d$FmP0LO7e;WIDTH<#;H-=h^#K;R)%?MH,%"K<W@&:49o5-8K<#m'\pG!n'41:kd&EqeZt\8h,B%[ZYjPdLi9VI"`tKQai75&0@,0DA/:8l8!(+(jWLdn57[oYd05qDRIh8kJor/M1db)'WG+6*Mr9XkoJs`brBikZ6BbFi?$tJ&!S3hQNcY/.+#o"GAj0gXr\_Q5^G74.D6hNjckC.e<,C5j/)s.4QJFlVC6`KB//S]gLW[GdS\BYPo7nG7"1Z]-_I#!u]P)I"trn`QNakkABf@4#N:V'<Jo`1V?Vd1Dg$)9$i7X#2HIBmSgQ',^iK\@pHC@&[OnT=D+ptN?&a5I2i%VSBeR_f#rkl_B``F3kUmj'#q+Q@'*GS50hK*T;Q9E+E7Pe]!5n%nobf7.$9;*m64SS\boB.;H*:bY]^[Q=Z!;AuWrY,e)%V*^28I#f,=_X\g_Rj^*>jq(I,lI'.^b!;H@[WVfZ1V=SBf=BsSF2!bE7I'0]#X/bj'Z?`"^XROgG70YgCV*W$23V:KWZ'W1V'apS%B%X^^/JDuJ4'R6ohM^]k:$J!(WJKG]KDE_]geIeIT$oiC7eoaFhZ<O,P^S6RmDa0WSD?sq)NGpPP,\!lcK+RBdjB'hO1BY$jCMpQ"<u.\DC7Z\$I[n(dkpO%i7q+\](Pkd]o!rf=W$*$El+gX?Z/0=h]'HCKoB4Ogj`!F`cFNb\VJ&8.iZ7$g`4P6i~>endstream Gb!#[D3*.*&H88.0bRh#>YXRt3@ip@/hC[ej7_+HkbOJ?Y46Dc3_i8pp>FQ[3%n"[VCulo?$G$[mb%-TAEJ/R$9%n2noKro>`Rdr9+$p"p`?A,>St50X0M?NU:$iDL``paK'*@aE0u:L/4+1lq@qX6\"/UHp0_UX"d^nRa$WBK2[B]P+`GDcj`9*LCkl7j.jL\=XrVUSG[iP72IeR_$2;iLlAc%d%KZm(`G*+<UCDh"#VOZa.#X7=o?P[*]9qFNRTO_@7cDSrCH]FlVYrQ\VUk\Zbbb-QjHSnP+=sj9'4,;jLRWC@,\3/85WskF#5C:n(p$+`&IOcPF#`r@"=fs49#,kT@ZeK:(sgQu]BsHSa#q-9Ba;]Z5#ctH=ZR4[U9kh'3<[^JBq0o`J3pGIYuD&9INp>56u!)P/fZMlKbUBF.a%>N6#"2pe-U5UP;)L^??F(.I[+'9';:=k(R\AsZ0l!`Yr">W@@)NeZ*HMZI;]W]'Z]_?c.0GRor$3^oqs`MIr2KSlJmml#KnUXHV7tmpTB=sj*7`em&m;X,=NqlGla&E68K/A\-"r@Y0/geSguH:"pOD23<"4,;AUO89J*@L`+#Fg0?9cr*q>eOGC9mT-;fPH[,kIOJ@iWSdW#m7o&9$,EHdFQaHH)ROY^N$jp/_'Q_t?!dr/&o93oV,P]eD]VS;IN4rbb8W-$k%G[=o)8h)VC=/+a5O'Y\&_Mosk7Tj]S$[/]go/2_K-qj?g984]i[W^3Rj"Ce.:Sn3EBf<^rHs=mCh)ccXZK&:kJL7XP0\>.3,_kFLGo]V"BP#\.;DV3c;KL<%;DVdkMqP59T<1[j:4!+&Pt\tu78`bY'SXod+[YB<;Pl`4^7EQ._OXJ*kOB$+]p(;aC+_bEZ!;4s#LZ&qj!W4IEE&"SS]sI`0h6l0`g`enF;em9WRO!!@Pf-3Nc)$arV?H[q7<n.,E7kW?E_+\H-7-)#+j;?($;j<omf#%8!C0L<r<E/E)Xu^grp7U!md39Sn;r^=Fa?\f.X;PTGH\g0P:2!'d3)d`bF'"hMNASs'8ur=3e%2m:uN)3idm%J(TYJi>/#_Ys?=Tk)bB#7b9'F3QsphmmU2,1I9ri`#jrGp]m9J8&6]S2&.ERba=2tO_+_[MJQUdAXX_GcW.?2`@p`GArIfUO]l4:1gA?1?/n[GP;@gaSKF0d3X&7n7^3`-.8n4N3^3_%gVpGdXNGRQp!2TK_6o#Wn#pbd$/Osc=`$AOiCq_m5$h[#E>Ohj(<q_hMh<E@^ICdVLU!T`U2aMT>4!5;=(HPFkQl\nEI"/_#sFpXR/NRk4ULM9X]l-*'=Ko0mck.bC;hYEbD4Qm6Ia&C=Vu$mZ'T(>,W8E:JMW0n";8,=R<-!+`FR])>I:k4is$m5"gAeT]gKL`])X=f.!61<jggm$g'V_Zf@*G9!cDs43EqnXdnlStN,4qD2/QL*l\Vj7"dA`Gm8VpY:Z(Q2UY+u`C+I"!<2&rrnY&"nN?7S6Pf$W7Vjd.F&/cNP"[pnj7?j3mJq>8rB';WbiI?ie6JV*?3p/B7B1OG\*H85O=MS4,E=uY*A('8LZA8bOV?rEoU@D!pD:sHQ6p^@4m2sn5lFP7/a*<:1n]7gJWke2N+\eU@OQp;D,1#P4?*7j=j>6t0o^1>oAKMU_/Fsj^mVa7YE<\:qppZA*PbXP%]4uF:[744e9"*I:%$'AfX!t4"mr#c*cq6Bl"1,aZhp@G^NX!<cOLQt<.-5R2lPgqMGtN#LSkho[PA*u)(QfKc2g9nb'Y0@RAUXZTHk+K&,00#/MY%\WCa6kTr5:.6G7rd)-UDK0$<6m4ZV/Vu&n[?ALSX31Yl4epbiAL~>endstream
endobj endobj
26 0 obj 26 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1871 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2132
>> >>
stream stream
Gb"/'8TWWE'Ya0?]L`WR1(f+l$^g0h`b@>,4fRHkm_ousKI#7s_31&3o[)LP#O[a$=`RgB;TU/HO21Kj]Yt:-ITcUgV>tP3nFA^E`aLn1AeQ,\VsnS=rGE58EmJoZ#YB`rJb[*0TU8q$3%f3!mPBsS,LWp7QAeB!9Am>+o9]c:K%Y4'!V'F[8K3IrN[rpiqZUt"8(KS<>h<??ho[=+GWp+OktoUd&&E2gCR+0^KhWt:49q`4*.@\FiWMI1\LPLNed6`'$5G0C\E)Ftj-.T)=iP$L,'^/+<2/t`A^rT(?GZ9EiAJK]D,)&bD\`:OQ,!O\V6'<jJNXD'E>F'EiI"+aG!IZu"nef>6;S2@Z.%Ws5f6-$]=&*H`?!C^p9_ct*hh)L,8up9.[r-<j^X1sXU2,$L,Vd]AtXI5O6_/uH>LEd28&u1+[c@Cqnut=Q45K]%n(asTg]28&q&nsCcD*'N&2'IKiQ0Xq)5C*q;0%b*P="eD0V"E`[Kf/E<-JfKlgkm12#Ya_AY56?Usm/NUIO0)rF$do.b,an1R`QWWXN2-i1H3Vt-uUE.-FFJiS2)r2mst>04S3#F77Qek";qMb@19ZAPtKj@N1mFHJncFTJ,a*u2=9Ti+0Ki*o@eo"7@NTo@H*-$Ti%Tui2-Rmk?/OiTW/GY<\GCUMKKhPjuB]ljBWg%T.7n_=_'>/0KQf%YuSl)`Vm_%iN'\":&\&s$nT[TghMD0?aU*TYMPA\!=AKc)SrO9IoN+6)SGo'9>2''okNM=7.%I6X9?l4>88:1<]3NhZk)n=-sO8TS0:7RK.*IET.K`V1CAnd!QAm%J.6kr)cK1?^^'"W[I\@m)NKHND_iHjqs.6%G`>Fc>e9n3IbqpJC@.T)?d.';(S@)+G@E+Gk"NHoXF)`U<?+]\thjFE"]nQZ'1t#Gj0]FZ,0@OQe0bPLdI=PXj(Jhb&Tf;A`YeZ<L?j%B:6aJN`Rn,NsdA\"?Dk+@sje)7!>M]:%Ej-+;hlN<,eV@^)']Y>%F7N<-IoKJt2*[;=0)]!.cl=LP#;j$;m624%./H=Mj!eq@("o6f#[;5]06f&9U7NnL'/Q_Id89@-TKC3VGGJLBRC2''Kr^rs(iEmT@IZF9W8D2-f)m=>8Pp&j.#*VdS9N+5*5&]YZ=e\sVrne>Q#bb7U3RsVthX6gI"GYQk0ejH1:q:iSU#iZ09=155YIXo(-2_jI9VC'/SJ8r=T$ET3u`Pl_>N[?W#oGE$4aU9,Ki,uC8X&s!dLY[dBFTeYK)p"@;dK2o7>F+Iq!eN'rU;J+QKt^YJil63Z^;'mGIinTuIB`T%pVCJm0jHZSOK/Gs(-r]gmYhIul!iZ1.bkej1^L7bES^q!5Ltm]Bo(BaZq@K$.jmeIe;n8*lc!Is1;[<R]u0mI7*]c$Y*3*GVIB>,Z)?p'):%jHU0`GC@M3tdcmSCU63f2*n9X.`Nd;"qam8/[@o&M^?<S."An2;Sr7USU0(eM7D!:`6[\`%BRn\uK5*nc@C4[ZHC"m(/]mR8Xe[RI'=,#5USuJAd(@#J1Jc^RM<UK<B31sg<6g"cu2/-Wi1pTf@V()tZ`ON#:=/sQk60E!Xf@NB_bnrF6Qot$KT!ZL_"[?ICqteE?fnp0oSb%?b/cHMbE*^C`F.<=#*e]qk%u_$dK,s\0#)u:<H!=jub3(I[F!Furbo5cQ#?5.2F\u2^*6p1,lbVTbA?j#LSg;Q_&?/58caL=73*aSG;gRAEOOA'!n9SF)AJW,PgpDE&&]7Gb..'VCL89)O'B[mh.!?'Do^OaZGTEUMY>oPD(-7;2VuM]UT<k!7S>p&4r>_JbO/V!MoW*buV\mJ<7\)mb?Y)2\kEX>bU2oP-pR9bQXM4emO]>o~>endstream Gb!;eD0+E#&H;*)Z/_7ri"m*@+<gZZ;l==WC"bCRRXF']%VZ)Z,b6EU9]KL;]b=Lu^^*7^BcI5<\<hV]pXs32(7=`(l.7$Y\/iH0?S/Ku(e.p+N#iDb_<^(55A/R,S$$CK"++EAT-!m4fqWQZf[PuRak:\=;pSi1`qs/5eI]Xlm!d7a_spH6@"U1QeA&sJf$hoX%ggKp0B^i3lAVfnrrRrL0YC?mI%[*X&]ZY3bOVd5[@.b0e'uhbp>UQcJ^0@:p"Zr$*>mIQ+'9(^-4t=oq$jQ+MJaQ"/4R`M\oTf@noqJ9."bkQiRM;i9oaIW:a!r6.?$<KK#In@]Rs90`6h"`59DfWjk1#cnHRj-]<!R?`q3K,VO547-2]mA<#4t[VkKL8m)7r+9g.,*s1%;W=KM2;h`kNJaFgIFKTM&0N5^#N5qL.i#d,-\+VC)2m"b/W6g`**jLCNP9nB$U"tV+!bf_j)kle.3'^8@SGC-.G+7G?,&]PaK$-"R0d.PZD][62`hr--<g<CYh<ph(WJ0LBCr;M6i&[X#UaCL\`N(#b)HOFHMD=#Dg]a`7i4BW.rRaHC$E[gQJe2MX;WptM^&s1Jc)IMVU"B-Y>Mh`B(6#FU=%n],2'#4mJ$clGQJg12?XOe;t!M<hi>8GF0\L>qr7jOE'TtF#qOboSQRjmO)Wf6]&0WLB6qd)1\).H<8]bod_p*/0lnJhar=Ij);.C_2aJR4t3RQp<+,,U9LiOOu[kkS8%h_o;kX"R`al3kp9K:]4cU[Rm@8Na2JSN[98QJUW?N:bM&%PcL-!PQ&9fN`ftH8Y@Z".6s'91[B8+F2,,/]PRGpggQ_YV"HGd!Ojlj:SA[HY*([\kp+ifq(4g<30s9i7NCacpu:fNnmY?H*`2f+otW&\O\1KJ/9oU0e63_K#iJ0:dbm+39;WFb^ELR"n@mG)(I$J$5FmJP)T5WkQ_<ka-ZFspcQ,AQG1dM%Pml1Mbh<F(7HF:Z:]IR/@&O)kg;0\%Cd'C_C#V=^ZgJ$)(1D%/%'!3BQ1geO`qnS?,3_*f8dEY9K>h:)XT=ncX5.A<Y`cHn%1s!V[q&(EO3[OQ/X>?bMY\5?1hoEgt1<N%[!%rEVgLX@1e37b\(*_k_0,]*%fUMA(nu"FfX\qJetQOTe2&l9Z+6m^0<756qM>VjcR4PF*h=lbdg^,1M&+mNFV=R.cDE`T<>"(G[thfeZ]+6I1frS2Sdg8*TSsDfG@E-Rjq.j^KE+.MQrPHZf2n-_BIk]ahqnUQrELZR"@4b,oB.C=j.51_\k)-W,<dSIJc2Wk"/Bhg7Vmrmk4&INZp6T_j'$CAh6B6-^4V.cO'mV,c:9=R-O.JFK8gEW0sV_%6UJ.7t'[A%9:M)V)QpF;#G<*$*fBP_:)<!d-<`F3PU(),V1:gi:!3>J%#U\hJr<Dd\@M.>\b"YO@R^X<447U&=3tilD0XNeNK_4A0deH<)u3ZHmG;<\%NR@*(eo>>#umKeH`%!=1R=e7X\8>4%A=VYC-3"maar`l*nIH5+\74h(>2]=1VV^=EkG\r>#U/m`_:aQ#]Nu&rL]b6-;965ek8*YP'[$[]$RgSiqO3,Y'S-.V_if,kKHG<(*[0PLuiR`F=:uc__fiI,Lrl#cdGHZC'*VOAjM([U!E?b/355PZ5%/cV$I&rO"HH&/gSae(5h,-VRR56Q@TbneL,s>@H6$XeJ0W+7cLI2T^#OqLZq#7Kt7\EqN2'['$<kRL1cYALr=_[uBrC8kD8_LF7`GpQUh"6s8?XmcZ6eg%jS?QWtHu5,sQl#F<et>5tP=>7>V>(fW@(FM!*i6k'?8F"io*Z:^q\),YDk<ukqM>T1X;\i?ifYm*8lBReV1"Y#JD[(Gnnd(L`%eRfaQVV:,^d]5f";q6$9i8AqD1nq*H3O<)"SNrd0"#LPeRqnlZ7E^g^kuKnheS1:9h_`c2U<=+M?:FkdJodfVG]Sjc*FJ!e>[+TbEqX:LXXF[okRPq9AQ)S,6cN`_3k5lk8mrJQ#!C=Vh^h5s1d$WbB3jg[ii5^5FC[CZH%%9d\[M#S)cL<A.[mGJRdqc1UcZ\DW(9m`NsO3t(V8Nf^\lZ7X%a%D*1OTAJmuc-LijK4P2IS)Ok)G'=`@@r$dS_R@./:@~>endstream
endobj endobj
27 0 obj 27 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1724 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2064
>> >>
stream stream
Gatm;>Ar7S'RnZ;35@5`D.A<r.$;YN]<5EAFC!Y3gOOD`A=9lkaHA&Nl*as66cl(`pnBCA*J?2eo:>c4$n-R-o0:n.HoH]$QOF^dfb0Yp!bSXp`Q\5?&KYsA![,4OI`-e*^AU=-no>"&&k4umTVs,.$4r(,V'6'_l(hi&8e&ta*WmTR.[:".=:hM%JDMXskK$hg+:P%BoK2l5'e^ci:-')qrBi8$_0/F2<*p3Z!ZAdi"-YZ.rGi@NMNt?-F\u]=_3?u`)u.LbKP/ocB2t3t7PE\HIAaD\]@"07"LB&74gr!-pd/p4TQpA/YB:#m#@&`W/6gc9nLDdnL^#j-d&%g[aEN5.Weg+BM7trbE6h%l-sqWOKT4"SdQY7Ubi=j_.b%jeX5,3jQjie0-g8i$T%h-fVAQX9DC`SF;#h*\BC-Qa5dulKJDG;/0mq1@Qdc%5a!L7h01CU[S<,gAVoD!n`*7R_9\F@MqTG3`G+.=uM;![(^h&H;Xr-na+g/!Me[S9_r'FM$k54HE5A]g6_@Q0R7'HsWFq=V.e@_gEn16;.O]V&MH7TQCLq5RMW<_hJIX%WM/:*M`[*;Z&d,]o+OTg-&4>1`=k&jO&E1Ir].V$[%j!.+G3k9r5)98_-\2M;1hdAK1Z%X,+8[6VFISB?T'?Ac@A8,r>$-_]l>ED"qNsh"E"6%S[o0=,T^;bG^:'gW"Pdei;pcQjY%kr0C1>3^W'YT&da/s222*$=iMX]2$)c'#X(+cq]GsYA$fPe=#((r*q*:Bcn9]G`Q0Ch]QYnglf]aud2"6dOG,hk[]!k3eYr=>Ae;Q7KJg4ReEQd$f+lTff"GP&R,rSu:!nf_a16'!<iA@Z^(98SN4hNkloJ?2YTY32.B];j%"-%Bj=is!q`"Sh'$"s]0fqJ&5`N_p'NW6\YU7=I+7g.+6`C(CD_lR_q>e=UJ]R5%$g-K6l7;$+tb361BbY2K%KSE><[O>G)`(-'%YHAirB7*nIeO<7DY";p'_r6-T-Os!XSPFH#fN<mKidA5>gW2=fuOTZYV:%imtF^@ei:JfC'gsje(2JE%$&:otA=QD`O.rRq3`JtSQYicEKKt=Q*N8*oMfdK\Hk38ER_.)'\,`RglD=d/WZ3?b!g=4lTGQ%jb%1!>oTjRkm0j@*]_S#$V>Mq\h\u\eFd-*SPef?;t=Dq!iUQL%pA'lC+8/Mp^PiD^CM*M\("M:RbjZ.IPE-`Qn"=3W9\qcEdkg.g'kK/.E8=st2B*jO?Q57o]13&VrFLj#]Irmm!MWArKApQ#IM6[f[#uoY?8,Tu'O4-$NGALIPH0@<Gs)#9\`WfIiY3XMd/l-Xp2d18rd[iC7??ZjsbpD0$,!($dTmerUS/cTGLc_,Lf]dYa"`&naNmf.V$dsY;"tc4db&k&>AK0=91)"p0*p4f=Z#I4V[gO`t(\Z6WGKAgS"C%g#8qm6<X!l9.5]Q<J`S]9mISB2@:$K[^0K5r(0($$URr$Q$=iP)rk$LPirsNr2&(C>e'5u9rLIud8ikcFd-82",)m!=IZMOM'2Q)H[i@g4emCk`tME/TkR,c$60QhMON)a$E>jng+)"XT3_/D4AE..W_5b*%rAos(-gqIOi8]T=W4E`P;e!ic`Y88^+Y(05,D"*a#Cdr;.j)UW'N)+\ns"`Z4!q\&#B!d,[5(9EUrRN]&'lQHCB*L[`<V0KLaO7r/Fh,NXaMN(RM4JJN3s*'E!HZ$NT`~>endstream GauHL?$G!^&:Mm.R+%D!9lRABNQjJ1:6TS)mC(c1KPb(@W3?N%?q[RQLUOsH8&Bo^#;Ch<1o14g1qL8>kJN$4]km:YPQ@<KiPf.^fFIOD@(V^4r=AD/J+VW\>s7HJQ6//]%=Yd'"^]r[FnBh2I%;WT$O^f]_5XOI_!fL6=H;il>n=#3d-cPs=CF`"WJoIgSrjO$flRufNDIU/oaL[ud!r4CJ"bJ7$b'u\D6N9]lr5o&2mRPTEiQm"!lqLe]T>-%.>7gk!NDT`L_7jk92CQ_S"N=J<M?F2(?UY9"KnTF<Ph-*;p:q3CqA3V!8N37dVXMk],rA4$#2/'Xrp*UA'>NS4N6:onrg^aQtK[.Zk^\6@j747$Q'jjkDU_05f#pBB;[Fl!e+Q=.V`)2E!X?dc3b7)E,lG?;pYLlB.Ha&I5nnpI,Ps`3[`u&(__e'RTb@Y9@Lb>6SULPE9MqE3G`Zs4fL_k3^t:uV\nYa_(l_V9IW`dY:sqaqD*"PIg)D.Ri3m8,>R2$S4+#b'bFhs<(@-[9ubln^T<sTG8B3"3OcqBa#j:?KV01GeQb[dWD$\B;e:sm`#P-&1.2`UJk//0Fb9`#7k@OnV\g$db8J0],nr`]3cq=,cgP&J//8rdDf0hAVnML\G-LX)kEqE-jnrkFT_PmCYd#b<MVQBd7^l2f1#aF<'5MSnro2$IkH^Tqc"'34]er"c@A"?eAM)U]ISr!%#:'rB%Fu9p*+r.^Une_/=*U@2CIAL_"Kg;cE:^s$4L"0t7'42,O"f1)2_a#MB@2AL:l6*W<_G#pBCl.NUX5+/_(uof:=nP.UPYJH_U?Qo"3>dhr"K`2W@[)\O;59EY(dr5OPlelFUOTUb/2b[MI$DR4El;D?=iZKjd;+YaiBi;4);kWYRL\6'+/jZgD!b9f0iVGCSmTN8BROgY[3CC:$(Hic@2%Bh*+@ZYT'BiS#pj-#4KLdCZrS[jcc.7J&L"[\([qpN_0,J:HOJ1:KAPV4PPrNK7fiX1dEMB&#AriEW[UZSkDAk;Y=f"Ulq_[b'PIQkrm(G;@?`9^>3br_\C[m*=D+LdBXCYk?(,^8i%\7FjWWRV2'tVE_6YP0\Jh`BMqi,O'+0KO&n#bROTqh9LsM0ehRhs>Cs,=ke5.+W`XLS'';qO5>3fu*MaOdTBGs&rhX*"VORO$h;NedrhbWC`F##5h/a(0WDfMo?f`J!o6HoEZJeS/?JlW\`jugu?F+fAGQ1'RJOPu)a=58iA!T$q;bji8LOU-ADN^m*$h2H.BWgf!%XW<>D3AZ-S7T?/)7:Mc4\Ff_okN!2Y/]OpUf@('K^iMcCH:L$9Kd9VHhPs!UIFbcgpsZe`Tsk&R#a^U!d`X1Ag0Z0`p!S?;:3nde))k#BTg.(*+XF@s"ssD?TbtTB;Lb/;k1-XP!R@.2dF$JLQ@u1RcLpU8sulM8(;6b<5^3MMt*7rXS)&$LY]e7YMFV4L3^)B`5D+s#>>F"3DU:3$b.Ma)4&d"$T>$2$0j,3U_J,q5X_WLWZ@1AU&uYg)ql8Ldn_AcE?qA?L!,^^SDLXNF/J/Xa9O>Z[+Q_R0bZ9;W(t!_;34ub#)b5!,&,R$47:gJaTLZi%_:`60P.dq!A+G48^JpM#n&@U5%ZW>`tfX0b]nG4(aqNb14s-I%N@\\LE+biB<i%,bBP=e_B3NQp%Og1>ImP&HFDJoL)i1A<ig'YUG3K4lK?[4ZW\opXC6#/-t_C*,"$*-9i7Sfhj@j&7*h_\hk)eVHKO(._QEARBO0hS)q\1&KE8G"(\I(_3SDoclB/5jh]l1cq#R>aeb<g@)f2oTT5Jtn'PYj-O`p%KHjJI&3fa(g\V82.-QLaI6IuY_%r+Z.S2ZaX3j[AJ0O2Aff/8:hl<.tbK&'>DO"G][9=Tf+ZJ[]o6-$>)O'&iMMok8_VL5'6RVs]TP),\V$.V+q);+f!X*%oM-5-0SBhkFK@'&*4SF706Qk!5U8Fu6obAbX"o4=mP$G&[4%(5"^.4]9tWTj7<rEN3A#D(>I?96oXmFYfhM*;WR(N5<b%qZ43AP8'1D^eplq#QNT#O(fX'E~>endstream
endobj endobj
28 0 obj 28 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1906 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1976
>> >>
stream stream
Gb"/'968iG&AIa;CbHeVWF,]/8^O$mN:V#K2GFid8M@eS,UCb]$&+'!8Uc((+@X^3g!2Q@e^Bfr*oF[qliL]_n\a/^ZikV6L&E5#N5K`I9Fq6RGOT50IIRRRGpEG6^8-5%J],;$K6lKV&;2<fH-AP%Lr`,;OGl^J9Am<4MHC"hK)'JK!Js,]8HD5b+I(imof`[B8'unk@2N?WhruMjH8,^fm82($MdSMsF[l&&^*&XD;#o&Xp>5iJq&%uRKkf&4ecgFV[7Zbi:-"Bnn9B!+1jNS1#-ZRf.eL;OQ$ES_1RLA8Z:=W+QNV/^0&s]F%58mnd25#b@6Z#q]Lm_%&^X$il4ti0SYd!a`:mu!_:\!lcUZ;P?\BVVdc1@BRseZ&nVC!kjJ\-_DO)Cr$Mh1ZfZm(%^(2/V11m6VXr-?@!VG#.CZk<sCcbNoE8*!-L8/Q%Ucf:VV5,.QZmbE6Y@P,P2e-BDl7G.h.f3pi^OKe9j4G:`g$rPd1I_\;N;LC%f9_&10pABB&D"Ggl>qV#3rGB7lhH'qJAk(79DC1+Ue6uXY-h-+a`V_B&&O_-4Ks/Cg@&U%UQ[7?gT\'@YZ@I2InWaO8Q'3*8h.n^VJ;qW=DsWS4t7P'1<D$JT=(i(;V;8>;K.a7E2FaN$rjbmjH>ZbX,3s@RQEZV:$Xu>/kgU>Rf[e/?K'`k$Ii"XiV2-?h99b:UYjr0[K\Uf)r?=MpC6Riflk%3l#.SilW1Vdo<H'`m@Frtb7'$!#DM_:XYhrXR(QTZ0_l/]-7RP1bVH-ECUZp&&3bu&r#Q&[-!c:A5mGe-0'ILKQ';X,M(u$5%_tZj5OuDiH^:cqN"h=^Ujin"`V18GC8oW]Dq7rA#5?:fa1@Spegq00E*kN'0ecOINP20gZ"P\3fZbMWDK-3!q%'aB0o=R?OP3hH(l\Z3RVq[3DpK'0;$EgkG]aE2\mEVhIBg[?O5cD^*_pu[T=i`OB`gqX`kh#Bo`PI@bS'.pe*!`,!n[)@G[#1u\9t[Er+8.5#i(u-76nU@'*mK+K^G!mdB-%#nKVFdf]Zm00QQW'<FTBZ75[I3+sZtg\h1)QZr_!Ri&jfn_oXO&b,;RjOS,:3oARV*Me$q_#cUo_Fr)cGNOJ!$\N%`+o]`FrA3J#F,@.3^@KSQHj$Lut]iljbB=k6l6K>f'[[0Rs_C08_Y(mCt^bg#(l4jLTfbe!A-HF;*8'qN9pV(@C3gj#;G5CS)#tERjSis^cIogp4]1W!Rmje>];&WAK?,Fp!Q8Hcl@BQh2A&1@LA2$r,.`#S2L5WrTQPgiB,%rMI;&D`a%#/Cf(e59kT&d"GV'i"9I>-&B'qUN:/f_J!pC&4=8B5_2gs/L)^5r(E@3Q1%e@&/GocZaa*B4^]4/pi)mm3o2LZM,Rj_1"k@!%KAkop?Okb)_#I^!EthFJG)@M_ab'3eoYF2j>p$lL/7fEIHNJE'=3kj2<QMrW'#1]$c$+=mq+?GVgi^su\!"(/2?VN#2idX7XeePDO[`XSTGO+tWZ"n`K"eY[4\,JCO9Zh>%$!`E<i3@:dCh8#igH35_<cjp6lhSKVQD/YAPkUtPk<b6E:j9?r-j9YM^At-HSG,'dH&OK+X:]j,aXKYS:n4d'?eTn;M+B.SmIt\4;"_)sBH&"?144Oca@^TbW3(2[ho!<b>OqLqtF+>=Qe0N6#F&]Qgn_D_&g^Pe9')GJ;Ij;m[F,o,3-rh6HQf8N[i1,P:5iRAt.oYWr%Suj`M1!U\QQuk7QTGE0\H#Fe=)MS./EaO^m-D8AD[h`n1WQ;4A)<.PdiDEQSrVZl^n6=gD:3`'YMi/LP4b964&H-MGGZb3KA6%ji'ZH[5KiPlT&jZq?_OuHG3T=V$/HP(eTUO)+NhX#;!-<J?pRgn2DKN0O/l=lnGWM3oD-i~>endstream Gb!#\D/\/e&H;*)EQG%/M]#C\b.]\A\opG&FH+[W=9k(VcF+?hF=A]Bqs/HjAD)+,3_>+0E>$=Qn_SB^4NDD[kf)lFkoAXG#gRn\^g.dOJG[QGd7\^B)LNOo<\aq`R(*HXcuW;/3'Nn.D<?Rjj9NK5f^TRj=Q8!ed*k9hcaaP\0kYKr`%Z:]cj5gK;JKR@9G6b,@7_$MTa/3um@hTaW7R(PTpOiSVROj7$f%`QjL/3ceTI<Ig8!`RiHQWF5JcP%7OL8AXrjsfpQJACSM2`b".;K^Fi:9@f1%bGrAJ.l.sdAXZoha''u`rQFb+'ZXl`_&@<1.Q15$cbZnHEH5^=k/o32?\*gS3r3pY6fAQ%ibk2q3N<g!SoQBt)*'qE*BMKuDO8Y]I!Wob"&L=lS@c%4&1QV'VJ9rFp%[=QXP-WC6^Dae(G``O-FlA@09[RkqGTBcO<EU8#$%;lmW*X<HXD7L7kGs5U:/D&uIhp`KkP&N;RZ[Qc&/TfV#jp%WGF5(R/"e;M)pp_=^21Q-mf<)\7>3<9NH:g&`0@&lKlK5;EO("n8a:QKn/J)eFjopCHL_=NpjJjUO0AsM[E;NR!3hu8Z79rMZB?$g-%]d`'.WYqcNqMNOm1.lC>#2dsP\c&kG_7?TmnHaI6s@Xpf1cn4R+Dl?9dR'WLLi^69;L+IaX#c1a]CgdA856U])o6?C3/^CF3E"cV))so3kLUO/4U8G!0fDJ$a&P!"&#Jm18AfG\+D0Q+hMEJb>i6fkJOq\NNQ/oiqqfhrJ$J\Wt[]kPhCWGVHBK,P0L>?'jRe$5!SD.(Bl@IP/+ZM\AOMlR]1XPW5^%TJDcm,!Nd_'+8A^_FB,ds#Cc$lK:A^?og7g9::.\IY@Z-J\&LQ\8Dgl?esapQ(HRjh)WfcK)PPH/DM;&,50*b;;&jLk3u_bV``>^i+)bV-2SQW_2plI@9F%o?*Nns:g*"\T9,n72>j`59nVB2fdm2i138>%0S[:bD&dAehAQYfj?W^PmLC6+2!f=L3Y;%oP<#)kX1pDb#eg_>oJk4ljV3"?T.PDPF\N0tql5kmkT;@>&"b+W+#FuA=k5,;0<k;WfLmV2q:ur6XWs)h[@03i>Sf3usm!r;n-D;6pCCMJSbIkpS<`s`'/"%2PGjc8(9Te'+o'4B_gd.r'C-@VHT'NQP?D/]f"X:L2.O`alN)t\I'2_?m$QN.m67R'13$R":f$[G%%>DsqT'9d&aJE'a_^K7/rC-uJWLjiq5<0s"\N'4Tg\9k.F)+toX.c4lIYl)Ag7KG%])D#Tg#:7`fO"_oK3QBG?+bIi'<MTtS-q]s");7;YTJ&"g"&l\/\AUsfZr!U3?Nki*up&_#]&2dpH4=ZSb+lL35KcNWunjbIDC9/Xj/93,^>)tlCnuY8g]k@[^`9f[2<03qo-=Yot^@#;N8k@q"s:9jQqO;U1n5^UORULDjmLsK3A7T;ED-I]5I\^PE#$uN(<!h$e.@LN#h:]_c#"5S7V?KgI^F$n(d1tI(P$GBo[PrYR.!C:#MX;j4]9HkKj,E?."&iiUAfm%/G-1H.L3Mo:6362TI]:"rD$QQ(gZDZ=kn^l25j:mX[9DCWLc4g'$C//$P#8#:44TQN?)+Ol!n"2<Uo`4l5%'p%^,eq#>uB%ufn-m^0NuMQA9:KG%dZU6sA-[n%s,[S6UO@H@S9I%_+I\e.7qVM1ZDifpU"KE8TECA!3fhZIA8X`B"*>s_sIU$ZLE&+.%aa1iHj*f.ZRK&$H7Hg.m:&#*43\\t)VV%aY1nKeX9ciDMcgq9T*['Ib!_9WfAIR^oL[s0dP-(`PtL3.pp8/FfCbBmJ(\XWM0_#9MOJ#ZL;?n%uGFP;gl\LLLG93%85_]L7O4EQXMLc/si%Yl?r,sb@UbI0%I#dt+6m[!,c-qpK)^TI-f3NXYN>/d$0[[DlT(3H;*&.>0@AQ=]XOjg+7+7FY5m.A.VV/I\Y/U%L)#6+`iJhF,~>endstream
endobj endobj
29 0 obj 29 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2014 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2055
>> >>
stream stream
Gb"/(968iG&AII3ChGdB-&j#KI"@&b`_N#AD8pQ9-1/N))2a(rfHd/@P[%SlgUdS;MN[Y:26K]6SVVk5]DLtJ53sa5/Gu4(h^ARd*Pq4c"j14p%FcO2N'>+^UYb_d@35"\(Pp<f4((;T]D;Ujl/giL`!][4JQk=s^&nkCK_']r_gT6pIF&%u?hGfYhm7^u!9)H#\G'CG9g$GBZpS*E$eG&2rpe.EJ9[&tk[sdOb#![JTN\J+6S.O!/rVRLT7?]`"s6egMJ/_`$=?CWU.GYmN;7F52E3,m=d1+b?c63FC7:!lHjQPe(qlqcFn@aR-]#`Kec/J\5]+<'U/]aPg5@YIGmKXX'`ESK?"ie2j/Hs:@b[30ak.L$SrJlk[c9@hA3K(<qQX=!Hd!k'jm^P6j,K9jW:."ZMS"t,HL2V7:ek`$Wa,s./o[N7D:;[cF^VHcTnA9dZq'Xb])9oi'j7Z(W+R]q8F]F?A@&h#d6SiH-9Y:,ePDb5iOc+79_E^b2tbS?R>9/u(uptk$oE1=aVhKIM'96doD37CJ7f8nKKV0M0]LL\.sJtJKi[^p.]!u\\^5O%b97Jn^2!/IeQ0T^4atfP,Wh:1:)lqAqJTA2(+TU$-JP<T\k^l<Y!\9IR'f'@"6QtG]ZEYi'6p!785L=U`3AshD"?7c]ad7l06;=%?)6(-ESHR[X@CBd\,TUp\W"I=@L+BuE7o[jrD!_$R!lYNT%T&>P$?$-)<SI@)TJK[e!2u!ba*sdBPP/[S/P[?KoSR4a3dBMK,Tg>iMrO6Y`o;bKeAV4;gEG2ol?,ZNJ=nlbKumr+pDMl2q5.iT>ES3j#\-FPKc^]J:+^#]duCl`c-MW7HMK0T:8!fZ7O(-50Q@#K$I/)j&AK%6bbF67)%:G7a\3JJOHOO1F9nSMfn)&]*:=6fPJZBlXD*N)V1sUY6"G!huCd!^!8Jb:;c92SXUe>'4d-/J=e><5Ag!IW+ecX?J-qo\N&@(BJ'3`V5]8^!k"eM4KJ5;A55bF&ZnUc2uJ#TKZ,sXOZaOfppRa03V!J]^@)XMmM>GV/^G]V9B!59VN$8m1@Nm%n["$`>*ep[3RMhka6NL->[r]RIZ>??dc_H:CRq@"l<dEGhs)KDW@l./ZssL/h_!"8WcBX"\/"`AS:CBW2?K:6PY/$\W`k+tiV_<B7c,i4S:3SIqe`01=5DeBoAa7bg3OB4,4[`c<>;;F:6N[r-FpuTi2T"C8r)"09gj(RnfN$!A_-C5/o3[M=K>S6m)/u%Q`:IVa%TsM848p*Gu!X2e3H_/gX1r&(Br%#*$13b0(_X:Io)Hsq[P=3;QIIqhP#bT'H8Z$)8ouJS74:/*088V+lfsgXR]mPs#BB@)R<!]gG&n]5QB@d4kUMDa0D6]=d3`FdBT.VB5-Ul>fC,+)66TS0R/4[f/-M%ftK4h(23%Hd+EorPa7/V4/%d5@Y'57_5[LL^jA.!D2*Y]>bLX(]=@MoYhG/RC*1AP^SbM=?c,,';'@-;B_.[-bRL;cGUClVEeU('j,]Xgo91%#ruZoT3='iDU%g8FiD\guI-]kJ;BD\6,5Y/F4R+#n%;Cebn[tBM^IrTG^pPSc8Tc\S_ij'F9gP"=L9ZVn@5sefc,i8._7c<%!Nt?g'38p(N5ICj//7HD%dG(>>A9';(m$..3$->q?'0)dfXeH0SeqbRfSoBM27D%2<JPGn10(e-S4XEn>pu+^7$J'h:IJ@3c;:@FL/pI"aK-SuC_o;$Lbi%HlnEetBD,&-LZ;EO&14Gb2Qrn=7q=Q-(qD5%\i.$=ZXAkqM`];]8#.X70W&U39sp?]ES!$ifeU$P@##bR@C'YZ5H;PBO2tAi@:ZcSd[GqpL>u6L#tIL-d#.7+I#gg1p0h47qNYXTKI='qbf0k!aG8`,Y;@?Xe46.4=7fAh=KH!Qk0X;B!=gggelL:(9N<6/*/-d8`6$q@-8Sr`hmEeg$pnL<9]uU$AWGBRn\&tWNu*2UrI1,fH1cZ#ae5mgi`=*cR0%^7!pZS^ir~>endstream GauHLa`?,q&A@B[\o^IlYsf>amaF/MPcI\\ac%UXkQ6m."Ht#i+D<.uqIrd]Zpp3!>F&h45i_N)Y9764Mc>2D-,A7s\GAaH:q2tqYmhZ?5/E#d_1)PbcZEF0%;8#B%8=Z`9H2UBYZVFYq4NjX<YFWIH66s=[D\^jokXjAb;ZPRJe;,]V\VU&A;FZN12.u>_DEIVCUh!3]/Npk(OU@0fVW'ia)cPg_<Q6*BX08e/MFtp'_aDNEMc@MU"\esUV/j0663NVfoQ2R,%;C$3DVr:0P+ZTQW]_G_nD^kT[1I_4b=Jr*\THnSgo!G10.R/0(:qr,tl.p-/=E*QmP@-27@%A66oMW%qtkl$<p"Kr"ORG4GadM']B*hPqdQq0hD9dp`i192EQ=AbOaf8Pu(^@`NLI_KM4j>)LpGegng!sHF2k3gIeZXkt(2="J]ld9f\Ql)ph(DOC]&F-<ZLNr,)A-0]9VY*3pHT`Y+XcYQ2Z**%`B>;*^Sl-"G'r1[C.WFq;,L<]75Y\_fmCMFG9hnLi#$""!N4L7Y(RO3<-u@P<g$d+Nu"?D&Ohj3bn8\ot+f:\`a$]"$RE]^N.;SfpJb!9aPsU;V:'n>sMY<W'I\aX@l`U.kX*J/7cko(mmC'D'3&>J&F72QU2#r%XL"ba(nn7QfC6"eB#:]2BD8c;1%Ud'",^^lT"mnj5'9PZH9K8ioIfJ.)Cr,+j@V?$oCL$5[ZVK+I$V"K5\A`Q3^1%pCJ3FPrcn%aj`E#Kn)h)"JYVf0M=MB48\AJdqH:]3r`*qb^='>f2LH"lH%@QC<?HEO&i/OU:ZD#7Q*6mgh/!<=:f7e3YN$_G.#8nM6$226IG(G)$pB<0\8=d$'[9Ae\bFDm+B?'C),EK&]''0iJl1d)kD;0WDDT33Q9Y_o-RIrKakQs6eme421JM;>Is^`H)e0]?boAj]%NiShdC-F,?k_NJgcs3QHNNcQ8_7o5Iq6]nM?'m=]&_NX!+BJn)8'Ni1-=C%F30g<^&mgT"i_R)sT=A<4L.8Sm\c0g)E^q5PAB*EKF:\Eq2Z)V)%-=O]/If*sPRK:!p'$$;di=qF:eYe`5rK?jX%%W'HIHp@,4B?@s_9tdcXAG]mpaYccHo_MA]Akk=!T*</PW9:[UW90LZGa@g/cj1/PH;]ZoI=e3)0?J_]T'uE.btaYZm1j(Zk'&kR*S,ObVbQo(3*6T$DidWjC5OtYK'HUFT<50(hXLb=5RDS<h)0$'pSg>L/BOR-OO[-2d]Z7,fCU"$N1-sg/ss6$@C'i1UXgbFiB]4WLE(O&=i(YRNb:>[AW[<92;i?%O.D,C/eios@@WSi`mY.:eT\+gRS]&[@p]ID(9a!M4.t8eiEDg[+gV__.?`n"``AAG!_WtsaX(ns>>(!E7\kl33N,Hnio2X79:HNi.n$h>9AH@?([#kYU'M0PokHp#,n7je(i%p1iVThsb0+76(`GiPTs!QYck+Re?`n'+o`>BcYtdStl11+_&?3Un$sWTiR>q7FqpQkbDOB'`JaP`_?gm*#KHqJ_2C(FC5(6TALV:^<5A<GIWufqrY4L1AUnp+KH?Qe=^P@.F*:`p*P3bf+[qV4oL#=bs\O@T8@spdT^c-'G[&6#oQ^RD4<UHtBQ6RBbREI/19lc'_'%@LY2;,C+XQN$Sk"V-_Kg.n?_r=@imQa7/_]lLVX,lJMWF\9JM;;RI43j6\>j2iK20&K4E^F-8KE'CnV[;n+]c\oN15,Fuh.qN5[*p<"jR(J7H<%%&HmGr,`424&l\5!dl-$B71/$@5b*\.M>#amQQV`*o=d%u1`In,tjhDd.h-MI7YW;*id<b+\8(,Lmq!IeHYl7fGe<ZOVVerop8_R2[6PTS6rY&)tjK$'r%Qo$)N[>>kkA%F#am7?FbBYYlFNP-OC=!bZK(CVggO&_XC(fiN%&59LbgHc,EE%go\uKS8Ra]rYZM>Id@0)X[-aZ+V[LI;#4gB@hoUGBK2F"o.(:'kIQ,qBR\X/+]J#h:4n?KiRBb6fRrE!g$F[E_'TL0R)4YWu*dtat*3%H*$o`bU;r!ttm6D+~>endstream
endobj
30 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1939
>>
stream
Gb!;dD0)19&H;*)Z(G2I'j/+>K(P8o3j(Q9N+4AP5BnMrJe^<Q3-p*ckLXngcmqWol&;'7NpXWrh/g4*m%"/_qA@1I0*8X%_Spj($knA*0PoR"iB>s)ci-*a.`l"(n%5GiIos/IoXZsFd;.S)Rfg8-]&e#cf*'p;e/-3a/1*O/k_&7>]&U:1BVl4O@JlFMW_XpNOBR9qme6KPhh'\A9<GpLgp1dHO(GYEe?[i=X<WH)NU,B*CH!m2Lg@?,Pq0JchO7hoMnM!KSY`7-XmLboV3_da?Vf%<]n,,VL2V9h9Y7L?[dC5.s("f\BLB:681ko$i9p>P$jMEF#:]T+b;a7/*q*NgEgFOH#RN$j&Z!K(9TOk51j']5-rZL)4q03#!(Sg-1\JiWg<DN(Nh06!ho'T'1QkHg`^j7*&'sE-&/`a('QJeGQ9N&D(//;C$HA8\SPV#,%sJ6p6\1VBTr=/:4VSqFMbl1e1$AYaGRt7>a1PJC.dnP+B&n>oY:581DL(r&52$r@[oXb>RMk2tZ7M?@N?jFq$2ai8Ns)FXi#IZKZ?(^-5#&kk,pX,d*#I<g'T7!mpBBLm,m[g0;^!f)*b'F]]8;])HK9Fg;Z\5[GolfPii#ON:b3/6\1Xl"'YtIQSG&.V$8KT7iUj.o]"RnnHm8SBh"M"1pQp*^,;A@VG_TOjkOP9eX3@B?S;0;(+_*eB?\Pcj$RTA@j&fkp>Ic`1J9$J^+&OR'^a/%lbBt;s%ACum.N^,A5UhE:Zh+D6(8.s7eB3mj8e[gs8]R3t`1f#C0_?*?S'$V%rh+bi1Ym!idiq79#EEX=TF\!QR0.]H4k"b^=A_PpDL1T_+Zu<D/3[jplC1Gl10lYN(lGZn#^#b=J/\&D!Sak\!XDkH",/@98BWZTYG7P[=jZHEX2AS7C*uCH\qS"EpaaLpSGn97h*/I"f[os0%$%[d<kS,UhU6!RmkaXh]g5hVhD5GlQ*7d8d`gFHDH>XM+Nc.6dMH5Xcg)=:_m;r3?9P]^:R,J@+`sn3TUJd/^X<Uf'-T.bcfi\FePERE29sjthXri2qrp@MqJ$<TV2pC?$de$iK#6h_^J_mlfCsJ-M+&uh`QnZ*<n,bFD\;#LH2otK3?<-5?S.E;J1m=nM!:D&Mb.EIl9Y[[TY!l1GZ5)).H=5;gdEnp,%<DfF:.T:NctIaZ[4asQG-Nh\)tL`61<RD,H;jd,%&1<XJ'YdO*UH!d<m+&4E6%5=EXtO'4UW?:siOKk,nK;cND9E)C%Winee"V1o!MBT9ae#;6D,eB$JEm39C>`a#PV^R`$r'@]ZO\CG.DG"%P9SepcJ^Ci^olDgCf.d1e`VftgQgHR;AAoXodbEH@-,c2C?'Maf%\i(2*T!JX.(qsHGo)M?<-[\-gA4CRhc3SU0m7T3VWH#,SYThe+S`kYcY94$i+6I^j\l?X@AOonBDq8`%[9-NJd!]]Z[PcS;`YedH3[=j=8#ZOCCH)EbV6'A,ZWnKIGocfsVYT`E;0nou\+#P_nCn2Ad&TLV!`SBp`U2nIKUmTSe"_JrMBV4<?#En3%S^=BsGX/2%'WMq(El=j\f)K7)VN(CJX^>p\A"TV>?L,k,RZs86Ous.+R."D'g;$ln#XqQ/1[7l,VKUmI1WMBTI'^Ol;*u[C,FZt0G*@=NnthA^LRZ&5?QMC:h;;LfWRW2hjHq#-oDa_W[:T<%B;9\/9j#tt,\1m"7d<=56Z#F)&:csB2oS3W#?CpF8MP-!d*rs1:l\pJf]_!$M"g"N%MhCVe2>[d!fU\WfK54_^&Cbf<,\G@c>'OagqpF!/(gW1<nU/rW;3paILS^I),J]O1:K=(-XqX)bNX7Ncc3X#WmS.3k-;An>D/7=5(`&L,EXX8]+tYc:<0]]b?K9La-]1F)@k3=LYpt+a!/%bT.\[ho4p'.@5L;X?!4$#:?6Bf&$lEG"9~>endstream
endobj endobj
xref xref
0 30 0 31
0000000000 65535 f 0000000000 65535 f
0000000061 00000 n 0000000061 00000 n
0000000112 00000 n 0000000143 00000 n
0000000219 00000 n 0000000250 00000 n
0000000331 00000 n 0000000362 00000 n
0000000536 00000 n 0000000467 00000 n
0000000641 00000 n 0000000544 00000 n
0000000846 00000 n 0000000749 00000 n
0000001051 00000 n 0000000832 00000 n
0000001256 00000 n 0000001037 00000 n
0000001461 00000 n 0000001242 00000 n
0000001667 00000 n 0000001448 00000 n
0000001873 00000 n 0000001654 00000 n
0000002079 00000 n 0000001770 00000 n
0000002285 00000 n 0000001976 00000 n
0000002491 00000 n 0000002182 00000 n
0000002697 00000 n 0000002388 00000 n
0000002767 00000 n 0000002594 00000 n
0000003059 00000 n 0000002800 00000 n
0000003189 00000 n 0000002870 00000 n
0000005192 00000 n 0000003162 00000 n
0000007290 00000 n 0000003284 00000 n
0000009467 00000 n 0000005337 00000 n
0000010262 00000 n 0000007296 00000 n
0000011873 00000 n 0000009114 00000 n
0000013917 00000 n 0000010815 00000 n
0000015568 00000 n 0000012753 00000 n
0000017531 00000 n 0000014977 00000 n
0000019347 00000 n 0000017133 00000 n
0000021345 00000 n 0000019201 00000 n
0000021348 00000 n
trailer trailer
<< <<
/ID /ID
[<1e77dbe9d9f83762252045861df07f97><1e77dbe9d9f83762252045861df07f97>] [<9e52eef3fab94e356c048904983d520a><9e52eef3fab94e356c048904983d520a>]
% ReportLab generated PDF document -- digest (opensource) % ReportLab generated PDF document -- digest (opensource)
/Info 17 0 R /Info 19 0 R
/Root 16 0 R /Root 18 0 R
/Size 30 /Size 31
>> >>
startxref startxref
23451 23379
%%EOF %%EOF

View File

@@ -1,436 +1,563 @@
# Les 13Externe APIs + Cursor agents + Vercel deploy # Les 14Cursor + Vercel: van leeg project naar preview
## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven) ## Slide Overzicht (Klas A — online, demo-driven)
**Lesvorm:** Tim demonstreert klassikaal. Studenten kijken. Zelf bouwen = huiswerk. **Lesvorm:** online via Teams. Tim deelt scherm met slides + Cursor + browser.
**Demo-app:** Nieuwe kleine Pokédex (PokéAPI, geen key, visueel) **Demo:** een vers `npx`-gescaffold Next.js project, gebouwd live in Cursor.
**Vervolg op:** Les 12 — Tool Calling **Aansluit op:** Les 13 (Polderfest-agent op localhost) → Les 15 (RAG).
**Aansluit op:** Les 14 — Agents
---
## Blok-structuur
| Blok | Onderwerp | Slides |
|------|-----------|--------|
| 1 | Welkom + Vandaag | 12 |
| 2 | Scaffold + Git + Vercel koppelen | 37 |
| 3 | Vercel CLI + env vars | 810 |
| 4 | Cursor — Editor / Agent / Background + rules | 1119 |
| 5 | Live feature-workflow (plan → branch → preview) | 2024 |
| 6 | Recap + Nu jullie | 2527 |
--- ---
## Slide 1: Title ## Slide 1: Title
### Les 13 Externe APIs + Cursor + Vercel ### Les 14 — Cursor + Vercel
**Visual:** **Visual:**
- Background: CREAM - Background: CREAM
- "Les 13" in BLUE - "Les 14" in BLUE
- "Externe APIs + Cursor + Vercel" in BLACK - "Cursor + Vercel" in BLACK
- Subtitle: "Van localhost naar productie — met Cursor agents en preview deploys" - Subtitle: "Van leeg project naar preview-deploys — met AI"
- Iconen onderaan: Cursor-logo, Vercel-driehoek, GitHub-octocat
--- ---
## Slide 2: Terugblik ## Slide 2: Vandaag
### Waar staan we? ### Wat we live gaan bouwen
**Vorige lessen:** **Vandaag in één lopend voorbeeld:**
- Les 11: AI SDK basics 1. `npx create-next-app` — vers project
- Les 12: Tool Calling 2. Git init + push naar GitHub
- Les 13: Agents 3. Vercel project koppelen → eerste deploy
4. Vercel CLI: link, env pull/add
5. Cursor open: Plan, Build, Context, Rules, Background agents
6. Feature in nieuwe branch → preview-URL
7. Tweede feature via een Background agent
**Tot nu toe:** alles draaide op `localhost:3000`. Geen wereld eromheen. **Werkwijze:** ik bouw klassikaal, jullie kijken en knopen mee. Na pauze doen jullie het zelf.
**Vandaag:** **Visual:** workflow-pijl van leeg project → Cursor-features → preview-URL.
- Externe APIs aanroepen (geen LLM nodig, gewoon data ophalen)
- App naar productie met Vercel
- Feature branches met Cursor agents (Composer + Background)
- GitHub Actions CI (lint + build per PR)
**Visual:** localhost icoon → cloud icoon met branches eruit.
--- ---
## Slide 3: Planning ## Slide 3: Scaffold met `npx`
### Vandaag — 180 minuten ### Vers Next.js project in 30 seconden
| Onderwerp | Duur | ```bash
|-----------|------| npx create-next-app@latest mijn-portfolio
| Welkom + Terugblik | 10 min | # kies: TypeScript, Tailwind, App Router, src/ dir
| Theorie: Externe APIs in Next.js | 15 min | cd mijn-portfolio
| Theorie: Cursor agents — Composer vs Background | 15 min | npm run dev
| Theorie: Vercel preview deploys + GH Actions | 15 min |
| **Live Demo 1** — Pokédex bouwen met Composer | 30 min |
| **Live Demo 2** — Deploy naar Vercel productie | 15 min |
| **Pauze** | 15 min |
| **Live Demo 3** — Feature branch met Background Agent | 25 min |
| **Live Demo 4** — GitHub Actions CI | 15 min |
| Composer vs Background — wanneer welke? | 10 min |
| Lesopdracht + Huiswerk | 10 min |
| Vragen + Afsluiting | 5 min |
**Format:** Demo-driven, Cursor en browser side-by-side.
---
## Slide 4: Wat is een externe API?
### Data van buiten, in jouw app
**Het idee:**
- Andere mensen / bedrijven hebben data of functionaliteit
- Ze stellen die beschikbaar via HTTP endpoints (REST)
- Jij doet een `fetch()` en krijgt JSON terug
**Voorbeelden:**
- PokéAPI — Pokemon data (geen key)
- Open-Meteo — weer (geen key)
- GitHub API — repos, users, issues
- Tavily — web search (key nodig)
- Stripe — betalingen (key + secret)
**API keys** horen in environment variables — nooit in je code committen.
**Visual:** Diagram: Next.js → fetch → externe API → JSON terug.
---
## Slide 5: Drie manieren om een API te fetchen in Next.js
### Static, Server (dynamic), Client — wanneer welke?
| | Wanneer gerenderd | Data versheid | Externe API key veilig? | Wanneer kiezen |
|---|---|---|---|---|
| **Static** (build-time) | Tijdens `next build` | Bij elke build | Ja, server | Data verandert zelden (blog, marketing, lijst) |
| **Server / Dynamic** (request-time) | Bij elke request | Altijd vers | Ja, server | Data per user/request anders, of telkens vers nodig |
| **ISR** (revalidate) | Eerste keer + na X seconden | Periodiek vers | Ja, server | Sweet spot: snel + redelijk vers (Pokédex!) |
| **Client** (`useEffect`) | In de browser na hydration | Live | **Nee** — sleutel staat in browser | User-interactie, live updates, geen geheim |
**Code-verschil = één optie in `fetch`:**
```typescript
// 1. STATIC — gecached forever (default in Next.js!)
const res = await fetch(URL);
// 2. ISR — cache met revalidate
const res = await fetch(URL, { next: { revalidate: 3600 } });
// 3. DYNAMIC — altijd vers
const res = await fetch(URL, { cache: "no-store" });
``` ```
**Vandaag gebruiken we:** Static voor de Pokédex-lijst, ISR voor detail-pages. **Wat je krijgt:**
- Werkende Next.js 16 app op `localhost:3000`
- TypeScript + Tailwind preconfigured
- ESLint klaar
- `.gitignore` met `.env.local`, `node_modules`, `.next`
**Visual:** terminal-screenshot van create-next-app vragen.
--- ---
## Slide 6: Cursor — twee soorten agents ## Slide 4: Git init + push naar GitHub
### Composer vs Background ### Code online krijgen
| | Composer | Background Agent |
|---|----------|------------------|
| Waar draait | Lokaal in je editor | Cursor cloud |
| Mode | Synchroon — jij wacht | Async — runt zonder jou |
| Output | Diffs in editor | Branch + PR op GitHub |
| Goed voor | Pair programming, snelle changes | Welomschreven feature, tijdens andere taak |
| Aanroep | `Cmd+I` (Mac) / `Ctrl+I` | `Cmd+Shift+P` → Background Agent |
| Repo nodig | Lokaal | Verbonden met Cursor cloud |
**Beide kanten:**
- Composer: jij stuurt, snel feedback, niet schaalbaar voor lange taken
- Background: schaalbaar (5 agents tegelijk), maar minder controle in flight
**Visual:** Twee tracks naast elkaar — synchroon (jij + composer) vs asynchroon (background agent + jij elders).
---
## Slide 7: Vercel — productie, preview, environment variables
### Wat verandert er per omgeving?
**Drie omgevingen, drie scopes:**
| Scope | Wordt gedeployed bij | URL | Wie ziet het |
|-------|---------------------|-----|--------------|
| **Production** | Push naar `main` | `je-app.vercel.app` | Iedereen |
| **Preview** | Push naar elke andere branch | `je-app-git-{branch}-{user}.vercel.app` | Iedereen met link (delen via PR) |
| **Development** | Lokaal `pnpm dev` | `localhost:3000` | Alleen jij |
**Environment variables per scope:**
In Vercel → Project → Settings → Environment Variables zet je per var **drie keer** een waarde:
| Var | Production | Preview | Development |
|-----|------------|---------|-------------|
| `DATABASE_URL` | prod database | staging database | lokale database |
| `OPENAI_API_KEY` | echte key | test-key (lage limit) | jouw persoonlijke key |
| `STRIPE_KEY` | `sk_live_...` | `sk_test_...` | `sk_test_...` |
| `NEXT_PUBLIC_APP_URL` | je-app.com | preview-url | localhost:3000 |
**Waarom is dit belangrijk?**
- Preview branches mogen NIET stagecoach naar je productie-database schrijven
- Test API-keys voorkomen kosten/quota-problemen bij experiments
- Eén branch = één geïsoleerde wereld om in te testen
**Cursor Background Agent + Vercel preview = killer combo:**
Agent opent PR → preview URL automatisch in PR-comment → reviewer test live in browser, geen lokale setup nodig.
---
## Slide 8: GitHub Actions CI
### Lint + build per PR
**Waarom:**
- Voor merge weet je: code lint clean én build slaagt
- Voorkomt "werkte op mijn laptop"
- Background Agent doet PR → CI draait automatisch → groene check of feedback
**Minimaal `.github/workflows/ci.yml`:**
```yaml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with: { version: 9 }
- uses: actions/setup-node@v4
with: { node-version: 20, cache: pnpm }
- run: pnpm install
- run: pnpm lint
- run: pnpm build
```
**Tijd per CI-run:** ~1-2 min op een kleine Next.js app.
---
## Slide 9: Wat we vandaag bouwen
### Pokédex — Next.js + PokéAPI
**Doel:** Een mini-Pokédex om de hele pipeline te oefenen.
**Stack:**
- Next.js 16 + TypeScript + Tailwind + Shadcn
- PokéAPI (geen key, gratis, geen rate-limit bij normaal gebruik)
- Cursor (Composer + Background Agent)
- GitHub (repo + Actions)
- Vercel (productie + preview deploys)
**Features die we bouwen vandaag:**
| Feature | Met | Branch |
|---------|-----|--------|
| Pokédex list (eerste 151) | Composer | main |
| Detail page per Pokemon | Composer | main |
| Search met filter | Background Agent | feature/search |
| Type-filter chips | Background Agent | feature/type-filter |
**Visual:** Folder-tree + GitHub branch-diagram + Vercel URLs.
---
## Slide 10: LIVE DEMO 1 — Pokédex met Composer + rendering modes
### ~30 min
**Wat ik laat zien:**
1. `pnpm create next-app pokedex --typescript --tailwind --app`
2. Open in Cursor
3. **Composer (`Cmd+I`)**: "Bouw een lijst-pagina met de eerste 151 Pokémon van PokéAPI als statische pagina. Gebruik Tailwind cards. De fetch moet static zijn — geen revalidate."
4. Composer maakt: `app/page.tsx` met `fetch(URL)` (static) + map + cards
5. `pnpm dev` — werkt
6. **Composer**: "Voeg detail-pagina toe op /pokemon/[name] met sprite + types + stats. Gebruik ISR met revalidate van 3600 seconden."
7. Composer maakt: `app/pokemon/[name]/page.tsx` met `{ next: { revalidate: 3600 } }`
8. **Bewijs in de browser:** open detail-pagina, refresh — direct geladen (statisch). Verander URL → andere Pokémon → ook snel (ISR cache).
9. **Show de Network tab:** geen calls naar PokéAPI vanuit browser, alles server.
10. **Bonus:** maak één pagina met `cache: "no-store"` (dynamic) zodat studenten alle drie de modi gezien hebben.
**Belangrijk om uit te leggen:**
- Composer is een TAB in Cursor — niet hetzelfde als Chat
- Multi-file edits zichtbaar als diffs — je accepteert per file
- Terminal commands vragen toestemming
- De rendering-mode is één optie in `fetch()` — verder verandert je code niet
**Visual:** Cursor screenshot met Composer panel.
---
## Slide 11: LIVE DEMO 2 — Deploy naar Vercel + Environment variables
### ~20 min
**Wat ik laat zien:**
1. **GitHub:** new repo `pokedex` — public
2. **Lokaal:**
```bash ```bash
git init git init
git add . && git commit -m "Initial Pokédex" git add .
git commit -m "init"
git branch -M main git branch -M main
git remote add origin https://github.com/USER/pokedex.git
git push -u origin main gh repo create mijn-portfolio --public --source=. --push
``` ```
3. **Vercel:** Add New → Import Git Repository → kies `pokedex` → Deploy
4. Wachten 60-90s op build → Productie URL — werkt! **Of handmatig:**
5. **Environment Variables demo:** voeg een var toe in `app/page.tsx`: - github.com → New repository → naam → empty
```tsx - `git remote add origin <url>`
<p>Omgeving: {process.env.NEXT_PUBLIC_APP_ENV}</p> - `git push -u origin main`
**Resultaat:** je code staat op github.com — basis voor alles wat volgt.
**Visual:** flow-diagram local → GitHub.
---
## Slide 5: Vercel project koppelen
### Van GitHub-repo naar live URL
**Drie stappen:**
1. vercel.com/new → **Import Git Repository**
2. Kies je `mijn-portfolio` repo
3. Framework auto-detect: Next.js → **Deploy**
**Wat Vercel doet:**
- Klonet je repo
- Draait `npm install` + `npm run build`
- Deploy't naar het Edge Network
- Geeft je een URL: `mijn-portfolio-<scope>.vercel.app`
**Voortaan:** elke `git push` → automatische deploy. Geen extra werk.
**Visual:** Vercel "Import Project" scherm.
---
## Slide 6: Productie-URL + branches
### Wat krijg je gratis
**Twee URL-types:**
| Type | URL-formaat | Wanneer |
|------|-------------|---------|
| **Productie** | `mijn-portfolio-<scope>.vercel.app` | push naar `main` |
| **Preview** | `...-git-<branch>-<scope>.vercel.app` | push naar elke andere branch |
**Voordeel preview-URLs:**
- Iedereen kan klikken zonder lokale setup
- PR-bot post URL automatisch in je Pull Request
- Per push update de preview vanzelf
**Visual:** twee URL-bubbels (groen = productie, blauw = preview).
---
## Slide 7: Env vars in Vercel UI
### Geheimen veilig op productie
**Wat je vandaag NOG niet hebt** (lege app), maar binnenkort wel:
- Database URLs
- API keys (OpenAI, Stripe, etc.)
- Secret tokens
**Drie environments:**
| Environment | Wanneer actief |
|-------------|----------------|
| Production | `main` branch deploys |
| Preview | alle andere branches |
| Development | `vercel dev` lokaal |
**`NEXT_PUBLIC_` prefix** → variabele wordt in browser-bundle gestopt. Zonder prefix = server-only.
**Visual:** Vercel env-vars dashboard screenshot.
---
## Slide 8: Vercel CLI — waarom
### Sneller dan klikken in het dashboard
**Wat de CLI je geeft:**
- `vercel link` — koppel lokale folder aan Vercel project
- `vercel env pull` — productie-env-vars → `.env.local`
- `vercel env add` / `rm` — vars beheren zonder dashboard
- `vercel logs` — bekijk runtime logs
- `vercel --prod` — handmatige productie-deploy
**Installeren:**
```bash
npm i -g vercel
vercel login
``` ```
6. **Vercel → Settings → Environment Variables:**
- `NEXT_PUBLIC_APP_ENV` = `production` → vinkje bij Production
- `NEXT_PUBLIC_APP_ENV` = `preview` → vinkje bij Preview
- Lokaal: `.env.local` → `NEXT_PUBLIC_APP_ENV=development`
7. Redeploy → productie URL toont "production"
8. Maak feature branch `chore/banner`, push → preview URL toont "preview"
9. Lokaal `pnpm dev` → toont "development"
**Wat studenten moeten zien:** **Visual:** terminal-screenshot van `vercel --help`.
- Push to main = productie deploy
- Push to andere branch = preview deploy (eigen URL)
- Per omgeving andere env-waarden
- Voor secrets: vinkje **alleen** bij Production (preview-key gebruiken in preview!)
--- ---
## Slide 12: Pauze ## Slide 9: `vercel link` + `vercel env pull`
### 15 minuten ### Lokaal werken met dezelfde env vars als productie
```bash
cd mijn-portfolio
vercel link
# → kiest scope (org/team) + project
# → maakt .vercel/project.json aan
vercel env pull .env.local
# → trekt ALLE env vars uit je gekoppelde environment
# → schrijft naar .env.local (overschrijft bestaande)
```
**Voordeel:** je hoeft je env vars niet handmatig te kopiëren. Eén commando = synced lokaal.
**Tip:** `vercel env pull --environment=preview` voor preview-specifieke vars.
**Visual:** terminal-output van een succesvolle env pull.
--- ---
## Slide 13: LIVE DEMO 3 — Feature branch + Background Agent ## Slide 10: `vercel env add/rm`
### ~25 min ### Vars wijzigen vanuit terminal
**Wat ik laat zien:** ```bash
1. **Cursor:** `Cmd+Shift+P` → "Open Background Agent" # Toevoegen
2. **Verbind** repo aan Cursor cloud (eenmalig — accepteren in browser) vercel env add OPENAI_API_KEY production
3. **Prompt aan Background Agent:** # → vraagt om waarde
> "Maak een nieuwe branch `feature/search`. Voeg een zoekbalk toe bovenaan de homepage. Wanneer iemand typt, filter de Pokémon-lijst op naam. Gebruik client-side state. Push de branch + open een PR met titel 'Add search bar'."
4. **Background Agent gaat aan de slag** — Tim laat zien hoe je 'm volgt in het Cursor panel
5. **~3-5 minuten later:** PR verschijnt op GitHub
6. **Vercel** comment in PR: preview URL → klik → live preview!
7. **Demo:** zoeken werkt op preview URL
8. **Code review** in GitHub UI: wat heeft de agent gedaan?
9. **Merge** → main → productie deploy
**Tweede prompt — parallel:** # Voor alle 3 environments tegelijk:
> "Maak branch `feature/type-filter`. Voeg chips toe bij elk type (fire, water, ...) om te filteren." vercel env add DATABASE_URL production preview development
Beide branches kunnen tegelijk draaien. # Verwijderen
vercel env rm OPENAI_API_KEY production
**Visual:** Cursor Background Agent panel + GitHub PR + Vercel preview URL. # Lijst
vercel env ls
```
**Wanneer handig:** als je een API-key roteert, hoef je niet via dashboard. Eén CLI-commando, klaar.
**Visual:** CLI-flow met prompt-balletjes.
--- ---
## Slide 14: LIVE DEMO 4 — GitHub Actions CI ## Slide 11: Cursor — alles in één IDE
### ~15 min ### Wat is het, waarom belangrijk
**Wat ik laat zien:** **Wat is Cursor:**
1. **Vraag aan Composer:** - VS Code-fork met AI native ingebouwd
> "Voeg een GitHub Actions workflow toe: lint + build moeten slagen op PR's naar main." - Niet een extensie — het IS de IDE
2. Composer maakt `.github/workflows/ci.yml` - Werkt met Claude, GPT, Gemini en lokale modellen
3. Push naar nieuwe branch `chore/add-ci`
4. Open PR → GitHub Actions tab → workflow draait
5. **Demo failing CI:** introduceer typo (`<dvi>`), push, zie rode X
6. Fix met Composer ("repareer de TypeScript build error in de PR"), push, zie groene check
7. **Branch protection:** main beschermen — PR's mogen pas mergen als CI groen is
**Waarom belangrijk:** **Wat je krijgt boven gewone VS Code:**
- Background Agents kunnen lint-errors maken — CI vangt dit op - Chat met volledige codebase-context
- Voor je merge: weet je dat het op een schone runner ook werkt - Plan-mode (eerst denken, dan doen)
- Geen "werkt op mijn laptop"-discussies - Build-mode (autonoom uitvoeren)
- Inline edit (Cmd+K)
- Background agents (cloud, async)
- Cursor Rules (per-project instructies)
- Tab-completion die meekijkt met je hele bestand
**Visual:** vier kwadranten met de hoofdfeatures.
--- ---
## Slide 15: Composer vs Background Agent ## Slide 12: De twee windows + Agent mode
### Wanneer welke? ### Cursor 3 — wat zit waar
| Scenario | Tool | In Cursor 3 zijn er **twee hoofdvensters** die je met **Cmd+Shift+N** wisselt:
|----------|------|
| Snelle wijziging tijdens coden | Composer |
| Complex refactor over meerdere files | Composer (kun je begeleiden) |
| Onbekend gebied — wil zien wat agent doet | Composer |
| Welomschreven feature, kan op de achtergrond | Background Agent |
| Parallel werken aan 3 features | 3x Background Agent |
| Tijdens vergadering een PR voorbereiden | Background Agent |
| Repo nog niet in Cursor cloud | Composer |
**Mentale model:** | Window | Wanneer | Hoe |
- **Composer = pair programming** — jij + AI samen aan dezelfde plek |--------|---------|-----|
- **Background Agent = delegeren** — je geeft taak, gaat iets anders doen, komt terug | **Editor Window** (classic) | Code schrijven en reviewen | Cmd+Shift+N → Editor |
| **Agents Window** | Meerdere agents parallel managen | Cmd+Shift+N → Agents |
**Risico bij Background Agent:** **Binnen Editor Window:**
- Agent kan vastlopen op rare config — debugging is moeilijker - **Cmd+L** — chat opent rechts (kies modus: Ask / Agent / Plan)
- Verbruikt credits (Cursor Pro of Business) - **Cmd+K** — inline edit op selectie
- Te vage prompts → matige PR's. Specifiek zijn helpt. - **Tab** — completion
**Binnen Agents Window:**
- Lijst met running / done agents
- Lokale agents, cloud (background) agents, remote SSH
**Gouden regel:**
- Code schrijven of 1 ding bouwen → **Editor Window** + **Agent mode** in chat
- Meerdere features parallel → **Agents Window** met Background agents
**Visual:** twee windows naast elkaar + sneltoets-pijl.
--- ---
## Slide 16: Lesopdracht + Huiswerk ## Slide 13: Editor Window — stap voor stap
### Pokédex zelf bouwen + deploy ### Je hoofdwerkplek voor code
**Lesopdracht (in-class, 30 min):** **Wat je live ziet:**
- Eigen Pokédex repo opzetten (zelfde setup als demo) 1. **Cmd+Shift+N** — switcht naar Editor Window (classic IDE-layout)
- 1 feature met Composer toevoegen 2. Linker zijbalk = file explorer, midden = editor, onder = terminal
- Push naar GitHub + connect Vercel 3. **Cmd+L** — opent chat-paneel rechts
- Productie URL openen — werkt 4. Voor snelle inline edits: selecteer code, druk **Cmd+K**, beschrijf wijziging
5. Cursor toont **diff inline** in de editor
6. **Accept** (Tab) of **Reject** (Esc)
7. Voor file-search: **Cmd+P** (zoals VS Code)
8. **Tab** — accepteert AI auto-completion suggesties
**Huiswerk (voor Les 16):** **Wanneer kies je dit:**
- **A:** Feature branch met Background Agent (1 feature, jouw keuze) → preview URL delen - Je standaard coding-flow — alles wat je in VS Code zou doen
- **B:** GitHub Actions CI (lint + build) in werking - Wanneer je veel files tegelijk wilt zien (split screen)
- **C:** Branch protection — main mag pas merge na groene CI - Voor VS Code extensions (linters, formatters, debugger)
- **D:** `DEPLOY.md` met:
- Productie URL + 1 preview URL
- Reflectie: Composer vs Background — wanneer welke werkte beter?
- 1 voorbeeld waar Background Agent verbluffend goed of slecht was
**Bonus:** **Visual:** screenshot Editor Window met file explorer + editor + Cmd+K-diff.
- Eigen externe API integreren (Open-Meteo, GitHub, jouw keuze)
- Vercel environment vars demo (verschillende waarde in preview vs prod)
--- ---
## Slide 17: Volgende les + Afsluiting ## Slide 14: Agent mode — stap voor stap
### Vragen? ### AI bouwt autonoom in je Editor Window
**Vandaag gezien:** **Wat je live ziet:**
- Externe API integratie in Server Components 1. In Editor Window: **Cmd+L** — chat opent rechts
- Static / ISR / Dynamic / Client — vier rendering modes 2. Bovenaan chat: **mode-selector** → kies **"Agent"**
- Cursor Composer voor synchrone changes 3. Typ de **hele taak**: *"Bouw een /about pagina met team-grid in onze huisstijl"*
- Cursor Background Agent voor async features 4. **Enter** — agent start zelfstandig
- Vercel productie + preview per branch 5. Cursor toont een lopende **takenlijst** + voortgang
- Environment variables per omgeving (production / preview / development) 6. Agent **edit meerdere files**, runt terminal-commando's (vraagt vooraf), kan **web browsen**
- GitHub Actions CI (lint + build) 7. Geen limiet op het aantal tool-calls per taak
8. Aan het einde: samenvatting → **Keep / Accept all** of per file
**Volgende les (Les 14): Agents** **Wanneer kies je dit:**
- LLM in een loop met tools, autonoom 20-50 stappen - Grotere feature die meerdere files raakt
- `ToolLoopAgent`, `stopWhen`, `prepareStep` - Refactors, nieuwe pagina's, API-routes opzetten
- Research-agent from scratch - Je vertrouwt het plan en hebt tijd om aan het eind te reviewen
- Wanneer wel/niet agents gebruiken
**Vragen? Feedback?** **Visual:** chat-paneel met mode-dropdown + lopende takenlijst.
--- ---
## Slide Summary ## Slide 15: Background agents — stap voor stap
### Cloud-based async — in Agents Window
| # | Title | Type | **Wat het is:**
|---|-------|------| - Async agents die in **geïsoleerde cloud VMs** draaien
| 1 | Title | Opening | - Werken parallel in eigen Git branches
| 2 | Terugblik | Recap | - Beheer via **Agents Window** (Cmd+Shift+N) of `cursor.com/agents`
| 3 | Planning | 180-min |
| 4 | Externe APIs | Theorie | **Hoe een agent aanmaken:**
| 5 | Externe API code | Theorie | 1. **Cmd+Shift+N** → naar Agents Window
| 6 | Cursor — Composer vs Background | Theorie | 2. Klik **"New agent"** (of `+` in de kop)
| 7 | Vercel preview deploys | Theorie | 3. Kies **environment**: local / cloud / remote SSH
| 8 | GitHub Actions CI | Theorie | 4. Beschrijf de taak — specifiek, alsof het een ticket is
| 9 | Wat we bouwen | Intro demo | 5. Optioneel: kies branch-naam (`feature/about`)
| 10 | **LIVE DEMO 1** — Pokédex + Composer | Demo | 6. Klik **"Start"**
| 11 | **LIVE DEMO 2** — Vercel deploy | Demo | 7. Agent draait → je kunt switchen terug naar Editor Window en doorwerken
| 12 | Pauze | Break |
| 13 | **LIVE DEMO 3** — Background Agent + preview | Demo | **Hoe je resultaten ophaalt:**
| 14 | **LIVE DEMO 4** — GitHub Actions CI | Demo | 1. **Cmd+Shift+N** terug naar Agents Window
| 15 | Wanneer welke Cursor-mode | Reflectie | 2. Klare agents staan op **"Done"** in de agents-lijst
| 16 | Lesopdracht + Huiswerk | Praktijk | 3. Klik agent → zie **diff + commit message + uitgevoerde stappen**
| 17 | Afsluiting + Les 13 preview | Closing | 4. **"Open in PR"** → automatische PR op GitHub
5. Vercel maakt preview-URL → review → merge
**Wanneer:** 24 parallelle features, lange refactors die je niet wilt babysitten, of taken die je triggert vanuit je telefoon / Slack / Linear.
**Visual:** Agents Window screenshot met agents-lijst.
--- ---
## Bronnen ## Slide 16: Context — `@`-mentions
### Hoe Cursor je codebase begrijpt
- **PokéAPI:** https://pokeapi.co/ **Wat je kunt taggen in chat:**
- **Next.js fetching:** https://nextjs.org/docs/app/building-your-application/data-fetching
- **Cursor agents:** https://cursor.com/product | Tag | Betekenis |
- **Vercel preview deployments:** https://vercel.com/docs/deployments/preview-deployments |-----|-----------|
- **Vercel environment variables:** https://vercel.com/docs/environment-variables | `@file:foo.tsx` | specifiek bestand |
- **GitHub Actions for Next.js:** https://nextjs.org/docs/app/building-your-application/deploying | `@folder:components` | hele map |
- **GitHub branch protection:** https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches | `@code:functionName` | losse functie/class |
| `@docs` | externe documentatie |
| `@web` | live web-search |
| `@git` | git history / blame |
| `@recommended` | Cursor's eigen suggestie |
**Voorbeeld:** *"Refactor @file:UserCard.tsx zodat het @code:useUser uit @folder:hooks gebruikt"*
**Visual:** chat-bubble met meerdere `@`-tags highlighted.
---
## Slide 17: Cursor Rules
### Per-project instructies aan de AI
**Wat zijn rules:**
- Markdown-bestanden in `.cursor/rules/`
- Worden aan ELKE prompt toegevoegd als systeem-instructie
- Per-bestand-glob: alleen actief op matching bestanden
**Voorbeeld `.cursor/rules/general.mdc`:**
```markdown
---
description: Algemene project-conventies
alwaysApply: true
---
- Gebruik TypeScript strict mode
- Tailwind voor styling — geen CSS modules
- Server components by default; "use client" alleen waar nodig
- Imports: gebruik @/ alias voor src/
- Geen any-types
```
**Visual:** mappenstructuur met `.cursor/rules/` uitgevouwen.
---
## Slide 18: Project context — `AGENTS.md`
### High-level documentatie voor AI
**Naast Cursor Rules:**
- `AGENTS.md` of `PROJECT.md` in repo root
- Beschrijft architectuur, conventies, "hoe wij hier dingen doen"
- Cursor + Claude Code + GitHub Copilot lezen dit alle drie
**Wat erin staat:**
- Tech stack + waarom die keuzes
- Belangrijke patronen (auth flow, error handling, etc.)
- Hoe testen, hoe lokaal runnen, hoe deployen
- Pointers naar belangrijke bestanden
**Effect:** AI begrijpt je codebase in plaats van wilde gokken te doen.
**Visual:** AGENTS.md structuur als outline.
---
## Slide 19: Tips & Tricks
### Wat Cursor-power-users doen
**De top 7:**
1. **Cmd+I** voor Composer met multi-file context
2. **@-everything aanvullen** — Cursor onthoudt context per chat
3. **Plan + Apply per stap** — niet alles tegelijk accepteren
4. **`alwaysApply: false`** Rules voor opt-in conventies
5. **Auto-mode** — Cursor kiest beste model per taak
6. **Diff-preview** vóór accept — Cmd+Shift+Enter
7. **Notepads** — herbruikbare prompt-snippets per project
**Anti-pattern:** alles in één gigantische chat dumpen. Hou chats focused.
**Visual:** tips als badges met nummering.
---
## Slide 20: De feature-workflow
### Hoe één feature van idee naar preview-URL gaat
**Vijf stappen die we live doen:**
1. **Plan** in Cursor: beschrijf de feature
2. **Branch:** `git checkout -b feature/x` (Cursor kan dit ook)
3. **Build:** laat Cursor de code schrijven
4. **Commit + push** → Vercel start automatisch een preview-deploy
5. **Open preview-URL** → test, geef feedback, itereer
**Cyclus duurt ±10 minuten** per feature. Inclusief AI denkwerk.
**Visual:** circulair diagram met de 5 stappen.
---
## Slide 21: Live demo 1 — Hero section
### Plan → Build via Cursor Chat
**Wat ik demonstreer:**
- Nieuwe branch: `git checkout -b feature/hero`
- Cursor Chat: *"Plan een hero-section voor de homepage. Gradient achtergrond, een grote titel, ondertitel, twee CTA-knoppen."*
- Plan bekijken → Apply
- Inline tweaks via Cmd+K voor finetuning
- `git add . && git commit -m "feat: hero section"`
**Visual:** screenshot van Cursor met Plan-output.
---
## Slide 22: Push → preview-URL
### Vercel doet de rest
```bash
git push -u origin feature/hero
```
**Binnen 3060 seconden:**
- Vercel start build
- Preview-URL verschijnt
- (Als er een PR open is) Vercel-bot plakt URL onder de PR
**Open de preview-URL:** je nieuwe hero section staat live, gedeeld met iedereen die de link heeft.
**Visual:** Vercel build-progress screen.
---
## Slide 23: Live demo 2 — About page (background agent)
### Parallel werken
**Wat ik demonstreer:**
- Nieuwe branch: `git checkout -b feature/about`
- Cursor → *"Open background agent: bouw een /about pagina met team-leden in een grid. Gebruik onze hero-section als stijlreferentie."*
- Agent gaat in eigen tab aan de slag
- Ik blijf in main editor verder werken
- Agent meldt zich klaar → review diff → apply
- Commit + push → tweede preview-URL
**Effect:** twee features tegelijk, beide met eigen preview.
**Visual:** twee preview-URLs naast elkaar.
---
## Slide 24: PR mergen → productie
### De cyclus afsluiten
**Op GitHub:**
1. Beide branches → PR's openen
2. Reviewen in preview-URL's
3. Mergen naar `main`
4. Vercel detecteert merge → bouwt productie-deploy
5. Productie-URL update vanzelf
**Mentaliteit:** main is altijd deploy-baar. Feature branches zijn experimenteerruimte.
**Visual:** PR-pagina met "Merge" knop gemarkeerd.
---
## Slide 25: Recap
### Wat we vandaag hebben gedaan
- Vers Next.js project gescaffold met `npx`
- Git repo + push naar GitHub
- Vercel project gekoppeld + eerste deploy
- Vercel CLI: link, env pull/add
- Cursor in detail: Chat, Plan, Build, Inline, Rules, Background agents
- Twee features in eigen branches → twee preview-URL's
- PRs gemerged → productie
**Wat je nu kunt:** elke nieuwe app uit deze opleiding (of erbuiten) op deze manier opzetten. Cursor + Vercel zijn je standaard-werkbank.
**Visual:** checklist met alle stappen.
---
## Slide 26: Nu jullie — Lesopdracht
### Bouw je eigen Cursor + Vercel flow
**Wat je gaat doen (in eigen tempo, ±1,5 uur):**
1. `npx create-next-app` — je eigen project (kies een onderwerp dat je leuk vindt)
2. Push naar GitHub
3. Vercel project + eerste deploy
4. `vercel link` + `vercel env pull`
5. Open in Cursor → schrijf één `.cursor/rules/general.mdc`
6. Schrijf één `AGENTS.md`
7. Bouw 2 features in eigen branches via Cursor (Plan → Build)
8. Push beide → check preview-URL's
9. Open één PR, deel beide URL's in de chat
**Voor exacte stappen:** `Les14-Lesopdracht.pdf`.
**Visual:** opdrachtkaart met checklist.
---
## Slide 27: Volgende les
### Wat komt eraan
**Les 15 — RAG + Embeddings:**
- AI antwoorden op basis van eigen documenten
- Embeddings + pgvector + context-injectie
- We bouwen een PDF Q&A-app
**Voorbereiding:**
- Je Next.js-app uit vandaag staat live op Vercel
- Je hebt minstens 2 PR's met preview-URL's gemerged
- Cursor draait, AGENTS.md en `.cursor/rules/` zijn ingevuld
**Visual:** plaatje document → embedding → AI-antwoord.

View File

@@ -0,0 +1,29 @@
# Project Guide — voorbeeld voor mijn-portfolio
## Stack
- Next.js 16 (App Router)
- TypeScript strict
- Tailwind CSS v4
- Vercel hosting
## Belangrijke conventies
- Server components by default
- "use client" alleen voor interactie / hooks
- @/ alias verwijst naar src/
- ESLint moet groen zijn voor merge
## Hoe lokaal draaien
```bash
npm install
vercel env pull .env.local
npm run dev # http://localhost:3000
```
## Hoe deployen
- Productie: merge naar main → Vercel deploy't automatisch
- Preview: push naar feature branch → Vercel maakt preview-URL
## Belangrijke bestanden
- app/layout.tsx — root layout met fonts en theme
- app/page.tsx — homepage met hero
- src/components/ — herbruikbare UI-componenten

View File

@@ -0,0 +1,9 @@
# Cursor templates — copy-paste klaar
Twee bestanden om in je nieuwe Next.js project te plakken:
- `general.mdc` → plaats in `.cursor/rules/general.mdc`
- `AGENTS.md` → plaats in de repo-root
Beide bestanden vertellen Cursor (en Claude Code, en GitHub Copilot)
hoe jouw project in elkaar zit en welke conventies je hanteert.

View File

@@ -0,0 +1,28 @@
# Feature prompts — voor de live demo
## Feature 1 (Plan + Build, Cursor chat)
```
Plan een hero-section voor de homepage van mijn-portfolio.
Specs:
- Gradient achtergrond (subtiel, twee blauwtinten)
- Grote titel: "Tim Rijkse"
- Ondertitel: "AI Developer & Docent"
- Twee CTA-knoppen: "Projecten" (primair) en "Contact" (secundair)
- Responsive: stack op mobiel, naast elkaar op desktop
- Gebruik Tailwind, geen custom CSS
```
## Feature 2 (Background agent)
```
Open background agent.
Bouw een /about pagina met team-leden in een grid. Specs:
- 6 team-leden (mock data is prima)
- Grid: 1 kolom op mobiel, 2 op tablet, 3 op desktop
- Per persoon: foto-placeholder, naam, rol, korte bio
- Stijl: matched onze hero-section (zelfde gradients en typografie)
- Header bovenaan met titel "Het team"
```

View File

@@ -0,0 +1,21 @@
---
description: Algemene project-conventies
alwaysApply: true
---
# Code Style
- Gebruik TypeScript strict mode
- Geen any-types; geef expliciete return types op functies
- Tailwind voor styling — geen CSS modules
- Server components by default; "use client" alleen waar nodig
- Imports: gebruik @/ alias voor src/
# Bestandsstructuur
- Componenten in src/components/<naam>/index.tsx
- Page-specifieke componenten in app/<route>/_components/
- Hooks in src/hooks/<naam>.ts
# Algemeen
- Geen console.log in committed code
- Errors: gebruik next/error of toast (geen alert)
- Async: prefer await over .then chains

View File

@@ -0,0 +1,228 @@
# Les 15 — Docenttekst (Autocue)
**Fysieke les — letterlijk voorleesbaar. Slide-nummers tussen `[SLIDE N]`.**
> **Structuur:** ±1 uur theorie + live demos → kwartier pauze → ±1,5 uur hands-on
>
> **Voorbereiding — accounts + tokens:**
> - GitHub Personal Access Token (fine-grained, test-repo permissies)
> - Supabase Personal Access Token (`sbp_...`) + je `project_ref`
> - Vercel account (geen token vooraf — OAuth bij eerste gebruik)
> - Cursor + Pro Student ingelogd
>
> **Voorbereiding — `~/.cursor/mcp.json`:**
> - Alle 4 servers vooraf geconfigureerd
> - Cursor herstart — 4 indicators groen (github + supabase + vercel + polderfest)
> - Voor Vercel: OAuth-flow vooraf doorlopen
>
> **Voorbereiding — code:**
> - `polderfest-mcp-finished` uitgepakt + `npm install` gedaan
> - `.env.local` werkt — `npm run dev` getest
> - Eén commit gedaan in `git init` (klaar voor Chat 1)
> - Backup productie-URL op Vercel als demo-fallback
>
> **Voor uitgebreide handleiding zie `Les15-Docenttekst.md` (normale versie).**
---
## BLOK 1 — Welkom + Vandaag
`[SLIDE 1]`
Welkom bij Les 15. Vandaag hebben we het over MCP — Model Context Protocol. Dit is de open standaard die in een jaar tijd is opgenomen door Cursor, Claude Desktop, OpenAI en veel meer. En vandaag heb jij hem ook.
`[SLIDE 2]`
Vandaag doen we drie blokken. Eerst kort de theorie — wat is MCP, waar komt het vandaan. Dan koppelen we live vier MCP servers — GitHub, Supabase, Vercel, en daarna onze eigen Polderfest. En in het laatste blok komt alles samen in een live demo van drie chats, waarmee we van leeg-project naar volledig gedeployd gaan zonder zelf één terminal-commando te typen.
Chat één maakt een GitHub repo aan via de GitHub MCP. Chat twee bouwt een nieuwe feature aan onze Polderfest server via de Supabase MCP. En chat drie maakt een Vercel project aan en deploy't dat naar productie.
## BLOK 2 — Wat is MCP
`[SLIDE 3]`
Wat is MCP. Het is een open standaard die beschrijft hoe AI-clients verbinding maken met externe tools en data. Drie dingen worden vastgelegd: hoe ze contact maken, hoe servers beschrijven wat ze aanbieden, en hoe data heen-en-weer stroomt.
Anthropic heeft het eind 2024 geïntroduceerd. Open spec. En in een jaar tijd hebben Cursor, Claude Desktop, OpenAI, Windsurf en Zed het allemaal omarmd. Geen vendor lock-in.
De analogie die ik je wil laten onthouden: MCP is USB-C voor AI. Eén stekker, elke laptop.
`[SLIDE 4]`
Waarom is dit zo'n big deal. Stel je voor: jij hebt een handige tool gebouwd. Je wilt hem in Cursor gebruiken — Cursor heeft zijn eigen extension API. Je wilt hem in Claude Desktop — dat is een plugin systeem. Voor ChatGPT moet je een GPT maken met Custom Actions. Voor Windsurf weer iets anders.
Eén feature, vijf integraties. Allemaal anders.
Met MCP bouw je het één keer. En het werkt overal waar MCP gesproken wordt. Vijf integraties worden er één.
`[SLIDE 5]`
De architectuur is klein. Drie rollen om te onthouden.
De host is de app waarin je werkt — Cursor of Claude Desktop. De client is de MCP library in die host — daar hoef je niets aan te doen. En de server is wat WIJ bouwen of wat we koppelen.
Een server kan drie dingen aanbieden. Tools — functies die de AI mag aanroepen. Resources — read-only data. En prompts — voorgedefinieerde templates.
Vandaag focussen we op tools.
`[SLIDE 6]`
Het ecosysteem is enorm. Honderden MCP servers beschikbaar. GitHub voor issues en code. Supabase voor database. Vercel voor deployment. Filesystem, Slack, Linear, Notion, Postgres — noem maar op.
Vandaag koppelen we er vier in Cursor: GitHub, Supabase, Vercel — en onze eigen Polderfest. Met die vier kunnen we van leeg project tot live productie-URL.
## BLOK 3 — Vier MCPs koppelen
`[SLIDE 7]`
Hier zie je de vier MCPs op een rij. Allemaal via HTTP, want dat is de moderne manier in Cursor 3. Geen losse processen meer, gewoon URLs.
GitHub gebruikt een Bearer token — dat is je Personal Access Token. Supabase ook een Bearer — je sbp-token. Vercel doet het anders en eleganter: OAuth via de browser, geen token nodig vooraf. En Polderfest is onze eigen lokale server zonder auth.
Eén patroon, vier servers. Allemaal in dezelfde mcp.json.
`[SLIDE 8]`
Hier zie je de complete mcp.json. Dit bestand staat in mijn home folder onder dot cursor slash mcp.json, dus globaal — werkt vanuit elke folder die ik open.
Vier entries onder mcpServers. Supabase met de mcp dot supabase punt com URL, een project ref als query parameter — read only true om veiligheid in te bouwen — en een Bearer header met mijn sbp token.
GitHub met api dot githubcopilot punt com slash mcp, Bearer met mijn ghp token.
Vercel — heel simpel — alleen type http en de URL mcp dot vercel punt com. Geen header. Cursor doet de OAuth bij eerste gebruik.
En Polderfest, lokaal op localhost drie duizend slash api slash mcp.
`[SLIDE 9]`
Even over de tokens. Drie tokens, één OAuth.
GitHub. Fine-grained PAT, niet classic. Op github.com onder Settings, Developer settings, Personal access tokens, Fine-grained. Permissies die je nodig hebt: Contents, Issues en Pull requests, allemaal read plus write. Token begint met ghp underscore.
Supabase. Op supabase punt com onder Account, Access Tokens, Generate new token. Belangrijk om te onthouden — dit is een account-level token, NIET de project anon key en NIET de service role key. Hij heeft toegang tot al je projecten. Begint met sbp underscore. De project ref vind je in je Supabase project URL.
Vercel. Geen token vooraf. Bij eerste gebruik in Cursor verschijnt er "needs login". Klik erop, OAuth-flow in de browser, autoriseer Cursor. Klaar.
`[SLIDE 10]`
Live koppelen. Vier stappen.
Eerst maak ik de dot cursor folder in mijn home directory en open mcp.json. Twee, ik plak de complete JSON van slide acht en vul mijn tokens in. Drie, ik herstart Cursor volledig met Cmd-Q en open hem opnieuw — een reload pakt mcp.json niet opnieuw in. En vier, ik open een chat en check onderaan de indicator. Vier groene servers moet ik zien.
Voor Vercel klik ik nog op "needs login" en doorloop de OAuth-flow.
*(Ik laat dit live zien — open mcp.json, herstart Cursor, MCP-indicator gaat groen voor alle vier.)*
## BLOK 4 — Polderfest MCP
`[SLIDE 11]`
Tot zover bestaande servers. Nu de eigen kant.
Vandaag laat ik jullie kennismaken met onze Polderfest MCP. Zit in de finished zip die jullie straks krijgen.
Een complete Next.js app. Het meeste is gewoon Next.js zoals jullie het kennen. Het echte werk gebeurt in één bestand: app slash api slash mcp slash route.ts. Daarin staat de hele MCP server.
Vier tools zitten erin. searchBands om bands op te zoeken. getStageSchedule voor het schema van één dag. festivalStats voor totalen en verdelingen. En getWeather voor het weer via Open-Meteo.
`[SLIDE 12]`
Even een korte code-tour. Hoe ziet één tool eruit.
Het patroon is heel compact. Je roept server.tool aan met vier argumenten. Een naam — searchBands. Een beschrijving — let op, die leest de AI om te beslissen wanneer hij de tool gebruikt. Slechte beschrijving wordt niet aangeroepen. Het input-schema in Zod — die kennen jullie uit Les 12 en 13. En een async functie die de logica doet.
Wat nieuw is ten opzichte van Les 12 en 13: het return-formaat. Geen plat object meer, maar een object met een content-array. Elk item heeft een type en een tekst. Dat is MCP-specifiek.
*(Ik open even de finished route.ts en wijs de structuur aan.)*
`[SLIDE 13]`
Lokaal draaien is drie commando's plus de env-vars.
Uitpakken, cd erin, npm install. Copy env.example naar env.local en vul de Supabase-keys in — die hebben jullie nog uit Les 11 en 12. Daarna npm run dev.
Op localhost drie duizend zie je een landingspagina. Het MCP-endpoint zit op slash api slash mcp. En polderfest stond al in mijn mcp.json, dus na herstart van Cursor zie ik vier groene indicators in plaats van drie.
*(Ik draai dit live. Open localhost. Test in Cursor met "Welke jazz-bands spelen op zondag" — Polderfest MCP roept searchBands aan.)*
## BLOK 5 — LIVE: De drie chats workflow
`[SLIDE 14]`
Nu komt waar we naartoe hebben gewerkt. De climax van de demo.
Ik open drie aparte chats in Cursor. Elke chat heeft één doel. Chat één gebruikt GitHub MCP om een repo aan te maken en als remote te koppelen. Chat twee gebruikt Polderfest MCP plus Supabase MCP om een nieuwe feature te bouwen. Chat drie gebruikt Vercel MCP om een project aan te maken en te deployen.
Aan het eind: complete deploy van een nieuwe feature, vanaf mijn editor, zonder dat ik een terminal-commando of een browser hoef te openen voor GitHub of Vercel.
`[SLIDE 15]`
Chat één. GitHub MCP.
Ik open een nieuwe Cursor chat — Cmd-L, vers begin. En ik typ:
"Maak een nieuwe public GitHub repo polderfest-mcp aan via GitHub MCP. Voeg toe als remote origin aan deze folder. Push de main branch."
Wat ik vervolgens zie. GitHub MCP roept create-onderstreep-repository aan. Ik krijg de URL terug. Cursor draait vervolgens git remote add origin met die URL in mijn terminal. En git push min-u origin main.
Ik switch naar github punt com om te bevestigen — de repo staat er, met onze code.
Geen browser. Geen gh repo create zelf typen. Cursor heeft het via MCP gedaan.
`[SLIDE 16]`
Chat twee. Polderfest features.
Nieuwe Cursor chat — Cmd-L, opnieuw vers. Ik typ de hele taak:
"Ik wil een review-systeem voor de Polderfest MCP. Eerste stap: vraag Supabase MCP om een tabel band-onderstreep-reviews te maken — id, band-id als foreign key, score één tot tien, comment, en created-at. Tweede stap: voeg twee tools toe aan at-file app slash api slash mcp slash route.ts — addReview met bandName score en comment, plus getReviews met bandName. Gebruik dezelfde patroon als bestaande tools."
Wat er gebeurt: Supabase MCP genereert de migration en draait hem, vraagt om bevestiging, ik zeg ja, tabel staat. Daarna gaat Cursor zelf de tool-code genereren in route.ts. Ik accept all.
Test in dezelfde chat — twee vragen die nu via Polderfest MCP gaan: "Geef Maud and The Cathedrals een review van 9, episch." Daarna: "Toon alle reviews voor Maud and The Cathedrals."
Drie MCPs werken samen in één chat: Supabase MCP voor de tabel, Cursor zelf voor de code, Polderfest MCP voor het testen.
`[SLIDE 17]`
Chat drie. Vercel MCP.
Nieuwe Cursor chat. Ik typ:
"Maak via Vercel MCP een nieuw Vercel-project voor mijn GitHub repo polderfest-mcp. Voeg toe als env vars in productie: SUPABASE underscore URL en SUPABASE underscore SERVICE underscore ROLE underscore KEY — ik plak ze hier. Daarna deploy naar productie en geef me de URL terug."
Vercel MCP gaat aan de slag. Project wordt aangemaakt, gekoppeld aan de GitHub repo. Env vars worden gezet. Deploy wordt getriggerd — ongeveer 45 seconden. Ik krijg de productie-URL terug.
Laatste actie van vandaag: ik vervang in mijn mcp.json het polderfest entry — de localhost URL wordt nu de Vercel productie-URL. Herstart Cursor. Vraag in chat: "Welke indie-bands spelen er op zaterdag?" Polderfest werkt, maar nu via productie, niet meer via mijn lokale dev-server.
Dat is wat we vandaag voor elkaar hebben gekregen.
## BLOK 6 — Lesopdracht + Extra werk
`[SLIDE 18]`
Tot zover de demo. Na de pauze gaan jullie hetzelfde doen.
Acht stappen. Pak de finished zip uit. npm install plus env vars. npm run dev. Voeg vier MCPs toe aan mcp.json — de template staat in de lesbestanden, met placeholders waar jullie tokens moeten. Herstart Cursor — vier indicators groen.
Dan de drie chats. Chat één: GitHub MCP voor de repo. Chat twee: test Polderfest MCP met een paar vragen. Chat drie: Vercel MCP voor het project en de deploy. Eindig met polderfest in mcp.json wijzen naar de productie-URL.
Geen deliverables. Anderhalf uur. Ik loop rond.
`[SLIDE 19]`
Voor wie sneller is — niet verplicht. Doe wat ik in chat twee deed, voor je eigen feature.
Het patroon is hetzelfde. Vraag Supabase MCP om een nieuwe tabel met seed-data. Vraag Cursor om de bijbehorende tools in route.ts. Push naar GitHub — Vercel deploy't automatisch want het project is al gekoppeld. Test in chat.
Een paar ideeën: reviews zoals de demo. Een wishlist voor favoriete bands. Stage ratings. Of notes per band. Of iets totaal anders. Wat zou jij willen kunnen vragen aan je Polderfest data?
Geen deliverables.
`[SLIDE 20]`
Volgende week Les 16. RAG en embeddings. We bouwen een PDF Q&A app from scratch. Embeddings met pgvector — dat is een Postgres-extensie voor vector search. Context-injectie naar de LLM.
Voor de volgende les heb je drie dingen nodig. Polderfest MCP staat live op Vercel — dat doen jullie vandaag. Vier MCPs gekoppeld in Cursor — github, supabase, vercel, polderfest. En je Supabase project blijft draaien, want we breiden het uit met pgvector.
Tot volgende week.

View File

@@ -0,0 +1,187 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 8 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
9 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/PageMode /UseNone /Pages 15 0 R /Type /Catalog
>>
endobj
14 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616185725+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616185725+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 15 Autocue) /Trapped /False
>>
endobj
15 0 obj
<<
/Count 6 /Kids [ 6 0 R 7 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages
>>
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2061
>>
stream
Gb"/'?$Fae&:Mm.R$XB_&hB+r.5h]"#81]1%0[Xd*Z>NSV6MRuV,a@W;tA;mhRb;[8k;iR+ZpA"=J,2i1\^=]fHD1Ua*^Apd??""5'+ebBt>G-ZTO\JdaRI;?[GQFB&Hk/`18l0j9F2f[lqgkZ/l2XOlqoc)V_$D9@dXLs/8WbT[VS_$##dDKM0TmRrF@<_'rIHpiPr87s#q"cB[C3V<E<r\5kAbq>A&2R]^?#:cn7eTA(?k]djRuZg)8*Nj3/3gY0*t[f9o*K4c:+Ph/S1SesBh_hV-cX#4ai/-U)P&&Zhi1Qf[>bj-h?Z6E:M($B,?4+&r$<=f'k4+2SKP^[u253QemX?5%m#OOPRXJfLH%s(LoS5_Ha9I32d,*<Xf3VecZa?)^(`fi,0">2$%i/dMa2g1YeY2t[qrmI^C^Hd7dPq1`Q$G9[OrjUU$*^LPHd:tL==qOtSMK&(TfPY&0^2;nC6q_k'ORg(,30/!Z`L>GC1"L,7>fR@%3&VTTG&j^3h"s(L7EMo]\7_;82=NA_2=]54r<TVu47<(i9Ur)fN`9;NO4l..<X+(Fi&HB[Um<RTP\HaX\m^IVP>qhmYOeb[q75er+),J^d/"q;<QZU&@=?7i05pbKn;Tr]0TW9.b\nWkoj,cJ4RX2R[kYB=@Q/-'oP$_j8)?0kFPpCmF>1:dkTK)sj'3fILCV^_7VaM`CJFPXkhq?q6X5!b7$$,!6M_/"Z8n8A`bD#$)dO:LZHcGR"=q%F]Xisn+oRq2?g9Yb_NlS@:se5Nii8`P(;MQ9895h!AV`^$5`.'L/@gi_1'RoK=^#6hXj/"RgK@g8o<@49p!'L^n_URQ).)3u("964jU0sp?h.Y1%<&b8=EM8E,%9=0IUL`8[dA%]/Fk+PaHG]MMARZT;o8.3:=mLViu26g0_DB0FJe=?onO)#(,C_jB9DWfpWFc8r^JI0Nio\/Rp9Tgn+PITY$$EFa!.&-SVXC*h&D9Z+'TN>i[_9)2^Y0CU=hbd<b-/kQpl_:!j$%D&a?R9&j<8)h!"I*`G!,NbSOtkQ[i`(6ZT0BMTX;un?r<mo/M^r-(uJ2V4gSGW[/Ln:K9ofIG-M/fr%/oR*,YXB`P@8D^k=WTuM$pj@/6+XSX#@n7P]3DsZ=2p-%SCogI[faCf!IOtkjKoiVg;+\4=UPt7r%a"s_oQ#I><'sK^AM[[3LE%]';9SgS?f>8<d)b\7#'XKCZ0=Y8rA2%J(SYP6!BJEe7+SPGTj?#A,b-Fuhg45G+pjlqTB&Lg8TT_<JVrM?ea>'c@&k;2uk\F3JA$g_7&-)G'P[q&$3MJ97hJ`LNkE+Af-)8T\p/rX?qrD]\PJa;=j^-8ufjG91kF*i9H?.DRjq?.[$koi)&$O3g<YfIak-E2tXAN&S'P$UVH1>coTo1"<JZh5Nr<7P\IGmgHp*ZakJ&Pnb`O@DL1'<g!@$P/fI!Sb9a2'W/r]@WblbRFNCZjL`G!(*f43b3XK5p:iFP#1C5[edc6t)\5;0FUS=]Qf1/UDF7WX6]RCkmV&AZkfHV0bf\,:P0)d6!HH46UEr*FRpV3`5T\!!@mC-VGu'6)er@CC+,hSBD>MA$]a7hV`pE7>P!0::OIX=WGd]BRTfKFQ^hd+@@YfdF6t!VY?W^=OWEhP&c!F0+r[M?2'9+jmJ3_-eQ[=E,3%rgr-M?9WZ;*\WCS8!sjQtV._I.9He**e8>nB&XpW\JHn/)i/r.n'&/'JdO/HFa0dH*(6&bZo5E&8rEWK;Zo0/X_04HkBFDn4cB(eG*g4`%luS'Vd]75ee4B`P[e@'Q-s0*&/UBF:HRM(nA//i][%Q+O!29@/Q62g5<R%Oo#OSdF,aad(4&Jk8B0A^0eAc3s.jl-"IY]H#(%bDdn;],(@%]i?D5s\c@i`RuTe<e[Q,F\@++V6L5ijsem/_EMe(!dAP:Y5.KFU=1WTs#;[-*o8e-U&3,>2'9o@4$(0phQ%,F564,<?2B'q0(dWKXXt9V7n7bFRY[/8J30F#rAVn9_(eetEJ^euh;H,5`\B%R3S'[eJE#NqUO4HZ$I_e)%hdf9't~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2090
>>
stream
Gatm<?$Fae&:N_CbZeeBUKXt`,eShaO3g^$0Q9c[+4ekB?L)+Oe]KN?ot@\pQ&hA4V1I\1OL7/+GON4$RukYb$N8;\+F65LT3m<e7PnIM3";`A^SD\;-\"8c]/OA"0KY+d69:!9_=?K/MKS\RLh>&^oT4[HnT]'e;/e:Q5@i4jfJDm3b\1<*6q$"=KSY#+Vp@$RrB_*EM4u6kX;pJnhO\$O8Fn^`J!0TAiG2d1j6<*SW;XFPFP6qi:cN@Ynj3<NE-ZI&JgQ!HGF[@FnW5_oW!sHt%<PNG5d/l@S%0eCiCE97R4)V[8i5"KlZd"6HKf*SQqf:Ei`pQhC>,mtkVCOs8qkElYeWa+[prVt3FhW+=2e*cQmej63k*a>QJfm9fE?\@<:*T/3DH+@#hMbT.@l?%]C![(Xk=n)h5@TA%8au#pHb#n(Rc"?JIc=S`2"+T42CTVfqLB?9]:R(ptk&Z2t.R-m(rG'F=V,V*D:X.:7qVi'TKr4PeTm%O%SJr1bmEN!da$lY;d$U77k)_a0EW5OONLulPBpk3,<>Im"ir-eKd)=T6$P`;iU]-V\^TSN)4Y,QNG+oSG;S'cack%-W;Et+QP$Z!?;b_.5u6iTZ\LgN!Jlj\egZR-<7)$Un.``3hnR]<p,\t-i='_#*kJ"KUTd#Nusr8SC,>!a3tE("5lM>-taD"#gP6Gg'Z@@aSkU=r+KqB#AE^J3lhE;WD-qu+iPfI"?/Q0[*U6JXR^m*36h[`60&ocoie<Q4BkjJX\N*bJRT#9CiRU94MJ\J6>4n)W+QTHZ],ajHD>7?m5THpf:\fQWJ[_!>+:[MY:fgfVe75s/8TN&<,X6dhP@aL6e5UaH<^UF8IkE0V;/,P3>D`47:!47s1`cBZ_hnrj"tq8?['\>JpUL5L)lt6(rQt$$rckK#'U2TZ>trd&T-Hb^qar)Q.(]i:3S`<SC$n=&7,!6c+u@VLU0gges;oA"^n&E3IHBVKJ+4P=_@`XqhmSnZHX@,\12OTMUTY`Pj?J\XKuOE\F10BZYf,%2"m(/-pn>*HH6EZfAYZg*DVC_aDZEjNE=hRm$bp$S$O(UhDI$'E6`F(JfN\Cer\l5eQ@p`b1'a%OIW=#Irl6qpo$F)gHmgAHf@HaBWo.$G?`.^kB^R%r@1td=7uZ*RpNEV%S0)q8sloLRjt9*`1Ue;<3ki;ZEJB4cPLL^%0`Q(J$NY.9N@e<&&DuN(f`\H+mlY]-UXcQXgt4!'*=WGPgUbaCs(E%>7a>9d")Fuo!1,Xaij.`2N<^,^5It^PoQ/Hs4WGl^]Fim.bAMg:7WTa;?JoNH5JITQ1V[3hCs5:<:5>,aU`D@/PY$M[:gIXXkMHE8F-T'B>7,Q"(XH<PTjXcjk+Zl`Aa@9=S.s37LWD38iV94JCN6RN5c.!h<S\#NYf:0)j\G[58-MaD(;qip!&`_hi]d]V_CRE(\2`d_GtKf(/S!r06PQ(R:8Q+EYW43]$r#V9Imjrl!YgJK"t:=&W',4ZjTa`THPY.PV.-DfEkD'!KCp&#^83K4R7M8ocT3:jnar7UkUnC*=f5G%\/kjUNl#f&C:kuZ>\<YAK,=c.H\jR-tEco"Vi'5B>a9?ZS2N,nF?J=&/:/"?o"8n$4?Z;R*POE^<9f$danCh.KZ=/*l7s"7ajH0r#[(&%1lE^&e0]h/Km\sf5SL42o&-p-nIa*1>KHY9fF#nkk0=P21i62R<8$AeeM#Bg].^^hB<V.g+,\>NSM=5jPXIG[3,*QU/-&M0P>t@6S'_"GKLGcZoe)-6Z>+U5DN.![PRkYgm&(CY$`@VpD475Ou(f^-B2F]&i(fG]n]t.^(E`GW"*(EUf129;m3s+q,pRJ,r]64aq3ODe&JLPd"$7Ic*hKj_HUl'V89t9*CqcYnaqr],Uo/=dV<YTQ1pjGg,gQ/KGfAaFSl+Sn"6(",o?&ke?Z.HjBJ^K$&#KB?ODqT.TcfA+fU1U7gn[.JA@1-!n5hH&Qju8+_Q#":ccXN%h2-[!@,@6&`Q(mEXuBBK$hX#=K+Hk"P+I"b,TM_YPN**2e-&j;js20+R@/%:5ElJ#(c^.\_FF%N6C[>m__aprW1QS>R:~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2272
>>
stream
GatU4gNM>[&q/)-bj&,QQGeZFXOqsE`D,.!M-I%J>!4kV&31pX5m.RI;WFtL*q0fsEI-ie2t6snIIA%5VA[,.n2K/:]i`[+5K"6`[+\?hL_dA/V"<-#^RiC"b%22;c]YW%T$:$*e!&GdqsZX=W;U`3'*sF"\T<S\[mF`0:_ENqGKl/XoM0I%[`i^":TJ8S#Bk5Xf1i*mWb0tkVi9^XA_'f95HgO(;el(!gTQqW@P4X#G0?j4`u@pRrg$Sh>5a5Jrme7M5a??sZCp?.P!$W+PsMPJ5i+@lOE4fHFC)2s8=/D*f![S*Cfl]n;=T7'K7j;q`Enq?f@uJRFZ!k(G/n1Dh]d*Ui;8)h/^J$=U:5A$Gd"N)e#\0&WmW%E\b8Iu:2Qg:mY9rT7b*2\DpfRh=Ig6CNOJOk<3G91LNC]mA^a,S?sM6.?hFqo0bR[iTjfLaY9_[i379i5rQt!$SVlX=ltE^:dEBP[emTa!+U\kNmWmh#>3daqkA''6kI#*tDN)&_YK4t68QXKYZ45E76H8GX11Vn?V90L\7QmO1.;SPbQW]Xu8`Po50d\6h,>=Wb2=\H5QPZjNl0NmEoZS(X&W-\=A<B#HBIG_\qis.uhG!/3r$7TcZ;&pVa%s]Tib)Ak"\qHMk`VjS;u"GtlM50]V$8PMA>%Lf7.7,c:m$:,V5J,&?C*[RI`2OM[_f@sG]ncbZ_]EE$*#iD\fNuIXYH!hHOX!5^'ruQ16jcH3GNm-^SdF,oUn\?\u=p!CCnLd`YboqaK1[DJssBaV(N<_AX]'J:4&"7-&QE]\HZdOoRcg,4K29FGXSJHB;7f,=?[SeT#!cJ5Gda*VU*`@ilOS@,[.6$>-GJIVZ>G6Nu^fC^PjX`Vmea]CX7Y!LfRrRXQSkBHd[&pCXn<7)G+$]B0pMic<!o*ID8UT/SW6I,g-(t)LY/S9[4QBG7hO9gHcuBCuZ\!Eu9R+a=rdp:1A#*&9i5OgMoaVghBan%2-Hg1`cK7qHgXAcj;lUPHs<@bB\CpMe;KqM<eZtfcAUtaO.h-V`+&3'2B;&3uXFYSJLEBSrhiJ9'32ZocY727.c\Z>':\;.,]Nb1%pFJ]jnCA)Y%/rHUJ??kDcrGf21'J&0XZV0sOi>/)PiOaETUp,8]sTBG[$3J-rUtqOuD5(lN.4E'L3Qc_$1qr@gXc-&DaC5lu7.p<md+2NO?S_^)Kq==EMWYI-_h=r3h5r[U@Bg4K&a2b3"eLoH=?Z2jI<n1C%,Gl@%^*>HL>U[_ZeKblb)`j@4!VM)st7g*7S=K%`3Wlp"gKI@"+0DF/lQbeo<pE(9H!hD9jDK`oj#JLt&<'*=sgerU9<7IXb+1+uf1^BGS2(_e31G(TB[BucpN$s<:!*H/A95TFUjj&Il!f+R=g1)fKM)T`c8l$MWg$_^?J1,R#ekX#8T@2jWIb]K`(p^Bs=M3mRV1?Q?(="!X':djcnGUgo*<Eu?n?e>CQb]u-\l2Okqq7UgcS)K!6aS9Y.3[>pRR0dY$&m_j42MA!;"MZs7(db`6#sOV(5SI'jOrOGUA=%iIKkSRk482%f,`;&^p+hN>-D:<^AA3;#u5r>%`P(?DeQF]gp,mtGUsoPA'&3U#Y"J;D[#F9$+&J,ScL"fJ:NI42H"(eF/3QK8jB&kP2u8YNVOU[Q-.iB!sH%LT!U?FZIJPR8M_"]Z/lGP91MC"b4lO.+<-<j"U;ma?+"0G/#N#8m:t?`'Zij23^+Jtf[$(Zc@pX,18VJX[g+s5?DoQAaAIt#)DMGfS<"GeeuuTg3!At&(K`-C98ep<%.[sjZJ.qYkJm=%Cj'SD&%O6tmmZ,$-^F,Bf`i\!Qg#R))%r_H_R[?F>EcR?[4iq0j3%=A7nk.7D_55/dG>7d4/)?dLYoI`!FKT0*nTdJ-0P1Fb@/-/fjB)ko/2n!CP5<5<$ZK$BJ=7XhDKuKj&&b:&P;f^K/+"rlZOMm6=iQ7.5Oi@#3Q,,Rg_.P_9.De53b,MLi^oMdk[9=T93nN-<Q=\lt<gO)8QdPf>',2"/V@PXRe1bA3BOn6,.eW"9&+$,q$f%dMb9',u+PtA%_W3e"@hW;Vh^r!2/hb#sk"PI7-<BoZrfk.lZ*,SR7aO\dU$S2g[0p]ItBZQ.i\]J#h9--N]<ZVmHGB@-GqkV'aD/PJAh@YUl_uIaK3VAA,f>&Nc$\k`u02@JC>1BirhYh_=cX"@Nd3mcZZ7QhbgYJc.Te><NS^Z>0<E.rYL_:Rh$T...qIk7D0W/o__f'<15@/u<qK6hKF]:SP?m~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2142
>>
stream
GatU4bBDW0%,Le2E4i1!A$R<OYj>ZU\7n^'7h[1Oe6S#(L`5k>m*&:`o^rBB'MUZp9dbB1R2c]e$fL"S!uTC/i;Q2+^rnb9p.#*K#kK>F,C]jf)-kFIr]:IUQ'9ReS;68.S.-kFF?CDfF*-.IU&NH]&T&/fqpGt"^#u'FPBT,T/3oS>&](b&+tF77r^Fn-+sR,n6L2C:q$kPtAc$u+pE:U_UHUjEdt:DC'c%.5eHVfM9-1'Q-_lqq=Gm5mBG^uc*"4K.KbcN!8f->^l!+M[_eY:,b\.g#\A?6QNi5a)DJJ*6p!nI)-TQ%[QQoLl1$].J`R]gF/-T`B-PO"k=4hAHCtO0Ue<$e6A;.[DHhkK6,t;o(1$o"gAGVfANUPkJII\^29\>IbrLrR/AKf;*A'h6]kZbr2DWHf9S&X_CZ;m_eLr-oUdDNT<oGQKpms=`SVqIP@=-T5)HPdY3I_8]o.MX.[=:69$>foC7WE:C1kG!d_08=57EX6'Xp=;R"5)BSlL&O@!I[7DVBo9kY6nt@#oV![SOG0q`bC$g!jd+`mnXIVC%&%W/8Ja,s;(r^)]#r[g:K8hZ_P!7Tf$FV?*JS4^NG[LSE3Z)/2cZ/@fS)qjP.#7h^pQ\RjXG9pS.E3j;H`b0@!ia`_-e*$`8H%M/s_bX[a%EKaW2-Om-+$M]'dZg6aB6V.2#bh+9&&Wj;t%,V36P/[FDl9QqWk^Lf-.'nu/A0/6WHSQ,T\g8>_>GA:lQ]XE&Sb\@"7b;$pb^"%$FMRaRk2UQN_l)l9*$153,(%>e+fd(eXF,coE(G2a*"rBu^R7QID20TLGopV)k%27]c)9"K^MY0i%!h"32050Q$+f`o>fG5N)]P].DMS[T`+%1k1(N^R;G#`7WP/u9(1FDYEM!FcTk#tu$o>.o%(pLT>(3;&'%adD*O\6sD"[L3q68U*-3K0Pk9L?A]aq2iA*_e2lb'0;eP&D$?`SA"q1`cWQ52d`^?i"\0K^Z'WXPYRR5g-"crk\P4YWuiOt;%KrONPfBG]%9sN.S*QW@DdJtWHlB2KSBUBgn7Df*YAC?bf)MBj>#!K>kKMrd9tDC\HdG05EB%N6qOQKo?\8N)lWG[AtiTB`rZ2UX=BSVq:=rP";eY4WJ/sPr`Lh?*^JhI4Z&I>+-JSP#Jp3aUb^W=$.UM"AfC=k:IihgRA&0AU20CbWm^Q,A'8a$Dp@)2K/;V$a.@rG`qOW]cCU4K1k/Aa"E?=8MD"6I3':@P?Vj7[FTX)#EWY@:gQqG3Anjt#4e94Dfr.^7*gg*FPAO>t8A'/_e!%dLCYr,(H[7Wt;nalM2:!K:P9PFolp<6AZQ`#R:n:_<3RN6:m:S5RV;$-de],rl.tpV+RYutlTHZ.,dS;<.h"h"*mT%2d\+7%!.h@Ea+)g8QFmaJ=]2+7AF4fc/2XMcsR2i@]*LU6rYrAHUd]A**dWFVsb6]KbHh"ImiNsCVLMN#u3>bMgkH;4Qh[5Kh>qN*G-DN_/bQ2ZEr]Q_DY)V"1F:j3[$56gElqi8]Ooj\*GIolT*L4.hdt\GKl&D(";KAIg,$Of_4TO\IK9%"#=H9%\X";*(YX%H=82Hk_9oXt#lT!>eLo&.B)",>ES(L!J::7`+3/5a$B@L8!9)L$p*A'Vc_fcJ9ddD)fEP0>LV$.qEH)J?kg:+Jj&:U]kaQSdg[>toE4fUR@R=:kqbspE#<JtR3A49Uc'raa`)l?f9AND:Y.k/>)87Ppk,R=]sG<DZ,<O(Qd7,1;hKg4%CY<2+&**AD)m89EXfO;41#L6eNZY"#/=*'LF0dITWY'W,iG`MKBJ/K^tQnb!%RRN8k,>Vmc4,o_te*$?=9V+$?mRsY%),Z[l3sfLgf3OU\/u'.1$D8k@Ha%!3J4GeNr0n*)3d==f2_o.o&04n3T%P$?R+E"8S@L.ZW%j\]p_A<-047MfSfH08jSf4eQ4s&GE0"2TR(8J1%Mp"TJm0^78$5L];'lInJb9^N7/qDZS5keVkmk_/!O/f+ck-P+c,Um?]pVguj47e^hQ52-;ImZW3O-6$hGRse2%Q8jRnrG+&GERj7]c=2"lrjY,BtSVCcln0JJnca"AI0kCr5WnkGEC-%X;YtbP:d9?4*`uWjV!kR:$<CgAgUG#TWdR^aO[?~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2219
>>
stream
GatU4gN)%<%"6H'@/SBKXi+D-/788#EGX_l;&VAoPNaoP=HO0&'$4^O9=aTEpL$QiZKk2OfU'N%QODa0huKeY*TDG(s,Ae.B/BD[q1&Ymbn`,Q"XBI1Bek&+c]^Z$EZ>;De*qHEa'fgT*$((/*ZbP0eNYs;JkZI:^4R)fAbDpUoqo%VdgsS5>=s.cQ$$_]>nq+W8u=N`=1Fa+A[[V.VG&@<3GifrS78O`G-p>37iVAs3&2ArH,HK-M9_;+kr%:;67^U`JS$F$rCB_TRWD)t;p#8cWKbXB-+KV+b*L%VIC]tgcBts\)>mK]?supqA52/FE+M;<etf$r$V)[@KC/ZRkBD;<JC4/an.?M^&<th.=WE)rd1SWm%)ZRq2p;=g,+*&@]V:BdFCimfm6]\$3%C2cdD64Q"hdc59Ic*;[UcsGDTT[L/R+/N4'CER^7gP/l*NrckV?<G#7Y.pBP*>07*a'"Ou<O6I:hg'TE@GL-]\8kdV;RFP&ApT&O<>$3b9Cg7g&S34_FHPjdh]Z$S?[K8?dLf,j=X3ftu7Q^LThE:H7g[K2F!;N9VgHGGG*%HMZoI6A+=53uI6`PbSKB]F]D.^bh_YCa5/QU?rTLiTT$Urhd6;iGB2Q>JA>:VrI'dQB/W)B,EgGqV[?2CL9$4E(T=;(S@.crPG!<8$:-`VH5dkd-bI&7h$/eMO#Ib/1LXiJbD(@.$G9q[:tK(;770N@_9.-S7[lRO/qkbkLUBIEpQXm`\RM_DCR^Ad%fpK]D&K]mbE-_Fuj=hm+m.Sq6SWYLgcDq;++T&[]TDF>bpKqjga#i,_cu[MN"A9`"g>$9"G`-;)j"m+n+CYFW%b:*gEWr!kN+ZE+<_N+!mF#ENTOZf;9rUNOLS-AL='A^*,eU"\:Q@+l9NsJJs,df7]^F8]!BgEAK;S5mO.3&X==R5b!+,Y,u"V@]Dl!^Vf<Z0TAgg$,L6[3s@N=*:tAqQH1n3@s#OC8b@`h/e]CrVlB0*<C3rZWcgKnQC[tRLP[ug'<-(fE*qV'dE&@(1TY8QF!omr?@lrffkh6'o;*IcnQKoC5&e)F>baITkA[c'&ubs35h-QjX+4?SP$_F64l,F1U?>'d-N%\+>ALu$p$"_>Ca9O=Yc1d+<+ro.QbWW$muWLu,K0-c1t?8%8t=`"Tb[q;/q,I!550/'>>@5eM:'^Z0H?(5`1_r&?mOJZ&<Flj0aJ[+:C`/XnGRGW?iLLA>R(i:l<@(e#OlR+p,*n:*_a+$"e'VAFS$5@rcM9;'R\SFfY-=m2g)Qm%\7H]4SfCPpEqEMkgk8!_YBKSW5EPr=0\bJNA;m/W_-2h'oJiU[UR]7BuBG9MdrA.l#VCqK`i9G^g'1*@<nPf?"/\c>?t(%+]`lNTp2c673SOnD_5;Nl:88qq:dan#G[]ha!0Ds<8LYX.gatqGFY?k5Z7N!>S!<gE]HW%.M4hnTVtA\,$t-:'Zu'`%9h%blmaG?/;$bL$E_.AXa>-G/^]2A3J2_mgC<*1U_C^2"7TmX).]i=aooZ\SC:5hBA9So58r)n`%R?Pqok+IpW%&87`L,`JF*-F+3L's`Tl.UFO?,/gSod?msHcTb7]&_)Y0FN]r0sNH?IECK_bn%^Dh=3FSpr0HGSJ(<26,9*,GrF>\9%n_?=.Eei1b$5iaL8B,>JUj2[)!Ba54V]@H2a@DA/ah1.t+c8Yo/Oag/Chi<4UO_RiO-4<eDoEFmJVB+7N[?ruBeJ)+of[=eb>Hi.5jUNCB*`WCtKi6&r3PiQ#<opc<qp#3K8*Y7+DN7;[P9g:oa3(CK>>9BQIEX8jkB]"@=Q&](*3%,nLZke=>h5*O;g9A%VF]uEp`=K_j\1DM5E;B:1O"&C"qdf@2;!q?!H!nArZ';de%LM5oi(1PWI<49/@8pn/_M2(DiG_:g28-.DkQ?o/t8'C6>[.\!Rg:hUO]KfKS/?\N]b/>gBH[CW^?sY`'p!g7sIn\*MT+Iad]+=a@@_hVP9>^_,`%\f"0"Fr7?<mi4ButX-hu=f+'JD^S],&;(XgG*.3`.4Y-lt@0])sCMG]/%6]gFHI#knHbP:[Ukr_EWd?<b'Fi%_]fU]eZHh-nj1%kH#@uM\Q?$>6=-q7tGOO;8n'6D+oR9tGe5.#+qp?$mBj]\=DIJ%#!Vs4l1p.'[^ELVk`,aPoJ'j!D3?&7CRai]7/%],TggNKUk>F\<Ti'9$VUIKuF$`\%"1rfM#Q~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 386
>>
stream
GasbTb>,r/&4Q?hMRtDf$?6?"CoPPaU`uP9fJrO\Fm@\sG1,8\qOgBX$T-Geo$!W7\cOmI39BfSGcpQ*+s74l)i-7";8$F9-!uM=FH28q4=b"FPC/fnTCS0.\==9n+5\=P&QeBFU\8>6IYJo+UQomAMD.$=OQD7kJ1];b$-I$Yg2[5F.G\N:3hsc7-q(_mQO$I2#bqWVl`_SJ-A0X=QpC2VE]i"J`b;qNeY9XqBTQ7daeY1KIq/[=Zu]MC7R:8&HQQA;M:(OsmHYI[mpn!PV&2rH;KPtX)&DqEp#FJQcF]Cnn[bq_]%sS+0;U"biZN*VJP^^AMY1g8U=U!(^:S"8;<P9XBm0EFj+cm[=oKMpbF>A10)cc<+PQd"Lu]L!<g=g~>endstream
endobj
xref
0 22
0000000000 65535 f
0000000061 00000 n
0000000132 00000 n
0000000239 00000 n
0000000351 00000 n
0000000456 00000 n
0000000533 00000 n
0000000738 00000 n
0000000943 00000 n
0000001058 00000 n
0000001263 00000 n
0000001469 00000 n
0000001675 00000 n
0000001881 00000 n
0000001951 00000 n
0000002243 00000 n
0000002336 00000 n
0000004489 00000 n
0000006671 00000 n
0000009035 00000 n
0000011269 00000 n
0000013580 00000 n
trailer
<<
/ID
[<34b5a86e4dd3638aeb863340f86aca3d><34b5a86e4dd3638aeb863340f86aca3d>]
% ReportLab generated PDF document -- digest (opensource)
/Info 14 0 R
/Root 13 0 R
/Size 22
>>
startxref
14057
%%EOF

View File

@@ -0,0 +1,213 @@
# Les 15 — Docenttekst (normaal)
**Fysieke les, 3 uur. Voor uitgebreide voorbereiding + context per slide.**
**Voor letterlijk voorleesbaar script: zie `Les15-Docenttekst-Autocue.md`.**
---
## Lesvorm en flow
| Onderdeel | Indeling |
|-----------|----------|
| Theorie + 4 live demos | ±1 uur klassikaal |
| Pauze | 15 minuten |
| Hands-on (Polderfest deploy via 3 chats) | ±1 uur basis |
| Extra werk (eigen feature toevoegen) | ±30 min als tijd over |
**Focus:** koppelen + gebruiken. Climax: drie chats die GitHub, Polderfest+Supabase, en Vercel MCP samen aan het werk zetten.
---
## Voorbereiding — een week vooraf
**Studenten checklist (stuur via Slack):**
- Supabase project draait nog (uit Les 11/12)
- Cursor + Pro Student actief
- Node ≥ 18
- Personal Access Tokens **vooraf** aangemaakt:
- GitHub PAT (fine-grained)
- Supabase Personal Access Token (account-level)
- Vercel account (geen token vooraf — OAuth bij eerste gebruik)
**Jouw eigen voorbereiding:**
- `~/.cursor/mcp.json` met VIER servers vooraf werkend (github + supabase + vercel + polderfest)
- Vercel OAuth-flow vooraf doorlopen (anders kost dat live tijd)
- Polderfest MCP finished `npm install` + `.env.local` gedaan
- Lokaal `npm run dev` getest — `localhost:3000` werkt
- Eén git commit gedaan in polderfest-mcp folder (klaar voor Chat 1)
- Backup productie-URL op Vercel klaar als demo-fallback
---
## Voorbereiding — dag van de les
**30 minuten vooraf:**
- Cursor open met 4 MCP-indicators groen
- Twee terminals: één in polderfest-mcp folder met `npm run dev`, één leeg
- Browser tabs: github.com (logged in), Supabase dashboard, Vercel dashboard
- Slides in fullscreen
**Backup scenario:**
- GitHub PAT verlopen → tweede PAT klaar
- Vercel MCP OAuth weigert → val terug op `vercel link` + `vercel --prod` manueel
- MCP-indicator komt niet → herstart Cursor LIVE (laat zien hoe)
- Een chat raakt verstrikt → ALTIJD nieuwe chat openen, niet doorgaan in vervuilde context
---
## Per slide — extra context bij voorbereiding
### Slide 1 — Title
**Doel:** verwachtingen zetten.
Vandaag is anders dan voorgaande lessen: minder code, meer koppelen. Climax in drie chats.
### Slide 2 — Vandaag
**Doel:** roadmap zetten — drie blokken + climax expliciet noemen.
Maak vooraf duidelijk dat de drie chats het hoofdmoment zijn — daar werken we naartoe.
### Slide 3-5 — Wat is MCP
**Doel:** basis-terminologie + waarom.
Niet te diep. Drie rollen + drie primitives noemen, doorgaan.
### Slide 6 — Ecosysteem
**Doel:** context dat het breed is.
Niet alle servers doornemen. Een paar herkenbare voor de klas (GitHub, Slack).
### Slide 7 — Vier MCPs vandaag
**Doel:** zet het demo-blok op.
Belangrijk: noem dat alle vier via HTTP gaan, met dezelfde patroon. Vercel doet OAuth ipv Bearer — uitleggen dat dat veiliger is.
### Slide 8 — De mcp.json
**Doel:** de complete config in één oogopslag.
Echt de hele JSON tonen. Studenten krijgen 'm als template in lesbestanden. Wijs op:
- `type: http` — moderne syntax
- `headers.Authorization` voor Bearer-based
- `vercel` heeft GEEN header — OAuth doet de rest
### Slide 9 — Tokens regelen
**Doel:** student weet hoe hij elk token krijgt.
**Belangrijkste valkuilen:**
- GitHub PAT: fine-grained, niet classic. Beperkte permissies geven.
- Supabase: ACCOUNT-level token, NIET project keys. `project_ref` uit project URL halen.
- Vercel: geen pre-token. OAuth bij eerste gebruik.
### Slide 10 — Live koppelen
**Doel:** student ziet dat het werkt.
Live doorlopen: open `~/.cursor/mcp.json`, plak, fill tokens, save, Cmd+Q, opnieuw open, indicator groen. Voor Vercel: klik op "needs login", OAuth flow, terug naar Cursor.
### Slide 11 — Polderfest MCP
**Doel:** student weet wat er in de zip zit.
File-tree doorlopen. Benadrukken: één bestand doet het echte werk (`route.ts`). Rest is gewone Next.js.
### Slide 12 — Code-tour
**Doel:** student snapt `server.tool()` patroon.
Eén tool (searchBands) is genoeg. Andere drie volgen hetzelfde patroon.
### Slide 13 — Lokaal draaien
**Doel:** student kan dit straks zelf.
Live: commando's draaien, polderfest in mcp.json toevoegen, herstart Cursor, vraag stellen.
### Slide 14 — De drie chats workflow
**Doel:** climax aankondigen.
Maak expliciet dat het DRIE aparte chats zijn — niet één lange chat. Reden: elke chat heeft één focus, mcp.json kan dan beter de juiste tools aanroepen.
### Slide 15 — Chat 1: GitHub MCP
**Doel:** student ziet GitHub MCP echt aan het werk.
**Demo-aanwijzingen:**
- Nieuwe chat (Cmd+L, of via "+" knop)
- Plak de exacte prompt van de slide
- Wacht tot Cursor tool-call `create_repository` doet
- Toon github.com om te bevestigen
- Cursor draait daarna `git remote add` + `git push` automatisch in terminal
**Backup:** als GitHub MCP traag is — gewoon `gh repo create` zelf doen, maar wel commentaar geven dat MCP dit ook had gekund.
### Slide 16 — Chat 2: Polderfest features
**Doel:** zien dat Supabase MCP + Cursor + Polderfest MCP samenwerken in één chat.
**Demo-aanwijzingen:**
- VOLLEDIG nieuwe chat (Cmd+L) — niet vorige chat hergebruiken
- Plak prompt — Cursor zal eerst Supabase MCP aanroepen (vraagt bevestiging)
- Daarna genereert Cursor de tools in route.ts
- Accept all → save → `git add . && git commit -m "feat: reviews"` + push
- Test in DEZELFDE chat met de twee review-vragen — die gaan via Polderfest MCP
**Belangrijke uitleg na demo:** drie verschillende MCPs samen aan het werk in één chat. Cursor kiest de juiste.
### Slide 17 — Chat 3: Vercel MCP
**Doel:** zien dat Vercel MCP een complete deploy doet.
**Demo-aanwijzingen:**
- Nieuwe chat
- Plak prompt met env vars (vooraf in een notitie klaar — niet live tikken want geheim)
- Vercel MCP roept create_project, link_repo, set_env_vars, deploy aan
- Wacht op productie-URL (~45s)
- Open URL in browser om te bevestigen
- Update polderfest in mcp.json naar productie-URL → herstart Cursor → vraag in chat
**Belangrijke afsluiting:** "Drie chats, vier MCPs, geen terminal-commando's zelf getypt voor github, supabase of vercel."
### Slide 18 — Lesopdracht
**Doel:** overgang naar zelf doen.
Lees de acht stappen voor. Geen deliverables. Wijs op: een mcp.json template staat in lesbestanden — invullen met eigen tokens.
### Slide 19 — Extra werk
**Doel:** snel-werkers bezig houden.
Hetzelfde patroon als Chat 2 uit jouw demo. Studenten kunnen letterlijk de demo kopiëren met een eigen feature-idee.
### Slide 20 — Vooruitblik
**Doel:** voorbereiding Les 16.
---
## Veelvoorkomende vragen + antwoorden
**"Werkt OAuth Vercel altijd?"**
Bij eerste login wel. Als token verloopt verschijnt indicator rood — opnieuw klikken op "needs login".
**"Moet Polderfest in dezelfde mcp.json staan als de andere drie?"**
Ja — zo hebben we 4 indicators tegelijk groen.
**"Wat als Supabase MCP read-only is — kan hij dan toch tabellen maken?"**
Nee, dan zal hij dat weigeren. Zet `read_only=true` op `false` als je migrations wilt doen via MCP. Voor demo: dat is wat we doen.
**"Kan dit ook in Claude Desktop?"**
Ja — `~/Library/Application Support/Claude/claude_desktop_config.json` met dezelfde structuur.
**"Wat is het verschil tussen Bearer + OAuth?"**
Bearer = vooraf token in config. OAuth = browser-flow bij eerste gebruik, token wordt opgeslagen. Vercel doet OAuth om veiligheidsredenen.
---
## Foutmodi tijdens de les
| Probleem | Snelle fix |
|----------|-----------|
| MCP-indicator komt niet | `Cmd+Q` Cursor en opnieuw openen |
| mcp.json werkt niet | Valideer JSON op jsonlint.com — één komma fout = ALLE servers vallen uit |
| GitHub MCP geeft 401 | PAT verlopen of geen rechten |
| Supabase MCP geeft 403 | Token is project-key — heb account-level nodig, OF read_only blokkeert write |
| Vercel "Needs login" blijft hangen | Browser pop-up geblokkeerd? Manueel naar mcp.vercel.com |
| Vercel MCP kan geen project maken | Vercel account permissies — check je teamselect |
| Productie-URL geeft 500 | Env vars niet (goed) gezet — `vercel logs --follow` |
---
## Vooruitblik Les 16
Volgende les: RAG met embeddings. PDF Q&A app from scratch. Studenten moeten Polderfest MCP live hebben op Vercel + vier MCP-servers gekoppeld in Cursor.

View File

@@ -0,0 +1,162 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/PageMode /UseNone /Pages 13 0 R /Type /Catalog
>>
endobj
12 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616185725+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616185725+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 15 Docenttekst) /Trapped /False
>>
endobj
13 0 obj
<<
/Count 5 /Kids [ 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages
>>
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1796
>>
stream
Gb!;d94-FC&ARfck30agd-<3##plbsD.mlKq6H=+[VSP$C0-/Nd*LQO4`'-p#$P1A+Bt;*f?X;,Af6tXZ0Hh'!IX+Kksks%K)Q%C@lViMKT#iOT-p_6oIRlfXcS*PGt-_\%]uMXL?2RPj+:\EVB&iJKXUl2@XLL)fi!b6rJX-H^]H+)WW[Dr;$(fI?s,\k+sP6r_O4@WkiAJZ3)9cthEZPtrO5suhs&jgO;F@$$JgoGKk4eU(01`#[;a#uIZ#q&55pWMc4Em<n%a[`/.B$#U4>!r@iQ8O`!?`s0LYk"aiDb^8E:-H'[3f^R1b3NWEHd9'%)9m5qB9?nP#&R/CJ&iX&u6TEl`ORg=VNbOj)>nr(O>+@(-Qc=3U'6H)^PP<$Eg>aPop&`XuPK"2f:E=*ndCJR^8<rdfK(d"VVa0M9=O?L;"$&aiO**[Nr#lRW^>J/LWa#1+TW"iBIT1"B>m'7.8]N6q5!c\B\kZcKTsjQcqEi(J0Ipf>Mhm\N\#.M^Rjm*Rmb41b#8O0OKQdl!Jp&R(3-KT6]ul+[dX8hmtJDH=-H=9d`5W<'&eoHu$oMU3t8O<t?g(c*<Wd%FN'YWTpIQGG-6;C41lo%bfZE;aOn'eb'r"5Fhn_NHIl9831*SeO&Nn2&C#fb6@hc':iALEe(sM/f,E+0\9q+[IsD9&@IX?%O#E/$jt8-@:.!C#9Qn`LSJ<ggVu_nX,8X<ZkHiKn0lH&4fS<'HAaeg86bHGUc!*dhp")Mm^Mb]=u_NoYb-'@+gc6Q6/(E"2h%0,inq'l,7Q"[`t"P'qFnJL$L3[G@H5@#);;>Rd:+ga7kl&:V^Ro2gMh8XYV](@nL(nJk"rZR:1!KH(&:AmL3PFRapOd9V4Kpl>nt5#HM_0?C,_.gP##P*o139@1'c@?`d.lBCpfI*%%WLIJTpo4$NdI*R?I-D]eJ)'eBZ_$`c5\b#25.OYjgAXQp69*MK_%h:$$$rqd$^9*K'0WYN$EAPD3AA4@5kAnb/nDQ._%kUa;?Gr-JQ3I;V4/Uq&c9ha8k_U\J_(c*8sb2&?lR.'LodUn6W(a7P#Yetp!)h)NG%d&J4@RNacE1SK!"`7J!3Xr#K+a2#0"!%p6j!2J(3OXZ4L;\hGk@Gg9e*4_L+eXZ&i/pWNIkV)b5I-i-NaXG@O#$&\&MKV/B1&i`fc)?i]V0^9@(#=6SCOaCD2'VFZX",iKs[C2:Z6^9Tp3n?"L`?O]3RdnFeg>m*@(7I1US;+!jI<DF2LW$UGfli,&m4h:\d%DEpSbn::L"+\lp`m:"D+_C$.sLYrmn3RB'YO$Tp8+OgQiUm#u7&8pt1]ff%F3;H8J&!F47'D_tQj@$jWeRMJe0gL]`UIW&k0\\Zb<l;pj>6JWL8Rllb1UWe!m*W7W`bC2"JG@Am+VaaWcQd[LS)cI*q/\oK,@Cu>UnJrkje-=*>?Zd$;H*$a&;%q+50$#sA]<J49UC6[)PB\e0?U;)7@_E-1St<Js:W3+b6-SZMf$gCiITk&]>h-`^SfnSIru.ogC8RcG*XJt`3Pft+9I6<DOQ9=01u/M8mM<3]q8,Zk`4(TD(fRR6T@LGA3pfi/@=4u6MDD*gGd5/&V0uUBk2s7t;%j;=1YXb2Xee''ea'B/(B`E'3=qoM,dNAHFVmqGXW^8s^^<Q]=%^#+b'?kG,+R,@$LDo%e.4CnQ#f+>`22nlH[gFX">OhmlIoEi180bbH6^S=+g!`<N&'-H4>4)9Nml3_97hY%S<!C^(0h*Z4s5<!ReB+NN>4XVm+2J&qXgcglcj_d-[u6>>aX+~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1725
>>
stream
Gb!Sl?$G!^&:Mm.R(#dJi*p2Ta]lKjD,8l_-_0ma(L[hD6+4Fh9[`k8NarD@K\"7c\sj0)!o)/I]eakEj?n]/^AFJ^!%1-Yo@=.2JDgY/cjO9,!U%A6^\@ZFS\<+&#/S*1W#pA+Y7o?*FLgEaX))^b6H?2>Ccifg$M>WM&-`$:#@9@WF9_hkK<G/V/E5Pk7Q0>7B!>OhfsF!7qu$U6?Gl8.]>5;0XZ#G??I/@UIr-j;^UGc^5@",n;HbQ3,o2T;Tt<L[?FG-Anq7m[1h93fZ$#p\](,7\EOj]`K(UlLn((<crS\20(fWLKR*\>WlLoW&;ZJJDPTiOR6#G>$;SpAcF\k&EhRr5.QJ=.9*)op'MY"f`j_jst[5A+sp(P)jcL8Yd[F_`oi^Cm-9ct92-F>OGVUa<lA.GA_+gVSU_JODAJubn^A#9i9VPdqG<8)r-W"KfF:O\&:DZo&g3*Zc]U:*?CWJ&WT_C%+tbG.4q@&Y&AB2(74[IkcR:099P9$dk[9Q>?be\ultlG_`^<i#`Sk$9j!bMsbi;$eUNM\HRAR1](c8'/<O!esYs7CU?30W&0cVCh@t`#:f]=(Vdua>6fXq7+Y:Yi8br<!5*9DdI?,GVOQHH#U:\mWQ3A1>TH)B17M&?FMgVW/`Qq\Uf@m$XfU$dY8?BM[Fk%rp.*0=N1d)E=237`6lD:QHsVm,+4TUMQrSSRB)>,ZPR"lHng$KiQM@/h-dpneGq.(2>F/8.V$]QF4t54E@Tt0aMQl;Bs-+%IcPXhW+WelqM)4qIm\;<UO2'3lVp\BF\)a;gB%JP+QrnFWn>a1)"VO1P>bsjaAZD]D^\Ypq8'XZIVm9g^Y>M-FF_E4_FN4KAOC><rd+T`YeL-\2%-!RZSRHtBL=J0ar?X?%J`enFGIkDiAIj8+$O)s=[DqM'e*W1E"2F]Ke0VeL#^#S(ni-MCFkKrRRu6^-8NOU[>S_;TI`s%M/Xqhc>IJUVM`t+X.'qI7Q[J$W$tt2e\HbZg,4^ZZ8?4UK"fq[LWW`?fo/*TKVX>1DbIn[S'Qf,]G]7jpuPJ),[#03Z3d<A_'SmLN9DTO_):4CW==]hS><\@X/n?h0E%P%JdS]eDNHOeo[ZQL?oKOSNg]=DHaQT*aF4J&W`'ANLcW/FS)saSg\%csBj)Vn&)i7nOa.UF_VjO'I^5YWLcUQm#A9>f-&$:]k3+?2cai"UlKRU?CY`'P-FWa/N]E#4`m,6s^MAIhs4W9:&FQ!lA^.K2DaSqH@h)4X9a!!18,nZ'7f382RWGC66q6,j+)LCBg?b9>ll%[6/lcb';YR>n;lYTSRR.2&8u%"ck(`Dd)=0$KQ';,E`bB1b)_,\9`4IAe%Vl,Eb[oP/.lBm-;"4dM8@3h1F]O$E'q$9-4E=$T_T8uC\"Lr*K6P5l-.jJY\RUm=mnc[`]TO-Y-'>l'1QZ^d*bKXWLD(r]Pll9q`-u9EOj%tN:8XHH._2d9:U>TEUKpAT>XuRI6CsFu[?fRnb#VPr46PJ.]6PjP\A!1(1Y[>Z%],uFA>oaURXioT]KE7@WF*!_9W8\W%p,AfJWYZ2kUTT.<'?)L)+BI[n;j(@7?_2Eh2L^_p?YJ6OiAS[Xltmcr*!#-2`U^P3/b]<fnU[L`l,EYnEs5.S*qXE_:4imK67uV3H^u?S"RM4L>Dj,#0(;%3^mXBDU1q*<;_ao^.>.,;h6M&[5bP\l/Un<@m/bdh@E.]]ta~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1762
>>
stream
Gb!#\gN)%,&:O:Sm.dDL,La<hroRG5bW(WH.-ZY%3Y8ir+Ar=MJ<JrJIX;kV,rq]2;lScr\p5P&=I_O$p4*Y*oPjAFIh$E]K[Vji3%bUei3]R$%'SW)GkC`m;dS/F=Im1\GG+94<j-ALBH6tek<I/ZBJkF5ceqjDpV8@M=Dl1H[b>,74ko;7@.c\S=3fd+"/?Fi1o7gQ1(U?=Ek:(.0V#uKG;K7hH>M1%*38Hd4_M0%FhV=Ndl1SnX6#XC)+%had`4>>IC#1C9G&WjhHjLDd(_+s#p\'h_M>Hk:E^:J+[D-fF=")N>Ocjk'o$nt!JZn=+L(gT)j+0]82!KoF3J@sIM((+7$oB[-H$abSNs:Zl1?#$Z5YIk0t]+B`e+V'8nS/O)O<q89Ei:tcN)u2[##59a_.<UC4t1&4N(p>E#kC;A'C*tQAUnr=Kjd-2utee\a5dA*P;&Uo_*Saa7%8V:SZos5P\Nds2`3!HSukZKI;!W8k'mg',"'^nmeO2pFSAF'P0\JdDqd+GGlY&"X^k17ceL%@"$eU&_;5dYb/7dNL@WaS6]BIpi8/QM.p%2_9=7]]Xo(K.`DgE%2"%f(6n/la[N!&oe2/c$LopTf;$*.\qVYlT0_h_"`[>PI*Dh75e3f09dQCl5ZsrUND3]R1^+ZS=Cs[qDK_oZ[X7_!'p'Wc-1I?V&0d76ob=XdZ%&&@hOC5H(o\Vr3M_P4+a\(8[jrN+h.IRrDa"_!`Zc_>"7ohBnNWq;T1L?il.f(:R1dX8(7L?)/8C>=`+=(i-7H=<-="cIf,pJ=0hFjCbSOpo:BKr?/`^YrNDKu"Pckc.XCr<e4JDjqS0/"C@M/1l\66+O&rU<71s.A&KJ<cLH&-SSZO]eWVkDJ?LoK3+W3sP?MqdD>C%ZonQh#%*,VG5Xi/)FnpH59R*D3_#DQU,aA^h[aRjVD!"'V)sOg%)_;.MXEo%K+L,qp[A=:u=FaH.JA.B.rn5)^E71mB35BPFlfkKtdf4rMSTkrMJ"+PbnG-DK)EX*85i7En"D_hAAT!+rN+&C+'m<@VFVY#Mbp8`nRO6Gt+cY;/.M=pjdf1KoS%p%NH3>Q*Q]TRdE%nu\!FIsOkC:nq)\1)#54"_S-A(%Fk9Z=Y;XY74Y&0qJs3H\C,;*BM_Wfg8*F=A/`N`p<7.YI.<8s(f=n7`A-B6>=Oc"U:b`d!58FB@6@5!Vg+fVppR\e-ZFq$6),R%9bV"au:q$Z0$qCnH2JM;SAR(TWoY/O7AEGBG7rD*]g:&,,pnTj8:@T$C4"JVWBo^OkZq7SpMa7O85=%!Fn\HW,m^$OE'$_Ea"i`8lFP&('39g1%ud$P06YiY(3@5Yn\teI$PIsj_'1)\=3S#EKQCH^aMF<lABN&1\0\fM=<=O>e>PE<H:,Vdj_6ERk?VB\1s(716_fmCdVA:%enmLBpV/)2WP:>4>#+Kr2tHeP'lF:2mMF,eKD8CPr6?t^,tKGH'Z1_\N$8dQ[Eu&M$%lqdJ]T9?@VEBFLUp`LSF3tl/RaX^*W5<%HE5IaU_0T_&46-5)(HtC"\+\5PMQI0=T!H>'oZ,QL)@8:V:2O\ITh%S8?5tjj$emEd;,Ds2U[l4^IHIbZ']?X%&?^d#$*4\?kS)I#1NASbQ-W3$!"Q<@ZPOh-]NgaI_W;P2h.UlY/8cW:9sQ.I1JK:ocPBl'Ju<Z"L)["Qq,O1d36"<_5(6*@0khm@"2E-,Zo.*KXlJ>.Kl/>0NDsb+BT9Us0peKZ-%@N53iUN/Y#(~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1786
>>
stream
Gb!;d?$"^h'Rf_Z\Dr@/1e'bQI/0m!:=:$63&O9>3m;gaF:eo/Y.@e&j+mX-hCJ,-@k^Xp[kY<GQ<&A,gj@=0].VUsjVGUd$,E#O]RK0ulToFlDVtZElb[6%jKB5@EG*11)Ju\F6IYOr<kT9q34_cch:8je(A&4Xc@]K=ot[^"iuE"-D'Gpk&]8=ST"fs8Un"^"V#hjdi177Do)\c4E<Xo!&V80!ms.W`E3?U#`?9Xj-lR`S<5[(UYdjAI>Du&`C/ei-#[q,$0ar2fTX+JM_AT@_;"&%qKYeK<LXLe*;](FSm[3,dFI)Y4*$=G<!$mPIYQ9U@qYUNkhbA2/Xan5;?j[7j>7=`t3gLq@gF--P0rDInF1BY<Lh;=.Kb5mp2jbTs<\rBnWSl/lFYq+mBtBS.J?c]2;_\h$(8/?mMqhjsclX3Xns)l[giG;N)C?3krOU0:$APpl6]eE/2N_8M\-pG_=hk3**DCQT#W7s4>H&fGk;t&eC!$bJNQpak'pm+sehAdNm]iMjbuK]3Y8Pj+nQRlc:!pBkbPl+pICInb)5Q^^Fk:K@)ftDe_nGs:cQBaSbKd<[5Y92_=QBJYQ&?o8ea:+&$]ZiBQt[e?`UtsS#R;]Cdkt@<,&7d.caWiF>YeQ&<@k#/)P*[I6*@M,Dab(j-G*@/<&d?W8ql$'bZKlV$qN-/.jHO@rl[H4poW]]@m/G-_!$j_.R=]Fd*Ue>&e*20X#5_gZ.8'nU18Bt9aeVfo<!*pf5;aQ*tkkdL-qbqM`>$$OPo!^?G1>SkS@5j^1R04;-SI+HAVtqE8X0)WU^,c?egS'Xd[$SC\g%KZ^JaWG.ON]Y9;)rn\-hB@hueXgj7`eW2)J@IpR<UFtQF)8P!X,i9#iR`E.n1&Q=_c)+oR<BF$CCmPU<43'Zir+EbHm4a%!=Tct^/;hSE0(aC0SK[a,4HXg_;hAHXAE%L#d<ESHU<q\eE'@Kbdg^G9<KhMD>-*K#@Pqe]f1*[PuhC@hKn(8t_\R@i&F81]+807%>ppZt(>!$$LGC,Bn*2M>HH'H<^O\e\\rKStl+k@iN"OCj8r;&?UMtSsT]DZUErdF/^p;k_oi/ool!m;7W)9dcUfE>l)`/(tI<N!3hQ3]^7Gr)`!:9gU\@HE^omp%86/U!(.g$9H'!;!S63Hdi7Z0AhCW$XL2B=)P6^#AA2).[E!#e(bmm;0m:,0Q&7R(eC8R.c7`=%AVg.JM@gIrqYBrX%lChXpd`-L$AFqnPC^QUB:d0RB=a!M'4IlI,:lFoVJgs7%P7`ds5S9a!%))5b\)e&5gpnD1H8g%:8L#9hYfqT088Pt)n\@1h>[Dg_"Ur^MjPQokkdZgHUrg0o'?OV+A1W0tNVS<:<Yh,lLj<$JKNUD59.5@rs898*"Sb1fM)K,j>>+.TH\9t&hmkn0C3&f9a'nYY]]O)AuAV0I2=dqNK_j9L@c4M&/P"@>Me.#W(fG'/=tnUfI<7]*8AXh*gBAo,<lFe]:==U@&t%_Ui_L<(C[Ld?uf"(1+7,7Qag>2Y9&(<M+^^M(8.W'rWULh0No$T#Q?]2Z,,h/V+R=]]:S8^0"PA>S_AZemCAJ,-Q2U@@<+8)^n;T);8j>V1uXaCu(P<^ndoH$E0^h8]2bs#jPg21d"ef'k+=T>[&9NkFD'M9b;'L<"j\b9:$2fJHY:3Qm_u+-V"i"n>;@eZ#?]NGk+#VK^h^*gO_?AX21<A6\*ja3H]Qa<Kf>WQ8e-K301XFSFujB9E_NTF=D4=aqibLB(5Q3I]>RF!Mi'i8e6HqoMV~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1796
>>
stream
GatU3D/\-!&H88.1!57b]bUjf?qa?rQ)%,M]sLKQ/sT1oWl6$KKsfOA?GeWBm^M*?g,LpCJ1bht"3eqE0DiuGK`PJ^r>Xo"B+m88E5D3TP(FW4!DG-)mH!L",I`pH4XV4r@i%U@kX<<S5^N(1j+Z><(kUd<"+TZg1\T'(W9f=V\t,<%-98X+^l=9\cT::G+H!"[C*@XL&qm)j)&D]]=J8ggH%O!kR*9"0Wr_g[Xt>1@nR)IG%pk7%SDl9KXRh!_mWe-&b+NtV("GXgVpehqi!*2?!gq>^T"I7q^272X?4Y?V*"Z,H&VSAQ!@nioS;IZ\>S-BN.NrB&bs,2jj7>S>0+o<^/JTZ_'=d_J&]h99=Kg;\obN-4N4pk%\3N`f`r*)_5kMtX.FEX,W8U@+"BC>E5mn"UTh).$H!EE%8et6;`X9j\7GgsV8/<@G*4KJB4_l7SP;'o='MXH-\5,HA`N1d]_<23IPf2RC86p.8UqV%<SXHo0>h:9kZ3oR.q%Y;g8RE7m47K6N^7\#rE0/FPbBO5[9i20',\L)N/KoU3Hst"_QtTKB(Zc!L7d2BBpkod@lM>:%j8PNfs%9=1J$Mc8n5(@+&:PBOEQtIj4B"pp=]DWb6:2qk:NC&6Fl7ua'GW<g0+C5]n;;*6m'%gleR9:Xl+Hs\R7asn.2Sl9c3:8JX576ld'A%pUE4%,hig\s<D7g*3A!m4\r-XY@\G9<;X'Cbr(1^Chic"&[oaCpcYR\&j=dDlF[r+!]#;1_eKK2ESobU[2Dpn2`uY;(>uak@1Lph-d@t3OD7n@(]E1Ht/8a'rZJ,T;db?:>._2QUoi!#80]hmY?S/<i3)qhC]P&muLk;U(AVQI(bj;DK1,6/`(mR,?jaq,$j@Q203;]p<njCG._P'QQG(IK&'6rf8\WF5EA\u7p3u4.'"BIT]%`9'8Ijg,ikg;mdj>p-pNhB1NM'pbZaf)c$11@Oa`Yf<)..lKWIB=4eq^m/Is'^0mmPR,\)V#n@_:C;iYj4FnPMJg]B>n[6FsGZgFpRNWQFh@;jA[8a)ti!Jo7G=,#Jmn`^Ffqi6d#G^:`*=![9j`iO165,edg)Bs*hW0&u1$?]&&4:eNV<CkF^_G<Y[(<iGY%a\sjA?h$EiM``B\jc7?NA*9+HX]O?C>FPc!Tor!j_`Y^L`(YX`1/6f9jonT\q'dflL5t]1Qf]2VpngtE$(k.&_ZW"-k48]`@_9b5ja7\ooeBZeW1"?gDgjN:/eZ7.jZW*2:6)i6bW))E@hOU@]D2PtdkLY1]4apVk8]./8PY04Uf)5c$D_a[Q%BqgXFeS[6QUHN2d7X*65$^E4c!`(Im;o4>]IL(((Xh9@.7h@U`ud5u5;L(<"F8"ECfdPUlIO6`6gddnZ<rrQb?Gh*C\Y]&)@]liP[3XAeu:+o/p+hBM9]YZlpeDbD-EBnBFhTpc*Y$6&lm5XMIX8A`A<GhAr"dTnd5P'hh>jY54T#>ArcgS^Ke5?V2;G^9V_Kn=Ao#o&"?K"\%PIEEr1fSgmPVU:6Q\sM[K=Z<!o(\)lClQGYbO=OlJonkL2ig7T&*Ui/EHpahX6e8'VZH*TcJY<+k%p\@*&`leqBSJJ4'"Fa$PL0+&_Dj?FhB*-;6XIR^)tJdsg6irUj4>].'$[X7WPH11cq<QO")$Je78fuHCIcnFA$krZM@@6VXUiC-d-E:r>p5$Jl;?VZ(Q<gJgs,-NJnrAU<.E33j1/%"!`BiC_[`ZpN.AAOW,HZ/eS6;6#il#U$sko:Re?aF$m$X&cD'"bJRc@3:`^sRYG>7/g~>endstream
endobj
xref
0 19
0000000000 65535 f
0000000061 00000 n
0000000122 00000 n
0000000229 00000 n
0000000341 00000 n
0000000446 00000 n
0000000523 00000 n
0000000728 00000 n
0000000933 00000 n
0000001138 00000 n
0000001343 00000 n
0000001549 00000 n
0000001619 00000 n
0000001915 00000 n
0000002000 00000 n
0000003888 00000 n
0000005705 00000 n
0000007559 00000 n
0000009437 00000 n
trailer
<<
/ID
[<b8b153d24f81055630bb0ab884390705><b8b153d24f81055630bb0ab884390705>]
% ReportLab generated PDF document -- digest (opensource)
/Info 12 0 R
/Root 11 0 R
/Size 19
>>
startxref
11325
%%EOF

147
Les15-MCP/Les15-Huiswerk.md Normal file
View File

@@ -0,0 +1,147 @@
# Les 15 — Huiswerk
## Voeg een eigen tool toe aan je Polderfest MCP server
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Werkvorm:** take-home (±12 uur)
**Deadline:** vóór Les 16
---
## Doel
Je breidt je Polderfest MCP server uit met minimaal één eigen tool. Daarmee laat je zien dat je het patroon van `server.tool()` zelf kunt toepassen — en je geeft Cursor een nieuwe mogelijkheid die er niet eerder was.
---
## Wat je gaat doen
### Stap 1 — Bedenk een tool
Kies één van de voorbeelden hieronder, of bedenk er zelf één.
#### Voorbeeld A — `findBandsByVibe`
Input: een vibe-omschrijving (string, bv. "donker en stoer" of "vrolijk zonnig").
Output: 5 bands die bij die vibe passen.
Implementatie-hint: gebruik `searchBands` onderliggend met een keyword-mapping, of vraag de AI om de vibe te interpreteren als genre.
#### Voorbeeld B — `comparePerformances`
Input: twee dagen (`day1`, `day2`).
Output: vergelijking — hoeveel bands per stage, populairste genres, schema-conflicten.
#### Voorbeeld C — `recommendForGenres`
Input: een lijst favoriete genres (string array).
Output: aanbevolen bands die in die genres vallen, gegroepeerd per dag.
#### Voorbeeld D — `getBandDetails`
Input: bandnaam (string).
Output: alle info over die band — stage, dag, tijd, genre, eventueel duurtijd-berekening.
#### Voorbeeld E — `searchByTimeSlot`
Input: dag + tijdsblok (`day`, `from`, `to`).
Output: welke bands spelen er in dat blok.
### Stap 2 — Implementeer
Open `app/api/mcp/route.ts` in je Polderfest MCP folder. Voeg je tool toe onder de bestaande tools.
**Template:**
```typescript
server.tool(
"naamVanTool",
"Korte beschrijving — wat doet hij. Goede beschrijvingen helpen de AI te beslissen wanneer hij de tool aanroept.",
{
// Zod input-schema
arg1: z.string(),
arg2: z.enum(["optie1", "optie2"]).optional(),
},
async ({ arg1, arg2 }) => {
// Jouw logica — meestal een Supabase-query
const { data } = await supabase.from("bands")./* ... */;
return {
content: [
{ type: "text", text: JSON.stringify(data, null, 2) },
],
};
},
);
```
### Stap 3 — Lokaal testen
```bash
npm run dev
```
In Cursor: stel een vraag die je nieuwe tool zou moeten aanroepen. Check dat:
- De MCP-indicator je tool laat zien
- Cursor de tool aanroept (zie tool-call output in chat)
- De output zinvol is
### Stap 4 — Pushen + deployen
```bash
git add .
git commit -m "feat: <jouw tool-naam>"
git push
```
**Vercel deploy't automatisch** zodra je naar `main` pusht. Wacht ±45 seconden.
### Stap 5 — Test op productie
In Cursor (mcp.json wijst al naar productie-URL), stel dezelfde vraag. Check dat het ook live werkt.
---
## Op te leveren (via Teams)
1. **GitHub repo URL** met je nieuwe tool
2. **Korte beschrijving** (35 zinnen):
- Welke tool heb je gebouwd?
- Welke probleem lost hij op?
- Met welke prompt heb je hem in Cursor getest?
3. **Twee screenshots:**
- Cursor chat waar je tool aangeroepen wordt (zichtbaar in tool-call output)
- De output die Cursor met je tool gaf
**Deadline:** vóór Les 16 (volgende week).
---
## Beoordeling
| Criterium | Punten |
|-----------|--------|
| Tool is gedefinieerd met juiste `server.tool()` signature | 25% |
| Input-schema gebruikt Zod correct (geen `any`) | 15% |
| Tool returnt valide MCP-output (`{ content: [...] }`) | 15% |
| Tool werkt lokaal én op productie | 25% |
| Beschrijving + screenshots compleet | 20% |
---
## Tips
- **Hou de tool focused.** Eén ding goed doen is beter dan vijf dingen half.
- **Schrijf een goede description.** De AI leest die om te beslissen wanneer hij hem aanroept. Zwakke beschrijving = tool wordt niet gebruikt.
- **Test met meerdere prompts.** Sommige tools worden alleen aangeroepen bij heel specifieke woorden — probeer variaties.
- **Gebruik Cursor om Cursor te bouwen.** Vraag in een aparte Cursor chat: *"Schrijf een MCP tool voor X gebaseerd op de patronen in @file:app/api/mcp/route.ts"*. Meta!
---
## Vooruitblik
In Les 16 (RAG + Embeddings) ga je verder met je Supabase project, dus zorg dat:
- Je MCP server live blijft staan
- Je huiswerk-tool werkt (we komen erop terug)
- Je `.cursor/mcp.json` correct wijst naar productie
Veel succes!

View File

@@ -0,0 +1,118 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 11 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 10 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 12 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 10 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 10 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/PageMode /UseNone /Pages 10 0 R /Type /Catalog
>>
endobj
9 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616091046+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616091046+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 15 Huiswerk) /Trapped /False
>>
endobj
10 0 obj
<<
/Count 3 /Kids [ 5 0 R 6 0 R 7 0 R ] /Type /Pages
>>
endobj
11 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1647
>>
stream
Gau0C?$G!l&:O;VR,]RLJrDmE]\.;Q\4u;]KK:bKA3kiM,OMM3PrR'mn*EXPCiIaX)e0#@*UV)-HfmaK%-cm-lQQG@T#0T9(L.KJ95jt\JE]RK2a.'4b\\mUc\X"*`.a<i3TG&qd!`:fG`aol6?)kmd%>R@9RQF:.QO7eh:>:'du7OVi/Wd)cT:9<_ue^*AtCr5%j`keW7Db=I=UQ8`r"Zc3Ym9.FSm6g9a!rqU/B8U]Jd]TM$Y:35b?>&9:=33JM@09#bPWGUKV-#iQo!*TKlMN3dl\&+7DYn,Ri]r,7]ZV)pQ_tj*8h`8gl$pIUde.cKoifY&4JG7DT@k1#-/jWQ[.4)B-N\7_QDr736N4*,b;uh>JpQO9l\\Pr\"l>2`q5\A]8YWKJ_mgd/8(%TY>PR.Y&tjLR`rj?ZqKfu4_l]o?ok`@2UJ\5>-T,N$-K=HPul0_IbF*-a-[+rjB^K@e9*7Ug[)UsS#npgdUd1]%Z:c>XJVJdG+0/a)OVc@8?O.j@a'W&c?;L(*=k"j\#Y@HN?JH1-[CJK)U.L'T5<G-c+WQK%c<MJgb.<`4hBl%UNAMUNTW@iK$`,3_%>q-1`$&00_]R(kW=o,FQ"=I5gR8tMOf"o5N>O#Dt1`2/3tV(/:Hfd8\E)ha0gR.?%*MG=X98"aA?OsV+_mh3mbf\<XIl+i-7EdW?V]dT(PijK&pQ-#@2f#0fo9,+;A$R1)X]3BW/rqY>rd!?4="Wpt;T/Ff+g#@C=0oEBXer\B)OPi\Q4"VNH]Oe\*E:4<48\:1Y;+upF8ZCj.Ug4!ON_0Z<YRba\o-QLu$`1PC".t/-rW$>T$^eOBpAHm7\>iF']&Y'LAg/k1+7K"F^RgJ+V^g-r\%9d*!-]sa;STIn<TJ'@1TJEO5?(UdWMUG7d?d&@.gO@0.Fs.hR22#dlJ)Ab?7BS$#/3YPG]H![E8[nKG1.#bjs2jGk5<&CDNE7&cCiJ+%[d.PB^^o;BjXA'fkj21aRggV3HY]^.(09AWoWIjFbQg6atdp4;lk@ESks-ZD;d:LXmpBU<BS?)Sm/-!PY*Zr(]dt!K$<H%dpm.rM?-_[OKPqq9+)(t.-q"h+8uE=>rV=Ya=Ls25>e\`I`oY2M_5V^qQHTjQD!)2O,04g)meCB>%73h53J\j5TX'Rc`QmE8KFQYmoJ6+^ai!?$`8n[F&I4U.>0nG1+uSG'ob.02+>8+OYR%q2HWMT9_h#!;Qr+Z)-,^66Q;"Z1*_Lnn"om*7b`JO0DM.H#9c]l_333%7]g]h='Ih<.E*A&H(\;`Ng3<&hHbin_L==#="L30*TdYTMAj$>_j2V^8f-!jd8*%+VEm@2D$n5kC<"^TLqAl?onXR*"CkSmmh94<2*u=46(/,)<aN)8")ao/d.K]rLU^]Dh$;('ThF08R(\k6BO`fb;h8,I+?(>!G"4bP9:UV2>m#:LRNJFi_p>=3>lYDQQO6[<[4W%rn>jO/R&=tQ]JuIb9hQe=doKY$YC:=B?"9'R7?/^ELh'gfdjDRW*;r]+HtV=-C3nS\Md;oEh;O8$rA66Q3?C?q;#@q:3PWcRq5'7N7RnPDE.7-kH>.ji@%Y)G\UEPIrSFpO3LNdiMX.Z&6[q7_L#['gXS4R!VasoFh)qP%r4q!8qb1\p~>endstream
endobj
12 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1825
>>
stream
Gb!#\D,]1Y&H;*)Z/[LF.3*Q0GM8kAVG3L9D3Dn5AfBdK+WjfUDb)6SaRJ*!-eXZ=Z"7U'e7L8r[(Eg9cQ3KCITc=__uN"ri:&/t`aNlhbTnN/?G\@Z_>/U5.;_grO*_9dl4Qid#PeRW;<HO?g8djUO$e@W8-UE.KNktS5s-hALUaMM[VO:+FK&kgQV<ar6oL_cFh0*kMC'/sCYUV#i>GB:W`cZb9hil8$g37K'/^[b.k%EGL^&etY1n4r>@Fnp.i?W6R0XlQ?'kJk2=567Qjlgad$OTecDfT9J6.#c&pNSW>[Ftq,9CLQ#5fVKZX=E`W0$gAk!Z%[jH%;7RDWQR'-!ddUn1`Z!=TVbiccT7+R<ur4"GAX@UTsPXM:UBP&Mt1@$ZT>7@9Tscaa,o6GYLD$>/7e^p@!8Dc4[[AhqNuJhMS#%^hBH/.,Xn&4n'moPeVUH1&h.:Hd49=lnh0)W:)rr_B@&-<[;X:b*EA*XNV&!'4^:60+s1#d-^lE\tfYq;XZbR2AP_H!L_]99;^_qkF\>1fL)Y_gSKPq!b%C-MfVp7't,Aga/P%Y>i1&,A.L[%UJ<DMT[7FJ+T_GCqnC.Y5L(;HJIglC(Y&^B%OXJdddI6oJ?tESIi&jKb=tgN:5LV'??i/#Nqr-0=M>`$nRT#ci?A#'FI%?<%%e!n-Hsd#Se%HRH,Vhd@J0bO\CZVHLPka#U-ErptbY'YX]OrBc,)a\Q!.3oGMRo`&(bd2f+b68g`g/5&=l>*25B&*ELBp:m[S4Jt\A--qjhn3J&&c?i@6'[IE8*+)0d0+Xs1"+#jJ(0A*-jCr:6S4+6JS0FZqXS@:'$4oXKoA%Ctd4/Up#Wi>r:=8&-<E()_P's=&OZ5EE?T3ppH\RMF4A^014Nl>'We#J[nhC=Y=;5`O!oLrXn.SNU*ged!R2:*qHKjOT?n7M/BYTa!bLA8EEC=k,4iP!5d]JM$kpU&nQ>1NcX=#]tI(.2DC+B\8J!OE^=@hs$^s#,NN[A*?4;MG'p!`-tO8Fn(A@+Ur_OUH.9dnmnd65%H36@q['\4TDM&\jDsLa5bB65>&!1Du_Ic?$?I<@iKrX+BBgl:S,/.j$X;n[1pqK@?3#F2tY<(j0-sY:n#0f)!U12C`Dp@PR#Nc<b^8jj+QgJ+EFY9W$J"<LR^l<Gm]tir2rfV3GqPU(:mD]@WV?X7J`?-C%TS$r@4jBNqlf&;V`dZrB"Mi"uL29IPD'c0>NjqS"[iRWB2j$a_rGf!]n2W@o1FlPAW4DpN%OSmRi'$qp(o$q1r.Qb0V<S/1D(pXNKTAJ(7`HD=>A/tj7L9rHRr<^_oOc&1rCbsTslRhaWaU[;@ugrODm$)]b<M<P_q*@:s7';TW,Q\--0-PDn!Hp?6C@o[1<D";h\q^eH&AXG?':r5rXUriEaBj"M+>Ci>[K$cZJ#DaLF;LX6fQR[uK$LoB=mFrE.#a&YdM[EdDaC4nu=M9_`R&mQ+r=snX,+)fbiAST.;+MZWV[,UN;ue^8VL=#)[-D+ji/e(U#$INmb5dSOrMhR?L+;p#D!\cROHFgNin6#UUD>hp<UO[&3V_pdQZtisOaS^_A9<FZ]7TbeCu^os/P+21$5X=-A:CD!n0c[T"4sQ3\=EW?lP78(/`(@bmV8MBD=jl&T7nMHB6e<@rb)lNFM*Ef/a1X,*1g*n@GH.ji5V+6-aq]rGke,ZEi`lN4'(.C;#OQ=j-_Gk!COP$1,0",jp8F"Mnb(QHj=0MBC#Eo<7]:d7g>9TD/$(HAE&[%#L^r'\k*OT]5Nl1haa(.1-/X"Cbp@<oANXi,Eb`Mf\dber!WA-aT_~>endstream
endobj
13 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1736
>>
stream
Gb!SlD/\/e&H88.E@W`YaVMml?7Z6s[^PM5(+mlk7Q5GG#?miBg6/CsP`9h<J)=+dD)Hc8dNRfi`GlN]mW]92H1#>QICs7l1W]Zh%,p1n$C&AG!n8S]$t$iWiQ3/h['^</'#lK"0YlF?d.d&j]\Tc0lflO66bi?1[[rP1op\GW$e`<+J9XrKM]%W/F'l$_\m?8W+\//@jg/8lDaXh_;]*efGif(?`G6.*UElKhU7GE5I.)&M5BZs`aQ<OrXi?b?3Fp97\<\pIjnF+3@U]k8f=fUY/M&GSWT>Gg,fVMOpm-O`i+]$IdP)D:(qZ@&gDc&R=$db]ZP'p#"#*@@_WCM`T*BpU'Bh,T2`qV;6@_Y7&;@Yqem.&4Os'VYP+sUmjI0R9ENWRN=f!n3]U0&.6n&IF.\0`[ofgOZg5_^=(7;Eh:O&1VWbbhkdHQ.K9g=Ln%7m<tOXEYVXmh_KajGtPfrAMc=>pu$*PMik8[,O'[hPEGZ4K`aDH>JD`Q/EMpX8P'(n`&?a[0Q;;5=9$5g2`rqt[R$*/fX9LOi[b:4j[$CP1C1=h*=paK:ac\3:K3Maq/=e%I89'?s&"DAB[#chTd"&LC9g7rXLI["-793d'&s#$J4KL;Z@pKc]#sDkDp)UQ,TG*e+(OW*a^-Vcn,n:!)X96\tc%gd^q-C2&Bf<Si;?A'"S0`4d=smGCls'h-1<VU!-c@ePH.a3YDe)JhI6R)hT$U!n`aPr'9<=6P@b`:7Qk)kDSCdt6=HMdpO=rNkGJh3K3,nX[XW!\&M7&o<.>QSI'VS6Pc];Fm&',&o(j`>/Hdq"#08g(h'h8bY^a#+N95^e>(C$_Bqcj6NU]c<E^Tp&;ht%UOJg^C`e]LVJlSDd=FjN`9*c?ODd<AnoV:"sYdo"cbNZ0M&7_b%(4TSdrOTf*5"R,-0gAJS^'Z]%n(lb=`hYm#ahu+4flbE^K7.?aCD4UPO3:GW(A*Nrg1AIFnPV4[1(j/D$-32^EBmAoP.bMLEeuiF'IpWh%I&63l@r"#')G%6di'LU&bWFc_;S-fQ-N%Ktk/qWo]_*P:(PWe.VFW=tOr7\jRqd628;3-B@%<?d,r+_OYT`]27Y89k4D3LeM&GiP6*PPpIoK'36uYV=i5$]U2+E`Fo2!`#7jaoYF,K#uG5<GWn5o?-)0J;c;/`1h9;JK5,^8RpPs`eso[[m'#c'*4,7DO@a54tQMW^/fpD\BF.*RXJo44k+D^f$HeRq@?\NhQE:3(@VeS:p(PpXsj[j3]3=BBWkC,b@snLVT-LGM_(p`7V=1lf?fB`9IUT7OeRKn;DjMp(s81,HFgdlE%O)MYrMq6%3Kss^+5MqS!mGRa]NlVDls.A[1k^fotBL8lAc<2MC$kt4_;73a!I6=l7r@OB8up0cijIX^UeIHVB.7l]cfm<DRCsa2AGYu6tYGAQfSB(j[Gc*(E+6s+P&Y9,CmgGrmgB`GS\4C$tF3o=('6V)glO)G`TsHdZ-+D5<cff55cbrSW5]`MTs9%h;d\N`1Q.$OZFL?306kF47=_8L9+-7=&m?_kcTkW3'hs#Bk?4t_Fd@=j,S=>l?AB%b9mW-W@QB0n9e@LHL5)JR9Uj:1+ZlB`hHKR3Y?l;T+,1Kf/U:Pg%*p9VT_nU.:B<!A0"9NM6,=ZoF!bZmiP0c/k*ZO>K-/6MFE\3SXb_%R8LqhU9+n>90'o@;RfLkG0Sc3FG@*ZrbR$jGFp?VKXiOKE:OO&_)U$~>endstream
endobj
xref
0 14
0000000000 65535 f
0000000061 00000 n
0000000112 00000 n
0000000219 00000 n
0000000331 00000 n
0000000436 00000 n
0000000641 00000 n
0000000846 00000 n
0000001051 00000 n
0000001120 00000 n
0000001412 00000 n
0000001484 00000 n
0000003223 00000 n
0000005140 00000 n
trailer
<<
/ID
[<ac8544ee605d4b361a9cf9d19fa393f9><ac8544ee605d4b361a9cf9d19fa393f9>]
% ReportLab generated PDF document -- digest (opensource)
/Info 9 0 R
/Root 8 0 R
/Size 14
>>
startxref
6968
%%EOF

View File

@@ -0,0 +1,264 @@
# Les 15 — Lesopdracht
## Polderfest live via drie chats — GitHub + Polderfest + Vercel MCP
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Werkvorm:** in-class na de pauze (±1 uur basis + ±30 min extra)
**Geen deliverables.** Gewoon doen tot het werkt.
---
## Doel
Je doorloopt de hele flow die Tim in de demo deed. Vier MCPs koppelen, lokaal draaien, en daarna drie chats waarmee je via Cursor + GitHub MCP + Vercel MCP je Polderfest live krijgt — zonder zelf `git`, `gh` of `vercel` commando's te tikken.
**Extra werk:** breid je Polderfest MCP uit met een eigen feature via Cursor + Supabase MCP.
---
## Wat je nodig hebt
- **Supabase project** met bands-tabel (uit Les 11/12)
- `SUPABASE_URL` + `SUPABASE_SERVICE_ROLE_KEY` (project Settings → API)
- **Supabase Personal Access Token** (`sbp_...`) + `project_ref`
- **GitHub Personal Access Token** (fine-grained, repo permissies)
- **Vercel account** (geen token vooraf — OAuth)
- **Cursor** ingelogd
- **Node ≥ 18**
---
## Basis — ±1 uur
### Stap 1 — Finished zip uitpakken + dev-server
```bash
cd ~/web
unzip ~/Downloads/polderfest-mcp-finished.zip
cd polderfest-mcp-finished
npm install
cp .env.example .env.local
```
Vul in `.env.local`:
```
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGc...
```
(Beide uit Supabase → Settings → API.)
```bash
npm run dev # → localhost:3000
```
### Stap 2 — `~/.cursor/mcp.json` met vier MCPs
```bash
mkdir -p ~/.cursor
nano ~/.cursor/mcp.json
```
Plak (zie ook template in `lesbestanden/cursor-mcp-config/`):
```jsonc
{
"mcpServers": {
"supabase": {
"type": "http",
"url": "https://mcp.supabase.com/mcp?project_ref=<JOUW_REF>&read_only=false",
"headers": { "Authorization": "Bearer sbp_..." }
},
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ghp_..." }
},
"vercel": {
"type": "http",
"url": "https://mcp.vercel.com"
},
"polderfest": {
"type": "http",
"url": "http://localhost:3000/api/mcp"
}
}
}
```
> **Tip:** zet `read_only=false` voor Supabase als je in Chat 2 een tabel wilt aanmaken. Voor pure read-only demo's kan `true`.
### Stap 3 — Cursor herstart + check
`Cmd+Q`, opnieuw openen.
In een chat onderaan de MCP-indicator: **4 servers groen** (github, supabase, vercel, polderfest).
Voor Vercel: klik "Needs login" → OAuth-flow in browser → autoriseer.
### Stap 4 — Eerst commit lokaal
Een eerste commit klaarzetten voor Chat 1:
```bash
git init
git add .
git commit -m "polderfest mcp server"
git branch -M main
```
(Géén `gh repo create` — dat doet Chat 1 zo via MCP.)
---
## Chat 1 — GitHub MCP: repo + remote
Open een **nieuwe** Cursor chat (`Cmd+L`). Plak:
```
Maak een nieuwe public GitHub repo "polderfest-mcp" aan via GitHub MCP.
Voeg toe als remote `origin` aan deze folder.
Push de main branch.
```
**Wat je ziet gebeuren:**
- GitHub MCP roept `create_repository` aan → URL terug
- Cursor draait `git remote add origin <url>` in terminal
- Cursor draait `git push -u origin main`
**Check:** open github.com → repo staat er met je code.
---
## Chat 2 — Polderfest + Supabase MCP: nieuwe feature
Open een **nieuwe** chat. Plak:
```
Ik wil een review-systeem voor de Polderfest MCP.
Stap 1: vraag Supabase MCP om een tabel band_reviews te maken
in mijn Polderfest project:
- id (uuid, pk, default uuid_generate_v4())
- band_id (uuid, FK naar bands.id)
- score (smallint, check tussen 1 en 10)
- comment (text)
- created_at (timestamp default now())
Stap 2: voeg twee tools toe aan @file:app/api/mcp/route.ts:
- addReview(bandName, score, comment) — zoekt band op naam, insert in band_reviews
- getReviews(bandName) — geeft reviews voor één band
Gebruik dezelfde patroon als bestaande tools (Zod, content-array).
```
Supabase MCP draait migration → bevestigen.
Cursor genereert tools in route.ts → Accept all → save.
**Commit + push:**
```bash
git add . && git commit -m "feat: review tools"
git push
```
**Test in dezelfde chat:**
```
Geef Maud and The Cathedrals een review: 9, episch concert.
```
```
Toon alle reviews voor Maud and The Cathedrals.
```
**Check:** beide vragen geven antwoord, en in Supabase Table Editor staat een nieuwe rij in `band_reviews`.
---
## Chat 3 — Vercel MCP: project + deploy
Open een **nieuwe** chat. Plak:
```
Maak via Vercel MCP een nieuw Vercel-project aan voor mijn
GitHub repo `polderfest-mcp`.
Voeg toe als env vars (production):
- SUPABASE_URL = <plak hier je URL>
- SUPABASE_SERVICE_ROLE_KEY = <plak hier je key>
Daarna: deploy naar productie en geef me de URL terug.
```
Vercel MCP doet:
- `create_project` met GitHub-koppeling
- env vars toevoegen
- deploy triggeren (±45s)
- productie-URL terug
**Test in browser:** open `https://<productie-url>/api/mcp` → MCP-handshake response.
---
## Stap 5 — Cursor naar productie
Update `~/.cursor/mcp.json`: vervang de polderfest URL:
```jsonc
"polderfest": {
"type": "http",
"url": "https://polderfest-mcp-<scope>.vercel.app/api/mcp"
}
```
`Cmd+Q` Cursor, opnieuw openen.
Stop `npm run dev` lokaal. Stel opnieuw een van de test-vragen in chat — werkt nu zonder lokale dev-server. Alles op productie.
**Klaar als:** je vragen aan Polderfest werken met `npm run dev` gestopt.
---
## Extra werk — Polderfest uitbreiden
**Voor wie sneller is.** Doe Chat 2 nog een keer, voor een eigen feature.
### Stappen (zelfde patroon als de demo)
1. Bedenk een feature die nieuwe data nodig heeft
2. **Supabase MCP** via Cursor → maak nieuwe tabel + seed-data
3. **Cursor** → genereer MCP tool(s) in `route.ts`
4. Push naar GitHub — Vercel deploy't automatisch (je project is al gekoppeld via Chat 3)
5. Test in Cursor chat
### Ideeën
| Feature | Tabel | Tools |
|---------|-------|-------|
| Reviews (demo) | `band_reviews` | `addReview` + `getReviews` |
| Wishlist | `wishlist` (user_email, band_id) | `addToWishlist` + `getMyWishlist` |
| Stage ratings | `stage_ratings` (stage, score) | `rateStage` + `getStageRatings` |
| Notes per band | `band_notes` (band_id, note) | `addNote` + `getNotes` |
| Eigen idee | — | — |
**Geen deliverables.** Werkt het — top.
---
## Vastgelopen?
| Probleem | Oplossing |
|----------|-----------|
| `npm install` faalt | Check Node ≥ 18, `npm cache clean --force` |
| Supabase 401 in app | Service-role key opnieuw kopiëren (let op witregels) |
| MCP-indicator komt niet | Cursor VOLLEDIG sluiten (Cmd+Q) en opnieuw openen |
| mcp.json syntax error | jsonlint.com — één komma fout = ALLE servers vallen uit |
| Vercel "Needs login" werkt niet | Browser pop-up geblokkeerd? Ga manueel naar `mcp.vercel.com` |
| Supabase MCP weigert migration | `read_only=false` in mcp.json zetten |
| Productie-URL geeft 500 | Env vars niet (goed) gezet in Vercel project |
| GitHub MCP geeft 403 | PAT scope te beperkt — geef Contents + Issues + PR write-rechten |
Tim loopt rond — vragen gewoon stellen.

View File

@@ -0,0 +1,187 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 8 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
9 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/PageMode /UseNone /Pages 15 0 R /Type /Catalog
>>
endobj
14 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616185725+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616185725+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 15 Lesopdracht) /Trapped /False
>>
endobj
15 0 obj
<<
/Count 6 /Kids [ 6 0 R 7 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages
>>
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1725
>>
stream
GauHK>BALZ&:WeDm+\3M'MH17NOH4=3B9gfcudPA1DsU/E3LqG2PcOlrq<ZR^/,c4UmXfq?nAF'^\r=W>#EZ96]5k9!dHe\P7J?UKBWH95n'-P_.5V%0qbA=`JEV2Q\*I5ig?0clYr:HMqNgk7!CKDAO'L+rB\"7TMA[[Z!\KD#mSmV-<O\aGU>2=-Hm=5!Ir;2:V!sW?bl:<VCr[70DIO1Jnd\4/5q*Z-t&I&^r'%7g-M)B1A"#H&6TKOYSFAO3;@21^g$=kgVNn,jI\>OGQiH2Y:<%s=!I<*#lhWN\DK4Qq/;r+L/4M#0KohDJPoDp#qQ&)-!K<o2hd58T#LU=`i#SOQ3CDDI!@<Q,*YYEiZ(S,9%!,j#t<bWnZo4ToD-ocI$.-bGq1=2pm.fZ6e,'(?)6eg9V\bF+<GeOcSC<=4TPlHgkhJmfl4q^8n#%t@EoH719!_s,#Jo(pmJ%-p7c'aUC].0;c--^4;8[tR/JFNV*lJ`P.AeF-$$LaO?%SO-o=O9KCu.1BplBE;iBVJ']K"P9Y,04pZuJgfg--[a56rTOD-+SP,f_,6muFTb$4?iSYnc,;,ZGgLa7EUT>4GL;O+a0XC[QfI/FmL\`Q#m)W3_Smj\U5Vt$o@lCdRfitc8O+lHs63:Ab>-;t=k=YZin?^_$nEp\H_VnpV5h\Fk9N,]?q7(B2D6*GmR%.\T>?[%3)/XZ[@ltY8jQoYQ8aXH/R0E90;(Q\mf[>5od9876V"jXWeW[B:rMmVm&eNCg>D6]s_g]TW*R-^h@7'OVuL#DbRD2$qhPAmT_U"2?E%EBa*mT@.K@2Nl6nC3Wrc?&)L/H&t5\N91D),.(fY>]Mg]N._pN&@-)A$0se3C'S;364PuH)G`hGt)Y_(Ssl`8Z>&r@unid3`ns'DhQ`ap*/F9bg($ho/RT9CoGdT3+qI^A[<2dfGm.`T8eBD<kn\263"]*N8KXK-eI7iSooF1eZ%J1r,VB$lL`onqWmt1jSGLjJ"=!R@EBp&MEJom0WUEU/bbWH[8rVM0YTM0b-1cbT'7^%<jqua^FfY,%l*MBA'Yg5Aks`@EX,]^1S2jTCI1([0HT'+k`h$5VdaY!2=lqla\u<BCd!rhng&Q1B8[lJULhU<iP1+H,SC`P7KI[G,!25Z](:;S<43ji7<su9c]aj.]0FPp92RNT8.BC?(#b),Z$nmnjK#'$ZY/'cfq3--]0l6VkI\ZuHg)VnmMUGR9\$p#UAM:8[]j[C[.IC/?>YPKl/;Bcf3.;U?7bX+e(\"Z>H7q!g0)'?mMJCTeJF;DRic7HI0=Y)Z2O#:e0qhH$3Z\U:N3mqn0*U?Ck\4^]-'t!>JaB=M-Hi1[\HNlfSSGqNp8OM9TprFKPK#E-oB6j8I-mh(gmneij]BD^p('qF^1g^$O;E]WJ/TYiR"-W75RB<pIC`G`Z*E;/uBPnEAJ'u`cP\UNbq(Ygbh`u)F6N<Wg]8n5`+kfHdc/n9P\Hif^(VX%pFUn>i0eTr:C2o_;Zb]WBO=OW.S<m$+$c#'DKB.&slI-J`RN^E1Gq12=u:kF`K"pHU0]s`3bA8kW1;]Cgn.F?RP=(D&c19B.$:,^5jHk)(j"$+=MG=Y@/&:\"*jtO.PAmFN#Y<Kp$m6L;@DdD&9)/7,8WbS;6'Ch:8jIQN.WfQAD.q9(;7O]S+_n^L)C"V)NP_oKK]<+2%>SD*iBOD0I*Sl[+!PRo[5=i.#H0LS+~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1830
>>
stream
Gb!ktD/\/e&H8h>EU_?"Bk*U-7(i`HL*T<FG28V,DMnN"&m;)K`Nl:3EjBlPIf#.gob,<%S76.:bVh#7jrNpPdG7/GT_N-p\$lK"*^]le"3+b/"_J;JL9KNWW.!%FHDS`?l#6lG*':o&3K5`8*0W[:[AiS587l&().4".E8H/BAlohRD9&gB!mRsK'!.u>Rh^iT64IZeBC'YuVc@em:)k_db>jgK-=P-*:hcoa=tcm--tbYRU/gGc`3SdMca6_Blb,E?f#@hPW,+'I.rX]W:Rc.KV\i)"`o,q:f"oJ'9o6QD2GTUtG)4$R2h@`2gpAUL/tk@OHZjLY1cW0nHiE;LSn2nfd-q"6?6igqmat5[nY//`?GZ-q4UTBu/U`9#o62i_:+sn"^H0('p%4;4q=3Qor/4&NBkVk"'b2)D&e6!?_P-`=:9k4=L>">:(bHl->jdW`a3pSN=SWgCefI)jA,j*EZ'5W;rQBp.(s"9"_cch\aeQ0YA@-R(`U-Fc-8>r*+2GP7RqUDN%E!t0e>euqbjf!?*%r&6nX`c:<9a8%BPrDTN0fX-6C>i0!;N]lhCUn8ltpRdKcosA?ll/tJPIY274P^G,U--SY$S>X]a)&0`scilCd#kp(c[I6\)?iG54MZF)C>?Y(R(mV$85g:1V!-i!l(:rV<n6rJeDteinFS%IofU9Mi"&k08:s9],/i@%3rBPr\c#/SQFMDYQ\o5H)f%fUT-ZT/8uCZl@/:M7*u&k\VpVLPMh3m4XYqc2j!9+VO8AuACrPOm%KD%0++WVXM7UH];*&lG,007-Bni&gEW!<]W`YpcCm.f9,>[\D&/$Z$`Y(ue7qf'[@3es10X/>[?2s.:+q.of>G,Z"sufP,$t`)_*efEE\3K^Er^'_f\]:p@ki^58LJZmjXNf#<3dpCI>jG%7o>MrIZ$>KUp`<#?R7ueXU'r5WZ(MAW@!(SPNYVbfgR*_LHiM#rV$3/q<@LC^/(Od\VS,\j5JJsd/:>s.4*UYN99PL<3ZoOFH0DT4A,NIDWXH_1?quQ$!-_@*+'gKlG2S<2dj'g"?mLL#+o*N@[9T=A(MK=ApuXFNS>,_9VW_>?^M*G3$R!1*ls!RqkPpdMdeo,Z8*I!<ojP&gh+j,"$N.B(hde#a"iY1"3iA`/QuB._+2J._%)$nJhU+??Q*TJa1BVknTMtik,8Q0*nH+S:DX45QX*lU*TTMpc"kqk.)8eUYue<E0.`i@@peN8+c*+%p-%9-o=%kn!hG3=9+Z39=?>WQg(q?VU&_<rRm$fC4l'D2lOM%:#,Ad,PHgWdCX1^T+<eRa"^^--=pP2+QIqMIU+K4JE8V:+k\<q<1r:!.08IUdl@E@mTk`X"o`.r`"L#5n]n`n<$.^#K3WZ<NW7'bc7GuL6]qG.t\XaeciVc[\[3m`"h!kP>S7>J"<0.6k46"aH5UkX!NBJ7,'!N8%4D\RbX:Ak?c6!QfL2ci=;dsp6.e.@TC$<SBRj85&+Z$mgkS';%*CWPJ"N<C2askl_gs(rE9:;\S#&rT-)7XRoF;H\Xn4q*EX$i8PkUP+GV&E,fbNO]q(2aDE=gF3Wj:8kLF!&Y9_JXnf?T>.N`F7jRB7VbW_OJ)-#1()S95i5'qj?4sk^LgWDbJ$jEq8SAAg[3pMF1aRDX],C%-A$hE4V5F>D_s00MeIA1)_;^:,%Tn,BZ?=q;K9dgal&-8ePD2WbXk'cmY8-OUU^:C"+_<i]`SPAKQf`]ao6k*UW7R*p(YB5OQ:N*/X.TN=TD/p^?<`0'+@t;_f,]cK"pEH*)C5D]2#&l9Q77d1$<>nS6hoL=K(r9K"*qg->MaZk<~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1956
>>
stream
Gb!#\gN)%,&:N/3m,U`5/=6G+IVS2*dnZ(W8_eKdUDX:\+dj*a!C=9df678P,YPSI9]K0Z,nZL"`U;E3"XmLJVWK\W!.MM4PB.(q_;uZ86QEU`#HodN?UhCCkqV1O/>V4YN!-<Fi&p?QeCH:_s",$3PY$3Z-nn"'%##W8JQ=K(Uj.`%;oX30/S(\#.^CU&O;:L7dj)B8J]jD6O:V-CAIRBKL-bG*J9G1jeL]'\Ik>[P>-$l:/lU'WSPTZ?<^e1jm$]GtVu$qnZr[;IC-r97B]r;1^&uOgNE,eq!J`?FgI'%ONpl#00GrNlOV:iCim?`hZl.p:bH_!<QCo01NJ::onYt\47T@tTP*#K'l>D=QZlRY#d'o[Hi?96#Y96s5[K^0kTIUp`K/"f=oX;4"LXcetUXAaJrXsXYhbD0ec><h]Lb'1G(gMupekDJ:N_;Lh%s1dk$]J;Q-Yb5&4(d,'6/]%W$BJ[>O3_7u4m)Qh5Gprl,_HnEOiB`5"[Y,*8uRDJ$2NS#H/3ZlDC0WM?,P_dA3@sm0X_?tR$Jc0^?gAo"6opJCWQS:l1iNQG,bJe_._8gD?c+;f-!Lb5U6M7U@%?dN190m0b<81W&>mYY(0NhiVhF5R3"L74Y0'E^sMeGrN>=+rAop$juY1,]?)!)mcG4ekEJ59b*oDL%5_,#8^u)Z9Nuer/FaI(AOX5P7kJm)B<^SbDuWiVB'DJ'3a_$+W,K..HreDtaX5H%:WN4IF-aeKhVFe[4X+j-o&UO`ammcfeG\j(a`U0?WQ!GVaUO'FnC9=Y)n(BF$i1X`9`7?)9RVgV#dX_@ccf'%4S,09V85G2G:;c[RRA/G%3X8YA:d5?!]\M#Ae!*MKVeYr_u`m]J>ZBTFnf4_A<GrFNTaL"$()[&HfV?9eO<bLj&Eop/T$VuI-"`m>1i6"RG$`Jr$Ni.NI\VD$"kDnU6[!Q)!S3TWO/`mV.Ftbi:U;=gT#&kYR`"Nh*t2>mltK\9o2MW5^QcA;T\tEOG,+5(5Y!2IAK50-*!&6[oc+T^MS)\=jC:@hXIrYIfK0QP`YRt_3G2>XQOY3Vs?VB[1"!IcBE,&l-6_4(0q\4q#^:2Le[)i/BJt_9[Oa(k_nojMGe*aV!e/b<Ld`45!64,fDuq3n1`<dcJ;2Rj:?e3rjI,r/Y[D9>R,YM[p`OgFZgsO\fD90_pF\.a@8K_-4D=l74cOZ?.\eo^@YgPK4*p>s-8Is2&4HV:m\nJ:Nacl]Fmp>e8@Toj9anr-`hjH_8>YG!U8%qaTW,BRf@l-[:gBggj1n/)J6&YL9hZ5g=Hbu/a-D\7FOKn'i!Rp%Do24f/$Jb'['!*$r"mc^F\>%8PN`l==)sM;LQOUZcJTq16r2]!8Lqn:sUjNeZp87jYC).hcO9h%-T.AOM;PM#R=%CD4'<=Oi!(?nPs(/KtNBr[M8NYCRc/A>)t#Ej<e5@:FV2@&ZO5B;HQ1NheT<a5S@Qu?e7ks%-@$s4Z)>EW^+$u/OV4ehR-G1'J'$>K@DNqe0gQ#Krnml_DUlHD^":KReDUanQp4$9?cA.&Q_DT'I^'*Ga]ti3l[s`=p7o-'mc-sril)&%;eL<LR*cW0oK;/C6\Ktm@0%`A@i?B?9?i)2"?hk'r`Ir)"=Wn?'lG;:[o@oYi>ieHQ6Ou:UF^p@#Q8K9Jq5K"I@O@(=]4c`[^8/H'4%`?ButO9MVg.5ESC:"B!\jColMu9Q[E,d/\8LehR&eBB$E-_f=kLktsh6GD0-K'3X,@FI9CE8FfADIJ:!to`M?_-<OKQ`e%qZTi8K5@]lBI+Ht["'nRM)oOH"l%N03'9\.>6*)Y-E;G"uq/;i5QAS+!uW3crOFSo2PCH(>^h)+(gnum9bhKL@SbUm*3Ia``(#2-BV;jJsgK9P:69'_mIiM$r1!Rfd((d\cpWm*"J[N2X`QiCP0l%9%LoAOa@^=k&W@.Xtr1.nS~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1741
>>
stream
Gb"/'D/\/e&H;*)EU\b#m:ofMog0O%JMqSY@4h<g\%htG.3gRU&m<X.fg.Q5^O8UTU6PF)l%Z8]('h9i,4CQdo%[_RYjb)^pL"<sEXJRC'I\Uu"]1]@0RXCoC%&MPX]N)m\3BL5GhaU^Z#&3NNsUH`Im3=p&o.'5L*c+q5cp#@X`8<C/sfEAha+\#(?<Fh]nGV`D<O-sFr5:S<F'<KKNclL.C>_aidPH!l.KGXk+LLbA^C'4,(qUQ=kp>\'5hVT[\0*Y0!T69lUjRO-Kl>8c&$@!%]r\f17(i&HDn2Jma[5!%iS$4?1a'c8j"7XO0)HNe_1@V<pNG<2oaH]^H##8)>"]XM^K?7rS"&YoSS9.V84@M:2M@_54KM%/hfC(F$3<sDD9*naY3i!Nuikd#r]=5/tmcgA3#BLXWqrtAX^V-^QZh]R[_VmYtk=Ym-i)FIJ$To2H<0P;$*Hf?,[VRJma-^hkAu,+LZ-8!sQ_J\(!C;U"Grr<pmCRL=a#JnAE`j?<_$&V0+q?.<Q='UJ_ot7]o(>8I3e!JeM9><1(k=!2YjiON-3C"dMMFYeXF8!gT,5=H,;3^o[I+Xa`'0dkjca`j)Pm;SY=fo6pErSfs)/U2Js0ND"u6\*;LZpf.4Hl#W(M2nM,MXL3t%1">1e!sU+\'jQ`:/KV^<M@^*F\X[0Wc.*`iAPujpnNs=i7,u8N.[0&$:b\0t(kP<rQ$E(uPd7A_22TiJ)FG0Dip,[m3m+jiEt$H!*Oskta`7)q3%(kJFL63T[sbH*Xs)2(T'ZO:Rn>%lf7.!1ko,3hg(.5F@K]*W0Or=Z:FZlS$u\ntN?QKo9,6Y.*NCYc^DqIOp70^5D/;D#iPF7WK;o$#4,'.hDl$:J3.Z:Y]Xe34F'B4!\"3O?*[l.:kJHV<"Jm/ZCi-(N:8u!2A3;OYN>Ufti.d@rXYQrbp!=hu;26g)S.FIIGm3M)G"Sh&T3=3j8kmH="#A'`1-&#R0R2BXZ,iu^&sY\`c4Qt['L$/D*3/o<kPfE<A=ptP4g'iU?3mG$lsJl<TQ3dXCS+K^8$\n_)I(<%ehSmd2S`7EX.YF"AG17qfW'pj_S$<N#"(BKq!Q,E(T3J'Nuq]Gp[B`OWeNs<Ek#)!ZKI".+]N]VYq9gjP7O:75+.phGl)f-p<2OK/_*qfb7W91,'Z%EpOe]U,0)QQ5_?Jl"@bZ\[n$Rd$5_p$6)jG*r93Y.gbK=dS-%Q9$D#<I;g!Wg3Gr\o3HiH5<BY5'^aNi"?]>2'\/up`k,^B(H$c+CPW4m7C2E8B>.tAXK,cn`3icJ&$Wi[pKM=N;YL4t#EKn]AqLXYp4PYKl>PCb)ktl3L27BY[%\/;W%sOVrf"K@-G?1,%o=<#KULo,rF/eO@Zc@hrp4*DFWcK&B(`HZ31A-+RKP0*-FR9!m@(TU:q2T]kVq6XF\+sL%[D^!;]L>t8`HX5>jF`T=U(P\?XhD-X^(j3$\9g1dim_pum'0[6/'Ybc/56Io#L:C--]W,UHV#FsSltPY=lX3F1N57RnRRn$34*U5rGd=dlZCpm-XZe=Ka;0m8bm-iK$#%'$Z]CG\%a@#`irM>=se;RJQf4LLX@-0UqjgkR.UnB8_51.7aUQY8QS\sca+(EoOtqZG[Mec8X^U9,`T`WQ!(5k#A0-)Rac7seK;[NpRjnSrUNUYE*$E@D#`bU3KN%/b2[*7DYKXmEql^A&07@+F[\'Abd%EZmdWs&T/R)fH@H%)7HZ?~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1935
>>
stream
Gb"/'D0+Dj&H88.Z-t?rYJJ3K$g)&=3c1>JHInK?@F?gQF;0'&#@k<M$r)6MX^pH'^mLq.D\k`rc*$^&dH]CjgX)^^D>o92J2]i?q9B26_/KgkCUl.s!2m`0mmhP4kqgL)Y^m73a0u,RkE6p89oaB[(4"0H>Pl-^kCDC;UKO\pPnWAObbGt]T#:&pSB_sni1uIFON/KL1r`urT9s)&)8"kr)TNY?mD@:mS$:P]3Z*2[QqJ_\W5,L]G1(1:Z:H.U`>6`ebW&BL\5\jF?kH=>E:!W4F4FPe5+u2#$K74"(bsTs--DX-["+bmNDQ'p&mVQB6Z[BkPRE]k]M9*`BlM.hLl,$JGQ88'E7gcKU>b2qYI[qAFjc7l=1;7)Yn\;Z,b.Nc8cHaG;)\aq%@ieG(C;[>9]R$7kjBG@K8?.]YK53:9_;W6EICV=Z.m&#C4ktF(@t\dotYU"ShhK]#*/[+?LG/`CDP1K"-&,%Paq_LP++=ogUsuA,$*;.is=*90\cRFUupKC]7TfM#RSZk+]"4e7m`_M>Pu/L#U_YO)5L_N6?\)S-Z5Wkod.V(\&QKh+V0Dh0=XNI?.-1W\OiQ#e**7A$4Mo\MJ%%j@FY+0adC@N`g%5^eZks3<J5EJHJXJ"o.*LP^t(44bh".m[:!TNaqd8#4=hE=QEbm(lt4u,T%10\^:-"!]nJP'hDfk#O90-.2QGITLhOtk!7APdESB"mT"l%[X;YLM'`Ch4;KfiJ=#2unS1feZ?h58rj2q*t(b]?)jbf=L\G(Q6R--E,f:;HcMD3#Q8\e[uNYlmrH[?TNT7er^!63g70iepm`RTSIU@XG/6pWU>8r.$-a[a["*nsY/.11cpb4i-[SqJuq!7of_:=OQO:Ee6plmu1e("]:4?_IlW1*XpTEVl4,?77Pk.aGp\W=ri#9$0(0.M>ks-_bE(8@an^N(bh-c5rYdi(7&@%UeC<U^VD.U(+<OR''f)VY\`*&R<Y1ZAe9:66&"We!rO9d<Bp)@JN%9I]c=H_R>&(&3uSf3!njEG8aGq<]7%gbJkHF<+XHD2EiXpJ_rM-T06Xg2eX4um9c&KIr$q>X_iYcG>5?tGus-o2:C+#CnE+gK0a?;e1/&aemWubH%`*(,s>D_Mp:<FfE8;>R3=2%&`YXEF1>3\lpP!e?1ToL-;35SYa#TWU2i"]&JJ+[qj`'?Cub.FcDO7(Wtng&os1m#eJ)!YJ&=:-P@H:Sj/?[32:?4R!]h$rM+b&pDtaA(_<W%I._6r4P/JrC1p!e`?+K%Mj]gh5aNaB4jIq[f$Fq(-]Wn!\^bu-uliZ2K*FJ408Pja;)'p]U;@DL,a7,K8a&/`YpDEO^<kQrg7'W>n6u*o%_&O<h;c?1bh;<C58opPG>(Grre3`ph]l-GTK7GtXGcW!sNX]$]jF^@``S$C8_6TB6q41(?b60GeV47trbO:X$2)S5^kg]kJ/m-D@)&lus;npMZWS?7s=2(,PH&>W!9YK<a/ID"NbO.H=:cka:psmsaMW\`+M1/@mW]`oc?$bN(o`'E(C/%Uh;Gb`uI.smSY&=bSpF]*eVG]%m8RHouqrjpkECu+T,V\@CBoprYkpsN4.>BYR,cH^)/+U+4kZn![*WGA3:q2WcdB"K%V[-^_4G@Zs/>tmP8O[b4jn<Lrc;AfAJp-73EY4)ZPp/M]iGJOPMosgWf436Nkm<nROhieR''Thc9j1``)X8`^E>le>=&`5G`*DP%XGt*>\LL(P5(E?HqY'k*j@Kq6K>TfFU_\Y:mQW^JaX$I3ZYjfG6@PCRrW?!>We"mh;n6YP,YjU>MKu0q7#S2)WF"2H_jHl;p_-sI=t$$-jfkDV+cqA(J+BPlO(Y)`0?kXPP;n!!H?Rrd=#n8>.@)eDA0U=T).fr97*P;@T'-oZEGC-T!bi.nqIh&gAkp%2s!GFpdD,;`A+]~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 626
>>
stream
GasIdbAu&c']&X:c8$sb@EJ(U_#cZo^m$"<L/hk'a13o7[U`np)>JO=7qh-D`7IfLgiFROLVn>ULO7DKD_etQbJ=@f%Cfp]fe'?_n#+2RA/MR"'e)We8sY.EFFJ+sM+GQ?":Gi,8-N*6oES(S.Hd$eO9oPkOsZP(#t*?M3mQS/81^?bYV_<%.)=<pj<Tqq]PdClOJ-+amLL%jWF!&[/3#uUp);-j7^$R$joX3Og#Wm7O:b-^9TSC5D6:FQ;WT.55@BO<3RZA2_g#S"gU6i,mg(#=(-#,jP-J]#dc@jbl:`N<LS9+o9rn-BX%cn^W_/s6>.3h*_Jc`G:@LYjs)0B7]dmnfo.S9kPXHIKd.bjST:E+8*rF`7:_d-gcUpptq[:I(6MII,,u[X"f'"\#p/EC'^i`T/GIuDPM_PV(#A:=Z*>bn8FG4T=QE[*LbR^%u&FmPg/$3%mCkigOhfV$G?PZ9S9P%pUW;-@1O*WO.E7`ZN4@qs!VI%2t$ohAUL=<C[D`efpE1H7V@057dNB?V#?k98p`)0MiFekQ%Wd5]tn@sK[Fr8PFW/bO=N5"^Gb2Zk?6CqagK3ITl_J$=u40UYh8]ipMT\VkTQKjnQHW4mT.D.mH<<F9~>endstream
endobj
xref
0 22
0000000000 65535 f
0000000061 00000 n
0000000132 00000 n
0000000239 00000 n
0000000351 00000 n
0000000456 00000 n
0000000533 00000 n
0000000738 00000 n
0000000943 00000 n
0000001026 00000 n
0000001231 00000 n
0000001437 00000 n
0000001643 00000 n
0000001849 00000 n
0000001919 00000 n
0000002215 00000 n
0000002308 00000 n
0000004125 00000 n
0000006047 00000 n
0000008095 00000 n
0000009928 00000 n
0000011955 00000 n
trailer
<<
/ID
[<91a34edc5f1ac0d3fb629e0a3e03ed80><91a34edc5f1ac0d3fb629e0a3e03ed80>]
% ReportLab generated PDF document -- digest (opensource)
/Info 14 0 R
/Root 13 0 R
/Size 22
>>
startxref
12672
%%EOF

325
Les15-MCP/Les15-Lesstof.md Normal file
View File

@@ -0,0 +1,325 @@
# Les 15 — Lesstof
## MCP — Model Context Protocol
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Vorige les:** Les 14 — Cursor + Vercel
**Volgende les:** Les 16 — RAG + Embeddings
---
## Doel van deze les
Aan het einde van deze les heb je:
- Begrip van wat MCP is en welk probleem het oplost
- Vier MCPs gekoppeld in Cursor: **GitHub, Supabase, Vercel, Polderfest**
- De Polderfest MCP server lokaal gedraaid + via drie chats naar productie:
- **Chat 1:** GitHub MCP — repo aanmaken + remote toevoegen
- **Chat 2:** Polderfest + Supabase MCP — nieuwe feature bouwen
- **Chat 3:** Vercel MCP — project + deploy
- Productie-URL gekoppeld in `mcp.json`
- Optioneel: eigen feature toegevoegd via Cursor + Supabase MCP
---
## 1. Wat is MCP
**Model Context Protocol** = open standaard voor hoe AI-clients (Cursor, Claude Desktop) verbinding maken met externe tools en data.
### Het probleem dat het oplost
Voor MCP had elke AI-client eigen plug-in/extension-systeem:
- Cursor: extension API
- Claude Desktop: plugins
- ChatGPT: GPTs + Custom Actions
- Windsurf, Zed: eigen API's
Eén feature × vier clients = vier integraties.
### De oplossing
**Eén protocol. Eén server. Vele clients.** Anthropic introduceerde MCP in november 2024. Open spec.
**Analogie:** USB-C voor AI — één stekker, elke laptop.
---
## 2. Architectuur + primitives
Drie rollen:
| Rol | Voorbeeld | Wat het doet |
|-----|-----------|--------------|
| **Host** | Cursor, Claude Desktop | de app waarin je werkt |
| **Client** | MCP library | praat met server (zit in host) |
| **Server** | GitHub MCP, jouw Polderfest | biedt tools/resources/prompts |
Drie primitives:
| Primitive | Wat |
|-----------|-----|
| **Tools** | functies (de AI doet iets) |
| **Resources** | read-only data (de AI leest iets) |
| **Prompts** | templates / persona's |
Vandaag focus op **tools**.
---
## 3. Transport
In Cursor 3 is HTTP/SSE de moderne manier:
| Transport | Wanneer |
|-----------|---------|
| **HTTP (remote)** | Server is een webservice (Vercel, mcp.supabase.com, jouw eigen) |
| **HTTP (lokaal)** | `npm run dev` op `localhost:3000` |
| **stdio** | Oud — Cursor start lokaal proces. Vermijden waar mogelijk. |
Alle MCPs van vandaag werken via HTTP — dat is wat we gaan zien.
---
## 4. Vier MCPs vandaag
| MCP | URL | Auth |
|-----|-----|------|
| **GitHub** | `https://api.githubcopilot.com/mcp/` | Bearer (`ghp_...` PAT) |
| **Supabase** | `https://mcp.supabase.com/mcp?project_ref=<id>&read_only=...` | Bearer (`sbp_...` PAT) |
| **Vercel** | `https://mcp.vercel.com` | OAuth (browser-flow) |
| **Polderfest** (eigen) | `http://localhost:3000/api/mcp` | geen |
---
## 5. De `mcp.json` config
Plaats: `~/.cursor/mcp.json` (globaal — werkt overal) of `.cursor/mcp.json` in een project (alleen daar).
```jsonc
{
"mcpServers": {
"supabase": {
"type": "http",
"url": "https://mcp.supabase.com/mcp?project_ref=<JOUW_REF>&read_only=false",
"headers": { "Authorization": "Bearer sbp_..." }
},
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ghp_..." }
},
"vercel": {
"type": "http",
"url": "https://mcp.vercel.com"
},
"polderfest": {
"type": "http",
"url": "http://localhost:3000/api/mcp"
}
}
}
```
**Patroon:** `type: http` + `url` + optioneel `headers.Authorization` met Bearer.
**Vercel** heeft geen header — die doet OAuth bij eerste gebruik via een "Needs login" prompt in Cursor.
---
## 6. Tokens regelen
### GitHub PAT (fine-grained)
1. github.com → **Settings****Developer settings****Personal access tokens****Fine-grained**
2. **Repository access:** kies welke repos je MCP mag zien (niet "All" als het niet hoeft)
3. **Permissions:** Contents (read+write), Issues (read+write), Pull requests (read+write)
4. Genereer → kopieer `ghp_...` (éénmalig zichtbaar)
### Supabase Personal Access Token
1. supabase.com → **Account****Access Tokens****Generate new token**
2. Naam: bv. `cursor-mcp`
3. Kopieer `sbp_...` (éénmalig zichtbaar)
> ⚠️ **Dit is NIET** de project anon key of service role key. Dit is een **account-level** token — geeft toegang tot ALLE projecten in je account.
**`project_ref`** vind je in je Supabase project URL: `https://supabase.com/dashboard/project/XXXXXX` — die `XXXXXX` is je ref.
### Vercel OAuth
Geen token vooraf nodig. Bij eerste gebruik in Cursor verschijnt een "Needs login" knop in de MCP-indicator. Klik → autoriseer in browser → klaar.
---
## 7. De drie chats workflow
De climax van de les. Drie aparte Cursor-chats, elk met één focus.
### Chat 1 — GitHub MCP: repo + remote
```
Maak een nieuwe public GitHub repo "polderfest-mcp" aan via GitHub MCP.
Voeg toe als remote `origin` aan deze folder.
Push de main branch.
```
GitHub MCP roept `create_repository` aan. Cursor draait daarna `git remote add` + `git push` in terminal. Geen `gh repo create` zelf typen.
### Chat 2 — Polderfest + Supabase MCP: feature bouwen
```
Ik wil een review-systeem.
Stap 1: vraag Supabase MCP om een tabel band_reviews
(id, band_id FK naar bands, score 1-10, comment, created_at)
Stap 2: voeg twee tools toe aan @file:app/api/mcp/route.ts:
- addReview(bandName, score, comment)
- getReviews(bandName)
```
Drie MCPs samen aan het werk:
- **Supabase MCP** maakt de tabel
- **Cursor** schrijft de tool-code
- **Polderfest MCP** voert de tools uit (na push + auto-deploy)
### Chat 3 — Vercel MCP: project + deploy
```
Maak via Vercel MCP een nieuw project voor mijn GitHub repo polderfest-mcp.
Voeg env vars toe (production): SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY.
Deploy naar productie.
```
Vercel MCP doet: `create_project` → link GitHub → set env vars → deploy. Productie-URL terug.
**Tot slot:** vervang polderfest entry in `mcp.json` naar de productie-URL. Werkt vanaf dan zonder lokale dev-server.
---
## 8. Polderfest MCP — anatomie
`polderfest-mcp-finished.zip` is een complete Next.js-app:
```
polderfest-mcp/
├── app/
│ ├── api/mcp/route.ts ← MCP server (het echte werk)
│ ├── lib/supabase.ts ← Supabase client + capitalizeDay helper
│ ├── page.tsx ← landingspagina
│ └── layout.tsx
├── package.json ← mcp-handler + @modelcontextprotocol/sdk + zod
├── .env.example
└── README.md
```
### Vier tools
| Tool | Wat |
|------|-----|
| `searchBands` | zoek bands op query / genre / dag |
| `getStageSchedule` | schema voor 1 festivaldag |
| `festivalStats` | totalen + verdeling per genre / dag / stage |
| `getWeather` | Open-Meteo forecast voor festivaldag |
### Tool-anatomie
```typescript
server.tool(
"searchBands",
"Zoek bands op query, genre, of dag.", // ← description voor de AI
{
query: z.string().optional(),
genre: z.string().optional(),
day: z.enum(["vrijdag", "zaterdag", "zondag"]).optional(),
},
async ({ query, genre, day }) => {
let q = supabase.from("bands").select("*").limit(50);
if (query) q = q.ilike("name", `%${query}%`);
if (genre) q = q.ilike("genre", `%${genre}%`);
if (day) q = q.eq("day", capitalizeDay(day));
const { data } = await q;
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
},
);
```
**Bekend uit Les 12/13:** Zod schemas. **Nieuw:** `server.tool()` signature + return met `content: [...]`.
### Bouwstenen
| Package | Wat |
|---------|-----|
| `@modelcontextprotocol/sdk` | officiële MCP SDK van Anthropic |
| `mcp-handler` | Vercel-adapter voor Next.js |
| `zod` | input schemas |
---
## 9. Lokaal draaien
```bash
unzip polderfest-mcp-finished.zip
cd polderfest-mcp-finished
npm install
cp .env.example .env.local
# vul SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY (uit Les 11/12)
npm run dev
```
- Landingspagina: `http://localhost:3000`
- MCP endpoint: `http://localhost:3000/api/mcp`
Polderfest entry zit al in je `mcp.json` (lokaal). Herstart Cursor → 4 indicators groen.
---
## 10. Bekende MCP servers
Inspiratie:
- **GitHub** — issues, PRs, repos, commits, code-search
- **Supabase** — DB queries, schema, migrations
- **Vercel** — projects, deployments, env vars
- **Filesystem** — lees/schrijf folders
- **Slack** — kanaal-search, posts
- **Linear / Jira** — tickets vanuit IDE
- **Notion** — pages + databases
- **Brave Search** — web search
- **PostgreSQL** — direct SQL
Zoek op `cursor.com/mcp` of `github.com/modelcontextprotocol/servers`.
---
## 11. Veiligheid
- Tokens niet committen — `~/.cursor/mcp.json` staat al buiten projecten
- Supabase: `read_only=true` voor demo's met productie-data
- GitHub PAT: fine-grained met minimale repo-rechten
- Vercel: OAuth + autoriseer alleen vertrouwde IDE
---
## 12. Verder lezen
- **MCP spec** — `modelcontextprotocol.io`
- **Cursor MCP docs** — `cursor.com/docs/mcp`
- **Supabase MCP** — `supabase.com/docs/guides/getting-started/mcp`
- **Vercel MCP** — `vercel.com/docs/agent-resources/vercel-mcp`
- **GitHub MCP** — `github.com/github/github-mcp-server`
---
## Samenvatting
Vandaag heb je:
- MCP gezien als de open standaard voor AI-clients
- Vier MCPs gekoppeld via HTTP-config in `~/.cursor/mcp.json`
- De drie-chats workflow doorlopen: GitHub repo → Polderfest feature → Vercel deploy
- Je eigen Polderfest MCP live gekregen op Vercel
- Drie MCPs samen zien werken in één chat (Cursor + Supabase + Polderfest)
Volgende les: RAG + Embeddings — AI antwoorden op basis van eigen documenten.

206
Les15-MCP/Les15-Lesstof.pdf Normal file
View File

@@ -0,0 +1,206 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 7 0 R /F5 9 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
8 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
10 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/PageMode /UseNone /Pages 16 0 R /Type /Catalog
>>
endobj
15 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616185725+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616185725+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 15 Lesstof) /Trapped /False
>>
endobj
16 0 obj
<<
/Count 7 /Kids [ 5 0 R 6 0 R 8 0 R 10 0 R 11 0 R 12 0 R 13 0 R ] /Type /Pages
>>
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1567
>>
stream
Gb!#\9iKe;&A@ZcqR4ZNMFWh@RAq6caX0uL5SAiOD/gFdNBLClBP5=[ZG1j8,YO8$f[u90_-kUpo=Eh$^O@qj0"kBW*i/k7@sIec"6RJq(7=Rc0YW(hj+Dgkf]pbtFb50+@*j*"-gYu(qEMRX\:NE/Hf0-Wf,M6X$p=CjC3i-\Z5P'?ECpcpYg>_H"[M5P26\.W\8EDPDf+S&$Q.d;peM:sJR8'S^q,Qh#<a$A]f-(B^V8[+&\]2iQ/TE?0d\T;RDEeGY*:auP0(Vh#%#pfAe!,hV8TeG.Y,,HfPt!MC'pMI5!LJMMVT_aG@5ZE4OdPJK/&nT&eg\2Ql0#^==r%5bS(\qaJS>NhHsT]_co,i*R1BRi3Q8tc.Yfq9-pG@$0q4&`THtB4/7MX;Sce\8*:dj7!uDo9;\)qgK"a*'gE2c^`g?c-Q(SPoF=R,4X]mXc#qDA#PAO%Xc"u,fe%IR1&bBfo5K)jTYC\<Dt?#]?fG(ePi^:1'%@=!TWmc?Ja)e0mdOas!52bTJBs\[GDjWjH3;H0Fack/_u2RdCX/A/gou6WA!A$UHAmQZ$+:X;V`7[#0<"]h%G8Z8)N]t5R7".<,XG*3&nRY*D)R,8$+'%U-<u,0=0/c[h5^tdPtb2p'^%6t0o82,X&_>s[GDj2hC^-Fn7e$V$.jD)AWInKPtYXd\%%\99)dK\q15qT"e9!GQ(e78W[3dla>rDid1e+V781:u'HCR.&>C%$)D]U'/Wk8i)6p$'6KmkupRX]=KseHDm[Y4?8dSU(?-<;"HD2k4-#fHPHqmsf8B7\d#Y*fO,,t2M(4*ggYu"jlL'>'26DHjWlX'?P8g\NI(#]q==X\r[Un_d#GgE=I("Ql^nCgR;7Wb1^ec-XZQBi@Z+MVj:g3lG9F<@E@=&h1$J,X\tY)k.\]S_P&b+1UG[r(^mMVmI$mkQggAr+P8=TiKr\q5q/A_YRO+aTDN>:]YQ:`hT@(nWP'o>4n1a7B)?qM(no/EX_a.UAM&pVkd>p'(uiI@^'IJ)>(2=(EA.?#=g%e'K48-.+`:BO,nJ#kHpmDE9)g7kdR1K)F=?lFE&QbmU1a1a*5sNjcC=V>d@CgjLuW1:q0S.BH1d%<m!:]WitT3\*f$s6n3$l5,-g(8S^A3,5cfpq%B?3Y;-jg\nfXpt@K;eS)s4+OAW3FB-e8ZBu\km%remdo>eVqE$hD@Ch*eF]l%)pFs@s@b0qg>\3_G?=8##rgOA!I8a<bS<IFJ7CN"@lds@VI?UBsr8qaA!q`?]K1?;dA.$5qCA6<7IeWC(KeuLWeEmkGNH$h-W!NE:B=2`fYX/<Zipllbl`hO1n&?S>Za:66PpX6q%gD4p<,qI4H3[ki_;M3t.m9F;19);I^BN<J36WFeHn-uCeIZe0FeX7'Ele&&=:sT#5@2LV9Z^O-QE7ql+f`'EaQ/pJ\\rSV<4ZAeGMobDLH.i9d-7gU-T0+m,,`u?=Yo!^T2(G;jWGV9QX88*]?8gbI#^%@Mi!VdSE+4I:g*"8T"itBKPkf.VgOlm3\XO\9Gk(mMhQ:d\Itkq@bLT_#nHq:Bn.3b~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1522
>>
stream
Gb!SkgMYb*&:O:SbgUH5,daM^K'b2=oTCi<BUX1X0*)..U2;rSW)Ak>p?Qq$MsV@iV'\GL)T=Gt*6oN6B?kHE=SU8.mj&']4bDWS$IjgX'W=Z0(k%(HUVYO_D32J+r5amH"HH8EcZ4"GrgVP:s/O`PNR5(=[#iisXkKYWGk+<1%SA&Scd#n5BE=lp`m&.D2Qf0WETQZ!o'O6cJ2j7a7-HIJb`D:aiO[)82l%]-UCb^e(DMYh9=/E)"qoSc+=-OUd!&ml]ijENSTaPX@ur1MFrL_!$11d#HjD;;Y*0fmCdhnC6%+]K*CVRLMj.u#GnTS$4*El*&(rf/s-SO$f+:TX[oQTG]OaVW3_[m..RR:CclEN6jCP:tWD;D-@:d[A%5/d0bWG#HCD"n`**/"IOhlXeMQ;L7IXT!GW#R.9B3jR:lp$igOGL/Nl<=LgAjNR:L+pRX(0iH%K"I6863FN_@mo`k*9%`6X)G$r*RnifH_IXc0d+;2=NGLdf.r2nRdrgc?Seh9Ns1S%BIT-SW-BIY2;uM.=#/$CZ":aNgY_d0$j[;0gVL2$K@"`hO8uWE%cHLJJ9\;5$Rac+3.=KX[OU"Wq\6nk-lSr3PjNo"Fk2)@8P$!P.^W=KU5+\P477QC)7aX[ZTi#;f-c^H7U,0I/5[7PD!Q&j5c80Od=N0:5?Qt_+"B\\_0'b-L7/afBsY@f;Zg*k`Fgr!^-Tt">Ph&Q<`4^jW!VI$c0Y%&@l7'RKPB5n&9<fJQa,Ft<jd3R5WhZVK@T+=d2MLs:PI>A[AgL7HGi:jZC%m79]`'$5ito1C6OE#H@>Q-$Rb"C;/TJ>!O8^K3_\;)CV\>tIC(66edS>j)CBX0)%M4[@TegdV0c%6d3Ht%,YoL;W*:?'+:>UsNNS&G[r2;r09rKs)8<FNE7I=cS/ifg]Q!7"UnbelAc,D8IDUsJ%g^]OnPj7P8/$/XXD[7aHo]OBFtttb!\rYIkR0n?8kpP#jJ42F2J*+?':CcGS;S923jl+6Q,j3MP"np"U+B)+Po42fdM-F?-S.!]PB4dMXQ*JckZGo49)#<DiQap/C6H6V9PLG^@TSkocNGp+WE@(c1jF_l]V"^^/0;l.;FCce^#OeF6#!SKD[lL#!l)P0`SAcH=L-/`.Xa-4<8?q?0W5N.`!AGcacR=PXZ4NAikOM"[>-fsThRko4gG5RQ58spF`d/l:ls7t1)oKo-gW8[(mkE$[ah?$qZX&gSb>_1,J3gVXMaJt-&l)G4)8rnr*/6s2]k-Lpau'2eFTT;?:_QFN36<i`m2!;Mt>nhjat^0SsSJobXE_`OgQ@tgotQ"XmNd`9N@;*CnBMt6K%]LFBAiRV/7KbhpC%Sh%a;g`F#Q=$U2u4MGIUX+lY_91KVMAF%(J1D#-l'lTGsB*d_RR\S_`P-I_?&/?<oJJ%m0"P=Z/C2HFjP%I%\taAqCTG<K9l_P>@iB2CHROltaW@rfLamkq,jY]i:A,G8.TFThVaZm/C'!W&C7:en:"M!rds!/(:P!)%R^~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1764
>>
stream
Gau0D=``=U&:W67f[3&Vc2e"iG\NUiSAXTZ3/QA'BZ?`Z8>8fj$45CLFOK('"V6a(,a2)9dl&Lk078+u$n,F5ruWinSr_h)PQBeuRK=-j^uu;R2<oVTZ'*Po`::Ca^uZ'rCatMW*u"']=4fbhO'4^Pi`kOMdTI)lZF(SU7LN23JU']gcP6k)!\YjI>_GajRAo6lKIWBZZQg;m_W[+:i#Q-.N,X0u0]JBFUL8Y"0FIA6;596Bni*,Ui%,?>SIHaoF*q_f1kYgg8[qAKPoa!BPn]LJ!K78i=A=AiL,%jDgrT+[K=[@_&<Dl;9fHUk8rOCE14a#:/nLtY5nM)H:gl&f#_I/G7+sB#I#$>q)MOOi7[`^!W:,Fecn.gbJ0<XeFWVA^iOZu!I'6k7HbuP4Z!J%a.u503"a-:b?:>P$ena($KJM<,7BP0<)UCDOiS8J]U9t0j'bjYtM0k2kT%A3\CLOU$(%u<X9T3?0[[-jaDX*9kDr-sI67gA]`2rL"T;M^E=IBAQ!:u&n)0^VcprRPNX:>/G3+j20n-HFhS^RZ40bcgm'OrKU&##!7Eeq7BXN6p6QGFifA-Z.Q5bdt@(V05%V;^de[9iR![.#kHC9)FL<>nrog$gt^l,k%[WeFUN\I@Qj<)0(aptOQ<r],K.lL^tr;Ai`0X!I]BC`:6*I[b@MM4hRrC7h8W&iAgEXRobod?d*g.PXCUQU**"a<o)O6:`34hQMHT*jIFIedB."=N"H<0Aif^lH^Y;?VT5p_&ST8ke<'b2TH)H`BBduQ4YRi$.UegQ*/n^gaZQ:R!['WAtWf.N^.ZHpoYjQ"3k(;?!:I^a&E@Vg];)''B[qV?_DfTlU'(>gI&\/`us0P?QRc^b8/_>]5HHpM%H9!l`Dg?N38Pj9XertG)=Vdp>*HeM.=*QNnraX@hf4<>\/4p;+aR0)*D(fYUnd;SC_4Sie:m[i/[,hoI\SCh%5(LeK_PG8Cq'ia?*=QBFYK!ld-4,<2jE<dD53oHis0U$tL<QO#Mk,i%7oCBp)35G_<_(9l_a72^BrML*B@(bRG'<+-jV4>hC`<0'-'MbR?C``Po?/&P9K/B\Zg%5>7eES:/gZ9-g((X69nnUECY6qJW1Ba<4Ni.-[5[BX-'!<-j[&;dFQXRP%W8Z`EHrf\S*0n"!r/![Dh6P71>LPArb!mC(?"0Fd]V:j;Y_=1q_:(\rlM%k*(^OoAoLJ[:OPNfVX;O\]V(J@CI,d8!&FV_j)&?Gd>C5HZ?,b+[s&E9B?6Z_6u.d+,Q@1(3<uDI6jc16\9Bf!ILs[uU8Q6Srqs8gA=[pR'^\S^8s\<",%sK+ClG.%+YYZ@@AG0BKU#e^BC/&#S@qP>!9IBF2OPAf9ep=:_UuZk7XHh27Ek5pD).L`Fe\3ErQ<Hb!*YUfI9%6u93p?UZ`fj'R0[1$=Z;/g^GkiX?U=%),,3_8_*Q_FiEEM;peXaGYaK6bYBY=<-d$9g%\\"<a`I5^YeH0VB1YQ=978!mM+<BUS&m+YZjpj-qmREd[K^Ia,0`gg!$I`A)u>$(l&gh#l(k0G5B[g9:@cQAJ;?]7#$,?/Z(H?sNI*EoPsH:1A%mbkrcF+HB.nKl]E!h]$CumbP%#`*@VmL@h0%'Z.HNS.pZ)mue];&f(<:Ur-/t/j62IZ>>MXk&L4olRej2<f'"f+`FsX!W#3U)ZbtIM7b:+d=W7Q0&<88(iK$2RikfA@<,(#1:DH%pCn-0%pd5eeGB,ep\Ttc<5*$+6oBSQs)Ln#jo~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2029
>>
stream
Gau`T=``=U&:W67f^VXIPf/.Zn,iR<,Yc]\S2?"ip;&Np6p_M<?6LbscEH[<4Cq,KRSG?cU&Zh>hJ\aT@,RO>:Ho&.5(i)M$C&AC!Rr;/bg,2/D"MR4CJ*>)NlO6hNlMI8%1CM@m726.$kk&.4Hbe!^B?b.ZV^c^Sr1@)*Q#A-E=q0H,E?W@KuIc6cNl=tDJ=a\/M'sDC0)Hr*NN[ZZ]7+rp[Ke@U7*<U24>bEioK.=`nNsW:jj6c:ENX,k7^BUO(8k3Ca#D/EO;'WN7s;I=)OBHK7&Mf)Hr1k`sFA=$&^/U6b<e!)UB>UZY\Q$C8kM+9-g?N>UYA1XR>)!NWES]?bXHa]4%b>`_#_<AljD#eNVL;XPP#u)O:pMnG^\Ufo6YT#UEr+HDR\Z8XdFu?:2;S6K[%L8%5;X)r)Z71QaF1n/&g2DN]#QafUL+'$><UB.b$B@4C<4e-***0J6;7'Zgso8pGN.rj#B(WA2Ap,h.35ErmBX&`;L<kJs/m*S)L:j7JN-('g<6*'CCc?flA`Y[SP\Wh<+PAtM#MR#@^1h&i97cPeZ&mc.;MB#WGF>1E2_:ZH.3MUag(Gqc?r:]JkHJP>I;+I%l'>II&c0g&MeP8?TI%6\:rbF27+^#8%\9'FQWQL^`0]VQ:%l\,-ZF'hE+qm0#-\qah6l:^OHE%#%FjF43DYY(m3Rc\&[8K*7T!meb79[::!-)T?Xhh5b[Wu%8<89<GC%=]+!-)FApPP7rZ"[jeKfa4aD1fLc4CfBr]M[pI0Gj(&@bmD%BOB_*iCL<\AAiiN*:i,W?=J/QLq`IYu=B,lEZ]9^[mh&FOA&P_o>P[qG!Fg@BTH*DSf<Tsp'tb.IE6+S.jBQE<.]lqY":7ASam#us=hlZ7f8D'5h*Xueg/I$&r2kV18"g4:Z1o9H6:D;,=eI6:&!;\gK3q&pU:!#c)6G'J-!Zfq<W<@V;L_X#a.:5A[sk8@?D'&u>M&K4Yl&>o#L'D./Cc;*Vg+'I;VnO=ANGKa;l`<b5s9uiGO;uk#hp\72XYo'O"Anb4)2)<N@\tGKABS_dMu^uL>1VE%?3U;qR0*XV$>DMRQXCSL+d5-gKmV(C=)AkX)8N/-uh99h)7<+7OQC"0$?<qN)&"$HtQ1JeK:n_fUo-X]kr6W_:-dd#d51h08Q37r6,2j,d,7VL8"-!+k@PmP#-%>M#3Ze'`(NXFW=2_1^[d-O@;0drTjXiB';,e]nlF3mXR][Sn*lIC5H<%4!N$[?:RGT+n-6:"<m*3Af\t]3-o8+GOTIPMX?0nYu5]X$]R_%nQ5V,E%(/dZ(&CQnCTMabf=EaB'#\Tq7.39]MqWW6A3SK6"<9'8H\'?/d.1Y!J4c)#/$7HX*0d7JcVXE#7kA/P@67A\j8dIEL;Q<_!BkPe$3"T#uK;0@.[_o$Ri27RG7me9\8O>qEI4c<h[fo2E0"gmI[,=BRs?*E/L3aR+TO:.Pe&N2#.)JLf0_0-?o;77&`gq9?F0$;sI@NXTn0Q6D8SMU/8I3FNf\=ek\aYcU8fqf+B(==i2kN1_/Vf)p7!4\Aj(i`chC_3leTZ9H7S`SBCAXAT?#&W6_<]J=aRmbAFeV0p'CY"+5"'Tl2QHi3H7X\6TDFq9h+!Ae/i8%YN6qGSn5s[uaudlSZ-<_G;m2l%BtQJ0T+dPOc)`YM`<C(QWJNA_G!V?%0DrHhabgASA59h49c9]dX#d#%.Sg?P[&qcU='n72AW1hPYY1.4$F$ZNjn77"Nc6mAu5N1A/>:ASl:"kZu4$d<pPPDHS\F3f\P,%bEDpD%/^W##itd-:._:$;7aU<]FUVAA15hMBD3r$IgqZZL@bMbe`?NRp8$Sgmb[MlQ0'SUr9-pdoP%]@FOPpTjH?SP<gFLEJ>lAn(!\E<!NoWpj%TPZ6![meVj_[i^6`f?7dWWZ%+0M)B9i8dF+Y#keFF0f/F4o']C6SN@eOj_o0M2`tC7,@&$^tJ<N61;0mYc-?&htK3G?`SNf:@R+2aE5H!hMhu%H`'Is'!BEt;^!#FGHD?~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1674
>>
stream
Gb!#[D,]1K&H;*)YohG<U0+^;bC$n8jEc#%=,s(12o?%K*0HUJQEd!fnEY#SG="Vg42[QjE@e;:ofTk?>u@ir6d+q;"n6C:UYdLs#1I@\S8ajEL@Nd)[:h/V1#`?f^`UE<^@4/.ZO!sQi)/_+_KGtbg"]1N%\,3h`rfFHh"QX:J`t*_M[S95m"W$CQ&nLPDYsAEdE$uA46F+B:hm=7<APtXc1$<S\Js$bm0_PZ<7O.M(.D?"pY,&"fq#7lZFe)ops?1OT&67+IYQFj\pfWDRLbQ[dd9:rZ>C7PZ]2j3h#Rg!AKEf@nK#VG@KhBo.@78q,b1hb<&#B-567Sn80YJ@#&0rQ<*6q*lO6hA9`cE3ifEaIA1H,kl?K23CR#^R<LAqN0YN&%lTJ8dZH55d0<Lg[2$]C_?mDut$E`5qJ@9(UY_[CAn`gB,?VfOmHTN*PIQdW<Dn=!2q"3q!pZ'HG0;$:OT>pmPYhIlld17^doc5;UB$_AnrV?K[N(m*TZ$G(_@A>bX/M6"oN-*fn:K[R^bn4SYl:e8<#YJmP:R1Dlb`j[.A^,;p[S\nq`-FLBfc"*Lf@pq?h`r/-bJm&uSt)jUF3:h%cUrKiS2A_*NU$q:Xlg3dhG(?:mA$),50n$]cVn+7*p_hL&VD$fD.GEj`@=2*Z9*l*8'WeSI$<mWOchJ1Au^b-"-(+,b*KOrA0S3c-53o@$/0o304h4'aFfo7%!,[-b'o!JL)XAWY+]:%J1F#<E,-'J0qZ'CS17*n^bJo]%lt'@%8_'m$mJ`_9VA^Q>"k#a0]iJs.J,)fLKEef?>hRqpP-#tB+M>hn2a19I%b055"ZnW+Nhm''^A[:X$XY[l?e)b)Tjm"3kS/3r\*Ae17p8GBtq;W((foF:0V&'6S"jR"mq*p0C;,2?J6"';eC6^?F[XEgEk?jb5g18o&r?EG0kOXGSR=J6Xt`o8_OXNn-rbB*)MW*4"eREZ!6lb`R*\%B.YGK)6B64(Q&oPVhAuF@P['B[$]ZOAIU2M$^6sq"U=rb9K<omT#U&(#]Ls>$XeshX&6T+jd"&*W'>oW;JUe:N\EJ'\IXh@6H7EdqB567U#K$_:P3h=@f/l+!-B^R#4-QtY+G\7b@DA7o,P/,M0p3]GDYMXi[#QomkAPe\u/WZmie;p?FViiR6(4\"Ic'i5S@S^,&cDKQ(Fs7e4G4f!UFj$6QM6Yfb32ule.?nVU%RrY[&I<]/4*`LE"WR=Z@G:rGXkGX$2-+7Eu59I4Cpj,HuURD@CdE)">skAb@T@:(+2aHuF"I^"P+1=l8ZHL*S3`P2-ko+grP^Jprt>14co-KENp!&eW49]q4;kK6k*'$,%b]Phr\q2BAbWl#%;+;\#j#3(5lPLBuo]#riH!d;rI_,jeL20h1I/fkF*Dl#2Y6R>$QC\2Ws\(@);$=tS^UaFd7HG-.u5*l2)-#^:s(m<lA]PBG(EF&bm@gsqT")n>RTXjC7RBB:PTP1A6KK'<lANJsL6<gE,>,Ddb*6NHF$KuRkZ%HJIL*l'lMR1fV@GRl!Ik6Agfj$A=i*)CC/IH@Cb>`!r%<AH\VN`F!)c;Z4`qCK<!Xk@$1iVsAT?S(-f=;0g)N`D)6(mWaCT74/'$f@D4ZKe.tT441P]6%\7_#-YM7>L=n=YM4:"b&=[Qs_$(3CeVm+&TU@li~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1979
>>
stream
Gau`TGuNHe'`['_if2^:Qk\,`X49WdSVDg)8SCGAUsm8MA1Oj#JLh+!L&f,8n'gRmM4j73>k5&S^$k_J$n-Q\rR[M?m%$EA"Tud'A->R;!@J,VJ'p@GY`dGo`,`E3^uU_67QL$^#c#VO(c5$D)D6eUg^-%D"Q:]q].iUXgEqs@3mD!X&J!&'Rk<U'K#f-8:j[Z3WG&q>]uN,\_K!($<UU7$dm`)hE6bS;W4&AU5uGlNkV-I-cJ33h#c!A3Y<s7=!aO6`QMc_$bt"rnl&6Zn/G!cgNXiN!Y)YCd:)Rp(Y/9Q=QSWL[Vj%IE-Cdc'Q`B+5,p\DC@*mY[P>aGd]Rc,(%PdK)1`ntA<AO(kHSQS<h<btYgENN\Ll1jmUs@o%S.hV6c=\W;g4lj>'=A:s#_m@n]%[53mt2D2Ij#RjlOC?Qb#_52pL'pt0Ce)HO$^@4U'*F5l,shM/YSB5[.'rHg>mK6G3'5K0'pG48[H>Ga+N5VMaZ8#)<?^ap]r_5@;[Yma5UIIisp'GX0HRk[<3sh9aA@mh2B39`?8ZL]@Sf:8bG(No1tu"W/TD_g9`X7[Z-fGKNVnuRnU"P%gK_:V)1GqiHDF/:O[%?%+i:`BT``G:`5e@'1cc@r(EoEN04J&mcoJkU3!:@PaC@Sj*VU:;]cf&*Gk\![jLGIL+*P+f&Vn=Ec!a=R]AOuIc-hI'HP>q_O,dto_jar\:+"G<PIQ-Tf^,CHl\:`q0KMLTKh=(JSC>'I^Z0T6X>UOk9q9.:pMHY5EgYJXe$b'6.Mu$R)CarVcM_E@@G"A*bQhY(0/FKl/V<@LbT<I)k8j-&s^_)&-\cC9SM.PI(Y$s.`N[sB'O/dp?%EAG]irX9j'@Q8=Q0lD$Xc?YRMT.]_quB/]p(3IW!j44VPDQk1#=Zq0h)'_ZR&sh>+]Gr71d%4Xp7]30m1nfEa-[OtlV&e<pZ!0Z+_KW"]WQc]$\c&[IRn#9TZ`Uu"L6AP/s4>)DRYn^ZuHQ)F&Iji5/J(i6>$VuAZ?GUNoJW(pu_KsrV13j1"a]oRi.:Wq2-6IaT,c(]9!,L"6D@/3r!_Bu(i.qn+"*YQGJfSa@..@R*MNI"="e8(HCk=)o"D.*K#$<TXM>&T4EY1;Hg*A0$"0L)@S&t?'L+g,N>(Ab<./=D1?.[Q&T8MiW0EZbTubO>Md)q?rr.(qDs<pPs@$]DM]\/"NU17KMaR]=fUb/(_kR9>d+cdQR+!hCTDZT*fIaGYY!3]q1T?a<1sOmi)J___Du^HFZ3j6*W)6AFtdbU,mqqqcWZH&'cKVI$nk_7:,UUd\e8n6RE4$?kK,`],pcI.7DX-`?2U<Q/L]NeT,($9CD1B3A6JEu]I?^;Ac+b^Gun#3I&;*J)J*fU+/DL8/*:`.Z;`bGIuti$s7eS`+@"b<S07&/41d^7IS,R-F>,pXHA&hcV03[;"1!l/[-,Fl2Mq":h.^.0CD6;YkU%BWXEf[*5o%s8W)V11G:Y[-$ffDEc5`gT']QY#uCf(=YGArLmQIpP4'M2Ss.&lPn_'(/W.)]_5ikUL:$`O\2sN]V%`Ckbf(O,l$d.JA`CE&p/WI&]Rc=&TYr8GQ>VM@R+aW6.AsrNNn]271aaM7E>O.p^Sog=Cpq2TC+j*hZ*NAkPuu?]fb.9HN3Qhro^&c#Q(-#n,J6SWug9%cZhHYmtEN;-^.\hpR7!LBVV/*q7WXTNUrG7]JL'9I_<#8LD=kdBFC4s0b9XgDld:J'::M0e[ecb<`k0[_a4U\[h7c!YoNY7gg,IPP:VW9qgEmQP:4\!%8,kSCXD0]kj^lX3$3*qro2#F!V!eXm3@GU@k"OIZuHj8*(N`C4OCs_\pi&R/@g@Jb].I\IL)id`F?1^a(?$g@*:hhcm&O/BCMroYagmcT$<E4LMcu#J]4j7domA_^-SfJbS*ca;3JdUM,\^1[#&ab=JfMES9V3=hi%MDjC,fW!n\;3VL[OfP*ODi)h-%lZN~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1615
>>
stream
Gb!;e>uT`R'Rf^Wgp_EL,`mZZ;RI#r3\sOA]/W\PCg"0r,St,cdS+iiep5p*^:0:+=U7R*MF(1V8od-kGFrrf[@ol>48nP:!1/,4J%!1:TK`RkBf2T6$eP?@+")5\B?RP`kK^UID7=)!]LJ*VUW\r*fs_rl:_7skQQeh#bXNAs6p//>eW`_(3HdVPrsAP:ROi[T>%R8.Gp]`N-tI(gQa8N&\B[R?*D4md<7c0E9b/eadr<X4c?%TbD(F35J$G&X]=kUEf=!frmb"RNRh;tdX1/>;i:IE/?^!boJ(;gn4??4V\5EX51['7b?P98ghL#o(>c/YFX=7].+)e9A0Q9&X`*e_/+pRe`PRX"sEYPZ/iCAK_)^LY4`e^0O`(+G9Lfj'0h/44:pI2BF]g&1^0eDd@.`E(tB!;B-L>k51f_g<j$t`3$C7UeU=QbTI'c[1s6WDB(@oV<4LoBSqhNC)4@48tmGBVbZ*j4NlY,]r5AH&C;fa3QS$hNJ-#.2hOY%G?L&tAVV?G#;R2$8!r7RUV?[-%>j[A(2O-X=R6eb+rgh#m58q`5Ym6Pr;HZBX#J'mt[bl'1IVNNR=fg2^N?>*kDs&FNWbZdJ7^#OnHK*K[+(7oDKRLa.cNGIQ[>#$_d,_r?!IK"JE_QHmVDS:[Bd?BAGncsHi%+Jt6ej"ALE'<-Yla;rbTJ%ALY(iDGjid.+'Z7!^cKYE7"rhE&+[.$Ld="%\hat8:P[R.ZBYh?89B$2t>QJ^&&/oTBloNTMdn!H_;D"MT!Jd:?[+,uBf0>#?';p8O`9cb,6Q;3ISQsd!<k,<?nW%lRElMB`E>IUmeA#>1WUe5l)BuW!6&-g(u'IS)L(r%#U/d5rt2S0oq87]Qo+qC5pdk]T5UGF&=)6la1U*(Y5$'-5!Bb+,uUjcj%??&>um6[IidI4r6Pe0EYRbiY'd>5h*@NhUQO;\_fGkWIC4KDQM2Y5^0q"#]W@hN@=BHaoQPVh4<GK)nlVklrH!LDH&Xk_d?4fd%R[SrgQ5%!19B'OFGogppYA<q`5(m63@3?>_n]`S#C"*a_@V]MccqW1&W^=LIQcf3r4HgXZYUOtiV&H7E+QQo@H%d'I]I<Z8*^[j:ui34:+.Xtn2_C4@DD*LOh2316bP!Z-&GQF:Xi7g'MgE2hdHN+<gg;V"!,X@3>/uX)>^%ML[a6rM>p"LFNJp#@&k&1HZ>3;=KQOKeZ>-?UoD3YOMD(fgPX(:"l#icdqBD'G;hB=a,B!*Jd/.NL9_3d-4:]$[Rg5]I[G3%X^&I_%K,Q,[*>-LhcMkiKUI3r8T+NY`^QPVS<5NLsgn(m2(WPs>ER&Z7BSgRu.`K2#!%)2H4]c)aqOH4q4qsI%97u/W)q@u=>Bm\d!R8u!XE\\'BS>9</cjYC?EoJgfH@\P,X['pBXcZM1"a]F$kRa#6+YoTZX<&V<KC!jYaJ>p^g><I'P<9S"1!;311Gn]"U:D\L:cNGZ(;6_/Z"R0LlUr*ZLQkm&-u&Eof%$P]g#aXd1\9OEi<qledMdNbT_/"fJ$H9umMcj+KJWhMI3GUCI)#GCAR%_-$j'<OPtCA^%Brs\Bo)uGBMl<&4X/0AQY]3%rXXVNqFMBkr!0]:V,[~>endstream
endobj
xref
0 24
0000000000 65535 f
0000000061 00000 n
0000000132 00000 n
0000000239 00000 n
0000000351 00000 n
0000000456 00000 n
0000000661 00000 n
0000000866 00000 n
0000000943 00000 n
0000001148 00000 n
0000001231 00000 n
0000001437 00000 n
0000001643 00000 n
0000001849 00000 n
0000002055 00000 n
0000002125 00000 n
0000002417 00000 n
0000002517 00000 n
0000004176 00000 n
0000005790 00000 n
0000007646 00000 n
0000009767 00000 n
0000011533 00000 n
0000013604 00000 n
trailer
<<
/ID
[<0292bdcceb39b20ff85637fe2f02e122><0292bdcceb39b20ff85637fe2f02e122>]
% ReportLab generated PDF document -- digest (opensource)
/Info 15 0 R
/Root 14 0 R
/Size 24
>>
startxref
15311
%%EOF

View File

@@ -0,0 +1,404 @@
# Les 15 — MCP (Model Context Protocol)
## Slide Overzicht (Klas A — 3 uur fysiek, demo + hands-on)
**Lesvorm:** ±1 uur theorie + live demos (veel koppelen, weinig zelf coderen), daarna ±1,5 uur hands-on
**Demo-MCPs:** GitHub MCP, Supabase MCP, Vercel MCP (bestaand) + Polderfest MCP (eigen)
**Vervolg op:** Les 13 — Agents (Polderfest agent met 5 tools)
**Aansluit op:** Les 16 — RAG + Embeddings
---
## Blok-structuur
| Blok | Onderwerp | Slides |
|------|-----------|--------|
| 1 | Welkom + Vandaag | 12 |
| 2 | Wat is MCP + ecosysteem | 36 |
| 3 | Vier MCPs koppelen — live | 710 |
| 4 | Polderfest MCP — eigen server | 1113 |
| 5 | LIVE — De drie chats workflow | 1417 |
| 6 | Lesopdracht + Extra + Vooruitblik | 1820 |
---
## Slide 1: Title
### Les 15 — MCP
**Visual:**
- Background: CREAM
- "Les 15" in BLUE
- "Model Context Protocol" in BLACK
- Subtitle: "Geef AI-clients superkrachten via standard-tools"
- Iconen: GitHub + Supabase + Vercel + Polderfest
---
## Slide 2: Vandaag
### Drie blokken + climax
**Wat we vandaag doen:**
1. **Theorie** — wat is MCP, waarom bestaat het (kort)
2. **Vier MCPs koppelen** — GitHub, Supabase, Vercel, Polderfest
3. **De drie chats workflow** — alles samen aan het werk
**Climax (in drie chats):**
- Chat 1: GitHub MCP maakt repo + voegt remote toe
- Chat 2: Polderfest features bouwen via Cursor + Supabase MCP
- Chat 3: Vercel MCP project + deploy
Vandaag is dus meer koppelen en zien dan zelf coderen.
---
## Slide 3: Wat is MCP
### Eén standaard, vele clients
**Model Context Protocol legt vast:**
- Hoe AI-clients verbinding maken met tools en data
- Hoe servers tools, resources en prompts beschrijven
- Hoe data heen-en-weer stroomt over een transport
**Open spec — geen vendor lock-in:**
- Gestart door Anthropic (november 2024)
- Cursor, Claude Desktop, OpenAI, Windsurf, Zed ondersteunen het
**Analogie:** USB-C voor AI. Eén stekker, elke laptop.
---
## Slide 4: Het probleem
### Voor MCP
| Voor MCP | Met MCP |
|----------|---------|
| Cursor: extension API | Eén MCP-server |
| Claude Desktop: plugins | Cursor: werkt |
| ChatGPT: GPTs/Actions | Claude Desktop: werkt |
| Windsurf: eigen API | ChatGPT: werkt |
| Zed: eigen API | Windsurf: werkt |
| **= 5 integraties** | **= 1 integratie** |
Dezelfde tool, één keer bouwen, overal beschikbaar.
---
## Slide 5: Architectuur + primitives
### MCP in één plaatje
**Drie rollen:**
| Rol | Voorbeeld |
|-----|-----------|
| **Host** | Cursor, Claude Desktop |
| **Client** | MCP library (zit in host) |
| **Server** | GitHub MCP, jouw Polderfest |
**Drie primitives:**
| Primitive | Wat |
|-----------|-----|
| **Tools** | functies (AI doet iets) |
| **Resources** | read-only data |
| **Prompts** | templates / persona's |
Vandaag focus op tools.
---
## Slide 6: Het MCP-ecosysteem
### Honderden servers beschikbaar
**Top servers:**
| Server | Wat je krijgt |
|--------|---------------|
| GitHub | issues, PRs, repos, commits |
| Supabase | DB queries, schema, migrations |
| Vercel | projects aanmaken, deploys |
| Filesystem | lees/schrijf in folders |
| Slack / Linear / Jira | werk-tickets |
| Brave Search / Notion / Postgres | enz. |
Vandaag koppelen we **vier** in Cursor: GitHub, Supabase, Vercel — en onze eigen Polderfest.
---
## Slide 7: Vier MCPs vandaag
### Het overzicht
| MCP | Type | Auth |
|-----|------|------|
| **GitHub** | HTTP (remote) | Bearer (PAT) |
| **Supabase** | HTTP (remote) | Bearer (sbp_-token) |
| **Vercel** | HTTP (remote) | OAuth (browser-flow) |
| **Polderfest** | HTTP (lokaal) | geen (jouw eigen server) |
**Alle vier in één `mcp.json`** — zelfde patroon, andere URLs.
**Visual:** vier logo's met een pijl naar Cursor.
---
## Slide 8: De `mcp.json` config
### `~/.cursor/mcp.json` — globaal
```jsonc
{
"mcpServers": {
"supabase": {
"type": "http",
"url": "https://mcp.supabase.com/mcp?project_ref=<jouw-ref>&read_only=true",
"headers": { "Authorization": "Bearer sbp_..." }
},
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ghp_..." }
},
"vercel": {
"type": "http",
"url": "https://mcp.vercel.com"
},
"polderfest": {
"type": "http",
"url": "http://localhost:3000/api/mcp"
}
}
}
```
Eén patroon: `type: http` + `url` + optioneel `headers`. Vercel doet OAuth.
---
## Slide 9: Tokens regelen
### Drie tokens, één OAuth
**GitHub PAT (fine-grained):**
- github.com → Settings → Developer settings → Personal access tokens → Fine-grained
- Repo-permissies: Contents + Issues + Pull requests (read+write)
- Token begint met `ghp_...`
**Supabase Personal Access Token:**
- supabase.com → Account → Access Tokens → Generate
- ⚠️ Account-level — NIET project anon key / service role
- Token begint met `sbp_...`
- `project_ref` haal je uit Supabase project URL
**Vercel:** geen token vooraf. Bij eerste gebruik in Cursor → OAuth-prompt → toestaan.
---
## Slide 10: Live koppelen + check
### Vier stappen
**1.** `mkdir -p ~/.cursor && nano ~/.cursor/mcp.json`
**2.** Plak de JSON van slide 8 + vul je tokens in
**3.** **Volledig herstart Cursor** (`Cmd+Q`, opnieuw openen)
**4.** Open een chat — onderaan MCP-indicator → moet **4 servers groen** tonen
**Voor Vercel:** klik op "Needs login" → OAuth-flow → autoriseer
**Visual:** screenshot van 4 groene indicators.
---
## Slide 11: Polderfest MCP
### Onze eigen server
`polderfest-mcp-finished.zip` bevat een complete Next.js-app:
```
polderfest-mcp/
├── app/
│ ├── api/mcp/route.ts ← MCP server zelf
│ ├── lib/supabase.ts ← Supabase client
│ ├── page.tsx ← landingspagina
│ └── layout.tsx
├── package.json ← mcp-handler + sdk + zod
├── .env.example
└── README.md
```
**Vier tools:** `searchBands` · `getStageSchedule` · `festivalStats` · `getWeather`
---
## Slide 12: Code-tour
### Eén tool — anatomie
```typescript
server.tool(
"searchBands",
"Zoek bands op query, genre, of dag.",
{
query: z.string().optional(),
genre: z.string().optional(),
day: z.enum(["vrijdag","zaterdag","zondag"]).optional(),
},
async ({ query, genre, day }) => {
let q = supabase.from("bands").select("*").limit(50);
if (query) q = q.ilike("name", `%${query}%`);
if (day) q = q.eq("day", capitalizeDay(day));
const { data } = await q;
return { content: [{ type: "text", text: JSON.stringify(data) }] };
},
);
```
**Bekend:** Zod (uit Les 12/13). **Nieuw:** `server.tool()` + content-array.
---
## Slide 13: Lokaal draaien
### Drie commando's
```bash
unzip polderfest-mcp-finished.zip
cd polderfest-mcp-finished
npm install
cp .env.example .env.local
# vul SUPABASE_URL + SERVICE_ROLE_KEY (uit Les 11/12)
npm run dev
```
- Landingspagina: `localhost:3000`
- MCP endpoint: `localhost:3000/api/mcp`
- Polderfest entry zit al in `mcp.json` — herstart Cursor → 4 servers groen
---
## Slide 14: De drie chats workflow
### Demo overview — alles samen
**Drie aparte Cursor-chats. Elke chat = één doel:**
| Chat | MCP | Doel |
|------|-----|------|
| **Chat 1** | GitHub MCP | Maak GitHub repo + voeg toe als remote |
| **Chat 2** | Polderfest + Supabase MCP | Bouw een nieuwe feature in onze MCP-server |
| **Chat 3** | Vercel MCP | Maak Vercel project + deploy + env vars |
**Resultaat:** complete deploy van een nieuwe feature, vanaf je editor, zonder browser of CLI-commando's.
**Visual:** flow-diagram drie chats → één live productie-URL.
---
## Slide 15: Chat 1 — GitHub MCP
### Repo aanmaken + remote toevoegen
**Cursor chat 1 — prompt:**
```
Maak een nieuwe public GitHub repo "polderfest-mcp" aan
via GitHub MCP. Voeg toe als remote `origin` aan deze folder.
Push de main branch.
```
**Wat er gebeurt:**
- GitHub MCP roept `create_repository` aan → krijgt URL terug
- Cursor draait `git remote add origin <url>` in terminal
- Cursor draait `git push -u origin main`
- Open github.com → repo staat er met onze code
**Geen browser. Geen `gh repo create` zelf typen.**
---
## Slide 16: Chat 2 — Polderfest features
### Bouw via Cursor + Supabase MCP
**Cursor chat 2 — prompt (nieuwe chat!):**
```
Ik wil een review-systeem voor de Polderfest MCP.
Stap 1: vraag Supabase MCP om een tabel `band_reviews` te maken
(id, band_id FK naar bands, score 1-10, comment, created_at)
Stap 2: voeg twee tools toe aan @file:app/api/mcp/route.ts:
- addReview(bandName, score, comment)
- getReviews(bandName)
Gebruik dezelfde patroon als bestaande tools.
```
**Test live in dezelfde chat:**
- *"Geef Maud and The Cathedrals een review: 9, episch."*
- *"Toon alle reviews voor Maud and The Cathedrals."*
→ Polderfest MCP voert addReview en getReviews uit.
---
## Slide 17: Chat 3 — Vercel MCP
### Project + deploy + env vars
**Cursor chat 3 — prompt (nieuwe chat!):**
```
Maak via Vercel MCP een nieuw Vercel-project voor mijn
GitHub repo `polderfest-mcp`. Voeg toe als env vars (production):
- SUPABASE_URL = <plak>
- SUPABASE_SERVICE_ROLE_KEY = <plak>
Daarna: deploy naar productie en geef me de URL terug.
```
**Resultaat:** je krijgt een productie-URL terug — `polderfest-mcp-<scope>.vercel.app/api/mcp`.
**Tot slot:** vervang in `mcp.json` de polderfest-URL `localhost:3000` door de productie-URL. Herstart Cursor → werkt nu **zonder** dat npm run dev draait.
---
## Slide 18: Nu jullie — Lesopdracht
### Doorloop de hele flow zelf
**Stappen (basis, ±1 uur):**
1. Pak `polderfest-mcp-finished.zip` uit + `npm install` + env vars
2. `npm run dev` — test lokaal
3. Voeg vier MCPs toe aan `~/.cursor/mcp.json` (template in lesbestanden)
4. Herstart Cursor — check 4 indicators groen
5. **Chat 1:** GitHub MCP → repo aanmaken + remote
6. **Chat 2:** Test Polderfest MCP met een paar vragen
7. **Chat 3:** Vercel MCP → project + deploy
8. Update mcp.json met productie-URL → herstart Cursor → werkt zonder localhost
**Geen deliverables.** Gewoon doen tot het werkt.
---
## Slide 19: Extra werk
### Voeg een eigen feature toe
**Voor wie sneller is.** Doe wat Tim in Chat 2 deed — voor je eigen idee.
**Patroon:**
1. Supabase MCP → tabel + seed-data via Cursor
2. Cursor → genereer nieuwe tool(s) in `route.ts`
3. Push → Vercel deploy't automatisch (omdat het project gekoppeld is)
4. Test in Cursor chat
**Ideeën:**
- `addReview` + `getReviews` (zoals de demo)
- `addToWishlist` + `getMyWishlist`
- `rateStage` + `getStageRatings`
- `addNote` + `getNotes`
**Geen deliverables.**
---
## Slide 20: Volgende les
### Les 16 — RAG + Embeddings
**Wat we gaan bouwen:**
- PDF Q&A-app from scratch
- Embeddings + vector search (pgvector)
- AI antwoorden op basis van eigen documenten
**Voorbereiding:**
- Polderfest MCP staat live op Vercel
- Vier MCPs gekoppeld in Cursor — github, supabase, vercel, polderfest
- Supabase project blijft draaien — we breiden uit met pgvector
**Visual:** document → vector → AI.

BIN
Les15-MCP/Les15-Slides.pdf Normal file

Binary file not shown.

BIN
Les15-MCP/Les15-Slides.pptx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
,sharp-zealous-planck,claude,21.05.2026 11:11,file:///sessions/sharp-zealous-planck/.config/libreoffice/4;

View File

@@ -1,412 +0,0 @@
# Les 16 — MCP (Model Context Protocol)
## Docenttekst (Klas A — 3 uur, fysiek, demo-driven)
**Les:** 16 van 18
**Onderwerp:** MCP — eigen server bouwen voor AI-clients (Cursor, Claude Desktop)
**Duur:** 180 minuten
**Demo-app:** `mcp-polderfest` — TypeScript MCP server tegen Polderfest Supabase
---
## VÓÓR DE LES — Setup (45 min)
1. Test je werkende `mcp-polderfest` op laptop — alle 4 tools + 1 resource werken
2. Cursor + Claude Desktop beide klaar met config voor demo-server
3. Open MCP Inspector in tab als backup
4. Browser tabs: modelcontextprotocol.io, docs.cursor.com/context/model-context-protocol, github.com/punkpeye/awesome-mcp-servers
5. Backup screenshots: Inspector UI, Cursor MCP-config, Claude Desktop MCP-icon
6. Test Supabase env vars werken via stdio (server kan crashen als env niet doorgegeven)
---
# HET SCRIPT
## BLOK 1 — Welkom + Terugblik + Waarom MCP (15 min)
`[SLIDE 1 — Title]`
**Vertel:** "Welkom bij les 16. Vandaag een hot topic — Model Context Protocol. MCP. We bouwen onze eigen MCP server."
`[SLIDE 2 — Terugblik]`
**Vertel:** "We hebben al heel veel gedaan. AI SDK, tool calling, agents, RAG, deployen naar Vercel. Allemaal in onze eigen Next.js app.
Maar denk eens na. Die handige tools die je hebt gebouwd — `searchBands`, `getStats`, RAG search — die zitten **vast aan jouw app**. Wil je dezelfde tool in Cursor? Apart bouwen. In Claude Desktop? Nog een keer. In een script dat 's nachts draait? Drie keer.
MCP lost dat op. Eén protocol, alle AI-clients begrijpen het. Bouw je tool één keer, gebruik 'm overal."
`[SLIDE 3 — Planning]`
**Vertel:** "Drie uur. 50 min theorie, vier demo's, lesopdracht en huiswerk."
---
## BLOK 2 — Theorie MCP (35 min)
`[SLIDE 4 — Wat is MCP]`
**Vertel:** "Model Context Protocol. Open standaard, gelanceerd door Anthropic in november 2024. Wat is het idee?
AI-clients — dat zijn programma's zoals Cursor, Claude Desktop, ChatGPT. Die noemen we **MCP clients**.
Tools en data-bronnen — JOUW code — zijn **MCP servers**.
Het protocol regelt hoe ze met elkaar praten. Als je het eenmaal volgt, werkt jouw tool in **elke** MCP-client. Cursor leest 'm, Claude Desktop leest 'm, jouw eigen custom client leest 'm.
De vergelijking die overal gemaakt wordt: USB-C voor AI tools. Eén standaard connector, werkt overal."
💬 *Vraag: 'Wie betaalt voor het protocol?'*
**Antwoord:** "Niemand. Open standaard. Anthropic heeft het in elkaar gezet, alle code is MIT-licensed op GitHub. Cursor, OpenAI en anderen hebben het overgenomen omdat het simpel is en werkt."
`[SLIDE 5 — Architectuur]`
**Vertel:** "De architectuur. Drie rollen. Client — de AI-assistent. Server — jouw code. Transport — hoe ze praten.
Twee transports. **Stdio** is de default — server runt als child-process van de client. Communicatie via stdin/stdout, JSON-RPC berichten. Snel, lokaal, geen netwerk nodig.
**HTTP/SSE** is voor remote — server runt op een cloud, AI client praat over HTTP met Server-Sent Events. Voor productie-deploys.
Onder de motorkap: JSON-RPC 2.0. Standaard sinds 2010, niks nieuws, robuust."
`[SLIDE 6 — Bestaande servers]`
**Vertel:** "Eerst — je hoeft niet alles zelf te bouwen. Er is een heel ecosysteem.
Anthropic heeft officiële servers voor de basics. Filesystem, GitHub, Slack, Postgres, Puppeteer. Allemaal via npm te installen.
Community heeft er 500+ gebouwd. Linear, Notion, Asana, Figma, Stripe, YouTube. Daar is een aware list voor — punkpeye/awesome-mcp-servers op GitHub.
`*[Toon Cursor config voorbeeld op slide]*`
In Cursor laad je ze via `~/.cursor/mcp.json`. Eén JSON met server-naam, command, args. Restart en de tools zijn beschikbaar in jouw chat.
Voor mij persoonlijk: GitHub MCP en filesystem MCP gebruik ik dagelijks in Cursor. Scheelt enorm veel context-switchen."
`[SLIDE 7 — Eigen server bouwen]`
**Vertel:** "Hoe maak je er zelf één. Anthropic heeft een TypeScript SDK. `npm install @modelcontextprotocol/sdk`.
`*[Wijs naar code op slide]*`
Drie regels. Eén — maak een McpServer. Twee — registreer een tool met naam, beschrijving, schema, execute-functie. Drie — connect een transport.
Dat is het. Echt. De rest is gewoon je tool-code."
---
## BLOK 3 — Demo 1: SDK + eerste server (25 min)
`[SLIDE 8 — Wat we bouwen]`
**Vertel:** "Vandaag bouwen we een MCP server voor de Polderfest-data. Vier tools — searchBands, getStats, addFavorite, getBandByName. Eén resource — bandenlijst. Eén prompt-template — daily recap.
Werkt straks in Cursor én Claude Desktop. Eén server, twee clients."
`[SLIDE 9 — DEMO 1]` `[SCHERM: terminal + editor]`
```bash
cd ~/novi/novi-lessons/Les16-MCP
mkdir mcp-polderfest && cd mcp-polderfest
pnpm init
pnpm add @modelcontextprotocol/sdk zod
pnpm add -D typescript @types/node
```
`*[Maak tsconfig.json en package.json type: module]*`
**Vertel:** "TypeScript config: `Node16` module resolution. Belangrijk. Anders krijg je `cannot find module` errors door .js extensies in imports."
`*[src/index.ts schrijven — minimal server]*`
```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "polderfest-server",
version: "1.0.0",
});
server.tool(
"searchBands",
"Zoek bands op dag, stage of genre",
{
day: z.enum(["Vrijdag", "Zaterdag", "Zondag"]).optional(),
stage: z.string().optional(),
},
async ({ day, stage }) => {
const bands = MOCK_DATA.filter(b =>
(!day || b.day === day) && (!stage || b.stage === stage)
);
return {
content: [{ type: "text", text: JSON.stringify(bands, null, 2) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Server started");
```
**Vertel:** "Let op `console.error`, NIET `console.log`. Stdout is gereserveerd voor het protocol. Logging via stderr."
```bash
pnpm tsc
```
**Vertel:** "Build genereert `dist/index.js`. Als je 'm runt direct met `node dist/index.js`, lijkt er niets te gebeuren — server wacht op JSON-RPC over stdio. Lijkt verkeerd, maar is correct."
`*[Inspector starten]*`
```bash
npx @modelcontextprotocol/inspector node dist/index.js
```
`*[Browser opent localhost:5173]*` `[SCHERM: browser]`
**Vertel:** "MCP Inspector. Jouw beste vriend tijdens dev. Links: lijst tools. Rechts: call uitvoeren.
Klik searchBands. Vul day: Zaterdag. Call Tool. Resultaat: JSON met bands. Werkt."
---
## BLOK 4 — Demo 2: Laden in Cursor + Claude (20 min)
`[SLIDE 10 — DEMO 2]` `[SCHERM: editor + cursor]`
**Vertel:** "Nu laden we onze server in Cursor."
`*[Open ~/.cursor/mcp.json]*`
```json
{
"mcpServers": {
"polderfest": {
"command": "node",
"args": ["/Users/tim/novi/novi-lessons/Les16-MCP/mcp-polderfest/dist/index.js"]
}
}
}
```
**Vertel:** "Absolute path, geen tilde, geen relative. Restart Cursor. Cmd+Q en opnieuw."
`*[Cursor herstart, Settings → MCP]*`
**Vertel:** "Server zichtbaar, groen vinkje. Test in chat."
`*[Cmd+L in Cursor]*` "Welke bands spelen zaterdag op de Beach Stage?"
`*[Cursor roept tool aan, geeft antwoord]*`
**Vertel:** "Werkt. Dezelfde tool nu in Claude Desktop laden."
`*[Open ~/Library/Application Support/Claude/claude_desktop_config.json]*`
```json
{
"mcpServers": {
"polderfest": {
"command": "node",
"args": ["/Users/tim/novi/novi-lessons/Les16-MCP/mcp-polderfest/dist/index.js"]
}
}
}
```
**Vertel:** "Identieke JSON, andere file. Claude Desktop herstart."
`*[Claude Desktop herstart, vraag stellen]*` "Welke jazz bands spelen er?"
**Vertel:** "Eén server, twee clients, geen duplicatie. Dit is de kern."
---
## BLOK 5 — Pauze (15 min)
`[SLIDE 11 — Pauze]`
`*[Reset server lokaal — switch naar werkende Polderfest backup voor demo 3]*`
---
## BLOK 6 — Demo 3: Polderfest server (30 min)
`[SLIDE 12 — DEMO 3]` `[SCHERM: editor]`
**Vertel:** "Nu echte data. We koppelen server aan Supabase."
```bash
pnpm add @supabase/supabase-js dotenv
```
`*[Voeg supabase client toe, env vars]*`
```typescript
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_KEY!);
```
**Vertel:** "Env vars komen uit Cursor mcp.json — daar geef je ze door."
`*[Pas Cursor config aan met env block]*`
```json
{
"mcpServers": {
"polderfest": {
"command": "node",
"args": ["/full/path/dist/index.js"],
"env": {
"SUPABASE_URL": "https://...supabase.co",
"SUPABASE_KEY": "eyJ..."
}
}
}
}
```
`*[Pas searchBands aan naar echte query]*`
```typescript
async ({ day, stage }) => {
let q = supabase.from("bands").select("*");
if (day) q = q.eq("day", day);
if (stage) q = q.eq("stage", stage);
const { data, error } = await q.limit(20);
if (error) {
return {
content: [{ type: "text", text: error.message }],
isError: true,
};
}
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
```
**Vertel:** "Errors retourneer je als data met `isError: true`. AI weet dan dat het mis ging. Niet `throw` — dat crasht je server."
`*[getBandStats tool toevoegen]*`
`*[addFavorite write-tool toevoegen — let op user-intent in description]*`
`*[Build, restart Cursor, test:]*` "Hoeveel jazz bands zijn er in totaal?" → AI roept getBandStats aan, antwoordt met aggregaten.
💬 *Vraag: 'Hoe weet AI dat hij Supabase tool moet gebruiken en niet z'n eigen kennis?'*
**Antwoord:** "Description. AI ziet 'Zoek bands op dag, stage of genre' en denkt 'oh, dat is wat de gebruiker vraagt'. Bij vage descriptions kiest AI vaak fout. Daarom zijn descriptions zo belangrijk — net als bij tool calling in Les 12."
---
## BLOK 7 — Demo 4: Resources + Prompts (20 min)
`[SLIDE 13 — DEMO 4]` `[SCHERM: editor + cursor]`
**Vertel:** "Naast tools heeft een MCP server twee andere primitieven. Resources en prompts."
`*[Resource toevoegen]*`
```typescript
server.resource(
"bands-list",
"bands://list",
async () => {
const { data } = await supabase.from("bands").select("*");
return {
contents: [{
uri: "bands://list",
mimeType: "application/json",
text: JSON.stringify(data, null, 2),
}],
};
}
);
```
**Vertel:** "Resource is read-only data. AI mag het inladen als context. Verschil met tool: tool wordt aangeroepen tijdens chat. Resource is statische data die je in chat invoegt."
`*[In Cursor: @polderfest:bands-list]*`
**Vertel:** "Cursor toont autocomplete voor resources. Selecteer, resource wordt in context geladen. Vraag erover — AI heeft alle data."
`*[Prompt toevoegen]*`
```typescript
server.prompt(
"daily-recap",
"Genereer een samenvatting van een festivaldag",
{ day: z.string() },
({ day }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Geef een recap van ${day}:\n- Top 3 acts\n- Drukke momenten\n- Bijzonderheden`,
},
}],
})
);
```
**Vertel:** "Prompts zijn herbruikbare templates. In Cursor command palette krijg je een MCP prompt-menu. Selecteer 'daily-recap', vul parameters in, AI start met die exacte prompt.
Nuttig voor team-templates. Code review template, bug report opmaak, vergader-recap structuur. Eén keer schrijven, hele team gebruikt 'm."
---
## BLOK 8 — MCP vs Tool Calling (10 min)
`[SLIDE 14 — Vergelijking]`
**Vertel:** "Wanneer MCP, wanneer gewoon tool calling?
Tool calling — Les 12 — is function in jouw app. Voor app-features. Snel, lokaal, alleen jouw users.
MCP server — Les 16 — is library voor AI-clients. Voor interne dev-tools, voor distributie, voor herbruikbaarheid over meerdere clients.
In productie gebruik je vaak beide. Tool calling voor je product-features. MCP voor je dev-workflow. Geen conflict — twee verschillende contexten."
---
## BLOK 9 — Lesopdracht + Huiswerk (10 min)
`[SLIDE 15 — Praktijk]`
**Vertel:** "Lesopdracht — half uur. MCP server skeleton met één mock-tool. Inspector test. Laden in Cursor of Claude. Test-vraag werkt.
Huiswerk. Vier dingen. A: 3 read-tools naar Supabase + 1 write-tool met user-intent. B: 1 resource. C: laden in beide clients. D: MCP.md met 5 secties — tools, resources, voorbeeld-prompts, setup-instructies voor anderen, één observatie.
Bonus: prompt-template, npm publish, HTTP/SSE variant, of je eigen eindopdracht-data als MCP server."
---
## BLOK 10 — Afsluiting (5 min)
`[SLIDE 16 — Afsluiting]`
**Vertel:** "Wat hebben we gedaan. MCP — open protocol. Tools, resources, prompts. Stdio en HTTP transports. Server in <50 regels TypeScript. Werkt in Cursor, Claude Desktop, custom clients.
Volgende les — les 17 — gaan we externe APIs in de diepte. OAuth flows, webhooks ontvangen, Stripe checkout, Resend email. Veel productie-patronen die je in je eindopdracht direct kunt toepassen.
En les 18, de laatste — Supabase Auth en RLS. Multi-user apps. Echte productie.
Vragen?"
`*[Vragenronde]*`
---
## Veelvoorkomende fouten
| Fout | Oplossing |
|------|-----------|
| `cannot find module` errors | `"type": "module"` in package.json + `.js` in imports |
| Server crash bij `process.env.X` | Env vars niet doorgegeven — check Cursor mcp.json `env` block |
| Cursor toont server niet | Cursor restart (Cmd+Q), absolute path checken |
| AI roept verkeerde tool aan | Description specifieker |
| Inspector werkt niet | Build eerst (`pnpm tsc`) |
| `console.log` breekt protocol | Switch naar `console.error` |

View File

@@ -1,301 +0,0 @@
# Les 16 — Huiswerk
## MCP server uitbreiden + Supabase + MCP.md
**Vak:** AI-Assisted Development
**Deadline:** Voor Les 17 — Externe APIs in diepte
**Inleveren:** GitHub repo + `MCP.md` in root
---
## Doel
Bouw je MCP server uit naar productie-niveau: meerdere tools incl. een write-tool, een resource, geladen in zowel Cursor als Claude Desktop, en gedocumenteerd.
---
## Onderdeel A — Tools naar Supabase (verplicht)
Vervang de mock-data uit de lesopdracht door echte Supabase queries.
### A1 — Supabase client in MCP server
`src/lib/supabase.ts`:
```typescript
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_KEY! // anon key voor read, service voor write
);
```
`package.json` install: `pnpm add @supabase/supabase-js`
### A2 — Drie read-tools
- **`searchBands`** — al gedaan, maak hem nu echt (query Supabase)
- **`getBandStats`** — counts per dag of per genre
- **`getBandByName`** — exact lookup
```typescript
server.tool(
"getBandStats",
"Aantal bands per groep (dag, stage, genre)",
{ groupBy: z.enum(["day", "stage", "genre"]) },
async ({ groupBy }) => {
const { data, error } = await supabase
.from("bands")
.select(groupBy)
.order(groupBy);
if (error) {
return {
content: [{ type: "text", text: error.message }],
isError: true,
};
}
const counts = data!.reduce((acc, row) => {
acc[row[groupBy]] = (acc[row[groupBy]] || 0) + 1;
return acc;
}, {} as Record<string, number>);
return { content: [{ type: "text", text: JSON.stringify(counts, null, 2) }] };
}
);
```
### A3 — Eén write-tool
`addFavorite` — schrijft naar `user_favorites` tabel (uit Les 12 huiswerk).
```typescript
server.tool(
"addFavorite",
"Voeg band toe aan favorieten van gebruiker. Alleen bij expliciete user-intent.",
{
userEmail: z.string().email(),
bandName: z.string(),
},
async ({ userEmail, bandName }) => {
const { data: band } = await supabase.from("bands").select("id").ilike("name", bandName).single();
if (!band) return { content: [{ type: "text", text: `Band '${bandName}' niet gevonden` }], isError: true };
const { error } = await supabase.from("user_favorites").insert({ user_email: userEmail, band_id: band.id });
if (error) return { content: [{ type: "text", text: error.message }], isError: true };
return { content: [{ type: "text", text: `Toegevoegd: ${bandName}` }] };
}
);
```
### Eisen
- [ ] 3 read-tools werken tegen Supabase
- [ ] 1 write-tool werkt en heeft 'alleen bij user-intent' in description
- [ ] env vars worden gelezen vanuit MCP config (zie Cursor config voor `env`)
- [ ] Alle tools getest via MCP Inspector
---
## Onderdeel B — Resource toevoegen (verplicht)
Voeg minimaal 1 resource toe — read-only data die AI mag inladen als context.
Ideeën:
- `bands-list` — volledige bandenlijst (text/plain of JSON)
- `today-schedule` — vandaag's optredens
- `stages-info` — info over de stages
```typescript
server.resource(
"bands-list",
"bands://list",
async () => {
const { data } = await supabase.from("bands").select("*");
return {
contents: [{
uri: "bands://list",
mimeType: "application/json",
text: JSON.stringify(data, null, 2),
}],
};
}
);
```
In Cursor: `@your-server:bands-list` om resource in chat te laden.
### Eisen
- [ ] 1 resource gedefinieerd
- [ ] Test via Inspector: resource lijst en preview werken
- [ ] Test in Cursor: `@server:resource-name` werkt
---
## Onderdeel C — Laden in twee clients (verplicht)
Server moet werken in **zowel Cursor als Claude Desktop**.
### Cursor (`~/.cursor/mcp.json`):
```json
{
"mcpServers": {
"polderfest": {
"command": "node",
"args": ["/absolute/path/dist/index.js"],
"env": {
"SUPABASE_URL": "https://...supabase.co",
"SUPABASE_KEY": "eyJ..."
}
}
}
}
```
### Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json` op macOS):
Identieke JSON structuur, ander bestand.
### Eisen
- [ ] Cursor toont server met groen vinkje
- [ ] Claude Desktop toont MCP-icon, server beschikbaar
- [ ] Test-prompt werkt in beide
---
## Onderdeel D — `MCP.md` (verplicht)
Schrijf in repo-root.
### Sectie 1 — Tools
Tabel met je tools:
| Tool | Description | Type |
|------|-------------|------|
| searchBands | Filter op dag/stage/genre | Read |
| getBandStats | Counts per groep | Read |
| getBandByName | Exact lookup | Read |
| addFavorite | Toevoegen aan favorites | **Write** |
### Sectie 2 — Resources
| Resource | URI | Inhoud |
|----------|-----|--------|
| bands-list | bands://list | Volledige bandenlijst (JSON) |
### Sectie 3 — Voorbeeld-prompts
5 prompts die je tools triggeren — getest in Cursor of Claude Desktop:
```markdown
**Prompt 1:** "Welke jazz bands spelen op zaterdag?"
→ AI roept searchBands({ day: "Zaterdag", genre: "jazz" })
→ Antwoord: ...
**Prompt 2:** "Hoeveel bands per stage?"
→ AI roept getBandStats({ groupBy: "stage" })
→ Antwoord: ...
```
### Sectie 4 — Setup voor anderen
Hoe iemand anders jouw server kan installen:
```markdown
1. Clone repo
2. `pnpm install && pnpm tsc`
3. Maak `.env.local` met SUPABASE_URL en SUPABASE_KEY
4. Voeg toe aan ~/.cursor/mcp.json:
{...config...}
5. Restart Cursor
```
### Sectie 5 — Eén observatie
Iets dat opviel:
- Welke tool koos AI vaker dan verwacht?
- Was de description duidelijk genoeg?
- Verschil tussen Cursor en Claude Desktop in gedrag?
- Eén keer dat AI verkeerde tool koos — waarom?
### Vorm
- Max 600 woorden
- Concrete voorbeelden + tool-calls
- Mag wat informeel
---
## Bonus (optioneel)
### Bonus 1 — Eigen prompt-template
Voeg een `server.prompt()` toe — bv. "daily-recap" of "compare-stages". Documenteer.
### Bonus 2 — Publish naar npm
Maak je package npm-installeerbaar (`bin` in package.json, `pnpm publish`). Anderen kunnen je server installen met `npx`.
### Bonus 3 — HTTP/SSE variant
Naast stdio: maak ook een HTTP/SSE versie voor remote loading. Deploy bv. naar Vercel (zoals Les 15).
### Bonus 4 — Andere data-bron
In plaats van Polderfest: maak een MCP server voor jouw eindopdracht-data. Eigen API, eigen DB. Direct nuttig.
---
## Inleveren
1. **GitHub repo URL** in Brightspace
2. **`MCP.md`** in repo-root
3. **Screenshots:** server werkend in Cursor + Claude Desktop (in MCP.md)
4. **Een korte demo-video (optioneel)** van een chat-vraag
---
## Beoordeling
| Criterium | Punten |
|-----------|--------|
| A — 3 read-tools + 1 write-tool werkend | 3 |
| B — 1 resource werkend | 1 |
| C — Geladen in 2 clients | 2 |
| D — MCP.md compleet (5 secties) | 3 |
| Server runt zonder errors | 1 |
| **Totaal** | **10** |
Voldoende = 6+. Bonus telt mee.
---
## Tijd-indicatie
| Onderdeel | Tijd |
|-----------|------|
| A — Supabase tools (3 read + 1 write) | 45 min |
| B — Resource | 15 min |
| C — Beide clients laden + testen | 20 min |
| D — MCP.md | 30 min |
| **Totaal** | **~2 uur** |
---
## Veelvoorkomende valkuilen
| Probleem | Oplossing |
|----------|-----------|
| Env vars niet gelezen | Check `env` block in Cursor mcp.json |
| Server crash on Supabase call | Check service key (write needs more permissions than anon) |
| AI roept verkeerde tool aan | Description specifieker — wat doet het, wanneer gebruiken |
| Inspector kan server niet vinden | Build eerst (`pnpm tsc`) en check `dist/index.js` bestaat |
| Resource niet zichtbaar in Cursor | Cursor doet niet alles met resources — check Claude Desktop ook |
---
## Tips
- **Test in Inspector eerst** — sneller dan via Cursor heen-en-weer
- **Descriptions zijn cruciaal** — AI kiest op basis hiervan
- **isError: true** bij failures — AI weet dan dat het verkeerd ging
- **Server is gewoon Node** — log via `console.error` voor debug
- **Eindopdracht-tip:** als je MCP server voor je eindopdracht-data maakt, kun je 'm in Cursor laden en tijdens dev al data-vragen stellen

View File

@@ -1,137 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 5 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 12 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/Contents 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/PageMode /UseNone /Pages 11 0 R /Type /Catalog
>>
endobj
10 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607091328+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607091328+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 16 Huiswerk) /Trapped /False
>>
endobj
11 0 obj
<<
/Count 4 /Kids [ 4 0 R 6 0 R 7 0 R 8 0 R ] /Type /Pages
>>
endobj
12 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1844
>>
stream
GauHK96Zjb']/F3m\Zn"-<Bd>+hC=pUrbWf:-8,j2VDJ7*>/bj&#V4Aq<`5,=C=41>YdG$%NlqNQgXh?#6TiJem+t1_!(u.?SGk--=;>7`.c<[31i2)%cNTJXp7?C.KU)*&EXL\R?N1LM%"XE5Z+.%hZ[(9U/[e9j$)@54qnpL*]XXb0<)-[+GtGBafYMn8Bt)?90=YUc2F2]b*WtVfia)B[f7`a=0Tt!.oj*41=,]&15F84hnE@3ANnRSFHbl:r)"1i\uFVVF!"/[`[>>cPa.9fX^:9L?d*Xn]n4?X9TG(7JUCOD`"gd/QiCBY^a*198bo$;]2n@?E0b#XY^rHC(t=fraZE.l@)NR<JVUKdif:s*F3!se2Tuk5*!CpnC#09809tJo=@2*$!Fp^XcXUR*5:;<W=TDJ@@mR`L.^Lpn1O,HOSME8oHYKig(-*)R8FbE9mqu_QPL-j[n=0e`M@9'IA$1V7CD97ua8+!aMA3cCqTaLU(%kPp3tJo&<gfL5^>"53h9OAR)XWggBA\\&O*HU4eiW",m]cbMAQ<7&f9X;>$e-=o[:!J6Y$;/[N$Zbs_$0UI,,K`B9]7=>=*_Gb!Y%])"sh,^\H*!r1DZY9?dCo&WJdT*TMDM$Tp&Skn-aWH(Z@2%2*U"4168'0&esdRR)(6i0!GNJ\_JgIGA-So'bE3^2MQ:2D6*UL-+$n'VtPT\;)#V@-RPjaO9Qgt?c'E6CISi:$8@lA#kjP.en%&V8aGXi#^)4BDflnp8Z&-SA>N[<BIg+W@:0.+.F/B8Em&BBlhjt`MbM%1a/]i9%,sZ.._W_U:(Qp^.SDL8L!H96D-m_kL\*3u0'ar%i$,X5Ccj3<I+Ls[KA;k?\+(N2*9>1**9t,f')lT4M(M.,HQQ=F<Z@mf00m0R/_KLj^V@\.LBc?Ebe]Q1$Y]mGnG2V.G.kZu]Q[[U20N$!_J-#)r)!VpROelD0MMUg>M:FO^q0/k>&P$YaF8qC#7Z<R9m1&4h!Kf'Ha(rg3PrIS907u)/*aQ>,:PD3K!/mtTY8dnc3S=GcQgN$(HRJ`Hm&M"'ZHF_P'VrT,\[!=f+6rXr%S]8-EoGkiOLcD83(2?):UJ2d/I`SK(tJoZq274ceY[OP>8e7UXQl)J6NKK4TViB/7;?9E?/&9*bWSmL\2URCL+k\^ec1a1Je[[">i#tLE`_ifZYBqj$K)X>t"O6Q,05`:.*KB0M0RTU-o.$_-%]-Ysp])n#CbTCo1f*cD0)9X3jB0Y5m1L`(/,HA-KJB@=o'>Z^acu;CLKr=!G>oZ=tEJ*0L`+'\$-83NfqnVVb!0SFKWq,0/!f"#>XdX4\r'3.]XbfglgG2:d<.7"bcISl:F'>LpQb<Y2ZB%,:;ulY19J\(S>In@Tu3Rj<_G$&*J!D3Ks0+Q_\>&5sd+6HWll`l[-E$#@GkGot+G8.m]08&u+%M3_MNae(DhfHdOE0\7OCj@,NqpVca*k?*ZKW6L7h`p#-Y1LW1oGoke$j0/k62Vlo2alqTQqcXem0"804gDG;V(pc#)O7fFEGacMfSL<O%<bmhf.sIY-Do>NU-M:n,mEnD,RI<@S,j'6mZ4CJ_BEZb_EJmnQ59n*GHA:$;ZBRC$U79UcMp#T5TZTBFY/71\Sc8:*SEl/>qQq#sT'cLS@-l&f2hJjM38F]>^U?X@*++BBgq:.GKa;&%b:lX"Lej4=W3Ie1'*R?sHJ)+^r*1n<7?ureN`br?mH;OL5c/dt/R]M4QkXfX2gm((J)_QMfebtC&#28aR`orcq*01NfJa0ZACmk:O`agC1'noWY-N)!bU3?"MTHGaIM=W,:QVR,BfsJX9r%jR?gm2T)u~>endstream
endobj
13 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1049
>>
stream
Gat=)@;jmY&H/b=EAR;3.FP7f;'_F$"YqVp$P_8$Gl9B.W?T_">?T*:3c,].^[ESmP1'n\43OkP'CWWHGL&*uQnHs<(2JG-k4%_h!TPk@!YZtirW^[2dGGOVad#Bt'-/G<#L&A#Jp%mZO@k`TcPYkJTiM5JAuKjLJQ&.<9L3H%q'9F"9#aI25g<3+jYuuA9$W8s?n,a]FoG40%cq7ML[(h^r_6e-K+1P7W/8<WV(.2p)NHG"HhSFb2(R^ES#31C&)Wi8`l!CL"_Y$2SiLGS2^SB:mS)0dY"e;E@K$;i:BK,h^.;-98ZWum^G-4cKNB,Y5+2+SO8F:<Z?6Y)&*PU%\jMX.W&0bH<PfCf7YE=)R3]bkH`qfn/0]q5V\IHb*=8Qg[DuAa#p"f>.5[&Q@Otur+47\`75W#O&u[,(<r4V*Yu1np^mWXLVQk_-6;Z.*MARhFpE%?g=?LR<;*5Rld:02"aIg&3^)#*je6+;,]`Xj3,`m-aN_p<6fc+1N)/!1U90'hO?,PHfC%"`RSij8;RcS++Ol&$uok&C<hr>eU"HdA`FXR6rGE#bAF^CEm)Zt@l_m2WbhUDgD<Fni5fCf<6caT/k66%o%rm]N+_rF$HXt`jQ%".eTo(b>UeLCEKcF:5g/WLUeBL47t46anubh)"9%bMhs@UVarY-m&5W=)fJm86X*YU.($0B)m1A=Oj-l)o>_U8a]]0$;u4VnHtPGQIW<gj3q/g\a*V#+'25VQkfpDRMNRS0L\J)k!9W!77.V'V6F`8bm$MqpEcjVTNOM'D05*30ag@4F`lTIMGF(kQotKfHk8';h-&<>2qPY9molg@B?Brk<8Sn[kr])2kN6qL)[6IaT8V$G>2A3H3W-=:;FI4[Va_snEks`4]$sY:^F3!2j0U0nq``CMHA!;k(dMB'o%1j^9"ai%)9-eQ0E/\Ps)9dfhWB#)/O>d+-E9kP^]QHf?96=g*P\231$hms%kRE_F_tL1;97RrL?U_R\s:cgHXTa]X</`<?.grFOl2]]Rb6Q_!/a"Cjr>KgBaO'!?u1U:&~>endstream
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1967
>>
stream
Gb"/'gMYb*&:N/3%.`Jd2FCB,7![2![V:Y3d<j]`XS7:3(e9E6\WUbf/b8?leC@.WglMa(U9P;''KOc$LI3ehpgN:a?j?+:"FDd>0FjjF@"JY$]nR=Jl`3Iab<=eaYpHf+%`Gj$+(S;#!QQLhc[?"\H:3&\KOdJC?NS8jT#Wt71`S]b(_G#iSeOF8F'Rs9>&P(@F&X`Ij1>6/osK4rR-,-l[LUQ`^*jD&YhH'WW\"KfNp"2k#/.M7^)XqhFK4rj-r"4H>\#lT.U^K2#>Yj7:\-]k$Q[**^!q$_TBW0(L!_W6*Z87!nqHHB@8lX-r7<t7k/+/fPco1P;TX?jVGX)'?5)r3KA&k:H#9NN-*`HN@!6,"/K5oZD;bg<AeoS]FkMeo(AA*26EZ;f=jI_6GqNGGELJn=eW/SlPDgAVm?!D#Hj<j.2QPuI"uHD_)VA<8(;m&OHUq@,[+,dk['LsrS(>Z,EU1W5kCIbdQ(Tn).ee9C%<tM;\jY-E=Nd+.)"TbS^eh0c*EhOkfih\fX298SG>5eUb4c4W/73AU0HYjm:B!bSVi*+Q:o&E*lD[Ndgfs&[*_-LLna:P(E.-HW_NZ6Dht(g-Ql0IHrR.]pNIAK!;%h-<MG9%E4,,Fl&t#UCbQfks]oRo%l?%P&Tu0=#qiDjc?77@f6'N@djICc?9m#mI'9nj)AJAX)[GLm`#VM6tj?Rr)UR<DjYMtC^/@L5-AmmXJP)2i1T\fO$Cutk$43!Xt6LU57g+eXJ_W[tQ,7:GBXTe(tMX#'fGWdM-*n*k$m;s6Db0C7GfikTJ]]h%-in"CL9HN!u?_9.BJo@Cm?^;#ejZ5a[OX@X!hG0EjSN^"s_nEb[?KJ(VJG:)P_NR4Ca(B4)'O^uY7j._h,(Le1ncF8*W!W]@n-%6570Vb1\XKI%PJ\rB2>VO:]-Q"5%LO2$KQhoNXig<n&5r(&<Mk[CQCok[=Wdt46hog_@i^aLgLdhrJ414_0D^1+#iQ?Hp1,[^(<uL8FH.*BUZSFY<=u(LhJo,6f86eMin9'$)Hhdn<XdsU:[d/']Z?gX)BGE0HR=u*8/?4##<q"X7OUblRiSaLHM6g\2,S4>c2=p>3S7TW\=#3sr:Ls`]YloapQj1n]ng^"%(C7I]0?[A>ph3G2s&DL)/i`AB"\=O2N5.gX5%1H3^JdokA<'h2_BgD(:C%*<p\S'/kgDd&!;gYcGH^[Y[sO>k>\AF<_eW2ZG84nE00.%/FYJ8G&G=I0g<Q.DP$*(UfUsX+Iud!nH1h"GCQ@jp(pkLI/U+?ggT77+Ajq#qH7&YiPW)E;qTaXEpESjocCKlg],C/hJ3iMp(8jp8o"^laR!3AC\It']Gb=.h13fYPoWKKcj[T`,rB%rlCtFD@n`l-X-qi=FJ\"9c_08;p1(K92'.)7P9QRLdi/J(m1!HIaMP"'kl2HUf5W9<CdfJM",uc@AgrIY)'pZNqU]?OoBEGAIC&s&&7PIdDg\.D6Z[?);(DInl$P2*<cq`68BL64*JKiD\<XaEL@$"Xni(n[.!n2Yam>"K-aOB$1g$+]5M5AhgMo1HSJE$N#3M99(sh$j:dmF[6/Tn6[\i@+PUl.u-rIF$<tM`f,M$P^^5;ASS(K,`j8@m`<4tmSe-'tJ_nLt*ZTa&`8Qdl/a0IlCGZ;S.AQje7X"5amKnQ!E.ed8d^i'bgpMODQhA]`>MU*@Ti%_Oi;7a:&BHcf_@J^6=Mn;"I,u(_&eo=g@o+o@eW#nADR?qXo(;LUVFK.-V-nl7LP$KO.?<8Gl$Dh'SijtJ\L`;#$h^pCOFKq4N1,ZaK&fN,Va!F@K6H-3<Xb"%.(+#5NiVL64mbGVL]/fk"Cca2!FCH."j45!\[=NoZ+J%PL1ThSLZmne.]3"Yar=UW5'>=1DiTWWMF:i=sLom.1oBa)Qq]#2;>Y:\XUJ;*]Vb*!76C3N\;3/u_j3#:?e!OJN.p2fPAHLQm~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1937
>>
stream
Gb"/'8U&oI&AIa;9FpB/P%TFJ2]t7D[aV`>D4Od'0>>4I/A^=XJlLJ%q@:#lembK?96t8'=X&^=2[a44"W(OQrJWO_=#5*kRK@%S]*7I5+(fHMI-03GKS:uJ"!AY/4i)XSci-3fGB*sU&dfs6E&St'o"jLRE%X+(g_WC9JkElH1PZ$g69!mf&5Ik>LKjX=&i,3CI1h4,f\GB-a/sT<KY;IUB(nbKUnGD!,HBo?QNg:eZODkCq=VW/]IE!c`/C?\iO?BrnY1bR`ZdCQ3'K&dRp%+WVH,/ub"Upi;jcCWm$Zrm_=tWnpmrEf`.+frck,%jg3clMZGXA?F.Gtn-U@.IVuor=H:]?J'@0"bfqT=,4L>Okr6PrtF'(]=bOJa#Y_]flcR7$Un#:\$UQA3B"9VBbN6h!FE_7q5pdcNu#[kUEBLaic0Kb.g^u7=s.'D"kU,rFe,F@&&f2ADh1CDfR*n?Wk*]jb-FXm:RcMkdulTD<DL4o=#0f_$U!nV(D"%?IU#J<4+o?"NSg6m6XH\*?Jpk#_eI%QOO$?Y&.pofRf0lVP?PdN,aV:+\-HTYa/g!C*s:UkW(G'OW"eTG(.fGL3tIZ=]iQ:K_C+tjkg9%^/o@C@>/FA8OoJV[5JnK7pka#<MB#4,aKY\:**?-NUqck_,5bu#9,9$^KIrL,[H)-*e<7EVXOb51t5\)2:KXg0^I[G!,g#EO?rdgWn'a>@_BC%=$3ZFNQ7=eIRm4Aci^C>%Z.I]]tFJj2N`VNtqrndQ[fBF<mN4&ZR#^;q#FDH"39bq<@.?(Jl*<a+WCjAMEb9V(D,0t"cif)J!LJ%*ijT3ai##d>e9$LSTt9:ci/;FrEJa,-lD3_#jKU7k#E]%6KAG.>B#%@P9-'*sZ+g%bU4o9H<d(ZAEfH2Y.OO$[JN<3#OMH3\aqfloNY,#&DuB`u<9LF\OKG4(p]5WaeV6GDjgO-r5@J%_sf)W0F@6\Oh7;=fe9@2EO>6c^JL!oSkT`:\=(FT@cqe8i`J*Z"CiRA(2rX?FVQ9%U)k9t$r?FjW(O],BidS%MetKQsh]&<H<kU;]*Cn$>EN`=M2.Q'07/NBOV!k=G6mMUUF7D040d%a!72HFrF'WS=i-Y=>C&,%ZY/MUgn\kTT.R*gi@">E18s.[:=k:S$7)SSE!8blK.S#2@0CLt_O.OU!4NOsLZLo9(SWd<?:4,*-NAb_*F[`N0=!CGZG\]7/;\KaQ-G4r(L`!**#fF\%jJqd+Vl7lDl4.FSAq*@Z+YA2]GFK`?7`fJmLb-4GXXT@_A>*)d-Us3VtjHlOq/[Z2,MS&3_TEol$^28Y`tPdE5XH-@98m[Y^a$E#SVj\;bJ/Wk;5V&_d?(\3R&Nj"@Gr<UHe^uO?:Qt<s&AdKSdhf*!_%<mDuHN"gNphOuaNcD8,\oigRkmgp&M\P2QJBMh+d!`L-S1uSQpLfRL:G<WoF@!NrL]tN.q:_9abX,d-KuB(,5'sB(2L9R(1kF<CBP?/Jln)[coDtj3gUo(qXK=T8R4]GLp3FdfiGUVCes@_sODVbj4C?dmcTnWfShPoL-]I#mWl5>\00pbt9j6Hi*r0MK)=3]q\0Q9qkZD!;%]^,2\AD@%)gqrm9dt"V;,]U3GA0`<9D'[6%*6*&mI^\5aTYXt_YUjLfh,]'Kf26*5`%79V:uRn9W;KLX47h#B6't0AU6S:]iMmD)V_kC.BA:\'A6,Zfc[1KAX7I96;]9u^ZG&l@?3qFN?LcDOl/G96U\G9@lbh9<QAF'Lp"_m=!D?sRtWk*Qfd`?btR)PKVs$V>PG2GM9n[ch[GrYiRCsq,.h!`F0c=b4BoG%6*IrE0l!.Z@r3'35D,f@>gNp7K!^)_`k[_+m_(X;CNFBOeOujA7VrT!2C<@=:)e!R2Ls)No1uL3QWlgTpNo4c)RWGp9h4M7C<QXKJ-"gdJdq&1~>endstream
endobj
xref
0 16
0000000000 65535 f
0000000061 00000 n
0000000112 00000 n
0000000219 00000 n
0000000331 00000 n
0000000536 00000 n
0000000641 00000 n
0000000846 00000 n
0000001051 00000 n
0000001256 00000 n
0000001325 00000 n
0000001618 00000 n
0000001696 00000 n
0000003632 00000 n
0000004773 00000 n
0000006832 00000 n
trailer
<<
/ID
[<69f244a5f38723a5170c7b67a8d3f76e><69f244a5f38723a5170c7b67a8d3f76e>]
% ReportLab generated PDF document -- digest (opensource)
/Info 10 0 R
/Root 9 0 R
/Size 16
>>
startxref
8861
%%EOF

View File

@@ -1,218 +0,0 @@
# Les 16 — Lesopdracht
## Eerste MCP server bouwen + laden in Cursor
**Vak:** AI-Assisted Development
**Duur:** 30 min in-class
---
## Doel
Werkende MCP server met 1 tool, geladen in Cursor of Claude Desktop, en getest met een echte chat-vraag.
---
## Stap 1 — Setup
```bash
mkdir mcp-demo && cd mcp-demo
pnpm init
pnpm add @modelcontextprotocol/sdk zod
pnpm add -D typescript @types/node tsx
```
`tsconfig.json`:
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
```
`package.json` — voeg `"type": "module"` toe.
---
## Stap 2 — Minimal server
`src/index.ts`:
```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "demo-server",
version: "1.0.0",
});
// Mock data
const BANDS = [
{ name: "Lost Tigers", day: "Vrijdag", stage: "Main Stage", genre: "rock" },
{ name: "Polderfunk", day: "Zaterdag", stage: "Main Stage", genre: "funk" },
{ name: "Jazz Collective", day: "Zaterdag", stage: "Tent", genre: "jazz" },
{ name: "Echo Bay", day: "Zondag", stage: "Beach Stage", genre: "indie" },
// ... voeg er een paar toe
];
server.tool(
"searchBands",
"Zoek bands op dag, stage of genre",
{
day: z.enum(["Vrijdag", "Zaterdag", "Zondag"]).optional(),
stage: z.string().optional(),
genre: z.string().optional(),
},
async ({ day, stage, genre }) => {
const results = BANDS.filter(b =>
(!day || b.day === day) &&
(!stage || b.stage === stage) &&
(!genre || b.genre === genre)
);
return {
content: [{
type: "text",
text: JSON.stringify(results, null, 2),
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server started");
```
---
## Stap 3 — Build
```bash
pnpm tsc
# resultaat: dist/index.js
```
---
## Stap 4 — Test met Inspector
```bash
npx @modelcontextprotocol/inspector node dist/index.js
```
Browser opent automatisch op `http://localhost:5173`:
1. Klik "Tools" tab
2. Zie `searchBands` in de lijst
3. Klik "Call Tool"
4. Vul `day: Zaterdag` in
5. Verwacht: JSON met 2 bands
Werkt? Door naar Cursor.
---
## Stap 5 — Laden in Cursor
Open `~/.cursor/mcp.json` (maak aan als 'ie nog niet bestaat):
```json
{
"mcpServers": {
"demo": {
"command": "node",
"args": ["/FULL/PATH/TO/mcp-demo/dist/index.js"]
}
}
}
```
**Belangrijk:**
- Vervang `/FULL/PATH/TO/` met je echte absolute path (`pwd` in terminal)
- Geen `~`, geen relative paths
Restart Cursor (Cmd+Q + opnieuw openen).
Check: Cursor Settings → MCP → server "demo" zichtbaar met groen vinkje.
---
## Stap 6 — Test in Cursor chat
In Cursor chat (Cmd+L of Cmd+I):
> "Welke bands spelen op zaterdag?"
Cursor roept `searchBands` aan en toont resultaat. Werkt? Klaar.
---
## Alternatief — Claude Desktop
Als Cursor niet wil:
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
```json
{
"mcpServers": {
"demo": {
"command": "node",
"args": ["/FULL/PATH/TO/mcp-demo/dist/index.js"]
}
}
}
```
Restart Claude Desktop. Chat input toont MCP-icon. Vraag stellen werkt identiek.
---
## Eisen
- [ ] Project opgezet (package.json, tsconfig, ts source)
- [ ] Build succesvol (`dist/index.js` bestaat)
- [ ] MCP Inspector toont tool + tool werkt
- [ ] Geladen in Cursor of Claude Desktop
- [ ] Chat-vraag werkt — AI roept tool aan, geeft antwoord
---
## Veelvoorkomende problemen
| Symptoom | Oplossing |
|----------|-----------|
| `cannot find module` | Check `"type": "module"` in package.json + `.js` extensies in imports |
| Cursor toont server niet | Restart Cursor volledig (Cmd+Q). Check absolute path. Check JSON valid. |
| Tool niet aanroepbaar | Check tool name + parameters in Inspector eerst |
| Stdout pollution | `console.log``console.error` (stdout = protocol) |
| `tsc` errors | Check tsconfig en moduleResolution |
---
## Klaar?
Verder met huiswerk (`Les16-Huiswerk.md`):
- 3 tools + 1 write-tool naar Supabase
- 1 resource toevoegen
- `MCP.md` met tool-lijst + voorbeeldprompts
---
## Tijdsindeling (30 min)
| Stap | Tijd |
|------|------|
| 1-3 — Setup + minimal + build | 10 min |
| 4 — Inspector test | 5 min |
| 5 — Cursor config | 5 min |
| 6 — Chat test | 5 min |
| Buffer | 5 min |

View File

@@ -1,143 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 7 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
8 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/PageMode /UseNone /Pages 12 0 R /Type /Catalog
>>
endobj
11 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607091328+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607091328+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 16 Lesopdracht) /Trapped /False
>>
endobj
12 0 obj
<<
/Count 4 /Kids [ 5 0 R 6 0 R 8 0 R 9 0 R ] /Type /Pages
>>
endobj
13 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1722
>>
stream
Gb"/&h/h((&:aF]Z-sSX1c1*(/BL"?'53d7$KVUJI;sYWjZ)/NZ$h2sYO>)l-/[f,);=&K8LCl0p:%Z^mVg\c./l"%^K:U94G2,G$lFcQ,qqn9b[//m=`U&EY.0cH&7MNY306hD+(uG3##[lh`\RgRC1=OQBc&QO1Vcj_CSmf1E-[s-+BhWZ,S7jkA^i;W5nrL\>*;jaCgVXqcT(MOU'Br1bl/-hE!I>P$E-9(b#4eIC^in<Is_1sI]XBo/Zu+RKKm&f(0;OSagm/(02A+ImBZ-G*7!r2/ZX272r0NKqZsN<Ve^l;\Kj;$H5HUXD1O.O7odm)4@$['(X#M_CkD<qcZK@hHn\S-7Waq']T96;HD45RSOf=F-V^;Q5t3h6DImhjap9'/$Ap);(__S".<De7:(a1&"p'^gXPXbWKpa-Q/=4JO/BlIs7\d75TkHoPs+gQj_p)X1-<CL[2.=LDJ\H16(Z>J:i+`+5^2nje6CXtC>SCjcC81/Z)g2Br@E/*a#/U$f6R*M/b5Sg;%P/nT@tmFm0G%0K@D7\7k'm2D3.6fJPY2i%/3[Ug,`4>"R20%X7M@6$feeS7jA&$e2\A/P0M`GCX3_a+3-.P3nh]$.HGQYg2pg:@`\qd5D*Q\_4G#>\j)_b^ZYe/lc(18SptDt^B$,3_aSSr+Yi.X:IX+$i[#TIUH(_ZE*Y4Y3,3W#+[nA7h@EoU3Z3,pGaeSV+lLBn^;l_Y^.!Gmi`Fca7G;I^N3csANHoU#$RsR-b9g/hVg>ur$q+En#b.MS0BmRCh1#-rP.H(Jp@dgbc#0"H6gNDZQ?Z>S":EK[SO=KQP#;[1Q*=a0ZV#g'HH#R2S^nJq)8rICaFAS4Fg(c9]77[q=E04cDPmXk@79lcN7+;Mr0ddZ)N[nWC5o'gkpg0UE50<oR1Ys6@*E%HL'US'gU&Rp%SHW97LL\s^&+!nWb86>hbYUDXbgmKMKOfMa)/)N7bWip0cSbGl.guJ;`6D&Y+MV?hoHE53d5/q39-;/eAP4+):7plsLHktY:=5/u&%5W7ZTF,P[N2C9Yl+M($bJnj:gLq@R;Ys`gpX8dWnjPt[[ibk\CWSHFq!3%i[XClDON[pOSag>1l>tJo3Vo=.35DJ;;]n/#eXRjUll'X,*mu_29]K;_D4MV?_$.gf]<BBn``_.mO=;QLG)bHI!lN(Qp(SR&(juhr17<33)Q?"j*R1J>\.o;"s=hk\-eWnlY/1r&.WSO4<FpnIc4d#3j8cB#HF!J'8G%o+g*M$VHnJ9p1K'Oa1VGBi;:BKh9LT:-TUF,ndbZ20!B9B(Y3f=gX^RK3M?k)RB5i^)+?q=\+#=_R`O+b\CO4>FU#"lTh[::7E-KM;E^R*j.8i[UbpImV/k9D.9G62n+_6Kc%\s/JhVgRESs/L)`(Wo&WB1b/fIFUJOWiKq<1P#4W"X\LA8rdLOrt$:h31GS*$kg]p^U:.uO16DV#UcHjI\R$kIh#N%b>/=E*-We=1cDoWnD3B>c49nOVKV:6`l`F*P!nk-ch@9kQ\7F]3E_;3e'HHj,"$o</6k]<6[.EXpo421GH[bIW<Rrr$qk3hU7Qh\h>$%C(/+T]2"X5*GB!QB,A6q$NC[/!nE_q:J^ec_p!a[^;+S2;3TFoZ184K=Bp_=kCCt'9C2!?><S`gg0*^.UQ;=H^fb.H\*?.rk%`NG5\$!b.e1nnt',o@EP_DEiAdq+T]\Lg[_q-~>endstream
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1715
>>
stream
Gau`SD0)19&H9tYfVgSD3H\-l`nfU64,o_ARPrF6=NuUhq1&Lf`!B>XE?*%!]mG8>jL?D+>"^WkZM3^l[sNH0",\2Or/]D/-ZbY<A\^:LQbXl/0RjSIcFptU_A1\N#9jQk!='5CqZl6*bJ@7J"[$1#W$Xu,OG4?W!WsG*C!AK!#qR0D%#@q`)[XG:?Q&OeHpJJ0BNc$UWSm4BNh<TK!hq`!8@/7=(i$;#.>KAuU!2+Pbh"Osnaap\poRXE<HMO])>mL$8T\:VJo_B^TDe8hWm[nB@X+&`9X8-6:@e5]jr\"1aP%Qj9.gM]Uu'(?SlEK>i2,)EAa#doKr[$@JIetkj19-E'^s+8Th+3`[.Xd#/5Wiimb0;->r3'nR;a80!N\G`Fh?R,Q?gnT40]U4W$jtYeO,2Hl9q5-$0i2AQ"cn]bRc8N9SY35d-<Rl:4c%8:$+1)N>XjU%ZC%H,*2Fs1;%U#]BR!^r@mff]dC5TVbM`q;ZT1J_j$N@9*ULd8AH*E^l9I?RO2q04d]MUjm4PCB1`(7nfgis&%e=4ED7]sURnVI79RM;r#'?HPO`3]_C[=^@L\Nnf*IX17:%@6l:kF279!g(.__9MNT+apD9.KIo&GNd+%JZd>!&kHZtSjOb:p+<nM_Dim-JXF/T%05*D\m]6_B9]'EYVp?4gV,P2/s)[W[aV<C=qDNZh3$qR'U&D]fSBaUq5+A6hk=!JONWj="Dh&itTu`PEB;`_#@C*[OF8Uk'L^3dLY_aXJuk[i_1Z6[[[kbtJ+-pj%E-o5o\>O`qdOU[1^CIYblVhQN*TZs%#Q?(_jd,;unQIUM5B)m>,M:<M[0Ic3u5%I9hF[F%)Y\"R>2R5u:n)%],(VYD-TX);Xb*si%5\4$i7p;_`(6NXAna>PJ<NTGLASQ&0_7cu.-*npjh]If9S34!g*Cs_tm'[,(hadAu"'<5agZ"E)]PH$7aI'o=o%S3k(1!@GXhRJ:;=,laX`Oa*S^PuT&?0;gPj@7<5]M2orrX!K9,6\.L@@mi+D6DT`e)W#^VHIapc5D6$LVcqBI#1Aj((-/O"(VOA^;(VkKB)!%9+dRTHtXV19Uj)icmglI`PI"5.NhV-_k"'[D<ktHfeGF\9^9eVa)Eo$0!C>oh$qlc)k5iqnmPGKb7XZ$qXR&kWcWO"R$NMh?Y7t7E#YhOE'%N3qEtX,3lAH(Pm*a1K(E%?)etKus0UXt$+<nC).&$][QG2p4AV\t0st$P^,*T;j"?18HOg_@6^9U4a.@b+'A/Wh+dH$ZH>oL`6RU&>N]W(i1Q^SpD$Q53e`&rQ^X\0'-c5s@N*.PV_T"kE4udAp=rg+@.u9;FG&/*.+^+i,3[!^hfXA]dg!cO'2qb_rnGM.O_mZ^BhkBK(40T[_L2hd@L'V5c2ESi"/;1FSI.o\;<t0UDpt,;(PI;dk_*>'>%2*Tjd$)X0MrB#-L3\=f;j=dNWd6KT&/YpDs8VOZV'#g3I5^F`-+4R_C$(j^WB6rJ_,b$7;Kq/i;CA/$)!u2=f3RX6(ecmaAdrktNKjH/A]Qg&i;V:\F[<Lm/]@FUK<dK4KK\7,$6\MK$J;gRBYV/t>ki)4Y'Geum5q-:F*^_"='2Nm7)f9;=V6A@3VGa/51>,!+'EmjF$.^J6`ar=;G9o2E3:;"04C/(.^2\5p0ca#1^)Y\jct8A3ncVL[A=oTL?^Op_ltILcFrqeqZkiGPkb~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1896
>>
stream
Gb"/'gMYe)&:N/3$l0,S8RmW:;,gj/a/L![DNiS>jIn1-=\J5+ek1gqFkRPO[s+d_]tQ\K5ll<K.uB]T*0Q(iL1W^@_s6,m"n5t*mOeU]@<N&Q&dbmmpgrT8EX5b%QQ?^3W<bJF5hJ:L(/>4.+tDLO"&43<I0ie-U33,[US%!2*sr+j:E9YX=4Gh7Lh<7,C;XBi`Ar)nM5O<ldD(Z:g[G9=g?:67k)3V\_:h3JW'V->4PteE$DtSQI_Q,kZ@sa&AX4Y3Xt!,PXPf1]+1CpP[`^EY.l2sIk0,)R^+sEq"D0#8HYWEriCbd0&#=[rJ)?L+']::\k=+#j^Bk]r)M/f7]u>9a^_C^>or09):`n<Lg.?Jk:6$)f8HgW"H_e4>gCf;=KMnd>D-XPNOt^85EJrH%6r)Wo2bZ#>U&(Vdm/dfECr%$Z5(KdZ_>q4F`*rG,%52E+7/L:7LCR0,ps99!3qpN/NBD6I:<b<dBpRZ4[J[!1XGCaC10QS*$.k+D@#->?Vs@oXUNI`]RHBFZ&\b:8"2["e,ps`AOLs]F&#q;gfhc*AS=:5P'R&%rR2%Y863X`/`2@TN3(4UPe[r3F\($.d5rIP/!RoX\G#e:pmY]S'qTU<`h6jkPEcP&iBku*^9V7pblI)n!]^mH22,P71+li"YY74oUU7rV=hFu&6OhT^$6f41h;H)m@2"gF5#aNm]\*2*aEr%]64?M#lL?dN@i^qm#$dN_!C83tE^'13b#Kp+Sld9K/JC1&D93498JI\rnrkiE(gIpc\+K3s]04Sc_!m\<-5TiOhlNNp[Hat8A[Q"N/P=Sek$S]Cc4tssD.E]bR\A;5M6qXS,l\jV@Zc[1ZoBZkl_0S?<N3^t`9]tV%8[Gsq4M)Y$H:/'HNtK6<@:T:XipOP8hfmsocX+/gf9WgK2Cs'4;XMKgh#*-_T)<^o^(A4!S.?Wf^##>*Q#-/.m;K5sY1EC6'jJ8q82=$?hYH4p3\m'ATF-_mK8D4\+A1YKk!eoG+^nA<=:,?u@,CL.I2F>7TnZ&JU<sH[eu=/9.8Y$b;//A:qZqW?P-g%fEkcaj(+I+YLTbE-d/;<Qc3Ta*i@1SJ,EpD3`e'?bp`0#>-@X3nf+nk'NhD;J$Qkg":[8>Q2Fs`<N;)F)0BU]kD@0l@`(h1IYZ\)e<sh#_?^N*4(I`^lg2!CpW2=`07j@aM2\l2)Ln(\)'nf(gQE2S=GuuZWE4R_n`B$3=OQ$-/r1`U#4sBEr6-9-"$l8P??s$EVemOaGr\.mu(=e,g0g3^C`6@6Fp9q;rs7@(H2AH)V<D"ll4_!Fl'?nbM7&SAcGi6A:<Lb\aDQL-J\f,%(a?=ZSaR'rAD<K1"S]Y-ei3=#La7\4@M@=.b/5Au:biTUU%UC343V=GnXa@1Y]_eY9]VaDOiJne5/Vo^$F]67*n`\H,_B+,O\mMa'2FtB&Y,c_5mR).+p1Ed_aXq6XJS@)?gACEJQ/L]&$;W^QU#:*m@T6*ZVN;Fc&un\:SI`,1pK]Y9P4I[d8PT^7@7='\R@=>%,0Sq<=IEV>/q0DI`!YJ.ISoOWMO"mR:3hQ!'c,3f73+r:VLXe.=dE*ep>n?7_Q^V*F:%6_V:pQDh5bXsmjqc!$1]giS8XEBX!ZeODW%??:TH%]M4e,N4X9Agd-[XqF=W>*Oa`=S>6kt-4pa?SJ%ST4A/^etSN?ruH)-57Aq((S?I_N_+g>a]%tWQSE!Yf6kQ")-^@#.L(iFVr]]Kdh](!u#O,o1&prD49o-B?X2-C7klTELNb&,5[7ePm']]%5X-qfW]`Gk%UqEXXkiZ(^`s0I@?YWn8Wh>Y'n,kP`/F"0RoWP0)lO5NYN/ZYX#/8T`7=$(DjYA]W;Zlnqu^RY%i]_q5g]RG]1PFc!,E;fs\,$N=~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1280
>>
stream
GauHJ9mbVQ%)2%/i7,e])b-VOMHWfX[s$HRXi]kiP`$a5[UNB=.GAY5lNc^uMA+`?G"A-*$q7hE$\/4*q"UQ-GIe"R0S>:E'I\Ue*%30SAtS%kRJ4rN9X'>.5Q^0T0`r$nmK^tQ.j5Tp`%7c.rJ8ON".,6C),"?c(I^.^UL=)TUHg$^8!3t:1_`hMOq3@V$rq?]$5)aMGP4U@?;*b"*Lb[\+rHoE*N9&si5DO%'Z+YqPJ=ie>f,rUU$?a!FTMMEUXGum>K.k3IsM%-Y4LAI%";2\<Tn2"8p^cKH^MKi\jlIbEPc0+4iIB%C7kXtTrZB_"8#U!aNF_]#jWE-qT)b^Hm1qM@m'7ghn3iP23IEuaKQg?]tB#s'0-])OmtVSBC<`/emgVCT%qaBH=5abaFKG8;FsBiT`%"R:*5'p)1A\d$bBN?Ihb"V;C1QW=X\;tlgcX[3n<=S?apj#H=KiqHZmaSM,&CXau`nTUASG`(E(_l1fa2ae*V=A@<u5?AYso!CUd<N9bMf;AftH@k"aO7B`:$*K$1I&0Y5tge[i-U]7;j0?($s@_N+FFa)7g@=mS>O,0<djGmVrZWKEmG_mDrmRDL^VYH4me&UZlnXfBEd/]s++a8mu_/e`TG2+b)eK@SR?0NqI^*0[1GV#G\d%)McY7O8?B5B><pCE-Z2"^VC<Duja8KqquaH\KKaiqlu@p6b2AEC(nd`]IUY-M<*V>bGT*K,V1-4;Wc,@&*Uf1P5`,7Z%EDlb]FlQL8)U"->`\8[2ZY46uj9Oe(u,A&LM&Q<r#Zh5;q!X9fc!?P(m*qb^=l:RIq/!/e:;$t[n6j=!?J[VjrONpoJi*u">bT/00>)#Nb(r#;JiXk%*\Ypj/I=%;'^$J22%)NT+<E+5%s3P;0*r,BsV0KO=DEG&bNe.E>*OTN)]4Zf?nS]'X\J)E)2'Xr;\_p1Dc2C=4:>ggaGTBci&g\U/["#GellkBoL^Jop,Br,'ncL28PnR8P%0-!TQPbkfBa-HjDSS\^V6@C6UlJ"e_GP5[Gg=7k'5$NthNTK%T3VC:iZHcJK1c!qCW.F;mCYM$fA[]`HRB$G]f3hE&IA;]eAbcB0J3DGmnRB)ThH\s0]nPOMf#>X<c6d^[a9p-WLGO#EcDbqTc]u;X,V^Z?kX;=AQ*+Ma-_k:PPc50I&2$'tmP`N5$Pa<,fNaajPdNh;.Np0.$GCsk-8FX'oNTHT4gs!KaZo:`*m9/"/ASm<E!QuCEEqL?KYK`s,_YXbj]_+.)ZM>GCKKi<q[`F`dVS~>endstream
endobj
xref
0 17
0000000000 65535 f
0000000061 00000 n
0000000122 00000 n
0000000229 00000 n
0000000341 00000 n
0000000446 00000 n
0000000651 00000 n
0000000856 00000 n
0000000933 00000 n
0000001138 00000 n
0000001343 00000 n
0000001413 00000 n
0000001709 00000 n
0000001787 00000 n
0000003601 00000 n
0000005408 00000 n
0000007396 00000 n
trailer
<<
/ID
[<745af1c61365c87f399ef0edd3dfacfa><745af1c61365c87f399ef0edd3dfacfa>]
% ReportLab generated PDF document -- digest (opensource)
/Info 11 0 R
/Root 10 0 R
/Size 17
>>
startxref
8768
%%EOF

View File

@@ -1,523 +0,0 @@
# Les 16 — Lesstof
## MCP — Model Context Protocol
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Vorige les:** Les 15 — Cursor + Vercel deploy
**Volgende les:** Les 17 — Externe APIs in diepte
---
## Inhoud
1. [Wat is MCP en waarom](#1-wat-is-mcp-en-waarom)
2. [Architectuur — client / server / transport](#2-architectuur--client--server--transport)
3. [De drie primitieven](#3-de-drie-primitieven)
4. [Bestaande MCP servers](#4-bestaande-mcp-servers)
5. [Eigen server bouwen — TypeScript SDK](#5-eigen-server-bouwen--typescript-sdk)
6. [Laden in Cursor + Claude Desktop](#6-laden-in-cursor--claude-desktop)
7. [Resources + Prompts](#7-resources--prompts)
8. [MCP Inspector](#8-mcp-inspector)
9. [MCP vs Tool Calling](#9-mcp-vs-tool-calling)
10. [Productie + distributie](#10-productie--distributie)
---
## 1. Wat is MCP en waarom
**Model Context Protocol** is een open standaard van Anthropic (gelanceerd november 2024) voor de communicatie tussen AI-assistenten en externe data/tools.
### Het probleem dat MCP oplost
Stel je hebt een handige tool — bijvoorbeeld 'zoek bands in onze database'. Je wilt 'm gebruiken in:
- Je eigen Next.js chat-app
- Cursor (tijdens coden vragen stellen over je data)
- Claude Desktop (research-werkflow)
- ChatGPT plugins (in de toekomst)
**Zonder MCP:** vier integraties, vier configuraties, vier onderhouds-aspecten.
**Met MCP:** één server, alle clients begrijpen het protocol.
> MCP is voor AI-tools wat USB-C is voor apparaten — één standaard connector.
### Waarom dit nu hot is
- Anthropic Claude Desktop ondersteunt het out-of-the-box
- Cursor ondersteunt MCP sinds early 2025
- OpenAI heeft mei 2025 MCP-support in ChatGPT aangekondigd
- Honderden bedrijven publiceren MCP servers (GitHub, Stripe, Linear, Notion, ...)
Voor jouw eindopdracht: optionele bonus, maar erg handig om interne tools te bouwen die je in elke AI-client kunt gebruiken.
---
## 2. Architectuur — client / server / transport
```
┌──────────────┐ JSON-RPC ┌──────────────┐
│ MCP Client │ ◄────────────────────► │ MCP Server │
│ │ stdio / HTTP/SSE │ (jouw code) │
│ Cursor, │ │ │
│ Claude │ │ exposes: │
│ Desktop, │ │ - tools │
│ custom apps │ │ - resources │
│ │ │ - prompts │
└──────────────┘ └──────────────┘
```
### Drie rollen
- **MCP Client** — de AI-assistent die jouw server gebruikt. Cursor, Claude Desktop, of een custom app die jij bouwt.
- **MCP Server** — JOUW code. Een proces dat tools, data, of prompts beschikbaar maakt.
- **Transport** — hoe ze communiceren. Twee opties:
- **stdio** (default) — server runt lokaal als child-process, communicatie via stdin/stdout
- **HTTP/SSE** — server runt remote, communicatie over HTTP met Server-Sent Events
### Protocol
JSON-RPC 2.0 onder de motorkap. Je hoeft dat niet zelf te schrijven — de SDK regelt het. Wat je wel zou kunnen zien als je inspecteert:
```json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "searchBands",
"arguments": { "day": "Vrijdag" }
},
"id": 1
}
```
---
## 3. De drie primitieven
Een MCP server kan drie soorten dingen exposeren.
### 3.1 Tools
Uitvoerbare functies. Vergelijkbaar met tool calling uit Les 12.
```typescript
server.tool(
"searchBands", // naam
"Zoek bands op dag, stage of genre", // beschrijving
{ // input schema (Zod)
day: z.enum(["Vrijdag", "Zaterdag", "Zondag"]).optional(),
stage: z.string().optional(),
},
async ({ day, stage }) => { // execute
const bands = await query(day, stage);
return {
content: [{ type: "text", text: JSON.stringify(bands) }],
};
}
);
```
AI kan deze aanroepen tijdens een conversatie.
### 3.2 Resources
Read-only data die AI mag inladen als context.
```typescript
server.resource(
"bands-list", // naam
"bands://list", // URI scheme
async () => ({
contents: [{
uri: "bands://list",
mimeType: "application/json",
text: JSON.stringify(allBands),
}],
})
);
```
In Cursor type je `@polderfest:bands-list` om resource toe te voegen aan context.
### 3.3 Prompts
Herbruikbare prompt-templates.
```typescript
server.prompt(
"daily-recap",
"Genereer een samenvatting van een festivaldag",
{ day: z.string() },
({ day }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Geef een gedetailleerde recap van ${day}:
- Top 3 acts
- Drukke / rustige momenten
- Bijzonderheden`,
},
}],
})
);
```
In Cursor commando palette of Claude Desktop slash-menu krijg je deze prompts.
---
## 4. Bestaande MCP servers
### Officieel (Anthropic)
Geïnstalleerd via npm:
- `@modelcontextprotocol/server-filesystem` — file system access
- `@modelcontextprotocol/server-github` — repos, issues, PRs
- `@modelcontextprotocol/server-slack` — Slack workspace
- `@modelcontextprotocol/server-postgres` — Postgres queries
- `@modelcontextprotocol/server-puppeteer` — browser automation
- `@modelcontextprotocol/server-memory` — persistent memory
### Community
- `linear-mcp`, `notion-mcp`, `asana-mcp`, `figma-mcp`, `stripe-mcp`
- `youtube-mcp`, `arxiv-mcp`, `wikipedia-mcp`
- Lijst: https://github.com/punkpeye/awesome-mcp-servers (~500+ servers)
### Gebruik
Voorbeeld Cursor config (`~/.cursor/mcp.json`):
```json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." }
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/tim/Documents"]
}
}
}
```
Restart Cursor → servers actief.
---
## 5. Eigen server bouwen — TypeScript SDK
### Setup
```bash
mkdir mcp-polderfest && cd mcp-polderfest
pnpm init
pnpm add @modelcontextprotocol/sdk zod
pnpm add -D typescript @types/node tsx
```
`tsconfig.json`:
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
```
### Minimal server
`src/index.ts`:
```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "polderfest-server",
version: "1.0.0",
});
server.tool(
"searchBands",
"Zoek bands op dag, stage, genre",
{
day: z.enum(["Vrijdag", "Zaterdag", "Zondag"]).optional(),
stage: z.string().optional(),
},
async ({ day, stage }) => {
// Echte DB query of mock
const bands = MOCK_BANDS.filter(b =>
(!day || b.day === day) && (!stage || b.stage === stage)
);
return {
content: [{ type: "text", text: JSON.stringify(bands, null, 2) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
```
### Build + run
```bash
pnpm tsc # build → dist/index.js
node dist/index.js # runt server (wacht op stdio input)
```
Eerste keer: niets gebeurt zichtbaar — server wacht op JSON-RPC over stdio. Test via Inspector (zie sectie 8).
### Best practices
- **Logging via `console.error`** — `stdout` is gereserveerd voor protocol
- **Errors als data** — return `{ content: [...], isError: true }`
- **Schema's zo strict mogelijk** — Zod schema = AI weet exact wat het mag invullen
- **Description = belangrijk** — AI kiest tools op basis van description
---
## 6. Laden in Cursor + Claude Desktop
### Cursor
`~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"polderfest": {
"command": "node",
"args": ["/Users/jouw-naam/dev/mcp-polderfest/dist/index.js"],
"env": {
"SUPABASE_URL": "https://...supabase.co",
"SUPABASE_KEY": "..."
}
}
}
}
```
Belangrijk: gebruik **absolute paths** (geen `~` of relative).
Cursor → Settings → MCP → check server-status (groen = werkt).
### Claude Desktop
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
```json
{
"mcpServers": {
"polderfest": {
"command": "node",
"args": ["/Users/jouw-naam/dev/mcp-polderfest/dist/index.js"]
}
}
}
```
Op Windows: `%APPDATA%\Claude\claude_desktop_config.json`.
Restart Claude Desktop → MCP icon zichtbaar in chat input.
### Beide tegelijk
Zelfde server, beide configs. Eén keer onderhouden, beide AI-clients gebruiken hem.
---
## 7. Resources + Prompts
### Resources gebruik-cases
- **Documentatie** — handleiding, RFCs, design docs
- **Database snapshots** — read-only views
- **API responses** — gecachede externe data
- **Logs** — recent errors voor debugging
```typescript
server.resource(
"recent-logs",
"logs://recent",
async () => ({
contents: [{
uri: "logs://recent",
mimeType: "text/plain",
text: await fs.readFile("/var/log/app.log", "utf-8"),
}],
})
);
```
In Cursor: `@polderfest:recent-logs` voegt logs toe aan chat context. AI kan vragen erover beantwoorden.
### Prompts gebruik-cases
- **Code review template**
- **Bug report opmaak**
- **Vergader-recap structuur**
```typescript
server.prompt(
"code-review",
"Doe een grondige code review",
{ file: z.string() },
({ file }) => ({
messages: [{
role: "user",
content: { type: "text", text: `Review ${file} op:
1. Bugs / edge cases
2. Naming / readability
3. Performance
4. Security
Geef per punt concrete suggesties.` },
}],
})
);
```
Cursor command palette → "MCP: code-review" → file selecteren → AI start met die exacte prompt.
---
## 8. MCP Inspector
Debug-tool om je server te testen zonder Cursor/Claude.
```bash
npx @modelcontextprotocol/inspector dist/index.js
```
Opent browser-UI op `http://localhost:5173`:
- Linker panel: server tools/resources/prompts lijst
- Hoofdvenster: parameters invullen + call uitvoeren
- Result + raw JSON-RPC zichtbaar
**Workflow:**
1. Schrijf nieuwe tool
2. Build (`pnpm tsc`)
3. Test in Inspector
4. Werkt? Restart Cursor en gebruik daar
5. Werkt niet? Inspector toont errors direct
---
## 9. MCP vs Tool Calling
| Aspect | Tool Calling (Les 12) | MCP (Les 16) |
|--------|----------------------|--------------|
| Waar leeft tool | In jouw Next.js app | Aparte server, los proces |
| Clients | Alleen jouw chat | Alle MCP-clients |
| Setup | Function definitie in code | Server proces + config |
| Distributie | Niet — alleen in app | npm publish |
| State | Per-request, geen state | Server proces heeft state |
| Best voor | App-specifieke features | Herbruikbare tools |
### Mentale model
- **Tool calling** = function in mijn app
- **MCP server** = library die elke AI-client kan importeren
### Wanneer welke
- App-feature (alleen voor jouw users) → tool calling
- Interne dev-tools (jij + collega's) → MCP server
- Open-source tool voor de community → MCP server (publish naar npm)
- Beide patronen zijn complementair — productie-teams gebruiken vaak beide
---
## 10. Productie + distributie
### npm publish
Maak je server beschikbaar voor anderen:
```json
// package.json
{
"name": "@jouw-org/mcp-polderfest",
"version": "1.0.0",
"bin": {
"mcp-polderfest": "./dist/index.js"
},
"files": ["dist"]
}
```
```bash
pnpm publish --access public
```
Anderen installeren:
```json
{
"mcpServers": {
"polderfest": {
"command": "npx",
"args": ["-y", "@jouw-org/mcp-polderfest"]
}
}
}
```
### Remote servers (HTTP/SSE)
Voor cloud deploys — anders dan stdio:
```typescript
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
const app = express();
app.get("/sse", (req, res) => {
const transport = new SSEServerTransport("/messages", res);
server.connect(transport);
});
app.listen(3001);
```
Cursor config:
```json
{
"mcpServers": {
"polderfest-remote": {
"url": "https://mcp.example.com/sse"
}
}
}
```
### Security
- **Authenticate** — als server gevoelige data exposeert, auth-token in env-var
- **Validate input** — Zod schema's strict
- **Audit log** — log elke tool-call
- **Rate limit** — server is gewoon een proces, gebruik standaard rate-limit libs
- **Geen secrets in code** — env vars only
---
## Bronnen
- **MCP officiële site:** https://modelcontextprotocol.io/
- **Spec:** https://spec.modelcontextprotocol.io/
- **TS SDK:** https://github.com/modelcontextprotocol/typescript-sdk
- **MCP Inspector:** https://github.com/modelcontextprotocol/inspector
- **Cursor MCP docs:** https://docs.cursor.com/context/model-context-protocol
- **Claude Desktop MCP:** https://docs.anthropic.com/en/docs/build-with-claude/mcp
- **Anthropic launch post:** https://www.anthropic.com/news/model-context-protocol
- **Awesome MCP:** https://github.com/punkpeye/awesome-mcp-servers

View File

@@ -1,257 +0,0 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 6 0 R /F4 12 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
13 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 18 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/PageMode /UseNone /Pages 18 0 R /Type /Catalog
>>
endobj
17 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607091328+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607091328+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 16 Lesstof) /Trapped /False
>>
endobj
18 0 obj
<<
/Count 10 /Kids [ 4 0 R 5 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 13 0 R 14 0 R 15 0 R ] /Type /Pages
>>
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1746
>>
stream
Gatm;bBB@G&Dcpm[E1Fh1MQRcRO1,:G%Q-]bW)lIfm&.B/MT^;"1$t%:Gj7[08qcoHmCT7HJIZ6Xb$Sm(G"Bq)8PDIS:"b\3/m9Vfu/Wm'jQ^!7uM?.?/#B('GVYoiX,2]fu-LuO[ONNA"]bnP\YoR(fAFS^sUGMR1#F;qdYNbE@#am%7Dp@._/t@Lu]OCgVf6&>IUg-;^OYga>uCVZhd1)\:`>))R%")PW>lKUDY'MrQjjWr0:$!*_b$>L4>+T#L`bB3-X?Ro&FGU7sUdq37gqe:?OF('^oK&+,l4SXH%ebk/>+.<rHCi*1-m,aG]])EdR1LU;R;fnaddIFQl?e3`.=CEBQhM^$%Uu9;:7dZ<a@hAI:.E4Mn'S^N<4X.`'lIKYV=<N!VfkLDek?RO#a]*]2^jo;Z(jYi\PcFaf@Q3`pHW[bb[:/?gP"&`\5O^]#ogOb;67_V7bO;Ht>fN`6T:Bb[/YiNRBS/qp@;fXXYRU5iunF0FHN"Db.Wm*29@hHZbV2qeK5hq3oELH(5AecN/aGF4/nZ7Ia%W[cug$c\:*=HHXtY*KQMZ,2nZ\,[=DUt#(K$ndtFmHf.ar1uN[;BA60994=fRGd<!>8lcDcI/N8^C%KrdNT.)oZ@G#RcjlpSNZ3G`p)J#=&3P!R9C$Y#cfV.[;7OqCLd[nVRrN6!!b#s\`(r1jj2*`_N\a@LGrDG[&<,]1-q_ds/NpYh$cJgbdEX40TU,MLJKElGI-k%?05OLqIZeB?^I_AE"muZ$cLZLcX00se2c9*%]=8i,[dmui;P$`oY]M^*Yb.4G:SbWBfprgr]6@=+kEl^p#8qe5$MPXEq]ZDg]0>Vl?So6oYK5l'gg>?W$0^oma8fegL.Q,XG^G>c4USaf9\Kc$&"G4(O@=qpU(-m$?`RAEeUO%<h\mbe@"Cmbou&dT6Tn:e3=*mYbbDm+%e3ACbL(-]Z/p*okpk9"%2%.@uWMe'S+Ue2j9#2-E-C=BdT\S91Pr2B#97<Qfth_0GNc]]/4PH[r.S<>Qum?!:A$D>e;lNBk'Y+LOg$Zgd16heN5AS%:BN-O!1db+U'bX&W_6qin:%'3nP1'=3V7us'T+P+3gg#)hUQkfthDiXcS?9AnY/+V^-*3-$G>(L1*+N;]Y2"A<I77=#LLb_=4MMkNIr:AQ&Xp$`6dPW/Z0ekTn!QEHbA>N0C92S.qB`jRY2YC1/j2.M(]FUg]i`Z`&[V6PE"qQnk0;FVO-%',3DDk'u=:jQQa3(r+A&F>g=,;=!D>CgJH\eHRq\"cQdMqBj:XGl]ke?e5^)WpR'PiClPf\q,dGfO&#pTBlEK8EXc#-IO2L6JT(tU;tq2H<<)L:lcEV21sWr<>:dc:H=BXaF!'iEKj/T=VpbY[g&II?MM6sQ53I1%u^"/UBH(*A"DTQ%rM+#3e/Iu/l&o<F[4mC^YI35d':GNfna27AAB\r]1f*d,2B/XNDu9"[>d2V&5X+?G[$co`LN']3SLihZKQsfPDK82$]+BE?K++pp3ib,c=eV%l6(oX!(<ASDju5;L#7YR*X%<:Jjf$^jBP'^VF,c^m"5;DLB+XS"12&iqB5<\5]VTEiql$QfR,aE"*>AGO@'$8.0s!j*Tt>KL9hoV@C3ik!,bSHpp"YeM!DS#YIDD0N'O<BE;%e:Q"JnUj=nI/%03]3/o6q=HJfc4'Gqk=<VOC`g^4hu.Q6iK`$b(+)D?'[KBY(OL("E9.X.2I!H6"$#M*hX3W2e~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1186
>>
stream
GasangMYb*&:N/3bY(ejfJ"R>;:N=Vm^<SOFAo9Q"VuBGBuX0uM697Y7D?K'l=/l+D<-N)3BEZjS7+\Q@H">(JCdFRHcpqZ=Pa#k7)8?qr3_Kt7>+=*M%iLTBTdEZH/ejsh*_^JP/s0V$l$sBaoi!hLf1bbNcnRF12%-PS4!cV->B=[&B.PHj<OPT8C$bSJtu<,&+'UNb1"Oj*7uFZreFmkK+3U):_Q,C9=#=u28gU:rq?KNgPtIif%LHp]@Q2JK58ag357F9n%,fGAdr\&_,PSZS&W-nj]>QUpi"FHL#=4FHM?qoQk#JdCcZ3IHJ#9-;`4gK)9sW%km/R15W#6n,RM^L(;Fs9Pb;Mc,,C;s7m&)GGFGQVSe4SKRa#h0lk4t_P0[dhEK?-W3UAH=R"q--Enm"Z0OZ?1h_Pi]-SuGUqc1RRPoU\*"@T/kk_L9_<&g79%`ZsPAgc3X2@>oD:ETG>[pD-^]TT3Z*j^q_9"8=m!J\+@8lZ-7S&L)chTZSc)86OS3T6%h]cpR=]"Nn!V/T7SE4NI[e%k^PBPm]t;NR<:V!r7#$2b=4/DSK8LjDBYVuO.DLbJ/L<3WTd1Huja[FSJtX74AQ^OnuA8Ju&LGPLH.dB&Ja)@f1<kDe6)G#rFgNZ3=!ouaYW.2UU>i*^C0CBN;AotDktiCYmI-[,Bi6SuGH7=UG]6K=,d6ql>P=;S,"F*R:;EmFtdHU4Tu(OdBoUP"6ocV+[3#VgX6<>e_^#-]rpO'UGar<<R%KYJ6/p/EH+rhsdK$[f_%ZfW$(kihF\HnR#FC1(OPmCp5%&!`+rRrX/[@^l4:(AkPH/[eZa7t89.h6""CO(m_$<&YgK[Y9'P[@A3Ss(@fY"q$Xh-QGbFQ0A3AlQamk\AUaiV<]Do2'3oeVuY9f9`'3:VL4i0o;K<()9d!V$M5sJC$e/ejj%L][&QEbO@(Ii:uMD\G+/3B]L4%93(uU[#9rb/djW9%+c[Zo5GDXpUFeCSJ;@JlRS]'@Yt8r'%NG7'%6QLaiQDNad0nF)+.\,H1fkAa9[9nXjk08e3>=+bG`3V<ER<;e)Vd75NDUXR:F1-U9#An<+g11J4_MI<SuW51J,\X(l(Yqbk82Ta(CrCfUH;*N4tg=k(sGamh8Cl+TSFJQ:m(u3$,mcsg+sY9ZA>q`K+>7a#jP?5+SlCu:BnW~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1196
>>
stream
GauHJ9lo&I&A@[:lqu&1&AA0g3h:8]A<KV)4#,2jFO<phV3DZ/I\`P6qCDpcUa4^[j:IJ!Jd^N(_rC&M$n0u6qb;-^h\0,)B#$AOpV8&:OM$?M8b=ka>]V,)&7MPo3KR@M(R,HX%[8;$jtQs2%C&BS[#A*+>MqKe(hTF_E;q+k5`DS[8g'EKD=u"u5r@cg=q[ZSHp#1,B3T[:d60RekoU&G&Zf>=5S]GYVMp*PLY7HP5Ms>Iik\XZVC44p>aI)!e?J#h^t%38<E3n:g9'>1qrY1nT=\BW!Qc-K?F7Enpel.,OPV/[HWL6sJ?Pt*mQKq(l=%;/JXGAC`0_$"ReK2Z[6t:8E%ddlGU+ss<_+f"j:PJ32hN]?\07)/40`e[O-5/6c)2=7N(KdP_ZmH+[8=[Rj1&2W^_Y.T#m:H"M?J]X(,>/e@j_^N1jDS:3jQir,EV7>LLj.[Sjo3Jdo:\kX^A_Y[F9t6'AL]?Z=St-DAa8+&6DtF].N',I3Hgiq=m+hf,]<6paLZm#Ss1Q&+HdFFL/)T=uT1?"d*D$PSGaBEof5]@\YPW]4t];of8"Fe;."iTrgd$7;nPmiU+&$B`tc(E6fklUbP%VK.W"WV<u4K'XCaQ.]Jh>lnBcGJh:R_J"q)j2:r$d_AZBV%j%d#:gahc#W7(<1,7?T]'0Ji7piArm'm6\Ep`%NWu7Cf.RcVXD'XO'-o$Q=iu3#5UpQG3qtS5)/R81D"8I"&LNdr$+6K25Y@0Wp%8C^>?+6Mg(Q8Ao)5<o<X94/4fCMg:&M8s.flD8lPZ:Re%TBES]e*&b_mQ"_.@P`//_4fbnN<sf6dN:J`^R)"[DRl(rEU6:96cXG[u7l1<s.q3C+Q43o8jq<dM?m7R5S.gF'.i(eah_)FjI>R=%-&bmL8OQr]e4ee9tK\qmnhTAd\2mUtO?U<FM8RAZKAs.70T2AhV?0K<iQR[l\OjFW:lA`M\`1f0)[5/RTSO&*NL;hh&'qf;B.>-b+KHm]<$4R5%L#`"K3i(@9uF'[XaVYZN4M;a*kgW,j[GBGQWcPs9_Aoe/"tq,i%Ykrd'%s*;t`(QQ>`%tnFj02BCSmcO+J?++(JWl7AVor@_b[_UH$gqW`\Epi/<3/YUL%)WTE+ZVOX:4f]!*;ntR%!g^r[](e5%L\[XD/4@1C9i.`O@kiUojN"7A3Y5r7,J#<L24C~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1644
>>
stream
Gb!#[D/\/e&H8h>E<u9S.2&)F.#Q0o!=cVX*K%;k]RFN7TSgTG9(h17EjdO?J)?B7[GtE_$?V2Y830\qh=9oYG[Wl-_sH8/"ZW'hoE>?:@<MrN&-9+=nDha'E<qod(EO,h;)dDu!n[?8Z#DJf,`r*<(b"Y_jU2WW&Z#O4*8g@9k!Pj$nqk87plbJG!)^t(8j7RY-'E=k/VkccYk`_]T/,@/6oNG$R.W+ai<n-]eY0R+;!.QpbYjh@Ie3Obr1#E^)-u?L!l%fjNijk!F7-rPQcIFp1+k)*!RLG@:@_trZg&*6f._P\Hk#S!i5EP&f(!,3&srg7PZ<08_nHQrE>F-Ci;AIZg&ak&"R8oS>aP:hMk(gT\@=99'iIRn*U]'d[^.7ipBdZ_%;]9,WrukFj?njWY)/[oL,Vd\AtV1`JEm7I3c(/5CUsLL6\GKKpPHfW>Ln9q4!<7&M&b5>Ol/SCAFji.`ZXlo\T2L^R87,bkmPs%[Ws/r9m[:@`[Kf'E>KJO]#ms[AF?*CK[nhiGobg9M]WN0(uKE<$a=R$n=OL+-t3Y/,#7/J`kKU83^G7LFi)Bo:oknH0^]-l@!?!6__s167U3p_D6Afgd*QKl72S_/(<Y394CfbU3n^rP'GEkUGLsDhD-J4S$*Q91FbK58)UR.M7poNq@W1Ob64tcH5I@A4:#pCSL+rjp8dj2&4DYeW[0:2@er.*F3X\:I&#M.2V$:;U*fTg,9QR3t#MR5P]\dANhem:+(BReW\udmn&objqirsn6=C>@2eA\OB8;/@[:U\GCpE#JHn>5N'$,Aj+_]lZQ%C:u`i!B>XB0(_t%+>f-L8=QXd"6bgok-6CfT7cF,$Z*R=bR4m.!Z2k'tSCAi9*RDMtm-,c5%X"-uQckg'mCn"T:<83*WO85Z]uUGIn/d/5i-E2h9lES\ST#B<-,<q0;BMK>id'K7a\CV$h?o2ErJ_ZAS6)`'VZYO*8.NhK1o3$uK;&bqVu9Wka$:&GtD-J-_h_Dt"f;Q?teMhH@0I/FTkMk*(.UBj7Nkq&qm!!Ub.V$'.X/='ZkESs<;Spnn]&&]PR@XE8G@TCge-_s_fE\h<MYc?n21=0!O_MBdS(#OVnG]SGciM-G."ks$]"Kt3_h0>G2Gh<\QtM,%n5R*!5`4H)8]W#''5B_QJA8Xc\_]UmGMM%m\U8`\LgofHIAX!5uOAf!M@36nkk%]`Zbj(dsjokj7!HaUk7_aZC\CZMu>_X5mB2W=@Eh--m`[h$/U[hd\MdGEJ6<pMW?2elVT"e8?uCk!KR3)LSSYtlarC%L-NZ=Y<OF"\,=W)=P:lm2l:X!0a\N+P9p8E3[qC4:o\AJ.M,dB`"ZR:g#+O3#),fjU&6f&RmggZ8%\aSI'd;$=Xagb')</Yg%Ggi&*5>uhEBYAb1+<q0faO&\5H-@"Bp_*H!AS=g&[rWa-MSf)Y%#;Tof/Jnn,3%WfQ4iLW`V0Mma;nTNb*fTg,^+mUYk(SQL93Xk2pdH#IGK9J3A@OH)IqNZW2U"GP$nc[*`OB8bVhr)6LAQ-Bl0!cJ;h%.VRe+6VkXXarO\2GE0.`^W<a_TA'1;I.k)Xe2a7Pt`")L-M5Q.DN,o#1dI<U`l57g.FmAV?AUZC;HMtu>H*s9=S)i>^#1]~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1415
>>
stream
GauHKgJZcs&:N/3FSX&#Rdo/i)Wb[aKaeb489".*Cf*NXD'cb:aX#A:p=m'!Qbh>O^k+")T'8Fek"LiI@6jA#WkWucE%lq/0a#F(2?tRcK\7doc)a!bE5`i3!tgt/QNM4rq#f<eMSW=]\_LbhWC+$;6#&lrFu=72Eua=-AmkVh@R(o%*A)hUAh1!Tp^<fUa3^"%9"C#])h,Aok7Z=L6^m[$JtW?`2V"K.8LUnc)eK\@cd':q_#%YNEdsK&+abmtj"CeMinT+`PJ-oW&+[Tm_JeSnKLm)M\9J+^Y:t<-`Z!c.abc#Lf'uO1%SQm5H'-<dfF)U0`#dP5S<'U_$`&#7e=i<:5oi(GXb'aYL!`4:W?ALV:$Tc:0!r,U.$]*4a)tl<$$-e&cIf7YDfp-r^bfOI<&`p5SSLTJ/Qcl=&SNP>,hP;9(;j/'0@C>dG#<XFp+/Q.dZJ@9]57t"mTW>bb,Or1+8tiAbag5/("#a%%$$9D_DU?T(ubm>lfuRPi*6PgAUZ`On5Y22')iT[bR!??hQGfZR(Vq$pt!5&LDQ&Am+u:KY$=elG.DS$K,3dUYq,te<2_VjT?l\O*uG[.eTa;2Q<B@T526Aa`+a>dArXrp9Qu,*/I,g"[G360W`mC$h92Pcf7<=ARfE!4r!`TDZ[)Y3YF+?49+pj=Asc"mXJ^S+bk,!oY]t9_Dmk2dY>K=U69g;7o:nj_Ue(D/SCXUF2\/6=5N)D]WoEcb*S36^>_h#54Q"1Ps"$s*Ek\+^D60'<@hBEue]h\L16X'\"G/aC)&t:nD%f8"f+Xm<oo_Z&U$$p[XM\V["Q,F0>Fp/TfcgKqY9uXUlQo!8o.CfHaZ&O)dB(4@b%-(VQ4<a,Fnd%h=J_uGN6+!DE0DU*d1X)lO[NLf,k2P6+!7cT8cX$"_\1<pg*d52(Z[9,U/`0I&+:VL@cM#5Lm'Un"MENJ]a;Eb((h%%OTh`#b$\0E93[sq'SW-McnAFn^*H9nK*CNU%Tcf)A:SD>^YL.<<i5/UCmc-Zieos32qFBr[7o5;&mcC;aHDQ^pSZhPBLsbbkh&[R9roO<;J.ta>8uJbk*U)oPHLpYg,Y"<J>*mY!G><[7NI,!#,1lHA_6!V4]nRE)!tguNK`f)5^TYnJ%qKPEF\#S^obn7DX*]re<k7B`YeDu%X"h\AibWqVSo[`CE!!tq3nKupEkB)';k+G#KD73[rhlFF`hO/0Z"*^F`NN!_%'i%?e?!tYP(ER\p!K7<%a;2CYj?2YE)`kgGa3q`T`H2A35D1O>8X3,'_e8AGt\)mpS&(d=8+#UVCu*OZ$(RdDI@Z^+VDNUYejZmlqn!?Q=L*4?qRb^"tt94M&2<St1?j3\,EZ=Jj]'O:hEG*^^4m#gW;i47"u)IJNnHA&#pm*D>FbCH!98IfYQ5>2T~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2015
>>
stream
Gau`TD/\/e&H8h>ED9<Gko-"d.#Q0/TkVtbh>4^/)pl,4'U_OWaHA'q8^fNrI7Ho(jQ=D^#.1gIM'ql)j5"c=;@Ft&RJfqD!)FpqIa3I`QlH)>7)7k04I^Sf4MiBT70(8-e;lSNa>Y1h&%rqa8S#aa#)\.'K`Zp:7&`]Xa)PP/67=]24S0JP:sr/H+Gp>#aXcD"+ur0DJcnt@hi;XB;oZJ+aOWC/ZhYa]p0d9`*3(XtAO.0KA.GBdDuP%`^D;U98recQK0S["-V$$f3=fQm`R\p,V,aX1Ybt9><\3AoY;]m1%$QH9f)T.`:'*7j(&t,5J4[OL<>PlLm+_:hc!uh;S9O2G%S60Ad#]a:q_Z5nh!/k)8&m#b80MF0nUNI*:6p/f,a8WnOE&hE&qkt%%jaNn-h3]8DjY"X5WM\prLs93[a1C+c@AZOTRkpIjtV8+qM3Q$;5Y`1BQ$=o%;035fM,j0o>E`]/>p_$$2sY0iJo4f,c[]]+CpEDXr-na+g/!MAO@4Ai^-c-k&7F*Hti*q_QVr<7^*/D]7U&64k)Jk\ieLhlkdi$SZcOT_uqd<[nL0D0emh+[:+`"!p1E[NR#'^_QZTn-d+WGW#=6G>]*/#"V$5"eb)Kr-OVU\N)=Jd3gh1Vis.X7+DNtu$_.?nqM$.\W79`[mD3J>bO@gjF.g4KmJ),grU%"?n0,*rU=[d9+q\H-8`c/_kIcZ&,%&P;c-mW;^)Ou;(H+Tt9K\f\j*<B.=]4=gr`h,!U+VTQ)cs?Zpeo=R#OJTDG[#MkT.b/c.apfqO6um*7ibE-T@]53PFs/JV_u\1XqD"M_'"lAhaXfr,P*n\=?IU(AMR$PRJonFC:^rbb-f7a@>([m."=@SO_[?5[DHV&"kjh#a(Wb0e&bbN,_j^Li>Cr-Zj3A>Y4'SJ:+6c3ZEYD*^GfJ"j`j#7a#ZEg6_E,4aHRkL7`:<^kDQ(9i<!=3Mt]s<0B&QTk-/hMgW'at`q*_,NiS/3f;K''#pLZre\//!C!e?!P]:#4nXVO=B(TAN948.<Wr%]QKPA%6,2a<Td$fGW07a.&XAOY*=*9j&4-8d7hRh7a"m#2YDo9ShaC`"GcrIdL#k1]V(-.6iTnI=!j`GR7AQW<7:p.-`@6kE<@p*:TWGu]1*Gnb:/sE%XEB^GMJrL:p5(W[0[72q]#a7Of;F$;giq%$^6E7Ujo>Bg+\k.cJNRN_?dt#V!G`e%Id8:#hY0QVAN2I6=3!+8PJW&8+0\-+ei.BQG!&R!Ge%lm6HLpe'J+n+6]At*ZC(l>M"+&9Aq"ba!M7M<mfeFDs3aP9DfX1*hQ+C:f3VpQi2QsUKT*r9:@lOpOdC\PH%DTT+o=FTmH>nm6)"W]=5/QD"V*RYrr)g#lo(Loa+OUdiP.,Tu_ZG,(P1Y#gG+%*hr48\qY1Gn>NU^/AG.Bt:9<Jq]E/I]$XtN3EF/A+#f#9#[1n2<W:bMA4239&Br=H$Q&$LQ$-P6dh2QVHr'#nJ'>R_BUgSqZTPF*9P)Qj9*$15QBYNpPe^ahLR\k;D91V:i!iE<D@jr@G&pYdY]LW*_2'6WW%[ni\OCUbrT2t5+`6Wk,6n:1!Mlt\]u665N-)V9b?L*TD.jtr$lk3,Ikpma[*%6X"^^1e":f"UL?!@3;Z7qPTLMU9%7oH7b8e"PfMJn+>6[>9"`dLMrT.5<o*Hq`9)bs/UZ-?'*;5D7g=T_$j%\KIrJO9ZuHj1aN%d(V-*eq22Y/Sm8AHYOSP`Rp^3Cll2S$/pna_FUdF=&LlZ22p4t(,f*Z!Ot/+0n$=8_P3!Q3_a^-")'OG%*&HVo>?S8XM%3#.77:P/.OCbLN&^%d#Rb1%VGAa1/2jn(8ZY#+.D#,4Y)h1;=+!/m2"F>C!`K?OVPf;Kb^MG\$)_Iee1<ja%)4lG0i.M\kCk^s1R,-qElui.QPrK\AD_^Qth6cRhpi@992l_Z"'t#Hsj(rhW]"i8\%OC(s`9N.ham6N>F;sp>[X)i$cRP3PN'E^9_29rWBMq>ZU~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1514
>>
stream
GatU2D/\/e&H8h>EA.s>ko-"d8;c[kpMK431hl!O)!7MgAB%Ij9<KP<)i"'qmmp$7nlC7-D-56Nm^\*sa#c&LpgrkXh#P)m_!EWT`aNin9Fq5_@sG<c^''*>*XN,ue23+EO>sThW7F68;@sZ8R";+Hd?+F&)::Lr^sOcl=9On?b@^hX&h+`5!CS`C[!H+%'#clB(.9Vehm`(LBhL*+Wagf@gE2$@#4!&d;+!VtPECO;#,^ibIX_U;P\X3q=(Rf;GuL.'WduJZ&^9,Qc0IEk)mb.Jk18VTG%6El&/Nm%G\2A`n:fdg,/(V`a/0'<!&&In?FI)9aZ:pbLdj?G0W%!piZ6F&C(c7X_Hd!mi'E0Md@jB'_'\*'['sA-#<X2p$9aPlh.[24,/qS8g$>GbQf/D&N*.P^MGZ$mi3>Z#l?EBD+@Un\I3((ibD)A5"&\M5%HtSO`s5DG1\T!/XZR&q!`r=ogSTGnf%,j3?:As;*i"rS9"8Iq"GXF[QJ/0/NUfD<)s7%L&\`;\1#WZ5D+1ZGlh1hfEO%>'A-KX"<HVH\:[fi0Ft81k<o5aqG$eB#9mp/lfu&pI*A+A];*\AGVB(BPo_XTCmXDT9\)b+QMrin<,*,WaTm#C=(!7r-'t:Qb[[$,-'RKi=D(ISrLn_<uGZc'n>SJ(d,2Y0jTn5aDM=,N7^rmR\+W#_j-ngL8O>;J1S70f*--sA"&8HHo72fl'H;+Vu3r$"'WeWIYJSSm_XCetjZq`uE>up0!AK$c"1QKFM-"0EW9MYV9]F4DDEJXU'h<G&80WSi`\i!JNGl`8.0n,f1=@92B>&VT3AZAC%2uB]l`^sjg]f"NAj^s\j.l<h3jiU8grtn'l`;4+VLnn3Yl@2Q@k;BDOG$XOM*Yh8PRB=]8/_ZtNjD9N5H^5Bso/E!+jKC`7KHr%+o[oW^DXC&)EcummIlT`?oD/=EZ)(5%R62RJIs^cW08=C0Fq]TU$994+m*Xuu`cg-[qKoUHX&]/m6jrN0&DOVM[pO`>$\JNfRcGYO3*F]7dQH:<6&\h`gaoJ]//3,P^d3n5hKG.2<"IOK-m9TP6tD;T;iCfc$m:]Dk]i_o/RIuX&T;B%67l)m(Q#>0VpPEAa"rE"q"+uPi4p&-#A_/!'LuDc(f)'=LU'"@:rf5AGD$Lo_1K7KopaD?$F`+-5QCKb,-99_nP6Q)/o/ZOQsA*O(?"F`%F^Xj:Ps:[?/N0Q=g+u*j'6;JW+8gfd$alL/ui3#".]J]jiKqsk.AXK2JK=/&(eM6B"p5`JA),W7k4/bA@H/r8Dk`\S>9orZ%OKhZn*i925F<pb`E(ii58K'5tF-gYQ$VVSt:q&qrVAKqef=(3%88POE#ej$m'$t<DrcORr>c]Q==gU!mYuDD-8a&QHLh%Qd;^udcWM\DIu'FlV3P6S1P9-aff>Ac0tk_AELnIY@f6OTl-l9g?a"5F20DB.SSWT^r[?U@1Om'eh?k6pRJ,do@)1?o>UW8\SOhhQhPpccPH`a!?!/jir~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1652
>>
stream
Gb"/'bAu>q']&@2mOiAq_Z]`9]kJ+WR5j4RNiStFCg[mMBMem7J@=49n)[L?6WTIdSYf=W,>oY'E:3Ti'O6%7p,cCX]UZgSCC6<BC_"rB50Qd[kF(q\+Y7/d*rti@lbs9'lnpHCR8ZE`Ot^K&"n"j57m?^<'$ats%+do[NBg.(5BSB9.!-^%"5u'BH$Eu87mb47CS(`<(uJZWoA^:nR"RUaUtL/-$tmpnW"ISOM+`ILJCt;'hS&r2$u])l9sRTqJ<*aSA7=FQ@hH@5hLAs-DQHOjQ+W?8W`HF45JZUeAl"o0-MY=$8:^>Mih&aD1^(H.X6(GhWQ=X.!2&1s5b/BM+mXtS/gm7R7VI\b(%-=F)".3n+2Z0/!U4LBZ,0o]EF#24`Ye/Wq1M3\RuRWhSAZ;cn:ksU)lRa)`d)P(,IU+S3?s)`+@1UsIGn"M9%OU0&]bu:8K;s!U;.iqaaWb.`^KC9=ZENZ2=Ji_#Ho*(Bq71.]Q]Enaf%iLVp#7#\/l0*CU.V_(IC2@hnDkT_\PH`ln3bI&Mutl]n(Y[NN.,QNq"?aC:1(CJ\6Y.Ml"ZOQJ9%rjR24H?a^NA@mKq9ee<J=g6b^YjLMGbjaJD41cHgV8W5A<\nj^MD''9LC%U-I_8^ROXQPDdoA8i[)gHRoD$o"o9VZ8G8bB*7U*#AAh$*.Qf!&i#m\;SjH_ln:$$0Nrfl")2Bs.p&KdbD:k%S.6Z?ujhYE-m`8>XG7&lP!2efP-X5G%ej?G"?5X>i^/!)nct/<i$dCI(UUm]3Yg*A2DYooRPEI6=3Y]lk+d`T])aA1CRhrdRGp9I:rdaAdc([SiG);Uj2`@R$U:?R>?$au7s3_$C%?[F,73Q."mRkB2N5/%`^]__"o!WX?7#aV()bdJM!*QZ,o>U\#`%*5-p`o1Base8o?jU$bk3IOS<<US?>(0STI2flK^@46B:&%_jW?IZ)u91!.,eoHs?q_XPpX8I/_cW[`\XCm8k%(j;/-AY#M[Q-pGPq\*H7Y6t%FL&!!:-89K60tp'[`p,UiFfk[i8Ga#!qN1.))%s>\@0M?W'[g1<AXk-_*1\-['@_\7)p;$3*ZSl$eG`'D8Fn2(M?F`<1Wf?HpJGR?;u(nVrJ40)s+d(WoG0D=9dLI2*n>R/>G/dk.;i>tmDTo36/;8=FV,WiU>rZQNRfKHkXnsdqo\H(W&qtY@tirQ1QDjp6fV$&,d-T*dh+df8XseE@kGdk$XkH\rW1P:cPe_7)WUV;V6PKfGrt)ICYT,pYrQ0tF4Vu>-#9+<gO!f*6/7pI^!GaoM&Bm`G\mje<nHhgL$??C<,&U)_>ptn;8kgbR]1,'6]1&@*)#BN\M.5>'mZRNG0Y:"X/:Y4fBULdb56FB#oj;;Lgm'^E<p7=*H&(3*XsG)r`dqL,VPK^@id76ES*a@3>hTAF&;Ps9p&d"qgB:N2bM?D93E2"gTeg*NV*HnRre=8>njT_UH&*=!Hi95Q.3,;SuXuFlKc<]86T97gL&jQgHRb_Jj8g:@F'Y[U"D.bNP6>n;nfZns/,F/?Dsnl0ZcZE(\?4qL1];WV%4>p[uM[2'0*2e,2I[8`R@-E^WO,[ljRk[p!W9B$q$u\=Lr[2^q1RjQPSnb#ocj`qQIiNrhPhAfU:R[C;BWYJ4/Q#~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2165
>>
stream
Gb"/'D/\/e&H8h>EA133m:qBO.#KK.?tm6\*P*k/ZPD-@&$em`8n?&+jKunQs1Y77.+tl"[6q8RN?(ZdmiL>Jcb7[6.)&gV)6Eo\T>C%n$lFc+"Y`4FS@sRtc^ZM@E63Oe!HAis!tHSqboc">-O!9Y)=W0-U-;Gg!216,.T!oXchEh<\VA'*LB6okop#NOQm\03U$iW93DM/^-Y9Nchq4icGla%eF_GCc7PL'7NDgVC'ne?gpY]is8$@C?irqQkQF_lQ=dHklAZhe.$0RiUE:RIa;A=+Jl8^uWf/h#3CRpdciTpWk*gjKapd/rZTm=9F06I:h!a^a>>L]mTiA+.q#^SJ6ED9X[aW!TqWeft=+8=dG"a=3V)!))R,ci$SOo!Xh,nBYUV*ajp\8%fB@D:h$&[.IV*'8m69JL.Plqi4QJHltFA[RISTH/j8_"Ks3(k0Z>0<#,LG*^4CSJ;Gp=<ebrlUK4laB:M<Vp)3uqW"1VG:N/UU)J@M[Z")#(#po#`-%?CW\*Q*$Xr]`mpCG:ZGI+X^PQkG_#i]b%fS+W:>rd71IJW!@?SRnEe"I^ZCVbr#p[8V9sf!S2s%m\>I-#K%&\kk878/'h;#V#>(IXspA#N_$8m^aDtg6e$sIMfJZV3&n9Xn+MN*W#@[L#RE5juur[,I,*R4N[raBXrOjcj/X)3$k;3F"5[*iXTAe<a,2Q[Y7gG$hpd<\YVM+3KXhe9\oi?I$jLKF=>8M1>U2k)PV)pOR@R$S]uSR:^\Y`8jN2mQ5B(t2qK@T+M!P+WVN=j\]=fEl=h:CD,)VpT@&@Iit>nY_b]o6@\q@m+:$U[TbaA!Ss7l"1Bom4=*PeW:@[Mam83)4"d]2km32TB>ubX,A\_>3p!2@$>5c*aFYs&q,_3E1LPB]4anFRXO'e(HO+Vq%0\dV,hiDo3&ee04uHJUnMC-c/c1&nc&YHWD_NjUrBUq]2droR)\'nB+FIi^!0180$_^O!cKb=^-5dE4V+RrQcc,CK6^Co^^==,pLY<O09kE(\V+P."=2I91`T?:lcXT\1TU#MU$u:jFJjJ*HPt1_W-aWM.+osah%LfbPT;gA8#V?4Ud;q'g8[u%']I$KYA#4XJ'uKb#ZgHg!Mn"(S^)0K638&RMVV(DZ(VNo>*k$lMopJMa+l^6mUr<./D@3Vel!Kif!<=$MRD4nJQ;)GQ%k\-(bSaS))6k5[!\FhFe-_"`1g:JhC*#=eE-?a)b7T)fi`Y)hnrom7V/*dJ=F3302e#$80ta#A+FXnZ0e82nLP4BO"cVPki>X20!J)3E#GOWe.p_\8p6*#U#-tgL%#CLDt^;qqauj<`758U<o'<9Mp\``adqSII#F6<n/]s=:rPFg#FqQc_psQO?'#@KAT&`lAJh@8"$CZSX3OXuL5_ikR"k"99,%kJ0#DCRf8fA!3ap[G`"@kF`GVWtI"VjlpIA15'00?1a+Kq%>uj8E0onjK>\LqkSfTC*COmKlW:%JUd9!@B2=81m2jY\_l?o7qXc7em9m:rGN,I+Kd6h.I9^]%:*TDC>.G)V\YR.)JE2g)MbL4:^[FLKB&H]"EV>3&DdVu<9O'qNGQ7n<Nd:Tu3P.AjS=P"o2Dhmn2i`48C."DW$]E0mb6">Q\3i4QL)-pUWlEf-AU.K]4+<3C"&TA_fF8Z,fSA;$7BGV&JbIPSu`/-)Xl*#pER_48K\*J*I9"SFfA]%F5eP*:5"Ue#%@>(a];%H')1!>j:L5n9Efb/E]7VrI6.cSh^Y">cj=XpZ>-$XVSLQHC;4;n`5N`ip<MZ7nE$5]0>ceF:Z+gD]F5-lu%Kru3V/8.OIbh<:@ZZ_"F@'lEhNaoNDWM<%LXk-uB2seQ/)@MfodjAM;XLUIl3-fa.Eo2jRNWk!iqYs_GhWSXBa5t?-_V(6Ci>QgErp/,HgL3,=lbn=IrE)'m^-?9V[MiJ#'U)l>Ltcr-/[NVM(."kMdq_'FS"gS(Fk<$Vih_BT=GDGD-ud_m\_57#=`45,c(TqoH6!c/RZaU5rh7Z-/`)4RN'>6C<G:8_:@HKODQ_=@b^!G[OtCJ4W`Ipq5H!XoE1:Wekh1NnP3=!Q>*o(uStfr\kE/)$\Ln9-dg48joaf*a=PUX'J1OE4L@X9m_cJ*;%E5g:a5%e$fN17c4O18ie[o5uc9G#:[PR~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 717
>>
stream
GauI39lldX&;KZQ'mk$YK0Vaoioh`6eC1d!-FB[U*$O(dJ1-SEk2/\k_]NmaWH/%VHN<HFn$=M[6f:_pr5o*!@flWe##$/-"/^l@EW_qsdI.YkN,T[9$:PNG"Ag(U%]C2=&gE'49O\DSi(.eo<+$t>/:b5ic7t)X_>Ibq!HAlo5lh930u8n06c97MW@<]AMr2Nurkuf_N*,8gfk`;hk#UCMC9&7N']=d;O;'DJpAO.g53mnXEiF0k5A[I_d_Z)0*%"U&@SCC89&WB*WE`MY.d[I3IR/"ZagJ$B6\-3b5LdGF"u$/RB<7]6ZF@//<mC[MHU;qZW(0T#Th%QPFj4m/KB@:ae[-W9g-W5Bl/MqT*4.FJqQslu!4joN?^"g'l1F`OF+3%ehdqU@KX&>)AB0S0cH[sbh,Q+9r:TWhX[f'`PtPCdouC+@[T_r9Abr$e"ZV[]nG6C&dO"X,J+]WA>\&#gIRI'Mrngh+qbk!'=(dfk9Y*:$c093&IBGB:VkpAW[(?6$oW2DKJi[AY;>7WHUqU*c`Z]90FK20'Z?b*,GM$aA]^F)2X)NYR'Z73M\^"d4BBrO-!GPS8=)Vh-/+'Xn<@41jW7'QcocMdgSpJ_#Dg8REp'Y#oVudiUS<<P?ZUkZ7%]GW8[K_N^V0Y&_Dh.q%h>9[tjB3@+W*ST\55K#"Xt'+=r)pYQF,fmPWh37-Nr7Zi-]D@4/q"!5~>endstream
endobj
xref
0 29
0000000000 65535 f
0000000061 00000 n
0000000123 00000 n
0000000230 00000 n
0000000342 00000 n
0000000547 00000 n
0000000752 00000 n
0000000857 00000 n
0000001062 00000 n
0000001267 00000 n
0000001472 00000 n
0000001678 00000 n
0000001884 00000 n
0000001962 00000 n
0000002168 00000 n
0000002374 00000 n
0000002580 00000 n
0000002650 00000 n
0000002942 00000 n
0000003062 00000 n
0000004900 00000 n
0000006178 00000 n
0000007466 00000 n
0000009202 00000 n
0000010709 00000 n
0000012816 00000 n
0000014422 00000 n
0000016166 00000 n
0000018423 00000 n
trailer
<<
/ID
[<48814193dedc24c078100039424fe853><48814193dedc24c078100039424fe853>]
% ReportLab generated PDF document -- digest (opensource)
/Info 17 0 R
/Root 16 0 R
/Size 29
>>
startxref
19231
%%EOF

View File

@@ -1,377 +0,0 @@
# Les 16 — MCP (Model Context Protocol)
## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven)
**Lesvorm:** Tim demonstreert klassikaal. Studenten kijken. Zelf bouwen = huiswerk.
**Demo-app:** Eigen MCP server voor Polderfest-data (Node/TypeScript)
**Vervolg op:** Les 15 — RAG + Embeddings
**Aansluit op:** Les 17 — Externe APIs in diepte
---
## Slide 1: Title
### Les 16 — MCP
**Visual:** "Les 16" BLUE, "Model Context Protocol" BLACK, subtitle "Eén protocol, alle AI-clients, jouw tools beschikbaar"
---
## Slide 2: Terugblik
### Waar staan we?
**Lessen 11-15:**
- AI SDK, Tool Calling, Agents, RAG, Cursor+Vercel deploy
**Het probleem dat we nu oplossen:**
- Tools die je bouwt zitten VAST aan jouw app
- Wil je dezelfde tools in Cursor? Claude Desktop? Een script? Drie keer schrijven
- MCP = standaard die alle AI-clients begrijpen
**Visual:** 3 AI-clients (Cursor, Claude Desktop, ChatGPT) → 1 MCP-server → jouw data
---
## Slide 3: Planning
### Vandaag — 180 minuten
| Onderwerp | Duur |
|-----------|------|
| Terugblik + waarom MCP? | 15 min |
| Theorie: MCP architectuur | 20 min |
| Theorie: bestaande servers + eco | 15 min |
| **Live Demo 1** — MCP SDK + eerste server | 25 min |
| **Live Demo 2** — Server laden in Cursor/Claude | 20 min |
| **Pauze** | 15 min |
| **Live Demo 3** — Eigen Polderfest MCP server | 30 min |
| **Live Demo 4** — Resources + prompts toevoegen | 20 min |
| MCP vs Tool Calling | 10 min |
| Lesopdracht + Huiswerk | 10 min |
---
## Slide 4: Wat is MCP?
### Een open protocol voor AI ↔ tools
**Definitie:**
> Model Context Protocol (MCP) is an open standard for connecting AI assistants to data sources and tools — created by Anthropic in november 2024.
**Het idee:**
- AI-clients (Claude Desktop, Cursor, custom apps) zijn **MCP clients**
- Tools/data-bronnen zijn **MCP servers**
- Eén protocol voor de communicatie tussen beide
**Wat krijg je:**
- Bouw je tool ÉÉN keer, gebruik 'm in elke MCP-client
- Het Anthropic-ecosysteem heeft al 100+ MCP servers (filesystem, github, slack, postgres, ...)
- Voor productie-apps: complete decoupling tussen AI-frontend en jouw backend
**Visual:** USB-C analogie — één standaard connector voor verschillende apparaten
---
## Slide 5: MCP architectuur
### Client / Server / Transport
```
┌──────────────┐ JSON-RPC ┌──────────────┐
│ MCP Client │ ◄────────────────────► │ MCP Server │
│ (Cursor, │ stdio / HTTP/SSE │ (jouw code) │
│ Claude │ │ │
│ Desktop) │ │ exposes: │
└──────────────┘ │ - tools │
│ - resources │
│ - prompts │
└──────────────┘
```
**Drie primitieven die een server kan exposeren:**
- **Tools** — uitvoerbare functies (zoals Les 12 tool calling)
- **Resources** — data die de AI mag lezen (files, DB rows)
- **Prompts** — herbruikbare prompt-templates
**Twee transports:**
- **stdio** — server runt als lokaal proces, communicatie via stdin/stdout (default)
- **HTTP/SSE** — remote server, voor cloud-deploys
---
## Slide 6: Bestaande MCP servers
### Het ecosysteem
**Officiële Anthropic servers (npm @modelcontextprotocol/server-*):**
- `filesystem` — lees/schrijf files
- `github` — repos, issues, PRs
- `slack` — channels, messages
- `postgres` — query database
- `puppeteer` — browser automation
- `memory` — persistent geheugen tussen sessies
**Community servers:**
- `linear`, `notion`, `asana`, `figma`, `stripe`, `youtube`, ...
**Hoe gebruik je ze:**
- In Cursor: `~/.cursor/mcp.json` config
- In Claude Desktop: Settings → Developer → MCP config
- Beide laden de server bij startup, server wordt vanzelf beschikbaar
**Visual:** Lijst van populaire MCP-servers met logo's
---
## Slide 7: Eigen MCP server bouwen
### TypeScript SDK basics
**Package:**
```bash
pnpm add @modelcontextprotocol/sdk
```
**Skeleton:**
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "polderfest-server",
version: "1.0.0",
});
server.tool(
"searchBands",
"Zoek bands op dag, stage of genre",
{ day: z.string().optional(), stage: z.string().optional() },
async ({ day, stage }) => {
const bands = await queryDb(day, stage);
return { content: [{ type: "text", text: JSON.stringify(bands) }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
```
**Drie regels:** maak server, registreer tool, connect transport. Klaar.
---
## Slide 8: Wat we vandaag bouwen
### Polderfest MCP server
**Doel:** MCP server die Polderfest-data exposeert. Werkt in Cursor, Claude Desktop, en elk ander MCP-client.
**Wat zit erin:**
| Type | Naam | Wat |
|------|------|-----|
| Tool | `searchBands` | Filter op dag/stage/genre |
| Tool | `getBandStats` | Aggregate counts |
| Tool | `addFavorite` | Write — voeg toe aan favorites |
| Resource | `bands://list` | Volledige bandenlijst (read-only) |
| Prompt | `dailyRecap` | "Geef recap van dag X" template |
**Demo:**
- Eerst werkend in dev (stdio)
- Daarna geladen in Cursor → Cursor kan zelfstandig vragen beantwoorden over Polderfest
- Idem in Claude Desktop
---
## Slide 9: LIVE DEMO 1 — MCP SDK + eerste server
### ~25 min
**Wat ik laat zien:**
1. `pnpm create-typescript-app mcp-polderfest` + `pnpm add @modelcontextprotocol/sdk zod`
2. `src/index.ts` — minimal server skeleton (15 regels)
3. Eerste tool: `searchBands` met hardcoded data (mock)
4. `pnpm build``dist/index.js`
5. Test met MCP Inspector: `npx @modelcontextprotocol/inspector dist/index.js`
6. Inspector UI in browser → zien dat tool beschikbaar is → tool aanroepen → resultaat
**MCP Inspector is jouw vriend** — debug-tool zonder Claude/Cursor nodig
---
## Slide 10: LIVE DEMO 2 — Laden in Cursor + Claude Desktop
### ~20 min
**Cursor (`~/.cursor/mcp.json`):**
```json
{
"mcpServers": {
"polderfest": {
"command": "node",
"args": ["/full/path/to/mcp-polderfest/dist/index.js"]
}
}
}
```
**Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json`):**
Idem — zelfde JSON structuur.
**Demo:**
1. Edit config, restart Cursor
2. Cursor → settings → MCP — server zichtbaar + groen vinkje
3. In chat: "Zoek bands op zaterdag voor mij" — Cursor roept onze tool aan
4. Idem in Claude Desktop chat
5. Eén server, twee clients, geen extra werk
---
## Slide 11: Pauze
### 15 min
---
## Slide 12: LIVE DEMO 3 — Echte Polderfest server
### ~30 min
**Wat ik laat zien:**
1. Server koppelen aan Supabase (env vars via dotenv)
2. `searchBands` echt naar database
3. `getBandStats` toevoegen
4. `addFavorite` (write-tool) toevoegen — let op user-intent
5. Errors netjes afhandelen (return `isError: true`)
6. Live testen in Cursor — vraag stellen, écht data uit database komt terug
7. Trick: ook nuttig in Claude Desktop voor research-werkflow
**Belangrijk:** MCP server is gewoon een node-proces. Logging via `console.error` (stdout is gereserveerd voor protocol).
---
## Slide 13: LIVE DEMO 4 — Resources + Prompts
### ~20 min
**Resources** zijn read-only data die AI mag inladen:
```typescript
server.resource(
"bands-list",
"bands://list",
async () => ({
contents: [{
uri: "bands://list",
mimeType: "text/plain",
text: bandList.map(b => b.name).join("\n"),
}],
})
);
```
In Cursor: `@polderfest:bands-list` — bands-data direct beschikbaar voor AI
**Prompts** zijn herbruikbare templates:
```typescript
server.prompt(
"daily-recap",
"Geef een samenvatting van een festivaldag",
{ day: z.string() },
({ day }) => ({
messages: [{
role: "user",
content: { type: "text", text: `Geef recap van ${day} ...` }
}]
})
);
```
In Cursor command palette: snel die prompt invoegen.
---
## Slide 14: MCP vs Tool Calling
### Wanneer welk?
| | Tool Calling (Les 12) | MCP (Les 16) |
|---|----------------------|--------------|
| Waar leeft de tool | In jouw Next.js app | Aparte server, los proces |
| Wie kan 'm gebruiken | Alleen jouw chat | Alle MCP-clients |
| Setup-complexiteit | Laag | Iets hoger |
| Distributie | Niet — alleen in jouw app | npm publish, anderen installen |
| Voor productie-app | Default | Voor herbruikbaarheid / multi-client |
**Mentale model:**
- **Tool calling** = "function in mijn app"
- **MCP server** = "library die elke AI-client kan gebruiken"
Veel productie-teams gebruiken beide: tool calling in hun product, MCP voor interne dev-tools.
---
## Slide 15: Lesopdracht + Huiswerk
### Bouw je eigen MCP server
**Lesopdracht (30 min):**
- MCP server skeleton opzetten
- 1 tool met mock-data
- MCP Inspector test
- Laden in Cursor of Claude Desktop
**Huiswerk (~2 uur, voor Les 17):**
- A: 3 tools (read) + 1 write-tool naar Supabase
- B: 1 resource toevoegen
- C: Laden in Cursor — werkt + screenshot in MCP.md
- D: `MCP.md` met tool-lijst + 3 voorbeeld-prompts + reflectie
**Bonus:** publish naar npm, OR maak een HTTP-transport variant voor remote loading
---
## Slide 16: Volgende les + Afsluiting
### Vragen?
**Vandaag gezien:**
- MCP = open protocol voor AI ↔ tools/data
- Drie primitieven: tools, resources, prompts
- Twee transports: stdio + HTTP/SSE
- Eigen server in <50 regels code
- Werkt in Cursor + Claude Desktop + custom clients
**Volgende les (Les 17): Externe APIs in diepte**
- OAuth flows (Google, GitHub login)
- Webhooks ontvangen + verifiëren
- Paid APIs (Stripe, Resend)
- Rate-limiting + retry patterns
**Daarna:** Les 18 — Supabase Auth + RLS multi-user (de laatste!)
**Vragen?**
---
## Slide Summary
| # | Title | Type |
|---|-------|------|
| 1 | Title | Opening |
| 2 | Terugblik | Recap |
| 3 | Planning | 180-min |
| 4 | Wat is MCP | Theorie |
| 5 | MCP architectuur | Theorie |
| 6 | Bestaande servers | Theorie |
| 7 | Eigen server bouwen | Theorie |
| 8 | Wat we bouwen | Intro |
| 9 | **DEMO 1** — SDK setup | Demo |
| 10 | **DEMO 2** — Laden Cursor/Claude | Demo |
| 11 | Pauze | Break |
| 12 | **DEMO 3** — Polderfest server | Demo |
| 13 | **DEMO 4** — Resources + Prompts | Demo |
| 14 | MCP vs Tool Calling | Reflectie |
| 15 | Lesopdracht + Huiswerk | Praktijk |
| 16 | Afsluiting | Closing |
---
## Bronnen
- **MCP officiële site:** https://modelcontextprotocol.io/
- **MCP SDK (TS):** https://github.com/modelcontextprotocol/typescript-sdk
- **MCP Inspector:** `npx @modelcontextprotocol/inspector`
- **Anthropic announcement:** https://www.anthropic.com/news/model-context-protocol
- **Cursor MCP docs:** https://docs.cursor.com/context/model-context-protocol
- **Claude Desktop MCP:** https://docs.anthropic.com/en/docs/build-with-claude/mcp
- **Awesome MCP servers:** https://github.com/punkpeye/awesome-mcp-servers

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,4 @@
# Les 15 — RAG + Embeddings # Les 16 — RAG + Embeddings
## Docenttekst (Klas A — 3 uur, fysiek, demo-driven) ## Docenttekst (Klas A — 3 uur, fysiek, demo-driven)
**Les:** 15 van 18 **Les:** 15 van 18
@@ -157,7 +157,7 @@ Vandaag: fixed size, vijfhonderd char chunks, vijftig overlap. Werkt prima voor
**Vertel:** "Klas, kijk mee." **Vertel:** "Klas, kijk mee."
```bash ```bash
cd ~/novi/novi-lessons/Les15-RAG-Embeddings cd ~/novi/novi-lessons/Les16-RAG-Embeddings
pnpm create next-app@latest pdf-qa \ pnpm create next-app@latest pdf-qa \
--typescript --tailwind --app --no-src-dir --import-alias "@/*" --typescript --tailwind --app --no-src-dir --import-alias "@/*"
cd pdf-qa cd pdf-qa
@@ -423,7 +423,7 @@ curl -X POST http://localhost:3000/api/ask \
`[SLIDE 14 — DEMO 4]` `[SCHERM: editor]` `[SLIDE 14 — DEMO 4]` `[SCHERM: editor]`
**Vertel:** "Laatste demo. Combineer Les 13 met Les 15. Een RAG-tool in een ToolLoopAgent." **Vertel:** "Laatste demo. Combineer Les 13 met Les 16. Een RAG-tool in een ToolLoopAgent."
`*[lib/agent.ts]*` `*[lib/agent.ts]*`

Some files were not shown because too many files have changed in this diff Show More