fix: update lessons
This commit is contained in:
1
Les15-Cursor-Vercel-Deploy/.~lock.Les14-Slides.pdf#
Normal file
1
Les15-Cursor-Vercel-Deploy/.~lock.Les14-Slides.pdf#
Normal file
@@ -0,0 +1 @@
|
||||
,sharp-zealous-planck,claude,21.05.2026 07:44,file:///sessions/sharp-zealous-planck/.config/libreoffice/4;
|
||||
1
Les15-Cursor-Vercel-Deploy/.~lock.Les15-Slides.pdf#
Normal file
1
Les15-Cursor-Vercel-Deploy/.~lock.Les15-Slides.pdf#
Normal file
@@ -0,0 +1 @@
|
||||
,sharp-zealous-planck,claude,21.05.2026 10:59,file:///sessions/sharp-zealous-planck/.config/libreoffice/4;
|
||||
194
Les15-Cursor-Vercel-Deploy/Les14-Huiswerk.pdf
Normal file
194
Les15-Cursor-Vercel-Deploy/Les14-Huiswerk.pdf
Normal file
@@ -0,0 +1,194 @@
|
||||
%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:20260521105902+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260521105902+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 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 2334
|
||||
>>
|
||||
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+-=9+uFH#onKRniB?1=+,HfKdgPkgYTfOBY@F_+57F!]T.*O*`%Q>C(`4FDdsPi'U0t%J?3Aqa2fWru=Aj\Hg+WTg?Kdg3tRu,%<8dBoL$e/>p^OUu($PiE9`0K_Ei-Y:T>pRSi"@SSu:8-jHBSn8P!+PEH<lc7r@QM&M6-GWP1rTL][:+a+n=.VU#Plo___S2*$tZ+C6rh2Tcf)XWggC[>5,5Q\qNl[ekb#;!l*g#SP"lVc^78(8.9Cr*pH2I-M1N%*$r_$*5?,,J/lLToZ)mO!t';ns(RAr;7d:i8s9cEma'p2,uLbj0"LAoKZKXFcf.mMnHgF],(E=OgEPdPe:$*.._EilfsRP%Aen@o-1$9+GRXE.Ua^^uoUP&!*12)s<lfe:?J[c>D-2PgD!JI,4hil@P.q;/$PF+f8A%^*bjX[Yu82'k:WcGFU.PD9G+LT!m0H&Fr[@hQ%nPnkpJuZ;IhZZkLu"(moh[Wi?tRWAJU<H1hQR>qMs(bOT]14Fi:;7q"G3$ZoORb2]-ieZcACTCV^IHAZFHWXoQXF10)5<q+/kQ[;)G#MPr7:%IW:"Ak/roKW%!(irf_lTGEdmjN3:).5hm&kgkP:A,@*Li"L*;hE1g3-](nSV/<a@GKBnXL%E`E0C*I0CXbkb>_o^Kf5>7$?m_3+YE8Mjr(aO?ni0tk:j;-%#!F(**e0q,>.>FYfdi8V[/:VHQeRGdd_O=CSfV18X.qD]7aojW@g"%RbA)(-T-(EY^erF5jA6AY[Hm.+Yes&7A""6%C#Jg\0D<g8]Uq$0g9]MiT0!RHdDJ\]FK[Qc#!iuT;Z-@WDm:-8*fbjbCC)#hN6.>C44^N/XD,,WYK%O$Ir+VXA00i)dQEK)]E"=TVGD[9`<I>^T0Cq7A"U%cO#!&e/X]&@A]32N?2a`"h/aYn()V[\T$D@9<TJ?b#ppK-)q0=dq>O42;#->6%Zjiot.oWoa4M9o#a#.T'b_<BW51jM-6N".ror"q'%'rd_^A&8]7&"`:><n'Gt:Wpn8cRK>Jo#I_%8p1!!Os0*bulg0)"`LO;&oD!%Ob)oEag:(/.b6^YUiX`q(5MjQNMJB^?:#&\l+*lGIgIn?1RlEk4k(%g)+hFP92_&+<(4DU920b^/"=QM`=M$^Ikc8G>B6:bX0Ra@Q6[ZI`=c+Dj8q1aYJ3Bk6FOJ?n3DG0t!)n7qHp;k_5#b0MIa>#QTSf_H6+q.b-r&A0OTl/>i`].T&X\DisLUsFbb+-q3es!-FHC9[cZar)_:[!^Q5KB;3WBIOI<WA/rHpnk<D9<g%[8(O1g:Lqio%P@\[4a1<)76STr#^0,:s?7mbsp=3-ZKS[`AP?/Imb/<W#?O'ggGtd!uRi_*._CrNYa!5BaZUQZ>+eP)8ZUS,Y5l\8EJl^I^>;G2jn9SdX7@])l<GJEcjSqa-5_N!:rtd(-X@\s"k0dN!!0+O#$Y2[OUF$]=hDpV/>RS+o4t90fT3.s"Es;i_ksFgYrZ99-jMVMORX>ICM&_G?(]u>hs*@rrD8\WES6eT_[ULp9I&J<gak=q.te`OohW,#2TI/Sci2<!j]iIqc20fV76&2<6_(,&ksKgKA3EVQ06s`/C\dN:Hlk1Yh_NHa4,n#WDZ/C5#N;hp)(=RZ@M?aOAVeXGYE/84k9<)rq8ihM+'Kb6Mbk`l@o15Iih.Y:SZm)grpX@Pj+A@p@2O8;5V*^i0\jl\.j+LZbVQnW;])(\k'u[CYB/k]e3%FA<2<8[bCC;Rf;J9=skFN)aP"@]'aSgLe-G3_$oT)2+_[Z75&_i^/Gu6D\!G`)jSq;@Mm/k"QF.Fh"PN.jqr[tMRK[F6=j7d=6nG-7AeV;.WmsD54s5ebS=i]T1:.75pE"ZW8WMWdGck`O%!edQ/a('DQmq'N>gtkUWOq8k7h4\:s8Ec@#j!7YM+uoce+UnP2sS&[dJ.ar`/$U!FbDfCe^G"VFBh@Pa3Y=ef9f:]El/P2iEZ&dF"42`Qui8$p+F*1o[*a<t"Ns%rsn*BE~>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"jd6eAtiD#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!M&kmrK\SaI@=la)fqHM"-2AoWY!e-[n<Y`26oaYlDAQnmFh#apG!;Ze"Uf*$@k6%q$r\u%fH+@,>T$fY4)=PpgW"E9;Rp0%L),&co#'dZji@A8=k6g$la>G_%8NN#Dp08#*2qP"#]mi@JgZa]X-jsE\ZMu^`+_kq#!T%<<s@kP2ZG[VqEW=BRS.GFURM#s+f$+)T7jbC%:F^?iI%sVhf_7,aKYQl1:"O?\kS(5G#B=ZB4L[$6;ONnS3jAu/oUiP?gjY88D[FFit'E;3Sd<;]^"A0Ij`q=K;E1(Q[s@rb:V>n=NmXR,MG@9UF@NZi/SreXZnC@&E*~>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.(`mq=(8Ab8XYj[CGDY.aC?l"/&G7W-]f/G>'kO6o`794aH%Y^lpK>.]FQ`\51i5DCtCg-Bq+Y=Q=#&9+n*:+0PD94d"?ui=.T%3N-gE^jUbE*O]ACSrUU8+[UeXqo!">0C/Y6*>%VF6lt_u,l",Z\J+DT(g%i)CTe$opMPCDHgQE_M-`Wp/j9J'$OC5Ck6;d_$KIVqB?pg3+fmih]@R(tC@D>B^VuP0paj4r]nQK%>eR*Qn1:U!4=fC0>I^&:D6ZZAN4b'=@o8p$g-s!R:`CI>_e2]42Mk'I2'gb%MA8fKa!&+:dnO0&!qJudLY4ElX*E/aAf?D*5ZVm4Xr"F6]XYOHnsaY8`T1g'SD@XtOIHLiTh7-Bb:&g>.Ge@b^hj,gCBEJ^dA3A`\h+HK>2i+@SRB4^eIY\&W?/40rLV*r.uiL^(,f"F)PZakZ3BD\[@92]h$QLA5?uEI@g7G3*fTg(>hGM%#24.SanOBY[g0fO5gVn64WLj1B,m,Na#fuaR**:qSma#fY"bI?1kZ4eQDQc+)qceWEEBUe?rJGp5D?eRD-;i)Ed'Fkhaml#MD0t>[QK2WdP/uG*YE%Tlq'7Oja'TBV*r`shGYLPLA2UT:Ai$_Xb\'d<lp\U;3$`I8iB8G,DC66X"b3YA";c>NhXhA.JaA97@dHO1^PZpr=KTt6"=$DJRJB/N0;0&)edc;#+.__\e>M,1,WhUaQr-U<fUQBC^?I6#I$,>-CqPpe'_:ik35p*J5MpmjU[1KggGOK0dmU#?[H]WpVJ[i`pQ*Ee)FIbKJ0UU+$Yg@s"qocG4\1%5bp5ks/6OD-B85@@t`;km4@5g1l1d\'&)-WLdL`Hl]BTT64b_RM]A;k`apY.EE6+KP/)0c=lfV9CmsG4eRb9'YnSS*NH.NcPPEdpebOE54Y4@Hj((1$7ti_q??JEZkO3NF_-#*LkK+^M'#*#Qp%NWfm)MoEHaU6VG@1%;*LlS:d,'[jn(9EZ4pW3<g0us=_1[rG%'a]WF).tY)]T>SZeNji=K!AN_ZFUZ?J&Dj>Y_%2VGH.J2tr;QcDns&k3c/PXR&e2fOuAF=T0>\mPXt$*;/.t9%DNfNjIJL9Al.+dq$"KgRBSXjl2E/f!R2U/MM2'NtZ+iLt1KHXM1<^?cW2@kGiEMS37CX/+dPH/Vdpr?FF^kqhE7kA"t8Pr(uYQ.P>R]Hs5o8OY_9d<#"I<r,fc^50=J0NdQT-6Ck:>nL4sekHPo=;:q?s_#fW-e!p870%-A)D6N8\!Y\5MZ3\@XK!LC\>Ait&52W_IVCbAW$p8+]:GhTJ(;>!Pf&G.&K5I1r$YtqPCr<?V.;1sBn`?hCG9?i1l1.BnJbMB0^]AE~>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`f9U7qL317bI65EY1hXb\+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:O7?aH/~>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^:`'jBNZI%"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^>MK&;g~>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!3d.57R4pbU+'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%DA[Ju.L~>endstream
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 831
|
||||
>>
|
||||
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'+."M65#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^L48#bnS5DsDVb"&7fNH:Q93[~>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
|
||||
0000004760 00000 n
|
||||
0000005887 00000 n
|
||||
0000007645 00000 n
|
||||
0000009549 00000 n
|
||||
0000010612 00000 n
|
||||
0000012811 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<5aa6d014f38fa67da8a3214366a4339b><5aa6d014f38fa67da8a3214366a4339b>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 13 0 R
|
||||
/Root 12 0 R
|
||||
/Size 22
|
||||
>>
|
||||
startxref
|
||||
13733
|
||||
%%EOF
|
||||
156
Les15-Cursor-Vercel-Deploy/Les14-Lesopdracht.pdf
Normal file
156
Les15-Cursor-Vercel-Deploy/Les14-Lesopdracht.pdf
Normal file
@@ -0,0 +1,156 @@
|
||||
%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 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
|
||||
<<
|
||||
/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
|
||||
9 0 obj
|
||||
<<
|
||||
/Contents 17 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:20260521105902+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260521105902+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Lesopdracht) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Count 5 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2129
|
||||
>>
|
||||
stream
|
||||
Gb"/'968TB&AJ$CFAYKW&/[j!D+WjmbI`W@Yfssq/riFnS9Sj[HO/uhrqC:*+aIC>YY.`%;RRlE?[#`1?amt(ifCM!A-qb[$k?W"?lWc+^]G.aln,l;H!`\N"d_L\`)kHL30;3?Io)\^3J<!?LdqjfPsi^o!l]snOTLR7Ugt)f%4;B%"!Ro6.KH2F.$WAQmh<MtM@N0*7'nthf2!:W+sM@#:sJDM_/KPCNO#L35U["O#dYgNNo#21"0[V]>pj8ROhmYaV3A4ik+#(hqI]T2Y1'u0$[AhhlDuD#3c?(Tll9UB7b.i0huVe6+P6/@l5Eb1GUpp*'jKnrnS+H4^d_EJn@K0<K(mCpT+Mrp5-Dqk+5ISiDs7E\m(P<1$l]f;<1#V>'bJONXt:ddCkFI7cnK!BY3Qb>QRi2r``U`MZGLERif.8W?8'Or:kqN2Yf*<87Y]NDI"*rck_J#_LJ(@/;VXds3P(.+3f_(\nc.:fI>%6h)V`O`To+TIg!6@APSIeWN:"&^D7;[XTi!3(5ML(k64u&NQno-k0k:m9b7J:Jf1!U999^Sn^n>3-5%6ufN)@lrc[CnR,,I&8?6"'Y4*'HU5\R-K[8c*l#p#.a1SY's^Fq5%I`N<$)\hC@\nQ6r%mfLgK+8[.b_kkNa)M9TFHu(Vg2K>$dSLF.bBSOHr_QEQi/:+[f>Eq@'ks^4[lcUnVr4Un$<l$ZYHnVJRBae[hB&I=bK6[WipiJcA$$sI.e=g$MTs_WYG\:@ETenELQ$Jl>#j0YEDu?9.t!<31gcZ):\/:``H?To"[?P5a!P\)oaOUYd'jTK*2i[)5"oSQq*O7%\gq;...Kl/jOhk6b6"4NM>h=VesEtZJ5t;C=$"=p>M=JWd)pZW(h2K'a8d/cm03^-*O))6s*06gQRu8e=@+.)N[Bjbi8a5Z$)FqY?bH9V)RC/8nStFg;j%?J/1jSn2T1N+KdGTA.RiR%FlMMsE(r_@OYASTCs*B"i'<BE/NQ'C3#?L9O4_m-5D:#O"E,HtVYu&58m=Fl3.oN^OF-_E^=]`F6o7dU^DTpJLs/Gb0$!Z*.n8f"Z-a0LNTgQ$L[FXa,kI.>nX_r=kBRKTU*i5O0A(A8j%`@3ajW5)1YmRVd3%oC(sY!ZCC\>t[HU'TD:'\bKY+.BMP2RR1d4\74tfs'aM>uKaOc`08R.RBi)Ce.rrSM?o$-7KWiCsaSe]GSW-)YUQ)j`ifr8QP=)%8PFo2MAI_9EQA_a(`FQ&mQk&K&*2"#X\gcROtTnpW"Bl@qn>[TaSe?pTeO8bmO5Qh.h$-]F*U[>shNZ">kC=L:(5K`%F_8e^t0IH=++$O),DG)5/Ku)uJp(eV=E]_8j0Q\S=i:*7^%,Wi7P$/uCV3p6dFP9ZkA3\b90s\Hq3AtXWf(p:cDe_dOVoTS]'0AQd?X\Xu1AeSg-*7!fS+dMD>E`Hg\>+!K39eSR210K[D/s!tV^?BUIMg`1^I?:8Se=Drg$:IRa?=Skjkdq.bLb9^:=cP1k>?drbj!#8;!-jP,;.fErmdsmq"Y+'ZM*HZ^+5On*+Beg-ScqR92hDJ8R=>qQ=6JsJGCI)B9Aulc6$;"^T11[^AW\c>M@tsjHfaaOqarI8iu,AUH:0sLFaAZn2i2=7BFteHrZVod\dG^M";YQ\Mk2sa:C&!r"mc<.FIaZ9_;H"Qh[kgVN4Wg]$t\p^GY'l9C9$tDF]L%B@7h=)5gjViNYYVG?<jNJ_N)PZNRhDPr?Yae4asiY%f7>TQ8h_L,#G:"sJh6+q2I=%)V@bAU?hrJP4Q)Z5V@:KQ7O^(K.6[C[/^6P@[ud3?+pG[@cohEK<1e"8D3Mb0aLd0l6*,OL40hB&--MJu$gLmc8FoYUQa+P71Z<1K9\pTC4s;)t85+mX<AB5_/++q-N>IWRA(,NB#08Z*qVA`5ZS!9^=0r0%((G.@-YB9$PeoL3l]+&+3^TMrU7nj2G&"Z5@\rHIDR>UWYthY&d/MiGL7F78nD';uU(L'^o`#7<a3'K"f":XeJC$_li3,kE+Z\*4J+fGZWn]C(U"SqHW=s\b=]snaOe(!hN2:NtVr>iadC&;e8%'Hqs:N[qWSQ2LhsE;qj2ZPg6(/T%pu^d%"_>j9E8H!K;2A70~>endstream
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1390
|
||||
>>
|
||||
stream
|
||||
Gb!#Z9lK&M&A@C2b[jX!LhW$H-n.DYAD>pXJ42EO]'[HQ7\Xb=P,OW[-cf_GH\^*`5+:8a7ZJqGRQ%!eO#(<3nRCoJ.0/5.@J,``j07AA'1BY41ON;Oh`Sb%%n-K:O/O.A+L,kbT\ar?"Hpin`sa.1Osh"e,"A:TNe!m1(pX]f%GOgB!=:%],69'!JW/YGH_3jSdBt*JPjq]L5/,*m(DGIlLLG?J"N)Ths&()A0*l,T(r3_5`G],_\qEjkVjh489FcQ\[8-EICcDsaic9=l1g/;m'RnG"SXZY+AU=LE)d&_9?Sd)G"lZ]l+6.k%%BohO0ND;W&c,,eZ;L^q@=?,-%UbIo_+Hp=9EkRon-Gd?QC,%!m#l:Nk$aP^%ra[d_@pA1-%&t@4V:P,3%^,k84!Ihk:=sL9]D%I%*9'pcQ(HE((_pJ%g+dY`na!l"QKhU9p=@B,`JJNcZB5`PHVp";_U4,``RTbl#!<P`7oW6?2!POYkmQNlr6YMeE^Yb>te_Q\-.145=!8L](b@\%^[kIarGN'n!?bLlF,u61DQjg>2`/=[^db=CLY)E0bGUe\AS9@<">lskSf_?l<qn)@7iMZcJ-=gjn\4u5tD'D+c?W1Dqh9P?`be7<`MU%Zbg-njDG-pJ',)ACO\`bYN#uN^^hXpX7?Jod.L8fn?XPn;kislX:5C1THPgkEcuSk]!rDRVp4Pc^/%(fo+$2T=K=!fbKBGc^'gmd9:ank`2:++[&2QI&GV>/ZpTS]f%]nXUi;Qp/J&E,<2tJn,/HCK.aK/X\iDd'5!dOpFk5h6^c1I5"QaJtkkY`8LX?]JChiTcWtW_#YPPiEbmmS8qf\5><Z#amD@CdBgpX(HpN^#Q-qYGl\O'!%6>Qh<\4@6<[4!6"V)6qE)Zme3QB7R<$LI\Qmh`YBTd2Aars,C>kOYF"ooh2u3<Q97VeUhX[Z99"<Y2pAUCFV5s)HGR9;3o:!r4gi`I&4bA+%+F^imcg`;*(GYkV+iC@.&%%A0+Xja5I`k<>`Lmk*\[r%BsW^YK^)n"nd<%iMgKCSbe:Y6#R\-!%*MORK>0<^(3IFfr"L-7pKg<nVYU<WuAH4,PHI35KbXpi\F]rKbQ6)F-'A$+!+]%*O;<'8Vkd3o25tMaQ>G9$;^&67l)e6\%DMrdjL,\i:AU_r%@gHLGsD5'n*qI2qfC`-<>[\0(A+)<rC_U0HWQ4+:"mYX'f"p>k6_Buh^.M6"t%m`P:>24elVj-k+'WMcMfI[sK!2sX4$'o$:@8aYORnno5liW;bu=7R7X1YkC#;I,m1dHKE5EIMRD22/=U)\!-I\h.LG9$2"Z_^7mb0$n"F3T,s4etT^Si<IJd5t+uE%AKTJs5I-2[DU/Cid^K'E*qE)hEUd$I&I~>endstream
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2009
|
||||
>>
|
||||
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(0H9MEjjPD-Wa>q9-k/EMFM5\:m?BWDFk3B1&V@U(+1El6-#did+W]f4@W@ko[mh-;ga/1WD]n(H4%^.J3d`?\].p!jA9c!S`j@3?no:1<K@)8Q1WDE$#sra<]8nOu:Kd^Z)F`.X#qakX&`XD^A38It0o.s!k#RHlrFr]ir(,kG#-^J-(*\BJA!CqnE<-L@$IAG2D3KU#+fni/Dm'9A:9ED$r2P4fM9UFdmZRRY<KO!92&$O"'h%R?D\-ooS`Nk9V+S]S40-`epsbs(-RCV,A`2UsBq)N*[)lkGk4Z\Dag4k[O;E.Cm4B3fhAe/6cj?\fa=)ds)/QGb-u1MGaqNWbSQO<_S*W)>fTI<+'75k8#_u5Jh4_dQ`e7AWasAZ]Cg!h(A3TOo>LQ<Agh#jqj+3>cWr@dnU[*2Xa0\o;HWqDO?69::I;S0AdIap"6;i>@DZm``OkMSNR$BRLK\PGH?)D3f>lmW`.EHN8npHs?k:4U%nR$!lW5$A45`r)E7'7o;:;7@Y'q@2e-<ZZ&_5t^-"_D".Q.gpK#a/Jl53L!q6!Q`9O7EnaR]*6PNm;.B-r+r/Dn!W(Il)#oG02;5)Jt;A=eWtN:4o:rKt3(Q8<1I#AUUo%p[23`Cm:OOg-?Jhdo%h*hTrMrl"/3r'OV^?MdE'[A5ugd\E0=Cp.s,m.-]HT+C3Z^mc16lZ8ffa,0s;a62Ccp+0Qj=L/.&$WM\8J%E%m:&=c*+WPt^6NG3bu[eHFQgsbi&7d3m#C;laTm9RdLVf<F=_G$pChAkr**$(&:T[H`#eZ:>KnrtAJ4DF\Of\Em*+_ghBKKRj@V!Zq(+QJ$8B[Se'$u$Xem?Q)UeqQa8cOD<dO4SoO7284)F@3ms9sIu59-K[j&ko4-K)^R<K:W]f8V3:+!aAIJ?kgQKW^.;'7b&J"/rDD7TKD_RmVjppoqt6?L3"1P5nZTM*U$g*/s(DL<7+QS7>,:ibc<3HpVp]j3a\BiFFO/.bN&>lj0se^`bcKQfrB06ScRP?^*qCJ7;([#:TmP'CbYM8s!7H#'"tUXU:2)9J#A0#`GhN.djUceRip+&LBC(pZtTEJo;/3m`_@]5bt#be>nqf5Nn,W$g!,1&C"l^4QL8u34V!oqkb;AqSTW0a`EA`a=O]25du=h[cehBSI#Sti96]Z8p5rllW(S!+$Eo?R9SM"KdE$Zk7][2]r4X1u(1H<QVRf)[X=+dc<\qqhnp?3pXq<>e^3JC;`BFMc:8su)^&1ilZa'aL&E)La`sP=a#So-Q'%fq^kq+(99>pNB&V&sJa3j^J1@RQjEVB3cak&K%GMKM0N3t-DfQ(fslW7!a3csF]-rlXKN[,W\<nJ.io4!71#ue2!&$I^a+?m2M2QSX<raj;nRAhp;gCEL3kTmaZ1d=ZnBdC&e7XJ>YXtc7mX?X$[r<>c5*R"V%Vfe1WXYHWKlW6ng:"F@.kf[,;\C>Es^683*#kE@C4iR(Kgu@8;:ooi]HhZPZcO%6c`Hh+q4N![Bf'%+@,DkS+g3sfXRk3XU9b_VC_u:<L<hQ^'>q/6U$h75QA=0P@0^_\rdFgaHId[J@<$<9RCgTS]Bqe@GemLs:nW(`k_(/I#LR]bh%Dt68djbP2[qJr^0kdkHm]!fY<9r6lk7+Bt&#TDgWM4jha5A.4@B8d=\!dqRlR4ee]+0($R6SL`&'UpHEr~>endstream
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 627
|
||||
>>
|
||||
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-/OYOOeAo3n([+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>=!HEFS=*~>endstream
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2099
|
||||
>>
|
||||
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%cE[)U,(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&XPZg9XlN\#fMlZ(u``EJ#RQm1E:2t>]8QfBB9]Wd4/*2%+ZLJEfR8tC@j.*NKO&e8V4o:17^j81J!FF)9,Im(8BEe-[?m!rZ@<.PH9$1uK^8F0G1.XUbk`;)Y.]bDb#I70qg2l>HQe5!O!\VqEL_[u]Ukob$S.AAT+MrZ]Pf%g,>,YBd5,!_281?<n`(?Vd..^&TVn,dW_FQ*H=V;BJNUVemo_'Gm=Vg'1jANff0(N\f0'up",URf5Ji&1p(c9_JSZEOqF`"l(e&bpE[9[;NcnXlN4IIMQ@!Yb-8Q[<nC=&Q2&C'N0=ik^+ds6ONr:_J,X7IGNd@c-5U)-<_T?K1?#M=F3a?a"g,">/:SWI#1eL\9BKda81K1tFK9;p7Zi\q,V?,aTiEX8#k#361&Vpgi$I4B5W0RYo\@:C'O(#Ei).dBsfbcL6Xf;:0XXO%FOC-4Wq=KR)\0^msA%MaY2;WtGrW9&>C,?IOG4eK6E`PGe>Gd[fJaFS;Uj&/%>^U)9LHO8]C96@fVE@u^:/L"Mih%Wd4L%scfCU5&.I%%I7+jAoKkE$WY0oOqoL%qOkZ#CgLUCK$ZnXD)c-8Ke46h.$FAH2I%OQtQ,=j*'Gg8]@-dlH$JOD9<j9E@5)?lgeIdfig+)aVNhI$%D)CnqO^XbGS=oNaQBOBSZnF#]NikAkRE5gu4N#D&$4/t,[b^HXkC07uBWf<S]diFQ9Z9EPO)aOP00?QOm3MQPh0d;Bc=47,/+9-rlT]-)-=oCh8?j52+tNoEYc9D(qDi1EF$K6h;$*Oa+Q,+=W'k658k$en"cnc~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 18
|
||||
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
|
||||
0000001531 00000 n
|
||||
0000001827 00000 n
|
||||
0000001911 00000 n
|
||||
0000004132 00000 n
|
||||
0000005614 00000 n
|
||||
0000007715 00000 n
|
||||
0000008433 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<cb35ba21cf1abe27c899c350354e0e11><cb35ba21cf1abe27c899c350354e0e11>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 11 0 R
|
||||
/Root 10 0 R
|
||||
/Size 18
|
||||
>>
|
||||
startxref
|
||||
10624
|
||||
%%EOF
|
||||
271
Les15-Cursor-Vercel-Deploy/Les14-Lesstof.pdf
Normal file
271
Les15-Cursor-Vercel-Deploy/Les14-Lesstof.pdf
Normal file
@@ -0,0 +1,271 @@
|
||||
%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 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
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
6 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
|
||||
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
|
||||
<<
|
||||
/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
|
||||
13 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
|
||||
14 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
|
||||
15 0 obj
|
||||
<<
|
||||
/Contents 29 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:20260521105902+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260521105902+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Lesstof) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
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
|
||||
15 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1912
|
||||
>>
|
||||
stream
|
||||
Gatm;;0/3d&:WeDlq;!>p7b.\"PfK19A<-eD;)Sg4_1FBZ"t(`j8sMYqt7E2(pG"oij8$W/Wb/MPBO]Z@6jA-b]gDo\/<)`f*iLOi<5N8*#QDFX.f48,$]*5EIlb+[0$maYk8C0WKi&cL^87@^o&7D4U2t!&`4+Hi4.)(Qj9%4aU="-nHAN4#Dg]2iJYDB2A)ISCPTDd"M=!#pj=)g5XZ>-L08uP(sZM(W7n9=&B\8(L#E*Y5(*9O%<'crk0Zl%"MPY>N+N7KEpgd%qL"CfEF-^@r$BrM;9OYeMrue,KAd4*X(C1.3,p[cD>Wd5%a5+SaU9sTc/+-&3":GHm!QZ5h&$d;$<5Z:G_'Yj=`ePk-#h\WE]ghdAI8_R!lCq`G?VU3I%*')!Kg@m(paNk?)H43f*&?A(-l_rZ(RUe40,PQ<pOp<<R0k[['<(>B9>V;N!-nrq:1P?ME,@p/3IO]I%T/LSR\\P->@k_NRM>!2O_!9^Q"e2K_j3k)sf\cB%>2l.iNatW3OrUBOd#[cMfbGD9O:Y@m4[Y3J>!q^foEUR!tBenEMn-&@tstZ$98#XUN2VB:d@bn1[7&]qCq4-?HA>'f&E.$uW4hGR9_;/6<_gq'NF=-JNZOR"H:=YWKN%,iDA:L!IDd.3pAMPF9Q$8Ad-6AZ!JJW/,"&_Eo"W^Yolb0aF-+*4_*k4cJJX;8;]m$O_B0R[C=`g(S4YE-X7uVa\ktboU?Te=beY)fF5*fIT>(j_('80Hi;II1VYU6`C?<ro[>/mAL*15J9:0D6n*6O-A5)4,Vd3'Sqgn7)n7HSN[O[YD4,H#KXIYMX;Jhj]rM]O/(oSSF%8N!Sj:"YZbTlq45QHrWhTR6Cj1DGV.D!k8fXD4YF]a[T:KaeFITeI9l1>1JHblAbp]A#'o0B0p6%;&pY:dj7"0.R>rR=ZH#i8o^NJ8;$-Ukncj-i6^d=Oq6]1i-5m[HO8DK*4%.Ok^^b'7@D<cXr!"h2)l!(8!KIFXN^o([0+Tk*m5>/u>0fYN>7E!t;'JjKU&q&7?Kr;!X!S1AW)_=1S8`=p>;`rCRNYjO"q8,+Ucn?(I&A^:>hV0aK*^)phkW$.s3l972$P:"5<+9rF38E9if.=Mhd8W`4i;?u_aFPEp_<H4`Pb4@,=1!2<C1Dofu/3>8#8ECk&sE)<b9ZZjtLBc]eX(kQ5\"0gootLP1`sZ7&D;pZs0G5;eIKAr+b4O]4KiZqM&FI.Chk:_ZTl\Ihh#sLG'u?iDM7n.Q-4ajY@1<):kI#VX9]#O:[LCj.si_F'.UO-ZR[^*b/JMA7:e?>c)3MC:!ad?,r@*[?A<dbi3T;nN`m*F!Q%,FD/-rWIbX^rsXD4I(acSIJEur$ACR.;'q46U:J9WV<]IUN(PVt72X:IR9JR+&%(+:g4*[2i#T4Y)1oJ)Bk`3RW^X%p\(R'd\ZAs:>\BL_Z=.HnMk,P\>Q)LWm,Sc]R`M^8@H*6O0TB[peWn*t)X'h]b#,U)kBe0@-8+/X7PI(\.a?rC)X^T`T-W\C&QDSEc]T-=]nFc_:<%Y8E`%/#?jg\m5_1:)k'=KFM_Ip';j-PC`5AL1K9Tn&?;Kd4d+^#P;3SA4)WN,p?DfTe)BWMQk!@<J0't%[>=_T?ITi6PM[:U5@ruDW1tQd@,SA_0g87m]3^g:G:BHFJ!7c,_,Z%jZ3MQtP3!hRV%leRiM*8ps?'q.G?ul*Og:5kG[um<mR^S`VD*KeDNi&[V4N)td]3t2n^i]IhjMr9H`To,4m_X#`ndp6=*(_3,KW#TX%W&&:&?c>G,8k#f(H]F&L,/ZJ@#ecV4b`;(EuM1eH@S#=._a)BSememMioGG:!YjoTmE;,`D5ak0\"S@"f$Lqq:PPD:n0&cZ[7[Lg.Ct86.Dgi3&VFMh6$/?K4+W_;^I)0~>endstream
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2006
|
||||
>>
|
||||
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(\!S(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*YR6H'~>endstream
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2085
|
||||
>>
|
||||
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?p<MFXR^??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!REUef"~>endstream
|
||||
endobj
|
||||
22 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 704
|
||||
>>
|
||||
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'T=Q;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!mdba'E~>endstream
|
||||
endobj
|
||||
23 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1519
|
||||
>>
|
||||
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]Vk9qF9Tkn]MuN&\dgf[=(r_.hi%(RY%\h'i!n%p.O#!)EA(X(E)5E/-m8[1^rGCuWGgTpk4+O0.TLDGX2[aE('_E9ijUh&kJm"G.E5%02\_rkjF`@B=Dd'uZi^U9D!s0@iEOmHUb34$ZQpUZ:h9@Z5W6n?NF(b(\+VANbbs=oUQ:-kQFm5q%6]#A`[Kf'E<-L.$KWeMAYQXeK[n9Dr7[&Nf_?';ho@[7?kMu'8c2/o:Wjq@9S.[tVXZuc3^6#e+JOl)GPt*PF\GnL`G#o]K*_"shf8mq1VM5i:Nd'qBPT>S36RDT&+L-1HEGX*UfG[dDahCi-(Ia0SmTaV^-A%bH3N&$?`3pd-TD?nqOo0coP</9M@4qqGLr:tQF9m4#d5%Xj2j!VCP3YM4K59!m"Sag#mceQm#S*r-Lok._tkO1?oF5go+@N-cj",bUo]\Gi'oD"pL'@-.b/l?]3r"8bE,eJ3RA7Oj54Fsh%#TL5\.DoLU\:cLbg(Pjs>DFfM_&!#gRS'fB'N`ZUYkM?Dgr;)D5-/KHMI:.`H7XD-8,dX,,'qjND@[H9Al%SkZD;TD\;,&3hGS9co##CaF)37&X:jVj!%\)(^[nllU0[JqSaIg@*:7V'3rYPg>hk?h`TjkGDZi^SJrO*SfpMp]^Lsp.=+ON7f8.gg6I<Wa1Q-8osbas5O6O;mjTdoFEY+-mPj8pm11cXS%SnP.<4J-_@pPjl]bMkZ'^fQdLL>':j^BeXJ$;[V<I%;8fV/8h'Ws?>K9LU7fuoQqQ&e)_r@rO:Rb@aE2jSle/[cP3QF[/mgJMMmP'\CnO1kJ>h;CF'KU]_PT,@=c9LAr@?07Xik-_/i^++PfEKm.*@7pfs56cX_eH,2)67AkL\dcQ,W;XfqlY\8nfG,p)iob.ip5-06qpSRlGi4c_54R-@#73J;Q9P)0R`TCbY+%WZM]NC5e4YeNdr^MM=P;kK+W(c]9bAWt#,Fr9#!;HN7-sUjh6pk+L8u-cKpBB5fl:eK?B*34jQb!0ise!0RrCBDgSYHEjIW<"6kmY-<[eKfXglh`To96`Z,>a'a;;i7r'fYW>;e&13fFjfecNcG7NPSH5>5$`<3@d2iJV_o$/B4WbJKN2I;u9lYf'Zmf`#3kNqNFPtM,]n(kt/Z6(9)"#b/^qU%2RW;9^Vmfdo[P]^Gfl/W([I;+LbHr,0ANdc>06ZmW'*WX\"5I;DC&~>endstream
|
||||
endobj
|
||||
24 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1952
|
||||
>>
|
||||
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.S0G!YMPkhZjqc_;)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=&9f,~>endstream
|
||||
endobj
|
||||
25 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1559
|
||||
>>
|
||||
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=gE2[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$/(fLH79D+B.@f-l)-L^$]\!9,Of$-Eo-kIDJZ7PZoo'TJB*_Yg&fR$f<cnRDRZ4BuAY@VT9UQ5cMo1o0*R=Dff[rf]a3BamDbnB4R`i(H-.k;\b7Nesa0Y>:Oi_A6*UIkXIqj(6-YliL'kC@93-]c\3oi"+"Du@HR[I>0PJKT11nO2XaAk73c#3%g7i40sT2@BOPa2s4_n]O!H<[Cf+I-Oo;\N<iRrj=e"*uKHoqJftFSB6a;M-1/;sDN0AL[3j'=q*gma7jFJtB^qU$OIb0CVlj2Y5D#=tlQVRAj)R"^Ks#S[IPf,HJk#-lLd?PH(8EIt_nk":J%i)"s#`kp-=1$?!"/'#JMtSRR'kh.$r(NTM5rglTQ%Uo9ZZHH-J.1)Tpn7D?&W;bnSGRh@6P#$3U,Fn["f-bST`Ti4aTC"'M[7c*>=@GSP#^tCe#N[I$K_5h"@^ppX`=nkhO!JG3_;E`WHNA`:S?Jle-rubctl;Jj@V6P%9lo'($ptHUh">O=nd]a;Jdj2e^(rj!u3JI0'T$\,4'.>saa),5nF1K>FmhY>hL?aVOci*WX7eAU<2p;H,!U63fL/QW=>1<P!<DSM0ELe%hO-3V$B$)4Hp^SfC,o:psr4Np&P.dLbYGo#'SogNrlN'0]:V/kY&k6"\N:%CoqIJ+X.OE8[$+3doe_#;(j6>d6n@*K+Nl)5[*RKmU1kYfmdg6dJMo4a4hap)m5fD'3n0I@Z"qQG+Qi,fN."PgHhh1`OY5c!QU4dKG3nkFgcVgo3TjXNGCj-4uV)29n>us%)98+@Pp`;.XL#kVI2Gn-[bTWk7e>:-EQ%:i_D=%Z?Ap?*cY&h_0k'SBiMU^1C&UYX_.#:;"J#W#XmhR=Xf648'CF6Su<Ah5B8kl\/)c!5FIAaku!/V%Jl^_ADcVjs!08t#nG^1^HHAscSrMCo\eNUlMl?oL(7e#$K2M2@?;RtOoo5inj-g`?"es$!-do$l.]JER<(^f%j.a+n;E4rB>DJ*5f0SXb'+"ir5F,Nt`V.1knYB`!3'jb6X=b#:@Z@)$nf=%cG,B)>!lfbo'R+^j*OWo>M(C"&T7K~>endstream
|
||||
endobj
|
||||
26 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1871
|
||||
>>
|
||||
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,"BT93LL^"+(g-j"(0j_YK-Km=8Hu$L1J[KV0C_@].$oJVK<(G&8$nM`[`Fm:hEr4[Q,"7l2jQ<An9WbJt<p>8(1'%<%VEbWthH+4p;uo[naQCO-"B6AP\dpPHlY/K8$E*f0Mp6Al=O,l#^ofl$6.(hk$p$`f::o5.b4o"BmN3d=pSg[H&jN)@G<irU(W$L,XcA^8@MKJd@J^Q.g>)W)q?2SP"Rk%!2Li*WE,<!brB:\8iE:ZYl5i;0ek"EX:0q-/hs[$$!D%kMQ-XHBGk(RMAQAJJj!aHN9ckotgPlN08L5:U\R6D]9u^r;ZTjEZT&65c]39(3\)6B`.82iEW=,E[2=nWjBmf4pou]i2ncHgLg9ZL?,Lj1-E.[!pj,XLA`/doj.eJh&l,D`r#C,Tb^1D7>U$g[$V53lmk)bAmVb$8Z(m+U`l'5/c!ljk$UC-.ia&'Al/)qgLT^e0.FNS\jGF*(LT1i&>_(P30?SN.u;3qNc,tMsi_ajU\)bfL+,JdquT!AC,=,$TRuC`d2#tpB%L]p_mm:K)eD[lPSNQiI`Oln"J\<4Sb=:-U'*_1Q3ei5n`''pi:h0Mr!Q5H,FX_khp?f0&L3q%n^CEl>.6^+k':N-`qeY.?C,t^6K$UV(fFUA@JX^)H8FL!diue8'hLaDa0ea5`hYT11[S#G;H^^95MXc)?WI6`*atC?^lkM)Zsoi#]E=3C>#31FC@<aZ#!"V`duXKC+c2<ou7^!XU2,$k4tl?UJ;9KXi%1N*4%p=/k*CNQ_:6!eF6jl"&I(eCH@!nJX:']jIcSqA8V&Og'kG0gB.J*m0C5%47J*Q)973I,E=>YX,D1njXJ,%R7!1F2YVgZ<nee#n=$Z?XG92Ro='(5&B#6PYA@G=quD#9DIUhP9hg;1!98N2'Nc:tMMp<[*)12%kV<!GOr$.t_<YbP<jBqR%_E=blNg<u2io\VU]l_L[k,om"9`(n7#+'+$[o9t`/X:>I";Uls$$3tqdB/*mXrhd@^p</+]ehp/V5NYg_gcse&B8A<OaU^Bb4NNj1Go"I]OVCdh&^LBpDr(<_eTqW#s@2f8IlpAV@[/HADOpM4EP'>pm0n9Z-L6@o(^-1S!Xo7D/ge`?b$RThj`4KaqL4i:Y0J*;'pkPLn2@a.>%F]sKD$bfCV0pp8q3?0KsMg!KDKD+hud2ObhuHnLJ_e-&8od^I&<HhhJ:X)Vk-Y6qD44]=YS/C_j@"U,,%WnQKcE'VOVLX$QtCXU?\Bk3V_92us>MfDnSY>h'`K?i":YHN^HRk\kK0mW%!4DBfH$@]qep@XW]Z5"1g46QXN>Pp(Oi4=`Jju<P$4U<ja*Z,tQ#<V?@$lYJVo<uetQ-Wl@k=3)oRPiB+&#dACl_:JF3LjA7f7V*OaC9l!4\;*I+B"COSo&DLEO_0mVt@dj+JmruiUsk1b:J8*\S:f,,)2eM;;.9f$q^&(-HrYZ;!]*fl/QJ?n2a,#?`W*g/T_UC:]D<55%caL3E=%Gq`-nN+&Tk$kuSCt:GImWNB2hO^<1DBc7>GM7HN$9mkqF-=FUD\9N>[~>endstream
|
||||
endobj
|
||||
27 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1724
|
||||
>>
|
||||
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:""!*h!P/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]QRGU9?]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!L(:nUA~>endstream
|
||||
endobj
|
||||
28 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1906
|
||||
>>
|
||||
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%Boj%d25#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=lnGWM\J,.U~>endstream
|
||||
endobj
|
||||
29 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2014
|
||||
>>
|
||||
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[D0lU/]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![57XjT~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 30
|
||||
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
|
||||
0000001461 00000 n
|
||||
0000001667 00000 n
|
||||
0000001873 00000 n
|
||||
0000002079 00000 n
|
||||
0000002285 00000 n
|
||||
0000002491 00000 n
|
||||
0000002697 00000 n
|
||||
0000002767 00000 n
|
||||
0000003059 00000 n
|
||||
0000003189 00000 n
|
||||
0000005193 00000 n
|
||||
0000007291 00000 n
|
||||
0000009468 00000 n
|
||||
0000010263 00000 n
|
||||
0000011874 00000 n
|
||||
0000013918 00000 n
|
||||
0000015569 00000 n
|
||||
0000017532 00000 n
|
||||
0000019348 00000 n
|
||||
0000021346 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<edfa56fc3bb0ffcbb0a5a8c9dd537e64><edfa56fc3bb0ffcbb0a5a8c9dd537e64>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 17 0 R
|
||||
/Root 16 0 R
|
||||
/Size 30
|
||||
>>
|
||||
startxref
|
||||
23452
|
||||
%%EOF
|
||||
BIN
Les15-Cursor-Vercel-Deploy/Les14-Slides.pptx
Normal file
BIN
Les15-Cursor-Vercel-Deploy/Les14-Slides.pptx
Normal file
Binary file not shown.
539
Les15-Cursor-Vercel-Deploy/Les15-Docenttekst.md
Normal file
539
Les15-Cursor-Vercel-Deploy/Les15-Docenttekst.md
Normal file
@@ -0,0 +1,539 @@
|
||||
# Les 15 — Externe APIs + Cursor + Vercel
|
||||
## Docenttekst (Klas A — 3 uur, fysiek, demo-driven)
|
||||
|
||||
**Les:** 14 van 18
|
||||
**Onderwerp:** Externe APIs + Cursor agents (Composer + Background) + Vercel preview deploys + GitHub Actions CI
|
||||
**Duur:** 180 minuten
|
||||
**Format:** Tim demonstreert klassikaal. Studenten kijken mee. Zelf bouwen = huiswerk.
|
||||
**Demo-app:** Pokédex (PokéAPI, geen key, visueel) — nieuwe kleine app
|
||||
|
||||
---
|
||||
|
||||
## Hoe deze tekst werkt
|
||||
|
||||
- `[SLIDE X]` — klik naar slide X
|
||||
- `[SCHERM: slides | cursor | terminal | browser | github | vercel]` — welk venster op de beamer
|
||||
- **Vertel:** "..." — wat je zegt (mag eigen woorden)
|
||||
- `*[stage direction]*` — instructie voor jezelf
|
||||
- 💬 = verwachte studentenvraag
|
||||
|
||||
---
|
||||
|
||||
## VÓÓR DE LES — Setup (60 min)
|
||||
|
||||
### 1. Cursor versie checken
|
||||
|
||||
- Cursor moet `>=0.45` zijn voor Background Agents
|
||||
- Pro account aktief (Tim's eigen) — Background Agents zitten daar in
|
||||
- Test: `Cmd+Shift+P` → "Open Background Agent" zichtbaar in command palette
|
||||
- Login bij Cursor cloud — eenmalige flow
|
||||
|
||||
### 2. Vercel + GitHub klaar
|
||||
|
||||
- Vercel account (Tim's eigen) — gekoppeld aan GitHub
|
||||
- Test repo: maak vooraf 1 dummy-repo aan (om snel te kunnen demonstreren als demo crasht)
|
||||
- Bookmark: vercel.com/dashboard, github.com/USER
|
||||
|
||||
### 3. Demo-app vooraf gemaakt (backup)
|
||||
|
||||
- Werkende `pokedex-demo` lokaal + op GitHub + op Vercel
|
||||
- Als live coding faalt: switch hier naartoe, vertel "dit is hoe het er eindelijk uitziet"
|
||||
|
||||
### 4. Folder & terminal-tabs
|
||||
|
||||
- Werk-folder: `~/novi/novi-lessons/Les15-Cursor-Vercel-Deploy/live`
|
||||
- 3 terminal tabs: project root, git-watcher (`watch git status`), Vercel CLI
|
||||
- Cursor open met empty folder klaar
|
||||
- Browser tabs vooraf open:
|
||||
- https://pokeapi.co/
|
||||
- https://vercel.com/dashboard
|
||||
- https://github.com (jouw user)
|
||||
- https://docs.cursor.com/background-agent (referentie)
|
||||
|
||||
### 5. Internet check
|
||||
|
||||
- Background Agents werken alleen met goede verbinding
|
||||
- Vercel deploys ook
|
||||
- Backup hotspot indien WiFi gisteren rare dingen deed
|
||||
|
||||
### 6. Backup screenshots
|
||||
|
||||
Heb klaar voor als demo faalt:
|
||||
- Cursor Background Agent UI (run completed)
|
||||
- GitHub PR met Vercel comment
|
||||
- Vercel preview deploy
|
||||
- GitHub Actions groene check
|
||||
- Branch protection settings page
|
||||
|
||||
---
|
||||
|
||||
# HET SCRIPT — Lees mee tijdens de les
|
||||
|
||||
## BLOK 1 — Welkom + Terugblik (10 min)
|
||||
|
||||
`[SLIDE 1 — Title]` `[SCHERM: slides]`
|
||||
|
||||
**Vertel:** "Welkom bij les 14. Vandaag gaan we van localhost de wereld in. Externe APIs, Vercel, Cursor's coding agents, GitHub Actions CI. Heel hands-on."
|
||||
|
||||
`[SLIDE 2 — Terugblik]`
|
||||
|
||||
**Vertel:** "Lessen 11 t/m 13 gingen over AI: SDK, tools, agents. Alles draaide op jouw laptop. Dat was prima om te leren, maar in het echt deploy je iets en wil je dat anderen het kunnen gebruiken.
|
||||
|
||||
Vandaag drie dingen tegelijk. Een — externe APIs aanroepen. Geen LLM, gewoon data ophalen. Twee — die app naar Vercel. Drie — Cursor's twee agent-modes leren kennen en daarmee feature branches openen die automatisch preview-URLs krijgen op Vercel."
|
||||
|
||||
`[SLIDE 3 — Planning]`
|
||||
|
||||
**Vertel:** "Drie uur. Eerst 45 minuten theorie verdeeld over de drie onderwerpen. Daarna vier demos. Pauze rond minuut 90. Tot slot lesopdracht en huiswerk."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 2 — Theorie Externe APIs (15 min)
|
||||
|
||||
`[SLIDE 4 — Wat is een externe API]`
|
||||
|
||||
**Vertel:** "Een externe API is een HTTP-endpoint van iemand anders. Je doet `fetch()`, je krijgt JSON. Klaar. Dat is het idee.
|
||||
|
||||
Drie smaken qua authenticatie. Geen key — PokéAPI en Open-Meteo bijvoorbeeld. Met key — Tavily, TMDB, de meeste paid APIs. OAuth — Google, GitHub user-data, dat is complexer.
|
||||
|
||||
In Next.js fetch je op twee plekken. Server-side, in een Server Component of API route. Of client-side, met `useEffect`. Server-side is bijna altijd beter voor initial data. Je key blijft veilig, het is gecached, en sneller."
|
||||
|
||||
`[SLIDE 5 — Code]`
|
||||
|
||||
**Vertel:** "Code voorbeeld. Server Component is `async`. Doe `fetch`, parse als JSON, return. Met `next: { revalidate: 3600 }` zegt je tegen Next.js: cache dit een uur. Eerste user triggert de fetch, de volgende honderd users krijgen de gecachte versie. Schaalbaar zonder dat je iets hoeft te doen.
|
||||
|
||||
💬 *Vraag: 'Wanneer client-side?'*
|
||||
|
||||
**Antwoord:** "Bij user-interactie. Een zoekbalk waar je live filtert — moet client-side, want de input verandert in de browser. Real-time updates, polling — client-side. Maar de eerste page-load: server-side."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 3 — Theorie Cursor Agents (15 min)
|
||||
|
||||
`[SLIDE 6 — Composer vs Background]`
|
||||
|
||||
**Vertel:** "Cursor heeft TWEE soorten agents. Niet hetzelfde. Niet uitwisselbaar. Vandaag gaan we beide doen.
|
||||
|
||||
Composer. `Cmd+I`. Lokaal in je editor. Jij wacht, agent werkt, je ziet de diffs verschijnen. Je accepteert per file. Synchroon — je bent erbij. Dit is pair programming.
|
||||
|
||||
Background Agent. `Cmd+Shift+P` → 'Open Background Agent'. Runt in Cursor's cloud, niet jouw machine. Je geeft een prompt, gaat iets anders doen, komt terug. De agent maakt een branch, doet commits, opent een PR op GitHub. Asynchroon — je hoeft er niet bij te zijn.
|
||||
|
||||
Het verschil zit in waar je bent in je workflow. Composer voor exploreren, voor pair-programming, voor 'ik wil even meekijken'. Background Agent voor 'ik weet wat ik wil, voer maar uit terwijl ik vergader'."
|
||||
|
||||
💬 *Vraag: 'Kost Background Agent geld?'*
|
||||
|
||||
**Antwoord:** "Ja, zit in Cursor Pro. Twintig dollar per maand. Hobby-tier — gratis Cursor — heeft alleen Composer. Voor de huiswerk-opdracht: trial van Pro werkt prima, of vraag credits via Cursor support voor studenten."
|
||||
|
||||
💬 *Vraag: 'Hoe vergelijkt dit met Claude Code of OpenCode?'*
|
||||
|
||||
**Antwoord:** "Allemaal coding agents, allemaal goed. Cursor's Background Agent is uniek in dat-ie autonoom een PR opent — Claude Code en OpenCode draaien lokaal en doen geen PR-flow zelf. Het hangt af van je workflow. We zien beide in deze les."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 4 — Theorie Vercel + CI (15 min)
|
||||
|
||||
`[SLIDE 7 — Vercel preview deploys]`
|
||||
|
||||
**Vertel:** "Vercel is hosting platform van de Next.js-makers. Voor Next.js apps de simpelste deploy ter wereld. Push naar GitHub, een minuut later: live.
|
||||
|
||||
Het magische zit in **preview deploys**. Elke branch krijgt automatisch een eigen URL. Niet alleen `main` — letterlijk elke. Push naar `feature/search` → `je-app-git-feature-search-user.vercel.app` is live. PR's krijgen een comment met de URL.
|
||||
|
||||
Waarom is dit zo krachtig? Background Agent maakt een PR — preview URL is direct klikbaar. Reviewer test de feature live, niet alleen code-diff. Stakeholders kunnen iets zien. Geen 'werkt op mijn laptop'-discussies meer.
|
||||
|
||||
Environments — drie scopes. Production = main. Preview = alle andere branches. Development = lokaal. Per env-variabele kies je waar-ie beschikbaar is."
|
||||
|
||||
`[SLIDE 8 — GitHub Actions CI]`
|
||||
|
||||
**Vertel:** "Last theory bit. GitHub Actions. Een script dat draait wanneer er iets gebeurt — push, PR, merge.
|
||||
|
||||
Minimaal voor dit vak: lint en build per PR. Eerst tien regels YAML, daarna heb je voor elke PR: lint draait, TypeScript build slaagt, anders rode X.
|
||||
|
||||
Waarom belangrijk? Background Agent kan typo's maken. CI vangt dat op voor je merget. Plus: 'werkte op mijn laptop' bestaat niet meer."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 5 — Demo 1: Pokédex met Composer (30 min)
|
||||
|
||||
`[SLIDE 9 — Wat we bouwen]`
|
||||
|
||||
**Vertel:** "Tijd voor demo. We bouwen vandaag een Pokédex. PokéAPI heeft data van duizend Pokémon, geen key nodig, gratis. Vier features in totaal — twee met Composer nu, twee met Background Agent na de pauze."
|
||||
|
||||
`[SLIDE 10 — DEMO 1]` `[SCHERM: terminal + cursor]`
|
||||
|
||||
**Vertel:** "Klas, kijk mee. Vragen tussendoor mogen."
|
||||
|
||||
`*[Terminal:]*`
|
||||
|
||||
```bash
|
||||
cd ~/novi/novi-lessons/Les15-Cursor-Vercel-Deploy
|
||||
pnpm create next-app@latest pokedex \
|
||||
--typescript --tailwind --app --no-src-dir --import-alias "@/*"
|
||||
cd pokedex
|
||||
cursor .
|
||||
```
|
||||
|
||||
`*[Cursor opens]*`
|
||||
|
||||
**Vertel:** "Cursor open. Nu Composer. Op Mac: `Cmd+I`. Op Windows: `Ctrl+I`."
|
||||
|
||||
`*[Cmd+I — Composer panel opens]*`
|
||||
|
||||
**Vertel:** "Composer is een tab in Cursor — niet hetzelfde als de gewone Chat. Composer kan meerdere files tegelijk wijzigen. Chat alleen één antwoord per keer."
|
||||
|
||||
`*[Type in Composer:]*`
|
||||
|
||||
> Bouw een Pokédex-startpagina op `app/page.tsx`. Haal de eerste 151 Pokémon op van `https://pokeapi.co/api/v2/pokemon?limit=151` als async Server Component. Toon in een grid van cards (Tailwind, 4 kolommen op desktop, 2 op tablet, 1 op mobile). Elke card: naam (capitalized), sprite van `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{id}.png`, en het Pokemon nummer (#001). Hover effect: card lichtjes vergroten en schaduw. Gebruik `next/image`. Voeg PokéAPI's sprite domain toe aan `next.config.ts`.
|
||||
|
||||
**Vertel:** "Klik 'Submit'. Composer denkt na. Je ziet diffs verschijnen — `app/page.tsx`, eventueel `next.config.ts`."
|
||||
|
||||
`*[Accept all diffs]*`
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
`*[Open browser → localhost:3000]*`
|
||||
|
||||
**Vertel:** "Daar staan onze 151 Pokémon. Werkt out of the box."
|
||||
|
||||
💬 *Vraag: 'Hoe weet Composer welke files te wijzigen?'*
|
||||
|
||||
**Antwoord:** "Cursor heeft toegang tot je hele workspace. Composer ziet de file-structure, package.json, alles. Hij beslist zelf welke files relevant zijn voor de prompt. Soms vraagt hij om bevestiging — vooral bij nieuwe files."
|
||||
|
||||
`*[Tweede Composer prompt:]*`
|
||||
|
||||
> Voeg detail-pagina toe op `app/pokemon/[name]/page.tsx`. Toon: grote sprite van `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/{id}.png`, de naam, types als gekleurde chips (gebruik officiële Pokemon type colors zoals fire = red, water = blue, etc), en stats (HP, Attack, Defense, Sp.Atk, Sp.Def, Speed) als progress bars die de waarde tonen. Voeg height + weight onderaan. Link de homepage-cards naar `/pokemon/{name}`.
|
||||
|
||||
`*[Accept diffs, refresh browser, klik op een card]*`
|
||||
|
||||
**Vertel:** "Detail-pagina werkt. Stats als balkjes. Types met kleurtjes. Composer heeft type-colors zelf gevonden — die zitten in zijn training data."
|
||||
|
||||
`*[Optioneel — als tijd:]*`
|
||||
|
||||
> De stats balkjes zijn te dun en de kleur is wat saai. Maak ze dikker en gebruik een paarse gradient.
|
||||
|
||||
**Vertel:** "Composer kan ook polish doen. Twee minuten later: betere UI. Dit is wat ik bedoelde met 'pair programming' — snel iteraten op visuals."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 6 — Demo 2: Vercel deploy (15 min)
|
||||
|
||||
`[SLIDE 11 — DEMO 2]` `[SCHERM: terminal + github + vercel]`
|
||||
|
||||
**Vertel:** "App werkt lokaal. Nu de wereld in."
|
||||
|
||||
`*[Terminal:]*`
|
||||
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial Pokédex with Composer"
|
||||
git branch -M main
|
||||
```
|
||||
|
||||
`*[Browser → github.com/new]*`
|
||||
|
||||
**Vertel:** "Nieuwe repo. Naam: pokedex. Public — geen secrets in onze code dus public mag. Géén README, géén license — Next.js heeft alles al."
|
||||
|
||||
`*[Klik 'Create repository']*`
|
||||
|
||||
```bash
|
||||
git remote add origin https://github.com/JOUW-USER/pokedex.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
`*[Refresh GitHub — code is daar]*`
|
||||
|
||||
`*[Browser → vercel.com/dashboard]*` `[SCHERM: vercel]`
|
||||
|
||||
**Vertel:** "Vercel. Add New → Project. Import Git Repository. Kies pokedex."
|
||||
|
||||
`*[Op import-screen — alle defaults laten staan]*`
|
||||
|
||||
**Vertel:** "Vercel detecteert Next.js automatisch. Build command: `pnpm build`. Output directory: `.next`. Niks aanpassen. Klik Deploy."
|
||||
|
||||
`*[Wachten ~60-90s]*`
|
||||
|
||||
**Vertel:** "Tijdens het builden: dit is wat er gebeurt. Vercel pulled je code van GitHub, install dependencies, runt `pnpm build`. Output gaat naar hun CDN. Klaar.
|
||||
|
||||
`*[Build done — klik op URL]*`
|
||||
|
||||
**Vertel:** "Daar staat-ie. Productie URL. Live."
|
||||
|
||||
`*[Demonstreer: open URL op je telefoon — werkt]*`
|
||||
|
||||
**Vertel:** "Werkt op telefoon. Werkt overal. Internet."
|
||||
|
||||
`*[Terug naar Cursor — kleine wijziging:]*`
|
||||
|
||||
`*[Composer:]*` "Verander de h1 van 'Pokédex' naar 'Mijn Pokédex (Live!)'"
|
||||
|
||||
```bash
|
||||
git add . && git commit -m "Update title" && git push
|
||||
```
|
||||
|
||||
`*[Browser → Vercel dashboard]*`
|
||||
|
||||
**Vertel:** "Nieuwe deploy verschijnt direct. Wacht 30 seconden..."
|
||||
|
||||
`*[Refresh productie URL]*`
|
||||
|
||||
**Vertel:** "Nieuwe titel zichtbaar. Geen extra commando. Push naar main = deploy."
|
||||
|
||||
💬 *Vraag: 'En als de build faalt?'*
|
||||
|
||||
**Antwoord:** "Productie blijft op de laatste werkende versie. Vercel deployt alleen als build slaagt. Je krijgt mail dat de build mislukte, productie blijft online. Geen downtime."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 7 — Pauze (15 min)
|
||||
|
||||
`[SLIDE 12 — Pauze]`
|
||||
|
||||
**Vertel:** "Pauze. Vijftien minuten."
|
||||
|
||||
`*[Tijdens pauze: open Cursor Background Agent flow alvast — zorg dat repo geconnect is]*`
|
||||
|
||||
---
|
||||
|
||||
## BLOK 8 — Demo 3: Background Agent (25 min)
|
||||
|
||||
`[SLIDE 13 — DEMO 3]` `[SCHERM: cursor + github + vercel]`
|
||||
|
||||
**Vertel:** "Nu de Background Agent. Dit is écht anders. Geen pair-programming meer — we delegeren."
|
||||
|
||||
`*[Cursor: Cmd+Shift+P]*`
|
||||
|
||||
**Vertel:** "Cmd+Shift+P opent command palette. Type 'Background Agent'."
|
||||
|
||||
`*[Select 'Open Background Agent']*`
|
||||
|
||||
**Vertel:** "Eerste keer: Cursor vraagt verbinding met je repo. Volg de flow — opent browser, OAuth met GitHub, Cursor mag in jouw cloud sandbox draaien."
|
||||
|
||||
`*[Connect flow]*`
|
||||
|
||||
**Vertel:** "Klaar. Nu de prompt. Belangrijk: SPECIFIEK zijn. De agent ziet ons niet. Hij krijgt alleen de prompt."
|
||||
|
||||
`*[Schrijf:]*`
|
||||
|
||||
> Maak een nieuwe branch `feature/search`. Voeg een controlled input toe bovenaan `app/page.tsx`. Wanneer gebruiker typt, filter de Pokémon-lijst case-insensitive op naam. Houd Tailwind-styling consistent met bestaande cards (rounded corners, padding p-4). Voeg een count toe rechtsboven: "X van 151 zichtbaar". Push de branch en open een PR met titel "Add search bar". PR body: korte beschrijving + screenshot suggestie.
|
||||
|
||||
`*[Submit]*`
|
||||
|
||||
**Vertel:** "Agent draait nu. Cursor toont je een panel waarin je kunt zien wat-ie doet — bestanden inlezen, edits maken, terminal commands."
|
||||
|
||||
`*[Wachten ~3-5 minuten. Tijdens wachten:]*`
|
||||
|
||||
**Vertel:** "Belangrijk om uit te leggen — terwijl deze agent draait, ben ik vrij om iets anders te doen. Lokaal coden, Composer gebruiken voor andere features, koffie halen. Dat is het voordeel.
|
||||
|
||||
Andere relevante punten. Agent draait in Cursor's sandbox — niet jouw laptop. Hij heeft toegang tot je repo, kan `pnpm install` doen, kan tests draaien, kan PR's openen. Maar hij heeft GEEN toegang tot jouw productie-secrets of services. Sandbox is geïsoleerd."
|
||||
|
||||
`*[Agent done — PR verschijnt op GitHub]*` `[SCHERM: github]`
|
||||
|
||||
**Vertel:** "Pull request op GitHub. Klik."
|
||||
|
||||
`*[PR open]*`
|
||||
|
||||
**Vertel:** "Vercel heeft een comment achtergelaten — preview URL. Klik."
|
||||
|
||||
`*[Browser → Vercel preview URL]*`
|
||||
|
||||
**Vertel:** "Live, op een ANDERE URL dan productie. Test zoekbalk."
|
||||
|
||||
`*[Type 'pika' — Pikachu blijft over]*`
|
||||
|
||||
**Vertel:** "Werkt. Search live op preview URL. Productie ongewijzigd."
|
||||
|
||||
`*[Terug naar GitHub PR — Files Changed tab]*`
|
||||
|
||||
**Vertel:** "Code-review. Agent heeft `app/page.tsx` aangepast, eventueel een nieuwe component gemaakt. Lees diff, check of het ok is."
|
||||
|
||||
`*[Eventueel: comment in PR]*`
|
||||
|
||||
**Vertel:** "Stel ik wil een wijziging — bv. 'voeg ook een clear-button toe'. Twee opties. Een: open de branch lokaal, Composer fix. Twee: nieuwe Background Agent prompt op deze branch. Voor demo doen we optie twee."
|
||||
|
||||
`*[Cursor → Background Agent → existing or new]*`
|
||||
|
||||
> Op de bestaande branch feature/search: voeg een 'X' clear-button toe rechts in de zoekinput die de input leegmaakt.
|
||||
|
||||
`*[Wacht 2-3 min, refresh PR, refresh preview URL]*`
|
||||
|
||||
**Vertel:** "Update zichtbaar. Klik X — input leeg. Werkt."
|
||||
|
||||
`*[Eventueel: merge]*`
|
||||
|
||||
**Vertel:** "Tevreden? Merge PR. Vercel productie-deploy start automatisch. Een minuut later: search live op productie."
|
||||
|
||||
💬 *Vraag: 'Wat als agent vastloopt?'*
|
||||
|
||||
**Antwoord:** "Twee opties. Pakje sluiten en opnieuw beginnen met betere prompt. Of: open de branch lokaal, kijk wat agent gedaan heeft, fix met Composer. De agent is geen blackbox — hij commit echt op een branch, je kunt erin kijken."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 9 — Demo 4: GitHub Actions CI (15 min)
|
||||
|
||||
`[SLIDE 14 — DEMO 4]` `[SCHERM: cursor + github]`
|
||||
|
||||
**Vertel:** "Laatste demo. CI. Eén check per PR — lint + build."
|
||||
|
||||
`*[Cursor → Composer]*`
|
||||
|
||||
> Voeg een GitHub Actions workflow toe: `.github/workflows/ci.yml`. Runt op pull_request en push naar main. Steps: checkout, install pnpm 9, setup node 20 met pnpm cache, install dependencies met frozen lockfile, run lint, run build.
|
||||
|
||||
`*[Accept diffs]*`
|
||||
|
||||
`*[Terminal:]*`
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
`*[GitHub → openen PR]*` `[SCHERM: github]`
|
||||
|
||||
**Vertel:** "PR open. Klik tab 'Actions' — workflow draait. ~1-2 min."
|
||||
|
||||
`*[Wacht]*`
|
||||
|
||||
**Vertel:** "Groene check verschijnt. Merge PR. CI is nu live op elke toekomstige PR."
|
||||
|
||||
`*[Demo: failing CI]*`
|
||||
|
||||
**Vertel:** "Laat ik laten zien wat er gebeurt bij een fout."
|
||||
|
||||
`*[Cursor → Composer:]*`
|
||||
|
||||
> Maak een TypeScript fout in app/page.tsx. Verwijder een belangrijke import. Push naar een nieuwe branch chore/test-ci-fail.
|
||||
|
||||
```bash
|
||||
git push -u origin chore/test-ci-fail
|
||||
```
|
||||
|
||||
`*[GitHub → PR open → wait]*`
|
||||
|
||||
**Vertel:** "Rode X. Build faalt. Klik op de details — daar staat letterlijk wat mis is."
|
||||
|
||||
`*[Fix met Composer]*`
|
||||
|
||||
> Repareer de TypeScript-fout in app/page.tsx (voeg de import terug). Commit.
|
||||
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
|
||||
`*[Wacht — groene check]*`
|
||||
|
||||
**Vertel:** "Fixed. CI groen. Merge mag nu."
|
||||
|
||||
`*[Branch protection toevoegen — kort]*` `[SCHERM: github settings]`
|
||||
|
||||
**Vertel:** "Tot slot — branch protection. Settings → Branches → Add rule. Branch pattern: main. Vink aan: require PR before merging. Require status checks: select onze CI workflow. Save.
|
||||
|
||||
Wat dit doet: niemand kan nog direct naar main pushen. Background Agents werken via PRs — dat blijft werken. PR moet groene CI hebben voor merge."
|
||||
|
||||
`*[Demo: probeer direct push naar main]*`
|
||||
|
||||
```bash
|
||||
git checkout main && echo "test" >> README.md && git commit -am "direct" && git push
|
||||
```
|
||||
|
||||
```
|
||||
! [remote rejected] main -> main (protected branch hook declined)
|
||||
```
|
||||
|
||||
**Vertel:** "Geblokkeerd. Goed."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 10 — Composer vs Background reflectie (10 min)
|
||||
|
||||
`[SLIDE 15 — Wanneer welke]`
|
||||
|
||||
**Vertel:** "Twee tools, één keuze per taak. Wanneer welke?
|
||||
|
||||
Composer voor pair-programming. Snel feedback, jij stuurt, agent voert uit, jij accepteert per diff. Goed voor: exploreren, leren, refactors waar je wilt meekijken.
|
||||
|
||||
Background Agent voor delegatie. Specifieke feature, kun je niet bij zijn, parallel werken aan andere taken. Goed voor: PR-prep, parallel werken aan 3 features, terwijl-je-vergadert.
|
||||
|
||||
Risico's bij Background. Vage prompts = vage PR's. Specifiek zijn helpt. Agent kost credits — voor solo-projecten geen issue, voor teams kan oplopen.
|
||||
|
||||
Wat ik zelf doe: 80% Composer, 20% Background. Background voor de écht goed-gedefinieerde features die ik dan parallel laat draaien."
|
||||
|
||||
💬 *Vraag: 'Welke is beter voor onze eindopdracht?'*
|
||||
|
||||
**Antwoord:** "Beide. Ontwerpen + leren = Composer. Specifieke features uitwerken = Background. Voor de eindopdracht-pitch: laat zien dat je beide kunt — dat is een vaardigheid."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 11 — Lesopdracht + Huiswerk (10 min)
|
||||
|
||||
`[SLIDE 16 — Lesopdracht + Huiswerk]`
|
||||
|
||||
**Vertel:** "Lesopdracht — in de les. Half uur. Je eigen Pokédex setuppen, één feature met Composer, push naar GitHub, deploy op Vercel. Aan het eind: jouw eigen productie-URL.
|
||||
|
||||
Huiswerk — voor volgende week. Vier dingen.
|
||||
|
||||
Een: feature toevoegen via Background Agent. Branch + PR + preview URL.
|
||||
|
||||
Twee: GitHub Actions CI workflow. Lint + build per PR.
|
||||
|
||||
Drie: branch protection op main. PR's verplicht, CI moet groen.
|
||||
|
||||
Vier: DEPLOY.md schrijven. URLs, prompt die je aan Background Agent gaf, reflectie over Composer vs Background, bewijs van CI (groen + rood voorbeeld), één observatie.
|
||||
|
||||
Bonus: tweede externe API, environment scoping, parallelle Background Agents.
|
||||
|
||||
Tien punten, voldoende is zes. Inleveren via Brightspace: repo URL + productie URL + DEPLOY.md."
|
||||
|
||||
---
|
||||
|
||||
## BLOK 12 — Afsluiting (5 min)
|
||||
|
||||
`[SLIDE 17 — Afsluiting]`
|
||||
|
||||
**Vertel:** "Wat hebben we vandaag gedaan. Externe APIs in Server Components. Cursor Composer voor synchrone werk. Cursor Background Agent voor async PR's. Vercel productie + preview per branch. GitHub Actions CI. Branch protection.
|
||||
|
||||
Volgende les: RAG en embeddings. Vector search met pgvector. We bouwen een tool die semantic search doet — handig om in een agent te plakken. Combo van les 13 en 15.
|
||||
|
||||
Daarna: performance + observability, eindopdracht-werkdagen, pitch.
|
||||
|
||||
Vragen?"
|
||||
|
||||
`*[Vragenronde — minstens 5 min over laten]*`
|
||||
|
||||
---
|
||||
|
||||
## NA DE LES — Wrap-up
|
||||
|
||||
- Push working `pokedex` repo naar GitHub als referentie voor studenten
|
||||
- Brightspace: link naar referentie-repo + huiswerk-instructies
|
||||
- Verzamel vragen die niet beantwoord — terug in Les 15 opening
|
||||
- Voor Klas B (later): check of Cursor Pro licenties beschikbaar zijn voor studenten, anders Background Agent demo moeilijk
|
||||
|
||||
---
|
||||
|
||||
## Veelvoorkomende fouten tijdens live coding
|
||||
|
||||
| Fout | Oplossing |
|
||||
|------|-----------|
|
||||
| `next/image` PokéAPI domain error | Composer: "voeg images.remotePatterns toe in next.config.ts" |
|
||||
| Cursor Composer onresponsive | Cmd+I opnieuw, of restart Cursor |
|
||||
| Background Agent verbinding faalt | Logout/login Cursor cloud, refresh repo connection |
|
||||
| Vercel deploy faalt: missing dep | `pnpm install` lokaal, commit lockfile, push |
|
||||
| GitHub Actions: pnpm not found | Check `pnpm/action-setup@v3` versie in yml |
|
||||
| Branch protection: kan eigen PR niet mergen | Tijdens demo: voeg jezelf toe als bypass user |
|
||||
| Preview URL 404 | Wachten — eerste build duurt soms 2 min |
|
||||
| Background Agent fixt verkeerde files | Te vage prompt — specifiek met file paths volgende keer |
|
||||
|
||||
---
|
||||
|
||||
## Mentale model voor de klas
|
||||
|
||||
Als studenten verward zijn over de Cursor agent-modes:
|
||||
|
||||
> **Composer** = jij pair-programt met AI. Synchroon. Cmd+I. Lokaal.
|
||||
>
|
||||
> **Background Agent** = jij delegeert aan AI. Async. Cmd+Shift+P. Cloud. Opent PR.
|
||||
|
||||
Belangrijkste regel voor Background Agent: prompts moeten zo specifiek zijn alsof je het aan een nieuwe junior junior dev op afstand uitlegt. Geen context-cues, geen "je weet wel".
|
||||
313
Les15-Cursor-Vercel-Deploy/Les15-Huiswerk.md
Normal file
313
Les15-Cursor-Vercel-Deploy/Les15-Huiswerk.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# Les 15 — Huiswerk
|
||||
## Background Agent + CI + branch protection + DEPLOY.md
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Opleiding:** NOVI Hogeschool Utrecht
|
||||
**Deadline:** Voor Les 16 — 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!
|
||||
194
Les15-Cursor-Vercel-Deploy/Les15-Huiswerk.pdf
Normal file
194
Les15-Cursor-Vercel-Deploy/Les15-Huiswerk.pdf
Normal file
@@ -0,0 +1,194 @@
|
||||
%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:20260521105950+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260521105950+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 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 2334
|
||||
>>
|
||||
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+-=9+uFH#onKRniB?1=+,HfKdgPkgYTfOBY@F_+57F!]T.*O*`%Q>C(`4FDdsPi'U0t%J?3Aqa2fWru=Aj\Hg+WTg?Kdg3tRu,%<8dBoL$e/>p^OUu($PiE9`0K_Ei-Y:T>pRSi"@SSu:8-jHBSn8P!+PEH<lc7r@QM&M6-GWP1rTL][:+a+n=.VU#Plo___S2*$tZ+C6rh2Tcf)XWggC[>5,5Q\qNl[ekb#;!l*g#SP"lVc^78(8.9Cr*pH2I-M1N%*$r_$*5?,,J/lLToZ)mO!t';ns(RAr;7d:i8s9cEma'p2,uLbj0"LAoKZKXFcf.mMnHgF],(E=OgEPdPe:$*.._EilfsRP%Aen@o-1$9+GRXE.Ua^^uoUP&!*12)s<lfe:?J[c>D-2PgD!JI,4hil@P.q;/$PF+f8A%^*bjX[Yu82'k:WcGFU.PD9G+LT!m0H&Fr[@hQ%nPnkpJuZ;IhZZkLu"(moh[Wi?tRWAJU<H1hQR>qMs(bOT]14Fi:;7q"G3$ZoORb2]-ieZcACTCV^IHAZFHWXoQXF10)5<q+/kQ[;)G#MPr7:%IW:"Ak/roKW%!(irf_lTGEdmjN3:).5hm&kgkP:A,@*Li"L*;hE1g3-](nSV/<a@GKBnXL%E`E0C*I0CXbkb>_o^Kf5>7$?m_3+YE8Mjr(aO?ni0tk:j;-%#!F(**e0q,>.>FYfdi8V[/:VHQeRGdd_O=CSfV18X.qD]7aojW@g"%RbA)(-T-(EY^erF5jA6AY[Hm.+Yes&7A""6%C#Jg\0D<g8]Uq$0g9]MiT0!RHdDJ\]FK[Qc#!iuT;Z-@WDm:-8*fbjbCC)#hN6.>C44^N/XD,,WYK%O$Ir+VXA00i)dQEK)]E"=TVGD[9`<I>^T0Cq7A"U%cO#!&e/X]&@A]32N?2a`"h/aYn()V[\T$D@9<TJ?b#ppK-)q0=dq>O42;#->6%Zjiot.oWoa4M9o#a#.T'b_<BW51jM-6N".ror"q'%'rd_^A&8]7&"`:><n'Gt:Wpn8cRK>Jo#I_%8p1!!Os0*bulg0)"`LO;&oD!%Ob)oEag:(/.b6^YUiX`q(5MjQNMJB^?:#&\l+*lGIgIn?1RlEk4k(%g)+hFP92_&+<(4DU920b^/"=QM`=M$^Ikc8G>B6:bX0Ra@Q6[ZI`=c+Dj8q1aYJ3Bk6FOJ?n3DG0t!)n7qHp;k_5#b0MIa>#QTSf_H6+q.b-r&A0OTl/>i`].T&X\DisLUsFbb+-q3es!-FHC9[cZar)_:[!^Q5KB;3WBIOI<WA/rHpnk<D9<g%[8(O1g:Lqio%P@\[4a1<)76STr#^0,:s?7mbsp=3-ZKS[`AP?/Imb/<W#?O'ggGtd!uRi_*._CrNYa!5BaZUQZ>+eP)8ZUS,Y5l\8EJl^I^>;G2jn9SdX7@])l<GJEcjSqa-5_N!:rtd(-X@\s"k0dN!!0+O#$Y2[OUF$]=hDpV/>RS+o4t90fT3.s"Es;i_ksFgYrZ99-jMVMORX>ICM&_G?(]u>hs*@rrD8\WES6eT_[ULp9I&J<gak=q.te`OohW,#2TI/Sci2<!j]iIqc20fV76&2<6_(,&ksKgKA3EVQ06s`/C\dN:Hlk1Yh_NHa4,n#WDZ/C5#N;hp)(=RZ@M?aOAVeXGYE/84k9<)rq8ihM+'Kb6Mbk`l@o15Iih.Y:SZm)grpX@Pj+A@p@2O8;5V*^i0\jl\.j+LZbVQnW;])(\k'u[CYB/k]e3%FA<2<8[bCC;Rf;J9=skFN)aP"@]'aSgLe-G3_$oT)2+_[Z75&_i^/Gu6D\!G`)jSq;@Mm/k"QF.Fh"PN.jqr[tMRK[F6=j7d=6nG-7AeV;.WmsD54s5ebS=i]T1:.75pE"ZW8WMWdGck`O%!edQ/a('DQmq'N>gtkUWOq8k7h4\:s8Ec@#j!7YM+uoce+UnP2sS&[dJ.ar`/$U!FbDfCe^G"VFBh@Pa3Y=ef9f:]El/P2iEZ&dF"42`Qui8$p+F*1o[*a<t"Ns%rsn*BE~>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"jd6eAtiD#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!M&kmrK\SaI@=la)fqHM"-2AoWY!e-[n<Y`26oaYlDAQnmFh#apG!;Ze"Uf*$@k6%q$r\u%fH+@,>T$fY4)=PpgW"E9;Rp0%L),&co#'dZji@A8=k6g$la>G_%8NN#Dp08#*2qP"#]mi@JgZa]X-jsE\ZMu^`+_kq#!T%<<s@kP2ZG[VqEW=BRS.GFURM#s+f$+)T7jbC%:F^?iI%sVhf_7,aKYQl1:"O?\kS(5G#B=ZB4L[$6;ONnS3jAu/oUiP?gjY88D[FFit'E;3Sd<;]^"A0Ij`q=K;E1(Q[s@rb:V>n=NmXR,MG@9UF@NZi/SreXZnC@&E*~>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.(`mq=(8Ab8XYj[CGDY.aC?l"/&G7W-]f/G>'kO6o`794aH%Y^lpK>.]FQ`\51i5DCtCg-Bq+Y=Q=#&9+n*:+0PD94d"?ui=.T%3N-gE^jUbE*O]ACSrUU8+[UeXqo!">0C/Y6*>%VF6lt_u,l",Z\J+DT(g%i)CTe$opMPCDHgQE_M-`Wp/j9J'$OC5Ck6;d_$KIVqB?pg3+fmih]@R(tC@D>B^VuP0paj4r]nQK%>eR*Qn1:U!4=fC0>I^&:D6ZZAN4b'=@o8p$g-s!R:`CI>_e2]42Mk'I2'gb%MA8fKa!&+:dnO0&!qJudLY4ElX*E/aAf?D*5ZVm4Xr"F6]XYOHnsaY8`T1g'SD@XtOIHLiTh7-Bb:&g>.Ge@b^hj,gCBEJ^dA3A`\h+HK>2i+@SRB4^eIY\&W?/40rLV*r.uiL^(,f"F)PZakZ3BD\[@92]h$QLA5?uEI@g7G3*fTg(>hGM%#24.SanOBY[g0fO5gVn64WLj1B,m,Na#fuaR**:qSma#fY"bI?1kZ4eQDQc+)qceWEEBUe?rJGp5D?eRD-;i)Ed'Fkhaml#MD0t>[QK2WdP/uG*YE%Tlq'7Oja'TBV*r`shGYLPLA2UT:Ai$_Xb\'d<lp\U;3$`I8iB8G,DC66X"b3YA";c>NhXhA.JaA97@dHO1^PZpr=KTt6"=$DJRJB/N0;0&)edc;#+.__\e>M,1,WhUaQr-U<fUQBC^?I6#I$,>-CqPpe'_:ik35p*J5MpmjU[1KggGOK0dmU#?[H]WpVJ[i`pQ*Ee)FIbKJ0UU+$Yg@s"qocG4\1%5bp5ks/6OD-B85@@t`;km4@5g1l1d\'&)-WLdL`Hl]BTT64b_RM]A;k`apY.EE6+KP/)0c=lfV9CmsG4eRb9'YnSS*NH.NcPPEdpebOE54Y4@Hj((1$7ti_q??JEZkO3NF_-#*LkK+^M'#*#Qp%NWfm)MoEHaU6VG@1%;*LlS:d,'[jn(9EZ4pW3<g0us=_1[rG%'a]WF).tY)]T>SZeNji=K!AN_ZFUZ?J&Dj>Y_%2VGH.J2tr;QcDns&k3c/PXR&e2fOuAF=T0>\mPXt$*;/.t9%DNfNjIJL9Al.+dq$"KgRBSXjl2E/f!R2U/MM2'NtZ+iLt1KHXM1<^?cW2@kGiEMS37CX/+dPH/Vdpr?FF^kqhE7kA"t8Pr(uYQ.P>R]Hs5o8OY_9d<#"I<r,fc^50=J0NdQT-6Ck:>nL4sekHPo=;:q?s_#fW-e!p870%-A)D6N8\!Y\5MZ3\@XK!LC\>Ait&52W_IVCbAW$p8+]:GhTJ(;>!Pf&G.&K5I1r$YtqPCr<?V.;1sBn`?hCG9?i1l1.BnJbMB0^]AE~>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`f9U7qL317bI65EY1hXb\+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:O7?aH/~>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^:`'jBNZI%"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^>MK&;g~>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!3d.57R4pbU+'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%DA[Ju.L~>endstream
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 831
|
||||
>>
|
||||
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'+."M65#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^L48#bnS5DsDVb"&7fNH:Q93[~>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
|
||||
0000004760 00000 n
|
||||
0000005887 00000 n
|
||||
0000007645 00000 n
|
||||
0000009549 00000 n
|
||||
0000010612 00000 n
|
||||
0000012811 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<047cece75ba73e044622d5b8b8c18397><047cece75ba73e044622d5b8b8c18397>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 13 0 R
|
||||
/Root 12 0 R
|
||||
/Size 22
|
||||
>>
|
||||
startxref
|
||||
13733
|
||||
%%EOF
|
||||
186
Les15-Cursor-Vercel-Deploy/Les15-Lesopdracht.md
Normal file
186
Les15-Cursor-Vercel-Deploy/Les15-Lesopdracht.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# Les 15 — Lesopdracht
|
||||
## Pokédex opzetten + eerste deploy naar Vercel
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Opleiding:** NOVI Hogeschool Utrecht
|
||||
**Duur:** 30 min in-class
|
||||
**Status:** Tijdens de les afronden — daarna huiswerk uitbreiden
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
## Vereisten
|
||||
|
||||
- Node 20+, pnpm, git
|
||||
- **Cursor** geïnstalleerd (cursor.com) — Pro account is fijn maar niet verplicht voor lesopdracht
|
||||
- **GitHub** account
|
||||
- **Vercel** account (signup met GitHub kan)
|
||||
|
||||
---
|
||||
|
||||
## Stap 1 — Nieuwe Next.js app
|
||||
|
||||
```bash
|
||||
pnpm create next-app@latest pokedex \
|
||||
--typescript --tailwind --app --no-src-dir --import-alias "@/*"
|
||||
cd pokedex
|
||||
```
|
||||
|
||||
Open in Cursor:
|
||||
|
||||
```bash
|
||||
cursor .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stap 2 — Eerste feature met Composer
|
||||
|
||||
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
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial Pokédex with Cursor Composer"
|
||||
git branch -M main
|
||||
git remote add origin https://github.com/JOUW-USER/pokedex.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
Check op GitHub: code is daar.
|
||||
|
||||
---
|
||||
|
||||
## Stap 5 — Vercel verbinden
|
||||
|
||||
1. Ga naar https://vercel.com — login met GitHub
|
||||
2. **Add New → Project**
|
||||
3. Kies je `pokedex` repo
|
||||
4. Vercel detecteert Next.js — laat alle defaults staan
|
||||
5. Klik **Deploy**
|
||||
|
||||
Wacht ~60-90 seconden. Vercel toont:
|
||||
|
||||
```
|
||||
🎉 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
|
||||
git add .
|
||||
git commit -m "Custom title"
|
||||
git push
|
||||
```
|
||||
|
||||
Check Vercel dashboard — nieuwe deploy verschijnt direct. ~30s later: live.
|
||||
|
||||
---
|
||||
|
||||
## Eisen
|
||||
|
||||
- [ ] Repo `pokedex` op GitHub, public
|
||||
- [ ] Minimaal 1 feature toegevoegd met Composer
|
||||
- [ ] App werkt lokaal (`pnpm dev`)
|
||||
- [ ] Vercel productie URL werkt
|
||||
- [ ] Tweede push triggert automatische deploy
|
||||
- [ ] Geen errors in Vercel build logs
|
||||
|
||||
---
|
||||
|
||||
## Tijdsindeling (30 min)
|
||||
|
||||
| Stap | Tijd |
|
||||
|------|------|
|
||||
| 1 — Next.js setup | 3 min |
|
||||
| 2 — Composer eerste feature | 10 min |
|
||||
| 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
|
||||
|
||||
| Symptoom | Oplossing |
|
||||
|----------|-----------|
|
||||
| `next/image` klaagt over PokéAPI domein | Voeg `images.remotePatterns` toe in `next.config.ts` (vraag Composer) |
|
||||
| `pnpm: command not found` | Installeer: `npm install -g pnpm` |
|
||||
| Vercel build error: TS-error | Composer: "fix de TypeScript build error" |
|
||||
| Cursor Composer niet zichtbaar | Cmd+I — als niets gebeurt, herstart Cursor |
|
||||
| Push naar GitHub: permission denied | Setup SSH key of gebruik HTTPS met PAT |
|
||||
| Vercel deploy mislukt | Check build logs in dashboard — meestal env-var of dep-issue |
|
||||
|
||||
---
|
||||
|
||||
## Klaar? Verder met huiswerk
|
||||
|
||||
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 `Les15-Huiswerk.md` of `Les15-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
|
||||
156
Les15-Cursor-Vercel-Deploy/Les15-Lesopdracht.pdf
Normal file
156
Les15-Cursor-Vercel-Deploy/Les15-Lesopdracht.pdf
Normal file
@@ -0,0 +1,156 @@
|
||||
%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 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
|
||||
<<
|
||||
/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
|
||||
9 0 obj
|
||||
<<
|
||||
/Contents 17 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:20260521105950+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260521105950+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Lesopdracht) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Count 5 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2129
|
||||
>>
|
||||
stream
|
||||
Gb"/'968TB&AJ$CFAYKW&/[j!D+WjmbI`W@Yfssq/riFnS9Sj[HO/uhrqC:*+aIC>YY.`%;RRlE?[#`1?amt(ifCM!A-qb[$k?W"?lWc+^]G.aln,l;H!`\N"d_L\`)kHL30;3?Io)\^3J<!?LdqjfPsi^o!l]snOTLR7Ugt)f%4;B%"!Ro6.KH2F.$WAQmh<MtM@N0*7'nthf2!:W+sM@#:sJDM_/KPCNO#L35U["O#dYgNNo#21"0[V]>pj8ROhmYaV3A4ik+#(hqI]T2Y1'u0$[AhhlDuD#3c?(Tll9UB7b.i0huVe6+P6/@l5Eb1GUpp*'jKnrnS+H4^d_EJn@K0<K(mCpT+Mrp5-Dqk+5ISiDs7E\m(P<1$l]f;<1#V>'bJONXt:ddCkFI7cnK!BY3Qb>QRi2r``U`MZGLERif.8W?8'Or:kqN2Yf*<87Y]NDI"*rck_J#_LJ(@/;VXds3P(.+3f_(\nc.:fI>%6h)V`O`To+TIg!6@APSIeWN:"&^D7;[XTi!3(5ML(k64u&NQno-k0k:m9b7J:Jf1!U999^Sn^n>3-5%6ufN)@lrc[CnR,,I&8?6"'Y4*'HU5\R-K[8c*l#p#.a1SY's^Fq5%I`N<$)\hC@\nQ6r%mfLgK+8[.b_kkNa)M9TFHu(Vg2K>$dSLF.bBSOHr_QEQi/:+[f>Eq@'ks^4[lcUnVr4Un$<l$ZYHnVJRBae[hB&I=bK6[WipiJcA$$sI.e=g$MTs_WYG\:@ETenELQ$Jl>#j0YEDu?9.t!<31gcZ):\/:``H?To"[?P5a!P\)oaOUYd'jTK*2i[)5"oSQq*O7%\gq;...Kl/jOhk6b6"4NM>h=VesEtZJ5t;C=$"=p>M=JWd)pZW(h2K'a8d/cm03^-*O))6s*06gQRu8e=@+.)N[Bjbi8a5Z$)FqY?bH9V)RC/8nStFg;j%?J/1jSn2T1N+KdGTA.RiR%FlMMsE(r_@OYASTCs*B"i'<BE/NQ'C3#?L9O4_m-5D:#O"E,HtVYu&58m=Fl3.oN^OF-_E^=]`F6o7dU^DTpJLs/Gb0$!Z*.n8f"Z-a0LNTgQ$L[FXa,kI.>nX_r=kBRKTU*i5O0A(A8j%`@3ajW5)1YmRVd3%oC(sY!ZCC\>t[HU'TD:'\bKY+.BMP2RR1d4\74tfs'aM>uKaOc`08R.RBi)Ce.rrSM?o$-7KWiCsaSe]GSW-)YUQ)j`ifr8QP=)%8PFo2MAI_9EQA_a(`FQ&mQk&K&*2"#X\gcROtTnpW"Bl@qn>[TaSe?pTeO8bmO5Qh.h$-]F*U[>shNZ">kC=L:(5K`%F_8e^t0IH=++$O),DG)5/Ku)uJp(eV=E]_8j0Q\S=i:*7^%,Wi7P$/uCV3p6dFP9ZkA3\b90s\Hq3AtXWf(p:cDe_dOVoTS]'0AQd?X\Xu1AeSg-*7!fS+dMD>E`Hg\>+!K39eSR210K[D/s!tV^?BUIMg`1^I?:8Se=Drg$:IRa?=Skjkdq.bLb9^:=cP1k>?drbj!#8;!-jP,;.fErmdsmq"Y+'ZM*HZ^+5On*+Beg-ScqR92hDJ8R=>qQ=6JsJGCI)B9Aulc6$;"^T11[^AW\c>M@tsjHfaaOqarI8iu,AUH:0sLFaAZn2i2=7BFteHrZVod\dG^M";YQ\Mk2sa:C&!r"mc<.FIaZ9_;H"Qh[kgVN4Wg]$t\p^GY'l9C9$tDF]L%B@7h=)5gjViNYYVG?<jNJ_N)PZNRhDPr?Yae4asiY%f7>TQ8h_L,#G:"sJh6+q2I=%)V@bAU?hrJP4Q)Z5V@:KQ7O^(K.6[C[/^6P@[ud3?+pG[@cohEK<1e"8D3Mb0aLd0l6*,OL40hB&--MJu$gLmc8FoYUQa+P71Z<1K9\pTC4s;)t85+mX<AB5_/++q-N>IWRA(,NB#08Z*qVA`5ZS!9^=0r0%((G.@-YB9$PeoL3l]+&+3^TMrU7nj2G&"Z5@\rHIDR>UWYthY&d/MiGL7F78nD';uU(L'^o`#7<a3'K"f":XeJC$_li3,kE+Z\*4J+fGZWn]C(U"SqHW=s\b=]snaOe(!hN2:NtVr>iadC&;e8%'Hqs:N[qWSQ2LhsE;qj2ZPg6(/T%pu^d%"_>j9E8H!K;2A70~>endstream
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1390
|
||||
>>
|
||||
stream
|
||||
Gb!#Z9lK&M&A@C2b[jX!LhW$H-n.DYAD>pXJ42EO]'[HQ7\Xb=P,OW[-cf_GH\^*`5+:8a7ZJqGRQ%!eO#(<3nRCoJ.0/5.@J,``j07AA'1BY41ON;Oh`Sb%%n-K:O/O.A+L,kbT\ar?"Hpin`sa.1Osh"e,"A:TNe!m1(pX]f%GOgB!=:%],69'!JW/YGH_3jSdBt*JPjq]L5/,*m(DGIlLLG?J"N)Ths&()A0*l,T(r3_5`G],_\qEjkVjh489FcQ\[8-EICcDsaic9=l1g/;m'RnG"SXZY+AU=LE)d&_9?Sd)G"lZ]l+6.k%%BohO0ND;W&c,,eZ;L^q@=?,-%UbIo_+Hp=9EkRon-Gd?QC,%!m#l:Nk$aP^%ra[d_@pA1-%&t@4V:P,3%^,k84!Ihk:=sL9]D%I%*9'pcQ(HE((_pJ%g+dY`na!l"QKhU9p=@B,`JJNcZB5`PHVp";_U4,``RTbl#!<P`7oW6?2!POYkmQNlr6YMeE^Yb>te_Q\-.145=!8L](b@\%^[kIarGN'n!?bLlF,u61DQjg>2`/=[^db=CLY)E0bGUe\AS9@<">lskSf_?l<qn)@7iMZcJ-=gjn\4u5tD'D+c?W1Dqh9P?`be7<`MU%Zbg-njDG-pJ',)ACO\`bYN#uN^^hXpX7?Jod.L8fn?XPn;kislX:5C1THPgkEcuSk]!rDRVp4Pc^/%(fo+$2T=K=!fbKBGc^'gmd9:ank`2:++[&2QI&GV>/ZpTS]f%]nXUi;Qp/J&E,<2tJn,/HCK.aK/X\iDd'5!dOpFk5h6^c1I5"QaJtkkY`8LX?]JChiTcWtW_#YPPiEbmmS8qf\5><Z#amD@CdBgpX(HpN^#Q-qYGl\O'!%6>Qh<\4@6<[4!6"V)6qE)Zme3QB7R<$LI\Qmh`YBTd2Aars,C>kOYF"ooh2u3<Q97VeUhX[Z99"<Y2pAUCFV5s)HGR9;3o:!r4gi`I&4bA+%+F^imcg`;*(GYkV+iC@.&%%A0+Xja5I`k<>`Lmk*\[r%BsW^YK^)n"nd<%iMgKCSbe:Y6#R\-!%*MORK>0<^(3IFfr"L-7pKg<nVYU<WuAH4,PHI35KbXpi\F]rKbQ6)F-'A$+!+]%*O;<'8Vkd3o25tMaQ>G9$;^&67l)e6\%DMrdjL,\i:AU_r%@gHLGsD5'n*qI2qfC`-<>[\0(A+)<rC_U0HWQ4+:"mYX'f"p>k6_Buh^.M6"t%m`P:>24elVj-k+'WMcMfI[sK!2sX4$'o$:@8aYORnno5liW;bu=7R7X1YkC#;I,m1dHKE5EIMRD22/=U)\!-I\h.LG9$2"Z_^7mb0$n"F3T,s4etT^Si<IJd5t+uE%AKTJs5I-2[DU/Cid^K'E*qE)hEUd$I&I~>endstream
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2009
|
||||
>>
|
||||
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(0H9MEjjPD-Wa>q9-k/EMFM5\:m?BWDFk3B1&V@U(+1El6-#did+W]f4@W@ko[mh-;ga/1WD]n(H4%^.J3d`?\].p!jA9c!S`j@3?no:1<K@)8Q1WDE$#sra<]8nOu:Kd^Z)F`.X#qakX&`XD^A38It0o.s!k#RHlrFr]ir(,kG#-^J-(*\BJA!CqnE<-L@$IAG2D3KU#+fni/Dm'9A:9ED$r2P4fM9UFdmZRRY<KO!92&$O"'h%R?D\-ooS`Nk9V+S]S40-`epsbs(-RCV,A`2UsBq)N*[)lkGk4Z\Dag4k[O;E.Cm4B3fhAe/6cj?\fa=)ds)/QGb-u1MGaqNWbSQO<_S*W)>fTI<+'75k8#_u5Jh4_dQ`e7AWasAZ]Cg!h(A3TOo>LQ<Agh#jqj+3>cWr@dnU[*2Xa0\o;HWqDO?69::I;S0AdIap"6;i>@DZm``OkMSNR$BRLK\PGH?)D3f>lmW`.EHN8npHs?k:4U%nR$!lW5$A45`r)E7'7o;:;7@Y'q@2e-<ZZ&_5t^-"_D".Q.gpK#a/Jl53L!q6!Q`9O7EnaR]*6PNm;.B-r+r/Dn!W(Il)#oG02;5)Jt;A=eWtN:4o:rKt3(Q8<1I#AUUo%p[23`Cm:OOg-?Jhdo%h*hTrMrl"/3r'OV^?MdE'[A5ugd\E0=Cp.s,m.-]HT+C3Z^mc16lZ8ffa,0s;a62Ccp+0Qj=L/.&$WM\8J%E%m:&=c*+WPt^6NG3bu[eHFQgsbi&7d3m#C;laTm9RdLVf<F=_G$pChAkr**$(&:T[H`#eZ:>KnrtAJ4DF\Of\Em*+_ghBKKRj@V!Zq(+QJ$8B[Se'$u$Xem?Q)UeqQa8cOD<dO4SoO7284)F@3ms9sIu59-K[j&ko4-K)^R<K:W]f8V3:+!aAIJ?kgQKW^.;'7b&J"/rDD7TKD_RmVjppoqt6?L3"1P5nZTM*U$g*/s(DL<7+QS7>,:ibc<3HpVp]j3a\BiFFO/.bN&>lj0se^`bcKQfrB06ScRP?^*qCJ7;([#:TmP'CbYM8s!7H#'"tUXU:2)9J#A0#`GhN.djUceRip+&LBC(pZtTEJo;/3m`_@]5bt#be>nqf5Nn,W$g!,1&C"l^4QL8u34V!oqkb;AqSTW0a`EA`a=O]25du=h[cehBSI#Sti96]Z8p5rllW(S!+$Eo?R9SM"KdE$Zk7][2]r4X1u(1H<QVRf)[X=+dc<\qqhnp?3pXq<>e^3JC;`BFMc:8su)^&1ilZa'aL&E)La`sP=a#So-Q'%fq^kq+(99>pNB&V&sJa3j^J1@RQjEVB3cak&K%GMKM0N3t-DfQ(fslW7!a3csF]-rlXKN[,W\<nJ.io4!71#ue2!&$I^a+?m2M2QSX<raj;nRAhp;gCEL3kTmaZ1d=ZnBdC&e7XJ>YXtc7mX?X$[r<>c5*R"V%Vfe1WXYHWKlW6ng:"F@.kf[,;\C>Es^683*#kE@C4iR(Kgu@8;:ooi]HhZPZcO%6c`Hh+q4N![Bf'%+@,DkS+g3sfXRk3XU9b_VC_u:<L<hQ^'>q/6U$h75QA=0P@0^_\rdFgaHId[J@<$<9RCgTS]Bqe@GemLs:nW(`k_(/I#LR]bh%Dt68djbP2[qJr^0kdkHm]!fY<9r6lk7+Bt&#TDgWM4jha5A.4@B8d=\!dqRlR4ee]+0($R6SL`&'UpHEr~>endstream
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 627
|
||||
>>
|
||||
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-/OYOOeAo3n([+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>=!HEFS=*~>endstream
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2099
|
||||
>>
|
||||
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%cE[)U,(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&XPZg9XlN\#fMlZ(u``EJ#RQm1E:2t>]8QfBB9]Wd4/*2%+ZLJEfR8tC@j.*NKO&e8V4o:17^j81J!FF)9,Im(8BEe-[?m!rZ@<.PH9$1uK^8F0G1.XUbk`;)Y.]bDb#I70qg2l>HQe5!O!\VqEL_[u]Ukob$S.AAT+MrZ]Pf%g,>,YBd5,!_281?<n`(?Vd..^&TVn,dW_FQ*H=V;BJNUVemo_'Gm=Vg'1jANff0(N\f0'up",URf5Ji&1p(c9_JSZEOqF`"l(e&bpE[9[;NcnXlN4IIMQ@!Yb-8Q[<nC=&Q2&C'N0=ik^+ds6ONr:_J,X7IGNd@c-5U)-<_T?K1?#M=F3a?a"g,">/:SWI#1eL\9BKda81K1tFK9;p7Zi\q,V?,aTiEX8#k#361&Vpgi$I4B5W0RYo\@:C'O(#Ei).dBsfbcL6Xf;:0XXO%FOC-4Wq=KR)\0^msA%MaY2;WtGrW9&>C,?IOG4eK6E`PGe>Gd[fJaFS;Uj&/%>^U)9LHO8]C96@fVE@u^:/L"Mih%Wd4L%scfCU5&.I%%I7+jAoKkE$WY0oOqoL%qOkZ#CgLUCK$ZnXD)c-8Ke46h.$FAH2I%OQtQ,=j*'Gg8]@-dlH$JOD9<j9E@5)?lgeIdfig+)aVNhI$%D)CnqO^XbGS=oNaQBOBSZnF#]NikAkRE5gu4N#D&$4/t,[b^HXkC07uBWf<S]diFQ9Z9EPO)aOP00?QOm3MQPh0d;Bc=47,/+9-rlT]-)-=oCh8?j52+tNoEYc9D(qDi1EF$K6h;$*Oa+Q,+=W'k658k$en"cnc~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 18
|
||||
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
|
||||
0000001531 00000 n
|
||||
0000001827 00000 n
|
||||
0000001911 00000 n
|
||||
0000004132 00000 n
|
||||
0000005614 00000 n
|
||||
0000007715 00000 n
|
||||
0000008433 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<a75d82cab81be88097c0b19a2252975f><a75d82cab81be88097c0b19a2252975f>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 11 0 R
|
||||
/Root 10 0 R
|
||||
/Size 18
|
||||
>>
|
||||
startxref
|
||||
10624
|
||||
%%EOF
|
||||
487
Les15-Cursor-Vercel-Deploy/Les15-Lesstof.md
Normal file
487
Les15-Cursor-Vercel-Deploy/Les15-Lesstof.md
Normal file
@@ -0,0 +1,487 @@
|
||||
# Les 15 — Lesstof
|
||||
## Externe APIs + Cursor agents + Vercel deploy
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Opleiding:** NOVI Hogeschool Utrecht
|
||||
**Vorige les:** Les 14 — RAG + Embeddings
|
||||
**Volgende les:** Les 16 — MCP servers
|
||||
|
||||
---
|
||||
|
||||
## Inhoud
|
||||
|
||||
1. [Externe APIs in Next.js](#1-externe-apis-in-nextjs)
|
||||
2. [Server- vs client-side fetching](#2-server--vs-client-side-fetching)
|
||||
3. [Environment variabelen](#3-environment-variabelen)
|
||||
4. [Cursor agents — Composer vs Background](#4-cursor-agents--composer-vs-background)
|
||||
5. [Vercel deployments](#5-vercel-deployments)
|
||||
6. [Preview deployments per branch](#6-preview-deployments-per-branch)
|
||||
7. [GitHub Actions CI](#7-github-actions-ci)
|
||||
8. [Branch protection](#8-branch-protection)
|
||||
9. [Werkstroom — feature van begin tot productie](#9-werkstroom--feature-van-begin-tot-productie)
|
||||
10. [Productie-overwegingen](#10-productie-overwegingen)
|
||||
|
||||
---
|
||||
|
||||
## 1. Externe APIs in Next.js
|
||||
|
||||
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 valt eronder
|
||||
|
||||
- **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. Server- vs client-side fetching
|
||||
|
||||
### Server-side (Server Component of API route)
|
||||
|
||||
```typescript
|
||||
// app/pokemon/[name]/page.tsx
|
||||
async function getPokemon(name) {
|
||||
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`, {
|
||||
next: { revalidate: 3600 }, // cache 1 uur
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
```
|
||||
|
||||
**Voordelen:**
|
||||
- API key blijft op de server (nooit in client-bundle)
|
||||
- Caching gratis via Next.js
|
||||
- SEO-vriendelijk (HTML met data terug)
|
||||
- Snellere TTFB (geen extra roundtrip)
|
||||
|
||||
**Wanneer gebruiken:** initiële data, lijsten, detail-pagina's.
|
||||
|
||||
### Client-side (`useEffect` of `useState` met fetch)
|
||||
|
||||
```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>;
|
||||
}
|
||||
```
|
||||
|
||||
**Wanneer gebruiken:**
|
||||
- User-interactie (search, filter, infinite scroll)
|
||||
- Real-time updates (polling, websockets)
|
||||
- Data die per user verschilt en niet op server kan
|
||||
|
||||
> **Belangrijke regel:** API keys NOOIT in client-code. Voor authenticated externe APIs: maak een API-route in `app/api/...` als proxy.
|
||||
|
||||
### Cache-opties bij `fetch`
|
||||
|
||||
```typescript
|
||||
fetch(url, { cache: "force-cache" }) // permanent (default Next.js)
|
||||
fetch(url, { next: { revalidate: 3600 } }) // ISR — herval na N seconden
|
||||
fetch(url, { cache: "no-store" }) // nooit cachen (per request)
|
||||
fetch(url, { next: { tags: ["pokemon"] } }) // tag-based revalidation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Environment variabelen
|
||||
|
||||
### Drie scopes in Next.js + Vercel
|
||||
|
||||
| Scope | Wanneer | Waar |
|
||||
|-------|---------|------|
|
||||
| `OPENAI_API_KEY` | Server-only | Process env op server |
|
||||
| `NEXT_PUBLIC_APP_URL` | Server + client | In client-bundle (zichtbaar in DevTools!) |
|
||||
| `.env.local` | Lokaal alleen | Niet committen, `.gitignore` |
|
||||
|
||||
### Regel
|
||||
|
||||
**Alles wat geheim is — zonder `NEXT_PUBLIC_` prefix.** Met prefix = in client bundle = iedereen kan het zien.
|
||||
|
||||
```typescript
|
||||
// ✅ Server-only — veilig
|
||||
process.env.OPENAI_API_KEY
|
||||
|
||||
// ❌ NEVER doen
|
||||
"use client";
|
||||
const key = process.env.NEXT_PUBLIC_OPENAI_KEY; // staat in HTML response!
|
||||
```
|
||||
|
||||
### Vercel environment scoping
|
||||
|
||||
Op Vercel heb je drie environments:
|
||||
|
||||
- **Production** — deploys vanaf `main`
|
||||
- **Preview** — alle andere branches + PRs
|
||||
- **Development** — lokaal (`vercel env pull` om te syncen)
|
||||
|
||||
Per env-var kies je in welke scopes hij beschikbaar is. Handig:
|
||||
|
||||
```
|
||||
OPENAI_API_KEY_PROD → Production scope
|
||||
OPENAI_API_KEY_TEST → Preview + Development scope
|
||||
```
|
||||
|
||||
Op die manier verbruiken preview deploys niet je productie-quota.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
pnpm i -g vercel
|
||||
vercel login
|
||||
vercel # preview deploy van current dir
|
||||
vercel --prod # production deploy
|
||||
vercel env pull # download .env.local van Vercel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Preview deployments per branch
|
||||
|
||||
Hier komt de magie. Vercel maakt automatisch een preview deploy voor **elke branch en elke PR**.
|
||||
|
||||
### Hoe werkt het
|
||||
|
||||
```
|
||||
git push origin main → Production deploy
|
||||
(je-app.vercel.app)
|
||||
|
||||
git push origin feature/search → Preview deploy
|
||||
(je-app-git-feature-search-user.vercel.app)
|
||||
|
||||
PR open → Vercel comment in PR:
|
||||
"🚀 Preview: <URL>"
|
||||
```
|
||||
|
||||
### Wat krijg je hiermee
|
||||
|
||||
- **Reviewers** klikken op de URL in de PR, testen de feature live
|
||||
- **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
|
||||
|
||||
- **Environment scoping:** preview-deploys mogen geen productie-keys gebruiken — gebruik aparte env vars per scope
|
||||
- **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
|
||||
|
||||
Vercel installeert een GitHub App. Die plakt een comment op je PR met:
|
||||
|
||||
```
|
||||
🚀 Preview Deployment: https://...
|
||||
✅ Build successful — 1m 23s
|
||||
View build log
|
||||
```
|
||||
|
||||
Per push naar de branch wordt deze comment ge-update.
|
||||
|
||||
---
|
||||
|
||||
## 7. GitHub Actions CI
|
||||
|
||||
Een **CI** (Continuous Integration) check draait automatisch op je code. Minimaal: lint + build.
|
||||
|
||||
### 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
|
||||
↓
|
||||
2. Background Agent prompt (specifiek!)
|
||||
↓
|
||||
3. Cursor opent branch + maakt commits + opent PR
|
||||
↓
|
||||
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).
|
||||
|
||||
### Wat als CI rood is
|
||||
|
||||
- Klik op rode X in PR → naar GitHub Actions logs
|
||||
- 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
|
||||
|
||||
- Open Vercel dashboard → project → Deployments → klik op preview build
|
||||
- Build logs lezen
|
||||
- Vaak: env-var mist (Vercel preview scope leeg)
|
||||
- Of: dependency die op Vercel niet beschikbaar is
|
||||
|
||||
---
|
||||
|
||||
## 10. Productie-overwegingen
|
||||
|
||||
### Costs
|
||||
|
||||
| Service | Free tier | Wanneer betalen |
|
||||
|---------|-----------|-----------------|
|
||||
| **Vercel** | Hobby tier — gratis voor persoonlijk gebruik | Commercieel of >100GB bandwidth |
|
||||
| **GitHub Actions** | 2000 min/maand gratis (public repos unlimited) | Bij grote teams of veel runs |
|
||||
| **Cursor** | Hobby (gratis), Pro ($20/mnd), Business | Background Agents zitten in Pro/Business |
|
||||
| **PokéAPI** | Volledig gratis, geen rate-limit issues | n.v.t. |
|
||||
|
||||
### Performance
|
||||
|
||||
Vercel doet veel automatisch:
|
||||
- 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
|
||||
|
||||
- **Next.js fetching:** https://nextjs.org/docs/app/building-your-application/data-fetching
|
||||
- **Next.js caching:** https://nextjs.org/docs/app/building-your-application/caching
|
||||
- **PokéAPI:** https://pokeapi.co/
|
||||
- **Cursor docs:** https://docs.cursor.com/
|
||||
- **Cursor Background Agents:** https://docs.cursor.com/background-agent
|
||||
- **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
|
||||
- **GitHub branch protection:** https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches
|
||||
271
Les15-Cursor-Vercel-Deploy/Les15-Lesstof.pdf
Normal file
271
Les15-Cursor-Vercel-Deploy/Les15-Lesstof.pdf
Normal file
@@ -0,0 +1,271 @@
|
||||
%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 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
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
6 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
|
||||
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
|
||||
<<
|
||||
/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
|
||||
13 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
|
||||
14 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
|
||||
15 0 obj
|
||||
<<
|
||||
/Contents 29 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:20260521105950+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260521105950+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Lesstof) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
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
|
||||
15 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1912
|
||||
>>
|
||||
stream
|
||||
Gatm;;0/3d&:WeDlq;!>p7b.\"PfK19A<-eD;)Sg4_1FBZ"t(`j8sMYqt7E2(pG"oij8$W/Wb/MPBO]Z@6jA-b]gDo\/<)`f*iLOi<5N8*#QDFX.f48,$]*5EIlb+[0$maYk8C0WKi&cL^87@^o&7D4U2t!&`4+Hi4.)(Qj9%4aU="-nHAN4#Dg]2iJYDB2A)ISCPTDd"M=!#pj=)g5XZ>-L08uP(sZM(W7n9=&B\8(L#E*Y5(*9O%<'crk0Zl%"MPY>N+N7KEpgd%qL"CfEF-^@r$BrM;9OYeMrue,KAd4*X(C1.3,p[cD>Wd5%a5+SaU9sTc/+-&3":GHm!QZ5h&$d;$<5Z:G_'Yj=`ePk-#h\WE]ghdAI8_R!lCq`G?VU3I%*')!Kg@m(paNk?)H43f*&?A(-l_rZ(RUe40,PQ<pOp<<R0k[['<(>B9>V;N!-nrq:1P?ME,@p/3IO]I%T/LSR\\P->@k_NRM>!2O_!9^Q"e2K_j3k)sf\cB%>2l.iNatW3OrUBOd#[cMfbGD9O:Y@m4[Y3J>!q^foEUR!tBenEMn-&@tstZ$98#XUN2VB:d@bn1[7&]qCq4-?HA>'f&E.$uW4hGR9_;/6<_gq'NF=-JNZOR"H:=YWKN%,iDA:L!IDd.3pAMPF9Q$8Ad-6AZ!JJW/,"&_Eo"W^Yolb0aF-+*4_*k4cJJX;8;]m$O_B0R[C=`g(S4YE-X7uVa\ktboU?Te=beY)fF5*fIT>(j_('80Hi;II1VYU6`C?<ro[>/mAL*15J9:0D6n*6O-A5)4,Vd3'Sqgn7)n7HSN[O[YD4,H#KXIYMX;Jhj]rM]O/(oSSF%8N!Sj:"YZbTlq45QHrWhTR6Cj1DGV.D!k8fXD4YF]a[T:KaeFITeI9l1>1JHblAbp]A#'o0B0p6%;&pY:dj7"0.R>rR=ZH#i8o^NJ8;$-Ukncj-i6^d=Oq6]1i-5m[HO8DK*4%.Ok^^b'7@D<cXr!"h2)l!(8!KIFXN^o([0+Tk*m5>/u>0fYN>7E!t;'JjKU&q&7?Kr;!X!S1AW)_=1S8`=p>;`rCRNYjO"q8,+Ucn?(I&A^:>hV0aK*^)phkW$.s3l972$P:"5<+9rF38E9if.=Mhd8W`4i;?u_aFPEp_<H4`Pb4@,=1!2<C1Dofu/3>8#8ECk&sE)<b9ZZjtLBc]eX(kQ5\"0gootLP1`sZ7&D;pZs0G5;eIKAr+b4O]4KiZqM&FI.Chk:_ZTl\Ihh#sLG'u?iDM7n.Q-4ajY@1<):kI#VX9]#O:[LCj.si_F'.UO-ZR[^*b/JMA7:e?>c)3MC:!ad?,r@*[?A<dbi3T;nN`m*F!Q%,FD/-rWIbX^rsXD4I(acSIJEur$ACR.;'q46U:J9WV<]IUN(PVt72X:IR9JR+&%(+:g4*[2i#T4Y)1oJ)Bk`3RW^X%p\(R'd\ZAs:>\BL_Z=.HnMk,P\>Q)LWm,Sc]R`M^8@H*6O0TB[peWn*t)X'h]b#,U)kBe0@-8+/X7PI(\.a?rC)X^T`T-W\C&QDSEc]T-=]nFc_:<%Y8E`%/#?jg\m5_1:)k'=KFM_Ip';j-PC`5AL1K9Tn&?;Kd4d+^#P;3SA4)WN,p?DfTe)BWMQk!@<J0't%[>=_T?ITi6PM[:U5@ruDW1tQd@,SA_0g87m]3^g:G:BHFJ!7c,_,Z%jZ3MQtP3!hRV%leRiM*8ps?'q.G?ul*Og:5kG[um<mR^S`VD*KeDNi&[V4N)td]3t2n^i]IhjMr9H`To,4m_X#`ndp6=*(_3,KW#TX%W&&:&?c>G,8k#f(H]F&L,/ZJ@#ecV4b`;(EuM1eH@S#=._a)BSememMioGG:!YjoTmE;,`D5ak0\"S@"f$Lqq:PPD:n0&cZ[7[Lg.Ct86.Dgi3&VFMh6$/?K4+W_;^I)0~>endstream
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2006
|
||||
>>
|
||||
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(\!S(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*YR6H'~>endstream
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2085
|
||||
>>
|
||||
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?p<MFXR^??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!REUef"~>endstream
|
||||
endobj
|
||||
22 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 704
|
||||
>>
|
||||
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'T=Q;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!mdba'E~>endstream
|
||||
endobj
|
||||
23 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1519
|
||||
>>
|
||||
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]Vk9qF9Tkn]MuN&\dgf[=(r_.hi%(RY%\h'i!n%p.O#!)EA(X(E)5E/-m8[1^rGCuWGgTpk4+O0.TLDGX2[aE('_E9ijUh&kJm"G.E5%02\_rkjF`@B=Dd'uZi^U9D!s0@iEOmHUb34$ZQpUZ:h9@Z5W6n?NF(b(\+VANbbs=oUQ:-kQFm5q%6]#A`[Kf'E<-L.$KWeMAYQXeK[n9Dr7[&Nf_?';ho@[7?kMu'8c2/o:Wjq@9S.[tVXZuc3^6#e+JOl)GPt*PF\GnL`G#o]K*_"shf8mq1VM5i:Nd'qBPT>S36RDT&+L-1HEGX*UfG[dDahCi-(Ia0SmTaV^-A%bH3N&$?`3pd-TD?nqOo0coP</9M@4qqGLr:tQF9m4#d5%Xj2j!VCP3YM4K59!m"Sag#mceQm#S*r-Lok._tkO1?oF5go+@N-cj",bUo]\Gi'oD"pL'@-.b/l?]3r"8bE,eJ3RA7Oj54Fsh%#TL5\.DoLU\:cLbg(Pjs>DFfM_&!#gRS'fB'N`ZUYkM?Dgr;)D5-/KHMI:.`H7XD-8,dX,,'qjND@[H9Al%SkZD;TD\;,&3hGS9co##CaF)37&X:jVj!%\)(^[nllU0[JqSaIg@*:7V'3rYPg>hk?h`TjkGDZi^SJrO*SfpMp]^Lsp.=+ON7f8.gg6I<Wa1Q-8osbas5O6O;mjTdoFEY+-mPj8pm11cXS%SnP.<4J-_@pPjl]bMkZ'^fQdLL>':j^BeXJ$;[V<I%;8fV/8h'Ws?>K9LU7fuoQqQ&e)_r@rO:Rb@aE2jSle/[cP3QF[/mgJMMmP'\CnO1kJ>h;CF'KU]_PT,@=c9LAr@?07Xik-_/i^++PfEKm.*@7pfs56cX_eH,2)67AkL\dcQ,W;XfqlY\8nfG,p)iob.ip5-06qpSRlGi4c_54R-@#73J;Q9P)0R`TCbY+%WZM]NC5e4YeNdr^MM=P;kK+W(c]9bAWt#,Fr9#!;HN7-sUjh6pk+L8u-cKpBB5fl:eK?B*34jQb!0ise!0RrCBDgSYHEjIW<"6kmY-<[eKfXglh`To96`Z,>a'a;;i7r'fYW>;e&13fFjfecNcG7NPSH5>5$`<3@d2iJV_o$/B4WbJKN2I;u9lYf'Zmf`#3kNqNFPtM,]n(kt/Z6(9)"#b/^qU%2RW;9^Vmfdo[P]^Gfl/W([I;+LbHr,0ANdc>06ZmW'*WX\"5I;DC&~>endstream
|
||||
endobj
|
||||
24 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1952
|
||||
>>
|
||||
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.S0G!YMPkhZjqc_;)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=&9f,~>endstream
|
||||
endobj
|
||||
25 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1559
|
||||
>>
|
||||
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=gE2[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$/(fLH79D+B.@f-l)-L^$]\!9,Of$-Eo-kIDJZ7PZoo'TJB*_Yg&fR$f<cnRDRZ4BuAY@VT9UQ5cMo1o0*R=Dff[rf]a3BamDbnB4R`i(H-.k;\b7Nesa0Y>:Oi_A6*UIkXIqj(6-YliL'kC@93-]c\3oi"+"Du@HR[I>0PJKT11nO2XaAk73c#3%g7i40sT2@BOPa2s4_n]O!H<[Cf+I-Oo;\N<iRrj=e"*uKHoqJftFSB6a;M-1/;sDN0AL[3j'=q*gma7jFJtB^qU$OIb0CVlj2Y5D#=tlQVRAj)R"^Ks#S[IPf,HJk#-lLd?PH(8EIt_nk":J%i)"s#`kp-=1$?!"/'#JMtSRR'kh.$r(NTM5rglTQ%Uo9ZZHH-J.1)Tpn7D?&W;bnSGRh@6P#$3U,Fn["f-bST`Ti4aTC"'M[7c*>=@GSP#^tCe#N[I$K_5h"@^ppX`=nkhO!JG3_;E`WHNA`:S?Jle-rubctl;Jj@V6P%9lo'($ptHUh">O=nd]a;Jdj2e^(rj!u3JI0'T$\,4'.>saa),5nF1K>FmhY>hL?aVOci*WX7eAU<2p;H,!U63fL/QW=>1<P!<DSM0ELe%hO-3V$B$)4Hp^SfC,o:psr4Np&P.dLbYGo#'SogNrlN'0]:V/kY&k6"\N:%CoqIJ+X.OE8[$+3doe_#;(j6>d6n@*K+Nl)5[*RKmU1kYfmdg6dJMo4a4hap)m5fD'3n0I@Z"qQG+Qi,fN."PgHhh1`OY5c!QU4dKG3nkFgcVgo3TjXNGCj-4uV)29n>us%)98+@Pp`;.XL#kVI2Gn-[bTWk7e>:-EQ%:i_D=%Z?Ap?*cY&h_0k'SBiMU^1C&UYX_.#:;"J#W#XmhR=Xf648'CF6Su<Ah5B8kl\/)c!5FIAaku!/V%Jl^_ADcVjs!08t#nG^1^HHAscSrMCo\eNUlMl?oL(7e#$K2M2@?;RtOoo5inj-g`?"es$!-do$l.]JER<(^f%j.a+n;E4rB>DJ*5f0SXb'+"ir5F,Nt`V.1knYB`!3'jb6X=b#:@Z@)$nf=%cG,B)>!lfbo'R+^j*OWo>M(C"&T7K~>endstream
|
||||
endobj
|
||||
26 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1871
|
||||
>>
|
||||
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,"BT93LL^"+(g-j"(0j_YK-Km=8Hu$L1J[KV0C_@].$oJVK<(G&8$nM`[`Fm:hEr4[Q,"7l2jQ<An9WbJt<p>8(1'%<%VEbWthH+4p;uo[naQCO-"B6AP\dpPHlY/K8$E*f0Mp6Al=O,l#^ofl$6.(hk$p$`f::o5.b4o"BmN3d=pSg[H&jN)@G<irU(W$L,XcA^8@MKJd@J^Q.g>)W)q?2SP"Rk%!2Li*WE,<!brB:\8iE:ZYl5i;0ek"EX:0q-/hs[$$!D%kMQ-XHBGk(RMAQAJJj!aHN9ckotgPlN08L5:U\R6D]9u^r;ZTjEZT&65c]39(3\)6B`.82iEW=,E[2=nWjBmf4pou]i2ncHgLg9ZL?,Lj1-E.[!pj,XLA`/doj.eJh&l,D`r#C,Tb^1D7>U$g[$V53lmk)bAmVb$8Z(m+U`l'5/c!ljk$UC-.ia&'Al/)qgLT^e0.FNS\jGF*(LT1i&>_(P30?SN.u;3qNc,tMsi_ajU\)bfL+,JdquT!AC,=,$TRuC`d2#tpB%L]p_mm:K)eD[lPSNQiI`Oln"J\<4Sb=:-U'*_1Q3ei5n`''pi:h0Mr!Q5H,FX_khp?f0&L3q%n^CEl>.6^+k':N-`qeY.?C,t^6K$UV(fFUA@JX^)H8FL!diue8'hLaDa0ea5`hYT11[S#G;H^^95MXc)?WI6`*atC?^lkM)Zsoi#]E=3C>#31FC@<aZ#!"V`duXKC+c2<ou7^!XU2,$k4tl?UJ;9KXi%1N*4%p=/k*CNQ_:6!eF6jl"&I(eCH@!nJX:']jIcSqA8V&Og'kG0gB.J*m0C5%47J*Q)973I,E=>YX,D1njXJ,%R7!1F2YVgZ<nee#n=$Z?XG92Ro='(5&B#6PYA@G=quD#9DIUhP9hg;1!98N2'Nc:tMMp<[*)12%kV<!GOr$.t_<YbP<jBqR%_E=blNg<u2io\VU]l_L[k,om"9`(n7#+'+$[o9t`/X:>I";Uls$$3tqdB/*mXrhd@^p</+]ehp/V5NYg_gcse&B8A<OaU^Bb4NNj1Go"I]OVCdh&^LBpDr(<_eTqW#s@2f8IlpAV@[/HADOpM4EP'>pm0n9Z-L6@o(^-1S!Xo7D/ge`?b$RThj`4KaqL4i:Y0J*;'pkPLn2@a.>%F]sKD$bfCV0pp8q3?0KsMg!KDKD+hud2ObhuHnLJ_e-&8od^I&<HhhJ:X)Vk-Y6qD44]=YS/C_j@"U,,%WnQKcE'VOVLX$QtCXU?\Bk3V_92us>MfDnSY>h'`K?i":YHN^HRk\kK0mW%!4DBfH$@]qep@XW]Z5"1g46QXN>Pp(Oi4=`Jju<P$4U<ja*Z,tQ#<V?@$lYJVo<uetQ-Wl@k=3)oRPiB+&#dACl_:JF3LjA7f7V*OaC9l!4\;*I+B"COSo&DLEO_0mVt@dj+JmruiUsk1b:J8*\S:f,,)2eM;;.9f$q^&(-HrYZ;!]*fl/QJ?n2a,#?`W*g/T_UC:]D<55%caL3E=%Gq`-nN+&Tk$kuSCt:GImWNB2hO^<1DBc7>GM7HN$9mkqF-=FUD\9N>[~>endstream
|
||||
endobj
|
||||
27 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1724
|
||||
>>
|
||||
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:""!*h!P/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]QRGU9?]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!L(:nUA~>endstream
|
||||
endobj
|
||||
28 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1906
|
||||
>>
|
||||
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%Boj%d25#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=lnGWM\J,.U~>endstream
|
||||
endobj
|
||||
29 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2014
|
||||
>>
|
||||
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[D0lU/]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![57XjT~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 30
|
||||
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
|
||||
0000001461 00000 n
|
||||
0000001667 00000 n
|
||||
0000001873 00000 n
|
||||
0000002079 00000 n
|
||||
0000002285 00000 n
|
||||
0000002491 00000 n
|
||||
0000002697 00000 n
|
||||
0000002767 00000 n
|
||||
0000003059 00000 n
|
||||
0000003189 00000 n
|
||||
0000005193 00000 n
|
||||
0000007291 00000 n
|
||||
0000009468 00000 n
|
||||
0000010263 00000 n
|
||||
0000011874 00000 n
|
||||
0000013918 00000 n
|
||||
0000015569 00000 n
|
||||
0000017532 00000 n
|
||||
0000019348 00000 n
|
||||
0000021346 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<2d70597e08491c5e3793c7c8ec02df61><2d70597e08491c5e3793c7c8ec02df61>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 17 0 R
|
||||
/Root 16 0 R
|
||||
/Size 30
|
||||
>>
|
||||
startxref
|
||||
23452
|
||||
%%EOF
|
||||
430
Les15-Cursor-Vercel-Deploy/Les15-Slide-Overzicht.md
Normal file
430
Les15-Cursor-Vercel-Deploy/Les15-Slide-Overzicht.md
Normal file
@@ -0,0 +1,430 @@
|
||||
# Les 15 — Externe APIs + Cursor agents + Vercel deploy
|
||||
## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven)
|
||||
|
||||
**Lesvorm:** Tim demonstreert klassikaal. Studenten kijken. Zelf bouwen = huiswerk.
|
||||
**Demo-app:** Nieuwe kleine Pokédex (PokéAPI, geen key, visueel)
|
||||
**Vervolg op:** Les 14 — RAG + Embeddings
|
||||
**Aansluit op:** Les 16 — MCP servers
|
||||
|
||||
---
|
||||
|
||||
## Slide 1: Title
|
||||
### Les 15 — Externe APIs + Cursor + Vercel
|
||||
|
||||
**Visual:**
|
||||
- Background: CREAM
|
||||
- "Les 15" in BLUE
|
||||
- "Externe APIs + Cursor + Vercel" in BLACK
|
||||
- Subtitle: "Van localhost naar productie — met Cursor agents en preview deploys"
|
||||
|
||||
---
|
||||
|
||||
## Slide 2: Terugblik
|
||||
### Waar staan we?
|
||||
|
||||
**Vorige lessen:**
|
||||
- Les 11: AI SDK basics
|
||||
- Les 12: Tool Calling
|
||||
- Les 13: Agents
|
||||
|
||||
**Tot nu toe:** alles draaide op `localhost:3000`. Geen wereld eromheen.
|
||||
|
||||
**Vandaag:**
|
||||
- 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
|
||||
### Vandaag — 180 minuten
|
||||
|
||||
| Onderwerp | Duur |
|
||||
|-----------|------|
|
||||
| Welkom + Terugblik | 10 min |
|
||||
| Theorie: Externe APIs in Next.js | 15 min |
|
||||
| Theorie: Cursor agents — Composer vs Background | 15 min |
|
||||
| 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)
|
||||
- Tavily — web search (key nodig)
|
||||
- Stripe — betalingen (key + secret)
|
||||
- GitHub API — repos, users, issues
|
||||
- Open-Meteo — weer (geen key)
|
||||
|
||||
**In Next.js — twee plekken om te fetchen:**
|
||||
|
||||
| Server-side (RSC / API route) | Client-side (`useEffect`) |
|
||||
|-------------------------------|---------------------------|
|
||||
| Snel, key veilig, gecached | Nodig voor user-interactie |
|
||||
| Voor initiële data | Voor live updates |
|
||||
|
||||
**Visual:** Diagram: Next.js → fetch → externe API → JSON terug.
|
||||
|
||||
---
|
||||
|
||||
## Slide 5: Externe API in Next.js — code
|
||||
### Server Component met fetch
|
||||
|
||||
```typescript
|
||||
// app/pokemon/[name]/page.tsx
|
||||
async function getPokemon(name: string) {
|
||||
const res = await fetch(
|
||||
`https://pokeapi.co/api/v2/pokemon/${name}`,
|
||||
{ next: { revalidate: 3600 } } // cache 1 uur
|
||||
);
|
||||
if (!res.ok) throw new Error("Pokemon niet gevonden");
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export default async function PokemonPage({ params }) {
|
||||
const pokemon = await getPokemon(params.name);
|
||||
return (
|
||||
<div>
|
||||
<h1>{pokemon.name}</h1>
|
||||
<img src={pokemon.sprites.front_default} alt="" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Drie dingen om te weten:**
|
||||
1. **`async`** Server Component — fetch direct in component
|
||||
2. **`next: { revalidate }`** — Next.js cache opties
|
||||
3. **Error handling** — `!res.ok` netjes afvangen
|
||||
|
||||
---
|
||||
|
||||
## Slide 6: Cursor — twee soorten agents
|
||||
### Composer vs Background
|
||||
|
||||
| | 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 preview deploys
|
||||
### Een URL per branch — automatisch
|
||||
|
||||
**Hoe het werkt:**
|
||||
1. Connect je GitHub repo aan Vercel (eenmalig)
|
||||
2. Push naar `main` → **productie** deploy op `je-app.vercel.app`
|
||||
3. Push naar elke andere branch → **preview** deploy op `je-app-git-{branch}-{user}.vercel.app`
|
||||
4. PR krijgt automatisch een comment met preview URL
|
||||
|
||||
**Wat krijg je:**
|
||||
- Stakeholders zien een nieuwe feature voor de merge
|
||||
- Cursor Background Agent maakt een PR → preview URL → klikbaar
|
||||
- Reviewer test live in browser, niet alleen code-diff
|
||||
|
||||
**Environment scopes:**
|
||||
|
||||
| Scope | Wanneer | Voorbeeld var |
|
||||
|-------|---------|---------------|
|
||||
| **Production** | Deploys vanaf `main` | Echte API key |
|
||||
| **Preview** | Alle andere branches | Test API key |
|
||||
| **Development** | Lokaal (`.env.local`) | Dev API key |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
### ~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, gebruik Tailwind cards"
|
||||
4. Composer maakt: `app/page.tsx` met fetch + map + cards
|
||||
5. `pnpm dev` — werkt
|
||||
6. **Composer**: "Voeg detail-pagina toe op /pokemon/[name] met sprite + types + stats"
|
||||
7. Composer maakt: `app/pokemon/[name]/page.tsx`
|
||||
8. Cards op homepage linken naar detail-pages
|
||||
9. Klein bugje fix-en in chat: "de cards zijn te smal"
|
||||
|
||||
**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
|
||||
|
||||
**Visual:** Cursor screenshot met Composer panel.
|
||||
|
||||
---
|
||||
|
||||
## Slide 11: LIVE DEMO 2 — Deploy naar Vercel productie
|
||||
### ~15 min
|
||||
|
||||
**Wat ik laat zien:**
|
||||
1. **GitHub:** new repo `pokedex` — public
|
||||
2. **Lokaal:**
|
||||
```bash
|
||||
git init
|
||||
git add . && git commit -m "Initial Pokédex"
|
||||
git branch -M main
|
||||
git remote add origin https://github.com/USER/pokedex.git
|
||||
git push -u origin main
|
||||
```
|
||||
3. **Vercel:** Add New → Import Git Repository → kies `pokedex` → Deploy
|
||||
4. Wachten 60-90s op build
|
||||
5. Open productie URL — werkt!
|
||||
6. **Wijziging:** title aanpassen, push naar main → 2e deploy, ~30s
|
||||
|
||||
**Wat studenten moeten zien:**
|
||||
- One-time setup (Vercel verbinden)
|
||||
- Daarna: push to main = deploy
|
||||
- Build logs in Vercel dashboard
|
||||
- Custom domain mogelijk (skipping vandaag)
|
||||
|
||||
---
|
||||
|
||||
## Slide 12: Pauze
|
||||
### 15 minuten
|
||||
|
||||
---
|
||||
|
||||
## Slide 13: LIVE DEMO 3 — Feature branch + Background Agent
|
||||
### ~25 min
|
||||
|
||||
**Wat ik laat zien:**
|
||||
1. **Cursor:** `Cmd+Shift+P` → "Open Background Agent"
|
||||
2. **Verbind** repo aan Cursor cloud (eenmalig — accepteren in browser)
|
||||
3. **Prompt aan Background Agent:**
|
||||
> "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:**
|
||||
> "Maak branch `feature/type-filter`. Voeg chips toe bij elk type (fire, water, ...) om te filteren."
|
||||
|
||||
Beide branches kunnen tegelijk draaien.
|
||||
|
||||
**Visual:** Cursor Background Agent panel + GitHub PR + Vercel preview URL.
|
||||
|
||||
---
|
||||
|
||||
## Slide 14: LIVE DEMO 4 — GitHub Actions CI
|
||||
### ~15 min
|
||||
|
||||
**Wat ik laat zien:**
|
||||
1. **Vraag aan Composer:**
|
||||
> "Voeg een GitHub Actions workflow toe: lint + build moeten slagen op PR's naar main."
|
||||
2. Composer maakt `.github/workflows/ci.yml`
|
||||
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:**
|
||||
- Background Agents kunnen lint-errors maken — CI vangt dit op
|
||||
- Voor je merge: weet je dat het op een schone runner ook werkt
|
||||
- Geen "werkt op mijn laptop"-discussies
|
||||
|
||||
---
|
||||
|
||||
## Slide 15: Composer vs Background Agent
|
||||
### Wanneer welke?
|
||||
|
||||
| Scenario | Tool |
|
||||
|----------|------|
|
||||
| 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:**
|
||||
- **Composer = pair programming** — jij + AI samen aan dezelfde plek
|
||||
- **Background Agent = delegeren** — je geeft taak, gaat iets anders doen, komt terug
|
||||
|
||||
**Risico bij Background Agent:**
|
||||
- Agent kan vastlopen op rare config — debugging is moeilijker
|
||||
- Verbruikt credits (Cursor Pro of Business)
|
||||
- Te vage prompts → matige PR's. Specifiek zijn helpt.
|
||||
|
||||
---
|
||||
|
||||
## Slide 16: Lesopdracht + Huiswerk
|
||||
### Pokédex zelf bouwen + deploy
|
||||
|
||||
**Lesopdracht (in-class, 30 min):**
|
||||
- Eigen Pokédex repo opzetten (zelfde setup als demo)
|
||||
- 1 feature met Composer toevoegen
|
||||
- Push naar GitHub + connect Vercel
|
||||
- Productie URL openen — werkt
|
||||
|
||||
**Huiswerk (voor Les 16):**
|
||||
- **A:** Feature branch met Background Agent (1 feature, jouw keuze) → preview URL delen
|
||||
- **B:** GitHub Actions CI (lint + build) in werking
|
||||
- **C:** Branch protection — main mag pas merge na groene CI
|
||||
- **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:**
|
||||
- Eigen externe API integreren (Open-Meteo, GitHub, jouw keuze)
|
||||
- Vercel environment vars demo (verschillende waarde in preview vs prod)
|
||||
|
||||
---
|
||||
|
||||
## Slide 17: Volgende les + Afsluiting
|
||||
### Vragen?
|
||||
|
||||
**Vandaag gezien:**
|
||||
- Externe API integratie in Server Components
|
||||
- Cursor Composer voor synchrone changes
|
||||
- Cursor Background Agent voor async features
|
||||
- Vercel productie + preview per branch
|
||||
- GitHub Actions CI (lint + build)
|
||||
- Wanneer welke Cursor-modus
|
||||
|
||||
**Volgende les (Les 16): MCP — Model Context Protocol**
|
||||
- Wat is MCP (Model Context Protocol)
|
||||
- Bestaande MCP servers gebruiken in Cursor/Claude Desktop
|
||||
- Eigen MCP server bouwen + laden
|
||||
- Demo: eigen tools beschikbaar in alle MCP-clients
|
||||
|
||||
**Daarna in deze leerlijn:**
|
||||
- Les 17: Externe APIs in diepte (OAuth, webhooks, paid APIs)
|
||||
- Les 18: Supabase Auth + RLS — multi-user apps
|
||||
|
||||
**Vragen? Feedback?**
|
||||
|
||||
---
|
||||
|
||||
## Slide Summary
|
||||
|
||||
| # | Title | Type |
|
||||
|---|-------|------|
|
||||
| 1 | Title | Opening |
|
||||
| 2 | Terugblik | Recap |
|
||||
| 3 | Planning | 180-min |
|
||||
| 4 | Externe APIs | Theorie |
|
||||
| 5 | Externe API code | Theorie |
|
||||
| 6 | Cursor — Composer vs Background | Theorie |
|
||||
| 7 | Vercel preview deploys | Theorie |
|
||||
| 8 | GitHub Actions CI | Theorie |
|
||||
| 9 | Wat we bouwen | Intro demo |
|
||||
| 10 | **LIVE DEMO 1** — Pokédex + Composer | Demo |
|
||||
| 11 | **LIVE DEMO 2** — Vercel deploy | Demo |
|
||||
| 12 | Pauze | Break |
|
||||
| 13 | **LIVE DEMO 3** — Background Agent + preview | Demo |
|
||||
| 14 | **LIVE DEMO 4** — GitHub Actions CI | Demo |
|
||||
| 15 | Wanneer welke Cursor-mode | Reflectie |
|
||||
| 16 | Lesopdracht + Huiswerk | Praktijk |
|
||||
| 17 | Afsluiting + Les 15 preview | Closing |
|
||||
|
||||
---
|
||||
|
||||
## Bronnen
|
||||
|
||||
- **PokéAPI:** https://pokeapi.co/
|
||||
- **Next.js fetching:** https://nextjs.org/docs/app/building-your-application/data-fetching
|
||||
- **Cursor agents:** https://cursor.com/product
|
||||
- **Vercel preview deployments:** https://vercel.com/docs/deployments/preview-deployments
|
||||
- **Vercel environment variables:** https://vercel.com/docs/environment-variables
|
||||
- **GitHub Actions for Next.js:** https://nextjs.org/docs/app/building-your-application/deploying
|
||||
- **GitHub branch protection:** https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches
|
||||
BIN
Les15-Cursor-Vercel-Deploy/Les15-Slides.pdf
Normal file
BIN
Les15-Cursor-Vercel-Deploy/Les15-Slides.pdf
Normal file
Binary file not shown.
BIN
Les15-Cursor-Vercel-Deploy/Les15-Slides.pptx
Normal file
BIN
Les15-Cursor-Vercel-Deploy/Les15-Slides.pptx
Normal file
Binary file not shown.
BIN
Les15-Cursor-Vercel-Deploy/lu41eglzw.tmp
Normal file
BIN
Les15-Cursor-Vercel-Deploy/lu41eglzw.tmp
Normal file
Binary file not shown.
Reference in New Issue
Block a user