fix: add les 11

This commit is contained in:
2026-05-19 18:50:11 +02:00
parent b053fc7206
commit 634789e615
37 changed files with 7587 additions and 209 deletions

Binary file not shown.

View File

@@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=https://ooozbbewsglfvysikbsf.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable_QNSXIe0FBRAP-Wgd_Rb_uA_GB1WcEHJ

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,296 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R
>>
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 /ZapfDingbats /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/Contents 31 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
17 0 obj
<<
/Contents 32 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
18 0 obj
<<
/PageMode /UseNone /Pages 20 0 R /Type /Catalog
>>
endobj
19 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260422152406+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260422152406+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
20 0 obj
<<
/Count 12 /Kids [ 5 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
16 0 R 17 0 R ] /Type /Pages
>>
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 941
>>
stream
GauI5;/_pp&BE],.K3,.dqcBC].D<q._T0=8le`Sd.YF3CREa"2Tm[3;60T`2\;.i\N$kDW(&O#bjPC@k!&q\!?\WIc8WPN%'Mlda:R#bU_Xm%%"DfO^F(36@i\-sd-rE;0N"!d)La]GGd.RO8<PeqPXZjU,D.ZYi!JR"-R'#J,g[*Bd&l8J-mOrTDlM5a`%"___a4RdK?\_D&1ZnJPSp.hWZ:fo5U=??NS"Z/AK+<Ue3F5*1HI_RWb@DuKl?$*(QL,/OBiuUfkhuIk",tLU83[sa=$kGqG\`$q,AT2BA;,mCdU28[O]#+l0JgQCMR08kPJUra(@m-ONcHQNSO7OGP0VW4u!#Is(X4Ei8iAW8^inu9*O2$'s1cTTrJ:q!C$PZob0O#,rPM@\M_+.T(:FV^%X-lZT#tJbns@%e-$)2em-!)+q"+^3(HQ\R#8rL&gfqZUJ290O.e5V*r$>o?S'5Z#A.od,uR-7.@0KF"!-8:4=]M+IK81)GM87X$^*Bl0i#L:(/'Pn_SU/nWHu0DpTSuZ5J\/90qXDaNg]@mH'aSY%=iXpkr\V3#IX/Q>4mF?c)0,nX/g_HgJWb$1gcT>oN,_I?]fhl9j&0G]!e3m7gB/9Un_$5!5?Fbjk1aDiN^V^ZE1Rr4=s]!hR4`([X?=0<I1*<oC2V<gh4QG-Z!U1Qdkco$S8m?+MT?2hJHJ3%U^A9^b=0_WOp0OCJp0:htjYWcJ%6>2a9Xren&1Y"Qi7&H#[t'rll*N_Y9Y(M;M1lMK2k$W&TTMe=R6j4JV,<EuE"XCTVNsU\=,-EZWFl'uFC3.Hbr(D!Ur-\Zli5b51?ne;h\$:+6;/m2-^3=Ti),BD;qEi[=.?IE/d4Z(`"fD,q$WI<!=F;9pZT)pmKO^>[,:4(kjNpam_7ggX6IXn)eeR/!(p1@B-';!WJB1&2VYa@&u~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1770
>>
stream
Gb!SlD/\/u%0#[%_0m0c(@t[Q]$j<&%Wg(*9p_\oisZk<+HY\XX@/@28TqNXrU8e2c*Ao]C)Rf=*$i[t8;]q.Gt=?TL-INllRi/I-STpBJMNTt"6G3Rj:mV%]t\k]f5++?5]gF:L(;/M"g^5-2n]3V/SYqgR/t/?KUK[W`e\&le:^r@onslr$[kqeQGr@9W%dSG4t!e4)*k/rFA#`O#ntfh'"L3HKk:AE*%ff.KSCFFknd*!b%I1\L!-u!Mpn(4Ymc8*?l%/#_,4_DrNl75T>94N7OIOc%"5WVA*M9;\7Eg:dE,d\,[QYV)8)+_l%dYQ#3g2rgqr-kQ@3k7E'nHp5EuOO%Gj.^//a+,DKkI&G_#suB'9=d\+8#s4>d9iG\*6W'ms!BJc]?bIS:l=MBOV_:TGa]p#+eEL;?s-?s,&WPm<(nGR*j@7a#l`n"WZ69kCQf8[C)*W-i:k=?HCFSl$$)Tp_2QXN54a8c>>q;QU_-h'gY:lm`cr0t5afoZpG`_RO-=BFcum!<qL_`Xp,JX`RJI,8kT[TOG2*cLTanWX;3U921&.FQ]Zo(cupqPE7pa4'nf3O$__G3)MM'EZ`CN0.LL)X*n)r.EEWQ)nZ02Q>mPW#9E`OXL;pN?#Uem60mW)1Oij1>1A(?!p>Lr%CZOf8J#';Et.<fB9g,]J]`c-%8/kIa)NZ6_g(-J(!jlpm.<!0<&_/=DHsYZ6M2<G4Qd-S,`r,HS\]IdbLlH>BRq>E?/OPUmQ:?[m8Xn-FoD*4L\C2[UG(2Ra-@1u"YOQeg;^S0QE*UDReb=!4g+7oP!R?j`=hKeV;ktF`F5EA'in)r3E,0d_32%i-c=AN%Y@7r(q.g[A?[8,kW8\QL8GTA+II`?A/IW]`aPg+&AYD!R@s_[<286,.3Yem!N+/&s-R&N0+LcRNipY\`hUH!q=3"Sa4$CH1l[ZNiUAa1h@GLJO^o=i;^[^G%4gKs=r<gD5J-dbps5;tT6Knk)qQ<fr7>m:"Db(=b9WCq2$SZs[0j^t[4SPV,-a(Q.^QGf6nm)?!OEl;q^jMd.Y3L/Y;u;n[BUfL'/)DF!)pu5iLPXe(X,dqasRMiNYBb0r\KliS/3&u=&(NCL3O6G@"pi<*2aH'\4PUk(l_<^"/(PDi`KktC>^e6i&DRZS7F=e[)al>A?cUVXu<RimVcaIP,h1@RVC)#D8g]O^o[itlt]a*n%cMEa1ZP5j=P_HN;%C2:.8F?a>Y-=LnRk/N3_PhV^%L/mk,^4qDeCNd`jI;iqf\MB5j>f"/fZ7YL,YEm_gV5a_#.a"Bqi:$_YXEZN(ZHPWhD+nIF)n(l.hl@b^Q=eb68%/tBO+9qQ4`IW*!0kpu$J48F'k:L\P3Hdeu'L:OoA9t#h.%W.eQH<VS6D!<g.#I-]TXTgAbqX2E[NFlRDM^XN_p^D$kSQ$B(50>\["g6m)!REY8pT0<[f&l)ORJuk7E)R9S<c^(-ral-#3IItV!l.a4b;a0]e7HBA#&53#AWgTq>h^[t_bXX;;U-=JeC6+C=,S`h^_nVs24U^Y-cT6r2A8Q*^QgI>D)R\o22a.ck6kbbl@Y9+\+(dslP&`j7hO%<BG@/[,A*(%i&YZk7UAZRDR.WKSAn4j:/13BSN,Lh$F-,kh*+R(mqo];?NqOP9Y$>EQ8`fB=ifl$U&Hg08;4%ehQ[0ijPK9l>?V<?2Rd&oCBFgE3mB7A>k?`Kghs:uhHUA&!Ci]iF,2I+]Z>?jR)Zs#f_'SMN[45mEK,V)FtF6Oq\OM!$6K~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1290
>>
stream
Gat=*D/\E'&H9tYR!u!Z3@0T-]jBGa=-]TB6H]u/+B9p872o@E$!q[Z6`uF>h`Jh9fKcHPD8BuMI:o.>2$Fq+])A6h!1+pD?;mL*7Pg^-E#A8@1MV;S&B>9.RfdY8i<omHn$jq,ON\X^Q$jP^7[X68E!c,@!K7WLH.7I7_<cT&"GOR&]=uU1A^hI2?D!EHfE*IDLuZ*2E5GiuA.lF:a,THgi>aEP5[5671t++0p9U+g,)VJE8rM/D2XuKo*10MA/i\_V@.eGfn%qd]=(\aoj!EKWM4,tn(c1;?]6hp`-8_]j_CoZ!QLQG&3^&,C,E1J$fZ2>E([ckQSh`QJ\<2)P/n1C+*QWN),rcW,T/!si8-NuRQTO+\LJZ,2<[CC&V4QTd*3Ma$R-P>#3+mot8A3+=H/@nijaEFKU9(_<=4kqY1AoANO+TjFiZs[9Sp_^pft^Kaa4lom[cU'D%RSBZcn-<PH-Pqq:cfoo^^?i:S>>WUP=3Ml%99]2aji/bPenrpOuS&;5F_=<5"MH;`[(#q"BAF]paO.OQL\4-9:H;8V^aD_nZ@]F[T/IP00Om>>#3QXo:NY?SW9iB)8,<`!#jH9/R/_5(j#EPjKf7fR`RC$:&krtXqQ6C87S]0).8`J'4hSLH:.P"5=YC"]?=OTl@*t:BOA3'Sbp#P]cYQ"RnC;BgV<cjoB1\]95OoFn?!o>^g*`'jmjf3j\m4<T+SB\30JbS59cL^\d2^Y%orI\YGqjoluhB"JSqP*cn\M^Y`[*d0(CEl,6@kiI/)rZ*N4[M!K=-Z##<)In3)Of+M:`u(%p1RhYn7>Nt&r/51mLQl^*YAm#pO(-V+&?c3[,\DV92uA_5"H9sc*ddb*aM>FR_MKQO3&RlaUNh]okUD$Jd:X+Mj?Z3Nq9"(]i#+sB/iMD"K'>i=aocD%N8!CK:>?`90&1p7dWHT*`H[c<GjXiVQaY+EV7D+QLLKn>"G*>+aJ/55Uh1fk403dcNL"eg[`S`)'<l(aAX5\XmB\rakr<#G`r.Ku'P]-CRh@&j3,n0IZ:"Qcjk,""_q%k*fuNuOoe%3@BW]Q(n75\P]Z9P(TW/FI8o.-ZN.fZ@,=Nm54n#qet_@P>N2"V\;,nSPs-=g.b@ipCXjY_K,^J]J3gnAaNAh04h!4rnZ(p_6K$9D'hJ>#OWV4t1n(Z\O[oY142JniY>5S,W\o:JsBm!b(Sk9T#0!'Hgn>Y%<bf1YP8h;R8`!+TQ#Qf,;ElTTqf\MMOY0Ludh@qH[VS0r)h?W]<7YIE1TVUAI8CiuaNjDOL~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1652
>>
stream
GauHKD/\/e&H;*)EVZim#om-EW4)c_k-jNC/PX^6faHpc?tJ%mUDI!pP86mlqi5uhC^4YYbXP:iFSG:`4\,'06,PA#Ibb"^`<8Z6.8U543D`I19MXPkdehfE]&qW`#GN07D<aB&h-^_JGgQmRfVT-ePaPo=#%<g+!b90U/8@I%@=HQJ:(_DYL2A^d))Y*cNp">82%qCgM/F7jE5E%K.$$K^j+CqGi>`FL+GPUTWo=E7[c6WbZ_37X>--#tLi&TF"1*j09o^h,%*m`.IKFG^KhU95'_u^9`mS.i0P/I[n]B`L8\?=61O(3aU`Z]?)5d9bq#ln6%R*c8gb_(eV_S:Q_&F!3P/A]lER2V@$O-#(d!NuS`RH/fDK$V`AeRcs6WI+V.CDie6$FlCB3:*0^c<W56gJ1[SEEViOR"G@,:+qBKIs+A(Ufe"L\"WnUGrf6Y5MVDU5)WFh9%!]NB&Dh9$#pK:5_ae^#ljD<)\NM(jNkHMGs9SDCe4+04]GTj-RK8.><,9a&)j8T9Jq?Hk_DW,tZlF'3'aYaY2l`RB3,:O[D[SY%1u;]Tu+u';B?.A.qob)3FcZ!K#lQke_]f\uCIb"Q!tWb?GS%koe9FNgdK,IJ?i_!UsY---q__4E#3aMSXD]MukE#LCN;BFMGd*^r>d]*aq"62''&3=)m<D,U"qU+_SiF:9Mkq?_!_#]XkU8+28Y"*+*!#38GO&?c`$/h:^=sa+&6kg?VUHUMEXP7iO)](,?p-rcHN/?29=g\rOZ`-):MbFjrn)rG]Z=']):N,eB(CXiaGgKj\U:$[7HV%),"U!\C/eiNK)@".oP&,oeh1inW!K,BaH)J??hhPYR"QAh$u\oY'Q36F9W0+aGI:?.q@r;5$-2,ob!M%/5aZ*W)\(XdM]`eg.?e#\\kHA$X(UnEM#``&_2(]":AnB&OI:hAeTRD(gNip8i*^lU.Xq)>dqjd\TPC8Q$.uaWFiHH1-gl#g`K6B).;RMJ^+n_i98o1U<$h>\b/+oqP?$Cp.tk4q&oDFq1]l_mV$PQ89'?99^M1>+#&T//HXh)QG"8rl$=W0*Hdfh?%CK=,Al"]9Mgtr7rRM+0!unC%!Od!)er*mTohM"_&YDk2OBJ5_;4Efcola6Ds_A@(P66(<SkZ9lAKEr@@kql4IXf#;>)^pu.P\hKZMh></YNjkSe37J9o#^=$1sXl\8Ug'[O;qmLg,U>+'teO)/6Cl?p]f,.=WD`/IC2HU`<=:TQEAMFlN#'7(&a'b2>LWk$YF7Hfs1$Ent5]meqHHi[B1DBo4g)IUX`XC<tV0L'$A\tB+Mi[tJBoU:UhS)(CJ;7\P];+(RdKHd26o]k+PM/=(<c+bJQL6?d*K(fj;15D.JuTEJVhOW-StO@E5.)Xhm9Rga^P9cUbYGFBBl"NrG0le7fI[tc-A#BXOE^`:)h&W/UgaL.XoZAIQ5d3f$E!+WF$CN%%Kff-r^YpR6QE4,$(1ZV>g%(ZMS#)jI@)e9.@5b_:t`JriYlE2,04#DYf&$fLo2IUk>7BU.o6F]7B#6T<0A>7jKUPA_CN9[*jH2imF!?Mp)T-c+9&J9Qu8OaX_'eEr82jL[]@jfY45hK_\I"4idWp_ZZS/9_!LkoLM=YIqcR)_m+bmT=UXl9#JGTfSfm$0~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1596
>>
stream
Gb!#ZD/\/e&H;*)E@A'1/K.fgW4)aGQG00>OEG9sp-[dlN(hu`-./RQ,YaF$^7ussZL#*S0OV4!1Eu9^GL&E7$4_N!q&<rHYD+_jTg0J(-U9]N0Kaus_qrudELeM"3"X^56f/%[\fQbhk\2\?jcDrb;<EKIH%Z?m%u_)b#\X3A_W4.H7(%L%G[\[4A^p=H[6>t#n+]/I9AIl%JJ71na@]G.*$:[^#j5kE!oc5M6*5SVFD,0ELgPpr]<m%!^`fE?;[t?(0)%A`AC1AGkN>cgYGSU]B1f#ia_2R<%#c#2i:us6\1!NWa>(>i2_nBk9]kIt%'O+7U1594,M`#Sp<R7Me:I?<*uB\^YO%j=Bp&kn>)#4MG+2$Nijg+S_5@=ML`'74-D/`%k5f\V3dDG*_)Waa_7`55<9;=QXfaI5dFbb1VPl#SGS"%S5FlRM;*#?phtpYldDI_<2^lk_YuX>."!?etp8&!A]W)'VLhGQ9PAHr"JaX.c.?&YF-C@8IJ;I;E*;h]cPH.!S>_jn-a?_!BNRS36jK+V2a?+GdMhT:]fdQ$nfi9KOjB\`3MJmbKP@Yf)"S;Z]m;US,o'90hjp3T>In%cCHfGSY7L.LO+bbqh2?R+P&sQ.qC?YC.aQ5"$\i=I6OekfCcaPIDC+N5nD2(`\)>+O,ah_/Uek.8ClJ&(/4=bcZ%&$C8.h]1Nbu&s8!iSOg3^bODN6&VU-M"-`ND<65^6FFkQ*6^de/p`FBk`kohl$P&Eh$ODT][d^/HZMfaSm4[Y'Yitpd++eer+`87*9A!j(s]cqj>63H/u=CS;hB`-.ij`@H_4sh<;;)(OSiEDJKW5!=8kr0L2LM[q"LQ&!2T;GXtnaM!('24;R^2L_'Ni$bpjgbI=U<@>e9J_$NJlo;i6rOoX<Aob@8#q*Z6gV6DRfKg&Df]`bI'7KRbXXjhCo),a?@Bp-e8fG4UMdfQ>h@4JJQ17:cEC/JlU7-A8>2_SUB1'=lD\uG]\6#e,B.O?$9fKd>![gAd[emQ;kAa=HtdfpLeqctU,E\3,b1QMURo61:F/`:q,+DfPaQUUIH7B5e&a0/5$?Q-2"Yc8>PbCP"^h&a;D,qD!#Ru&DEKiU.S(pb_\IP6hWo,[)_L?l@1KhK<=?=(U_K&o0i]b?J8Tj]8W>cn@sN@8C?9'A#-$8p[BD6&/F`.RuP`53.+K?!b0LVe?8MT$6>bG97A#^fJdl<"m*T3GD)bC);T>QY9Vr!U6UND^\7laqSg_m@rET-QH"Bh^aC:T)+4\41nAVUH5.F;NRke_j</T4YUb7MfZ$$Fu(P,ha*$r^T(,9GT^'jtsP\*A?sBQ&l?S`jMnR#ZmD__UqLkC4T(:ZRnPY"J!?I>c,Rt\$C\>'(<&O26N1Eqkbh*I#7OEEf'sf7]sl-0F^d:6"oH,J9`-t7TMCtJ'Bf0`pRIXcKJT3@1C\iMJ;c<82Gn49%=p6&Y(:3d/8i$:mjZ::\b.g;YLD>\$"+#bkn>o;`Rl*J/*NWRrj,;dg^nYQ]Rsi&9=&M75`75MDIZg'A1t1UA_@r%5RoMM"qfr'fmCl`F=q_p]_S-,c8*;P1J#Z,5#,RLYcB@Is00~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1174
>>
stream
Gb!;bgN)%,&:N/3C`YX#1GqSicH`rK1pI^<PfBtnoV(OP9L0:3?;pp3e+*4j])X:;,3as<kt@k88-"#1EmCgU$5U@Tq2R7]TB@oATKm@]YCO4&L%u/&kG(73`3H&`Ee-3A+8QJ/d.-JD"gK-1#S(Q:Chik*738]9cNi2OILsj+"GbON&W(7M@!Q0oj41#u+78SNM1!JO8fcoE?3i0g4W8$m$Lcuk$A#]>HAI)r(2cs.YEKH=c4TU4P]<&@eHH]pI@n)YLW"1\(JQZPr;,CD-+kbZ>fN8-8E#aF4PhJgfQI7u/l,hGR7P;`n:oil[+JtQ=!o]:KDE]NInYj3^=Vm>itA9omo[;gVaXqf]jg:QBAg6=I7/7J9RYn\T'JX,#ptVt`L/'Fd_L&/k[&&q!\4?([>AbZhPjg]S6<ktSfu<IbA&u1m_[sLW$?_7-2=JO),jZ3'bbD:1/fPSEZ\O:VmL.[5P0'FdgNY5JQg"@@4(#4Eer0IaW']+et5NsD%!&q&RrSEiie=J4MaW()2s'6VTJ4)Q<SOU,bK6"m(qoa\gS:0%5R*ReMLq5adT3)aG2K/$:?]_9H`b6a1iPQ-"!o4<NmtE=F5['Jbl'rHneco^glCE+>.]6i-1ct,lJc0=6KGJ%le@a/LKNJihR+<1+=:PYr&D[H>s?I/_Di#f*417mLN_VQ:a>obd`m>KL*>UmZ.[AaI?_2J(bgrA\C&UUo76RgFK-=m$[5T8&KLWZu$??2`P(l2cL`1&BEj=GmoT;rrK:1YiGjIqYXZH3J"LY%n$NXA1P/RHrEVmaU25,/ga2T]RO0"GXu"JOK:.&*H@XAdBBR18)Q]J\r3hD#F=_E+KSN]+tNGaMoGfl_tCI-VWs[6Fn2^L1slQ64"S`\Oh,gaNh1crp>9gDI_F_1(V&L*ic&74eXYfAM^h8_TS@6M-M`uEj*lN2oeYQekOUJ//Q&"n_#7'>XlbLW9=[?/VgZu3'DuP\>[)W7f3Sio8"`*(?2O>'^;n"#G_TtAk&P,B%8SVch#g%coZu\U`I8C9U?S?sn!>W9.RcO+fRG[.s0U*9W.FIZBX"DtIRB]BULTeWR2.hu3&@Z]%$'GaDO9M5k84'ONJQu^$_M8sfB]mjI$Akh*d"^dUQ/ZXE+=p0p47@jD7'dV-3K8lRQ$3)#%CI:"T~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1300
>>
stream
Gb!;bgN)%,&:N/3m!,D8SJa`o#i7=V>.7'AGML]IQ1dX"O9e^*^l(e2ZH(FDn+%Z+2nJNPB*s\'!SC\J4aT^O",_T0q4&f45>>P`JH=db=TB+'^uQ-b:I_f<@ai,j!f66R^2!i@D2/>tMJ/6KZd/]i)0`n'*5XkZ"jVtjH-Cn-_;a"$"J*:t*T-$>F6:!KPJ85k93krL-FWC<+:*0n:g:=+!Z0>k+p:@@J2e6%=RG!sp1qkQEBD7ObI[%&8po6%!k;$Bi<hD5i8iAUIpTq2/5kSWR:R;NQpT&a#,[o;E.25dQXsD?'uE%TVf\4P.Q++X\Uhl'SbNJM#hRadJ#Q%J'L'Yr/?LI?j'KhU:*k-(JNb<j4]8aVlJX[l3EB_,^_OQ!mD#mCqC:5WZC7kf.57s93glSpWT#PZ&a0YN6/H'en.s/G71,lR7b%W8:LA$++2C@$^cStLCp9nP$(`lq`e3*XPD4$=&:tQjER1XdXWhElor7r3Fe;SWg%Dati7Bo9G?(S1<[?+$<NfpV7jqhYH]=E%k$?\oh%;#<Pqbn$L,rEM1d,!jJpRK9_Y=W'>*e7tRAdHe,YkJ<<R(uK;HRUT%l*WX>'$K='jIic\>`(60-7]GQpk%Ta9Tn^7rNU1pMQ/gf#gTO=BUPSCFF6a=4W#c6Z2_s(I9'g[6uiJA5sPJd(2`FV$s\qV5(`ig'dY!EIZ(hE>Wh:f'o0t2TB$5WNg''%M&TVd24d0#'<)[G!*e+FW@5bEqfZqcf]@;D*Z6#O^D+7%'SXb,"3MB\G5q4bjgi<KV*1fBIjX?.?qq_geY016.f6V(?7O'%mnDLcog1GH2FNU0kBNk2+qD"p:/%"N)oB\)=,KB!Bu`k4gstRHYKtp+n#bRD(Pr)*7@8sVWCKc=IMX-aGR;K<F\010\ujle(OZbRf-8OM$i9XTFs?JnEP$N`4QsH&-`0TBjM[5__j>+&%UsAKt=DG64M$Pi(iQ0SNSmn6hCJHlt<D"KSIqh2*HZ"[nnR'i56"5A.^7XRTS%fi@s#/AC,AH4n&3[AOHH)nRi,rAOT_9<LTmub,2Oq!@6WCBl]1_=Q1!qY`(MD[JI/MRA@pPGX*,+6"R\s1H?eq6H&nhD#TUM%'24sn_:J#A*i:J[/O)0XZ]u?U8uT.J#T4#i\XE+eDOdJn1@t\>YEfVI96(lrniY?GB%##YquFMb6WU<=+-Et4S9Tb;T!Wu6oPY?ei)><gb:`"kjqWW`.R"D:65;/7^KJZcF"Zghp`+EK$SXeZc\Xd/`D%1q!4p02g'H%[l;+?kTB^!'?:~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2056
>>
stream
Gau`T>>s99'RnB331H<fTeg[>=hZUUVql5pG1gb;B_R2s`E!rl?a@*KV+gpd*ncS6"<17]b_D%<FX2Cu4$#;+kkIThhd-`\l5V-h#S7@+1E]A$E.///T+CoABSPq93WmV\/]TV3Et"8D%'j9&i*qJO)C<l7dks!tRhei#=gh%@a99_Y%lCb%DsO:\:jc/M>4-]"dp,PTJ.P((*7Fqc;3SL^U0>l^TKAf$L-)o<R6&&)C4-QF;4(Y6WZgD,;JTq2RU]KR!Hg5O=U[`.(qotHb6bgS"-Is%1l^G"b_.pAE'^H402KS@o/t6nnDUdb&lH6!73_Kbr"@;IDs`V41hN.[qk&`cL^rluiof>YWV6;+K.u9BZo7c:lo%Bdbqr++J74lb+cN-q'`!F7(:.HH=d-n)WL:O=eYnLHa47]Z9eU]sZ\4.!Bk_)0e^6A8W7-4oOGR,/`E]/*+*bd1BPM#/V9(ZfeY].Q6;/bO/?1mZ_^]F5?+i4O&N$-7=QDIQ"?_#8(S_]S/HLAh5J.%Nid(Npa5Ub\`E,(F@jmrj>!Z\ilcN!PPR2hUl2'moFkZT-$#Y(EZ0Ys"qs*S"bVkE-aZVAm)&pJrI8GAr8;VMU2bPH&9]JqZ_%1TNNJ)e')l;=#PMgcup=;gej&ls_0T3kN\1DY<c>IC1@<jZ+Yl$Oo]aRT`bX]M70pW\Q&9rr1+\Ra37qQ3PC.`h3RAG`[a2/@FReH&m-j<:snMD_KijO'q4M;B\_+N/,onJ'N'H%>@c.LNm.)bFKZNMphn!*\nd55^DpT+H&o<U,A4ti3!-P<#hAce7R_l:>5,YE[h#MkUd:+Cl:pXjuMU=EWWC%>IBHAeQQ3I5ior^)\>:;^ViBA!7["p>ga4,[_G)=s2a-sNEObH(-:q9()HO'2KV(.AERUS'$kc#.Bjm<8*=M2H!&L@1!Y("]!9=-oYl"rF%mI5j9B>f`-*2I;>k=^B6PI+cl@0>9U-?PU7i8Q5(fo6,h\B.%egG@CoG!dFi7,euPRDhrcANSjB07+ZaW"qYT%C-,dh`r/s(ZrtGm40L:1RmD+)P>D4oT?DdT/c9"ngj4Eq42B'^h9)>Z3e(0`+uh*Np%FWanQOf]D$b?&p9FPLaRrP5@^(!XmhW_8@U0CYk$40i8QkXYhU&iQl`\iHF=-eroC>1=R"cmn1Wc(,9@[X>+Iih@Id458AR0Q3>`]EC]9-Spcn8g-eUkhY4ms't<>!Um.qKGVqN=;Kpu7cn*-G@-P#V5cIOgon%tgRYI6Z'-0tMYM\Vb(O&7$LG<sV8`.hSo6(h%[LR8b6jM^o2PR;ul;V,gf\P%>&)OXQV<<3ggf`a]S/]85?b83"q'0'2Y315Sni8+SR(#FJ?[l=utQf9g8_-&<aIi*pND<@ITccsWWqrCIHW],LSbKaUG<0YB>T%<hfpaO:0$GCb2lW\>u5*o^N5>]]7$Y#FBsOO8t`*%#+<0j%Y)jVK,_fCA;NeC8q.e7K6)_`!_'Xn\1tO;*L6_!WZ@+\m`K-f;ea*<HHl:[!\e?/33>KssmkSeXm^jgfon4J,DJNbffTb<jLZ!XLqA6VR0nn/8nsLNKEc.e%@Af>Y01<KTB,U-B4HM=%[FA:LN5'd\tBUPt@Z)(!V6benHOa-KaMp%P/\3_bC0ZZ1,53"kC8bWo!n]@!:`[+/RDi!6ke#Aj*D8\eI$OqaC3ZeX+[2&r25=$-h<W'ZK!c(;3UO7I`Q-4Y4_"FJ]iUEEbhr`ORqk+G/#J\?0Q?+eVebdgM;L:WdT_.A5q2eWoeLZjKl%TGP/__r6J`AB)!%$\Rc;6<>+_ml=qr#Q?Ho0aRG_F>gbT#;#!/g&[AdH>E5?,[@^5g6&`8%$#eR>$3L.WOZ^CNthe[jLS(^jI=5(rcqWE`Mhh_=UOA^ai+MBQ7%6EN&p\)&O5-6=B*cb=F0FYgerKC[&:&rZ>fPPIXeI<i%0Ek+jWR1!6j1BcQM>OuIF;olrX%3>c684P$Y7*EbnEG%6'?ej:@Y%V"!YKl8g^e7_Y9W1"h=WH_K8RR)q/[k4i;3\Z%'^u.E_9KEg~>endstream
endobj
29 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1546
>>
stream
GasaoD,]1K&H88.Z.#*kTn?q"Ol%!`9!@@1RpJ_q]e<)E.#sH-r0"Bs#fH2iDe2"jPLshdL_=Frfk6p_aeE2%+"]^p"Q8)nQkpI8%G*D/N%hd@$m2VWrsZui$@Ia4)*+kgNe$mL`srsIo0J8Z4q3<BD&8_X+h$RD@,&Wm4R,3b@.n1tK%f+g*8e5F[&N'i)h1<Yf5cGj&K1D)%uf-N%CARccgZ_O"(;TM![*MW'3+k\Uj^KJ+DsmkB5*p-B+a$D.0J<P,N^a<8RV>Tlee>m@l>,Y>\$tG5?'8PKn?RHHZ8u+30uC`8ke3W\.`kj9LPljiVEb]hsB8G28WU2#QM,l5V;cPe#>10[)0N;o-i]3[",la\d_TIB'LNRN]'oAQWp^G(g.d60(UnQC_U&#:(WsV'Lrr]%u@8tf(FT&6fD=@h88@mXp!l6i&/0rabr9LMD_*dpT.1XDXm%$BJg41%Ho-m(TNOF=<l#\4g=6upHZG(e+Hk9CeUm!)qCr(n*b!.?7EV,G!%CRGYVT?_XAg6LG2Ae=c-JdS>Q\.($Wmk,1uL?=[e_C<mlZ#k;&+dNLImpSDQXNZ:qL0+3H]&)e.6sW"LCX[G`$KGl3'4g3rNR,DV7$YR%'6pO&A<Ib3XgGH?_jGW'Pl0q6f2c5P7%WqobQ]\2$S7pWIt3*M*W"^adsm4oKa/5Ndm*IGnL5/:(EO]RijCTKc[1-<_#YUHo)_5]9oS'992L'NDH&B5;3GHb,Gg`mErR4EN80GduNhb+W-Jm"ZkE:DG;UPUFX-(f-*bT24n1SU5*/T3aMYu52Vn"MgD;3lD&I$i/HW/gLOCf)JsDr_Q!a&*@?JW;s*/'cn(h"lqK,I^ScX3u>EB"Wp7b_e7,Pj2Qs&''IpfRs=mrDY9AM4F,]O@&2N^YlIBGuDZ^(XaLfcc/=8N">[\o1G%lNAN]CT%TPh)R^%#[]Sp9\St8A5!@Y0]`t!.KFs9gLGX[%gU6]!9Y'0'<Ah.^:YP?17iQ&n(MZ6NfUX#S,M+Fa@;#7IlW6$bdh2NU[?k`!Dh4g7/?Y^mqR'-QUt!@e<h=Z>X5]4P+W'1tJJi+q-9LG"_7c3DD73/bLMe_DOlLe.\-6l(rK!G.9DO8hiq1c3DVQlhCid/Fe*PqQ#u%XP.WT%il>/uAPA.DcS+)\_Dg8<"P*LU?A[#DW$tOC;UV#Pk54Ml2q0U3[fX"\]i22ScRQVK$U$dV*@:@MX=9-PfO[b.D.e4)ADL=f(ngM1/XA0;u=jXU8_W/PLA7o&FN(Fd\];a^e>0_3g?j%6s?Y49dkKDtD\Y,MK9I/5tn6#+/f<9CVpoJ.G.:CGf#4"hkrT03?HRWD3C_C]h5'7cq6mOmPTDE#a3_1oV8YHiiHWc5]9R(p\\@'$H0VOhS/uG(XY91P(]+b![A*dE#<R=--UEXt1f,p-l9f_*948';hEp;hb^)E%CpTs3Cm[-,aDmT.:lTf,[Rlg,3pjY)5Y#mdGpHJlW?G:@6*Kn\7TpgerWkM9;QE73&CWj(re-YiNmjdD-DE?YZ0_c.j-Y%u~>endstream
endobj
30 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2265
>>
stream
Gb!#\D/\/e&H88.E@W`e/J)'\W4/iVQ=b%#c#?S8m4bX=5hh7"7M]9d:6lCemlOFQU;W4iAneLaNPbFFG7PePOarGkM"s2<!PFpfpD!]q@<)j2=UZVZkY2ON*E\=B"uZS,EP9//^^;'JL()ML6%X^k&mCMr&)>O>W:,$tWTEq>nKAWdrp7E-Mt+OK"p)[r-*A'@N+Z2TfcC^a=AIcY^b"c6;"r&O1G%/Z=H)2I+]tn,'h"*95R/3KBR'blX<kjRI+nujO"d5XjY2a(QPFUsVsjNP7P7(e>=odN4-b]V;S`T1I5pX:*bSW?.5?UM`MJCPH6j3s'IFo1o'F["N4>0Jr&aK\6jCSa2a!L@LGI!74l_GKiE0HTkq/@5K^':KI?.P+V<A\2Xt4/ee'eu:cgE;e331^scl"@J`bn:Y9abA`Qf-K5+!3%0q]kP86[h&W.4?VtV-&NV3):[Zf5dX(!Nq4%]`!pp%[h":BZL<8l'%[A7WQ)[`QaQXMl(tO94qPE??^HUQHK1VH>I6\!05LKoa[#9+JpIE3uqSHAIeLRZUhVQr)4hn]AJ4X'Lr2A.B/;lLSgH&f/E6W:c1bD*ESc6mD@KekdA,h9?"G8='QmLcZmSCd%A8HNF-UPo1<jO9lX%;FnZWo%g9.[`;hsHC+WAlp$d5V=7F+'h3l4H"IYg'o9LJAM"qGXs*.Agjj&)Bq>_>A\hV"c`WW.Y_q6g"eJA$_[9`0&ku0OkgI>N_>.U`BK,4a$I+^p%)Wk(JAiST1,nM@V(GM-GlP;n[6Il8l-^s4G!R\88Q8HLr2^b$2l8eQCS)_K=*$'Rm"l6c!.T\`)?Mlrj7e&SUaZn+`VP<GI]R+F_#7qJa;W/+O>*6&U=&6Z,\WME6A(UQ:n?'K=2?hDR<?I!VrW+N[+Z>K]77+X_6Hc4>LDgdhe@Bc+fZ@]U387X^e.qj<\=];7UJbNY!?Jhi1>m.'KK0$dXC,Hc8p)R_7I./]"3qX/`5<cSKbk)Vb1(PR,muh'MU.MeF+=U12r&03CpFeH:ciB,0eD/OosT<>E45]p4a*`;oX,X$rF4f5DT14gEIBo#Y3uZ8==!XaOj!YmPJS!$W)Or];GDp>).V<LJ#kR86>7U&W.+K.Op:TVe8<3kDJi)OiI-si"9.'^)+NUJ("lK[?g#h)M:@WjT-Ich.o;kt6LYZ+n.sb&c<!I5CE-W1>f2bLOc&6%NW,`l^!PpoiECH1.;D-S(4KO.T;?S.).Gi_Q!,8P&_`2a9:;b(a0oh]#'Ws_i_$(h"UgHMH%^bEQYn5H]&k*U"^4-I`9'Z*_gp=4R),%Y0,4C$/P*a+G<bg>$`aN?na+jK</N]'7SA`lFNR#4ALoKb*Pk9Q_*I0A0G^uh)QE$<LtOr.a.+*epZ-]]%Pooq%ktXHY.999mp"7iCp@P'`-*sSU['X[j;0"XUUj4Ymt^]Bq9`uLT82rKc)3Z;n2&CV;p!KK]\(>q`c!S&Qq'cG4$7rm6ZE:%2BluO`]?D\<p*nmj1(?gn=D<L0;3-pA;]8.?ZMSJVSg<`0%P>1'bT:(MGHZGEI-em0^4R)Er9t)K=-Zu%!k9>(nLX1KAQL^!62^9GGG/upE+/UagI":dUq,6iaH?N8gnJtX^^9^chaTMm*HS"3Adg3ZHKbFia3GKKV=0iY`4R!c^$>n&f`_(8AR%N*Y9>&0K,$YReV-K9dmZ2e>:+<.tL?r,DXSHoJughI8&RD?p$&>Xs1M6(1N/aI">A=r&a[B!+2qg1QMRE3<?d&b.!^t^0Im;8J@F5dA!&#TQmZn(J'<FF]kZ8ch'4G^Qh&&buomRZ.(7b0)O0(dRA4%3N2C*]tb=&>k5u)?u??h5MKB8;G%VSk%EBfqtjrf@S"-*=c#u/?7#t)'p[:FD_@3Hh^FV80h7BO-Wb[m==H_B@n>+3(hRIT*b(uo0Q*A0GZkaUACN]4o\f(Xcp.=HSt)4"*&i$YM(:cL0j20?r(jLNO*jh83uo)P]K.(,1(JUXPHsT3\3GA#bGoY/5!1FYd^11Ej#go/@M,id7%1qal=\Ci;@GckW:WXpb2\Ib@T"/i\:,4=a3X6ocVf<Oq'6DOqnmDlFG"^V$G8E&g$i3qnchkGoM0kkh8obP?aHRQf"0L+p2<"CX8]Q@nQZaem2(3.4Dtm_s's.M8FIL(6crMH>>skA>=58S$"TGbg-2:)rhl7L_U&m5Nk?j57gf?caKe?=0W^ij;:;9rE[dm7r(1K*&l3&D$NlUds7GgOp$W8Jp_T(X3<K~>endstream
endobj
31 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1602
>>
stream
Gau0CD/\Dn&H9tYQsJr%\e8oI3,^t9LE9)mG.WG[oM2=MI5&Udcm34e:t5L@)^cfACd[He"VitV4aH1XRE#sJ#(C:c4,s=?*5_4`"j144!Qk_(i?`T#N?8U=)P1_`+Jr_^[L'l0/`q%DVMuEO]@8'a1)hdj,0[<.@#W`u6QJ]C9Kk=i=718@bB4\_N*<[!hTq$k)`CN-'%=6ni.P7_djp=S*%q^:#j5m;"(E--61t3i[&Yp.`%E4n/1nPV%-#u!jhP,1Egu*P#b"i.^UeBXVD91L.2B]oQH8T%#.KOOE8#0"VJ2-(dYH0>6&'+qaZ)A6i=(I5G3j9b&VE,+p+Ktae:7o=qe8^BL#'J1#-gEH2_9AIH7Pq;$.V>g3&1n8blZ1k"[=om(r5h9BhlV:)(VIb+$aKL1,6fY-TNT,34VIt[.3/VZ$!j>L!Wn00FlI9#Gi(&-)c))W&TK)N+BH[eFTV5]?W"O[($F.`AZKqPWC]m$p.se2:Ua'3?ub`\X'rZP/YMEY#>G?T_"ROft;V^hJi.OId8A%U68&X)">716j)\IVN]K92&8[5>Z9D(C`f;>+p"Z//H(rJWs)enb='Ji+u-3U$K[J1=%"q#f:CB!#Pu-WDYkONfIIaEo]%*V9/-c"N#i!U0R6X&r)dh&>u_ZdHIjk7>^"i=lL)oiHZ]h-bq*#=gS<Y-oD(NY/=p<?:GI6T<$j%^=9W\^%jNSm*/XQ=N2MU#%SIr^$XFa+J[(#>S4K/QTrLQ8]c)!qf1:Z[&=Wm))h3'e(XTn[[(j?+%kQE?M\W]Qk!%M34aO4\%k=WXqPO91D7o4#9an%Rs+5a,7TF"]j7R["'5:UGH*g=gI2TmE^L_6<0Xdj<NJ/<ESh]lNY$*F&p5>#$m5Ge(k5Z)48do\gW$4nYfLgd_7>Q<YQH!T0_+3mO]9U["3j7$Y59$)/'k`@1V9IKZ6%t\Wps4=",K,d5YjhT*F2rX!@SM?tYuPbe_94:1FU+B8_LFS?[22QIQBO_6G;,4'k4=2S\I+I"e/URhK3=.<0BGL<g,3Nk)e&[l`'%d"N<?^narI>1Wrhrl-MQ2K"&SR_ATWJ!K8Pcnbr,0Zn4`;)N5-4nD2Z\fA^NXibjbh?k(<A>LJ2LjpYt9<hA(\?q!QB=K-n$;SZQ!)ij"rl6)H>Q@mld`'GfI\'bCb8PdsO!JPPs[_K9Mqn;VJQMhIp!r!]IT8b`=0W*/)rEDA\=02^[)+a+4F0oU3)/!DC1;O"+K-I_Y@\&V(0^8hdBW$TME@bSVd"Z'M?_<`3Y61uS1P>ZLo:iGh.)tB&6o<qeD2XgrDl3Ma_K,in>)jG7Zfh-V^?3qrFK_XiZ/@lcg]@@RFMSmJeOIP\XMF#o6'l]GL8'"+`oBVLko-NBW`-&T+J9b?>]uP7+&&e)/"V<Q2.n$B'ka@4sjq3O1Or!Y<[@c?=)3YcqR4D_<oYc4TcK.IZGF].<56"!hA@XM"bW@i-E\YF@5K)iWjr0<Ds!F"^Ch`.M)3k?(^.obITC@Z(b)ojYrTB2oZ8\SVrntDo_^pi:]I$Oqo%oIoXW^RVXVo$WXi><;^Y9T+lS_fACo4WmAF='U!fd3\#rVDo~>endstream
endobj
32 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1671
>>
stream
Gau0D>?BiC&:Vs/R&;!K]-rgng%$Ash5,n4]+Fg:qOfel0l_$]G(B;<GN:hB2teM9^t*Apc)8tQZPZO:A``r"!H:)OiF`@R_rq,g+9G#6E%r:/4Wk7-'brtRkpk9cU)_:'!SMQ+j']iR_ARP`qN>en]"g=G4calM)0kp9+FZK1UJ`P$:X5d%GN/ks?0VK$%WR_4+:+=X[lpb]!Xhs.&HXQZ5U=;UB#iMrQ,W)'[t]jLXS0r77(Fo`^_LjB/$R1uNip.qE=$!9WQBcqP!j7jA1!ZYE6l'"-^Vk$Gol+%RD?@Y1/X2e9O)sqd3FT6^9K>Y,a<RmlsH/#(ko.ST&dp`>hT(YVdZZhi1?be88emdXeC&=&:AM'(2WD57R:/J]_$:?R3U,1EIubqAGC9:`rk=)RdKsedAb4qM^!F%ftX6RnJ=0SR:%)0K<Kg,L*GCgb*ie\eh-3Hj)?lSV``o1b-!YeLb3[GYK+tDkkmYfQ8I`g&aid7O%sdZ2%-PLKn46_Q&3!AiqiXhN(pfRL\3C)?(mE!k$lP-LIgA`E,=i5G&T5=C\nCblXNRl410g!D]-CUrL+qf`LZ(uUH%'j^$krR@?5?7.opu1`@bN^AS\bK$68taR<b:ARPe!Y,d#Xo5GX2OVhpY0Kk;R3%1UO3K_e%7Pk"@`6uOX*E^Z8R0V,:f#EqdqB$<W6OL@*o[hq)e'aaua^7nn6ilJH6(kq!Y^.e3%k@PQ!+f7G$j?*-rC$AnOC*qSKR^;'82]5R._u7tcol??LdNliAr;OT;/4]qe%RgPq_Ap-+'pS0<MVE0gNet?[,86Z`7j8`-A.k;CSXH&HpXEO)a$\M;hL<O>5(d2c99rS@%V@%lK^j/:pJUG,H_%$$]e!d55Y'4(#P2@1ALAt'fE2l'UN-V_$KF,TiS+Z*JHj7$%NMoi%\a(0bV7RGlh\eJh]99p_3>F1I^O'r7u1'c:5Ybl1K.7(R4^0?dS`YR$N<27*jkf]GcdUX3nqpTI3_1M0:\=L02)C>Rhs!;+i1jB97\7r!l]*QpGa9&-'<gPfrp[.?R`7L0*Ck8\J6-CmMrXR#]=FSen^q^WbJYHYKIhO<fcmBYh1l(f;!RL%r.)1$^R4ce.]M"/2[mrPq/R2^gG@4S*;pJeLF]1D4s8pA"DnK./#SL;k_^tPj(UR:P.Df]("2bCp(,@X26nuDG"&K9!NeCrX"(>LcSsb"=I&W0^;jS%D%hU4.mDTT\tW_T1G2Seh";YD1/A#h9?[:W%iasc-8I7!-Jdpi_>^#9STL;9st^Zm',8N`gCV?n%_:$9@7$I]FU+!%SP/_/P.:ddO9AI%.W-qAS)Qe-7C"f3eQ]#h&tH5aPMKcm-5@IP4JT>cV66I,_\(3U>50k,V?kY?8$1WPk)&s8rG,,6=dGr-HIZdJ_Q*`MmLeY^t6cqgP["uAM8#srVU2JYnOt5^tCD9X7-7OUfsd<m3eN6*$t3e0j.KuLR=$h`Z7<cB60*r^?hIV)t2%_o7s;7I+07+jjftGMRtM+=0.nL-Ycc!,RKe1.L_O=PI;N?`lDnfVAM-c3X4@>Zf]C\8LKq7pJ&)i\dp%T`Jh-73/<p$r3m)o'@Ws`7MT?:el*@C,G$-j2&hr=1/#o7*pKXE$01N[1c8Kl\ZI!6SR[C0ba3R;QBl8T@IP@$&_`E~>endstream
endobj
xref
0 33
0000000000 65535 f
0000000061 00000 n
0000000122 00000 n
0000000229 00000 n
0000000341 00000 n
0000000424 00000 n
0000000629 00000 n
0000000734 00000 n
0000000939 00000 n
0000001144 00000 n
0000001349 00000 n
0000001555 00000 n
0000001761 00000 n
0000001967 00000 n
0000002173 00000 n
0000002379 00000 n
0000002585 00000 n
0000002791 00000 n
0000002997 00000 n
0000003067 00000 n
0000003348 00000 n
0000003486 00000 n
0000004518 00000 n
0000006380 00000 n
0000007762 00000 n
0000009506 00000 n
0000011194 00000 n
0000012460 00000 n
0000013852 00000 n
0000016000 00000 n
0000017638 00000 n
0000019995 00000 n
0000021689 00000 n
trailer
<<
/ID
[<dbf6b6212623f71a2823abf47256514b><dbf6b6212623f71a2823abf47256514b>]
% ReportLab generated PDF document -- digest (opensource)
/Info 19 0 R
/Root 18 0 R
/Size 33
>>
startxref
23452
%%EOF

View File

@@ -0,0 +1,475 @@
# Les 10 Huiswerk: Supabase Auth Uitbreiden & Eindopdracht Brainstorm
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Deadline:** Voor de volgende les
---
## Overzicht
In les 10 hebben we Supabase Auth toegevoegd aan onze Poll App: email/password login, magic links en basis Row Level Security (RLS). In dit huiswerk ga je de authenticatie uitbreiden met Google OAuth, polls koppelen aan gebruikers, en nadenken over je eindexamenopdracht.
**Wat je al hebt na de les:**
- Supabase project met Auth ingeschakeld
- Email/password registratie en login
- Magic link login
- Basis RLS policies op de `polls` tabel
- Login- en registratiepagina in je app
---
## Opdrachten
### Opdracht 1: Google OAuth toevoegen
Voeg Google OAuth toe als derde inlogmethode naast email/password en magic link.
**Stappen:**
1. Maak een Google Cloud project aan (zie Appendix A)
2. Configureer OAuth credentials in Google Cloud Console
3. Voeg de credentials toe in je Supabase dashboard
4. Bouw een "Inloggen met Google" knop in je app
**Checklist:**
- [ ] Google Cloud project aangemaakt
- [ ] OAuth 2.0 Client ID en Client Secret gegenereerd
- [ ] Redirect URL correct ingesteld in Google Cloud Console
- [ ] Google provider ingeschakeld in Supabase Auth settings
- [ ] Google login knop toegevoegd aan je login pagina
- [ ] Google OAuth werkend in je app (je kunt inloggen met je Google account)
**Code voorbeeld - Google login knop:**
```javascript
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: window.location.origin
}
})
if (error) {
console.error('Google login error:', error.message)
}
}
```
```html
<button onclick="signInWithGoogle()" class="google-login-btn">
Inloggen met Google
</button>
```
**Styling tip:** Gebruik de officiële Google kleuren en logo voor je knop. Google heeft richtlijnen voor hoe hun login knop eruit moet zien: wit of blauw met het Google "G" logo.
---
### Opdracht 2: User-gekoppelde polls
Koppel elke poll aan de gebruiker die hem heeft aangemaakt. Zo kun je laten zien wie een poll heeft gemaakt en ervoor zorgen dat alleen de eigenaar zijn eigen polls kan bewerken of verwijderen.
**Stappen:**
1. Voeg een `user_id` kolom toe aan de `polls` tabel
2. Werk de RLS policies bij zodat gebruikers alleen hun eigen polls kunnen bewerken/verwijderen
3. Update je app zodat bij het aanmaken van een poll automatisch de `user_id` wordt meegegeven
4. Toon in de app wie elke poll heeft aangemaakt
**Checklist:**
- [ ] `user_id` kolom toegevoegd aan `polls` tabel (uuid, references auth.users)
- [ ] Bestaande polls een `user_id` gegeven (mag je eigen user id zijn)
- [ ] RLS policy: iedereen kan polls lezen (SELECT)
- [ ] RLS policy: ingelogde gebruikers kunnen polls aanmaken (INSERT)
- [ ] RLS policy: alleen de eigenaar kan zijn poll bewerken (UPDATE)
- [ ] RLS policy: alleen de eigenaar kan zijn poll verwijderen (DELETE)
- [ ] App stuurt `user_id` mee bij het aanmaken van een poll
- [ ] App toont wie elke poll heeft aangemaakt
**SQL voor de kolom en policies:** zie Appendix B.
**Code voorbeeld - Poll aanmaken met user_id:**
```javascript
async function createPoll(question, options) {
const { data: { user } } = await supabase.auth.getUser()
const { data, error } = await supabase
.from('polls')
.insert({
question: question,
options: options,
user_id: user.id
})
.select()
if (error) {
console.error('Error creating poll:', error.message)
return null
}
return data
}
```
**Code voorbeeld - Polls ophalen met user info:**
```javascript
async function getPolls() {
const { data, error } = await supabase
.from('polls')
.select('*')
.order('created_at', { ascending: false })
if (error) {
console.error('Error fetching polls:', error.message)
return []
}
return data
}
```
**UI tip:** Toon de email van de maker onder elke poll. Je kunt `user_id` gebruiken om de gebruikersinfo op te halen, of je slaat de email direct op bij de poll.
---
### Opdracht 3: Eindexamenopdracht brainstorm
De eindexamenopdracht is een zelfgekozen app die je bouwt met Supabase en AI-assisted development. Begin nu met nadenken over wat je wilt bouwen.
**Schrijf het volgende op (geen code nodig):**
1. **App naam** - Hoe heet je app?
2. **Beschrijving** - Wat doet je app? (2-3 zinnen)
3. **3 hoofdfuncties** - Wat zijn de drie belangrijkste features?
4. **Supabase features** - Welke Supabase onderdelen ga je gebruiken?
- Database (welke tabellen?)
- Auth (welke methoden?)
- Storage (voor wat?)
- RLS (welke regels?)
- Realtime (waarvoor?)
**Checklist:**
- [ ] App naam gekozen
- [ ] Beschrijving geschreven (2-3 zinnen)
- [ ] 3 hoofdfuncties beschreven
- [ ] Supabase features opgelijst
- [ ] Eindexamenopdracht idee uitgeschreven
**Voorbeeld format:**
```
App naam: StudyBuddy
Beschrijving: Een app waar studenten samen kunnen leren door
flashcards te maken en te delen. Gebruikers kunnen elkaars
kaarten beoordelen en favorieten opslaan.
Hoofdfuncties:
1. Flashcards aanmaken met vraag/antwoord
2. Kaarten delen met andere gebruikers
3. Quiz-modus om jezelf te testen
Supabase features:
- Database: tabellen voor users, flashcards, favorites, scores
- Auth: email/password + Google OAuth
- Storage: afbeeldingen bij flashcards
- RLS: eigen kaarten bewerken, gedeelde kaarten alleen lezen
- Realtime: live quiz-sessies met meerdere spelers
```
---
## Wat lever je in?
1. **GitHub repository link** (public, of nodig Tim uit als collaborator)
2. **Korte reflectie** (150 woorden): wat ging goed, wat was lastig, wat wil je bouwen voor de eindopdracht?
3. **Screenshots van:**
- Google login werkend in je app
- Polls met user info zichtbaar
- RLS test (bijv. probeer een poll van iemand anders te verwijderen)
4. **Eindexamenopdracht brainstorm** (mag in een apart bestand of in je README)
---
## Tips
- **Google OAuth testen:** Je kunt je eigen Gmail account gebruiken om te testen. Je hoeft geen "productie" goedkeuring van Google te hebben voor development.
- **RLS testen:** Open twee browsers (bijv. Chrome en Firefox) met verschillende accounts om te testen of je echt alleen je eigen polls kunt bewerken.
- **Supabase Dashboard:** Gebruik de SQL Editor in het Supabase dashboard om je policies te testen voordat je ze in je app gebruikt.
- **AI gebruiken:** Gebruik Claude of ChatGPT om je te helpen met SQL queries en RLS policies. Geef de AI je huidige tabelstructuur en vraag om hulp.
- **Eindopdracht:** Kies iets dat je zelf leuk vindt om te bouwen. Motivatie is de belangrijkste factor voor een goed eindproject.
- **Foutmeldingen:** Als je RLS errors krijgt zoals "new row violates row-level security policy", controleer dan of je policies correct zijn ingesteld (zie Appendix C).
---
## Appendix A: Google OAuth Setup stap-voor-stap
### Stap 1: Google Cloud Project aanmaken
1. Ga naar [Google Cloud Console](https://console.cloud.google.com/)
2. Klik op het project dropdown menu bovenaan
3. Klik op **"New Project"**
4. Geef je project een naam (bijv. "Poll App NOVI")
5. Klik op **"Create"**
### Stap 2: OAuth Consent Screen configureren
1. Ga naar **APIs & Services > OAuth consent screen**
2. Kies **"External"** als user type
3. Vul in:
- **App name:** Poll App
- **User support email:** jouw email
- **Developer contact:** jouw email
4. Klik op **"Save and Continue"**
5. Bij Scopes: klik op **"Save and Continue"** (standaard scopes zijn voldoende)
6. Bij Test users: voeg je eigen Gmail adres toe
7. Klik op **"Save and Continue"**
### Stap 3: OAuth Credentials aanmaken
1. Ga naar **APIs & Services > Credentials**
2. Klik op **"+ Create Credentials" > "OAuth client ID"**
3. Application type: **"Web application"**
4. Naam: "Poll App Supabase"
5. Bij **Authorized redirect URIs**, voeg toe:
```
https://<jouw-project-id>.supabase.co/auth/v1/callback
```
(Vervang `<jouw-project-id>` met je Supabase project ID)
6. Klik op **"Create"**
7. Kopieer de **Client ID** en **Client Secret**
### Stap 4: Supabase configureren
1. Ga naar je Supabase dashboard
2. Ga naar **Authentication > Providers**
3. Zoek **Google** in de lijst en klik erop
4. Zet de toggle op **Enabled**
5. Plak je **Client ID** en **Client Secret**
6. Klik op **"Save"**
### Stap 5: Code toevoegen
```javascript
// Google login functie
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: window.location.origin
}
})
if (error) {
console.error('Google login error:', error.message)
alert('Er ging iets mis met Google login. Probeer het opnieuw.')
}
}
```
```html
<!-- Voeg toe aan je login pagina -->
<div class="divider">of</div>
<button onclick="signInWithGoogle()" class="btn-google">
<img src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg"
alt="Google logo" width="20" height="20">
Inloggen met Google
</button>
```
```css
.btn-google {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 20px;
background: white;
border: 1px solid #dadce0;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
width: 100%;
justify-content: center;
}
.btn-google:hover {
background: #f7f8f8;
}
.divider {
text-align: center;
margin: 20px 0;
color: #666;
}
```
---
## Appendix B: SQL voor user_id kolom en updated policies
### Stap 1: user_id kolom toevoegen
```sql
-- Voeg user_id kolom toe aan polls tabel
ALTER TABLE polls
ADD COLUMN user_id UUID REFERENCES auth.users(id);
-- Optioneel: geef bestaande polls jouw user_id
-- Vervang 'jouw-user-id-hier' met je echte user ID uit Supabase Auth
UPDATE polls
SET user_id = 'jouw-user-id-hier'
WHERE user_id IS NULL;
-- Maak user_id verplicht voor nieuwe polls
ALTER TABLE polls
ALTER COLUMN user_id SET NOT NULL;
```
**Je user ID vinden:** Ga naar Supabase Dashboard > Authentication > Users en kopieer je ID.
### Stap 2: Bestaande policies verwijderen
```sql
-- Verwijder oude policies (pas de namen aan naar jouw policy namen)
DROP POLICY IF EXISTS "Allow all select" ON polls;
DROP POLICY IF EXISTS "Allow all insert" ON polls;
DROP POLICY IF EXISTS "Allow all update" ON polls;
DROP POLICY IF EXISTS "Allow all delete" ON polls;
```
### Stap 3: Nieuwe RLS policies aanmaken
```sql
-- Iedereen (ook niet-ingelogd) kan polls lezen
CREATE POLICY "Polls zijn zichtbaar voor iedereen"
ON polls FOR SELECT
USING (true);
-- Alleen ingelogde gebruikers kunnen polls aanmaken
-- user_id moet overeenkomen met de ingelogde gebruiker
CREATE POLICY "Ingelogde gebruikers kunnen polls aanmaken"
ON polls FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Alleen de eigenaar kan zijn poll updaten
CREATE POLICY "Eigenaar kan eigen poll updaten"
ON polls FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Alleen de eigenaar kan zijn poll verwijderen
CREATE POLICY "Eigenaar kan eigen poll verwijderen"
ON polls FOR DELETE
USING (auth.uid() = user_id);
```
### Stap 4: Verifieer je policies
```sql
-- Bekijk alle policies op de polls tabel
SELECT policyname, cmd, qual, with_check
FROM pg_policies
WHERE tablename = 'polls';
```
---
## Appendix C: Troubleshooting
### Veelvoorkomende fouten en oplossingen
#### 1. "new row violates row-level security policy"
**Oorzaak:** De `user_id` die je meestuurt komt niet overeen met de ingelogde gebruiker, of je bent niet ingelogd.
**Oplossing:**
```javascript
// Controleer of de gebruiker is ingelogd
const { data: { user } } = await supabase.auth.getUser()
console.log('Ingelogde user:', user)
// Zorg dat user_id overeenkomt
const { data, error } = await supabase
.from('polls')
.insert({
question: 'Test vraag?',
user_id: user.id // Moet het ID van de ingelogde user zijn
})
if (error) console.error('Insert error:', error)
```
#### 2. "Invalid redirect URL" bij Google OAuth
**Oorzaak:** De redirect URL in Google Cloud Console komt niet overeen met die van Supabase.
**Oplossing:**
- De redirect URL moet exact zijn: `https://<project-id>.supabase.co/auth/v1/callback`
- Controleer op typefouten en extra spaties
- Wacht een paar minuten na het toevoegen (Google kan traag zijn)
#### 3. "Access blocked: This app's request is invalid" bij Google login
**Oorzaak:** OAuth consent screen is niet correct geconfigureerd.
**Oplossing:**
- Controleer of je je eigen email als test user hebt toegevoegd
- Controleer of de OAuth consent screen status "Testing" is
- Zorg dat de juiste scopes zijn ingesteld (email en profile)
#### 4. Polls worden niet zichtbaar na toevoegen van RLS
**Oorzaak:** RLS is ingeschakeld maar er is geen SELECT policy.
**Oplossing:**
```sql
-- Controleer of RLS is ingeschakeld
SELECT tablename, rowsecurity
FROM pg_tables
WHERE tablename = 'polls';
-- Controleer of er een SELECT policy is
SELECT * FROM pg_policies
WHERE tablename = 'polls' AND cmd = 'SELECT';
```
#### 5. Gebruiker kan andermans poll toch bewerken
**Oorzaak:** De UPDATE/DELETE policy is niet correct, of RLS staat niet aan.
**Oplossing:**
```sql
-- Zorg dat RLS is ingeschakeld
ALTER TABLE polls ENABLE ROW LEVEL SECURITY;
-- Test de policy met een specifieke user
-- In de SQL Editor kun je een andere user simuleren:
SET request.jwt.claims = '{"sub": "andere-user-id"}';
SELECT * FROM polls; -- zou alleen polls van die user moeten tonen voor UPDATE
```
#### 6. Google login knop doet niets
**Oorzaak:** JavaScript error of Supabase client niet correct geinitialiseerd.
**Oplossing:**
- Open de browser console (F12) en check op errors
- Controleer of `supabase` correct is geinitialiseerd
- Controleer of de functie correct is gekoppeld aan de knop
```javascript
// Debug: test of Supabase client werkt
console.log('Supabase client:', supabase)
console.log('Auth beschikbaar:', supabase.auth)
```
---
**Veel succes! Denk goed na over je eindexamenopdracht - het wordt het leukste deel van de cursus.**

View File

@@ -0,0 +1,200 @@
%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 /F4 6 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 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/PageMode /UseNone /Pages 15 0 R /Type /Catalog
>>
endobj
14 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260422141611+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260422141611+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
15 0 obj
<<
/Count 7 /Kids [ 4 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages
>>
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1725
>>
stream
Gatm;?$"IS'Re<2\AJ_:Br=r_=gI/'[8<MiG7.+n07?]6OdJ+oZ(6X:YMUilEP@mm=TUr1`KKZ"pRHRU-oh!g<hJ=Ri3El++"0qir?"9biR#>jX+'55LU^)%*=g"&T(H81dM4=kO:OE%/s\=:N%to3+:.Rm#E08@pSe\6L*+GV'$Su)Nf]F_P>U9BbOqA.T&TjJ/od_L^^1Z2YVYS;&sp32i'-i1TO*-BYsSZ<E"h+Pddnjl6&EKaO21r%_]51SjgPA=SU$OL-5dA5Sg322cB1@O7uS9V3;<T`#]Tm9=/q,:H:/*uW'>k$5I^C=nAu)]&WVfAi*2W8E)a[V@m4.&T3`4#>a.:$p\I`7-Ca5+4G*Q.kTAR+/,OBW0l_\Qru55DFl:_*/nR-dfDC"o_`1X]=#To49JoAS3Dpt!Kp21RM_%!,m(6Vq[f_W%6)b'F`fc*'KLq\6gc/D,/mKU1lf'$i%Kp,X8\uA*^j1H8$6QM-S;[4iILi^C'a$GQpHAE0Bbi^PZ`o=Q^^.Xr6eZgddqB-:.mgTt(P#8#:ohX92QiIL"R]4jB8sP?KjcCK\f`s=2Ta#,/KJIG(pHUqDUfl!oW/X6bPlY`gb(g6?7EXjbJ,H8<W5F8SRnhmOu?fD@QEATkg&02X3^Tnq892kKJ+,_29,k(_E@bV"7^1P2ML=pL$/K6n?kr>XV[M^Jl?CH=6mD&:OLpq#XNbP':JqGE<X@5DJtuCJLWK]9Zn#qMp"Dhj%-*;(L4pnA?mUb[P]^Z'rOK7o#^3>/NQHoT=Bj%X`eG+poKmfR0_N29h!R20J%qV"d>Xoa*kPl[)q+(nTb;AY$J`6gVQg2-%OE(,ei"a_;`K!n2Z^p9X+anjrG/tH`a"f:A&ZNmu>F4ddUq5+f\j9U5Kcm*VZd\`5jrB8+mq@2+=.Q7q7;+R()bPm\Y36-LB@#F'fOrna-rDpGnB/H[K%gXqX(g4MgOjDGCL5QT!CK);e`QrrkK6hKWaG]W=$JgIIf!`N?Fh-!cA=PDf+4m[VO%6>C,Ef;Y$m27/Je%+&?nHCZK5N?7@Tr.mV;'MSA!iJU8?\B(ssck@-AB:16,RE6[E%/U"G,)oNV30`/sB@*4-3?J;0G[(6g_#SHUG@h%<[e6>l_eC0Ys2%,>`UIh7!22i$$)>IV@Ar+^[N\cY$[j3/<>fS>2VtqLldYHH\KS6e;&:oE=f)j#@'^a95sC:K3\O5ZW)V_6A,Ket&K=H>,kZ)0[^6SmBu(D<#572XTnmP`oNd^tMFoEg-";P=^X$+,EJ&\%:^Obanj%VQ.??#=5OH!r.pY^M*RqZr)XAY`!KQP!jk%=SXm7^^qaaTHPu*Rn8tIVrTe@H,g#%eggVR>r*$3+2h01e@gCA3?A+Q26.]VnH]IrHE"Z4Oi4L?&3Q;buU$>irEhZLMkg)e>@(>/-U`Y$&4PC_-CKKlp5`C5`WYITY#9l$3/"j]#E<q5Nl(gFtla1,>]cp%l53Cft,'Es:e4#/cdPq'pN)b17$G2:%*.*CKZ1>&nF?AVAA["ZCp0)(D(U(Sok:/tfN=G(l.h^)kQ2/[&fNJHp8)TmcQ(ra8Kio-g>;KmVV?UW$B]JJf5GW<r*SlR_t@BLi%2HmQLN=9cL@1qAt.V4Bhl4U$`M0`A0BOt=*G&sNKWYc2(b$1AX!Oj1WGqbJQ^nmIE.dGnrgEmfl7VM3%Dd\OUJ:4Li1(sA@c`II6rrKDpHjB~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2332
>>
stream
Gb"/)lYkN;'*"Q<@^*YBDR!WgVl0UiU"?tAp580RPAYq=J:f%%[UND38ofO>UAt*#8NlE"Z'.(*"A$nj&h2-'2kqNr$5*92r\p;Kr::a8?;mL*NmE4_,2i5Zr9JUrO$1@T6q7:aQV&b<7!$H_!S(_a6t/k(_C/>*'1l:W'ei*V*7G`S"(s:\8CGr,OjoiYPu4NDJR4F+71(I)KPRPQ&12,N<Wm`.j?*J('BkWFs7+>TO`6.NieV9EVuTC\`4081#Kb>I-s;rgi+T=%;EH*:U08Lm5]Mnj+p@1eP%94Ze7j^A[.RBOKe&moe*)#dU]5`:r>iPY'2rOBCCBpe<$n(mE5G]c0Al03XR!N:bSYde(T+Ep\LF$2E-r/90:_5@rdn!(4MJ]4]r?QPn915OQ_p2T^;$P/]M*mY0_u5'eSD0g9k+bIFCgCCG(HHo&i#*@f6&ko&5M`2CY"'Q]4pnPC9gun[_R[;\4W<OoPdNU2&/**4,O]-C#mXVJdRWi*!CSt7:$&]_)>u#!l^4u#&T&@4Y7'D,&8;Onc]5'i0lZ&8[MlgiLg1N8L/TJ"pi@*:J%Xq*l\8eQ%pB&Yqjoc)/_?upG3]XlNbC`Y\6`Wfb6@f(N-6Y+"i\b.>''sIKAQeO/<n8fY&LI#?AC*rVeE[e#ajfJ@eKbD&dL@CdNi":e:jDe>3hA+lb;9BEs[u[J:jt^KgjMVhAgMo]j)3-g5#.n/?QVdOQV,I,A$f5u53EluEMmb+:\]VJ/%Fo/*!q)aM-;<tAu@"Zfqf-U9mk5dL[skRT>5bnM"O&CWYR<8cTY@*3A+A?s?70JO5>R`_"4Hj><t9bR*J'>bh=_YF:"pd[-c]c01aHk2XIr-aS?)uHUDN-DL,e6gn7[Y;6oc8d7O,Ys^'OlBV.&=g3X`hqf@+.F`F4I`4S(?J'07=(ER#(4Cn:"/$Xq@5KG@Z4%Z<6lX8cK2s\H!q_^Au/cuA$J8/^n7lI`SnIV\FBCMgETs<ZqNXUjGt&7l<thu[-$ed`d'N;^eP[R3#U]"Cg">%#(_f:3R"a"a2sW$bqb3<:S.$0IWr^T@ht*Nd"L^$#h:U'mP>tI2K3)AF_]^Dae)"=AG)6U+[mZCJccg:7Ah*2:\j>j>S.a.fOpi7$'>YPN@G8Njr%$bZXKq?`cM<OE(;ep(#`-VJItV>6%g1faM;)>Nj0Nk"(gP=B"iT$r+]<@QcNsU,JU2O3aBAlq3;?qn%6sfZO3I&A).@%4j1Ka]ckLB3#filE:H+OJ7$`_oI-d2`T@cSW]2;V\94Ad;1aN-Y6F-O/QcB6P@4=M&r9/M;Q(7fde_9Fo:iJS:`f4fX'5a9`>1XMb;*jR>OH*'kEGIBq)NAj!QldbPNVFHX0j^o-YkNnZS]9oquYoc8Uge?<ORL_n;lE!VjD+H$dNnL>ET[n()^B99A%@"],Pur<,Rq>XbTr)':^rF>2M\l2XrMHp=!'YN89\*]5SUd%sPrRde>#jC:CRjp(/kJEbmC#@C+H46#;fr);M)Z,48$^YM5l:ke<ito,_+uU[ALQYY@aaErYQ,c-LQGe&f[DkcXcumgDln3:\1XlKHpT]@]n#5&72'R@Cc_-Y@^!f`)+`o\L.&q`]jX9isaX7\A1YeRNo-Ye)7p0u6>6LRpmp*g(B1,S0KP:XY5N:XT]*n5S3lDEF#ANJn)@mS/>P1)ejYknNTBgsYZV7Be"-@O=NqhKP@M>o+\dXP(Y12tS6E]d7Zr&9:R$ggbaCI<002USHtt+tk=jjDB7Sg3et>NB+,,q$aJh[:a,tQCFW;WFpof<nM-H?d&oDgK3!I]2k2=Ml+LaoaT3Fb-Pe\jQ@i;puPG49:"])f%$G/#3H]upV1`I\oQZ#E&]GtjeZn]VtdH,s21<J]Pqn<]J@,m,h"^Goo'Ch[XP#ZF2.#;HJdm?&\]Q5?ZasEI&]oK5;=t+=C@2.?t"t&o<;pr*NJq83k*mVn=0/Ms7QeM.:tjNImS\^9L>gT"lnAADB=D=nh#p<J$%I;q=B9H_'$;p@=Sb']K0YQVupm0kV-Hq+7[*^jgJgSG!T:GNXS@G*9Y`aT-?21.(BLoW8"9X4$H%uci1d\Qh`Xb2Q#8grMC**oF4"N?-'1[n>O0/-XuZ_Op/P*A?*-*KIMK0XdOEh0+h(]bJu<CKq5h3T.ZAEG?Bu$E=3L+r@KLBp#[/5)1g-%^JpjQ+5\/#D-Z2$iA<d>pq+oI=K=Lt\14]upgo:^U5sMRJV06'mkub`cEd4?>P.<[E<;aJBS#E.*eHQ#109dgs7=`+o`>6CjTOKW^s92$Xg>"u8Jeg.>E+El3&ej*<4pkf$hjQ(n$Q6m~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2220
>>
stream
Gb"/)=`<%S&:XAWR$XObdBL80"k\SOkr(uW/+2h`JV>;sNac.3,rn7qoY9jVOt7upiiQF,OgKXM.\)./mi)a.1SFN8\^+pjn@T!%?SPq.H:))'8EcV[;6TT%6NdjG.Y.l%I4^!E6Ld<-53eplm'D4]`\j\[&ah(WS.k:8b_+]dn8X3d[Cf"pmrNksVKC2-9+kp<JMBHDYC)Fb.i-bYQ+W1;+op+kaTn+/a$2XR_%eHi..O]W&elRT$dE)]MW5OeYja8rdnW%uh[VCa\fN)YBTDpSUdQ<PP+q`'=V"A[eWH;)&XrL;HISELp[,T-No>+=1.?;VAI3`6Etb+2d-'2:B?(8'MBXZF1OthSdNmLf@aafZoIcg2fU?-n00-q$HT1ShQi\+Yf<_us1ASH.q`Z"=BaA06d0H2@\Ym%4e<>i!-p=)/4-@b9dUR7fH$u((:4TXY*?M0XNegLi<aA:iQXqVa.1?W<dj\P@##$d10[5a`cA`^-&;KGj9YtaR??:(nMh2SaP]Lhe#:emWDEA%0Z5N^]!:?Ypqr6C!irnNIhG"6#B]k3oY4kd9A>pAqp*A?(]Q/S75?A&i&31,G:70T?>tI.<1H8`<L1c?/:e)g"8N]H"9'e@MfFhP3`NbP5p^'s;ZOJ0\opucAp\hNP+^4YN,FpBuCQu25QFbE"7&Y%\8=#4^CG)TsOH"6h?e,+U#BaO6i#M`F$u+mU8I%oKhNC]==.:iJo5@Vu\Fg8D&2mb^H6/H-pC^Fn"-oP0_JDh00iU8c/o)SME:*TD0HY047khf-k/^Ikb?DZ@c\c0HXe*7iQ1=P2L@2,sq*4Db\['<#/r[+^Rm<C0Oh&DkJ`S!'rG';WbKaRN/C'5HT\l<bikZ9:n'L,BNA0OKZgsVgRmmA)&jpOH\7LQiM%J1KB?K%@$NK?aBu.E</C.$f+q"V*Z;c?/98m!!Vi`8<$M:roE!-ue^;V=5=ojEan27pNGN&bV6dc`&pRJaWS.L&+`2sE>[C+c0P>fYZ3;RY$Yo-rD7o57dWMh5.R'."kVai+)07(51>QpR0WU>uL*<itN*<D0^O<be(!\cBBq(*Rs+Tpb-R[OHL\3*I%Z%MuEgXDpY]0Y:k#,u#)OM@9H=2,p>_^)K)I8JNG@@4[nDTe;[F+.`f$%bPBVtVj:oo=2^]W@0.mLapr&[24qLqbeB<IA4J*=M_1=P<$HH42jP@GHM7DM$aB9t%CSVGencfdndp^PeX"")%--S=?gdFY8dtVCmF;:BMJjl=Bpd#'Z47rbmts/IfiR9?UUZ*L;fC>V8QS&pI?`me/prJ)9.>D]^`>W@kM.5;OW(e2KD=s#E4iaOQa,)m1JD;*mhTb[V$pXX0BY,a1P.:8R,E-:L06a/G`5P#q*4dD"OU>?bR\s6+`/Cu4e5&PW)(,4:B?KPa,U^cmrOZ#.igOuf.r9+mO[9s<hJS_%(Z`*Drnk-LqIo;H[(^p\D3&'dAj%<h%V_4/b.>S>Qr,>u>&qpVJ4-G0a_UHG-N^Cdg7Uq^BX&G:psS#ZV*?8+cgT8K7-!RZI@at4H=nKj0b.82?fG[foOHW?!G-9]7L)O5<.DH@S7-7k.1F2.TtAdU']"L>"T(B$bmD<&Ecgsa%`ZrV)=p)=FW1-ELQ44X:is8U^mNUSEVDkXh29uF<A0Z;e@N(j$RD=kFj$I?[&E*>VGXm22!=bo]cW-VLrR;$h`^GaM9^uTd,s-,3U)D3XCH\s9jk1QQfIHV0%*c@QBk5"n[)Q/C^ZJXJ/+/\.klo!!MKK4fH)HQ_:D4N'bRuFW*XO);LgFTcc%?S/]?>#Pl$VTBd^k_=e5(uRI^"ep#JpkIJA*WUKSha)(gn#M^?'fN=/Ne>d5dK&s9+7f$Frqd9FQXFY3$#Obp$cC\"j+[s76!&l-Y;E-n\9`-2ruU*`<r3^<pU,/*"r-8jnI"Wcn?HK?(Ns\nYZhCmC%[^9c1!!ENMV"'/[__%bjX+kn2](LoU)K<X3cZ'IBdR:$LQB?0U6'Ui]L/bGu$N7ZF.EI_-MkYbH!bYH6cDOR;mS-%@'(59PLc.'N?M[anLfK/p`emZft?Q(2gJII47nhs.H.dkR*WoPhd3N;Mk04ft6-ET58QY[Ma)3Fq@S5Nc6S!cO%2Y1hjIVn(t_+e,.]#&N,hr[Ua!)6U/>)E86@A'=J;rHUbmKONR122GPRZ(jHGWdd?$FoX274ghGu`J4qFrWQ:jIk:~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2086
>>
stream
Gb"/(=`<%S&:XAW(gp4Vm5BoJl,ZM[DN>K9Cq!-IIE;k6dB[4ank*r8c*-"JF]e06Vs>l8,`!%hPfWZ4jr8&iUu:/Q[Ju"\JCbTVr/LhiE%qDDLk(DEnFiUNGE6\jn/VO_9@nAJ&>)T2#frcIMM$<q&4W&q16@07dKRXHlZq!4>f5boNs,-#c]PrfnB.\OKB7>D!nVFC;GO_TI1V'')ONI`nNIHG!T6)jpn/Q*jaE1\=PtHr6[[L^![K'%j">s58;9/b6)QAeo3SPqDj1Zd>O+f_MK=.f66Ja^,TZ6E"sFJL[A@%Uga0ZkLb>ROs5VS[%rdh'\epI6/:kRY>S"k$>VbHXl%c%_U54_*L[[c?lQhroF]ptaM^u;^@75>%(Q>*JhTGa<FTL!CZ[#e,F?Z\drDQ.)W^(YQqM_"\Oh*DJO#)Bbo[(Lf*SH#&J8590^LKO2SX?6?s3iOCQ-kP_^tUgXofie+adV'PY2Zf:Z[@',e4*PX+M+eUk6d9el?*gWnk<**OJ\&K8"rV6m>V=`%Oj"qlP+5Y15b]L]>8hORm@m,\C*(Pk)0kL\qK\N9FXsPF/6fcWqKPi7mC7)NkfFiVMB828#@ccklccQf/e\@.a>ni3%=7]3[K.*_P9>($dk>nXd\6s36pQb&W@=YFd>E6blZQ,0)Lbc`\>;BRY>a'q>=b6FL"Q4%Ku&U(b3(%VefE9qNQMqg-sj_7B`LZ:$X1MRJIZScSCLVBPUoGi2]"[F)b=;C_Q6g-Krk2f5V^G_u:*:&S=]&kQl@Mi?h;uSR+M;6P\l-<Y`>?'b7a"NPa(**L<r_G%l"2?_r9($?HWOJPK/_Ta`=c1,..VSEIK9_a?3K&$)#KUfJGg%C3*nl"$NT.m\nePD;38Ll%@9?).`$4N:4T7l<Lb$b.mjD19;7m0c,jo4uP,-J7"Y>*SM4JW?^qWBC;h8pP&_993r*G"Fh-hPd^]GhHG+6Xs%B#kQS1&KnW.&"$,Tjg`9^Mtn2k`]`k=i#_BXj0qrU`^JE]Aj7X>JT9m:JGJd+]X:J:,FoNqJIt)\%kq05AN[Be;(=Nefaj(_#T'Z!)M^Yhm0e,o_tM8XjiTK>m/Jup_b#H:"*>0Pm2.iq_i>O`0WVh-odg6d5->#hf!#chRu;KS1cs^M^ucjY:PnVkY_6EtcS-^^pl8KDM(b6,fLXB;`5,Pb<A%;E;r[Ve^MK\23Y._-8Z/b[A9kNTHJ:6oq:\<D-Hs"(8[a81N>'c@^L(7dX4%X"nEgCT]k<_cXQf\9>%J#J\N+EnWa]IV&%KDL+i7tY:rD?OWpk`m_/^<+WXTr&'8Mqc2&&'^e__#%o*TIs-.JhN:24PbjqH1=?r1LYCWp&fdP@0p%^FFR`8F/-@.li&geGf)QK(!VD<[,PmHqQ00hJ/>e&qSZH-IE:Y?*20XLK0`hgru+-NOR'ipu-jZ?RKs?7X&>Rpg:K?PVZCa@nUfr$c3+XCRV-KZkAn_;e@-R)`c[LGcXl7_'b"bCX,;[E:VHIGqqAX'`,d*b_*t0Zrsc.X1:h8M10*`udc3q`tcR2Md#oKK6oU6.@f+i-1[7TnH>\l[Kreaj@-AN=E@BCa.'1f)uq1IElZH=cRUjS3neafhHO&%0W``Rc[Xk"74+9mRV5;&G:>9S,3%rot#kC]K2*?MAk+E!j-OP3lMAG)^>'e+F5q`DdCtJ2+/5<f0!ZS9"u$!0B_<gfCL]o^/M>%#Emk:fh^OCI6M07Gb@;M3\@V;0G+MQ\]N_PdJ8Nal@K]A5i=u8$TC[W0cm>W/Bfa3<11'6r7N+@7&L1EfDEF1;+'+FNd1j2ViqEj$"`+GA#+ZF[+3/Lr^Q06b@>B1A@!IWC<&W)JOTBc@)4HM_4_-]f$/uZY?WL(587$FJTja:TKt(XQnuPD>cHphV<,S(?l'aM:t0"YI]jc!q:sn)B((4p:luR+TZBJ4OV@LPO)L&U=ZA(YTu3-RkH-'t8mR[nj$ESr]OQ`Vh-0N`PC)pugcK83:JTVb'<l*Po)dbGZEm*8X@"((rt:2tef8pOK6T)'jtPm"oMifOnOs\:!^d!;L%e^ciE,f<H+YF:K&46n6g;#~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3051
>>
stream
Gatm==``=W&q9SYkjm;@.4H2_5k&f2c'C;.*7#lLa4>'5ncD[m"IgSM*`=?"IN&8?aZ=go<(qoe\f6'Hh59,gs4$t*B)qrL_T<#iPeli)-mp!hTAY*AnDf&/:)#`/M!Ol1mfEf$O$t&Za[(f>6Q-P+R1EWb&JfUT0Lks_TE4*dDVlLsW'2:_?5PGF1@"[4GFk#>GC6(5-#Vq#^^9UGUqa.s6Tjd+J9:/RaI^/\3$97nO\"Dld.\soNtR5Y5(a_t%.E+B]lse<*7VG+od(jBf68J[ff:2ec;@'lj>m!eC'jD1XYnXLEIAIG>,'sa#64Ym^J"?h9k4i3ZRg[Bb$;s8'#$.;^tIi>=]),lP[eqk(Ga+p1RXPI@!Dpo\!hH#5%bQ_*j!ch,R^7TI&p;PPki"hHR1!g(#\`jI76f:P5<+<4mo&c*^Nd1BOT]qD'\WgP[]kJe88GWS>\I5cc8s?i5h6F4MXC;MhlVVB3]E$m!CR)kd#*n6ej7UMPm!,iL?J4Jbn=B4u#aigKR2ZIZs$kJLlK&^di/*ce;H^HHYdp(Y1klF)>\Dj26oI@FfaDUDIdZd-:\'R(@igk*>,0aW5?J`R##@+#nrMf7r_O(iIJI(DD'$icZSY103X-VD(nlE1;Ym)N/uJX;L0eW[Nh@ROHd[DGEJIc2V++][Usmm4L.WJr(I_nRT6XP!n:SOhYG<90hdTSq']aV4+*`'"*hjUJeje$]1R.hE@&Krfd.[U,e.B9P"-!DdV,76Pm5JkXV/iOA[[qjq#,mK:U,#jY"9\#m4p_"Z,C^n4f.G[Y;ra:4c+<]iBDrY2BoZESh[cgdWF,T]<1j,7kMii,KE]Snm6DRkP8_2:iGmDOT5D/R?TDBRuH\ot6<=?Jo8`9H(coiH-Ff6Sm:+R!<M_Z4FuH]dKCqf+'$&,OdF'1-X7ff7&@KV>#u$<YaKu6DPT@SKljgd%#,imhA7i#0J@?dg<YF::_bUYoP8!J%0XZpCHqkW:`8G__n%[O0,SI#.FFnMsQdPX926Fg[d\gg8b-'qX7uE<3dRjO&+E^OEL4?V@l^-Im-U=\mKrj_rsFP+0-!b`B5Xb<f"LFj/)L2;"$3ndoH*CeL4;b&S/A4An*]LTpe9D8kf'b`*&7Z&QUKJCV)t?ak1BBK'cPu7b*^t:k'^b5;Oj+W:AUFo8]"'I6]rn@6Re*)1$Q(rQTd14BJ+3)sAc&Bmp,I9TG0AM9D_D!'roM-M:Og\qH$JC5J4#JKhs5[N\A0NjJO@(C2:qf,1N]fkVGIh.rt@iH#[qLc^K.<pmalN@gaDEm5:7=Z4*JP[qAl>!R`VR:@4=geeegb>ikX*GGocV(BbpqPI&1ONi+0qZGkYio1s68H'=UEAYtjHIsV^.P3=`bT;n7Sp0EV97.Kg<&A:H.HZFR&YKo95feJD$K#>I>b@Y%s*VOHSj2`%h':W=M\The#fM,>oeh8m^ZEgc$ZSFM#?T.SQM95?Y3Z@nXe[+Eo=K]RHC+K9hXl!'B_J'E-Ua#H',jCWZ:Hp_'G#2CgGn^c(1MJfYItk!`N(+W6UHS'4H+$b7@Q_?J:!6b!V8?!n\Iq\[=,E@Fd(#fXpkS?6`r:2VrMAbFf[.Ac&D"u[,KAA("\PsJ5N+2*E6nS3=9\jlPd)lei?sH3kEM<)7=X"C[9e!],%-9f#Y+!X]r@R*^h:7q!iYHe+=4XRbJAHGOdju[79@A[,4A1SUBbp.p!.RPq.T6&*eoj+W>YI$b_@YJ:8gS@D-WkPE)=S_[GT.OfSN2TKI6sY;lmc%>8%NI3d1H\Z7/Z7u'iMeC/&HDRE4+p,iV5Tpj`iAsq?T-KBQQ5lgRY8#n(8?d>ZlC<DT2:EL,b^`V4B=)"Y_Ui#]VAr*KR`,VY]:ZNq-'G`X%^RSp,<kmt3qn_5Rcc25>7u%;6grh2!#\>0@PSF3Be5VGt*?8e6<Q(!PFc3,?DAN5p*/TtSM0LDoQ<8,7KP2TQ[ik0XRV+Fh;[OT\!k@U[pG\s%@uFX2;jAWl%'mEUM])fQ9T*[Hl2m^1U5*XZA>@\</bDMRbrfP:AT"NY(l:SrHVmI,RE3F</p1T&Nd?;HYNSqq%JBf+6fuHj`Hno=$)r&.(<1m>Ub-iYD8^hY=RZp65.g1*]h>SRM5X61eBY\*-nh`X[;a.2M)L2k[$LY\-$(Xi^-Y2q@PtV;85e=0M]up^0Wc$I(/i2O9StSq\#QH5T3gR[X$!\U<loah:!S)>GLK:ce3YTe(=6eK\V3FPpTVH.-!3+1+I^+/XAfS$)ZWlOBi-pR:3(K$NtuDIFXpp<.-'@A=*-9L#2C%=GF3<ij$ulr`5JFopU8H_$YfKhhHlYf^SC"jL4W1YZqK=]M[&3inh6JKS^#P,`p+qAS\duo\rgP]#D?j`A$*(IrS.=V<tH4jpmuM+hmQ,7c<=pf?1J6\/AI6fr-FmppW$EcS]]],(,KMm1O@I$C1SZuAS`)t)AMS,/g4-H2mr5QK<9;$rj2]jp[h&&bq?V,f-_0ejE[QIFBs4/La_h#V-TgknbdT&<fPjmEFfg$rl[N-NqJuD5Uo/3EL%AC,"\l4+VQ(=M6kkIb_3JbaX^As>!edj5_uT$2[/pE$;2LLFG'Q[e`!Y[#eJS#7o3D(a[hsahVeoiJ*s<A!;dMaaK^;aA^TaY(Do6G\h)d3R)/?IMQrED?AfP&Z(Nf1&6icWc[V69:IT;K"H*V\e\$&!5q4ETOTop,'Yh`M6rtWA_obZAq3nJZAu)Y(amQ2<fA'EI\K'X@P^"lnh4Rl"K3juA46*S5S1rCDHFS?rP'[hpQ,"U"l98'Kop>+R%0)pB;tq\0NSKQVXjI^>%]n\.Rc[e6PtB/K?!j0]hjH\_)2KsubB\)REaj1rs0@\H*0-9niPG!V6&HT4@18\4PiZFA:LZ_N,8"j_cTYq*&?JVIJp)`rq7O0M7dYr>16(dl@0C/LV9JG%=kKJBK&H`[0KejUL'N0546+kS\hZD;qniO)_=3!Lqe]QjS[s\??!H*+l[5g=kH!EbM[3oV&:s_OpZ<!4mgW0si!l_e3;>_!R)K=~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2719
>>
stream
Gb!SnHZ.Xu')t1g@SgGE\%>L/ML+C2!bpXPhX`%-ltl+j!s_8,*$qoOQ/q^`(]Eb\Un2a=a_fSdXsap:12#`._g-6,$3^B+p"Pr<]GncSpCo$.nI8<tE>T4[5Ml9c:Xb/1'+UIKR'ruHB'TYB35i/:&;b8hBT+\[%*-_^AXL9#Am,tX+;SqfC/8nkHA`WKc1&iR@6,,df,U!A5cLpN[UB9iQpDb',GV`1>^$2DAFrVdJ,*MK1LPo1/>Cgp8N@Ve,>:$6be[\c"*&)E!Z:=d>1ukG7l,3,23-[P%3"Dc&LA:t?VlRj$#"4U;Mo@,[NQ6BkKl:Ps(qb6d7bhBD%m.mQ,^\Q9jto+"\42tUEoAn<(u7=4GY+`bXIo5K^g(A?V/X&rgZhbgtL[N'F@S;Vb96WM>09pHf<9JcipbqkTc\K,GZY9O/B;RK^n)pXp'tSl<X\sD<n%V"o\Sja,t*HS3*1UFWD8!E29iLDW$*cHM/E;fX>(f3I%qB#utNt;FdoUD^lW%@T4Q4!f>G`-lC,2W@3V_kt)#fLQoEV#>qE_\:m_^+PcicK[WG[[<Si@;uf*iV8&]W=>j&;S5RN]r@Oqa2\hHmk<p.I5`75*h7,m6+d8fR6]I@^<ZE31JE#+AeS/\7Z+BRd)@KiVk`&JaZ/uU?Q_<PA%P%$LS<(>4(E9H4U+*ICNS<l,VM=6o(5:#r-44KO$%m"I=t\TG2^uT8]I:G_B?ODq,&C>p!:kfM9Lbl#G-#A<AU98Cm!EX87j5O!M`?%PV]nn*mgi8P6SEfprD:)c<.g+E]nbX`7hQZ/5R"N]^DmP5(?p#;priIg$U0!!L'G]G<'V@YLIfX4Z'b?0-V)bls+\r%"Xj:#*/'s^oJX&(Uc3Q*2p5%o(YT#qrWJ?W;YOV,O>t(!6b(%SU@dj0V"Z6SpS9.F^5/Nq/O+OVKJ(%!ceT*(6O!.qMHO3+@M,=rk@RP_Q`DZQNJ^iA;;u>CP10eZ`a=UaU:H,_18E)6=ktJ=ELB#$Af/_r<ih9CWI!m@<b>s<RG;uVX@J1;c@?j-dk,026G1!/AK$`pGPPbfZ7o:`A88_[DIUi2MmqAL?4^[]8HG<Z;PHP_MQD7>o7e0I6]l;C/D)8;D<b*h$Ub_HL8EtV2V)ZoY!fk#@*U(IZ_eeCT*VEg#H:HCUY<X7Ofq9QAJXs0,C!t%qS'5NcqtJoKTmDo)?;R/!&3USiDt\/8$M35&g(c>HgL'7SQ0H(.?uQbDFq-Sabclne#jNb`)DPT$P+,rB2C:J$W9h7`D\gPWE_+6Ps:'kh9l:"Q)A$]=Yp*k;$$GWY-L[5HYpZ.@k:E<H.0;!gle-!X9k_&--4]AiE,ci*I4p)BIF!=[d@=iXeU#<:UBbB,S"mO'$.nTIhm?$Oa3qK12`\<RtNk!Q-W]#`jI,tS$0K\a5=H/Rd!@qVul62ItXDVZs=.)6+3P>:PLk:gK?[BH2_PVeg;CdM_.Pa[Nk\XVJb\u![<Z"(d>j,G4j[VlgMVLadXX%`P+dA/9*<>Qq=L/$DuL=].NRhkn*Q$l3-g1Bp*89NoWY;koe(kg,BdG?fm)_F9jB]S45"\LpA'sMdg#t_<3.=H<6g7q8mNNPf('>1]O?.3@6oNE#9&4:SU0gft+Z-Q6"Ju_u8@R[CJg@G:&dOTVO)b4d,AU>=IV]/Val'M2GDO0Zsprj-AoI)LAVaEKJd^>#k$n'q+YSqh>LBRc`.K7hl3IZYS0%JORi`&%juWdDmCm7$ngWM2;3!:Z4JEU:KWY(K'+;^.>](7"pV_3jp&5H<t((7RLd5f`ZW2!N3S4^sAZsYX:BW@\6tWKEi*mr4?\'Ffk":QqhFI^MijU3G[H/br30MgSf6J[ZP\c[Sc?sCc/pT.#HsZ4hDMeW9R].obH;@aZJ]m%M#:a2PpPNX'C1\e8=c=j/JLlqH8nN6].NKNc'$)@ZQ9(rac>*bkUDXYi!_Re$#%kdE+rbR#]LJ=8t6qB2fj>/&SEg5o7$`qsXEKrNkP`VupY3-^#AnPFb"`$X+4e^GS&QE?!QTYo:QRT77F3#-LCXU1\+!'[Qb2>-:JU:O3Q(a2n(thuk_m(?7,o$aR7Za-G)SV+fVa"iJm0ajIU[0-9f,]0"mhfU&HqOR!!uH6=S&qs?.&SY@WWki.4II.HW=3;b[!m-LVC5FUIj6Ig@9gW=$]ci:Wk0&Q"aG#q*OT&khY>D&Z`KW*aNmW-eSk"n(K4MU+<fIkI)KB%Hh1E*o]r]e$98)k2o3!t7N"`=l:+p)RG?aSiUh]Zl:3&eF9hGrt=0s'i!Qt`/<qOL>!ILPNq(K37-cXqUZeV*J5Yl5=mchFmIf!uB&VFHC'Cn2M"q+XJqTp+GlIL'T#=KVd3%S,'",TiQG6WqV1)deE2I8FB%n93R<4N/G$eHR>]`.RXRV6r;`@AGd]@3ceB0N&j?0EnMM,`8LtU^#^5k[:2MkeP`8%lt]n=lp]E0%<H`+(+_c\k7)\e1WFRli!`2(ZI+:2SF:i3#amS6I+mbpnC?$FT-/7h`8:&Fannt`?esB+81c-BZRAHK<l*#9]XZI.4-\eCIdo[me:SCjd3@SVsZYQB-N.DGR9aolZh[ObQ",S^C3J,9F4u`S@eMIDU"[neZB'hU]+g@DZ+9'5)&%Grpelina.>%ErK$e6efoU9lW+1M&0nh8%`r/".qe_qYj0A1?79(+UGVThO(dj:W,fT8\s85!>;Er'E~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2641
>>
stream
Gb!SmD/\/g')q<+0jA%jDNQUu,uIQXG6)XI2!MPnlSp=/#Z)p)j!^4iX<]1Lr;%@1YAnA=c5CiAU93)\adIo?_r2$GJDqZJJ,MCC+(I8iB#$BbYCW+[K^f'=j3D!P@Sk[M-4n'-N\GuCL;dF3P%c;\aUW@uB3]uK>(kXr!7$G*D.Z-ZiVHi8\A`ib>)pM*jie?T4l7NfTE5rdLuDGGR"S\f<?q)1%Serf5X,!k2kN>2clj/e+;:4W7gN^Y`<bR[i%PCoiu4lNp_L_LVc]dj(?lH/XHAkuN]KF!'/?4,MrFu=&E3gnBW@5)#i\qnd<H/WcbkZ>3/OOgol&hLK_$aWr_3T`SD2CPh=%1SOCiM7n)_4kj1Dj\ZF;O%L%.5N]#!:b$l<e7nK5gd(kn/BO?U`rQLk(79nULh]><\]>,jn=Lh0\c!<QDu.W)OHG5lq9E>iBE<@C_RIKk6<gnF]McC0BBE]H!o,XNb]@\KBU0MS-g8$b5\YA"#d9eaK%Bs_W/r[ju]%FpVh\:JSe+R8kUUWG;KKJ?[R^ZDon(<+"N\Z_se8Z5sADD:Iu<;SDR]XZH#g5g6F/#8kd$3iO).Qj>DE,:-$5$Xl;QYq_r.$"X8GmDKDmdRQk!:C,)fZ(6u%=u,N$rb6f[BM\5N]-L_aQ<?Cqqeit$V"d,T0I/SF4oBja]i:7A._Kf]9dFp1].[tWD(%dn@)2`qs0COEOYAUK\oPF-ZF1U5Pk-SGI@Hc>:tXb^"W/'<[si;Jm9[WiR^V?k`0b22!XPL$bm)TVal5SB(VrB9P*:1$-^$fUa#HFK6s"K"Lf&4aQQ@QdY7Io7YLO763!Pi&R+pb`W$a$`7QR-[&e%m+"h*0;-1U,7L]N!-AQOdB>NsD]F[k;FKiOkAD4I'DXPU+cd9Lg`\6oZ_#HLek&LtY:\N_MKbs0n6]g,OpMZilCQJ3O6@1g5IX2%Gl1\)2RlA?rPQ'bL)b_V?CAI)@^ZZ6ubB&14]!QS'jRSg>0M7_MQr:ie!S"(QW9sogpTZ;oN\gu7qb53G9uG=lhq:'dR//bo\!."k/0sd,`e!B5S*QfCgUk)8bV/_l7u&cniW\Tn`S<H]U?Pi'W02)edKYf@.BQaSc)oT0@O5DdVG:hW,QH&R6(OH&<D$I.>R3Kp+rr4Vd+^$>gnqIb7Gq_4L*^=ud!@%h4FMo+:R:TL77/rt%I%kZ0r>PJJG4=l68>m+W\Gi9N5:$FQ>(s)%WZ_eQ#]s/;U2#VW``+W1L[tK1W+gASn50,S8m-RN")["Z*"dc9;S:\jd,eCT%"$2Q$_[V%.2olnKM0ZbG\'MgU1A.H5J#jT!pm]Ea]O2_8>Y`h!`)sX4:jpnE%'CEg&gDd:F8Lj`VLK'.^>H#%to3;1e&h#BAs=5odR'\Z\H<StO`6K\D0uYS)G9DNT[h/=pJc"Yt]Idn>Osk$1pkVg9cV&8V7/7-`Rh"m4MJhX6Le7j20nGun<%M,(FT1-7u;.bOmAqA:TFICg.G/$l9#cQ^3$7D6KQ[M/8(7e^5F65)?c<,elLSW<uEc225i9BmE1dK-0G*[;cdGT+^3b>B7U0h1'2Q`1oEAb+*CXK\f,OGg5LXG7>B:GFntr8Ag1a5Aj:KUXO)9+MOC1kb;#GuIhGqg71]b4NFITU-64-pK]$B*UV4LK@edVM.73fM8oV@nG#@AX^J9)#H,Bk#/$I0h1WKUr+7V'n!BYN&!qP]g&!PlV:qM!OY5B9SVkqeHXhAZAVg[iDtQ#HU.p?/2:4%+[[)mj6].$H.eq7ft2=e!,om0O'pjY-YTiU_m`"Th3E0\U(Yo^6QA5nkk\A\ZAuT_1C!$e(<+&#:tl4d^q@Zh4qKh0HB=3(qP:C)@<l.cV,K$Uq1NB64Mu4G\hqFL2bOplmG:HHM`V6;]kAUR\JM+=/tl1aPuLi'nc]9ZCN1mS,en4ZaR9rQFhg+1leN=`K0%-k_$`TmN^37Aj8K>(]fu`"ii8jT=VIRh[gL`[J'f@2*KSK9VrL"r9X\Ll>[t&:BRs;#hpnX.g>OM>[1qo&_-Vt-8MqU95UK'm!1N#N3%@m5d<Ll=?FQc7Q*#4^B8D87%bBp4^$HdrIee-toPK,1=Is+>[NL:'%&+jP0RH?e9NK.$O8/2;"g:C@,rBNSGY"hI\1Dim>tC8eWlf$t.C/6U;Pr-$AD)[u8Ya<A<<j9dN=McT%Q?>Z.UD3pOVa@aT3I!q<.@?$rS-UhU#@[pW^>7pY'N<B3!hMh(KfNt*4lP]'prR7gFRbQThL:s*OdF`=[A8\9T'S%k#O(Pn!qq&Ml%,ghVrc/[B,=RlM3bQ6Wu[S&lgVk/LV^a8dPr(W^+mgJ(<8+;+m.(e3/U3QSk)P=I,I]QE&!D6;[&I96C']6NNq@<oI/_ehsorgbo_1_:RNfU$7@]#%4q6%XjDFjpcP#HR*L>KIU>67Sqq0d!ELi-VMp;l48oJ$trS]q3,A]Ml)Y3Z`.GFAME)mCb/c]\!6UneV#5E9*$[njX3.f(XJ]?<uG'1=pKmc4F5F-,IUh5O/nJG\0/9'Wn>*ek>h&+PT^6GQ1"YTmq"<XSR=sZY)[YA)dp,F_ja:]q^./$4c\FXl1Y`:P>)!0P`$Dkj=ilK:Q+Ia0>apN09UF>Ou6=<d.n3WY\8^~>endstream
endobj
xref
0 23
0000000000 65535 f
0000000061 00000 n
0000000122 00000 n
0000000229 00000 n
0000000341 00000 n
0000000546 00000 n
0000000656 00000 n
0000000761 00000 n
0000000966 00000 n
0000001171 00000 n
0000001376 00000 n
0000001582 00000 n
0000001788 00000 n
0000001994 00000 n
0000002064 00000 n
0000002345 00000 n
0000002444 00000 n
0000004261 00000 n
0000006685 00000 n
0000008997 00000 n
0000011175 00000 n
0000014318 00000 n
0000017129 00000 n
trailer
<<
/ID
[<4c88adf75e990bd3ea85826d6e0ee994><4c88adf75e990bd3ea85826d6e0ee994>]
% ReportLab generated PDF document -- digest (opensource)
/Info 14 0 R
/Root 13 0 R
/Size 23
>>
startxref
19862
%%EOF

View File

@@ -0,0 +1,296 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 8 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
9 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/Contents 31 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
17 0 obj
<<
/Contents 32 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
18 0 obj
<<
/PageMode /UseNone /Pages 20 0 R /Type /Catalog
>>
endobj
19 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260422143453+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260422143453+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
20 0 obj
<<
/Count 12 /Kids [ 5 0 R 6 0 R 7 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R
16 0 R 17 0 R ] /Type /Pages
>>
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1754
>>
stream
Gatm:968fX&AI`dqN@),ZH)RfBMY,<B&f+7fbflWI$)AbA;!>r@5drSgG=-u0\@WS<&&,Bh>L&4^)dIm^Wc_tF+Dje3.Ju!E>[L5P=3:O')idY^(6$mTqN*T:tfR``!F=L$,pG:TsA)MiJ_PKTcuTq)E9**#Zq(\L"hpr&iTRe<Q8u_3E%tDlUqk*WduljKM_q!iJN>,8$-]+?%d`OCAkBf^\W:B'WfYP5`*mW8Pk@B/i8>D.#qr5@IIJ/_a/?9islq>h40TuF@QcO.1meN;A<IlLCb2&'<098d)7>fBJbL.O.X-m_#&LgbBTK=.=A*%)c%uojue/q(MBqo1+=_8L:?m5VYO\=Zg%^QmU5\8\aJ.;ggGrWQMjH\!i(9==lm-#fmfbgP"!04G!9E13hX%b)IOo)Tm4;^Tfn7p]%c_&nEe/h?)XKee7.e)$K<mFp`r^qW)S?s.0L"B=tlg,Kq(]Z,tJn_;H3^j`b3j"W?K:.j,sK3%g)-iBr=@iZ.OCr@1(mu68j(#Zge,c:K4.]f`F3%Te7ST%G*'%Ptlb(o<-Bnk]"I;#Gd\+$BhuJ#i?H]ml58n7*L.=8XCitT]'o'h*e)n=3G=cceCRQ>[7guEorPU\QL""8*PYhA*W-^62&E7UJKKmBEGon)b.CP[*tc:@f1/>cn*]DM'#M$*aL6"KPUJg5d]k//hOYijpZrN:C@BGa+=GS!mlMGc6G(Gq@RJC?RZVM9:<7=]iF*oSo"%^b7M*m&k>r2:LT(Z-+5piJ!0=][AsR1g'k.T't,5CE0n_@NL*(3rHLAqD)WrgMKH52h2U4p+hJ3<`J7-B7q&F"5"=@t>9GKD111Vn=-6jbfcM8A*S4KRj8`*3G6#XCMXT"<[E1DM-G*+BiROi'8n3m=nnfFXFETqi-1_5sBQ[96,E)\g<>=oGm77%\7IH7noI5`J/F5&CP!t)N;3J'pm+F>8iFteX#G@EP@&L!;2(PN=2Qo'thgQ\(ponu+j3[5VM.CDE)X^$:QuDH,1Q7+T%?Jl3DL;Ze)<Pa3eu)*P\er81bh/gbL?tQ+`IJ5kc.W`H+jmi>6Au-(R^RsA5hDISgN55IHdZbS2%0)UT-2rd\.<VQ$n:qWa@FlMl0-\i^W<l$>"3^8/Yh:neEs*?`Ak?+boW+OkL5J(9Pjs4rJ>WC+([-$.N%@=dW#rEQ5SK2]&N\^CDI'B*Yn*[JII[:YY;(H:*uoF/^"cnW6ag,^W;>/FQ/U[c$>j5IbLegKfRkrF)7E=<J9/6b4B*/%*e>A'gnF#ZZ=79QO^qj\+&A2#c`8aZdNR,Gs9-n!bI$T>1mBI%Z2U,XCohEWOEX\ML8FX_Bmn>!V`@2^O?b`ffY]\5b#";29Sq7T4N)@o<KojLYTN+brL_mOkebs(OOo2EWOGu]3KX]7D>kmh2PoU>Kj-"bh\Y>G!*XL]C]@CJ0[Js"H*HA$n_$,M1%3aVhYd\DCEo\h'VlX'$%S%2rJO]DcL]&EID!*EYcA8!e4@_aJScRr%q>mdJ)<Mbk1?94X7RNpI7m7QgVg^9g=Z#WT.IK].e+\PJ:Xf$UgKHaNS:!Wmc@N<;WG(=Vr+pXk8K=.<DR^f<-@UXp9Ib_W-63]>/8?0t'n?h[dN#fl?P]?2/a,;<JfW$cNu]Pb[_0$diF'!*FO-hU24H#Mqj_H@qP3Uafo\"/,0/[nO"6g?XJh0n*1]_`ii7H%mOtag27+^D@+tV+5D>ZO?#HL4:*b4pWW?F.%]ts%S8^h#~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2925
>>
stream
Gb"/(>E@Ms(4Ml_EF&]+fo/O$e1JIF0gX5ug9fUHgs/Q!A;ar5/5&o5*i&>mpDd(4>JA+m53sK78/00e--t[/#DW8gr]=Be]H>$%5B>Z``Xfk]-"6]T5)uiG]!?,fpe69N40J&+@/75U!X\\h.`KXr%kPPN$VoV^_K/rn_K%f#H]Tb&G]RF"eUEQ>^+\t)\Dme\IVAs<F+JDeZ@0auBaKP"[)22(EP!4kdP:V$ZiB?h/'EK$'(M@N].TH.E/P%W'sIg2TFNO:HqWDcWm]<C,m2>mFL3=!@m4!mU5[18@JGD;KXcS7Q>3q^Y2q_jd<Gtm?iU)f/UYiUWgM)</:g%.>YXB-HkXALS(m7uU54_*7IWT+X!E$qWjI-_j$ss$+k@\1;YXp7H\;;6<<-Z^O.si>%,'OT=e(3h3cog@7l>nPBPQZ5i,PQ'9,AV-dnEOVS[>GcG-u!'c$Mq^5J3F5A-?I'8JF3q>/E+QT?uLKY#p9m>:p74EKG_<=c@?m#'Ngka)g1nU)g!Om@`.'FM?9ng6\r'c'Km-CBhlr1Y*5]DDj6)QJgY8IJ$uOV^]jKrXGZ)Y>cEWl$_J6[%c,)f?KdFa[^]n"a$7KN\GptQ*>NP)+Ze15Q$<ekW:<eC%h%>\IRj[NaTfk'6;q+Y0RKHR,=rW:"8^5]Adtd>MO:ra'nSM.?IaA`:a'A3eWH0<ok=IHoqk]=M]%Zp`[i@Jqgr@4=$$O<d,&BV'YA$U-sVA>d%FP]Z:r%*$[=dKAOutc@F\"Ks6g]?K+Jf(Mfk?<&n9!Le,!/>X4Nb3N-I#L,l?A4MnXsRYP;a]nIb\Htfr/9iS)-.`E^=.LVUoPsC^+.PE>tYMuM?K3+ZJU"O'Q*B-*]3/M8T_Pa<MLhLWCF";4OJ9Ph:o6*cT];7pM8DD_)>@J<>*9khTF):Fg^\GP#Np1SlPFg.URNVlXN[ML`KFV^!ci?#jB2TD?!Gcm79lm<F52G+BQ$G&8olo5!/Q"+h[;`-=gKZqU`o0:#."QX\BWOI>,#bD<HC$UC1@%iHrSo<$(5E1MrXdn'8D+r>a:E+Yf(g07C_*E`DdS1'Rt+nFgY3N[kFoje_K#]-$dRh<,K;Pp\>Q'L@]17.qI^k(Z^i!l7QZeOmk_Sjr<O'q"HVhT7i:b_4E&4V`+I((j.&_c1I`saG6sI^aAd34U1a&Nd-eSNa,^Bl@7^g#D+du@=NC.P2(O_Antj9L_K5fo9Hk>2Vg*k&P(Du6MTb0VjXT(rGlKNh)&(6t1JW&=ph00P;#3(EZIoYIo2:jHQ=6<!5@>L@-+uc>G"^4"+(O;ERGT$Lj!I?^^/Ub7''C]:OogqOh`!G84f]P58p%iZ;I73NpL0L?Q1)7tPoJ"Z[s@],7k@#"Lb!Zh[ejY2rYkL_^4ck#dU%GeAp2LAqhCNZfX%EZ>-pK53p`eQIc@UO<r[(1'McM3;/JE[,1'SJl.9a2D'_*:E2TS64>-YgDkjLCm_6@UOPhMas*-p^DG$^9Vt._KFl%6]e=@4i42q'.=M"rVSB_P'l3T"R3YOJ.'0\@XTd$po2h`R&$J.('V!2Jfc_%3i8!>`9(TG+S#^>4:8Q$jm^2emfrFIj0X87"n070ZhAsOtGTIN3P4fAWW7p*2IS<h:hlE8%Y4;u4m@>L@f>nq;-neFC?E\k=2<effl,I<]f*'10u<KXH-F3h9SF52(%$Eo5S(,J'23?,^gl;]<'NM"20G^nIF$.2YOjVToANp]jB_@"Zc>MWr@=`LWJE"FroKClk9]8UqqWsY&(c[8e_-g5=/U))PW>;EA1d`6P58at-C,;-BCod3/H^'>7dO;%E;k&RLUEB6gLl^Wi^+@0=B;#NX+9tP:HF;)Vfe%RmX@P+AGR7cJZ7@`WU8uJ++1uAX20F"ECFNj'f!$_a_?$N3q[8TrlTSbLR`mSZ=L?2X8J:5%9EVn^@;Upe%Wp0pM$Fa314su?Tl'dg71hA&"TGII$!-^%D4V@jqp@l]'\t&l]V"-niQ6g&P[9>-p&Z5b!:lU;B;gQa=,Z^$VL13<T%5I4fCEs9=l'pCGE.RDa$iV%KASNmiUV(uDU2aKR]!HoY[T[H0d"dIHW;%D*>bJ.AD*)X5i=/G?a>$]Dp$MDV.+B)TR.tj9*Thn.LM=#T_]DLLZ(o5KLZ)mhcnu)F1Y\(Qlhqn.rZ;;pS"C#dM]hNg!dA8k_6e#umdEVn"fAkW_$Te=gkkkcK$KX-.!=1_XJ%&87/[s#r*>OZ/TlH4B=)nQX%h?)D+pI+=%f4Q5e\R#l'U8qaqa'5;TGfs?)R#-L%DO9;a46[U*(RdU?9sL^4e=HW3eg].?<pk'.QHnEs!@OGtaB4%hT#AGnqRj_[nQcU1;Q@GFt9iVG?Z_=*+b\5BJfW?duckJT7?V?[T</0+M*rPA-Iabr,;LLmL(C"CaHXf/7_)7ffkX>e&I/$Hc!6p8Kb/0?17Yh&?'U"OOF:;?*.89VM8K(JMFQ#b,6oc>7qNP7$6@C*R*)Meu:1&?ti[7G@a%#&%bScM_1eWA1An,1n8p_(PXaV0>\L%\]l>kqlV@AHrJ++AZifYhJ;,+[WeS?l>4X7=sX$/ca.W7Z]e#Xt(E'Snm9oofacP8r][?0FP:7;&7s9R;.j"qFN2FYM_F:$BBS_.X'"u_e#M;?c#r.KeIGW3:,lC+f9mPIU6$jT$i=90ud=C?f^b;9]qqc3!fot:\o%KM$6*knH.N:B$rL8eB&#\:"4pt:IMX4!n&WW#:fI"LL3NoqDphjSIPmb,Y8e"AY$qH_*@6-3;!k('))M3MMXN_,T=b&O%hLY<59tb;c[Z<-7*BQ.$2G>)DnH!p&)@n&Z3#Hcq(Zpd"p+MSKYTOZO7]9PZEE+21ZP!W0nKm`T1l:j_/t_*_M'cHRMU<G:29$pZ-\3M7M,8pIj&;-B(s`l9@P1GgQ~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2894
>>
stream
Gau`V>Beg[&q9SYR!n1tP-nFt#a!hsmR`nFEDDsu)l8#*$VeGMD2M:aaP)hb^B8uhWLG2Zj5e-5#nf$2$td6L5aVVhJ(4JCjoE/TiMT`k."h%!cnG`6r"a`#TDa0RE0A-bKRaFJ9+F83"\V1\Ve7:_a#")>AJ.BT!Yu(KN!1toBEY_pS*+EMd`P'J`%f.[UI$<@Hl+,l8>)1P4;KZ)!>@:E4e-nYi]I7kj5^k$s.A2p!WafN&s4<&P(k)m5T#grjrbMdJL#k\"aiFJB,R11-@p!r(kajgM'3-lZk5e@"<UHM+UZTI;goE5K3/tK`cE!1HiM\D]WS-3a_"OQ^aEEgCo6/I@hHA`]Qhg\H)&mm?E/?P</\::oeRHB_YCYZHD!K50tN`jk^TWOTEOc4kj$0erK'eG1'Kkgo-Z0?QD$&\!I?N<"if.Y>UpKjSR**>?(5QLML:!C$W1XK@@.0Aj;]*BK]og7KE<*di9iThM;u*aLQM1gLfp^[KhFrkPe<GUpA4;#"jNn-FQ,IR6Gt:qbfJFfFC!T.Z.<M@K<+03T)G>)ajuC\"K\l)1\?9U2Td`GJTb<%*H]-;7[G`/Ls6[s1Sn<+(e!]]Ta:</a3Dd+oSRc0\C!#,6g7`2;/;(N>h11?%)R^HPk*FkJsEA)1sD3`pYIVa@0%\oIFK*8Biuoc_c0pnbPY,#S)Y'\!0n"!`L.YoTTKX-$CfZ-`r<ZOVAKI)?S(m:YQs9(6aFXi8N4Y*k?5].OeZQi"oS/(1-%6o)Os5\Sp/bE<`M<10WD&j_*TfAo`WDoXkSm!TLu_pjrPL3>1LeFTXNTi\Unq.#S&L,9hq/qPriX0XlQU9VTSJ:JATTIPnuK39;NSGAQI%lL&*HGY3*_+>/;#O[46B'qZd)X*WbI_"6Y(B@`tEg#moj:^qBCnM^@LKVKj,qk.P^/pKiF[`P$RN5_d_SlVf/sdHOb4&Ct<VR$'j7-:Hc*JIj@:ei'?,_e]c*%@E;DL2l5BMRN8]Ca`E$,6Y2FHZo+fpD\*kN#*O3JecrQ2J2g>eqRU.[!gS5k,h0i1?j`D(A7&KKbYOZd<ojmB[&4ALafEr-$uNJ++%e9A8@9.br49k7EJcE3Or1KS)[mkA@MtW\-Sm;""1KjUTo20.3mN2iJ7Yj3^hf[+V_Z.n[TTb+QPG',Fc7dQ](JsD%mg4KUZP@l!-)mOi)6o0T$&m4=O@)8?iBH5pb>p$P\N-+2TiC?SGPW(efSSP/QX56I&sj3^=T3+&)G2)]emB,`5H?&lMt+%htQK+U9kp@TSluMT'#;Co?,'BF`"o3LM5,E\Y*`,T.8ObN+f>Y>S;/eP;^BPCLqXCM'&[*0S.B9q-;lMJ3k`q6^(,pcSENm"ef-'A:HuRf+j?L*gG6[4F22BfStOJlsRtk%S/;5C4`?iXTOR6tEUT]GUBS:Z@\`ng`7t%r'K05'3<Q:,RaoFO4TU,\FXU&ie#KikuQmaVF:;Ob%PQ't_0K#OWTg..Zp=mT#6/Zpp*Y*bnuD:?i(a"g[g!M2M%#I@LsG*IkO3539A8"oN(RCRm!9NE(Eh;<X&Gk5Y*!@B/st7b,ORs/UgglX`9%V[BmK;h]+V.)07Z'#0g1`D-p`ceV;=*,n3mFS+9e]AY8,r$s==6NS_7$aD$&H3q#,_)_ll,NgUNik'r?&!(>mL2*g=GUhAbO1,gs@k0q;1166)7aS<S2TWNQA2bfa,1_&ipNQ8Se0-U&>o;UWgP_'5HnA+.739RjhSS$PgT>f^H77f=:`=7lFO-!SpDk=5a0OgIdJ@UrP0b@:Aq>n$le?GsgTi_)N"SlsgoHR3f8oYD$;2'0X\NSdGUGnPji,Y#=kfdd&8U[N]GRn"M*k%U9mS7Dgei7Md5.jkg^t9!idA:s=<ZfIJef(j`3DDqjCSecHH_#h3+)U,6C&S>$&md`lT.`9n#gW=/Bp*_>NmcCnO93o>fS9EF5:@_,YX#Bk*$198+8U>8_VCkX0ec1U\d0]q^^0aB;s1W&q5QGF`j<drFk^"2bPfQL0q"P-ElSah=s+\=EW!*38E\0!F1qpW2n%gcs\>#A?KV$,k!qu+cCtBj6qJ6!V2l[,+1qFTMgGIOj"7G7L=#dJe1f*H2m(CEsDl(A"Rh$/jVfCq]%P[KZ9n*PP08@ID0Q-h`&2i0mM7ul#4#Aq^3^2I&#=4N3&Gc^!PdqjM9'G2mp9.n968B%-DgHLJ4q-G")D2!4+%5barf^%:iA(q2?[To&g2nCX;a0QAF6.P\<&SYoH1.D43V1@A3Ql@7aO1pt)"90oRX<X).CQrB\`GJ;/1G-P[CC^;Bu-TgqDmMkVrZR@?p(L?ncgj23n4ebSU^!Xhc$`M"?_7QA4)i8d)jg@H:/p?(E4GAsC]SLXbdM.hu0p/R5!;=g1%kSb'!gqIJ6i&!"5jqb(dTZ"6)%bkZDIu?4.'G"Q8)O2MlBVQ[)a677m<DASo,+RZj[S>)JlE8sC&$<<qj;(CllY;.Goc1\F'*hoVY+1,DEMq/CHWF,u9"R<Y<U*$mK[gDL"lK>[]3e75jS`QeJ)A<CK+R+WNr!,<'\-suR>;)C7Y`^@!cW=aI6UWr1#-?!<B6fNG6`-'6Q&KPdHtn&<'dq4(d!?#L22;,@<3SVH:PZ6cCJT^6GK:.fsgV?Y;;I"mIREW**H;,\girED4FAO'ZnKP!9lWrq02HZs-=_6J9fM:p;BZeW5XAK:s`\@#u3qOo_k9uIa^",%K*>0?TqR]>gY780_+hp"=KDs4$$Ml9.0b9l7\FVM[$r+)N*::@VV:n^$FO/Epm7SgH'bc[Qgk^F`kli:>ZA*'j&Rsl"\6t+,FQOj1VF*8i=3Sk;d&WYO)uBnb/?!V6JgYd+a<TXT(H0!T"`W5k:lPaoH)S"0la-+o~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2717
>>
stream
Gau`UBli&f')et?dIEMQA$6$oioMg4a`\k>?*YH0mG06l\MtRB`"A%:acR46o&$oBO<E`9?!EU`!X-(Vd#EecL]PJEXSs6:!/Eqos-WoJ0Y@B4M1jgmGX(!ZI>8(Hpf%;,NpF:YLbB_h815fH)0Y^V5WIdV3.XH,kIA;fEBTA.:aihu]gICK^IK=%kP4,^EJ(F/,_8To?q_-j4$H3P*>[iSVSA2J70%#AoFFXqhgX6%%c!]hNtU>mR089Wb(\"^"c)+TB[EeQi,#;/V6:+A&@WN%Om%i%5bpf,kfKN#_)+LF^m^[s?A(t!7jAqL0KAn'jVX$KM(5:D4AXmV<N,,eJof4#3&obV.*K-%e/7&ACR*te<+XT1(f"N7>.a4UQe]V1(/+?cW"KpNI$@W[@Eb<tP;iq9p?[1D5Zsat"4Rtb_3spL^cIM"F/?obq7/IW$<$cWs-Ws&FY7obQ3L^6$GOOqVukrePfhq9YJYiA+BZG+:);,j&&ZSQ6Kh7"UI8O8UC*_@Sm_mNp.@VC">#8B637cEk;gu<O;q]oZ,ZP:Xt1a]IH8'/ArE$I$kq>5dTL<jkQ5q1T1Ng#(MMEaei':L;A*8M>+#Vt;+GR3?XH1/o29/[Q''n.G\TVui'$Kb.?Ed+Qu,4D\i+l^GWo:@,f<1,A^U/@qM'^=cEQfI2JF%[elfd?+1*32H([l6qgH%D&iPl2#uN(ua)%i7Yu8]`h!oAq$L`Q.Fe4WC2?(<he`Qej4sbOG+6;HTSeiUk_0F'aXo"fOrZiXsh.uhgGBouNS@2Y"m"*I7U9igN1Bgd-Ko6q#_ISfGEL!P.-hD&b+12%(^sO$Dgs(HUkFU23IVqs!5Ee37%d*';8pNX>b[!<E?du$Aa&f8ha'f^sJU**BHMhXs<#Wcu/eiT:gL;%*X!5[W=VX^kj(kRmJ[;TaYFL[1^l*.Gm4g5"1oRXUMUS3'r&Q0-nmY6_=R["J3K#7f=NucK3S+JSI?Ks;L"Wt6R.,)RJNSWY+c5!me*i\((aU#'+9Sr&Z<1*<QOno)7*WTS\huNE"7utu9qpbbP)k4<Ye5YT0LMkhli@'m+D2WlqH*SZ]n<1;qbGl":9g`D<3s.-^3HgZJ@eA@".%.9T:'*O4hc>'ql]q@4\j5@S(dW1l[<1tS58G%41NT=/Hcr7P'WUALuWFrG[#?\bs,C]"Bn)fi^uFKG%]AA)?\FH7!RMOS4#GO4kJh)*G\No>Hb/>=;!ZS5MCB=]fK67k^#ar/SdNA>XaX(WO3*MKMdus9lgGE(RJdYpfR6+U.VgCDB.=Xo;2#$:"FjYXL1%=:[=qOb1<DR:hBuOD$.fl[__"#Dce,07!X4rX)a-\[E_Y@G0Ume@,/.'k%N4UH0^oW'l4m[(q_`/i?h(VPX1;EE2Mk]#j53ZL/j1f&4i)'dmu?g(ZVB$k@=RfGR>q2!o-nlkNWC0*$!BGi$O+W"Q%]*WTM&i;kKm#gBIh4hN$M`+U-`@$Ds7CZYC;jk??WjKSaj3<^U+9\S?'s\BNJlV#b=_/N(:U`&]m%Y>IU+f\pS:B`lV56_ZCHj0@Q2rDA\k$#)LC-gE$H_29>[%'$d^3m0=t;49k87M2!<3hDu5>E#QkY#2njA(`BmZ'O\o2c;lN4gl1?ijs\CaZP.;>NuA7"XAROKaGJYKX[ppEt7)N6o"lo"EEsR-u7rV*heho\q!:6;ic]P?gt5"$-j<&n"a%:)X1R/RbO8:mFah3if#KT'"/GW[jIfGIIj-)Ql^C#lCbR=HhP^SBC6`?gnKWY\\kls#V8@4qP&Yp<sTl;\EiAK%*('+a7?otHCIg)Q;YorRL9J+n,BJSj]]g,0/\!&D_q0fSBo&L=K;>*/:'s%-,5e_FEu1)VR2%cL$.(m9-bnG6V+@ch;][fisNoh#1mTRdlap.G\6cSiO:Kiom#)3)AW'B\ZQa"O0aiL+jF$;-QiPc<+`*Te81g$%8d6D%6NMZEc2OcYA;+E2&I6m"HLjg/<$aKM$54KW1>T[C<_PAV"8,ZMe`Nd.K[Xn=bTGA&-j8#O]e_DgF18X\-mr9(N_QU\+#@k0&]cU7]GSZ-'VY\+\6M9S$d(U7SkrGe7qQV_P^eEhjKU%B-6"g\_tBRMRiUu3pF'O3$r'VZGH+<@1nQr"hH(Pqm$I0#i;-`S*RuRkD#am1tRkKeLUA90N:IZBAr"M&Tl"Wc/Ri!Te>m;pXVrr:W"AEJLm2XC6i+LY.-@t<%"AXo#qMVONgZsh)_NCO[OMlk%/If:U[Ka@&WWAgWV#"pM>K&lEs!ckhp:j04)))!/I*!o=]AUH]VKZB,A;-4>!A9E3!AU:B$M*LK/LQE-jWX%2[O:->HauW^6Tk5W,*=dklsYWhph"WG6l1j)h/]Z9I)RN3i$pfKrrgGI-nBCk5*X5$]jj+hk`.9X)fr/ni8O:J2S:5eEVp[eYl`;'WBRThG.C%E@Uq&4]kkqaioUlZ:r/+/r;MrLWc!I3pG[>g&$4rdq(F@ATDmfjsp9E1tTW5;$p!NuS(kTi&tQU"o5u^g)$D58Uf/5O&QcQtK82k?0SoAjXcJkHEgIc/X#?.nY<uM[-M'?'NV>RfOCtqCI-sAjmd"JW6VpR[h%jcg]]RkJIhNDnhik5!:kf3.(\mo5>_"f\Hs2q#=-/#:CTh^A[s-It-)ZO?YLk[fAO?[FsrRabIS!.KgTRk5'"[&F_gZrNSDV]bjj!"(:;A"-,ln~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2259
>>
stream
Gau`UqbQ!:&H;Y-MQ*U(QRFEALL3Sd9*d@g.3[Y@=pYKQHT;KuqeH!N-/sFCm.ti8T-6KqN[2>EG%SIZq.@9$TjZdhec5IZJ6*uJgnR+PN6Zt],%.bg];*3W"2YN!0a&&*>)pRQ38A&IJ93o0;:qQp@?ao&\gCjoB`V_^Z>F>D.cL=$J@MGgD4$@$nB%VC\W+6EY%:^#.4<n**>!mle5\"+GmD=!$`KtbG743W^CQ,,2lOYQLgsReJ4YU6\A_hW-Gqt`mbD%M7tYiKNnU:%XE'@iTK0mj+p;@C9;[7R<$=X1>'d3W_Cac7$INMg_h3.Thnkjn/2R;M8Kp.>;s@ZME5ItOgM]$ZZRj=@)8L$5/Yci;O`C@/7[GfTY"[1+(X,/]o-OA41';7k5-d:20@V%hj"U2BSC@'N"'#H28t\)K#^Xq=!9:9G9]9=pI>@ZuWI1?`DlB(F+OmiQ9imqN(l#4?A2:ia.JQbr)hFJ(%<<</ZZHshjUfG)0l??`>2ShJOP!,Odj.DVXBa2@*.mMgh"L=nM-Hb]]TJ3Dar-[q**iGh5=r"ER@Oa^T!arup1g(^5V,._RhsK\AD.5CR6[_aEX@l@7F_);".t=7fD$qc6;Vr@CEYZ1iA%kl3,lp/$dY"mM,Au^4,Y@>hp7RKVoW``V'[=H(!I4I%O)mc,\@&eKODb]-Cr%W1Ub);#+TZZedO.IWT>so3--#PC>bigoo(B>C1&l]L=YVLe&!t3[AnTk)RU(lnEDsOK-rDa<d$s2Aelp^bIX>Y<Id$XW$hn*G%6Z'7=93r@WfAar2_XgP?L&Wb37sM@YUXTPnTX6U8SWf.](%T+\VdY-p68.AlJ0BbN*7I@o@?X/]qa4'hPe3j=lk^8NQB^/-CZ6l=!-APhWd4/j*4^'KN0!l4bKp[Q+aNQ-pYdJ=fFeQURY8'Ie;4k:&1Zl#.$`/:G+,<%*ukd6#K+CFX#hH.I\nFrlET_c[)D`Ko%UXQ4$]"Y]a+p>LB7lY9H)XpCFF;"1=!%q]/\!Xqenj^it&ncVnX-$V@T=Nr_RZQ5M0NZmD1Ka=oijKg[\8Z.b\a6$NSE<2H(M2s<<,3*HZYl"1Yo^MY+rnF!mUZ98Z=5-6WVoKnQIsTLn%ZaRR&Dd&r]7\QsGQ6oZ86sdk`/]eNO0't(L7ub\X\K;<<?*DOA!q7*d='Q!p/kV#:R,6\R3<$L%VWLN$EMKY(HYcllr9UYoKc'8;0!rRH\,i$9ADPuC,?'qm!AX7`bG2O@W*Gti5%FBi?OcU@l`<fig7E]LYUW-#[eNk_(>6Dafl4h(Y*gn">q%.bZd;pk[qRlmkW/bV"7mD\YV6sSHZ,KQ7[%JrT]r[ls2T8(t@&Zc@lFEF+ZCZm_=tnS)@#,K>*S=8r')s$Bl:teYTVklUtqL&40#i,J1UO<bb='dBDD(-u9gHIFM63.j;=m;3t.m'"X+]k^]\H<*hr:\_V)[i9N%i7F:Mf^T[t9?hS&k`GDTO2\@TbAr<(4KJ6WuEp/u`CO7;qo&[jH4VY$`,#.PnK6E>)#C*R"QPgi^8B&[/8"#A`!fo^ZU;"9T/7"!6/EO0PoZ6g*PtUjt\f8]kbN16rkJT7egVnL]dt'/=o\cqh?.8ZaKQEr^BaC7Pga7bA*Aubm3F=XFWj,0XH;I7)F*!V0k0(p<H:Bi5gfhh*0k14sYD"%Fe*6982c-P.)tg\D9IpQ*`e"ro/iBb[QKt@qW[2OK\$_WAptEYuQDsR6$=Hsp;)]9Qf2Bf!bBC0SDgm,_/cr=.5ZB\0R-q6jrsFB$OuH\s+3n\7;IYKK6=)<!XEq]\n_rJ3R_K;(Q0`sO,q$J0';V5BiRRYhFcrOCUFX-[FIU@PQE8ZnCg81C8n.&UU(!iVlV(+IBWA]#FZE$j=="6cQLP]7?E>X6Ud8%\=G:fCY@SQ)A(5f??7N3FcI,]h>6UZUWA*AlWl=@48AFCEleSOdGq8Delm\KFN-6Sj=N4ktN%<;L[Q>:rLXN*_1Z^YLCe^Nt#U;mE\1Y;Nf*?g;U7ELXqSrOX32*e:g<%YrU^@_8?NRPQ)7R;r<9S9+Is`/#Sm[sf@>[2o'WIHEl!DjXQM-32gH'P5hCMIlE#!!a`<1KQl1a_aLLc^o%!dQ2A3*l^"6A7IaFYg*]&J'B4&9]XPsM@,mGdOk!HBS]0N2^F/EY3'&%Gk)^hWPX2\h3-%bN5B^_kWAR9m9_+D=l/?OInh)44+<15e7g0C>Tf8EIhZr_5G4:dOL>+97&o!E=LNS,~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1767
>>
stream
Gau`UD/\/e&H;*)EP[aj#ngF;W4)h2QaZ,d(8Q4qed_3^0J[LGP9j;)P86ahqi6gZ43k]$aOnUbBaQ'QiUbhFO"pkD5IC9I-NGfHd!\941?]0uEl+NZ%su"@k8&'..!.E[fCMAe"VSBfM`;EG<ec5o)A,H,%pn</(3i"s7f4C%!>H0$Rk571ird=06\[1fSf]Q(Et\;/&!XZD%D<<4@31F#'D;,3LV"/V4SAKJ*e4*r76J)u8HO[#^/$b6PW6n[),.hU\-=<a\sL9*X''#4(E!h"=X2m=c3]&@NYp0,kMQfL&`NfE0Qh0Moh"@]7(inK%R0ga*o?!'=iTJa:Z57f1*.t01D[doqsE4F9M-T9n60/>1nRNRVDb+A$$r)po[<Z-Kh/J\1B5'>JNA<UWUaI\m@b@CR/h_NSg+s7(9$*j%GZa^P5u#lFejT7!t)*rh,QMO[GC6i#1Tc&+j_c_?;$Y2K0^t?"j.J@"*qr`MLEhD\-l_V*i5n'YFeI@%8*.g&uCR&`+^tqq0X/>!KP?8%M,e-@L%[_bSgt(omQJ4ct/-L&+NO&kt7;RpqrqW\<tA#hSOs6`&N<ce+pqnj"*Y\eXWm^nJQ!=<7<fAl57P1E>,Y/]$(BQ<^pcA0W_^Bj&_'!J@B[)p33F/Yu[\.8Z.`&h$2r1B2mMr]FhVmg!t7G>dkK8f-*CnV<q&T(3+OA02^h`ibG5fVi94'@3LDO0bFcKqbRt.,LrW.:h>GEm6)!t[hT<n&cHdb\kY#Kk(SC;L^PYr4H[U:/7,.Z`XLY<YfJ3=rhZ,Z`ORC.[k*I2"!O=Y60-ciGp'l#@p)RuXHZq^^.r6\lgmf9$cEq>l^$()N-P0In8=!l!<*d=Te-H0Q\l_ZIa)==cE^OuH+&qtqOO?mkP6l5r=-7l0]Unc&g3GHjpj88&pE[k9=00Hc4q%7?2GQfYWkrRc^'N?)*TPee0$(36aGT(d,<X,$t*9"0lDe8Af'N9n+Y\liT\PapTMk;:(MPUm[id9SK@7'D&L\Tp4&gk4nru8p?UQW7Yk0-fRmjn2ZD2gA2LA./,q\nj`;XNC\\:*CF%GJReAi*bel#ET(uAW2(PtXEZklTA-($Wca2lUqRSD+.!KNSW4pE/K8,RrINlP3<H)R%H']M\6:9,BSqtFc'5eALQs^qR+jue.Rfr*UCd;a;L]U\%Y.J"oN:o>+0MUEc=t?6]pl+o6gYND>mFLb&[KGMNSSno,WNNkUDgEQJdscf`Y-Rtu45L#td`h)KNmU%s*>bTSlVb>l@C"-%RgsmW0HAef;j5_tW@@$C`B:Jo4h=pD-U\:D#r=]"@[$m79X.Z.SXN)G'!SC^&imRgR=Pj+fqLZK>erZRJ[JpGo*.E@+>-*g3/S8%;e')SS`1=FqM7;B5'i08(98UCn#_gW`Q8KIQ@kuZacm*/5am`HP@PF:U1=P,,9X<riN-#ho'G&Ip8QU=ASkaWkn;s^8BFi0Z/3VoWC3H&O,:`5\_XC[C#_*fKPIpEq-L!\YhGt!U)$[l\ZVi2l]aI)b_d*ubple7RV1'=AuO73A@<a#igY:Z!KP_,UcGU7WKN/d,P=,(f#X)pil+_Xd&1KM8*g<nP>4LDC,PtZ\ufNY"4p=sF7T[;XhC6=?=MN#!PaE3#U@[fL>&5'j)"6f(=1r8dn/5%^pgrPI/.7=XSs/+$'iH\FXNT5Oh;`mMZcTqpiTm(VJEl-oJaj'J"pjA:"W,'Ii6I6X%HEFiopC!U@mEL+XIWk!`.i9C-h/-C^7!<~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2586
>>
stream
Gb"/(>E@OI&q8_F0u-)d:6;;@h%cUHg#g^8OJ_9^Bn9`#BUQ<>Q6hl\f4g-?pWel3pmu:[IQ@]?<[,d+I[iJi*$"msc/3UQnDaNFY;gJ;#%QRTo3q,,?4P9[3b+bF`/YAP$c4mqJAkjQ-9gY8=cUET:RK>]&:u08DA7c^\h&da*eBV2IWEGkRQbbD3-OiVnfUUeHClLW'*@><oAUksT=FtuBSE:DOk`S0q,mL3dU3#r`WB6cPK70P(ig]"c5bFD?'n";iYe7sD(^HbBD!]`3b6+EqM`Y=m@5#\mit<W.<'6X'1*u&[A;M^]TC)5+WARupZC)bYMeSQ,0P(3'H+QeZqRA4gph5)QWFuiAZ<gENo5W(P?G/XbIi-0K3K0TTrlrAqeh?:p9sb2Q6gJTn536*fWooU-7TfCXmu2r't"-#9K5C27]9_mq9`"_),t(48JH#F6(4bVMD%At3T/Rni-QLT1@PD*i=JLDnE`8P2>5gC*(&?g:hn\;:G26`(jBf5T];+%.2(YcCO"jNdh4[Z/>WYXi&"*lLL\].0SM`_5^rDtk+eI\EAWg1pJ:jLWRRe7-lRmX33`,<(pR4L)]*IP$b3;@1ic$>gp+.i;k!l\O*6>jiW526?b*\*B#_W2'`&F!H0P_.I[$8A-X,?OSf']FXY!fhVQtfa)N?M_&dWP,b=^\inZSkk[9/W;OI5^;:A`jf<gC9JT>I(uQ\,CI@^5.cOtcYK@ApK:Z_!CAPREm/+Yn9e5?.!XCp5Y$n86`t;5I3jpRElgo!+W+m/gEjE*!_l<\g.9G^o\'KTTM$ICoGK$__=e>"L-KV5aeu,q=W:f89mtn3?Kb$FKTB=B8@&(nWfA!GU2@j,iU)<^0e)$6'cg4]E=<*iaWuJVW4.M%[1!O80KUN*Uh9/_RB*H*?j&[/0iu-[DO)0ur6-%D8L.YPk19P?52FQ9&6>CRB2s43Q@rEn#8om;1p$5k8*QYP6l!,b#9Yo5`6mh:%iJ!jUC%K3E>d*.09rA+1_X-BE=E??Ao]LbOI8!M*jWJmR(He&B?)6.6183ZgfVgE2FiRt/?[&2uVn2/0XG8J_;1AD'eT1Uo''Q+Vm/F-gl2$$T(U=V-6`]APK=O+1$j_a%XX12RS<6B$I"nm[8L_*io:BQV')UWGp#OK!W9-bPS^mumT`,bsHlObrHK@rRj$5]LWbLO,q3;F@_A#^G160[%tIHAV`9\^VA,>;!ijPS-/PaG&!qdE_reIWNRPdUFK:(W2KLhEDo4,dO\,=GU,1"[+/_8>!akQ)2E,8\(O^>:GtrN4"5Rhsn*7.0[KS5R&4R9T(pNgPO^FIqq(U4NrjgK>_Ods'4f='oVDQOY%U#+TkEMj?:4mkF9nn)4T'J)=X"CJ[Jok(Cps"T($V*)GjD-B,cqGi,k3DiVB-#`/V=Ep9P5r##PKtJjs*E8jme+BWB>EkI0]'f(FGq]lJ!Qs$+l+mgWdm)aeB*J:3k5"G'G=UUOEe$]V\8bLF!f$`pj$mR8JtDEN+S=m%/jO0'GWU\<2b;4rR>"0%T\P0Yq<<A/`OYsu>gs)1,#_gYa8:VW.j:?qEQ`@U!?h,E@0>39"SM]tZ8DK+_tq'G6pLS;>9&&?r+Q[:3AaU^iRUAR)K@D'h58ejAaoQ"p"e+0[gelC&Sg:l?)L4>5>K:bb=5Xf0Sg+&rGl'/^'bGS0M7D\4%Hk3bm;>o-VAkS]N4?a03M,0(q!]T+B#L"O#]'5d70BYj14]']'0WXtj.T:S'mEo't>>9?'75V#XiXMnTXQUS-*h.%na&EZG-U_U`2!>d1E@*oG;nO*mk:!iAKmlMu^;q6Z*X;Jf:ilj(Vo-,m%ohCK/CcnS-FBk?3oA!]X=:&bH8`haP<D-qm'e9jY`SS\]F+I#FKgl$4L\Vcj]@K9%`CM_.Pdt%^mYn",hD<,1ga1js)>'@Zj>H3*Pj\E9)R,n$)mj2p&78oGA8CG#qMuf<5N.a/GEH^9Rq$9D9o=n+Y$b3\S5r'"nesaG6N:=olAJu;5r<4bgc%(<peCT)4@?+ZD]:!Ql!8W<G6,H'_;VHrfsV5M#h`0AR3coHe^'TIChHQ,AJ1R\m>5:j<tRmdU"[IdHhNSY2l0$a\I?5Fo=17T!Z8rqs42LA@:)t%%N?-i0Xm=i,n^GJ:KRH[.IdnK-YQ5R(A2>>mn*QN5=59/o^<>\r8cIRP*V.71D@h=I3s0XQoPI\S8%j]s9t7YL(^;^$V2\JY6FHWN2=f$cd;_@j,J-`jS*62r.YA2:d2cc$8<@ZAFEaSbYEPFa.V0DNYuWeY/..=C3EShl?PqCt5p89HM]`g[3&7/mR"n45T\/::6[')DHTN'#'"](n;=H\3pPiYO5ihIf77Q:r;CUdk:DBl1Us.O6RL/?mROe$-X(:j=n*/JBeT=p#,;7b1)+,(R4gd17_K[5gT/"?Yr4cXG%kiIBfd'-Jid<.n3[a_GQ[*ZXP\AfeUlnA,BD;,eL4o=Z]$Ya9KhtrS>>4<tF%^9Ld']58It"SM<kk6s84W@hk%*pJ.*gYB#:bh^=`Oc/YR92leWk<N%SIrq5afm<W!FVFK8;XAYgEZ[W!ekA1V~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2150
>>
stream
GauHM>>s99'RnB33&<WJTgKO;G;I&,hIWV6dPgGU1jr2:-#mqcX=]3N=oS:>a`_P)"3\2.+Nr=rqT_O%jWb*:r_i\ZS,p8B5lWD>C;O?ee0['^hSYgKkO/ncN5.Ag+I;'aXp)bQ*&FfBRrec?q_TEB2-k/&LR=[nJnYkLo;Dm3L[h\\#qC5HE.$Q]ete<FXQ442\:tBU>/]tP+)GCI1f,DhGot#-!)QH?+-c>ghfX_WQ`.hYM!qq)#)<i/Zs48Vi0\21&Zje]i(KuRdT8mB`3!i$2GRo[5\qAoi_5A`2lK'!!jqOJEYchE^7u5KB$gMGn]t//4e;()S>L!%k;))V<5R!=4a:u#H]dK2/pRT'q1:!YI)^lqLV\0O4B#1^l*jA/?#4BBJ'W2/#%3Y<FBkh/A!:"KnCR;[_E]](\eS&&;M(M40YmI.D(5W=)h!M<WM=j6,B'MB@j'9BD3*OeZO(_\F-spDPjh>#Z&)p"QQ0H83=dEZ*16<EL^VSdaO";q;9C2FPe`GLr6'Y(pfNSKU3OOP(-R=Snq5YdmU_hU.)^,L-nF9b/X#WP7b:6DN+J[&p>9$b.*ULrP+kULRk%^S"PpdTG/e&Pjk7np&>inV:LM-(N$T*R9.i27Bd#28,@qM6B$h7Q5Oiq`2F=W(#d/*L"lXXnDT9^t_<J]lJQd@cH7DVE=/eGd`i3@5BW#DsTUS-L`JA+"J78c`!t)2LVaRl^<LGc$5<)m^6AGp,0IGp5cS;lm*(CA.*YqGJLU"YijH@V/-h&nj^.?^h`%_9&0k2Ku)2f.DAa!`o$C'^_l"-,074_<MG9Kd9:,7]PKQgEOD<(uJ(CNp;F:oTP3Q&*AgHlRt2.o#hED.L:Cm8>K<LSV54@a:/;/-:nY"@*H4mXHH6'!,nWA*";GonK]\'.k0KlGSII7oF!\h:7dl`V#UI^#8-F[B'#g170"42sn@coOO1>o[A(UT2>\W'crh?dCuZSo>:tY`)C!DV/hW<n<(A^cg3uak4sX/5r?<#b;Ifnnh8$):Qi"TOk!#I(J-OB:;htR9fA>V)lYGK00P=@60mYe<"sjb]Y3%+#*%6O;GE/U]kd11u,i'ljAb_.l^>M1-SGhA-$6&1R9bC_-#4XoS[R]]`G<C#8[*b1=12gpPPIkY9LWm><#d3Da&(<Spq,u*HPA49=DhWLJ5Q"KGeEc^X?0#22nk`#(+\n8Nc;$`LPo`kP`#@@^[#8,u0EDB1:e4&u=GMA7)Q04CO+?S)@0((VkAR*ojOg:mYON,P_s#H8oa3Se\d&_NR4I*&4&JCYjEPN,F'kqjo`uHt8$TOS5!t<kS2<m?!-b=b0e0E?RZ644UBUfH'Ct<J(h;[K6VIdumB!MTM&&i+>T]@"![fAMtRnkktm`8<T0F=#F2E\I%r6/9N>iXB^b*X-K[>f79eW/uoT7M2[hQ=m[hDO,W<jo%In5-qaV.F&a,#T/:/cd9\NlE(nD"`b;USV[26&Yft.N9I$VWfG-eJZGVj5'dI$8N0c)%V%@'hS<D$HjodGD,HDGu?@u'^)hGB;:&rlT'=Z%?!;?jfMr\o,!+3Ju:]BZ.`,(Q&b1/4X,"hWsGTo<enj/2C<DM5T54]CtF)''&3Mh*2P'N0cLY]YLI^.K1VcC=,i1Ouc$D626QkoLlJM!htlSapUC!YHMQUN6?@5OAJ=CPs&oauZMS2<pr,#bG4/,f$dqR"oXX`B$e652A(U#c`;7Zbra377.:\cd2)eCV0I8(I0r6),>5;W=$;.W?1D?6!<hnG;h"\Ltlr\2`c?,9,RL]!9UiqoquSFb9]Kgi),O(3^P:nE*0p>$XQ7h6_!&RWdH3T5L]L;qiW'F`^c1@mGHgis#:iF2,;im1ICI/C8NH8*M^LH/>gKqfR1mmd%H&h/NKJ<Bi7>Wc,#ZF]SA/>1J#*=dFG@<FDt`%&N;fp"mJb'j`#63^3/>/Pku<HEY1`"79O[iRQd?oQM92RJ/aS3b'p<;fT>-Lo.3GDkH8F5@ShmUbd-eTO0bE*$iRaC9fla=;I2G@4[QE"uu5Fo\o_T`tPW$lqen-:L?(W2NKiGnI@gr:7,#R7@dAMJ;?52p27?&[qs+9Q"bVtJ_J<@E-PqAcF`";09=YkXsQc+#5Bp/-JU_d.b>8SrWBaYA*!~>endstream
endobj
29 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2772
>>
stream
Gau`U=``=e&q9#Ik[_k:Zn$gm":!^1fqr8U(eH&gVipTY7#i[O+@$U%<77u-Sf(eGFB[?i`F&d(h-V8Qa+sf5s/>m?Z2f,6L:<d':^5r9TY_?r*^G51q9e^l#o+'1ETo!GK;Z4iTn([pPN$1C6]Y+,K++Ts!pKesSf\(lJ<bkq2]XP&pBO)9`UM!JoYY:Un3WcB5UlH;OpZl_,Rr!*[pS*;*WknPqa7iBpj:&FE/dNmi[Nc_![RFEj$&)!8;90eYk9W";5_oA)'qYr06QmiMB`Hj+V5kjOp#,gK+KugdSdIemX%PE7,nGmO,gsq_WPa4o>u9D-1J<El,e2Oh\cg[i37EekAahl.=BWH3m!ZqSJJD;Hi`7d7WaR'?j]WP2u!o*%[6rWnfg/:Cepd.S44'upj>@teNj'GBRh(:^_o1XI/uiFO<=[5.8LeMidr6m@QurND(H5;$`o?k\e[*simk"t,Z$n,M+2DX$CE+/&j>77M?Rteq5B-8+ora0KMZ4c*gZ7L:_^dIBceZ2;=@7`mj][/O0.O:RgJYdM.B6r=bFZY9a%pOpi!^WHk<@-"O>u02ir2hl#aM5pH9n#JQi$9e\>@f5M-'%TT98?V<;eDCkaG@@05Z[-WIo'C1b@H^uOTNUX+/l>ucSTDsb4I-ke=Bb%J[m#3B8#g>Xj.HkB3-Hu9#V.mVT*5\>(2GF2ZGGiRb5Oe,g\io10VGDCgupXa3N]aIRUl'QBO.RJV8dd%(,eI-i-UZ)@(#QgdL^i<sFDpGf`C%MH/&E4\::>nl:`?dS7.S@ND[ORS*\gLWebZ9&^APdhkpikP6SnaW^(e[`/iDq==*dlNBSE`Ge0j0l^E+-V"VNW"^QVIJC`[RdN,XDjlEfJ=#f#V5:R8;H0qcY@gA.`/k]ng9^kZaVW##UU;U:L)B\\u19cWSNj9!>DXMlC%Ie:Wg%@FR^`gDWPJ7CJ*FW/1,TMS4:g3Z->9UJf2)4GjbibHoq5,I6F8LIPAdns%mm'A*Tf-VI;G\B6s:+:*)FIed!,(Hu]Xhj`h_9lr^TSeR^L6mo4u"B%bm7rn>Fh^G<>g>n[a,F<RX9a4]rn^,mH.+WuRKHY<@I3o\=6]bn-YrjDb+=<@RX5&>0eB^`u(/Y7Fa(1UQl9&V/(b=0Q\>n9aT*1O&]hhPt@U7:&?2/$PVDZ@uh7;U\ghEk/2\Un^7*Gc2`5l45Q.PQf\FXlkh%a#D(jRkn?-Z@Or4+UulY=hu/X2;X0tRV'a*\jS0uj--Y=b)%e0D!o/^"-"(O;rRW\O;UQ,PlWFZ9AsU,5MOY&EU4K_Y`pb^#S717d9?n=KieD\D8p9>.oflYJF1@"rNiT$.Qj0JV0=]#uX]-aCtkRgk;>cZ%8ccJqK>YBPZp8`!T<QmS?n9fXu*VCEfm->qkP&[5,L<KI$*oL+UR?p)6'^kaEpF@]_+WJ5)<2(==8p]A>?Hh8StR,?j>)ZC/cN)9.'MH82QIIfjP44MPMENU":*anM.f$uo\;2_%1dj1F/[9cS4H,E)Q9l>;&HQ8KlCO,u6Bg<FqqE#%6i;XT()#7E^=gY1U'm@GECImbai7B'T=L:77jZY*Xr*`3Cp"Pa5o9K-&Z^LVo'@lit!lqGf+Ng!b`BlA[;VWNk4]e5>9]<cadU?;?-gSb/ZjObCKaQ!Y9IliJ+=-Q'^gn[#dM]G\]14I*9YINj>4i(=*DIuD1t(0^7[.Do>EE&^.g[e)PHn2n9a]1ZPXrPmNo2M"Be^uF;mQ!F0k5PZW:i%%T^Qd48e;H@$HI%jmV-%Q@UHXX[X/%=on)B#[rrNYo>s#do#VpJ(55DAC'(;!9DCYK]KhZ7+p&Pf33c)7BnJi.pV*/ZCYH8f;\EMHG#]5&`8f?K%3J2e,Jaq'C,aIb?oJ4&FqV^`=]7Ah`eDZMe[?A](#GOU*]]^bF6fEm]L^b(mRcc^>te+1^%rhdR1];E(oJm+08qNMDRfQ\jl`Pp>m86l:WbqIFgBG.fkM*6Kq=l(WsCKqa8!%OrRu<#OrK;9Ic@$U.-FRsp3M6(D@,qUg_G$K@&/#j0F*5MIgG(epOg1/2)l]PO"<"JrX/r/DuB]qqB*E]&''2"G<\2UrWRQ"ZA>jZB`RrbS_6qg&8mD80BF[BS"g6.qcaN?M')(&%L.RbW80Q5ZJPfWV0ms+rm9:<@V8U3m\WF`1<ic*fqN4*nK#(7@/DHm"6\Nf.@d+q.d2$nW3.#*C_kfuM++XTPa,$\<G3kQbJY._[k"!\m6ZT0>U$?'8CI!o`26s7ROg!!n7iWAA&U6E0W)]`#u8J%pb,k*oT4<Fm\OI7*?PaBn+bOfmeGZ),YD]"*#d]$\@[YQgYa3]Q37T*Fa!jd./dc;gl8A:3;6+fh8X'R`IVd$>GICQWtr4dUjQ\p/XO5f2[i1:;W'eDe(C+o*NP8kp3*R'MmmWWp;SL:h488*pU)K0nTr#m>EtBZ2BkT-?JXG>3;HOQ-ciJrhKG?/J%&bq-Iij6]j.[X:!qDWehel#D5m2o_g@+Jf!ZEF)0kX27Q9#ZEiS*@&:'SDMB0/,miJ-u0;"nKS^u05FA9kSJ'e3LFuRWg:;=Oq>dO$[G(mYFGD-(RBF4jhn.4:rEe;l7DuqgX<1A\\F^D3m1g'$d0R34sC/"B>?ESD#[q=c7`cXQ$cMjfV`*P8BLAcaE+&7h$LEHMM?V`5?BOsuZ[C,hp%Ho=U]#dkKl[F@;eq"e_Pns4<49TjjrX\tIf?rR)n)fdDFfE-jhJE&^Anf5scr\XAC$>I*)7<gI~>endstream
endobj
30 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2137
>>
stream
Gb"/(CJWtM'`HlqEJ65I)1fhOm)5=BC;g0/F;OHu1Kk*!'iIt.o&qDLB]n"9F^@d8$Y>W]*R+j)pEoV,n\+WVG0PQs+"R?f&\s/c]_fd#ETga3'1>+c&GH!l^H9B.Y+buR-#I3t5a3S*.j,jU@Vi-59/tri866Gl8/jN9[c9%H;@V5r42e8T4&d]KZ/eU4m^0YMY"`b@bT`@9$3]=N\Y_jL?=@ou"nYa%GK]\mT/oBW4Z[R2;7=^P84G#:C^q%JNan`"lWFMVSSFD3%+6GF2+Ado40!f:JJbX>8aug;#dFE2,1Z<pfgJDaW#n>"kkK30%[*O2r0nr<i?D(*J!7brk76kN[Wf#.aDsFo4aj6GA/shnhh:t7qJT*[/cUE,Ar(O-%s8)8!Z"q1>aK>(]O;@uppfGL'Jn8^[4A&5?j&j]@"qB"R:?V[_0e7)9F;Pi't\qhO^h/1GFT5mKW<cg3o<0fQQSCDNkAR:cX<,-+Y>,oYU+.[G]T\n4lkI"W%l2=%Xo7R0oIruMEp+`3>E#B+W;tqQu+[5i8o$M>fu&)gBN.B.$Nem-&KTW20G\De3AEVYq[#&m1[7`V@b]2Hn9*85jKn"5mt%`gq]8c&9m!pGjo*/KGF$2q6HUdC'o&%NH2>R,'[ZgMY0MunFL6Lf$VJ'g9uk%S!-fY-Y_Z'36P(0eADH"1!;!]l:e`FcPEH/bsakQm;:J^6`ji(iZV(NiBY:W?_rtE<i6hj:?!,JNKZ'?(/KQ#R$+WZk3gq%:4F?\NXpKKJ&94N,eq17(&4A'DNi:0ot;4?<9'/Tm`':7Q<FsGj.e)'+-4a@>X?P1E3U`(rF>?N!Eotad5=5bdd&!jm_Snj+)ooXdttfor+-AXksGq0REQG`OCAfm`>(@J._Go7?(C8La]20nI5(L9$s0,`C!d]RT2bPJ:gNN%N^MogUAm$[h'KU\,uRCUH'#rjXd,sKa/$,$oA7lmk;/RfOtal-;FZk72\fYNdJ!8EpM371dj^I65c6T^0>!@lM^X`[0Y$IUDI.3Ec#a$#:XT,O]'k475#(i)_@oQN?Fe7kRDbbX^u7a/bHpp/G^\_dBXhrQ6]E%qjM&M,C7@3s,K"ihMYaS\YK\$UCS=FOTsZol$<m@4^mFVbjiF=9l9$I5(:4Fu:<A'5L?RQA')QsI+/?FT"E/p[%?W&K<77k)P\YSM%MWN,<qK\'65e]6"<!S<R>nTrGXt%H39,am5!M3cXoQ?>5Ne68GNEBSnjM@]HC,@*5UIRfI@\7bI]H`FWU#J#hVNkpc,dN-:+&E=/!ek_@irN3@0U_E,7eK#&_;*5*d1XSTR1d#Gdq.+79>:&ctT;`fkT(-mu@h.U>HA7s!A1EG6LQLmpX*:U]CR[2lt-eCG;dACZ+:q>j6U+idl3(XH:u`gj9?arL>.tQ.FpPr@B-4N#Cpg#70HQ/OXB><S)-3Wp@sP.\hCFJME+fR^,dkA'h(r2%J4dXTb:c>E/<'EE2=6_j/.Tk;3+0N,_`X-EM";!A"u:,S'hT4UCVDZup6&5GP>VSLE'dn>g?jg>r2]D11g/DZ7!)1Z]gk=q<L*reXp[ik>!f%FUCD-,LF`<RP8l\B^%E/h(`j68IOk@H`[oS[dcc@j!QCAI)C*iSNB-ZtW'j6bhh>I%81t^Fgu3X\iqV=g8f"q5`g@G,pLsRX5a<3XQn>_D#J@%oP:%H4(WKI\1(><Lf#BilSQ!CM#Dd=*h*.Bn9J$*4:PX@&^Lf+UH%HI#Ik4\DKe*&$i%gc+Zk"qMjoq&_u^A(=*\)SU.fLb'oiGWDT"*UuX<b<(+EsgfEV2+t&%+Eo*so-_C.O-F:lZg$FO0'ViVV8abOq:03ThG#P._KAE_^No/C%H-QY*60MY]gGtk&G5GJYo5?L*(7_[*B@=WO(J)RT_6'":qZToM)sbWEP'g6`qr]9e\O!J9Jh+#T[MGi^g$A,%)6IjH>EE$HWZA^]O0+5=otjVW$\j"('4o)Np%OWAc!-)djZ(NN`i.cZ(ELDl:h@.CES&te1Me:p*oN_\EeW9#b%?o2pp8sgP_KfT.(:CF<Z@;++ZZeIV#._6^N86;(bN]6$^<e(mG]=YS#V0"LTYIH'"qtXkZ.&p")9HqCQFONR?>rRQI,FDJk;-;~>endstream
endobj
31 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2380
>>
stream
Gb"/(>Ar7S'Roe[3%l6!@V&,gUo4=3d*(";bITQN=('+-/2N;q=ir!M7ec4m-rRG=`,X3?BFCUP;j;MZ4I>LV8dm-IA_h:5!-g\^VnXup*6)X,";*=OJmZNmImt]XM5C>F,aDCB&04?6#cMC2&rAJ=#\RCn9I/T%<5:LkEQhmj%@fR&Oi5_h<,0Ztj@lRa9eT&3Vujs_&o"=2&2%*p=#bMcjA?!"a3c8prqP$"86@%!QkHM8'-J0d4Am.7+?Ak@-A-XKS;d@u[G>@[Pi!\t.#r$b6D0>e8Q*4;XG09]]VU4E8.dGU7cOXl]`$lo6[2n]9Tu#K<<;RCg19@uS8o[gZbn0:RO2rV1PM1XaqWCf_NaF4(gk)RYn]F[06h$:\AeKh1r(Ntd'V>kA,Bn"bKCS/2IQ.e4`sItV)1J:`opiYr8#:6*OP(,0PK4BKri?Kd=).r(*H"3Ukg4b'Hkb=YnRfu6:UB,hU,N1k5qs^7#R)*T.,^*N2=>0de3P.)@>e-oK;Xc%7J]$bH,5Z5l2jVTUiM/SAU-&:l`7Gjn.8ep-n=U%o9_-@>;#tXC$QI2_;4R)P_0q&WF[Y7%u&dL4"!H`OF/Ig1;*3q:0gd"1%232'cTR:Epeh:OhIhLWFKq3-f_$Rnf<0fa*=@3%uK]`>s@6qrah6F&V5G:@8^\6stUq)Z0F6<i26'W%Lf8[^g+$hNC8g(<CB,.3Y34Jo2,SKC*Y_\o?(B8-ZrOImCS]cS6U7/B&=ZCV]g>om:;?H[]!0`ftFB<Aik!$pe;[2)$hMHC<)RI0WDp5!@J/h^K9r0DQ$r[[&8`I!U*XqIm(+mjmqj%DI=Ap_'aX'%"9M)#=^SI6T<N\l"I)'&K6#^3p1KoFH`;f=fsl(U&=6n_4LV]YXUaG:7eY9J+h_K/6`l8ee@3_V<-0Uk;nK1h*`ISp_SjiHYZ="1F?K@S6r2K?V,d%[m16pY?]S'4X2FJtRBuX#WNcN-ZKX.CfZNVqRDuCnnGk1.6s33cR^:r3`F;P*7:`9^fN)UQ`)?W@ML=8AZc51U\[hG&91m;Tt:"RsnIa'!F(XQ)3DrRPdZ,2Y>a5B-Hq_fGi?6,eYNd[LmB^D$$P8.'Vi<o8Rc3[+sN.kMl1pfGYcVJI\59f04.QgH&N_2`!-gI01]BY)0Y_`1%Q>*Z!PbWbu@I$H8t0"M&KJ1r\VD.W&AmM]Y7%Drr:'e%&%Ol,VWT(fZV(-!f96X_[,qV%*lNl,K[f`d1G-@5qYa4e*fD@S1jAer!Yl66F-0D:']?dE"5[fT9m]Lhq"['2cq(l_G9qVjjtIf3Jp]>u.EQqmmiUMnZ<3&A_B7A>3>r_J0-R%H(@=H"/AW2$^hLG#[r5]osP&mu$/MR-kAWLM>8kG+CY21m53J'0iG`f+[q1G&hKhB/a!m3KX1B-hk)/U\Cj*R+:NFB!7?'%"@^E;f:M@=Q/]OleI2Fdt<kGANEC%A%cu5qM5QnA_3e2/CKqjH[KpE=1g8,DC-t+aJZkL#A#d;B<G\.3[^+-q92*MGtC31I,;Wjb)Ndp>j;;%cb-n4()@Pe7O]C0WBJBnG1&Ht1RJ_*3Q5J`mG]V(Q_)?kRWQc"VL=lc']dhkEHETYo-*9I_BuTXLVMR&YGYZ-!68Pm..7@T8a0"*'2!aiOOq7QO=gqI:[B$OcOP6POj;q?!:P=#_nhfQA,%*O<7UoDDLEnb$+A1T%lh$?kME(l9.c_Zn-h$=#b'B@7>G+`O0!!J$;\bt7:8cH#-uNIYc?u&Mask:Bo3VH#eW!uaJ[M(XO;ESDc`egF0f,ZGZ+@hfL'A\5`fffF-tkd):-?ZH1eq;R6TWc=+YXJ-:HLhH5Gd6E?#aA9?9U<O+jqoBr1%MKQcnU-%!>qar#A#jOe,#"8=!g-i6,YhgY'A?<=2Ie5f<9!B+^rD:7#1gWhUQO'I1sAltoGQW7`_7c;57=+ND??I-SRh.5LoL.m%,W/3cUcIrbQ9l@5g0n)C-Y65^fVFcA/gPLkoX0CMd6H7bE@snr&r;BePYoag.F=6ZE!98N9h!!lCDsWDE#u9Ug?M;)(Kfc'HCMY#hFdhHp5K']D\fY>4r'=h]-9"-lPXKbcfIr/[?F3O1bLRdO4uZ3*U7q'8[65rqFnTpZ#XQ+N!5kuB$oup-=Ng(56TeA'"lBq0En_mLdVsniXFk>:I+`^qa41NWlpl6c+0oIK[ib&KMG2gI%1f,A*u'cs:'R)>&3*D='G&%9BO;!..J#'R/d_G]PuC)nbGrXigb]d3e"XX9R@+c>Hu;V@-1K'*Y7$FOr/_37_Dd%:OOaQVGh@J\^omhB=VR&fMeM94*6&^j6H2)',`-rPi_TCZc+EGK(nP^`T'qtj2MYd`Ank7VgO0`orW@JS;p,~>endstream
endobj
32 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2151
>>
stream
Gau0Eh/D%-%"HT1_3Y2U%TP&_o#JH![9^66O=a\#DA^_:a3bO/Nh*MI#'YRq;=\q"-?5/8Z,s@@r^_pBA;5\eIoQI:Y5k`&0_dna178%BapEjf#_pgbh`YEeY%)DnVQ_&`"QGf#\K$c$01'9+\qEpmJT(d$[]k9:@bEZI+d)9[?&uK!^MO7s@5p"l/@Xd)`3g!]-oHkG#R<=tK">\ccL2mfklT#Lq;%S@p1'-hfV/r3+HC#1%Y>=GR21Di#_k5SDW>".P?q[oUPs;O<S0i%_ANgpc;@)"&dH(C8jd-^Kr6C7disfK)GO;[s"aS&"/"S,9`8NBLN&S+dU-U*c^50.2]%fp)Pidk_gD2)_g&kX;>(d&%krF0TH!G^1R3eYnjbG=C&e`X(B;OrIe=IBjPaE&"F\1YHEJ]0"C5a%%cd2NR7'6[]NHZ1[seO1(p),7*4]+c\P!&a9dE'`kVcXaJHQd$rZ9rVN`4CHr!#H.[YeT4'c9Z1+HH:t&;i_b4bf50WYDEl%5$%1,H`cD<Q/?ZXaO<gO*;0.hT0C0bd&IUX#2@A^0m[k2bX:GjVlTf)f&-FMJ:8<*L,o28o:Vdac]<Dj,u-.p8g8$fqA67Bh;oMC3ReDFH5Y1k\DGBkI>(!.\5f13n%0pI"KMEd^Z.CoBe'/YbA*OX(lUAfaaCPaKrG!;bj*8<AXn%==M*d,5P@rbre[]!F3[G'gBU=b0d:29\<GOSQ>m+O(GgGg<_ClhQfH^22Rue\4pCUgkEYp7,s]n2W1>&$A&P#$Ua\Fh5n;XQc_U_-D_4N`nTM3iZUJ$Gh_GZ?f;LKG/m=[S!45(RqrCHc[YTVW-D/?6oJ1,AA6AEC#TZ??re..^j:eA&'7SO]rMA7D8/^&D2O)2aDg%Oc6"_Af5ZI>l2B@(IP<fPaD$)gNLj[$RNK+UYhos)NEEfdTMKY3F3YL1X'e+%jjm)c8_,]!=ktj_g2$]N$MJ[7nIcL+5kCa9DRM<Ph]Rb7=e--&RCYo1"E-keSAP)iSDp[&"-o8V+4G=dff@FUSE?NJ>3q<292ug$G^,,+"BKc^o-"O:`tV2?=KORD%GGg"p3WRU&>IrGdU3[CXmc,l@^Gbq=8Wm4i;<$K1W8mZQ1oNBs/C@2rI8GL:%T[OQ=%<^h8[S72![6ho$U)A`et"4`(FXGp$`>uMif\XjFjZBg;sZ]m)7`KXN#_d5?B#3b!VV7P/@lNSdOu2q!f[mdHpHMs':`JqgQW;ruo,n1:tH!n3gL,PMi&k_)tgjfVi"?]O#pC6*nn#j-QP*n.qmf=/-oM6=BRW5MhEM^hELk3.f9AqX4&hqX_35cBIG#UPZReqF`AZBKu=oUt";_cS!L;B:L:"YA]2_dij)ZO)D+MC3PWOki'GS(*,+J?;HWoiB6?gOts^Qa'mp+F%X859=@X:q@\fiY/'ceq/U>SCd$5JY<Dr\r+$>4KuAY:93B8<Z3_SWef?m:c="+VGg*kMP2/1BXmb]GjukhP&YPu.brYIbq`t@KDklU?Vo'X=qsV@NiP1-]>(CmrAlX=c.oLNfb'$I"$H[%?=fj+uX!sggA^jtb_pXoMA2T&_p!"p2X!ucbU,A9POYipL=\Z</BEO1$Z"O5t[]sKJWt!TEC'->4g+X6Kj),noWd@h[GtW)(Gm>dQR,R9&T>Opa_PP>l<K@]B"iH718b8ZtkdMLgWr6TBMlW/7DXGi7-j4M*LhSYBKD<,O%:'AWY\FEL'".'bmW;#.4JPBh\LOOEk^K1P/M.PQ:qH%n>H2F+#")LhBN/<%],o-MG*2Be1K6TNFD)D?47qKdDP9HEXjU^o>MJhi[`XmmFc"#(mOc,V^a<Zu!2js]W$luVB*=0d8BXgMCg-;Pb8M4L=E`Q^f\88rU0_f3CMjlSFup`bbL!)qG-^4sXH?R9-hEXbd;it^[2<?%nqDrb9+>;Y;^fUX-:8kV.ZJSt'MF>"d+TQ*Q65O"MM=hA8E?>bWk3JVF,>@B*IDN<.AH5cWb7PInigQF_*%EMlIQ`;aX8`?-OlZEOoCV6SNQ]Y-NlWq#;%Ls_o`?cLqGS:(0d1h_CTrYY?TV>^1(,]Q^],JbOG"%.j$%X+I^@:H(r%!#^fE1]Xe%CP>$`#YCrghdu5I,D'$C3o0W$77/Nk]T_BOHSN]7~>endstream
endobj
xref
0 33
0000000000 65535 f
0000000061 00000 n
0000000122 00000 n
0000000229 00000 n
0000000341 00000 n
0000000446 00000 n
0000000651 00000 n
0000000856 00000 n
0000001061 00000 n
0000001171 00000 n
0000001376 00000 n
0000001582 00000 n
0000001788 00000 n
0000001994 00000 n
0000002200 00000 n
0000002406 00000 n
0000002612 00000 n
0000002818 00000 n
0000003024 00000 n
0000003094 00000 n
0000003375 00000 n
0000003513 00000 n
0000005359 00000 n
0000008376 00000 n
0000011362 00000 n
0000014171 00000 n
0000016522 00000 n
0000018381 00000 n
0000021059 00000 n
0000023301 00000 n
0000026165 00000 n
0000028394 00000 n
0000030866 00000 n
trailer
<<
/ID
[<35a2564fd62d2f3a937cd464104f15c9><35a2564fd62d2f3a937cd464104f15c9>]
% ReportLab generated PDF document -- digest (opensource)
/Info 19 0 R
/Root 18 0 R
/Size 33
>>
startxref
33109
%%EOF

View File

@@ -0,0 +1,933 @@
# Les 10 -- Slide-overzicht
## Supabase Authenticatie & Row Level Security (15 slides)
**Cursus:** AI Developer -- NOVI Hogeschool Utrecht
**Duur:** 3 uur (180 minuten) | 09:00 - 12:00
**Project:** Poll App -- authenticatie toevoegen met Supabase Auth
> **Voorkennis studenten:** Supabase basics uit Les 8 (project setup, database, tabellen). Poll App met Next.js 16, TypeScript, Tailwind CSS. Database met `polls` en `options` tabellen. Nog geen authenticatie.
---
## Timing-overzicht
| Tijd | Duur | Onderwerp | Slide(s) | Vorm |
|---------------|--------|----------------------------------------|----------|---------------------|
| 09:00 - 09:05 | 5 min | Titelslide | 1 | Presentatie |
| 09:05 - 09:10 | 5 min | Planning vandaag | 2 | Presentatie |
| 09:10 - 09:15 | 5 min | Terugblik Les 8-9 | 3 | Presentatie |
| 09:15 - 09:25 | 10 min | Eindexamenopdracht | 4 | Presentatie |
| 09:25 - 09:30 | 5 min | Wat is authenticatie? | 5 | Presentatie |
| 09:30 - 09:40 | 10 min | Supabase Auth -- 3 methodes | 6 | Presentatie + Demo |
| 09:40 - 09:45 | 5 min | Hoe werkt een sessie? | 7 | Presentatie |
| 09:45 - 09:55 | 10 min | Auth in Next.js | 8 | Presentatie |
| 09:55 - 10:00 | 5 min | Row Level Security (RLS) | 9 | Presentatie |
| 10:00 - 10:15 | 15 min | Pauze | 10 | Pauze |
| 10:15 - 10:30 | 15 min | Hands-on: Auth opzetten in Supabase | 11 | Hands-on (ref) |
| 10:30 - 10:55 | 25 min | Hands-on: Login & registratie | 12 | Hands-on (ref) |
| 10:55 - 11:10 | 15 min | Hands-on: Sessie & beschermde routes | 13 | Hands-on (ref) |
| 11:10 - 11:25 | 15 min | Hands-on: Basis RLS | 14 | Hands-on (ref) |
| 11:45 - 12:00 | 15 min | Samenvatting & huiswerk | 15 | Presentatie |
---
## Slide-indeling
---
### Slide 1: Titelslide
**Timing:** 09:00 - 09:05 (5 min)
**Titel:** Les 10 -- Supabase Auth & RLS
**Ondertitel:** Authenticatie toevoegen aan de Poll App
```
+========================================================+
| |
| LES 10: SUPABASE AUTH & RLS |
| |
| Authenticatie toevoegen aan de Poll App |
| |
| Tim -- NOVI Hogeschool Utrecht |
| AI Developer Cursus |
| |
+========================================================+
```
**Kernpunten:**
- Les 10 van de AI Developer cursus
- Vandaag draaien we alles om beveiliging: wie mag wat?
- Supabase Auth + Row Level Security
- We bouwen verder op de Poll App uit Les 8-9
**Spreektekst:**
- "Goedemorgen allemaal, welkom bij Les 10!"
- "Vandaag gaan we onze Poll App beveiligen met echte authenticatie."
- "Na vandaag kan niet zomaar iedereen meer polls aanmaken -- je moet ingelogd zijn."
- "We gebruiken Supabase Auth, wat het hele inlogproces voor ons afhandelt."
- "En we leren over Row Level Security: beveiliging op database-niveau."
---
### Slide 2: Planning Vandaag
**Timing:** 09:05 - 09:10 (5 min)
**Titel:** Planning Vandaag
**Ondertitel:** Wat gaan we doen en leren?
```
+========================================================+
| PLANNING VANDAAG |
| |
| 09:00 Theorie: Auth & RLS concepten |
| 09:15 Eindexamenopdracht introductie |
| 09:25 Supabase Auth deep dive |
| 10:00 -- PAUZE -- |
| 10:15 Hands-on: Auth opzetten |
| 10:30 Hands-on: Login & registratie |
| 10:55 Hands-on: Sessie & beschermde routes |
| 11:10 Hands-on: Basis RLS |
| 11:45 Samenvatting & huiswerk |
| |
| LEERDOELEN: |
| [x] Supabase Auth instellen (email/password) |
| [x] Login/registratie pagina bouwen |
| [x] Sessie beheren (wie is ingelogd?) |
| [x] Row Level Security policies schrijven |
+========================================================+
```
**Kernpunten:**
- Eerste helft: theorie over authenticatie, autorisatie, sessies
- Introductie van de eindexamenopdracht
- Tweede helft: volledig hands-on, stap voor stap
- Vier concrete leerdoelen die je vandaag behaalt
**Spreektekst:**
- "Dit is de planning voor vandaag. We beginnen met een stuk theorie."
- "Daarna introduceer ik de eindexamenopdracht -- heel belangrijk."
- "Na de pauze gaan we volledig hands-on. Ik laat reference slides zien die op het scherm blijven staan terwijl jullie werken."
- "Aan het einde van vandaag heb je een werkende login, registratie, en beveiligde database."
- "Vier leerdoelen: Auth instellen, login bouwen, sessie beheren, en RLS schrijven."
---
### Slide 3: Terugblik Les 8-9
**Timing:** 09:10 - 09:15 (5 min)
**Titel:** Terugblik Les 8-9
**Ondertitel:** Waar staan we nu?
```
+========================================================+
| TERUGBLIK LES 8-9 |
| |
| +------------------+ +-------------------------+ |
| | SUPABASE | | NEXT.JS APP | |
| | | | | |
| | polls | | / (homepage) | |
| | +----------+ | | /polls (lijst) | |
| | | id | |<-->| /polls/new (aanmaken) | |
| | | question | | | /polls/[id] (stemmen) | |
| | +----------+ | | | |
| | | | | |
| | options | | TypeScript + Tailwind | |
| | +----------+ | | | |
| | | id | | +-------------------------+ |
| | | poll_id | | |
| | | text | | PROBLEEM: |
| | | votes | | Iedereen kan alles! |
| | +----------+ | Geen login nodig |
| +------------------+ Geen beveiliging |
+========================================================+
```
**Kernpunten:**
- Supabase project met `polls` en `options` tabellen
- Next.js app met pagina's voor lijst, aanmaken en stemmen
- Alles werkt, maar er is geen beveiliging
- Het probleem: iedereen kan polls aanmaken en data manipuleren
**Spreektekst:**
- "Laten we even terugkijken naar waar we staan."
- "We hebben een werkende Poll App: je kunt polls bekijken, nieuwe polls aanmaken, en stemmen."
- "De database draait op Supabase met twee tabellen: polls en options."
- "Maar... er is een groot probleem. Iedereen kan alles doen. Er is geen login."
- "Als je de Supabase URL en API key kent, kun je direct de database benaderen."
- "Vandaag gaan we dat oplossen!"
---
### Slide 4: Eindexamenopdracht
**Timing:** 09:15 - 09:25 (10 min)
**Titel:** Eindexamenopdracht
**Ondertitel:** Vrije keuze app -- jouw project!
```
+========================================================+
| EINDEXAMENOPDRACHT |
| |
| Bouw je eigen full-stack applicatie! |
| |
| VEREISTEN: |
| +----------------------------------------------------+|
| | [x] Next.js 16 + TypeScript ||
| | [x] Supabase (database + auth) ||
| | [x] Authenticatie (login/registratie) ||
| | [x] Row Level Security (RLS policies) ||
| | [x] CRUD operaties (Create, Read, Update, Delete) ||
| | [x] Deployed (Vercel + Supabase) ||
| | [x] Nette code (componenten, types, error handling)||
| +----------------------------------------------------+|
| |
| TIJDLIJN: |
| Les 10 (vandaag) Introductie + Auth leren |
| Les 11-12 Bouwen aan je project |
| Les 13 Inleveren + presentatie |
| |
| IDEEN: Todo app, Blog, Recepten, Budget tracker, |
| Quiz app, Bookmark manager, Habit tracker... |
+========================================================+
```
**Kernpunten:**
- Vrije keuze: kies zelf welke app je bouwt
- Zeven technische vereisten waar je aan moet voldoen
- Alles wat we in de cursus geleerd hebben komt samen
- Tijdlijn: 3-4 lessen om te bouwen, daarna inleveren + presentatie
- Voorbeelden ter inspiratie, maar eigen ideeen zijn welkom
**Spreektekst:**
- "Nu iets heel belangrijks: de eindexamenopdracht."
- "Jullie gaan je eigen full-stack applicatie bouwen. Vrije keuze -- dus kies iets dat je leuk vindt."
- "Er zijn zeven vereisten. Laten we ze doorlopen."
- "Next.js 16 met TypeScript -- dat kennen jullie al."
- "Supabase voor de database EN authenticatie -- dat leren we vandaag."
- "Je app moet login en registratie hebben, en de database moet beveiligd zijn met RLS."
- "CRUD: je moet data kunnen aanmaken, lezen, updaten en verwijderen."
- "De app moet gedeployed zijn op Vercel, zodat ik hem kan bekijken."
- "En nette code: goede componenten, TypeScript types, error handling."
- "Qua tijdlijn: vandaag leer je Auth. De komende lessen heb je tijd om te bouwen. En dan presenteren."
- "Begin alvast na te denken over wat je wilt bouwen. Een todo app, blog, recepten-app, budget tracker... het mag allemaal."
---
### Slide 5: Wat is Authenticatie?
**Timing:** 09:25 - 09:30 (5 min)
**Titel:** Wat is Authenticatie?
**Ondertitel:** Auth vs Autorisatie
```
+========================================================+
| WAT IS AUTHENTICATIE? |
| |
| +------------------------+ +------------------------+|
| | | | ||
| | AUTHENTICATIE | | AUTORISATIE ||
| | | | ||
| | "Wie ben je?" | | "Wat mag je?" ||
| | | | ||
| | +--------+ | | +--------+ ||
| | | SLOT | | | | SCHILD | ||
| | | [====] | | | | {X} | ||
| | +--------+ | | +--------+ ||
| | | | ||
| | - Inloggen | | - Rechten ||
| | - Email + wachtwoord | | - Rollen (admin/user) ||
| | - Identiteit bewijzen | | - Wat mag je zien? ||
| | | | ||
| +------------------------+ +------------------------+|
| |
| VOORBEELD: |
| Bioscoop: ticket tonen = auth | stoel kiezen = autor. |
| School: pasje scannen = auth | lokaal betreden = a. |
+========================================================+
```
**Kernpunten:**
- Authenticatie = wie ben je? Identiteit bewijzen.
- Autorisatie = wat mag je? Rechten en rollen.
- Twee aparte concepten die samenwerken
- Vandaag leren we beide: Auth met Supabase, Autorisatie met RLS
**Spreektekst:**
- "Voordat we gaan bouwen, moeten we twee begrippen begrijpen."
- "Authenticatie: wie ben je? Je bewijst je identiteit. Bijvoorbeeld door in te loggen met email en wachtwoord."
- "Autorisatie: wat mag je? Welke rechten heb je? Mag je alleen lezen, of ook schrijven?"
- "Denk aan een bioscoop. Je laat je ticket zien bij de ingang -- dat is authenticatie. Maar je mag alleen in zaal 3 zitten -- dat is autorisatie."
- "Of school: je scant je pasje -- authenticatie. Maar je mag niet zomaar in elk lokaal -- autorisatie."
- "Vandaag leren we beide. Supabase Auth regelt de authenticatie. Row Level Security regelt de autorisatie."
---
### Slide 6: Supabase Auth -- 3 Methodes
**Timing:** 09:30 - 09:40 (10 min)
**Titel:** Supabase Auth -- 3 Methodes
**Ondertitel:** Hoe kunnen gebruikers inloggen?
```
+========================================================+
| SUPABASE AUTH -- 3 METHODES |
| |
| +----------------+ +----------------+ +--------------+|
| | EMAIL/PASSWORD | | MAGIC LINK | | GOOGLE OAUTH ||
| | | | | | ||
| | +------------+ | | +------------+ | | +----------+||
| | | email: | | | | email: | | | | Google |||
| | | [........] | | | | [........] | | | | [G] |||
| | | password: | | | | | | | | Login |||
| | | [........] | | | | Klik link | | | +----------+||
| | | [INLOGGEN] | | | | in je mail | | | ||
| | +------------+ | | +------------+ | | ||
| | | | | | ||
| | Klassiek | | Geen wachtw. | | Social login ||
| | Makkelijk te | | Veilig | | Makkelijkst ||
| | begrijpen | | Simpel | | voor users ||
| | | | | | ||
| | WIJ GEBRUIKEN | | OPTIONEEL | | NIET VANDAAG ||
| | DIT VANDAAG | | (bonus) | | (complex) ||
| +----------------+ +----------------+ +--------------+|
+========================================================+
```
**Kernpunten:**
- Email/Password: klassieke methode, makkelijk te begrijpen, we gebruiken dit vandaag
- Magic Link: email zonder wachtwoord, gebruiker klikt link in mailbox
- Google OAuth: social login via Google account, meest gebruiksvriendelijk maar complexer
- Supabase ondersteunt alle drie out-of-the-box
- In het Supabase Dashboard kun je providers aan/uitzetten
**Spreektekst:**
- "Supabase biedt drie manieren om in te loggen. Laten we ze bekijken."
- "Nummer 1: email en wachtwoord. De klassieke manier. Je maakt een account aan met je email en een wachtwoord, en daarna log je in. Dit gaan we vandaag gebruiken."
- "Nummer 2: Magic Link. Je vult alleen je email in, en Supabase stuurt een linkje. Klik erop en je bent ingelogd. Geen wachtwoord nodig. Dit voegen we toe als bonus."
- "Nummer 3: Google OAuth. De 'Log in met Google' knop die je overal ziet. Heel handig voor gebruikers, maar de setup is complexer. Dat doen we niet vandaag."
- "Laat me even het Supabase Dashboard laten zien waar je deze providers configureert..."
- *Tim opent Supabase Dashboard > Authentication > Providers en laat de opties zien*
- "Zie je? Email staat standaard aan. Je kunt hier ook Magic Link, Google, GitHub en meer aanzetten."
---
### Slide 7: Hoe Werkt een Sessie?
**Timing:** 09:40 - 09:45 (5 min)
**Titel:** Hoe Werkt een Sessie?
**Ondertitel:** Van login tot beveiligde requests
```
+========================================================+
| HOE WERKT EEN SESSIE? |
| |
| +--------+ +----------+ +-----------+ |
| | USER |----->| SUPABASE |----->| JWT TOKEN | |
| | login | | Auth | | (sessie) | |
| +--------+ +----------+ +-----------+ |
| | | |
| | email + wachtwoord | |
| | v |
| | +---------------+ |
| | | COOKIE | |
| | | (browser) | |
| | +---------------+ |
| | | |
| v v |
| +---------------------------------------------+ |
| | ELKE REQUEST | |
| | Browser stuurt cookie automatisch mee | |
| | Middleware checkt: is de sessie geldig? | |
| | Zo ja: door naar de pagina | |
| | Zo nee: redirect naar /login | |
| +---------------------------------------------+ |
+========================================================+
```
**Kernpunten:**
- User logt in met email + wachtwoord
- Supabase Auth verifieert en stuurt een JWT token terug
- JWT wordt opgeslagen als cookie in de browser
- Bij elke request stuurt de browser de cookie automatisch mee
- Middleware controleert of de sessie geldig is
**Spreektekst:**
- "Hoe werkt een sessie eigenlijk? Laten we het stap voor stap bekijken."
- "Stap 1: de gebruiker logt in met email en wachtwoord."
- "Stap 2: Supabase Auth controleert de gegevens. Kloppen ze? Dan krijg je een JWT token terug."
- "JWT staat voor JSON Web Token. Het is een gecodeerde string die zegt: 'deze gebruiker is ingelogd en dit is hun user ID'."
- "Stap 3: dat token wordt opgeslagen als cookie in de browser."
- "Stap 4: bij elke pagina die je bezoekt, stuurt de browser die cookie automatisch mee."
- "In onze Next.js app hebben we middleware die bij elke request checkt: is er een geldige sessie? Zo nee, dan redirect hij naar /login."
- "Dit is belangrijk om te begrijpen. Je hoeft het niet zelf te bouwen -- Supabase en de @supabase/ssr package regelen het voor je."
---
### Slide 8: Auth in Next.js
**Timing:** 09:45 - 09:55 (10 min)
**Titel:** Auth in Next.js
**Ondertitel:** Architectuur en bestandsstructuur
```
+========================================================+
| AUTH IN NEXT.JS -- ARCHITECTUUR |
| |
| Package: @supabase/ssr |
| |
| +----------------------------------------------------+|
| | BESTANDEN DIE WE GAAN MAKEN: ||
| | ||
| | src/ ||
| | +-- lib/supabase/ ||
| | | +-- client.ts <-- Browser client ||
| | | +-- server.ts <-- Server client ||
| | | ||
| | +-- middleware.ts <-- Sessie refreshen ||
| | | ||
| | +-- app/ ||
| | +-- auth/ ||
| | | +-- callback/ ||
| | | +-- route.ts <-- Auth callback ||
| | +-- login/ ||
| | +-- page.tsx <-- Login pagina ||
| +----------------------------------------------------+|
| |
| BROWSER CLIENT SERVER CLIENT |
| - Gebruikt in - Gebruikt in Server |
| Client Components Components & API routes |
| - createBrowser... - createServer... |
| - Leest cookies - Leest EN schrijft cookies |
+========================================================+
```
**Kernpunten:**
- `@supabase/ssr` is de package die cookies en sessies afhandelt
- Browser client: voor Client Components (interactieve UI)
- Server client: voor Server Components en API routes (data ophalen)
- Middleware: draait bij elke request, refresht de sessie
- Auth callback route: verwerkt magic links en OAuth redirects
**Spreektekst:**
- "Laten we kijken naar de architectuur. We installeren het package @supabase/ssr."
- "We maken twee Supabase clients. Waarom twee? Omdat Next.js twee omgevingen heeft."
- "De browser client gebruik je in Client Components -- dat zijn de interactieve componenten met useState en onClick."
- "De server client gebruik je in Server Components en API routes -- dat is code die op de server draait."
- "Het verschil: de browser client kan alleen cookies lezen. De server client kan ze ook schrijven."
- "Dan hebben we middleware.ts. Die draait bij ELKE request. Zijn taak: de sessie refreshen zodat de gebruiker ingelogd blijft."
- "En de auth callback route. Die is nodig voor magic links. Als een gebruiker op een magic link klikt, komt hij terug op /auth/callback, en die route wisselt de code om voor een sessie."
- "Na de pauze gaan we al deze bestanden stap voor stap aanmaken."
---
### Slide 9: Row Level Security (RLS)
**Timing:** 09:55 - 10:00 (5 min)
**Titel:** Row Level Security (RLS)
**Ondertitel:** Beveiliging op database-niveau
```
+========================================================+
| ROW LEVEL SECURITY (RLS) |
| |
| ZONDER RLS: |
| +--------------------------------------------+ |
| | polls tabel GEEN SLOT | |
| | id | question | created_by | |
| | 1 | Beste taal? | user_abc <- leesbaar| |
| | 2 | Beste editor? | user_xyz <- leesbaar| |
| | ** Iedereen kan ALLES lezen, schrijven, | |
| | updaten en verwijderen ** | |
| +--------------------------------------------+ |
| |
| MET RLS: |
| +--------------------------------------------+ |
| | polls tabel [SLOT] | |
| | id | question | created_by | |
| | 1 | Beste taal? | user_abc <- policy! | |
| | 2 | Beste editor? | user_xyz <- policy! | |
| | | |
| | POLICIES: | |
| | SELECT: iedereen mag lezen | |
| | INSERT: alleen ingelogde users | |
| | UPDATE: alleen eigen polls | |
| | DELETE: alleen eigen polls | |
| +--------------------------------------------+ |
+========================================================+
```
**Kernpunten:**
- Zonder RLS: de API key geeft volledige toegang tot alle data
- Met RLS: elke query wordt gecheckt tegen policies
- Policies definieer je per tabel, per operatie (SELECT, INSERT, UPDATE, DELETE)
- RLS draait op database-niveau -- je kunt het niet omzeilen vanuit de frontend
- Dit is de autorisatie-laag die we eerder bespraken
**Spreektekst:**
- "Nu het laatste theoretische concept: Row Level Security, oftewel RLS."
- "Onthoud: onze Supabase API key staat in de frontend code. Iedereen kan die zien."
- "Zonder RLS kan iemand met die key ALLES doen: lezen, schrijven, verwijderen. Dat is een groot beveiligingsprobleem."
- "Met RLS zet je een slot op je tabellen. Je definieert policies: regels die zeggen wie wat mag."
- "Bijvoorbeeld: iedereen mag polls LEZEN, maar alleen ingelogde users mogen polls AANMAKEN."
- "Of: je mag alleen je EIGEN polls updaten of verwijderen."
- "Het mooie van RLS is dat het op database-niveau draait. Zelfs als iemand direct de API aanroept, worden de policies gecontroleerd."
- "Na de pauze gaan we dit ook daadwerkelijk instellen."
---
### Slide 10: Pauze
**Timing:** 10:00 - 10:15 (15 min)
**Titel:** Pauze
**Ondertitel:** 15 minuten
```
+========================================================+
| |
| |
| PAUZE |
| |
| 15 minuten |
| |
| 10:00 -- 10:15 |
| |
| |
| Tip: Denk alvast na over je eindexamenopdracht! |
| Welke app wil je bouwen? |
| |
| |
+========================================================+
```
**Kernpunten:**
- 15 minuten pauze
- Studenten kunnen alvast nadenken over hun eindexamenopdracht
- Na de pauze: hands-on, dus zorg dat je laptop klaar staat
**Spreektekst:**
- "Oké, tijd voor pauze! 15 minuten."
- "Denk alvast na over wat je wilt bouwen voor je eindexamenopdracht."
- "Na de pauze gaan we direct aan de slag. Zorg dat je Cursor open hebt en je Supabase project klaarstaat."
- "Tot zo!"
---
### Slide 11: Hands-on -- Auth Opzetten in Supabase (REFERENCE SLIDE)
**Timing:** 10:15 - 10:30 (15 min)
**Titel:** Hands-on -- Auth Opzetten
**Ondertitel:** REFERENCE SLIDE -- blijft op het scherm
```
+========================================================+
| HANDS-ON: AUTH OPZETTEN [REF] |
| |
| STAP 1: Starter project |
| $ Pak de starter zip uit en open in Cursor |
| $ npm install |
| |
| STAP 2: Environment variabelen |
| Maak .env.local: |
| NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co |
| NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci... |
| |
| STAP 3: Packages installeren |
| $ npm install @supabase/ssr @supabase/supabase-js |
| |
| STAP 4: Browser client (src/lib/supabase/client.ts) |
| import { createBrowserClient } from '@supabase/ssr' |
| export function createClient() { |
| return createBrowserClient( |
| process.env.NEXT_PUBLIC_SUPABASE_URL!, |
| process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! |
| ) |
| } |
| |
| STAP 5: Server client (src/lib/supabase/server.ts) |
| import { createServerClient } from '@supabase/ssr' |
| import { cookies } from 'next/headers' |
| export async function createClient() { |
| const cookieStore = await cookies() |
| return createServerClient( |
| process.env.NEXT_PUBLIC_SUPABASE_URL!, |
| process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, |
| { cookies: { |
| getAll() { return cookieStore.getAll() }, |
| setAll(cookiesToSet) { |
| cookiesToSet.forEach(({ name, value, opts })|
| => cookieStore.set(name, value, opts)) |
| }, |
| }} |
| ) |
| } |
| |
| STAP 6: Middleware (src/middleware.ts) |
| import { createServerClient } from '@supabase/ssr' |
| import { NextResponse, type NextRequest } from 'next' |
| export async function middleware(request: NextRequest) |
| // Refresh sessie bij elke request |
| // Redirect naar /login als niet ingelogd |
| |
| STAP 7: Auth callback (src/app/auth/callback/route.ts)|
| // Verwerkt magic link codes |
| // Wisselt code om voor sessie |
| |
| STAP 8: Supabase Dashboard |
| Authentication > Providers > Email aanvinken |
+========================================================+
```
**Kernpunten:**
- Stap 1-2: Project openen en environment variabelen instellen
- Stap 3: Twee packages installeren: `@supabase/ssr` en `@supabase/supabase-js`
- Stap 4: Browser client voor Client Components
- Stap 5: Server client met cookie-handling voor Server Components
- Stap 6: Middleware die sessie refresht en ongeautoriseerde users redirect
- Stap 7: Auth callback route voor magic links
- Stap 8: Email provider aanzetten in Supabase Dashboard
**Spreektekst:**
- "Oké, we gaan aan de slag! Deze slide blijft op het scherm. Volg de stappen."
- "Stap 1: pak de starter zip uit die ik gestuurd heb, en open het project in Cursor. Doe npm install."
- "Stap 2: maak een .env.local bestand. Kopieer je Supabase URL en anon key uit het dashboard."
- "Stap 3: installeer de twee packages: @supabase/ssr en @supabase/supabase-js."
- "Stap 4: maak de browser client aan. Dit is heel simpel -- je roept createBrowserClient aan met je URL en key."
- "Stap 5: de server client is iets complexer. Die heeft cookie-handling nodig. Kopieer de code van de slide."
- "Stap 6: de middleware. Die zorgt dat de sessie bij elke request ververst wordt. En hij redirect naar /login als je niet ingelogd bent."
- "Stap 7: de auth callback route. Die heb je nodig voor magic links."
- "Stap 8: ga naar je Supabase Dashboard, Authentication, Providers, en zet Email aan."
- "Neem de tijd, volg het stap voor stap. Ik loop rond om te helpen."
---
### Slide 12: Hands-on -- Login & Registratie (REFERENCE SLIDE)
**Timing:** 10:30 - 10:55 (25 min)
**Titel:** Hands-on -- Login & Registratie
**Ondertitel:** REFERENCE SLIDE -- blijft op het scherm
```
+========================================================+
| HANDS-ON: LOGIN & REGISTRATIE [REF] |
| |
| STAP 1: Maak src/app/login/page.tsx |
| 'use client' |
| import { useState } from 'react' |
| import { createClient } from '@/lib/supabase/client' |
| import { useRouter } from 'next/navigation' |
| |
| STAP 2: State |
| const [email, setEmail] = useState('') |
| const [password, setPassword] = useState('') |
| const [loading, setLoading] = useState(false) |
| const [message, setMessage] = useState('') |
| const router = useRouter() |
| const supabase = createClient() |
| |
| STAP 3: handleSignUp functie |
| async function handleSignUp() { |
| setLoading(true) |
| const { error } = await supabase.auth.signUp({ |
| email, password |
| }) |
| if (error) setMessage(error.message) |
| else setMessage('Check je email voor confirmatie!') |
| setLoading(false) |
| } |
| |
| STAP 4: handleSignIn functie |
| async function handleSignIn() { |
| setLoading(true) |
| const { error } = await |
| supabase.auth.signInWithPassword({ |
| email, password |
| }) |
| if (error) setMessage(error.message) |
| else router.push('/') |
| setLoading(false) |
| } |
| |
| STAP 5: handleMagicLink functie (bonus) |
| async function handleMagicLink() { |
| setLoading(true) |
| const { error } = await |
| supabase.auth.signInWithOtp({ email }) |
| if (error) setMessage(error.message) |
| else setMessage('Check je email voor de link!') |
| setLoading(false) |
| } |
| |
| STAP 6: Formulier JSX |
| <form> |
| <input type="email" value={email} |
| onChange={e => setEmail(e.target.value)} /> |
| <input type="password" value={password} |
| onChange={e => setPassword(e.target.value)} />|
| <button onClick={handleSignIn}>Inloggen</button> |
| <button onClick={handleSignUp}>Registreren</button> |
| <button onClick={handleMagicLink}>Magic Link</but.> |
| {message && <p>{message}</p>} |
| </form> |
| |
| STAP 7: Error handling & loading states |
| - disabled={loading} op alle buttons |
| - Loading tekst: "Even geduld..." |
| - Error message tonen in rode tekst |
| |
| STAP 8: Testen |
| - Registreer een account (check Supabase Dashboard) |
| - Log in met dat account |
| - Probeer magic link (check je email) |
+========================================================+
```
**Kernpunten:**
- Login pagina als Client Component (`'use client'`)
- State voor email, password, loading en foutmeldingen
- `signUp()`: registratie, stuurt een bevestigingsmail
- `signInWithPassword()`: inloggen met email + wachtwoord
- `signInWithOtp()`: magic link versturen (bonus)
- Formulier met drie buttons: inloggen, registreren, magic link
- Na succesvolle login: redirect naar homepage
- Testen: registreer, log in, check Supabase Dashboard
**Spreektekst:**
- "Nu gaan we de login pagina bouwen. Dit wordt een Client Component omdat we interactie nodig hebben."
- "Stap 1: maak het bestand src/app/login/page.tsx aan."
- "Stap 2: we hebben vier stukken state nodig: email, password, loading, en een message voor foutmeldingen."
- "Stap 3: de handleSignUp functie. Die roept supabase.auth.signUp aan met de email en het wachtwoord. Als het lukt, krijgt de gebruiker een bevestigingsmail."
- "Stap 4: handleSignIn. Dit is het echte inloggen. signInWithPassword. Als het lukt, redirect je naar de homepage."
- "Stap 5: de magic link als bonus. signInWithOtp -- je geeft alleen de email mee, geen wachtwoord."
- "Stap 6: het formulier. Twee input velden en drie buttons. Simpel."
- "Stap 7: vergeet de error handling niet! Disable de buttons als het laden is, en toon foutmeldingen."
- "Stap 8: testen! Registreer een account en check in het Supabase Dashboard of de user verschijnt onder Authentication > Users."
- "Dit is de meest uitgebreide stap, dus neem je tijd. Ik loop rond."
---
### Slide 13: Hands-on -- Sessie & Beschermde Routes (REFERENCE SLIDE)
**Timing:** 10:55 - 11:10 (15 min)
**Titel:** Hands-on -- Sessie & Beschermde Routes
**Ondertitel:** REFERENCE SLIDE -- blijft op het scherm
```
+========================================================+
| HANDS-ON: SESSIE & BESCHERMDE ROUTES [REF] |
| |
| STAP 1: User ophalen in layout/navbar |
| // In een Server Component: |
| import { createClient } from '@/lib/supabase/server' |
| |
| const supabase = await createClient() |
| const { data: { user } } = await |
| supabase.auth.getUser() |
| |
| STAP 2: Conditional rendering |
| {user ? ( |
| <div> |
| <span>{user.email}</span> |
| <LogoutButton /> |
| </div> |
| ) : ( |
| <a href="/login">Inloggen</a> |
| )} |
| |
| STAP 3: LogoutButton component (Client Component) |
| 'use client' |
| import { createClient } from '@/lib/supabase/client' |
| import { useRouter } from 'next/navigation' |
| |
| export function LogoutButton() { |
| const router = useRouter() |
| const supabase = createClient() |
| |
| async function handleSignOut() { |
| await supabase.auth.signOut() |
| router.push('/login') |
| } |
| |
| return ( |
| <button onClick={handleSignOut}>Uitloggen</button>|
| ) |
| } |
| |
| STAP 4: Redirect na logout naar /login |
| router.push('/login') in handleSignOut |
| |
| STAP 5: Testen |
| [x] Ga naar / zonder login -> redirect naar /login |
| [x] Log in -> je ziet je email in de navbar |
| [x] Klik uitloggen -> redirect naar /login |
| [x] Na uitloggen kun je niet bij / komen |
+========================================================+
```
**Kernpunten:**
- `getUser()` op de server om de huidige gebruiker op te halen
- Conditional rendering: toon email + logout als ingelogd, anders login-link
- LogoutButton is een Client Component (interactief)
- `signOut()` verwijdert de sessie, daarna redirect naar /login
- Middleware handelt de bescherming af: geen sessie = redirect naar /login
**Spreektekst:**
- "Nu gaan we de sessie zichtbaar maken in de UI."
- "Stap 1: in je layout of navbar haal je de user op. Let op: dit is een Server Component, dus gebruik de server client."
- "supabase.auth.getUser() geeft de huidige ingelogde user terug, of null als niemand ingelogd is."
- "Stap 2: conditional rendering. Als er een user is, toon de email en een logout button. Anders toon een link naar /login."
- "Stap 3: de LogoutButton moet een Client Component zijn, want hij heeft een onClick nodig."
- "De handleSignOut functie roept supabase.auth.signOut() aan. Dat verwijdert de sessie cookie."
- "Stap 4: na het uitloggen stuur je de gebruiker terug naar /login met router.push."
- "Stap 5: testen! Open een incognito venster en ga naar de homepage. Je zou geredirect moeten worden naar /login."
- "Log in, en je ziet je email in de navbar. Klik uitloggen, en je gaat terug naar /login."
- "Als dit allemaal werkt, heb je een werkend auth-systeem!"
---
### Slide 14: Hands-on -- Basis RLS (REFERENCE SLIDE)
**Timing:** 11:10 - 11:25 (15 min)
**Titel:** Hands-on -- Basis RLS
**Ondertitel:** REFERENCE SLIDE -- blijft op het scherm
```
+========================================================+
| HANDS-ON: BASIS RLS [REF] |
| |
| Ga naar Supabase Dashboard > SQL Editor |
| |
| STAP 1: Enable RLS op polls |
| ALTER TABLE polls ENABLE ROW LEVEL SECURITY; |
| |
| STAP 2: Enable RLS op options |
| ALTER TABLE options ENABLE ROW LEVEL SECURITY; |
| |
| STAP 3: Iedereen mag polls LEZEN |
| CREATE POLICY "Iedereen mag polls lezen" |
| ON polls FOR SELECT |
| TO public |
| USING (true); |
| |
| STAP 4: Alleen ingelogde users mogen polls AANMAKEN |
| CREATE POLICY "Ingelogde users mogen polls aanmaken" |
| ON polls FOR INSERT |
| TO authenticated |
| WITH CHECK (true); |
| |
| STAP 5: Iedereen mag options LEZEN |
| CREATE POLICY "Iedereen mag options lezen" |
| ON options FOR SELECT |
| TO public |
| USING (true); |
| |
| STAP 6: Ingelogde users mogen options AANMAKEN |
| CREATE POLICY "Ingelogde users mogen options maken" |
| ON options FOR INSERT |
| TO authenticated |
| WITH CHECK (true); |
| |
| STAP 7: Ingelogde users mogen stemmen (UPDATE) |
| CREATE POLICY "Ingelogde users mogen stemmen" |
| ON options FOR UPDATE |
| TO authenticated |
| USING (true) |
| WITH CHECK (true); |
| |
| STAP 8: Testen -- app werkt als ingelogd |
| [x] Polls laden nog steeds |
| [x] Je kunt nog steeds een poll aanmaken |
| [x] Je kunt nog steeds stemmen |
| |
| STAP 9: Testen -- zonder login geen schrijfrechten |
| [x] Polls zijn zichtbaar (SELECT = public) |
| [x] Poll aanmaken FAALT (INSERT = authenticated) |
| [x] Stemmen FAALT (UPDATE = authenticated) |
+========================================================+
```
**Kernpunten:**
- RLS moet per tabel ingeschakeld worden met `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`
- Na het inschakelen van RLS is ALLES geblokkeerd totdat je policies aanmaakt
- `TO public` = iedereen (ook niet ingelogd)
- `TO authenticated` = alleen ingelogde users
- `USING (true)` = geen extra voorwaarden (alle rijen)
- `WITH CHECK (true)` = geen extra voorwaarden bij schrijven
- Altijd testen: werkt het als ingelogd? Faalt het zonder login?
**Spreektekst:**
- "De laatste stap: Row Level Security! Open de SQL Editor in je Supabase Dashboard."
- "Stap 1 en 2: enable RLS op beide tabellen. LET OP: zodra je dit doet, is alles geblokkeerd. Je app zal even niet werken totdat je policies toevoegt."
- "Stap 3: de eerste policy. Iedereen mag polls lezen. FOR SELECT, TO public, USING true. Public betekent iedereen, ook niet-ingelogde gebruikers."
- "Stap 4: alleen ingelogde users mogen polls aanmaken. FOR INSERT, TO authenticated. Authenticated betekent: je moet een geldige sessie hebben."
- "Stap 5 en 6: hetzelfde voor de options tabel. Iedereen mag lezen, alleen ingelogde users mogen aanmaken."
- "Stap 7: ingelogde users mogen stemmen. Stemmen is een UPDATE operatie -- je update het votes veld."
- "Stap 8: test als ingelogde user. Alles zou moeten werken zoals voorheen."
- "Stap 9: test zonder login. Open een incognito venster. Polls zijn zichtbaar -- want SELECT is public. Maar een poll aanmaken of stemmen? Dat faalt nu. Precies wat we willen!"
- "Gefeliciteerd! Je database is nu beveiligd."
---
### Slide 15: Samenvatting & Huiswerk
**Timing:** 11:45 - 12:00 (15 min)
**Titel:** Samenvatting & Huiswerk
**Ondertitel:** Wat hebben we geleerd?
```
+========================================================+
| SAMENVATTING |
| |
| 5 KEY TAKEAWAYS: |
| |
| 1. Authenticatie = wie ben je? |
| Autorisatie = wat mag je? |
| |
| 2. Supabase Auth regelt login/registratie |
| Email/password, magic link, of OAuth |
| |
| 3. Sessies werken via JWT tokens in cookies |
| Middleware refresht de sessie automatisch |
| |
| 4. Twee clients: browser (client) + server |
| @supabase/ssr handelt cookies af |
| |
| 5. RLS beveiligt je database met policies |
| public = iedereen | authenticated = ingelogd |
| |
| ---------------------------------------------------- |
| |
| HUISWERK: |
| [1] Bedenk je eindexamenopdracht (welke app?) |
| [2] Maak een lijstje van je tabellen + kolommen |
| [3] Bedenk welke RLS policies je nodig hebt |
| [4] Optioneel: voeg user_id kolom toe aan polls |
| en maak een policy: alleen eigen polls deleten |
| |
| VOLGENDE LES: |
| Bouwen aan je eindexamenopdracht! |
+========================================================+
```
**Kernpunten:**
- Vijf key takeaways die alles van vandaag samenvatten
- Authenticatie vs autorisatie: twee aparte concepten
- Supabase Auth: drie methodes, wij gebruikten email/password
- Sessies: JWT tokens, cookies, middleware
- Twee Supabase clients: browser en server
- RLS: policies per tabel, per operatie, public vs authenticated
- Huiswerk: eindexamenopdracht bedenken en tabellen plannen
**Spreektekst:**
- "Laten we samenvatten wat we vandaag geleerd hebben."
- "Takeaway 1: authenticatie is wie ben je, autorisatie is wat mag je. Twee aparte dingen."
- "Takeaway 2: Supabase Auth regelt de authenticatie voor ons. We gebruikten email en wachtwoord, en als bonus magic links."
- "Takeaway 3: sessies werken via JWT tokens die in cookies worden opgeslagen. De middleware refresht ze automatisch."
- "Takeaway 4: in Next.js heb je twee Supabase clients nodig. Een voor de browser en een voor de server. Het @supabase/ssr package maakt dit mogelijk."
- "Takeaway 5: RLS beveiligt je database met policies. Public is voor iedereen, authenticated is alleen voor ingelogde users."
- "Nu het huiswerk. Dit is belangrijk voor de eindexamenopdracht."
- "Nummer 1: bedenk welke app je wilt bouwen. Kies iets dat je leuk vindt en dat haalbaar is."
- "Nummer 2: maak een lijstje van de tabellen die je nodig hebt en welke kolommen ze hebben."
- "Nummer 3: bedenk welke RLS policies je nodig hebt. Wie mag wat lezen en schrijven?"
- "Nummer 4 is optioneel: voeg een user_id kolom toe aan de polls tabel, zodat je kunt bijhouden wie een poll heeft aangemaakt. Dan kun je een policy maken dat je alleen je eigen polls mag verwijderen."
- "Volgende les gaan we bouwen aan jullie eindexamenopdracht. Zorg dat je een plan hebt!"
- "Goed gedaan vandaag. Vragen? Anders zie ik jullie volgende week!"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,814 @@
# Les 11 — Vercel AI SDK
## Docenttekst (Klas A — 3 uur, fysiek, demo-driven)
**Les:** 11 van 18
**Onderwerp:** Vercel AI SDK — AI features bouwen, gekoppeld aan eigen dataset
**Duur:** 180 minuten
**Format:** Tim demonstreert klassikaal. Studenten kijken mee. Zelf bouwen = thuis.
**Demo-app:** Polderfest 2027 — fictief muziekfestival, 500 records in Supabase.
---
## Hoe deze tekst werkt
Dit document is een **lopend script**. Je kunt 'm letterlijk volgen op je laptop terwijl je lesgeeft.
- `[SLIDE X]` — Klik naar slide X op de beamer
- `[SCHERM: slides | terminal | editor | browser | supabase]` — Welk scherm op de beamer
- **Vertel:** "..." — Letterlijk wat je zegt (mag in eigen woorden)
- `*[stage direction]*` — Korte instructie voor jezelf, niet uitspreken
- Code blocks = wat je typt
- 💬 = verwachte studentenvraag
---
## VÓÓR DE LES — Setup (60 min)
### 1. Tools open op je laptop
- VS Code / Cursor — leeg
- Terminal — open in `~/`
- Browser tabs:
- https://supabase.com/dashboard (ingelogd)
- https://platform.openai.com (key paraat)
- `localhost:3000` tab (nog niets)
- Dit docenttekst-bestand
- De slides PDF / PPTX
### 2. Demo-repo `polderfest-demo` voorbereiden
```bash
cd ~
npx create-next-app@latest polderfest-demo \
--typescript --tailwind --app --eslint --no-src-dir --turbopack
cd polderfest-demo
npm i @supabase/supabase-js ai @ai-sdk/openai zod dotenv
npm i tsx --save-dev
git init && git add . && git commit -m "init"
```
### 3. Nieuwe Supabase project
- Dashboard → **New Project** → naam `polderfest-demo`
- Wacht ~2 min op deploy
- Settings → API → kopieer URL + anon key + service role key
- `.env.local`:
```
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...
OPENAI_API_KEY=sk-proj-...
```
### 4. Schema runnen
- Supabase → SQL Editor → plak `schema.sql` → Run
- Check Table Editor → `bands` tabel bestaat, leeg
### 5. Seed test (verwijder daarna)
- Plaats `seed-polderfest.ts` in `polderfest-demo/scripts/`
- `npx tsx scripts/seed-polderfest.ts` → 500 records erin → check Table Editor
- **Wis** alle records vóór de les: `delete from bands;` (zodat je live kunt seeden in demo 2)
- **Verwijder** `app/chat/page.tsx` + `app/api/chat/route.ts` (maken we live in demo 3)
### 6. Backup
- Zip van werkende eindstaat → op USB
- Check OpenAI usage dashboard — key werkt + credits aanwezig
---
# HET SCRIPT — Lees mee tijdens de les
## BLOK 1 — Welkom + Terugblik (10 min)
`[SLIDE 1]` `[SCHERM: slides]`
**Vertel:** "Welkom bij les 11. Vandaag de Vercel AI SDK — eerste keer dat we échte AI features gaan bouwen IN onze apps. Geen ChatGPT openen meer — AI in onze eigen code."
`[SLIDE 2 — Terugblik]`
**Vertel:** "Even kort terug: vorige lessen hebben we Supabase geïntegreerd. Tabellen en relaties opgezet. RLS-policies bekeken — wie mag wat lezen en schrijven.
Vandaag iets anders. We gaan **niet** voortbouwen op QuickPoll. We beginnen een nieuwe demo from scratch. Nieuwe Next.js app, nieuwe Supabase, en dan koppelen we daar de AI SDK aan."
`*[Wacht 2 sec, laat het landen]*`
`[SLIDE 3 — Planning]`
**Vertel:** "Dit is de planning. Drie uur. Eerst theorie — 30 minuten — wat is de AI SDK, welke modellen, welke functies. Daarna vier demo's. Eén: nieuwe app opzetten. Twee: 500 records in Supabase via een seed-script. Pauze. Drie: AI SDK installeren en chatten met de data. Vier: vragen stellen aan die data."
`*[Wijs naar de gele rij]*`
**Vertel:** "Belangrijk — dit is een **kijk-les**. Jullie typen vandaag niet mee. Pak je notitieboek of laptop voor aantekeningen. Thuis bouw je zelf een versie, met je eigen thema. Daar gaan de lesopdracht en huiswerk over."
💬 Verwachte vraag: *"Kunnen we niet meedoen?"*
Antwoord: "Liever niet — als jullie ook typen, gaat 't te langzaam en haakt iedereen op een ander moment af. Vanavond is voor zien-en-snappen. Thuis is doen."
---
## BLOK 2 — Theorie AI SDK (30 min)
`[SLIDE 4 — Wat is de AI SDK]` `[SCHERM: slides]`
**Vertel:** "Wat is de Vercel AI SDK? Een TypeScript-library die één unified API biedt voor alle AI-providers. OpenAI vandaag, Anthropic morgen, Google overmorgen, lokaal Ollama als je dat wil — je code blijft hetzelfde.
Open source. Gemaakt door Vercel — de makers van Next.js. Daarom: naadloze integratie met Server Components, Server Actions en streaming.
Wat zit er in:"
`*[Wijs naar de bullets]*`
**Vertel:** "Unified API. Streaming out-of-the-box — geen WebSocket-gedoe. React hooks zoals `useChat`. Tool Calling — komt volgende les. En type-safe gestructureerde output via Zod."
`*[Wijs naar het code-blok rechts]*`
**Vertel:** "Kijk hier. Dit is alle code die je nodig hebt voor één AI-call. Vier regels. En zie je `openai('gpt-4o-mini')`? Als ik dat morgen wil veranderen naar Anthropic — verander ik dat in `anthropic('claude-sonnet-4')`. Eén regel. Rest van mijn code blijft hetzelfde. Dat is de waarde."
`[SLIDE 5 — Modellen + kosten]`
**Vertel:** "Het modellen-landschap. Loop ik even langs:"
`*[Wijs per rij]*`
- "**gpt-4o-mini** — je default. Snel, goedkoop, $0.15 input / $0.60 output per miljoen tokens. Goed voor 80% van de use cases."
- "**gpt-4o** — multimodal, kan plaatjes lezen. 15× duurder dan mini. Pas pakken als nodig."
- "**gpt-4.1** — beste reasoning. Voor agents. Volgende lessen relevant."
- "**claude-sonnet-4** — Anthropic. Beter in coding, 200k context — dus lange documenten."
- "**gemini-2.5-flash** — Google. Ultra goedkoop, multimodal."
- "**llama-3.3-70b** op Groq — open-source model op snelste inference platform."
**Vertel:** "Vuistregel: start met gpt-4o-mini. Werkt 't niet goed genoeg? Probeer gpt-4o. Pas daarna iets exotisch. Premature optimization is een valkuil — het is letterlijk één regel veranderen om te wisselen, dus geen reden om voorbarig te kiezen."
`*[Wijs naar de blauwe callout]*`
**Vertel:** "Onze hele les vandaag, inclusief Polderfest met 500 bands en 10 vragen? Ongeveer 1 tot 2 cent. Echt. Schaalt prima."
`[SLIDE 6 — 4 kern-functies]`
**Vertel:** "De vier kern-functies van de SDK. Deze tabel is je cheat-sheet."
`*[Wijs per rij]*`
- "**generateText** — wachten tot AI klaar is, dan krijg je een string. Voor korte server-only calls."
- "**streamText** — streamt karakter voor karakter. Werkt met useChat. Dit gebruiken we vandaag."
- "**useChat** — React hook. Complete chat UI in 10 regels. Ook vandaag."
- "**generateObject** — type-safe data via Zod schema. Voor database-inserts of classificatie. Vandaag niet — komt later."
**Vertel:** "Onthoud: streamText en useChat — onze combo voor vandaag. generateObject zien jullie volgende lessen terug. Tool Calling — onderaan — dat is volgende les."
---
## BLOK 3 — Live Demo 1: Next.js + Supabase scaffold (20 min)
`[SLIDE 7 — Polderfest concept]` `[SCHERM: slides]`
**Vertel:** "Voor we gaan coderen — wat bouwen we eigenlijk?
We bouwen **Polderfest 2027**. Een fictief Nederlands muziekfestival. 500 verzonnen bands. Allemaal namen die niet bestaan. Geen Spotify, geen Pitchfork — pure fantasy."
`*[Wijs naar de gele 'Waarom een fictief festival' callout]*`
**Vertel:** "Waarom fictief? Omdat **geen enkele LLM** dit kan weten. Geen training data over Polderfest 2027 — bestaat niet. En dat is precies wat we willen demonstreren: AI alleen kan dit niet. AI mét onze data, wél."
`*[Wijs naar het schema-blok]*`
**Vertel:** "Onze tabel heeft deze velden: naam, genre, sub-genre, stage, dag, starttijd, stad, members, bio, tier, populariteit, ticket-impact. Genoeg variatie voor leuke vragen straks."
`*[Wijs naar voorbeeld-vragen]*`
**Vertel:** "Dingen die we straks aan onze AI gaan vragen: welke bands spelen vrijdagavond op de Main Stage? Vat de hip-hop scene samen. Welke acts komen uit Groningen? Allemaal vragen die ChatGPT niet kan beantwoorden — want hij weet niets van Polderfest. Maar onze chat straks wel."
`*[Pauze, ademen]*`
**Vertel:** "Goed — laten we 't gaan bouwen. Eerst Next.js en Supabase opzetten."
---
`[SLIDE 8 — LIVE DEMO 1]` `[SCHERM: slides]`
**Vertel:** "Dit zijn de 6 stappen die we nu gaan doorlopen. Ongeveer 20 minuten. Volg even mee — niet meetypen, kijk."
`[SCHERM: terminal]`
**Vertel:** "We beginnen in de terminal."
#### Stap 1 — Next.js scaffolden
```bash
cd ~
npx create-next-app@latest polderfest-demo \
--typescript --tailwind --app --eslint --no-src-dir --turbopack
```
`*[Druk enter, wacht ~30 sec]*`
**Vertel terwijl het installeert:** "Standaard Next.js 15 met App Router, Tailwind, TypeScript. App Router omdat we Server Components willen. Tailwind voor styling. Niets bijzonders aan deze setup — dit kennen jullie al uit eerdere lessen."
`*[Wacht tot install klaar is]*`
```bash
cd polderfest-demo
code .
```
`[SCHERM: editor]`
**Vertel:** "Editor open. Niets in `app/page.tsx` — standaard Next.js welkomstpagina. Standaard `app/layout.tsx`. Tailwind config. Niks bijzonders."
#### Stap 2 — Supabase project aanmaken
`[SCHERM: browser → supabase.com/dashboard]`
**Vertel:** "Nu Supabase. Ik heb nog géén project voor deze demo — we maken er een nieuwe."
`*[Klik New Project]*`
**Vertel:** "Naam: `polderfest-demo`. Database password — kies wat, hoeft niet kopiëren. Region: West Europe. Submit."
`*[Wacht ~2 min — gebruik deze tijd voor de uitleg hieronder]*`
**Vertel:** "Terwijl het deployt: waarom een nieuw project? Omdat we van scratch beginnen. Geen vermenging met je QuickPoll-data van eerdere lessen. Clean slate. Voor je eindopdracht en huiswerk geldt: één app = één Supabase project."
#### Stap 3 — Schema runnen
`*[Supabase deploy is klaar]*`
`[SCHERM: supabase → SQL Editor]`
**Vertel:** "Schema-tijd. Open SQL Editor. New Query."
`*[Plak inhoud van schema.sql]*`
**Vertel:** "Dit is mijn schema voor de bands-tabel. Naam, genre, stage, dag, tijd, members, bio. Een paar indexen voor performance. RLS aan en een policy: bands zijn publiek leesbaar. Voor onze chat hebben we read-access nodig, geen schrijfrechten."
`*[Klik Run]*`
`[SCHERM: supabase → Table Editor]`
**Vertel:** "Check — tabel `bands` bestaat. Leeg. Klaar om gevuld te worden."
#### Stap 4 — Env vars
`[SCHERM: supabase → Settings → API]`
**Vertel:** "Drie dingen pak ik hier op: de Project URL, de anon public key, en de service_role secret key. Die laatste is belangrijk — straks bij het seed-script."
`*[Kopieer alle drie]*`
`[SCHERM: editor → .env.local]`
```
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...
OPENAI_API_KEY=sk-proj-...
```
**Vertel:** "Belangrijke nuance — let goed op de namen:
- `NEXT_PUBLIC_SUPABASE_URL` en `_ANON_KEY` — die staan in client-bundle. Mag — anon key heeft alleen leesrechten via RLS.
- `SUPABASE_SERVICE_ROLE_KEY` — geen `NEXT_PUBLIC_` prefix. Server-only. Deze key bypasst RLS — daarmee kan alles. Lekken = ramp. Gebruiken we alleen lokaal voor het seed-script.
- `OPENAI_API_KEY` — geen `NEXT_PUBLIC_` prefix. Anders zit-ie in je client-bundle en kan iedereen 'm gebruiken op jouw kosten. Server-only altijd."
#### Stap 5 — Supabase client
`[SCHERM: editor]`
`*[Nieuwe file: lib/supabase.ts]*`
```typescript
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
```
**Vertel:** "Standaard Supabase client. Voor onze chat — dat is alles wat we nodig hebben. Niets bijzonders. Kennen jullie."
#### Stap 6 — Dev server check
`[SCHERM: terminal]`
```bash
npm run dev
```
`[SCHERM: browser → localhost:3000]`
**Vertel:** "Standaard Next.js welkomstpagina. Werkt. Supabase staat. Schema staat. Klaar voor data."
---
## BLOK 4 — Live Demo 2: Seed script — 500 records (20 min)
`[SLIDE 9 — LIVE DEMO 2]` `[SCHERM: slides]`
**Vertel:** "Demo 2. We gaan onze bands-tabel vullen. Niet handmatig — met een seed-script. 500 records in ~30 seconden."
`[SCHERM: editor]`
`*[Maak folder: scripts/. Plaats seed-polderfest.ts erin]*`
#### Stap 1 — Het seed-script bekijken
`[SCHERM: editor → seed-polderfest.ts]`
**Vertel:** "Dit is mijn seed-script. 200 regels. Laat me even door de structuur lopen — niet alle regels lezen, alleen de aanpak."
`*[Scroll naar top]*`
**Vertel:** "Bovenaan: Supabase client. Belangrijk — met de **service role key**. Niet anon. Want we gaan inserts doen. RLS blokkeert dat anders."
`*[Scroll naar de bouwstenen arrays]*`
**Vertel:** "Hier zijn mijn bouwstenen. Adjectives — 'Lost', 'Velvet', 'Iron', 'Neon'. Nouns — 'Tigers', 'Wolves', 'Mirrors'. Cities — Nederlandse steden. Genres — muziekgenres."
`*[Scroll naar generateBandName]*`
**Vertel:** "Hier de naam-generator. Vier patronen:
- 'Lost Tigers' — adjective + noun
- 'De Wolves' — Dutch prefix + noun
- 'Sanne Van Dijk' — solo artist
- 'Sanne & The Wolves' — solo + collectief
Met 30 adjectives × 30 nouns = al 900 unieke combinaties mogelijk. Genoeg voor 500 records."
`*[Scroll naar generateBio]*`
**Vertel:** "Bio's. Drie blokken — opening, middle, ending — gecombineerd. 'Begonnen in een garage in [stad]', '[band] experimenteert met analoge synths', 'Debuut-EP eind 2027'. Compositioneel. Geen handmatig getypte bio's — 500× zou krankzinnig zijn."
`*[Scroll naar bottom — async function seed]*`
**Vertel:** "De main functie. Genereert 500 bands, dedupe op naam, insert in batches van 100 — Supabase trekt 500 in één keer niet altijd. Done."
#### Stap 2 — Service role key uitleggen
**Vertel:** "Even pauze voor één belangrijk ding — de service role key. Die zit boven aan dit script. Drie regels die jullie moeten onthouden:
1. **Alleen lokaal gebruiken.** Niet in productie code. Niet in client. Alleen scripts.
2. **Nooit committen** naar git. Service role key in `.env.local`, en `.env.local` in `.gitignore`.
3. **Lekt-ie?** Direct draaien in Supabase dashboard → Settings → API → Reset service role key.
Vergelijk het met een root password. Behandel 'm zo."
#### Stap 3 — Dependencies (was al klaar — kort tonen)
`[SCHERM: terminal]`
```bash
# We hebben deze al uit setup, maar voor je eigen project:
npm i @supabase/supabase-js dotenv
npm i tsx --save-dev
```
#### Stap 4 — Run het seed-script
`[SCHERM: terminal]`
```bash
npx tsx scripts/seed-polderfest.ts
```
**Vertel terwijl het runt:** "Daar gaat 'ie. tsx is een TypeScript-runner die geen build-stap nodig heeft. dotenv leest de `.env.local` automatisch. 500 bands genereren, vijf batches van 100, klaar."
`*[Wacht ~10-30 sec, output verschijnt]*`
```
Genereren van 500 Polderfest bands...
Schrijven naar Supabase in batches van 100...
✓ 100/500
✓ 200/500
✓ 300/500
✓ 400/500
✓ 500/500
Klaar! 500 Polderfest bands staan in Supabase.
```
**Vertel:** "Done."
#### Stap 5 — Verificatie
`[SCHERM: supabase → Table Editor → bands]`
`*[Klik refresh — 500 records verschijnen]*`
**Vertel:** "500 bands. Allemaal verzonnen. Laten we er even één openklikken."
`*[Klik op willekeurige rij, toon bio]*`
**Vertel:** "Kijk — 'Begonnen in een garage in Groningen, De Tigers experimenteert met analoge synths en gefluisterde lyrics. Polderfest is hun grootste festival tot nu toe.' Compleet verzonnen. Geen Wikipedia, geen Spotify — pure fantasy. Maar overtuigend genoeg voor onze AI om mee te werken."
#### Stap 6 — Quick check met SQL
`[SCHERM: supabase → SQL Editor]`
```sql
select genre, count(*) from bands group by genre order by count desc;
```
`*[Run]*`
**Vertel:** "Genre-verdeling. ~30 per genre. Mooi gespreid. Klaar om mee te chatten."
---
## BLOK 5 — Pauze (15 min)
`[SLIDE 10 — Pauze]` `[SCHERM: slides]`
**Vertel:** "Pauze. 15 minuten. Tot zo."
`*[Coffee. Stretch. Check je OpenAI key nog even.]*`
---
## BLOK 6 — Live Demo 3: AI SDK + chat-route (30 min)
`[SLIDE 11 — LIVE DEMO 3]` `[SCHERM: slides]`
**Vertel:** "Welkom terug. Nu de echte AI-stap. We bouwen een chat-route in onze API en een chat-pagina in Next.js. Daarmee kunnen we vragen stellen aan onze Polderfest-data."
`[SCHERM: terminal]`
#### Stap 1 — Packages
```bash
npm i ai @ai-sdk/openai zod
```
**Vertel:** "Drie packages. `ai` is de SDK zelf. `@ai-sdk/openai` is de provider — we gebruiken OpenAI vandaag. `zod` is voor schema validatie. Vandaag gebruiken we 'm niet, maar volgende les wel."
#### Stap 2 — Chat API route
`[SCHERM: editor]`
`*[Maak file: app/api/chat/route.ts. Typ live, niet pasten — geeft studenten tijd om te volgen]*`
```typescript
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
export async function POST(req: Request) {
const { messages } = await req.json();
```
**Vertel:** "API route. POST, want we ontvangen messages van de chat. We destructuren de messages-array."
```typescript
// 1. Haal alle bands op uit Supabase
const { data: bands, error } = await supabase.from("bands").select("*");
if (error) throw error;
```
**Vertel:** "Stap 1 — ALLE bands ophalen uit Supabase. Voor 500 records werkt dit prima. Voor 50.000 niet — volgende les lossen we dat op met Tool Calling. Vandaag: simpele aanpak, alles meesturen."
```typescript
// 2. Format bands als context-string
const context = bands!
.map((b) =>
`- ${b.name} (${b.genre}, ${b.tier}, ${b.day} ${b.start_time} ` +
`op ${b.stage}, uit ${b.origin_city})`
)
.join("\n");
```
**Vertel:** "Stap 2 — we maken één grote tekst-context. Per band één regel met de belangrijkste velden. AI kan namelijk geen SQL, maar wel tekst lezen."
```typescript
// 3. System prompt met context
const system = `Je bent een festival-assistent voor Polderfest 2027.
Hier zijn alle bands die op het festival spelen:
${context}
Beantwoord vragen van bezoekers over de line-up. Verzin niets — gebruik
alleen bovenstaande data. Antwoord in het Nederlands. Wees beknopt.`;
```
**Vertel:** "Stap 3 — de system prompt. Dit is de **rol** die AI krijgt. Drie belangrijke instructies:
1. 'Verzin niets' — voorkomt hallucinaties.
2. 'Gebruik alleen bovenstaande data' — niet uit training-kennis halen.
3. 'Antwoord in het Nederlands' — anders krijg je Engels.
Een goede system prompt is je hefboom. 50% van de kwaliteit komt hier."
```typescript
// 4. Stream naar OpenAI
const result = streamText({
model: openai("gpt-4o-mini"),
system,
messages,
});
return result.toDataStreamResponse();
}
```
**Vertel:** "Stap 4 — de AI-call zelf. `streamText` — onze keuze van vandaag. Model gpt-4o-mini. System message, plus de berichten van de user. En `result.toDataStreamResponse()` zet 't om naar het juiste streaming-format voor useChat aan de client-kant."
**Vertel:** "API route klaar."
#### Stap 3 — Chat pagina
`*[Nieuwe file: app/chat/page.tsx]*`
```tsx
"use client";
import { useChat } from "ai/react";
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, status } =
useChat();
return (
<main className="max-w-2xl mx-auto p-6 flex flex-col h-screen">
<h1 className="text-2xl font-bold mb-4">Polderfest 2027 — vraag de AI</h1>
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map((m) => (
<div
key={m.id}
className={
m.role === "user"
? "bg-blue-50 p-3 rounded-lg ml-12"
: "bg-gray-50 p-3 rounded-lg mr-12"
}
>
<div className="font-medium text-sm text-gray-500 mb-1">
{m.role === "user" ? "Jij" : "Festival AI"}
</div>
<div className="whitespace-pre-wrap">{m.content}</div>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Stel een vraag over de line-up..."
className="flex-1 p-3 border rounded-lg"
disabled={status !== "ready"}
/>
<button
type="submit"
disabled={status !== "ready"}
className="px-6 py-3 bg-blue-600 text-white rounded-lg disabled:opacity-50"
>
Stuur
</button>
</form>
</main>
);
}
```
**Vertel:** "Client component — `'use client'` bovenaan. `useChat` hook regelt alles: messages-state, input-state, submit-handler, streaming. Vijf properties, geen extra useState nodig.
UI is bewust simpel. Tailwind classes. Berichten van user blauw rechts, AI grijs links. Input + verzenden onderaan. Disabled tijdens streaming via `status !== 'ready'`."
#### Stap 4 — Testen
`[SCHERM: browser → localhost:3000/chat]`
**Vertel:** "Naar `/chat`. Eerste vraag."
`*[Typ in chat-input]*`
```
Hallo, wie ben jij?
```
`*[Druk Enter. AI antwoordt streamend.]*`
**Vertel terwijl AI antwoordt:** "Daar gaat 'ie. Karakter voor karakter. Streamt. Veel sneller voelend dan wachten op heel antwoord. UseChat regelt het, je hoeft niks zelf te doen voor streaming."
**Vertel:** "Klaar. Werkt. Nu de leukste vraag: vragen aan onze data."
---
## BLOK 7 — Live Demo 4: Vragen aan onze data (15 min)
`[SLIDE 12 — LIVE DEMO 4]` `[SCHERM: slides]`
**Vertel:** "We gaan vijf vragen stellen. Eén voor één. Bij elke vraag — let op wat de AI doet, en hoe dat anders is dan een SQL query of een gewone chatbot."
`[SCHERM: browser → /chat]`
#### Vraag 1 — Filter
`*[Type in chat]*`
```
Welke bands spelen zaterdag op de Beach Stage?
```
`*[AI antwoordt — geeft een lijst]*`
**Vertel:** "Filter. AI heeft door de tekst-context gefilterd. Even bevestigen met SQL —"
`[SCHERM: supabase → SQL Editor]`
```sql
select name, start_time from bands
where day = 'Zaterdag' and stage = 'Beach Stage';
```
`*[Run]*`
**Vertel:** "Zelfde resultaat. Maar het verschil — onze chat geeft natuurlijke taal, kan vervolgvragen aan, kan samenvatten. SQL doet alleen filter + select."
#### Vraag 2 — Sort + Reasoning
`[SCHERM: browser → /chat]`
```
Geef me 3 headliners met de meeste popularity, en hun bio's
```
`*[AI antwoordt]*`
**Vertel:** "Sort. AI heeft op `popularity` gesorteerd en de top 3 gepakt. Plus de bio's erbij."
`*[Vraag 2b in zelfde chat:]*`
```
En welke daarvan zou je aanraden voor iemand die houdt van techno?
```
`*[AI antwoordt — beargumenteerd]*`
**Vertel:** "Dit is **reasoning**. AI redeneert over genre + sub-genre + bio-tekst om een aanbeveling te doen. Een SQL query kan dit niet. Dit is waar AI waarde toevoegt bovenop pure data."
#### Vraag 3 — Aggregate
```
Hoeveel jazz fusion acts spelen er totaal op Polderfest?
```
`*[AI antwoordt met een getal]*`
**Vertel:** "Aggregate. AI heeft geteld. Even SQL-bevestigen —"
`[SCHERM: supabase → SQL Editor]`
```sql
select count(*) from bands where genre = 'Jazz Fusion';
```
**Vertel:** "Klopt. Goed."
#### Vraag 4 — Samenvatting (waar AI uitblinkt)
`[SCHERM: browser → /chat]`
```
Vat de electronic-scene op Polderfest samen in 3 zinnen
```
`*[AI antwoordt met een echte samenvatting]*`
**Vertel:** "Hier is geen SQL voor. Dit is **samenvatten**. AI heeft alle electronic acts gelezen, gewogen, en in 3 zinnen samengevat. Dit is de unieke kracht van AI bovenop data."
#### Vraag 5 — De mist in (bewust)
```
Wie was de hoofdact van Polderfest 2025?
```
`*[AI antwoordt eerlijk dat hij dit niet weet]*`
**Vertel:** "Goed. Geen verzinsels. AI zegt: 'mijn data is alleen 2027, dat zit er niet in'. Dat is de kracht van onze system prompt. Zonder die prompt zou-ie waarschijnlijk wat hallucineren."
`*[Pauze, kijk de klas in]*`
**Vertel:** "Zien jullie wat hier gebeurt? Geen LLM ter wereld kent Polderfest 2027. Geen Wikipedia, geen training-data. Maar onze chat beantwoordt alles — omdat **wij** de data leveren. AI + data = product."
---
## BLOK 8 — Data + AI = kracht (5 min)
`[SLIDE 13 — Data + AI = kracht]` `[SCHERM: slides]`
**Vertel:** "Reflectie-moment. Drie scenario's:"
`*[Wijs naar de grijze box]*`
**Vertel:** "**Data alleen.** Wat heb je? SQL queries. Filter, sort, select. Geen taal, geen interpretatie. Gebruiker moet zelf SQL kunnen."
`*[Wijs naar de roze box]*`
**Vertel:** "**AI alleen.** ChatGPT zonder context. Generieke kennis uit training. Hallucineert. Geen privé data, geen live data."
`*[Wijs naar de blauwe box]*`
**Vertel:** "**Data + AI.** Wat we vandaag bouwden. Filter via reasoning. Antwoorden in natuurlijke taal. Samenvattingen, vergelijkingen, aanbevelingen. Schaalbaar — voeg data toe en je hebt nieuwe antwoorden mogelijk."
`*[Pauze]*`
**Vertel:** "Quote om mee weg te lopen:"
`*[Wijs naar de gele callout]*`
**Vertel:** "*Een LLM zonder jouw data is een gewone chatbot. Een LLM mét jouw data is een product.* Onthoud dit. Dit is de fundering van alle volgende lessen."
---
## BLOK 9 — Lesopdracht + Huiswerk uitleg (20 min)
`[SLIDE 14 — Lesopdracht]` `[SCHERM: slides]`
**Vertel:** "Lesopdracht. Voor thuis — niet vanavond per se, maar vóór volgende les. Je bouwt **een eigen versie** van wat we vandaag deden. Met **je eigen thema**."
`*[Loop checklist langs op slide]*`
**Vertel:** "Eisen op een rij. Bedenk een eigen thema — moet fictief zijn. Nieuw Next.js project, nieuw Supabase. Eigen seed-script. Minstens 100 records. Chat-route en chat-pagina werkend. Drie vragen stellen die alleen werken dankzij jouw data."
`*[Wijs naar pink callout]*`
**Vertel:** "Inspiratie: fictief restaurant-aggregator in een verzonnen stad. Scriptie-archief van NOVI met 1000 nep-titels. Museumcollectie met verzonnen kunstenaars. D&D NPCs. Cryptid-sightings in Nederland.
Belangrijk: **moet fictief zijn**. Als je echte restaurants in Amsterdam pakt, weet ChatGPT die al — dan zie je niet wat we vandaag demonstreerden. Het hele punt is: data die geen LLM kent."
💬 Verwachte vraag: *"Mag ik echt elk thema?"*
Antwoord: "Ja, zolang het fictief is en minstens 100 records heeft. Twijfel? Stuur 'm op Brightspace, dan kijk ik even mee."
`[SLIDE 15 — Huiswerk]`
**Vertel:** "Het huiswerk bouwt voort op de lesopdracht. Drie onderdelen — alle drie verplicht."
`*[Loop A, B, C langs op slide]*`
**Vertel:** "**Onderdeel A.** Pas het Polderfest seed-script aan voor jouw thema. Het script staat klaar als bijlage. Open 't, lees 't, en pas 't aan. AI mag je helpen — letterlijk: open OpenCode, plak m'n script erin, vraag 'pas dit aan voor [mijn thema]'. Klaar in een paar minuten. Daarna jij review. Minstens 200 records.
**Onderdeel B.** Voeg minstens 1 extra veld toe aan je schema. Iets dat een **nieuwe vraag** mogelijk maakt. Niet zomaar een extra string-kolom. Concreet voorbeeld: een museumcollectie met `acquisition_story` veld — dan kun je vragen 'welke kunstwerken zijn op een veiling gekocht?'. Update seed-script, re-seed, test in chat.
**Onderdeel C.** Schrijf een `AI-CHAT.md` in je repo-root. Drie secties:
- Mijn thema — wat is het, waarom kan een gewone LLM dit niet?
- 3 leuke vragen die werken
- 1 vraag waar AI moeite mee had + hoe je je prompt aanpaste."
`*[Wijs naar gele callout]*`
**Vertel:** "Bonus, geen verplichting: deploy op Vercel, loading skeleton, vergelijk gpt-4o-mini en gpt-4o."
💬 Verwachte vraag: *"Hoe lang gaat dit duren?"*
Antwoord: "Lesopdracht ~2,5 uur. Huiswerk ~1,5 tot 2 uur. Samen een lange middag. Loop je vast — Brightspace of plan een korte 1-op-1 met me."
---
## BLOK 10 — Vragen + Afsluiting (15 min)
`[SLIDE 16 — Volgende les: Tool Calling]` `[SCHERM: slides]`
**Vertel:** "Eén ding voor we afronden — wat komt hierna."
`*[Wijs naar pink callout: Het schaal-probleem]*`
**Vertel:** "Wat we vandaag deden: ALLE 500 bands sturen we mee als context bij elke vraag. Dat is ~30.000 tokens per call. Werkt prima voor 500. Werkt **niet** voor 5.000 records. En al helemaal niet voor 50.000."
`*[Wijs naar blauwe callout: De oplossing]*`
**Vertel:** "Volgende les — **Tool Calling**. In plaats van alle data meesturen, geef je AI **functies** die hij zelf kan aanroepen. Hij hoort vraag 'welke bands op vrijdag?' en besluit: ik roep `searchBands({ day: 'Vrijdag' })` aan. Krijgt 60 bands terug. Antwoordt. Schaalbaar tot duizenden records."
**Vertel:** "Daarna in deze leerlijn: Agents met maxSteps. RAG met embeddings — semantic search op heel grote datasets. Testing, deployment, performance. En de laatste twee lessen: eindopdracht-werkdagen en je pitch."
`[SLIDE 17 — Afsluiting]`
**Vertel:** "Vragen?"
`*[Open de vloer. Verwachte vragen + antwoorden:]*`
💬 *"Wat als ik geen schoolkey heb?"*
→ "Eigen OpenAI account — $5 starter credit zit erin gratis. Of Groq — gratis tier. Of Anthropic — $5 gratis credits."
💬 *"Hoe weet ik welk model het beste is?"*
→ "Start met gpt-4o-mini. Upgrade alleen als het écht niet werkt. Premature optimization is een valkuil."
💬 *"Kan dit lokaal zonder OpenAI?"*
→ "Ja, via Ollama. Niet vereist voor deze les. Komt eventueel in latere lessen."
💬 *"Moet ik de Polderfest demo zelf ook namaken?"*
→ "Nee. Wat we vandaag deden is jullie laten zien. Voor jezelf bouwen = eigen thema, in lesopdracht en huiswerk."
💬 *"Hoe duur is dit nou echt?"*
→ "Onze hele les vandaag met 500 bands en 10 vragen — onder de 2 cent. Met gpt-4o (de grote) zou hetzelfde ~30 cent zijn. Met mini blijft het peanuts."
`*[Sluit af]*`
**Vertel:** "Zorg dat je vóór volgende les minstens je seed-script werkend hebt voor jouw thema. Dan kunnen we volgende les meteen Tool Calling toepassen. Tot dan!"
---
## Backup-onderwerpen (als tijd over is)
1. **Andere provider tonen** — Open `route.ts`, vervang `openai("gpt-4o-mini")` door `anthropic("claude-sonnet-4")`. Werkt direct. Eén regel.
2. **System prompt fine-tuning** — Verzwak de prompt ("Help bij vragen"). Vraag iets. Verzin de fout. Versterk weer ("Verzin niets, gebruik alleen onze data"). Toon verschil.
3. **Token-kosten dashboard** — Open platform.openai.com/usage. Toon je verbruik van vandaag — letterlijk een paar cent.
4. **Privacy / data retention** — Wat gaat naar OpenAI? Zero-data-retention via EU-endpoints. Belangrijk voor productie.
5. **Hallucinatie-test** — Probeer met zwakke prompt of de AI iets verzint over Polderfest 2025. Toon dat sterkere prompt dit fixt.

View File

@@ -0,0 +1,181 @@
# Les 11 — Huiswerk
## Seed-script aanpassen + uitbreiden + reflecteren
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Deadline:** Voor de volgende les (Les 12 — Tool Calling)
**Inleveren:** GitHub repo + `AI-CHAT.md` in root
---
## Doel
Bouwt voort op de **lesopdracht** (eigen thema-app). Hier:
- **A.** Pas het seed-script aan voor jouw eigen thema (mag AI bij helpen)
- **B.** Voeg een **extra veld** toe dat een nieuwe vraag mogelijk maakt
- **C.** Schrijf een **reflectie** over wat werkt en wat niet
> Niet klaar met de lesopdracht? Eerst die afmaken — daarna komt dit. De huiswerkopdracht heeft de lesopdracht-app nodig om op te bouwen.
---
## Onderdeel A — Seed-script voor jouw thema (verplicht)
Het Polderfest seed-script is je voorbeeld. Pas het aan voor jouw eigen thema.
### Stappen
1. Open `seed-polderfest.ts` als referentie
2. Pas aan voor jouw thema:
- Domein-arrays (in plaats van adjectives + nouns → wat past bij jouw thema?)
- Veld-namen + types
- Bio/beschrijving-fragmenten (de samengestelde tekst-generatie)
3. Run je seed-script tegen je eigen Supabase
4. Verifieer 100+ records in Table Editor
### Pro tip: AI als seed-script writer
Open OpenCode (of Cursor) en typ:
> Hier is het Polderfest seed-script. Pas het aan voor [mijn thema].
> Mijn schema is: [paste schema]
> Genereer 200 records met realistisch-ogende variatie.
AI doet dit in 1-2 minuten. Daarna jij review — controle.
### Eisen
- [ ] Werkende seed-script in `scripts/seed-[thema].ts`
- [ ] Minimaal **200 records** in Supabase (was 100 voor lesopdracht — nu meer)
- [ ] Procedureel gegenereerd (niet handmatig — gebruik combinaties)
- [ ] In je README: korte uitleg hoe je 't gegenereerd hebt
---
## Onderdeel B — Extra veld + nieuwe vraag (verplicht)
Voeg minstens **1 extra veld** toe aan je schema. Iets dat een **nieuwe interessante vraag** mogelijk maakt.
### Voorbeelden
| Bestaand veld | Extra veld | Nieuwe vraag mogelijk |
|---------------|-----------|----------------------|
| Restaurant — `cuisine` | `dietary_options: string[]` | "Welke veganistische opties zijn er?" |
| Scriptie — `year` | `keywords: string[]` | "Vat scripties over AI samen" |
| Festival-band — `tier` | `collaborations: string[]` | "Welke acts hebben samengewerkt met X?" |
| Museumstuk — `period` | `acquisition_story: string` | "Welke kunstwerken zijn op een veiling gekocht?" |
### Stappen
1. Update je `schema.sql` met het extra veld
2. Run de SQL in Supabase (kun je `ALTER TABLE` gebruiken — niet alles opnieuw)
3. Update je seed-script om het nieuwe veld te vullen
4. Re-seed je tabel (eerst wis bestaande records: `delete from items;`)
5. Test in chat: stel een vraag die alleen kan dankzij het nieuwe veld
### Eisen
- [ ] Nieuw veld toegevoegd aan schema
- [ ] Seed-script gevuld voor nieuwe veld
- [ ] 1 vraag aan chat die specifiek dit veld gebruikt — werkt
- [ ] Screenshot van die vraag + AI antwoord in `AI-CHAT.md`
---
## Onderdeel C — `AI-CHAT.md` reflectie (verplicht)
Schrijf een markdown-bestand `AI-CHAT.md` in je repo-root met:
### Sectie 1: Mijn thema
- Wat is het thema?
- Waarom **kan een gewone LLM** deze vragen niet beantwoorden zonder jouw data?
- Welke velden heb je gekozen en waarom?
- Welk extra veld heb je toegevoegd (onderdeel B)?
### Sectie 2: 3 leuke vragen die werken
Voor elke vraag:
- De vraag zelf
- Het antwoord van de AI (screenshot of plak-tekst)
- Waarom dit een goede demo is
### Sectie 3: 1 vraag waar AI moeite mee had
- Welke vraag was het?
- Wat ging er mis (vaag antwoord, hallucinatie, foute filter)?
- Hoe heb je je **system prompt** aangepast om dit op te lossen?
- Werkt de vraag nu wel?
### Vorm
- Max 600 woorden in totaal
- Concrete voorbeelden (geen vage reflectie)
- Mag wat informeel — geen scriptie-toon nodig
---
## Bonus (optioneel, niet verplicht)
Iets extra's? Mag, geen extra punten maar wel leerzaam:
- **Deploy op Vercel** + production URL in je README
- **Loading skeleton** in de chat UI (terwijl AI antwoord aan het streamen is)
- **Vergelijking gpt-4o-mini vs gpt-4o** — beschrijf het verschil in antwoorden
- **System prompt variaties** — drie prompts proberen, screenshot per variant
- **Themed UI** — Tailwind aanpassen zodat 't past bij thema (kleuren, fonts)
---
## Inleveren
1. **GitHub repo URL** in Brightspace
2. **`AI-CHAT.md`** in repo-root
3. **Seed-script** in `scripts/seed-[thema].ts`
4. **Updated schema** in `schema.sql` (met extra veld)
5. **Screenshots** ingevoegd in `AI-CHAT.md`
Optioneel: Vercel deploy URL als bonus.
---
## Beoordeling
| Criterium | Punten |
|-----------|--------|
| A — Seed-script werkt + procedureel + 200+ records | 3 |
| B — Extra veld + werkende nieuwe vraag | 2 |
| C — `AI-CHAT.md` aanwezig met 3 secties | 3 |
| Chat werkt end-to-end (geen broken pages) | 1 |
| Reflectie sectie C is concreet (geen fluff) | 1 |
| **Totaal** | **10** |
Voldoende = 6+. Bonus telt mee bij twijfelgevallen.
---
## Tijd-indicatie
| Onderdeel | Tijd |
|-----------|------|
| A — Seed-script aanpassen (met AI hulp) | 30-45 min |
| B — Schema uitbreiden + nieuwe veld + vraag testen | 30 min |
| C — AI-CHAT.md schrijven met screenshots | 30-45 min |
| **Totaal** | **~1,5 - 2 uur** |
---
## Veelvoorkomende valkuilen
- **Thema dat LLM al kent** — Yelp-clone, Spotify-data. Werkt niet voor demo van data-power.
- **Te weinig records** — 200+ vereist, anders is variatie te klein
- **Vage system prompt** — "Help bij vragen" werkt slecht. Wees specifiek.
- **Geen reflectie op slechte vragen** — sectie C wordt vaak vergeten, terwijl daar je leercurve zit
- **AI verzint feiten** — system prompt versterken: "Verzin NIETS. Gebruik alleen onze data."
---
## Tips
- **Schrijf je AI-CHAT.md gaandeweg** — niet aan einde. Sla goede prompts/screenshots op zodra ze werken.
- **Maak de prompt-iteratie expliciet** — sectie C wordt mooier als je echt 3-4 prompt-versies probeert.
- **Niet bang voor AI in je workflow** — laat OpenCode het seed-script schrijven. Tijdwinst is enorm.
Succes! Volgende les pakken we Tool Calling — dan kun je dezelfde demo schaalbaar maken naar 50.000 records.

View File

@@ -0,0 +1,175 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 6 0 R
>>
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 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/PageMode /UseNone /Pages 13 0 R /Type /Catalog
>>
endobj
12 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260519160527+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260519160527+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 11 Huiswerk) /Trapped /False
>>
endobj
13 0 obj
<<
/Count 6 /Kids [ 4 0 R 5 0 R 7 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages
>>
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2227
>>
stream
GauHL>BcPr&:WeDbY-8R]="@@#gnu%Bp(A;VqYFgksNo)%#RE0_$/Wlr>fkip_q2t2U8Qn8>H!VDe;2LJDu@7hpDkjob`_H0OPm?2o&\X_Y?9aO.DmQiKF)@#=Ksi!Y-Iejq!?KPCY*<%!]QQ;4f8o"]mFQ<1nd%38\@4'H1jq3"1TrWNTPLMKr+A[IJi>c>dTcGZ"aHj'2PJEA&H?oa]eorm;SV?nEdB<Od];chCg0K;/r!YMR)a51O4dS*)6u>8931Xl,Xh+&:[P:<G=@.ilNqrh\(k51<[-*6*1VIUV@WfQW$>>XA="qsSY6N5<IW.IVELUTs\FS0hAECgcH_/o##jo%$_g)"@](i+D=\EC]#uRH+-MZcj+]]EnkAdQABu^JogZpC\[jhCp8^ofQj+p2,ruT*8W)0buUZlt!ONEX%S'Vk)&?d,#].-]8t%DP['`p2>FS/!A8iF`<rZ;i1t$cB0WOArX[XGWP1rTLn+C+a38eWi2hAlo\s.fu\&g_/=0BG2#?1CUB`#\$6l^*q:(GX9[l#m]cbEV2I)"BgUeR%F-+kZllVu)_M4"@u)pJDui/68?![>.9n[<j7.-qe4G8B8ODdN)j#S+irPU^e/21oR*EOSe/A/fMkldU3f30l6JnS"WcXr@#GVFHq^0QqPRKY+JG3;d48a26rO?&CFH4q4F.:pqnJ!Q36P*2f^RS/_>m?-ge^d9^+HN\B+0V!#*dSPDREQ<1CRZp1IQ/W*:Id^LK0XgSVmmO72LQFb^,)iUfogZ*5iM>X-tbT"dj3BQ,M@9m'dQk+?GNa,Sa%cWOCGk-2c7J^W#6P!VdYHX9Wt)W!dG9jG?Kes^e!,\i'I.p-Ip(EJh]:30f5#EV\^^j15d<tTZH`t/c319]o?#&M^);jc;nD^msk_Hl`_uP^:_G)q`=8X0EYA\D9dbJ/iO_k<F?:(e1F>Z+RMMIie"X-b&t:<M>J0e5F?G=H(.7i(O<5&E:c1ugfMRn]VlsF"4F0?Yi#$sUfTa@Mm*SShTuk_:+&S!W,D8Z*/4"[olC1I7A9hY0i&H&UEN-j(9pVW7^doR\^P,+!9Vs&UN1o8S>7(f&60:<akTEF;hVD.8m?u7Z7.r1QA[F5ZG/i/K=^0662&Tg::E1An_Q<57lV96dak.*MInr')G?47hY<JH*L^nMq\d4%I!l$`o*iNZ%1A=On@@]d830[o79=3:JTr$j9iGBmXsU[EN,P?lMV/"XG[!ZECO!DdCFF5+Mm@p8%3W<OKg49XBq0-DBk@!_^lo:cY2DegL'>fnP,Z3iYoY-$>,qcYYg7/6?mdftFb/VT=h;nPXhcZd1<N*M-t`_OQP-HC,q:uM#gK2_6[aZ'MRmQL``:d:8.E[r/RI$VEbl\$TP1?:WboUX+p&:VD:D+!+6u2D&>=a4Fm:Z.b5-nC)PVBj,P8Y*HeLR7%^n%qaBSVPWo$pl\Yu=S]$:WgWbp%@-7;,.<1??jSIB[T40Paa=s'JPhk9"+7lA_8ahA@-UG=<^H'>br%Ooi%+A><anZ3G=:&OLZji=-3PTf:h4N5fnUQN@R^o=,_4'fHVd5m6nT"dmm4BK5PSdp)`3?PiBF#>nAm:PtTXZO+q0L;n`gPn31;qGF*]e^.iEQ$n;hI_&L[]bT^gS8XNe"\hg,N<XMC2UM@<4Fr&BheXtdo;1\3^&dc-UGiJ5j08DE!t2M0MEZZ)5$0[-!'1c*bF<S>YgN1FAmik,O9K\6S=g;lVjGmH_BPWU&VF>9]uq<Z-l+7@,p?X*_sRmfBtg/4Eh<\LS;?.7<4T*?/W8ERNlU4(NF.Wn[K>LkAaD]`Imj2`]<XGq="@5>*9?&Q5%MG9?Q*P?5p##5FpB*?`UP$W3LHSefk,('Ld9hE7FW\\:S=F>K%lnmZ6l"M%l:a)\pt*P$nn<3E4,u'cqege267XI?@L?fNW[P):*]$k-7*6>ngjVTYa<_6s-<E(U00Vokg#M[1`L$J]l,>]Rk(n&@9K&9D5-iQs,B5S/jPF5nN55#e\:kQ&RcI'%D/=-jI!hmZKZuq^^(gO3i$nICc.tSs9,:7)cc^BTF^$]t4!0S8ul%J#ed3"IiJ5#:c.F.'r4U#tl0@lXYED*nSZogAS4t?1idJ3O%=EO$)n1P@Vcd%+DUomXV]\a';l.4fSbeKA[9h#"9TtG_&/L_UOgh3aQ=G##RRR,[#rWVgJf=3N_+l=ZjTV:%+.3^]`gb\n^BI~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 444
>>
stream
Gas2E92EGZ&;9NJ'm$,m@D%&E>7j:HnQ+St9.<O1%O;)S5&Se89*JYF(OKc/oakXbC<kcX36?,]7NdEO"_<9PG)9Ok]Hom)jc+u@60_E8UjBRJk/d\U$(%ZV&>U,Y)jil7KJYq_SbM2B10tG'K()P3?m#MR(Yk$W$`?s?D1m$arN0GsKdC%Tgkm^n[QBqs(UP+GgJo19aa/k]Nm@=Bis!_dS?(VI-t[_&9-0+.oTf"2?6"-hC0]<eN)T`6=n+Q97D4auK'ZmuCW#%C.u]dCg]*Ee&E3P8>^^WQD1-=-q04WEg+^*rT=RDT[1^;J'>oXXZFOk.C1<<"/F#<][6FS2X8"'=%<)W[6p3S0CWe'f:7o2V,2?_Y,V2fcT2oY"+5XBlIGa@^?M0=5/]sOP5-AOg@oP-/N4g,ikIf`9\U;DQTB[GU];C[;!?j[u_>~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1935
>>
stream
Gb"/'gN)%,&:N_Cm%]`&&H]UrH-+]qe"e39NNFH:FXKOTCP`ZW1.bWV^V1)=+t6Yr8+e-uqBh'=0$[o:c7ZmuVf,o>oco!N*kZm@JIC[I"#lVS]e5iKZl=u2><&J`b`KEp$=L$1^*"G00j$???/]gc]#1=RJgi^DdZkrrc>g>r.Dkb!N<=6<h2s$<aXp`N&*sa;$UI^\bd))0ftP<N'g.i9'fpqLrfAoS_5j5DYD1/@DE/L),3XFPo^r=!:Bm3^_^#QI9:hkBbi)G*#ED"g*G8N:jk,?tG`,ucM(m5I6#Qn>kA%/cJ((WhC$<j:s"7p!3P:sgfaQ>Ll1Y^,&B#V,/OuFI;<ONe;'C((aUI:ZO#F/NB$e[k_mE,W,#)1@Hs8fC2;p5p2eD<COOYbCEQ8j^-e<[(YLUt_%@7c"fQuW,37ic9\&$6B21YK)T)d0oLDm)jo&cQjm7=EnnMS!G&^lFQA7]#$d>H`2L0?kmoj-_ZjZ>o&K"X(+'gGk^EuF/D&<]pZ$S<r5jQr\-(WOX0?@Hbe[gW3g'+b&2Jm$kTi\(!jrB!e<+?K2S"iLF:B*buGk7-@TF,T9.%#BJ$caL*rG4h:EC.CVdZcIGr3]1QTNV.<2/!um2#nA'b@cDQ)$jCH$jU!^;J9_?I0*fF?N\Q?29JO^QHE@0;Zua)l1ks*C`]77R]G`W?COt?P\dr8KP:<OLP4T(f1<+)WY'7QjfF+af6,1K9k`'6r$+&3`MHXbRCTeYn>btpEajOs9jG9pe:&,(0_VF@!:=YL..u6e:9/fN7A!VIJgb61A>3WQ!1SL:5D:9&B[=KJl&ktmG"s7'FDjfh.pc2[7T'P?-?8suIBW!:6Z;aksF>eqp$gu0I+(4N5GMeTU*[Gb!$F2c'ik_7:D2FuBs+]L"@_dO2;i(dYJ067AR(ZhnJ<\S5/2-oX:]jA7GNbl-!*=>R]UL3]Is7]Rh,i=N<6MR^g'f_i>1bV=+DDc/6r5-W!0YG^J<c+dKV<UTUu$P.>$71"lM75<W`G9<PsEqRVd#OZ9f&;/=[&sflNGZZfgafGeFRSm@2.Ga=e;F5F3%PcQe%cUPl2`#i@r&e6DE:*W\ip`qAk^@8LY&\)aUTd;)CG!j9Nt[e!L<F\XGq2(8akN>SL)E;FN1;\Ph'XM:p#PVX)e8IT0o4Ec3_d7:H_XBMH(NR?tjiZ'QSmXfB#<B=ju0p#X6DX-TiTBs2:IAO<4)6#H/5A(aQ^&a$3a\TVEgp-E.$42b_q0"1@c$l>PJ<8HMB3q9AcN-)%?h!LZ(6gsfGHb.^`/;@dY,WoRlnB;tI*Fcbr5Y.WLd381SL>`>Odu!0/nX2JW-Q\[>4ArF]aq3#QLH^G`JR,/fAWUDdD+rhQU9f$!mW$)9W4XWiqEloc<OJ>r[DAW>;0=:e"Eg^6pc]PFSb/r\\5l01gH(]uSd*k#1+B$j317/?6Y,MG43h#kF==IT*Pkp#m?OoMZ)`ot"2(HpI@u%pd`tQdPYW*$1Y5s\?*m"QPU#'V\X&!6:D[.if3XU0C3-@JWQeI\XeMF+i)MfIkgL)eNq`n_IU"5uPQW6N-@$@$$YkcSNY+'.;tcL;:quqjrGdleH\dM4/Oi6(rr7DkPMXp(q3UPc)Cj+EqDq$Z34eKu<C>N>-H=:;I[7'O=P@5jip&3,7b:I1o(&>KNThuK['B$Irf3)U:6MA/8TqY2&\?LZ\8**AlA`t'A?o;\?HU;k0T%%2./n@'j@$*S`IFpFINqi%Z9gs;eTh%.f>;HtVcq;!'b&WM%<.WO#i4ndFeJKJe]si#3R$FhkJBhemZY/AWOc9g\,sE1c(*\kW;B)b+1#EB2TGBq=%*kOdS95DKtH[cH0Z]0E,td@@@1hg,1iAQ.uPg@`IJMtj-jcK@%c5>?$^HmW1EAE/Mo\?%c>R8k3k%;?BZ\^rW1$n=_@~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 771
>>
stream
Gb!;abAQ&g&A7ljk*Q8X,O.QlV]pNl$'7?&]'[HQem?OW-..uk]D_W0AB0gLfKaH_>)A=NeF(WK/4U<ohm.Y?$fGkDQe)ku7t1pbM@f_JI-C<Ron9OH3/es]m0/=WOT`p_.d`LWPYd4/88'Xl3Xua:.C;i,I#a+p)5AC7c[$3J<aMjp#V@9teR!VGPj`\X:ddo%]CBZa\q-1R[781j=,W:4(NN5bP;NW"-_iXf^VIshceiY\@oKC_'5MZ:Eto-3%KkULEHQVJ<S(C@BS?,eT\Rg".buQM&*TWC[6"-$+;/,@`S:4WW.pPHR:l2UWp)#%(R&I-6.ebNcV<4_)KX5IjFo,hDgUmFMGu*af?3)k7!XaS5's>l,;OD'HO(RlQe24Hc;s.mJW/V@Lg?*n'TnV`G%tHn%O@Cqa`#8:P\t2P28[4pgh[Bol=!+\ee@@Wa-bt#HJrO@]><_t+O'NoahESs%q@(n@$EZ*4/A03KFnq7=hRo.I]'XFf@.O]c85g;@^l6k720Rjapt/L1isW*Ail%dMoqD$9?,ZK:ftj[Tch4jN"1#B1T*jFQd'6??;J/1>0kZXngU^;oB++&#5\"[4Onc?'KWJeUnA&`<qC$00WB,Z37$bT2C+lMF,]LVV&s?\UM=aoN:R!V16s;TIc>pI<W?JT0/2Fm3+MP7AP+aW];$AGH#)B2MIVYVjW$q9pW4EIP:4ga5C$SkO6/BckFqQep,$EQ0tG(]>;d8bcB1"c[kK9$c[)+jINs$OU%]Au-gWJ~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1607
>>
stream
Gb!kt9m<'d']&Xf]VXB4":5,"@M[6ZS'8uJgOs[6%07?i_*C<)dojn5T-qFHZ&S/@a_#!04rA;.]6_oUHQceVMc>1Y$,D6"kFW%l$Jl="1Dk@\E:%dsFq!Y4Qe>bKgpHnlN4u-<+HJPifT+q_BqGAa?%]`t(afONI1?s`F'[cl/n"IZ6bmX]e)^uH:2G\IDk=EHLJjp[L@$u!p#hh>\.5]\3)+K3B)P1Q*'kK?F&\&8-F$hq]Er][r;,FhDUM&i_'i#fGZ;3Z7LG_8F^]7D)\duWPR-5?]S\\n-6c;/^`.q#;0+>J1Wq\DA\+r;GG5((i22aX<9,\imPd@nM%B:md!)#0)3R@V86tKtgbp#sA+uKHKjfS.M9eEHZrIc,X/pFsd\JGPWLIKlVMPlSdt\\7jT+FAj\WZ*J6C^2l)Jl7<pP@_=2jNO%dnm&Z['bPAf^IVES[`_>/[WOiFIcQlLu'5Z3l%,Z8hddBa-di*f(ZY+Nr6<%.sJ5j8C">"R`Gu=+=InNIg+h]q<XTf0Mal;EpC;"N?:TKgKXT.iA'h*cH6.ZhXD(>AtT.%.b6k=H6aq\gUK?QM)X\OmlO2QTOh:oDS;5\.6s]n-be"U0$G%;t0)8dT9?19l)[o(8%6*EZpTTPUaaB?B7_.Nm3R3hbj2H_S))!T]JAoU/]I*nPghYpjNj4bV*o^dPM2_A,Hck^TW>H2cI=a:M-K58W:\U/s:AcAXjcRU"h[tRY\4+6tS@?e;WWD>Rdt=?-4C6^IWKG53#M1V:mOPSqX&E[[E%Y7O(oDINWg88*;-ikoNHfj-4!nE-X8/N(LNNUn3*CSu-7X7!93"efGIWE=s@H#^bb]1/jZ!ep!#C?*82\M/3]ZoJh\g=m50D%P']L%\re:^JXjWSHoDY09uEbj^,IG7Sfc1hF$ao)4_iX`MSTsXm*MMPVIWuH;pPqLNn1o;7hZ_G>I[&oQ(LLF<!H>Rl)/]2&jHcVD1PIG9Ia'J'OY&K=hZ(mi2ApdT0V];,doUh5G@S>9-%Pr=E.)Sccp.<?H:M9-nU]::;uDAs8n@phs`<<fY5nPHV`RPP-Mc6/=+E=VL!?9%B2Q+Mnam\?=ScBqD$,!#JV^(12]2D'"JnB&0t&eE#5&00gT'/s9$r,"4L,n='6'e4/*Kd;=P=,ukU@D`O;(W=,;spfg4\4ir:B"M4GFTki#`b-Tom:"TF<A>5`g8pg>ap(sE6^iQ*A9ZVVJMH:d:'4Cu6[LqN5eG&FN0WkO83oCW]!C</fQVoX%@9a:tXcs:U<m!J@?E/B;DLngC&p%cS[\)IIGMFnYkL;hjTe9tYp!3u*c3*fJ3ee*m\WKQ$RnR2/n?C_8WP].&3OX+:bla=h+)lHpPf@-8XjGt@3A[=GqNZjO/g:>BEXZ\4a$UG/imj*"O_d72((h."-)1G!qOS[^4fmdWTNdh-j6'B?Zh>aCs(]kl[1KoV1$<eMMRN1A)mAjPS2iM4Zb`-YYHU/?E#[D?eDM*3AXp/GkF#nL4\RI$mk*,@)"U*p`Bbs'F"V!ZXCm=@EW]pA],2K^H#(Z.?EmO<[JEn(^k7($+7dO.*]D\_R31g(\0I:J%'LHG"eC,D8pg`<~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 830
>>
stream
Gar>DflEQI&:E)2oSZA&'<cVH@j,d9iRh/Qi<k\t<IVbt,[k]6J>I;#5-ocm/.2eAlqC2n:ToB29=<(k#<kSd\5H-Ad2^:D/trZd7\clDd(VE@#N5g6#rP-7fH7-4W.=%=5LcAn+H6Yc$TCm+#f6S1l\n[F'k)PL;@pj7-?(+0jnXK-VVVp!f%esoT'qHTof^(*dL1cdZX]H[c!['8qG$?S*EPen)%TT)diUfP2n1IV<%#kpbj_O0-s8I<f@o0Rk5;Nfe%24j^<hV?Eh-.V]L2=Ikfi`]^ipep\;iDLnL%:'.9o*8op2UXL\iT.UdY^l!9f_;h')U*B53r5GY/-%N)p>_Xn[q1-TmA`*f+g``"*`j6:leT9@m3@Y0,]A^RKgC`W6J03ArjeNoLYDLL^[TFDTJ=H9=64J5J'=)V?1kHMc!6=SlInlXUBjnCut3g#mHQg%%kA`8);gd0UPo2g]#sKLU<+aNb+!j7U]]*KM5aEuT7&Of`_r,?dmdf$es6kaf0)[9m-$%u)T[(3Z:L4TZF$g('AKK^*r7r2pepSJ#M'ZE.(QRq>.B`7aE9c)><rDFq>iK^Nc]Z"=j*S_bl,&1o>l0oHuE-JoDJ=Kr#k+8r$PI@at\5p,TC>rLsF7Gs2Cet>R5JaJI8jeZWp>4`l"Ybe]t(KStiRaiFMQL[0s<"W@6W+$Yu#k\QIKcKEYbh#"3nMPC'6i)U5`@8bCgU;U&fjf+,nr9U+NcSNB;F7q^e#4qf+R<q<1UbMd2\F(\956ae0AQVQH"0qTV(B]XdUn9]'BCJ%>HqKV0qPh!XY0;Ae\SR>laFJ\q@=ad6HK~>endstream
endobj
xref
0 20
0000000000 65535 f
0000000061 00000 n
0000000112 00000 n
0000000219 00000 n
0000000331 00000 n
0000000536 00000 n
0000000741 00000 n
0000000846 00000 n
0000001051 00000 n
0000001256 00000 n
0000001461 00000 n
0000001667 00000 n
0000001737 00000 n
0000002030 00000 n
0000002121 00000 n
0000004440 00000 n
0000004975 00000 n
0000007002 00000 n
0000007864 00000 n
0000009563 00000 n
trailer
<<
/ID
[<eafbb3f2da8b68eeafb9581da56c62a7><eafbb3f2da8b68eeafb9581da56c62a7>]
% ReportLab generated PDF document -- digest (opensource)
/Info 12 0 R
/Root 11 0 R
/Size 20
>>
startxref
10484
%%EOF

View File

@@ -0,0 +1,195 @@
# Les 11 — Lesopdracht
## Bouw je eigen AI + Supabase app
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Wanneer:** Thuis, vóór volgende les
**Inleveren:** GitHub URL + screenshot van werkende chat
---
## Doel
Bouw een complete versie van wat Tim live demonstreerde — **met je eigen thema**. Tim bouwde Polderfest 2027. Jij bouwt iets anders.
Je oefent end-to-end:
- Next.js project from scratch
- Nieuwe Supabase aanmaken + koppelen
- Eigen seed-script schrijven (mag AI bij helpen)
- Chat-route + chat-UI met `streamText` + `useChat`
- 3 vragen kunnen stellen die alleen via jouw data beantwoord kunnen worden
> **Belangrijk:** dit is **niet** dezelfde Polderfest-demo namaken. Je kiest een eigen thema. Anders leer je vooral kopiëren.
---
## Vereisten
### Jouw thema moet:
- [ ] **Volledig fictief zijn** — geen Spotify, geen restaurants in Amsterdam, geen Wikipedia-data
- [ ] Minstens 5 velden hebben (waarvan 1 categorisch, 1 numeriek, 1 tekstrijk)
- [ ] Minstens **100 records** bevatten
- [ ] Vragen oproepen die je écht niet aan ChatGPT kunt stellen zonder jouw data
### Verboden thema's (te bekend voor LLMs):
- Restaurants in een echte stad
- Films / muziek / boeken die echt bestaan
- Sport-statistieken uit de echte wereld
- Klimaat / financiële data uit publieke bronnen
### Geschikte thema-richtingen (kies of bedenk eigen):
- Fictief restaurant-aggregator in een verzonnen stad (bv. "Polderstad")
- Galactische bestuurders archief (sci-fi)
- Verzonnen scriptie-archief van NOVI (1000 nep-titels)
- Fictieve museumcollectie met verzonnen kunstenaars
- Fictief NPO-programma overzicht voor 2030
- Verzonnen mysteries / cryptid-sightings database NL
- D&D campagne-NPCs voor een fictieve wereld
- Fictieve nederlands ondernemers ecosysteem
---
## Stappenplan
### Stap 1 — Thema kiezen + schema ontwerpen (30 min)
- Bedenk thema
- Schrijf op papier (of in Notion): welke velden? Welke vragen wil je kunnen stellen?
- Vertaal naar Supabase SQL schema (zie `schema.sql` van Polderfest als voorbeeld)
### Stap 2 — Next.js project + Supabase (15 min)
```bash
npx create-next-app@latest mijn-thema \
--typescript --tailwind --app --eslint --no-src-dir --turbopack
cd mijn-thema
npm i @supabase/supabase-js ai @ai-sdk/openai zod
npm i tsx dotenv --save-dev
```
- Maak nieuwe Supabase project aan
- Run je schema in SQL Editor
- Vul `.env.local` met URL + keys + OpenAI key
### Stap 3 — Seed script (45 min)
Open `seed-polderfest.ts` (zie bijlage) als referentie. Je hebt twee opties:
**Optie A — Met de hand:**
- Kopieer de structuur
- Vervang Polderfest-bouwstenen (genres, stages, cities…) door jouw thema-bouwstenen
- Pas de `generateBand` functie aan naar `generateItem` voor jouw thema
**Optie B — Met AI hulp (aanbevolen):**
- Open OpenCode / Cursor met `seed-polderfest.ts` als context
- Vraag: "Pas dit seed-script aan voor [mijn thema]. Schema is [paste schema]. Genereer 100+ records."
- Review wat AI maakt — vragen om aanpassingen waar nodig
Run je seed:
```bash
npx tsx scripts/seed-mijn-thema.ts
```
Verifieer in Supabase Table Editor: 100+ records.
### Stap 4 — Chat route (20 min)
`app/api/chat/route.ts` — gebruik Polderfest-versie als template:
```typescript
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
export async function POST(req: Request) {
const { messages } = await req.json();
const { data: items } = await supabase.from("items").select("*");
const context = items!.map((i) =>
`- ${i.name} (${i.category}, ${i.rating})`
).join("\n");
const system = `Je bent een assistent voor [thema-naam].
Hier is alle data:
${context}
Beantwoord vragen op basis van bovenstaande data. Verzin niets.
Antwoord in het Nederlands.`;
const result = streamText({
model: openai("gpt-4o-mini"),
system,
messages,
});
return result.toDataStreamResponse();
}
```
### Stap 5 — Chat UI (15 min)
`app/chat/page.tsx` — kopieer Polderfest UI, pas titel + placeholder aan.
### Stap 6 — Testen + 3 vragen (15 min)
Browse naar `/chat`. Stel deze 3 vragen aan jouw AI:
1. Een **filter-vraag** ("Welke X hebben Y?")
2. Een **aggregatie-vraag** ("Hoeveel X zijn er in totaal?" / "Wie heeft de hoogste Z?")
3. Een **samenvatting-vraag** ("Vat de Z-categorie samen in 3 zinnen")
Screenshots van werkende antwoorden bewaren — die heb je nodig.
---
## Inleveren (vóór volgende les)
1. **GitHub repo** met je code
2. **3 screenshots** van werkende chat-antwoorden in je README
3. **Eén alinea** onder de screenshots: waarom kan een gewone LLM deze vragen niet beantwoorden zonder jouw data?
---
## Veelvoorkomende problemen
| Probleem | Oplossing |
|----------|-----------|
| `OPENAI_API_KEY is not defined` | Dev server herstarten na `.env.local` aanpassen |
| Supabase insert: `permission denied` | Gebruik `SUPABASE_SERVICE_ROLE_KEY` in seed-script (geen anon) |
| AI verzint dingen | System prompt verstevigen: "Verzin niets. Gebruik alleen onze data." |
| AI antwoordt in Engels | Voeg toe aan prompt: "Antwoord in het Nederlands." |
| Chat hangt / streamt niet | API endpoint moet `result.toDataStreamResponse()` returnen |
| `tsx command not found` | `npm i tsx --save-dev`, run met `npx tsx ...` |
---
## Tijd-indicatie
| Stap | Tijd |
|------|------|
| Thema + schema bedenken | 30 min |
| Project + Supabase setup | 15 min |
| Seed script (met AI hulp) | 45 min |
| Chat route + UI | 35 min |
| Testen + screenshots | 15 min |
| **Totaal** | **~2,5 uur** |
Loop je vast? Vraag in Brightspace of plan korte 1-op-1 met Tim.
---
## Tips
- **Begin klein.** 100 records is genoeg. 500 als je extra wil.
- **AI laten helpen bij seed.** Schaal je productiviteit 10×.
- **System prompt is je hefboom.** Goede prompt = goede antwoorden. Slechte prompt = AI verzint.
- **Test met simpele vraag eerst** ("Hoeveel records zijn er?"). Dan opbouwen.
- **Token cost in de gaten houden** — onze hele les kostte <2 cent. Jouw test ook.
Succes!

View 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 6 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 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
5 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
6 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
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:20260519160527+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260519160527+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 11 Lesopdracht) /Trapped /False
>>
endobj
12 0 obj
<<
/Count 5 /Kids [ 4 0 R 5 0 R 7 0 R 8 0 R 9 0 R ] /Type /Pages
>>
endobj
13 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2031
>>
stream
Gb!;d97,A^']&@2mNu;31mU"oGBZfG\4N5MEAhV;0u+VhLa;>jrq/E1#YBQ_`-'ef@"b>Dq`OW..i?ra1%Q9\6neCYnsU0Ne/o?F/XQ=soW$gGhQs`<"4nd@$#M'LA4;OXULMZpaR^uJiXqs4%]h@'aUOXQL/CJ]qgs>%<(IollS5[Hh[Vu>XH7^Vk\oj4/>%!^FtM&7UWrbFIA&PPZM`E;R(7T3(fmlpWCEoJ=uPsRO8::G^&N'ALAo7AZAb],T$Z)hOR]]P!TsKS^,\A/93##56ndYe9_*HYZg'"40<+tFlfuQ8\SL`V%G'X5bE<\e*:ip(&<5n7@\PVDX<[$Eijocbo@d=(@9*qb%e?k`kWL$bd[hD&b=LBPi$>V<mHZj)%/?O6gN0"H??<uNA(dHoo\T;1D`IiVW0,)%Zs;-0CTbUhcL%E/.r2<?XYdXi(ADX4Ol3QP?Q\o?i350-+k7>96gha6%_+J&3$G$dE5$Au,+TkCqT]2d%W*k8IHOJ@W_q>PdCqOeNUP+,]9R!;b5S[7>*rrJN.n?U$paGeKR-ddR=6`giT)k<ppsi5/7)CO#K<W^(-(*C!%-EC&9UidOP>?WH6F-*@"p/$]qQ`%i_19laY35(2-ih;C*GGJ*;rK:(3;2EV'Idf03'WC<`>ED46m&R!mNa(QgTI&qE:0($bl@OF*eW?fOu6$Gsc\SrAGoP\%oE5H_"Jn1[U9aD_8o3jjAdLrO-<[f;]+\dZHLg2HuMr2a=FFZcNOBjhuq",1T4qPIk*\<5XG@4*Z"2/)7k*/)Na\8Qjj9aJ&/`*9g68WdIQC"35WXImI.]!r.d$-*l=hcb8+ZP9ibI+gV7j\sGWoWqp3[Up$E:;4;'$8=>al]V91Eb`#Wn<,AJsdtS<+I<t+;49V'he7ZAnFLGAW4Wf&8_o6CYHS2/UqGGL^PgGcO"hJ-l#i%MOX9HL[U2?b8^'r$)>UDPmghQJONlEr]>$>*b[LI@,,rHnkaqX"fb-$n\r0@ZpOjUS8W39)ke<Qhl9_5>RTu[:\0MH2>CI*!*b,T%)Q9\[)8!]8rK[Puf\T[U":I_W,bHaGPTPr4^F_TE+Uh!Jg5iR^Mg)5cker#&S.%UhnVB2bU0TkQnECFlAk'H%9%3.O]D]6-"\\@6[lOB@GIX;F$SXpY]:!K)D]5B.;j/mENO0KLVUmC6/L0JT$[A6pr4`9$IK&rnIm#5XNhk!]rHNNt\q#\W,!Vb@TOO[([=`nG^1O'o"AD*C^mQZ41j0o%=kQ6Xn%G5,_<,Wfe^18Fn8YIbL%2@Sl&F-'70_-8V#MO'+1'Z*/E#`Wr;C59B.^8,:M_^2c%7J!WZHrlib4@qF1s>&sJ>e"&[aDFF1`W,N=:&>b/9T"Wm]L%*mAet[dp5(Q_Q&kgm9<lH-8^tLZJ*RS:$HWClY&L"-t-bU3%Rh62jOIPJt5A?G$L&t.p^m24-87[\;NI@j@E!YXPujo1#8eOCm19QN&K'_pgp$uCAc]Tj]MWJM+&RC0G3O01dsH`O[s\b)g8*AT-'8D:-e8IVI<VZA2),bi9+&LLH#^&n;JVB3QVY_l0(-<A;aD&9$[)'9$Ycc:sO]kHNMPh1I")(.p]bQEGOTM!jL@rQu-Vnc&5H0Kor(A'%O^2%bnPW?h!u"RD1/'-I.!FjDp86,;aaDAu8^4T]uK:$1$qfT4X"_,(p60PL3+-$'m2$YDbjq%VJ^>eUu6ffQ<LA<il<o$*[lH)+m%1)U>8Po69<SklH1Ph!":th%+,mo/^Aq'%9W8'%mtoa%]dA<K`Yi6L!J6KS2"r>\DrEf_gmje@pjS"o9<[dgpb]##nRBE2:A;TT"L2V.ZM`O*DQEB0Tu@qD2!J^sg7/H?(@<fSjM]D)Jd>?g!>UU-5D_OlI%"4/"_BLN3#qKQF#?+AakIgb@#nTb)^=:%g^M2\ZoBL2m/6VO0l(d+@q9eIG*i@PTCn6r[Q&O:Ij+DbT0Y,+\`s<6mrVi%0C$%D+/,KmD5\"e^DX%$&J4*KS`&"47aVeGC0dEk%>~>endstream
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 596
>>
stream
GauI1_/J$](rbtA(%4!,9";=ifT(.?b0VU]ek0XP>d@a([_uTo5hc56R=mmD.T.q<<\2r"Ic#L$@A&(Rp77MbQPao[#0%6._#t@+Vt_/@ILc^N>_s";cnpP<8/1D3eChV]PXIu'@aJ'dXTX;h7-dBrq`:R@6>`s>*AA$NWR:i9ZVYC^<Z_mp\Sj&j-tW%#rEc,Qabtu1X7J*Yl!aQ![Pr?%#(fYeWXs0,iU$I-,JI0GETsL'$G84'8QQA`S1EAZNp`I]?"4WeS#n->F_PZ5KD6hRgKh!(?Z8;U,h+8mC)e(C?`Ndn:(HKc,g1$+hIA_GHT`#mPpSPf_WTm=Bm\Y2C6P?.1Am6W>J9``Y2hoI4n04q-hhMqhjA93_`:Z\6t0M6^duP6B"_O7ju[O*$@ar+ho],4iR0bo2n9ePFh5`?&oB2k+et:=Re<W$)cO9Bc+@.9!e!5La'KiC4NUN?EbeKhSCs-@<>9&SK@2U/*cC#i`qj&f-;!SWpuV-oIZM0+jS,AIj&96B>b8'dgh%%SUe-"\c<rQDAF59"\lS",NcMMVZ]="S"V3hW59inFFcol`RQWg(a7\3t%/p9p>reP~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1846
>>
stream
Gb"/'>>s9G'RnZ;3,^,-GgTj)>32S(C""i6#mjB';#7Ce$PqqA8N%j/^1VSDJ7I$e:1g^nem?\qgN1YtJr=!ir.c`:M`L.<U-KU?:Oris@"&*jbaje)@?UH'"2^>H!sU%/p&jEEPCY*<$s5b\\NWm)!21f<.T!p"TC5]#8ctte*=B`OY-.s>76RX2(]-4TN$2t&4=PoPq`FV<'e^]8P7GQJIn0uLcq^Qk.Mp%"T'sKXKq6h654$1*q3e5!7@kmr^+N-iW8X*u+85D@HW:e^[>EgSZSG*YUaE.7_#,27:G&aVIDH%pJafkl5;3gK$M_]#W71q66[]__)Yf!:>I80A9X7Dqhc5>Nf11=l27g]Q3/S(m,btqA8D>-(&r6;$FbQ9C1"EB=K_gj+8%a:FkR:tRTF,g5-:7g:$To8>F5!c7"R$(W'X2L'%`MR3::^NNGMj)pmO1MUSO(>7o:X)ko?^W"%Bf',FoUk[Mk:3hP,Jj7'=Ui*0LZjL0;5))V]*%lM-gO*c$]+CTe@A]!F]?`(Q<Sq5jVgP5@9dW_iK>HZ]3ucmQqh)a%u-=_Z4$-M#e,/*OhI)"gQ3,K!=4(pA5SDeS\LlU5@B5DC]@p>YDC`q`DjJpTKE5'b]?e;0:ZJ(UUq=Lst\.?^SoF(Jn>"grT\OLq>fE7UGi;j="8tnbFWp"VeP(:&,2M9laYB@gMAp'E$PW8puH-Fh2HfeSin.K.1T<'?IO5iFlBX'3uJ8H@+")q[:,VDt"hmS\tWY*;miB\&CVQ*]VLeQMJ&tCCG=f30&3][l2kJORO)Wf=/;O\k!eKL>gfPUO`PK(kf<@;b-V3>AO^XM'J7#2i^6m73[R=>^nf/qDFs&N;8%,6X6f<eZZk<kKJRM\Dl>=6%WMJ8W=aK$=BdSZo\nA7pZrJC=L'?AaT2Pg5O9\J\`LCr8ujjMgtqh1->k,nHA$^EEcIK6!,%2Ros@+lBs8^k6^j^)Ei;9c0@3(*T(01od#5],kkLc!6f'JVZQ1c1+m=rd;a1EB+[%a3rYTuM).5Wjg1rqB2MpfA]sWi06DHDSe^G,kDZg]_It.TqL&b]DemKjlP*fQ??Jfpm2ND'$8bNAGZI6L]6Rg?m!d6TosFVZ8@kt,+NeJ`X\c>rYKSOQX08@IU&=m(gahE7#C5l_CmLE"$k:/e/\8ft>\9DG^?5Vjd+K\d6d1[lWT!ff"4ZbUI&c]+#(a.jDgRtO8V)Xh:nKu7afX\srkNgMaP\)FMiGb=@`&<td*=ckmZ#HdV(l)Gf!23Gh22,nQ0V.IXYmr9R1JJ?:,p"n9OY-<$3,RS+u6CL=nHhc\G@!Ks.3^LDG$kmEZ*HOAI0lX6%tVs'fjk,jXK=N;HF"*Wl:qn36@MRGl,Mm"KOX<i>ha^rIeu0X8K.Qp0tBGL5j/-.1e"5B.%;V-fua^DANWnjTYpbl;e;HJFt[YbjNYc=eRup-:DX7>+[s1*su:>'\BHU4"R.KXm,h"bkCcA"HM3_Sf2Z^<d+L)Z\m!R>\^<?YJO&7ogQQFR@+Bf`G>#D?R9_NXUdFTTpV3oWa(R<+dYnpaQ6M4@bVps\<b^3lbDc9b#MB@Zqd]G)ip"*'qUPk74*u:6jiDQ^"%;TP^)+Col>V=[*4lE]uI>Mn#Su0TP]!+gcK3LC/2o_Hi]!iKV#;>=^a/:8HlLn$[XNME/;?.egDf4AQl"?K)C:a41-AGg;]fq@L6%nfJE"&g22GmgBM?Kn)*0E"-oV$_)GF5,@HXXLX3Ig#so:K)k'2O^F:k9:S]K_X8_K1;a)ljH1qX_m$e`;ClZjb)";ME\'l+HOnih?B(hK`ek9G0[ALmugV8"Y!9YH0r\i^~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 649
>>
stream
Gatm7a_oie&A@rkhCWUFdiq4P<%if\ejt=aD%JR1cjO^>Zr1%#.Dc8boc;4K][T4Y6mAE/3H*ckiJ?^Zm<:YthA,5b3Oh=/"d_oW\Aj;uo=Mn..PdEB`@?ad!LCdL#MsW=,UlB-O`RHL@k*\(MDM)L"[R96`\Vjll$8&:ASIAX$)Vs1Wq<5RAuQ?k>-]DJo>CD^5II;RZZOHPBW`,%d0[N&[7-'$Mg1C2.-B`Br,d.:94psTSiKm29CQOU$hu/=L;?l.cno;IbQRCZ_Gor3DX-]q`BB+MAZm,%.3oS;bkCS19`r[,5PHl%H40[SgToCl4'4H&lOhf,O-iN`8U8[+e8DI3[E1gR11H`rVHuR.X-+ing*%P(0'W(b@Y<?Gh;rI6i%@G9X^uB2*Q,!E*^Q2r05o5YQ;oWQ\+!SU[`P;;mTZ302:X@4DjZs?aOL!6EH[KOh5aTh/"Rnb]M.\->&UJPV)^""da8L['hJlfBO+o6ThdS2L+G%-!4nbfDBaNll,9V>&O40hQhbU$Bn-s[KX/$6S[d,L','"<lX6gcA3mQ'6Wm@B^L^]?qUgFi"/`'MN)1jE7VIS&J$%Z+<nr_K0m,9nTu"hJd@P$Y"$C:M)<'GFG*C?`iLD9M'r=HV%NRt#+"<79,l~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1821
>>
stream
Gb"/'gN)%,&:N/3lq>>n=u-9ehD3R%.#F*8FV;_mh!'c04HKhAPY$HM+h>ib^V($EU5O/6PI+,nA5SSqkNi9&K3](nnNkCb,R&EIKVu![&.6;o+O":K4GVdto;,qoO8@'(b6_u4UgSn:re"\6ah[R#0;KbB_"OnGB>=RX"[Nt*76BBS4qn'9Sob6?^1[<D""PLs(O5]@*Qt2lWL;jo?FXiZq\Nr?6HcFC6T;%nK&Hu5#M#P0bd*)k].cq;oY\_M5.m.Z;,1_$-Lb.Q@PUk9U==s9s/]FjEN)>/YJa<a1/C?9*lDq,_^Cmg%A-L0d?+0npue@S63&YoNsa`1?gqd"EH'O652j.4?llG@or2R-Y0CoK%Sutj-T64<qrqmd3AWVD/DOr(9/)/gA/h?1[-\iYDQlTGS$+=TZQX.k+_\J.)Z'N%o?TO1XeunGjXIJ.]#a5aFrR=UE#$G$e=uWBR)8*W=M8"!E<G=NE3/%ZKiBIf$^f"+m,c8^r'NfH%g\;H>DNcRP<t),S4Q`;$Za?F\.ci`a<D%8`YHF[Nr$'5IJ(<u+RU<a[e#$R^Fea-aDjcU4Gbl1\"eBVGFTI)Kj0KWVZs$X:1aRQceh)8^'[sXI#9"Y0lDQIi0h0?ZAA"6Y7g#tJ@Sq?6,XLfIaA$i>B.$\Mph:9Eq`1^A;Mqpb1%tI?H@+1dYHm=@h4fc9\Xj/9!6L%]MWGBBZ;a7CQ'!C$$TeSg/q5L.!"O""0bR^ij,+@*PnR6UMOq0(=I%O>['f2LeGu9E+'MTUq7-I&dFZj\m[\uA"H4RG-^Adph^<I&$XKdK":)nkk;3l(]EZ,HbVpQ67*H8p)?\eka3kX#+g-;M?6Q_R#.P:]MKPODNi_UcFgt;k@7g*'Ge&UcQF#m)Il/j6[DG2U[/H:kDSbdm\"bekZqNf?hUWU5G%UC0N0\Y(@gd@?NO[J\pZj$^W%,qNYB!BI*5ca%K=:%5Li>kk32ieQ,,0>:nI$KFT$s"OUi\=4etfp,J:GOPn[.n;BTP[`m>8Q'Vu<AALQM?[,1o.@L_m+C\j!&dliCW93RB07(GjQV;s.k[fP&E4M;G?/ogDNVk_;Bn("/Rp.oh=;cNRJfj?[YOW@Z`as,#-LdBddSX"ROc6BMu1Xo%j%fb]4MZa'':pML8MN0,(!u*K"I+,?*\8*rN?0/-$9jk24S!apl%eEpWMRs,GZlSYLi]W7`d92u.nV!rfIqd:[2T;EZ6/;Jjp:mEU"3'jpA\MFllHN/=\DJ[`_jO%Ur.J(>Gr*H6ch9ucMSHTCCe3T]RZ'b`>YGj;[-Cn*?69W^2CIo9P"%;4p.8A4`]5q;Vn/6b/Y"DUI.[1>9b"lJZIBe,jK4LO,qt_4r?KCkO`M)E(;cb1e^)H+CmNEEiG6WSdo3HU<1G=+n>`13d@X]H/;jI$%4T+_S^De!fI@N-:-7B,kL!C>pBldf<J*ktgM2ni\aJ8#$@.''ijFo6q:M+I:&t"-cN)7ZDBS60-4%"VEu,GtO0(Z6[R/%/'=83kC_\/.[2Tc`,>I[ein&?:TR+#*T+dU.DbhMA4Ml^H?7\(>Ther0Jn:gkeX1;]A&:]F*o%nPS[^cX)`QPeE/84I81!r`=5E<Rj*]D4Z0c^*Dk"06B1.+h?C4a)9a&2!+!HGWl]ABo.!9fkjUp&c##h!m7[V%!f4crQ8WK-W4@eMK6oPB"=h(GrY*>["7)d>OM-(qjaJ&SKdRI*1q69+RM4:cg[`j7rV5QEorV\DBmA^oUK%&B[AXqm,1$opP@Uk0>.SA%/>:Glj0K$[05J]*%_Tks>A*i6!1egt^5j*'n9RVT~>endstream
endobj
xref
0 18
0000000000 65535 f
0000000061 00000 n
0000000112 00000 n
0000000219 00000 n
0000000331 00000 n
0000000536 00000 n
0000000741 00000 n
0000000846 00000 n
0000001051 00000 n
0000001256 00000 n
0000001461 00000 n
0000001531 00000 n
0000001827 00000 n
0000001911 00000 n
0000004034 00000 n
0000004721 00000 n
0000006659 00000 n
0000007399 00000 n
trailer
<<
/ID
[<e7826657c82f631c6aae1c8477e154ba><e7826657c82f631c6aae1c8477e154ba>]
% ReportLab generated PDF document -- digest (opensource)
/Info 11 0 R
/Root 10 0 R
/Size 18
>>
startxref
9312
%%EOF

View File

@@ -0,0 +1,527 @@
# Les 11 — Vercel AI SDK
## Lesstof
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Onderwerp:** Vercel AI SDK — AI features bouwen, gekoppeld aan eigen dataset
**Demo:** Polderfest 2027 — fictief festival, 500 records
---
## Inhoudsopgave
1. [Wat is de Vercel AI SDK?](#1-wat-is-de-vercel-ai-sdk)
2. [Het modellen-landschap](#2-het-modellen-landschap)
3. [De vier kern-functies](#3-de-vier-kern-functies)
4. [Project setup van A tot Z](#4-project-setup-van-a-tot-z)
5. [Seed script: dummy data in Supabase](#5-seed-script-dummy-data-in-supabase)
6. [Chat-route + chat-UI implementeren](#6-chat-route--chat-ui-implementeren)
7. [Waarom data + AI samen krachtig zijn](#7-waarom-data--ai-samen-krachtig-zijn)
8. [Best practices & valkuilen](#8-best-practices--valkuilen)
9. [Wat komt hierna? Tool Calling teaser](#9-wat-komt-hierna)
10. [Bronnen](#10-bronnen)
---
## 1. Wat is de Vercel AI SDK?
De Vercel AI SDK is een open-source TypeScript library waarmee je AI-features in je webapplicatie bouwt. Gemaakt door Vercel — de makers van Next.js — en daarom perfect geïntegreerd met React, Server Components en Server Actions.
### Wat krijg je?
- **Unified API** — Zelfde code voor elk model (40+ providers)
- **Streaming out-of-the-box** — Geen WebSocket-setup nodig
- **React hooks** — `useChat`, `useCompletion` voor instant chat UI
- **Tool Calling** — AI kan functies aanroepen die jij definieert (volgende les)
- **Structured output** — Type-safe data via `generateObject` + Zod
- **Multi-step agents** — Via `maxSteps`
### Eerste indruk
```typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
const { text } = await generateText({
model: openai("gpt-4o-mini"),
prompt: "Vat de Polderfest 2027 line-up samen",
});
```
Wil je naar Anthropic? Eén regel veranderen:
```typescript
import { anthropic } from "@ai-sdk/anthropic";
const { text } = await generateText({
model: anthropic("claude-sonnet-4"),
prompt: "Vat de Polderfest 2027 line-up samen",
});
```
Dat is de waarde.
---
## 2. Het modellen-landschap
| Provider | Model | Sterke punten | Prijs (in/out per 1M tokens) |
|----------|-------|---------------|------------------------------|
| OpenAI | `gpt-4o-mini` | Snel, goedkoop, default | $0.15 / $0.60 |
| OpenAI | `gpt-4o` | Multimodal (vision), krachtig | $2.50 / $10 |
| OpenAI | `gpt-4.1` | Reasoning, voor agents | $2 / $8 |
| Anthropic | `claude-sonnet-4` | Coding, lange context (200k) | $3 / $15 |
| Google | `gemini-2.5-flash` | Multimodal, ultra goedkoop | $0.075 / $0.30 |
| Groq | `llama-3.3-70b` | Ultra-fast inference | $0.59 / $0.79 |
**Vuistregel:** start met `gpt-4o-mini`. Werkt het niet goed? Upgrade naar `gpt-4o`. Dan pas exotisch.
**Wat kost onze Polderfest demo?**
- Context = 500 bands → ~30.000 tokens per chat-request
- 1 vraag = ~$0.005 (een halve cent)
- 50 vragen = $0.25 (kwart euro)
Met gpt-4o (15× duurder) zou dezelfde demo ~$4 kosten. Daarom: start mini.
---
## 3. De vier kern-functies
### `generateText` — Antwoord ophalen
Wacht tot het AI-antwoord compleet is, returnt dan een string.
```typescript
const { text } = await generateText({
model: openai("gpt-4o-mini"),
prompt: "Geef 3 koffie-poll opties",
});
```
**Wanneer:** Korte antwoorden, server-only, niet-interactief.
### `streamText` — Streaming antwoord
Streamt karakter voor karakter. Goed voor chat UI.
```typescript
const result = streamText({
model: openai("gpt-4o-mini"),
messages,
});
return result.toDataStreamResponse();
```
**Wanneer:** Chat UI, lange antwoorden, "ChatGPT-gevoel". Vandaag gebruiken we dit.
### `useChat` — React hook
Aan client-kant: complete chat UI in 10 regels.
```tsx
"use client";
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<form onSubmit={handleSubmit}>
{messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)}
<input value={input} onChange={handleInputChange} />
</form>
);
}
```
**Belangrijk:** werkt alleen met `streamText` als API endpoint. Vandaag gebruiken we dit ook.
### `generateObject` — Gestructureerde data
In plaats van een string krijg je type-safe data terug — gevalideerd met Zod.
```typescript
const { object } = await generateObject({
model: openai("gpt-4o-mini"),
schema: z.object({
question: z.string(),
options: z.array(z.string()).length(4),
}),
prompt: "Maak een poll over koffie",
});
```
**Wanneer:** Database inserts, formulieren vullen, classificatie. **Niet** vandaag — komt terug in latere lessen.
---
## 4. Project setup van A tot Z
Dit is wat Tim in de les live deed. Voor je eigen project (lesopdracht/huiswerk): zelfde stappen.
### Stap 1 — Next.js scaffolden
```bash
npx create-next-app@latest mijn-thema \
--typescript --tailwind --app --eslint --no-src-dir --turbopack
cd mijn-thema
```
### Stap 2 — Supabase project aanmaken
- Ga naar https://supabase.com/dashboard
- **New Project** → kies naam
- Wacht ~2 min op deploy
- Settings → API → kopieer:
- Project URL
- anon public key
- service_role secret key
### Stap 3 — Schema definiëren
Pas `schema.sql` aan voor jouw thema. Bv:
```sql
create table items (
id bigserial primary key,
name text not null,
category text,
rating int,
description text,
created_at timestamp default now()
);
```
Run in Supabase → SQL Editor.
### Stap 4 — Env variables
`.env.local`:
```
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...
OPENAI_API_KEY=sk-proj-...
```
Belangrijke regels:
- `NEXT_PUBLIC_*` = client-leesbaar
- `SUPABASE_SERVICE_ROLE_KEY` = server-only, voor seed script (geen `NEXT_PUBLIC_`)
- `OPENAI_API_KEY` = server-only (geen `NEXT_PUBLIC_`)
### Stap 5 — Packages installeren
```bash
npm install @supabase/supabase-js ai @ai-sdk/openai zod
npm install --save-dev tsx dotenv
```
### Stap 6 — Supabase client
`lib/supabase.ts`:
```typescript
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
```
---
## 5. Seed script: dummy data in Supabase
Een seed-script is een TypeScript bestand dat **éénmalig** je tabel vult met dummy data. Geen handmatige inserts — procedureel gegenereerd.
### Het Polderfest script (voorbeeld)
Het volledige `seed-polderfest.ts` zit als bijlage bij deze les. Kernidee:
```typescript
import { createClient } from "@supabase/supabase-js";
import "dotenv/config";
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
);
const adjectives = ["Lost", "Velvet", "Iron", "Neon", "Silent", ...];
const nouns = ["Tigers", "Wolves", "Mirrors", "Clouds", ...];
const genres = ["Indie Rock", "Electronic", ...];
function generateBand(i: number) {
return {
name: `${pick(adjectives)} ${pick(nouns)}`,
genre: pick(genres),
// ...
};
}
async function seed() {
const bands = [];
for (let i = 0; i < 500; i++) bands.push(generateBand(i));
for (let i = 0; i < bands.length; i += 100) {
await supabase.from("bands").insert(bands.slice(i, i + 100));
}
}
seed();
```
### Runnen
```bash
npx tsx scripts/seed-polderfest.ts
```
### Waarom procedureel?
- **Met 500 hard-coded records** = 500 regels handmatige data → mind-numbing
- **Met combinaties** van 30 adjectives × 30 nouns = 900 unieke namen mogelijk
- **Met seed-random** = reproduceerbaar (zelfde data bij re-run)
### Voor jouw eigen thema
Open `seed-polderfest.ts`, kopieer de structuur, en vervang de **bouwstenen**:
- Domein-specifieke arrays (in plaats van bands: restaurants, scripties, kunstwerken…)
- Domein-specifieke velden
- Domein-specifieke bio/beschrijving-fragmenten
**Pro tip:** vraag een AI om dit te doen! "Pas het Polderfest seed-script aan voor [thema]." OpenCode of Cursor doet dit in 30 seconden.
---
## 6. Chat-route + chat-UI implementeren
Dit is wat **alleen nieuw** is — Next.js + Supabase kennen jullie al.
### De chat-route
`app/api/chat/route.ts`:
```typescript
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
export async function POST(req: Request) {
const { messages } = await req.json();
// 1. Haal data op
const { data: bands } = await supabase.from("bands").select("*");
// 2. Format als context
const context = bands!
.map((b) =>
`- ${b.name} (${b.genre}, ${b.tier}, ${b.day} ${b.start_time})`,
)
.join("\n");
// 3. System prompt met context
const system = `Je bent een festival-assistent voor Polderfest 2027.
Hier zijn alle bands:
${context}
Beantwoord vragen op basis van bovenstaande data. Verzin niets.
Antwoord in het Nederlands.`;
// 4. Stream
const result = streamText({
model: openai("gpt-4o-mini"),
system,
messages,
});
return result.toDataStreamResponse();
}
```
### De chat-pagina
`app/chat/page.tsx`:
```tsx
"use client";
import { useChat } from "ai/react";
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, status } =
useChat();
return (
<main className="max-w-2xl mx-auto p-6 flex flex-col h-screen">
<h1 className="text-2xl font-bold mb-4">Polderfest 2027 vraag de AI</h1>
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map((m) => (
<div
key={m.id}
className={m.role === "user" ? "bg-blue-50 p-3 rounded-lg ml-12"
: "bg-gray-50 p-3 rounded-lg mr-12"}
>
<div className="font-medium text-sm text-gray-500 mb-1">
{m.role === "user" ? "Jij" : "Festival AI"}
</div>
<div className="whitespace-pre-wrap">{m.content}</div>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Stel een vraag over de line-up..."
className="flex-1 p-3 border rounded-lg"
/>
<button
type="submit"
className="px-6 py-3 bg-blue-600 text-white rounded-lg"
>
Stuur
</button>
</form>
</main>
);
}
```
### Voorbeeld-vragen die we live stelden
| Vraag | Wat AI doet |
|-------|-------------|
| "Welke bands spelen zaterdag op de Beach Stage?" | Filtert door context |
| "3 headliners met meeste populariteit" | Sorteert + select top |
| "Hoeveel jazz fusion acts totaal?" | Telt |
| "Vat de electronic-scene samen" | **Samenvatting** — alleen AI kan dit |
| "Wie was hoofdact van Polderfest 2025?" | Eerlijk: "weet ik niet" — perfect |
---
## 7. Waarom data + AI samen krachtig zijn
### Data alleen
- SQL queries: filter, sort, select
- Geen interpretatie, geen taal, geen samenvatting
- Gebruiker moet zelf SQL kunnen
### AI alleen
- Kennis is generiek (training data)
- Verzint vaak (hallucinatie)
- Geen toegang tot live data of private data
### Data + AI
- AI filtert via reasoning op tekst-context
- Antwoorden in natuurlijke taal
- Samenvattingen en interpretatie
- Domein-kennis = jouw data, AI redeneert erover
> "Een LLM zonder jouw data is een gewone chatbot.
> Een LLM mét jouw data is een product."
---
## 8. Best practices & valkuilen
### Doen
- **Begin met `gpt-4o-mini`** — upgrade pas als nodig
- **System prompt is essentieel** — "gebruik alleen onze data, verzin niets"
- **Stream alles** — `streamText` voelt 5× sneller dan `generateText`
- **AI-calls altijd server-side** — keys blijven veilig
- **Loading state** — AI duurt 1-5 sec, zonder feedback voelt het stuk
- **Foutafhandeling** — `try/catch` rond elke AI-call
### Niet doen
- **Geen `NEXT_PUBLIC_OPENAI_API_KEY`** — wordt zichtbaar in client
- **Niet de output blind vertrouwen** — AI hallucineert
- **Niet alle data altijd meesturen** — werkt voor 500 records, niet voor 50.000 (volgende les)
- **Niet `gpt-4o` als default** — 15× duurder dan mini, vaak onnodig
### Veelvoorkomende fouten
| Fout | Oorzaak | Oplossing |
|------|---------|-----------|
| `OPENAI_API_KEY is not defined` | `.env.local` niet geladen | Dev server herstarten |
| `Cannot find module 'ai'` | npm install vergeten | `npm i ai @ai-sdk/openai` |
| Seed: `permission denied` | Anon key i.p.v. service role | Gebruik `SUPABASE_SERVICE_ROLE_KEY` |
| AI antwoordt in Engels | Niet expliciet om NL gevraagd | System prompt aanpassen |
| AI verzint feiten | System prompt te zwak | Voeg toe: "verzin niets, gebruik alleen onze data" |
| Chat laadt niet | `useChat` zonder `streamText` API | Endpoint moet `result.toDataStreamResponse()` returnen |
---
## 9. Wat komt hierna?
### Het schaalprobleem
Vandaag sturen we **alle 500 bands** mee als context bij elke request. Dat is ~30k tokens. Werkt prima voor 500. Werkt **niet** voor:
- 5.000 records → te duur, te traag
- 50.000 records → past niet in context window
- Real-time data → context wordt steeds opnieuw gebouwd
### Volgende les: Tool Calling (Les 12)
In plaats van **alle data te sturen**, geef je de AI **tools** (functies). De AI besluit zelf welke te gebruiken:
```typescript
const { text } = await generateText({
model: openai("gpt-4o-mini"),
messages,
tools: {
searchBands: tool({
description: "Zoek bands op dag, stage, of genre",
parameters: z.object({
day: z.string().optional(),
stage: z.string().optional(),
genre: z.string().optional(),
}),
execute: async ({ day, stage, genre }) => {
// Supabase query
const { data } = await supabase.from("bands").select("*")
.eq("day", day || undefined)
.eq("stage", stage || undefined);
return data;
},
}),
},
maxSteps: 5,
});
```
Workflow:
1. User: "Welke bands op vrijdag?"
2. AI: "Ik roep `searchBands({ day: 'Vrijdag' })` aan"
3. Supabase: 60 bands terug
4. AI: "Op vrijdag spelen 60 bands. De headliners zijn..."
Schaalbaar. Slim. Multi-step (combineer meerdere tools).
### Daarna in deze leerlijn
- **Les 13:** Agents + `maxSteps` (autonome multi-step taken)
- **Les 14:** RAG + embeddings (semantic search op heel grote datasets)
- **Les 15-16:** Testing + Deployment + Performance
- **Les 17-18:** Eindopdracht-werkdagen + Pitch
---
## 10. Bronnen
### Vercel AI SDK
- Hoofdpagina: https://ai-sdk.dev/docs/introduction
- Voorbeelden: https://ai-sdk.dev/examples
- `streamText`: https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text
- `useChat`: https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat
- Tool Calling (volgende les): https://ai-sdk.dev/docs/foundations/tools
### Supabase
- JS Client: https://supabase.com/docs/reference/javascript
- Row Level Security: https://supabase.com/docs/guides/auth/row-level-security
- Server-side usage: https://supabase.com/docs/guides/auth/server-side
### Inspiratie
- v0.dev — Generative UI in actie
- chat.vercel.ai — Officiële demo van AI SDK
- Vercel templates met AI: https://vercel.com/templates?type=ai
### Tokens & kosten
- OpenAI pricing: https://openai.com/api/pricing
- Tokenizer: https://platform.openai.com/tokenizer
- Usage dashboard: https://platform.openai.com/usage

View File

@@ -0,0 +1,334 @@
%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 /F4 6 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 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 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
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 31 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 32 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/Contents 33 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
17 0 obj
<<
/Contents 34 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
18 0 obj
<<
/Contents 35 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
19 0 obj
<<
/Contents 36 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 22 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
20 0 obj
<<
/PageMode /UseNone /Pages 22 0 R /Type /Catalog
>>
endobj
21 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260519160527+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260519160527+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 11 Lesstof) /Trapped /False
>>
endobj
22 0 obj
<<
/Count 14 /Kids [ 4 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R
16 0 R 17 0 R 18 0 R 19 0 R ] /Type /Pages
>>
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1834
>>
stream
Gatm;>BcPr&:WeDbffEG]<s*p&>Cg@8^hQug?iJ32WsGK$NW9-k#T:^[_I8]J@WTQTjHJ;[O%_fjqBQ.$k+Pu=nV1&!,$@tre:I60OFgdL^!DEn9Su"1C+fPZ"tP[Uo)=d\>F^@IVU3:$R/jD16C!/d]pTNN(E2*i4.*QQijmN`D_BiE<LE["[k+M8?oMY,_C(oD%TtXp\t035H3I[M.jOh2sV!+i<n.tC/I]`:hDdIb[WX.nF>fJ]M3<Ei@iY:JhL]<r+-2dS3(\90fptEYUEKOkNYf2BbmbFHiimSbb&WS4abH4`('KUq!V2`MuinlA\+gK5C'_HiW8B(I)[tu!X[^C.SN;Of/E/*N#oJPE*mE`Na884Ka7o9iDf"k^!deGjP<pgDmTVd9L<Tcl+PoC+K!%]B[IZ<lW('lG/cIeQQr7[f$;VPNHW<hK;':sIQ-S^+$GN:BIjV*c%J[1:#sSS<6Qh*,bE$G\8r<,9h4ofF,*R`D:rplDR6?#f$7ssm@`RtcHm&\G/jM?G7A-tRk`#G@M[o\F"pb).Al!F-9Qd12M=g@$nU7G9.tlO_jP"T'-)+V;TJ/pE##oj<UQmno)^Pjh[S&-&dhYGKfKM.&skEA#%E))4g0N@Q*=/Z_HW9ji0@W4->=0mI%cE135om1iq1W_5r4Oe*))e"Ji<FAbgPm1D[*<rDbgV/U_NpelJ-PI7>,spEiVDlFhpo'N:[]@+I-'#I.c&D##=p+E\Xll;,"r@6gVkTBkWNY.1[Wm0oK!JKZWWA<2Nj/7qI_iO&4g-+=ch(YBZSV<,f8R%]=8o@_u`@_r=/uWqE\JE+_HXhPoT4aV^k,(nGGa0?Y]H3f);a1ot_=&c-(;(8.SC4"kma2T8'^I[UM\N[(1b3Rjq,NO=pBPdc#(g+>R@gFh.G(B4:H-bi_L-m8df\?uO8#/fQ?q/9\7Ym(&;r-"j>'LV-@6qVf5Jff]7DF&AE6(o*3<N586YY@-Z.bk`o&5f-&4<;,9)sV0GFYF4CS(<*2ppa]27LcHu:YU<)!q`l5i<n;?WP<0/Y7F:9H4#N!RnA8Q:;j!3Uj.']^,D8^ims)W;b/BV[_C&ZW3.5+'b[9q2$#T@.d?/qWai#ch5Ws2DeF1.;0e8'80q7YBrb:JVi=p-iRPJ[Em5R/Y#L8Nj#\$@5Rf4YKE^R4.U"@@>,scR/N/Fqi<#>($C4:9C9c1%2;DPQTH\Je(3rK%ZK,T$hBWEeA._kE!rr0!la5#._aH.+bJrWRj>C7;6FHWli.Lm8,c^u#<H7'\-M),f6K-hA8'ag.*V?ua;7!@c56NL$$>,!aLKOqGqM@$'a_SZZdPS/u0\F>6O$P[I+Ks/<=e9M'D-B"aqf5P6huY4VMs':M?J#ra08)#Kl.8?mjZDn#WS9O?A_!2qGoG;2]Acb6\-LTqLDl1Q4kDV85^kXo7MMGEQg[QD?BHpIXQe@BmhH/XQL0:O8!?5HMH3HPk6G=oh*nVC2o/fLD0:NbZtFH#Q=-R_o@Nnf=S3Qbl_?n<kDu46?W-ePIH-6V/:+kY,-hhO@E#.K&r3`<M_Q:AjRa9:lR8:I.@dKW31VS0$Sf=!MDdq\+,,k8Kb?V/d\f35l(V1VEg+<[MT.*0BaOfud@s.)AP]!g&#49(\dc&b^M*8MP"Y.*[\[Abi,LS@9]1,fA3<soG)ODYm#Ih]c3/:q9#oJ,I=n0NC5h%iF@8m^\$`RN[r&phbkfT6adI&71LsF=5'B?/A(jB;9c/lH[Gsf74+Yq58tsM]3fQf!n5(1>QS@#eQERqL_C&J7'rG5"_XBHqRSMdb(qm7<eQq((4pm8K!9*,7H2~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1749
>>
stream
GauHK>Ar7S'RnZ;367<4`[j\Y'%LITZ<jtI?'?mY$OhJ4,L_Zhh'UN@];#=RLQZdO@QVXb,^+#"M#,DNkOO`9`>>R>UD2XQ#s<RXJ8pt#JI*N.ndaMMSA-)]I3&Y)Lb#GDEZh&ks.EUV)0e@86QS"E1a"Ob+=&10L(ZI0(Bc:>;T&Eh_ghkR%AWtJRHI6CcLW[Y3ChVOGeP9VIXYe![-!(\hcPYG2K&`9'l;o0L[#45R2:*%8R_8jo[=-EQ7m=p?06D6+&/`09*8,<"#rI^+'2HI)*NW(qFW,\Om:.1_INt:-W\h!?O0Ch2")dj4'_ksJ7"S`k7-dG>/Zg15/?uE0l;Jh^+hI;P.3+RE%gnhgi5&&dM9Lc+a(:bR`eHl3'8=J$9=2f\QL3a$[b.&GjUs@l"#%T,l3W36m8]@4m9+Ueu\*EBE\tdfNop8_n3"b8NS'KBo*h`>ERj)cH+8JSJq[W,NmLSF3;4cXag&qk"_J[5*;?,(i1(bKM0<jg`[*5oOPXHCp5RG2.=5t4i$f-r/gE[pbj@!Lj==Ls0FHl="r+m8XGQ70[8PRp$XR&9*[]N]6(7l#gN6YQK*IsV64%Ve?(TE9F7JC-l>X\@<>]:Cg?(;<"G12Kg(R?\1j#_(SrUr_6m0PW\l?8/V1H5AmJjea-i2tlO-],8K9/mCUhI;f7q)E3`Tp+@$]tel^#fgegFY%SKVmB6D;T5Nf6FmUDIL$PA13goiFMHeIe>;nk,8<.Sksb_h.2Ca,u5MX<KT4[67\[%\pFU+8auQ-?`\Ke4EU-fW'ms$hXJ\:=WX&nZW,fWbHmn9pfg"Gh5UCk9XMADt\?eZ1"o?Yg]?r$\Fp=@bH#8MlbBM(U/JT@0`2)UQ8P?O0K]HT:OImHB1NiF8JHTF%ksLa,_a?%6@WXMSChg7Y3gXU<Kh#R4bQ)iD(mLi7*"Y:pdeT(lEAS'e53L"lsGTcKA\YWQ,hNfBmQ\BUutPI;tiK.rE:k2X=h5s.978rjTH[kl78Y70[^GY,^kAY:KS:;OO<V!c'gD[Wn:CLpP7&bWpghi8_qN8@\6\,15\%?mI(b3P86]K#t5['\MEg>[r\_Y;:!'fM%RQ@"sYZ))#tDbi:8*\jYC0]#p4jcEMU-]tZrpID!A&\fP0%lc9QsRB;T\D72sTi!:6&C9D!BN.S*4@C]ne-\FUr=Q690VsmbOgD>c<%)m^R`#n<``+*>@"fR+qC"jk@>u0#&F'Mf&1$-<U=m&Z^J"(S!YVXn+Guh(LpQ&HY9&^5^iM_\ZgHe`pI5ec7F&Y7*&DjXbe.G!n*!eT.iWs6I$L:.:WPKr]csS8am@),\A./tV_KAuje]Bq\::SZ&1H^fLM`M5,#t";L/56SL*8IDS)!uHKVCHp7BQ*5!q;.V?E[t(2+fom=DBl/)#s%$0TO4jWMJF7N,<Nc&n\(ug$1]J=!HZGh%TIiF@f&Hf"nP)*KTuB]=H:m-`?!1K$F=oaHIdo@KCf6)eT!mrh5^[ud1%!L*$LfXC/&48:S=jjCi6a>*IZhKeTFIAEo$d_6T1NS*1;fs(53WaI#O7M5+!B4mDn4R3VJEB_*YGj(?RUsI)7Ul4o$X)AsU-)c(&7V@`R*\FTIcEKAjX0f^6/%r\6Q`E?F.`"5;;V*l9S=nbpj^jP"RCREI?=ioh]$RCXH_nDLrE-AGK'iJ8qFP14@39,fEKaRns0CmpQteK;?bheHAb47G\HRT^+$YIek94\s;oD:/p>^C#kQCB~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1636
>>
stream
Gat=+gN)%,&:N_Cm"7*hU_/P9$"hlr\#CBlP3/X;1EF6W'L^EI8=?:0:;=LN&>WuF_PWVMOPoP9&,%PtaaiB+\_hK<nFH]VcP\DHM\Rp;,V7fhYD_=&+?\_I<'Z,6,3D."@K3"GJeD$R_O/@P1l[<O,Y619f!,F>H;VU)W"#h7":Rg8dR["RR]0,=Hhr>ISE&%\W%a0Sot>k/\egRWH\ZK^?SmKMb_$h8$:uXEOb1OjB"u+\H3nMibV\QY<>G#"X\F,j_A25GS6aFdRO2rVZ\6rmd1k,CD9DQ4/8end_QGc%0-qtQh0Wo:X_E;?^nGJC<G2"mP3$6V@V"XHmV-SfjPJ:?C*Ee?0G`R$n3GpOBH]_\_E-V4FI0mM&b(TQ.eRJ8CG-t?MaBV!;8#XA%!-[01u]oQ;_cG6$3o7:FY\ROrBuMQp=DaKn1!*UUk9gOXHEd0lU?@`K9KVeJ2'B^"uh+nPl5]t5<ZL2ET@Rcp@K2UN)@GLirUHY)m,BK+lf?-&Ct^imPqKo3r:Yjqt;Td=H'n@@fMF%@9kHZ;4)B1,ioZ<QY_\D@:64#F<JIhrh3Ys[n/OAEl^2S2blib6S]pqM24]H`a4SP8]u,K57C&0d]P\<C;MSfLiSP&e4&mm%$>Ug]=<9fX2fU%lGMjNA8O"!cA)+u/<^[&K]SbkrS3k<cG8S9Ce]39GbN3#7HMU]r3h0W=^I\#%kVTk%IDp$o8tqRW]`lWnM`/mfGT+P1$.*M'LFICD;)P%k3K\(Vf6sAc_W)K#KSC$]W8&,G8r6[.X<G&SZ2d.DrQE_VVi/-%-\?o0q@Mji-=2jc4gmEr!km80oM6k^_f2r9jX)\2ZFm(YC(rb!jrlt;$<?dG5tt#q+oWG!&+8l,Y_;$AbHb"$u9$&iOW)PiRVjob\IqRWO&nDWN/ab:,q[\dis'>RQ:4C)MBdF^ao@]/Y2W\J7]#6$8^j`/DDsUl.okm]T!T`JtS.?_#r5F>uXg)QjG<M2D[63jr!%PW^rSJ#r#\$`28lOf6&k0a93,o!4.5G4tX]M;KWk!;-3K*?ol,u8Ckmf&2B70o`U@Oj,8&.^.Oh0noA;V;l3(YD#c8#[>I'?UNiSC$fQ%!IuBRoCG)jNEU4KqlPqe55koF5&-[Om-#L=Ymd4ilT<Xu:4PX<nCdKu!>)>-0YQ-SS]tjS71XIX^S[+>H]nbo9AmZXaOfV:NUaR4&9^;/Zqo5&i`*I<L$5YTBh7([G0hH-H0X/`>$_<js0=?"3D[jnrI+`%QZG[rO1lrC\PaGhr3U5qWQq0./p"?ahjqc\3\hI6%dh^@Uhq4eI[4jL9fYrSSA7U?f]+Q%GP;`V!C*+.cgRF:-V;Y/l"m"6*(iHJ9Fc*ai']($?dU%W29.:>X+&ai>rDMfk$9.Ip$RgX?VHiOt612](=#.e)(/)cCeHj3D&)ME1`blOJBf3=\b&!2%&95P++m8QA_M7t:)6mq@rttN-("fUh%-HQtXK,FlUPn4BQg!=8X?1Hh5;q4?@OOk-g,>onRq.Sq3AeA-Ok:h@Ifb5Y6!N05Z>n\lRM8jSm2OK0LYW)6_oLK^U$l@L$4hN#it[,09sI%a>2j`6^eaBt,k*idl>$9t-hFG(;?YV/-[9i=5k$EZ+_Oa~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1785
>>
stream
Gb"/'?#SIU'ReT:\F>\\`%4JW&sZl9?+Y7\g9k_P%N,,\b-\9SQ5k`VeeS#L]@7'3VciCD7F?n05pXk-iSil]LR2F7n\aY,aTg[i.6N"#J8pr/J-dE-qAasTH5@X]+cf"k`0\u73KVn&5>WuG*.j9*0om2C)A*hn&<[VS@gO.4M]rp&WF>>4@mHcM7T'5K/-oh^g&!r>3QIfs$,SC1q0pL_>'_n0>BWQDq*d"(_:hI@<*p3ZJgc06#0+EmI!6(8'h_gYX.m\nXXSCu1,.`NJk,!f7obgT7aL%M^=6;jh<<Xa_"7_&+5!h&pd/p4+ME4$kIL`7!&'Td((KU\[7kWF@Y"%T`j>gTGtTugP5%5#E%Sd@E6lS?87.#oKT4"SdbMFBbi9;/<6.?EQYeBA=;:=79@3?'4P,<H9]A1?5uWa@iW't,c^I%X5hD)?_<sMY(k0[)0<#&JDNr)74c9^j*6Inc2iYYt'6!!_3FtG"dQ`+oF/MdiON9=C#"uraiKA1&<sSm["'I:F5?J`=RP<X3%e.@S5:6;LI%(^!`k:-FJ"kmWeKq;>R84U14>Eh<%cp#*WQU!R"14GZZ*4<?U/8m<][XJEQ-iP/GF.O(\LNJW%`NUf17dm=\XcO+`s39`rr!?/2Um_Ae-,bM<aqp[W%_'(f]u51'%3eR<,/`iEs%[VdsO^s1C@rOXBSf70T'j?HZ@Y,276W7NsK[TA`JUqC95ObAY).4I'IU1rY6%qBJaOf?(]($HUT2h@3B<`Xt:tTO(p=a^a%EREXZ1\6M1ji0ThTPYA"M:qm!57A3U=QF)Bh'B@GbpWYlo<HEHCJP-H5fNj]O]d#XWK:5Z\>n-P&uVec&Fe<NW8%+B#NrcGbfG'L_s,+nH2&7AG\F`rN(W,n6L`hVYH9LLVgUj5"iSXb-HMdsuu51*hVb@7e8B/"Y/o`?.D>q?bjSns<9_Z?k67L&q*X2V]BSc!S#X9%1#WH`E@+;u-I\u]HtOH1,HSnhW'eV!MB)'eUuBI=UeT0fBt93k;M4Ss@1H)f+7bmh\J3m-G!al$4Mo=U]pTMDoKoTo'FIR`Q_`^fN,H[P6n.a<JJeuD\bMum8DT^IZP4-e-W>%1[L06cYakA[8+Ssgn`,WI/<areE&U=^5L1`0DS^%so'S@(o&mGo+o(2GMi_-)\3Tfj7[a84*4&k!uV^<[#?;X/"`)0D0.b76SUd%TlA-o]BWM`HJ+j'?KbL2@naR:c&+VbKkT6lFP9pM4&O0dM''5/N*Q=Lc#WYJ,4GI_8R\\6j%D(r1`KI98U\Zq'+nU4/W/=E%'ekTOT#TT<_(4YX-/hO[*'gYSc5lE!$5`a%LD'E?8Nqoa5;klXMjR%jaE)!X$8CiRqAd\=q!CKTM4%O76&/8Yk<!ZNW`8>UL[N)Ma6flh"'@>,XkVU+W3EljZZCO3=6^VP6ZqKRDj>$3q`UP-iYiA1/qe[@*^##pn&h"*GX$qq?V]fHChcaO&n%`^!=%u39]aXQ<4q[Y`%IH?kdDS$%r%kF;5DU7Nn8$HI]f10KMEN:IP=u:k?%9r7dVcY3LW[s]F(3R^t.cs6S8&g3kLjf>V@4\W1c>[GNN*/U'EE[=9Vq/GCA)LOl_70__0Wp-'@'h$A3u^l[,=X6'Sna01Rm'&j1hh-;C;=6Qf!`:]fmq+";=AlA<gBcY7\.@[Uc=CXSq@+D>jP9/BouVYk$2`YirG#93XG!q%gL3dW3TAEBB(/fm"F<mIq_riZ++RTFT'`;ZPQ69_:hh/.(ckd*N;hKpH"^npFj.ac8Y~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 402
>>
stream
Gar?-b>,r/&4Q?hMRtF)R+dRmb9Em`;ur7N=)=V^9`oZ+')6pG#H.gHE[#ekbfL7u$P+?)I0n[S=KFKl5]R!o,`un^QUDo-#q_*8T\-R&$':)BKdM^--r&D%a'kAZCF#_;A?E0L+jV!Cd!-Pn&I02uN0PO/Q+$NrIS<q$:g0%1gAT9gr+rS"85FCpF5-d3,5Ce/^XQ?uj2rbq-"[[K]/B$Bbd[IJ>o.bR)S\LZGS.f#\\;CYD;KI8J6W'8Mu(-Z<Hj/X(!4+lFiKbGO:$<#r/GX23PqialJm\K9!c0&U!>XPd*j(u(6b\R2-In/QpnMK/&`WX3$7:f=G6sa^',dZh.2o?dcupk)62JQ_S[XS\CmhN<gOgfhgB%W.hl^moI,<iYNS;i)^5%^SQpY.~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2096
>>
stream
Gb!#\D/\/e&H8h>EF/nq=j<%m6r=s3bir@`=HK>ddr'd=6]i6jM(H3#o!T)+m5sTeOrYoFL*2t&fHS:]m`<"emOSPVhlQ_c(B=[bf`/m?E>Vs]F,0pn,8cB@mhXl6H]AKPQIPUg&Du%laC\HSXrtUE4p+A>,!?/NabZ1q"HIMoa#qEW>jPT%LaT,K*?^NU6Ac$=2A)ISC59;cq!7LYI8ZQt'2_thAl]uV_\K90PX2\t6CI/M0Nu9;n]BBMpbeVA4Ii<`$ti#+_Kc/lc9:A5pA0%Ujb9/?0Bu$Gl#NJ\@M_M#0\Y!e3r"`QM2UMuiZG->(^']XMtE`q4Ul"#&qK3(I)[sZJ-<%bor'.E9g/-fP6Z!YGVIQhKHjl5EL0XkAhLO%7jrLjB9(/]Ps^odG^%/EeqB?W_[@f2+)tr9a*#*(#2<b?)Il#g&Yaj$F_ieFbM&H8!)]L;ZtO5DN!!R#/SqlCfm.8UXYQdG]5mlL-QP9[n-UB*nE@B@;8OX>2L5W>>.V"]RFC[6@F[*9s*!m#M=2,W&77p1o.b,an5$C_;M>;8WjlBP9oqU^MR7mu\&F!hWJLkNJl$kr_]01[CFSk@<dC_Tg\I;8)-eXfd7bjjXDJgC`%"QLdk&;-hk+7Gq5qfB`k1]u0,7hf($%ff>bXIpkAV@S0T$VW7lLdP8kmEb`LC_aX,bG#*p8/-)"3a(gDQ_;Ge;1n@s(k7b8g(,\@>)iWCjVRiqaC>Pki6GH3O\sr)S)CjqBKciK`RAQK*6*H'u4qE[91em?D$=6)[/amYku5KU/$I0tJ6t]S(%^,bZ2bd"&`W]uX;`Ae:s_k`kY6m+eHi(i=E[XHMrlBPVB8hsRATgmD)"pn_3YVPNd>cPadQ[#&F:?H%;t]Rqn:6(rT2HEN#holH(e]RWFmRIlBI=p-N#Dl2(iP2fE)%b-R?]j>]n7,lLdAYB>p']2C%'JC%>^Ju=FM8#1L]fZ[LdjW\gY-[_A)2)'elY:mVWc6[sEa+j@_D+p].M:"mLl^'YXb,O)1,oebeYM]0@;kG%9=kDUnoj%b6PA1=p-rA?/K:R4[.ZVfLG&+URHH_J,ZXr4+m4&k]nk50qD:a10(u2/5!MhqC^R#QrD1_--/97.SM_7[P?U$&EhM(!d'l)u<nE6>jI7K:pmYd/-BKjBVO):Ks4!XsE(g4G7mg8j;)6=%maV7#iQHqXdUf4R#%q;'i("'91k@0"O-dE"DPBs%Z8>U.lG7p65?;tJ:V8;)fG,V>_icn)dDO:t][NfMZgCa(]-W'KnGg301?Rl189CmT@ifV1XeuA9fNOPuN!3f_=$\Vr<RAt:(6#]5hREph2IcI0WTm#W!MKeE0nc8f/TP[CSh<BT0"1(RGZ5tBRY#gU7l-+G.#>,B7#N2"f2;A3BfP&ba,uP:E(R91%:"I,)%u:'PNt3b!orQe[;As^KXe'cM?_Qa>GN:+#htQNE#.[0Y&(mo]g[+@WrSs)Wr,ASVU*W%ej*46QqjlhKd'].2\IYuAR5MkYs+*#n]lCRq/*unpSVlQJ^'u^^/q!0VkO6eOrG%3@1[?)!,`0rIkC[*D6WMca+Wc##sd\fou`$o'tD<L7E,BbUC"^E$iu_5g4I^Qf,GAg2qX/r^Q0_>B*Uq^+"+t4e,DX_31+*Iq(OIZAc@..s6f<8FBY+LUr#Ubrpe,#jL;rI*sa#C8.6_-*.7R.pi/"`EcO!]*_nX7:/2&7rPJqBiDMSKlKOVB[u.Gq@qBNsU#2socIE>=>fE/"rmr(.d8E1V6E*CV?-BIF&ECPR_R9'0qp<G#mEgQk*I+_&1*__iiNs_j[chrVU9)jJ.R^8<Y!o9Y$MD.c]M7\qeMf)I$t0Y'p)pVh;"tQ4I4pt4^;mKX!@m-<><bZRV$2j$4WIGI]q4&t,@@[DCK:b<HoP-64=.0b%Ic=t_9RUhK4L9:QWL&&._2M,b^jqOCJNOs2Z6_+0<W7XhHC;q"!@/TW2/Xr9',5%OC<PG@N6Y4URtmMm9%WU>2a'NO,!-.Q+F5!=YEM/qfe<o>cHs'"foJQ!VQ6KjD\'^2X,,T?!#eP4%RA&-QufiCq.`RaRC[HWI2r'Yl4[dhuW'~>endstream
endobj
29 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 900
>>
stream
Gb!#X>u03/'Re<2\DmnBlMCBFBG-#ML/o$kdqP#-WfXe'Q8W$4<G9k\qjaI%>/n^VVDVB@h"mUhI!i('@/#cJ2kC&?K[;k05U_:a+<5A,qf!;T?NW\B<EoN5)Da_])-22oGo+[j80eX%">j.M.h5etUp\T8?(Oq^_:$3m0Hbn1`(G)9RL#nSDtsf58mQ.kKTtV5rgKH3kntbull=:HlbM9_ZJLg9AK5,&P^m]Wqh*(Q([s&hZ[$qjALWRqV2apO\=lkfn"S:So-?!U"Q%jt9s_g_cAMc1+!<ZsCMi!N:6BMf[dj5Q#=?38H'(g:fW43gR7Eo.E3>b/%?RB$U=/7?k0>/9";<#N@",220*M`R_MAO82_YWL;RP.@Q3LUS19h;a'9NQAX>V<7=VD$(`YgO6#Z<rG0o60ub(2J/C;$QWcpZlVU@K-(.BPQ.`[r$FCW-<lae\@DF)CLHDVAeEg]s+Nb,7-_pRHf6*/p"dHXu0l$o4&D$.S@CFB<U$o>M-$H!WmQ$HMtO7<BL1[bFJ:%lm#a*(NRJT7#m8$TiR9hm:Grc=e[aj3n(#:*-NRB4=q=jGu":$9MV.BF]a03F"e3])S@^gF_@%/W4i!bEf.BWV"gb(=<1oD9h*dNtA52[b%^T-R&[\(p!\PV8QXUS?XZ4&hK.*S9^G6G:-CK49HZS?0Ye0976rP@(<?3O'V&E`IoaHRAeko1qE:f6q+iEJC5Rc[AHrE10\;4!e*DIZr(7-X5er,0O89HfR)3AHUk^<KgMLH`SpiB'mCiWV(32PYp'cmXqdV/"E1_.h<aU5ar"$8dM`[)Z-6[$gEP6:BQ3Yo*++\#qA6'iiTR:cdMnd;7j%T2?LNX(\dSK=Z8]95N8L7UIL15i#.QZQg1i5GIK;kg\@2~>endstream
endobj
30 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2295
>>
stream
Gau`TH#q`M(&snuimlMKaA*-E+:3YVH<SATF?SsGG[i+h:=[J7,V2mQTotYG]:"'@4oi58>>j1G8L7S##lNP1LNeg\`@j$^E=VL9)Q#/)0Fl^D^c^Vmo`^jk49'I0(Hh27+KhMXaFT#Ir]i>b$Le32+V,G])A!d2i(6D6$mflJ(Bc:>9#9nG_ghlA#1<StR4k@\YhpaYF!U,b?\n4Cr@NLsZPO8%EeeJb56Sk=X;.a*FZi'Z+8BfG_rR_^DpI>=Ej"^&Xt2ARh,Gl<CLG/-!%6rb-Zu!2NCr+j[8pG@bZ3'W_t=)6%lCC#^?9Lq,CB1T02rC>!B,(!hHJRTBV#tOa:Z_DW7)5In<VIP9&dE=^pD[nE5tDd8CnNHJDB*IZ/'j]F*Kqup#KeHbBB.@=<RB``p_Ks:-XLS-;fe>1_o#]d"`[sATgeo5W99"K3^]_(d;NK6eVXiSkA@rJVspA4d59q?At$-Z#f(FS'kYjo[=W1G)Yqu,$XXU*>tW*9?(7"+]Pi@V)^;\F=r5L21OJCn:e[h$%Ic7/V8[i\+fBq'Uhr@=6["NJn1/0^HiJi&YoS8Xd7@/<3';j+Tg%qL;&USs6^ggrYpIO$<);*233qW<BuO;**O[s]uh6q-to=oQ%;h%*bFRUEn--$,Ofo)!d@M(jAD,d(%PS0Wi<*p0<6j/af85gl^4<geQ`grVlGF*PN["S4?8^AX5X?r4Y'rjcK:?=h]qHRAX9:[<n0npk81A<a"0?KA+_eEF/4%RF'WCG9!iUIZ0;Y*>raY9?M5q4VJ]IW=;1HmD'6u9\4IK>KFld&U4-1*WLCU5W[cFbWMA9Y\Cq#kf=28Zd/_S?p8rHZ`bhAY-JTMQmW_$*I<7]?3M*$mj"T"FE^?WoKCOoef#5klmNjfpcp78FLrR\7$dPTQMg>m0)2&n4g2dBpNZQXMN"35%o8nS5FPsR_5=dSP+*AVWKZ9nQA%j05+_Md#OcMXa0K2)<QY"^_7u/Z12<m"k0j;@?\?3mnpAS+%[sQhO%N"fp[uX%C*Zf5'FdYJ6hb2*]($fR=(:WOU-\<k)D@p:",-DYP/K)L/)sEZBj*Te0-9K:':-*j?\qR^Ba=b#LI<kD]jQc*flW4mPHF<p0;W?*NgBM_Ap?Koul.N[M0/(W-rG_Ik?'n;XihQ/cbh;t*[:-ioIOMnsH=OfJQYrV.-s<LY8_4gSO5Z^X(M2Fh4kk=YZF/,Oo;X@Gq+9fT#U06%`JDjfD?FfFU*s)3%HaEW>bf39C'8rh+q`Z,0s.HR:ONfU%h2dLE/17Q)oFpC2JuS"MHCX%*'DrpEsrG_8NPU]O#r.P9G[aK[Kg$9AlTR-2c62$iHNiQ:dhiTJm\NTELFi8B9PXCZi*\Hbo/1m`%O4=,';3?ZmT_;;5ai\/W(DgbVPV-BPkW&F?:I'3nO*[<*?_,41$Y-id"FBZi&;G[=1@t1>@7USkk"YfN_oIKl[p)F`qa<2=V]UjZ4kfV0"dNf-Q!k\V7RG/!s$dN,U4L*;pYB^E?dDe0Iu3]XcKI:QIBuDA1qF$$7*C.Ob-)V1Z6#g/lQHHNnk9[SL_*0q29[FdC#S%IG%/:CXS"pUG!'=7>13PDUu5rm;G@eZ(#sYrc(RpgJ4"s*(\jq)D&X$GdSqIN>F=LA:*B':E,\`+85`ILjt2?TAOUO1ABl$;LJ7Rq((!%Ct(+45a53["n3BmGMgql0(@SO8lf=./FRA"XEln4RKN'kOQ8=`k1V_%'lo>2HG_T=<?'!1DU*4GsopP%'-`IXUWg9pLs*U;i4"3PoReTJoqW*gLZZ6:oPO9=>=1fg_Uj:[`HoA3@n5,9j5b"nqD?4rHc<k[/MT7:J38B9%J;sQ]BOKR>\rAPR[*%&>DX9UamQ@;(q1nrANoTU@A2Hm(2OHXRKcj2B:aKj,HT10c#O'GXG00;lRZLp!@:L]^_:q>0u/iDf4&E@iX((RF5-d5b+ZDrAFs@jlj@1F^>b;*/$`TXr`O/9kb^$%=O%&%[?3Pr<OVmk!;.d&-FueA))u:efkIg);?`B,K8=1pmAJtln:5D`c)0$&"mmU;+:%@()#%l;@g.8o?gd*E/5#CHc5n,%dZ9_0D6H39T226q:]qc.t+sWRq54L9m-`)bGp4B>opi(\LD44mi"AhBqq]Bp@UU&U\2`iMW0sq[!qspMV_fg\uP&$,&ir=mmm&!lfd:V/BZKJ*8RSV,4MIeUA5B`=[k7o]!fTd[k9D!ajUD3MI,qNIkNNB?BZ!8S0oX`_HU-CR)!oZRmdGfA"DpbI<bN2_jVr4rrM0ur:'~>endstream
endobj
31 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1632
>>
stream
Gatm;968iG&AIa;Ccsf"D?Kt_8:]s#e$O!>l"#r6<cZX)O:9<SpbGG&;31^'(Z85P8*lq8`e7CFDng]%@R0J&h#C(UnG<h60,ZGRbA1L;#["fl5F:5t7sN+O#_;]-q@NrFq=Y?AXd;<bOtbl-"%=M5;LhT&-l"2hW$`mT:`?i-o,&#V;Q0d1`I^p?5Nqtij;^l%&qj]c:qu+i/A6jm/N]c]]FbQrYh#cpeJEnE5R+Gd@J9ENVq:DfhjS;$PNN7"X@on1WF<cVKGUiT0<-M)C3'RAk)2h%+uNMO$HlXUSlo8BHm(HUOPV/[)cHcIJ<-_UY8c-gH5gK8U(P].(t/sCj=O5#(%H]%0c*=^meI\:jblrgOEs#3c*EZ^*g_OXTggf?g0K:a)=k$Xkk8i*?A8ibd#V]_MJuo:5`b<=Aku3q'5Wb<[n]&?hm236g&oaZb/U1YMZc9:,[9BeNKNBCn&BE+ARA0s(GBXm\DdsSC<L+O1.DRON;Jtc%($A+93F6s$Xlbs+$N3iTu2osrt4-h"H0L%&+KV/dIk/rGI<!o@:MNLHjt(!,fUUd.8;^#(h#i0_RLO(`(4#EbVcD9?#&%9\)pt6@<N^D&6J@$fAQ!T-eu.5(7X.3<nQZ^E&3'Sk$(Pt.jTMI/+d"H#!SM-1E#!9OsDrY'6:nbESe2DGTW"6".)KVFGZNYRjN;2OOL(T)NqlHK^0pR0e552qIe8UY*X*2&`.%Mon8RSi9Fm1-PWtGK;bD[+eaM1Ys`_]2I8&S![`I`5Ei@r-SiL2m_F5_1n/dq]`b$`&%8%);PUG[!(I2t[\M@1"!+n[qL^`Eo$NKg+`^_YO#9m),0m]8mYJBM[.V.8J1c_50&hT0fYV3YeCESh["lH',7#uE(';)m05J(Q$HKJJQWT6OL[-]kiC(^6^Dr$g;*[HArZP6IQqYO`b\oP6=')"1NHc(6@>ond5d"m^*cmI^lU<1m`0=@5JifDkFp5DHV6b!P3Qo#1So*D3QYl"Icf"c6Mre\SR_*5%_;+EO<Nf0VI_rNiK_hbV&\oa>.dKrha-O`O&NtFMqQ%W`J[dZFnn^0fSPD3+LVuB;qno\q3:/8,YqQ6_ZqfOOf,CtP@>!N0q16"TI0OJX?&bh-M'+]<X4`@,ra^.D.[-5oNKUW3Br<@*Jk"b0=%k%Ui(t<(iWNTMAVsDf-^>u]@hBeAKkDG5+sd_hCGjKg8,js"l2l#]>@Whgs#L/t$9&H!5_.$<4Lng_9W)J5X-m739id_];^\bY;msk"rI)FA]pukK3)1.?&]VoKjtToZa_I)88!XIO.>gP-Oi9s[,XX:SegGG,*UdYj'U[K].]<dE/!<@,6*2((kA^6`JMj%9@.7jA`n$!]k[^ic941d6671&n5Zej[m%3"Aak@h<ML:r(?(O9Knt.lD4GrI)\LqdE]%k8sZ#12tC7Ti/aB<$HN@T*k3>#@UAUJ\/*"?_?,?lS8%g#[uC7!R^)[>bG2DIi+l-::"=':u!RH.L4E3U=i76MI_4H6ZaX5`A`1d'AV?`G0K8_):=1ejqG32_2M48o,"J)_*K5;HDrdaYlaCZ@\Fr#>ffiF;>n<!C:_r.dSj4Y>`Dl>jqAl>J0Pf-RD8^Gm:iP/-r2G,@p%~>endstream
endobj
32 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1540
>>
stream
Gat=*hcJPj'Rod`EDKpK>u;kM[c@q0e#m7nVe9d87UP_\3_EkCmEBaH/LUSqY%5Q_/`<0#_-#oMpU`Y35;Ai+c2@#e#C&CcaXI>3@CcFcLk+`:GWQB6#mcU&'G6F'd4jNEP0eVC[1!Cu@M1%N@A'PE8dZ,e&Z$*D*:9P4&h4!AB)r)Y@k*`DU0&,1p]p\X`13T<6)$>6^:3S+.7Z$7EQ8Q'[Qb%P4'3:uNuA?l=X55Zk&Bh1T,2/0Y$@DV?'V7?^RNBAWs31+96bT&oW#(#\hS8NA$1eCf=<?!6^V2soU5<YLG(t_0G\pMs1PC_0u-<8P`$=NK3i/D#"J?;5%7E6829S#7T9spKu@F'KMe&RR'HYDB&7)M^_@G'iHbQN+J`h[h65Ks.-_4rV7n30Tn:Gc't)e[#".?'W%\OSL[*JFUJs4r(7\k=r2-&O^Of;"M[^Mc9'uSH%UaDJPm2=QA+?DYg=["a%"G.j.e'S%^=s-bS;*.#(8aKB9sRj1KI!)X)"8=J=^/>V"S@"'8QWKkXS(#'VZ(Ucfm@L,M4f^pk"dnCJ^e;a/5L_Dgl<gP,-7TLbOTdY2/iq:s"!r#/M*nWLF5-P<(<eGBJLo3U-+?(G/_=BR.k=/Gb9&`cl:`T@T0's<YjWkA#GdK_<__N8)-QR.jEa+!Re9P>nf;B4X/D%+Y2-5juKMb0t6;h]c_^-1qTUUc0%4,E(A[h/>-HDk6BjTQRG@E/2^[p7)h<KQ"`-V!OICj.jjdce%t-cY-[-LL_5UPM^-Ug!Sm*`6#sY.[Keg)p#P55s!5P&mL%Z6+bt>d%9M]Xa_aeYq=Z*uWm[@'$Zn`iq$m&Rp3;2$7Yp&$%O6OGHU+2Dc43@e&19jOV>-Y4nY3+*.#YNo-b&tQh2[SQ,]OgVh%5"T^pUTiF)pSR[YWgabmo6,OgZq]E?cY?LJmN'SiG:GAmn5@aSB4f4S]L.^i;h)WTpQSRre=GgUC[ui9'E%(/Tlk/%sh)n#@H5ha3N*Vl"nYqFPpN;3oaFA*j-W]#+.l@%J7G0jq>'""8P?DMgN'p)+=&)W"opf]@WE2'9d!Ll;6fcqS>kLXZ.oOMorf0ZNRQSQ-;`!_`MWE.o4Ljc@IC<<uN!IHpWh,37pN0E4J;p%&*"iK=esW9Eu)6A]r0#4p:0?V79S^@TK.mCV7)D>!=\Yu_,nj/K&P?5nf^n4bj=Rd28aCD7"uNUjE-o*1]FQSEY(k\4W@!i6'I%/a6(o"a)'9d4Kg_TM)QbmID"-m&nI%!EeqbOrm?.VlbYq!N+H;::Epr=dZt:"hj2Xrm3T[E%oFM,ufLiDP1&L,)jm&OaaCdBq<TN^_ct9T=HSaDT@2BIC:IGu\F<V+5ZC$9W2e^N8&qo?opp]<0\rBsM3))PX>k=>F]H/+&@91HTu?6`XaigaUi4i15'H`uoBK(@\$9dp=U[iHUjNd9+19dT]Z'm.0EF4M<IOYNuURbVW,iCu)c!*C#lMW_bQ>*"3hUr1Un(.i];nf5C\Hi2/a7q/DYTX+bUZGWl<Cr!U*Z"BY~>endstream
endobj
33 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1376
>>
stream
Gb!Skh/D%+&:`;=E@=Z!bn?><,`3MELC@n+U=i%5YC4NN,%#0*ZKOi$1?>XrOch":R?OKrJn;A5M%oE&GJ#T#!)A%&n+d=Kk:+&l00i'qSqDi$"Ogk*2[qAA-C%9!(g*_^QqCb#k^H@SK*l),<%:fIRO:cV&O]eVX]#[lk`S=Z:a;M!"Thf#GbQEj>/X%6Hh8I[;A/;O`CctNkr\^J'/*psPE*Rt0*7m`U/S'^%3rk]SA[*e_;t>T5(A)iboE4#@8$uWfc'I9Ki9E63;d`7fJH$,E<\pXKr%qUYd/1TH3)JXa(p>MKpn,Va_grcCd89!7q9eh$i=Qp0<0u.7mJH/WEUVo4%U*g=W$^_N:Ghd#9iJn%O2];=9UHlm;"pf"u9VW7s[!>Fgii0,#NAC'5uL`5sIjm6B`VTBI]j$Bad.J.,$Pl!8E@#"Pg_XJY92UprIe)hK-d@5"JQ!TGOmYmaW.gSd11h4-f]P[X\223KNo@`H:8b,F]"bU_Y^2>L5k*=F6M`,J,YWDY)]I#[_lTPf`gtO^t^fa`27XJ'#"MXC.@I%"9<HK-hhPJn:L8k7r'J.5%X@>XN5^]>iqW;MmZ>,KG;@.=Oo:7o#/I/.lg,*Ad,Y8'*JqmQYVK_el?mQ$oCZdVB,2-gPY?hB?Zml''HS<$]R\+j$Qi5n5a2"mr]">hfP`FE.eV)lsE`IY@<eZrlca@qnY\/2=Uq3E'\Qc_oX8lUgC?63`1=,-BERg<:>B%s'[BZ`NM"]dmE#eeZg6+`3iKfl,C5XPOXaXC5\e_@;goi68gg<7Sanq8jZFI@S9Ur,-LDOoDac(5MI1)eC+rWeq#ERUbH2D$d*R>A$An53K93l'n"[BV2QDTf@\*/9ABSaQf7b8j5]!_lE\0UWR-UKCb$QAW;DL\3cj0c+5MUBqVrl'7u-4hm\EPCAN0o78eP<E5h,<$oII"P(b=)["XFNYK5ptS2J=,Q!&aYARc6k6P\PJLJ$5-a<?>(]f)aZq18eb=dr,k()KX9[HqI;O&)M`9`!SIfe5@8*17NWe\SPn!'e;Rfj+EEZ1!^h23L,M\LuIq$L:1qajAJ8nY,UJ),c)USo@^SJ`[a=Z<4`YP,MCm-*$?%3Ku@n`\@tZ@,.T5C)FsP%g+*t;*PDeJW0jFV$;$fc\[r/AVWg[HqV<s;GN:?54`YXHfn_\*#A$T&$"PF<jR.a;!4>q16,8tS@&dcY4Ld^Jus,"1h[iiYKh,u-de@UQ8q=km+GRiH]2nD]/)?=:,[jl!OXp<7(>]9(C8MTGD@7fK[8L"#6JQ(6+Y1=GNAV@a1oHMWFg76l^c!8*F'bVj0bitnjR0:N+K1TZ3(t8=/TIXm![<)D.]8b969"2+SZ0e[FZf~>endstream
endobj
34 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1796
>>
stream
Gb!;dD,]1K&H;*)Z(J2+"0*Z^ao)uIg<1K='N4(2L)EJ58n)76WNCh(^]%9Bj#Cb:5c;L."A#8#?_5g5g"fK/nNj5U;$Lu%K_M)e:^4fL:m\[G4GEd]qIU?%ZbZ6a38A[D"se<7%hrM(#L=cHa?g6h%L<SME&f57LE%/R"T](*O>(un;':F.#M]L"WTS@&#C2+"N1jsPGeP8;auB<>j'G:S`]J:Kr^CD.K/TB+Xc?%jl3/4;1H7?U^E11_C+RPAU/VJ,FWmN:L)\,6kREPuAXScd`c*+<10(YSk5$Y<mpZ":f]q[E*DR/=Yce"*Q9ZnW;lS<G<dQ"08M2]u*CX<hW(TiKCHN&B:)q:an1]#Tgi>)(gi7boLWj3KWF*)LWPnpJgFhrr1#D8;#n;:5?1F5-,-,"e(R-/+['LJ+^<hL5[mTIEXTQb##T[0a:+_?qdaa+rX2o1\7$bOW,/lLCPDsMhUOt\7-<?C2k#%cLqbJPaWpKBbDt9ZEB%//d;s*!M@A(0OX#&e,F!7n#oB(*a^L-49$2[T7bD8YU9\_N(a0O2K.n$1s#h.fE=GV&[:K!VL9*h0ED*iX)iu;hgZYQXc6;[pGi'_G7lWS*X$XUH*7D&D@M;?+R;TN$J=NY^#'Wmq]T!qpoXE@QKF)XL!@&oKY5p"!ml.CUaGDgLQY5DD]4bQLD(shi:X,aJ2:[0GkT8a3j9:1PK6s;p[iaf^P&Iq?P08=X]lWIo)^*A\MR!1$LBh1i]8jY:+UeGRJDoV@NQmYf8S4]3%ar_qJ6AmcaJ:%:=!FON+*cRosO]A@&)JN?V%-;sW(.tGB]GnmBk7[M'44$%djj:/H4UZ+F!"hOTBrdHp(eRp^A8tQqR*?B@WVg)>?(0P%n;J/=8TuL>ZX444b1<m1T.1F#q%un-BXo\L&C9Nn#(#?="JS8.<-^#&(eGV&M.kV5qZQA:WTV`9!I-p.^BFS#RVOXH#fN'=(d<I&^;#_^N[@RFBTVIXRSLk5`=G.[g$ki?1nMU7>GX>Q0[0'Nb)P`A^K,5Y!=sT:UjCVlB6\dnp%WXunb2mTJ(>Rop+?QYJ+ruF1\X0MZ%g:%HdFht$GZ]XV9+&4q$L9(:&tZgX0]>poRiQg]VGomJ&&9>Ds[[kF<2a=`/e4`.ohC"c)F>!P)V8S^#-J),(>WmeC5U@J7mVlO;jnUm1aFJ(+L=t]\%b2lfRSMAuR7"FdlAg<3[F"H.pJF['%/uZ40CG!kU4(`Lb@UY/>]->?t`+A<(&tZ7>Jkbh8mj(ZT9pll(t&1XWA&iZmTo"*$-/<-p=jI^4`^Al;)t)paLCkjMLkY[>cCo"BA&DG86!Bq<1(-\BefX!]/kdsaJl;_/L(G(Xu(\LJYSR/VKBA%o=S6[,qbQ7AEgo5;aX=_5Gjrn6sD^VS]^)%[4kNKC^1I?$6ILs-Y\<EPRQCHn+6:)s'n+H?#M>VrQ'2DGBD5)%i?lN?!I,BJR`cNfiZ,"jTo.S#9AnihOBiEhNGfK2DdAn41Wj#Fb^O-.RJ4tk01^$$L2Y55V/rQFNCa8YJfq.\a)Dk#Kf:g4ukaRt1c.O3N^IZC*OpNYN8<j\VW3'5+S/d`cA>Mus82M18DOq9WKK&u\3j3;IH`=mMjEfHW%ZLpUh0,q0=A(\^`54$PX7eFrJAZn_"ndqBJ_Q>g><a%[^KSEG3%'WU9a6Qo@?HPmAl)[rBeK2bHBrt.WXa2h9PiYU#V7\guH&JJLoeBg3S0?Y%nF%EdSTEp9X0R8f:A+9'8"PFXcPH'/Tr$"M77?9T$<Ksh!:,+GFl/k~>endstream
endobj
35 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1941
>>
stream
GauHL=``=U&:WfGf^Y/qfE_J/,j8"WELNcH9VtN&24<^m"em--2@l4/iZ+0]p+F-J<$[qn3jE4Z"G(L+4g;OP",\2_r['Y@UFA<M_LQWn/qF<Q@/:jlRsN[-i?=7M!5t5RO#`<S[m*]k]E3\HKJAN*8/`'X#nW73.:WZ(f0p:0T[L'Wk:n)V80]R68/?k6gfs62OE2*f8.^OGkNc?$daF%FfWml5bnn38#&BLh;11_Y$_dWu3DBGIoCXna>-;t<>a/e(J"'%X(C"KA7r\IooXq?Ug?8]&eo,\3Y&dm9A!gT>oU_=*^_a^1W(XLUs'40k(uV5Kjg'GcTiPm$JGB;?QY=kY.HE)0Le?\(1dYj1BlVHhb=Gun,)"b3!GQHKZ-;5?TiB+L(mjV@6!+iliCM>?8\BK\DjWp]ZGB@roATarCnES+H32nfJcr^_b[^F6D-t^4M"X^ZO)A*2ps>4@(\_YQ1f(alcNTU41qnJ/`!29\hN5f:4pF2\P]4aSO"Nadb<S+\`<3jdesQ:)lMs.kpbCb)pk"][`>H*HphuCJX"lVQOm8rC^VJ>ghQJEPdUEALX'/S7Tb0oNLW8um9@e#s@HA#Yi_2+QWXWA2/![6;Eh7!9mmoYo?\AOW4$o=`P.>,^dKd1ccofR^>/B:-.MbXYTJi93(N?,MQ.HRD$?LSp6a#fQ%7s\>rQ`2@]`am3C#&L8)dOBas8)?p9R3ImR_TkJ>o?aGBs-B2=]Q;pF@o5q\*hZ.meBJ5<1A'4j#C`h@m!]*h&9+:8%ubW9`AteG[4o3>q@gC\j"jXNEMH?q!<6q;@r:ng04M!LX#s\f-]\,@'8g3P8a4hZR%ZRg4$R]&sK&"<.RmR<N4T]3#edT#E4<kE*8,sg^feHqeMD?>Sb4D6$T_CE_f)#;`"9TbS(ic\8XOZ(RC.Z6)3Cpm,fSM[esNJp1$=-;#M6^"Y/l]#+T0EGE[=Wr<P`,+)E-ZU`@%E>;3XRATO9pl9Mg`g\"cf[^QK_R1HRo&NuB$@M3b%0f(R[I+AuRgc`&rO5RIn-[5p^7\NI0]QC!/T&T6Uo'pZZlg\or#TC<7R/l=06>'q-U?>N;RiJQ]k#@SCH!7"6Zf;_S\94pXR\&;ob+p9`HG2g*)!)V$,?1lM&.Ilb$:(^nQ!?GW)<8qLN,$6siI\@j!*EXnYL9gMbMiqjr[IM?RtJj@/C`qfP8#Kg@]`\qUG9F`N_YSP!.V+#Q#!-FjS4:jo1PX5>Fc(BZ=H=oaAfj^&@JJk)9aG6@o>AA.^KXqbcnOq;(-T!SSI&5aftD-FNF%%p`^Uf('[,MqOLA>#)T;TI?@p+qe1+h/T2fkiQl#M&c9(^L;@;J\dbl?7L_7@]ihBr<`u`0arH&dq>4hQWud@Q)8csfk4,,J!)i.,\b,),64eR)GKBdI`9K$<ron=I0IB+Wa`g!#NHMLFX@JQ]&[@SQciYNd(BFj@8G4RR4m)Veni?0)N!\F0=`'YbAIS8"Q-jB+ra98FGI?a#m98ps?,<D=XXeTp\>C;".rfLMl=u`P2eTA2Fpm/-fT5Z!K%QVUj$EHCX3eSs,D)tKAseK]Jc'8YX"(eN#%?*bI5QO)bRM!6$Z(i0\Sc;f</$ei9Cq$0PC/R^oFt(XlRupen989un*4X/,8WK6U_qYYKC*MtQ)0hdXW72d)JB&8"*JOoHIs0epZKtB\O:s#/oEg2h*QJ/gan>#jbP:OJDkdMQaI@VFi!uP([RSNs(nkF4i^H1C:N/>*R'=SA'1Oi2/F:X7i*SiQTn7D:=cnQ0D<rrlhktV?0?:B=C>"3g=JC+kY@=bQXMVO9'ef[JulAZn$E4q5L'n4XDb@3E6?%G@ncEs@pB!1n5um#o"G#%"$XBEeZu:'.#GZ2ocL..[jrMj1:aU`DeZI)f8E6I(Zt>o:N4sEiDC1e:JGl78*HfHJG1,r:g@f~>endstream
endobj
36 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1102
>>
stream
Gb!Sk>E@5m'RnB330+/>*SYRMB;F'm(m_!+>*a$aW@oE7gTYBsihXB)kN(&J1Yp=b#`VfPW,Z[HF2%+ui\I0OZ1^94!'Ha's+1:30OFkp7)8^Xr%[q0H%\hn6jP'b1l+,Q-tke@Y.Og&8S#aa#+@$9PlRKqd5o^k;,/R;TJWm#l7jDR;PaO.bsJDkOaKD>k@e%\:s/"1I#h&^n00''F9o4b;&Se04fL__+:)PP$=UZ"Mne/^T>F751+l^6S&;k<1s*=\"m65qVNWOn109kXSsu:mm<2usl3Y[!_Ye+XCSY;?RNNDh;u^XnJ2+ipX6d,jqbOlIMATKU'YQr5mPRSm,?7^,+J9'oEMpjj!'R(Z+bbT/a$\8aLUl4#MGPepBTDD-QQSsn5+W)THP17a_ZA4W,Tf`L\1\1tclK?.cN%?;OW_I<3;48mZGRk,'[a`him@jc*.JA>Hf9OqLjA!^]f)jYX,F[kU.Lq7HR7'fIZ.__d@1a2)FK1<etHtk`ong)KQVaXr>Z]:Xau;t@fq=HO@>PI2[hf!D,H\[^@4ciL[]f)#7)8@=#;6n`*:q3mnK4;*&ALFcX5K^4>o@PLnoE/R^UCd`opa_C?@lFfZS'[d%`apXs_S>3nkEkZ!^1p"GqsK1/*mo/a2U9b1TMN0<JNWgU7;>+jMDerK!adkGL4XITBFE:$\UtCiL)=L-RsEc%E.eU?SnG`LFF6;B<(pc-hiR8;uH.9G1PJ^QY$a-uoQ8:*5Pn_:Eg-YFW41`t.-7B0L4),e3_(b)aX=LBu4q=4_Sbk?5'A`Pp-b.oW5JEoRp2NnpqHFcj[<MEAR>;0R]Zm4E]Q:C<B^Bg$02DrVA51\\AROGP&p7qYYrJ'Hn4cgY%V$*JIX8aPS__L@GfYtN5UHfha3YPjEX+ZrE6bTq>!<F>cepSuM:j8?Eiep_<t:\$taid-H&0%H@=GG^lij9N)N=6;^CmND#g1<&;+:Fjn.iO2mGmG\/#._,Cf$["t/%b_r^s"oFD>d?0TDL1.Cft@4'Y!!hh3:#eA=Y@$+>6JAr)bA5nSo_mD8)MI*[Wu-:s08Wh-1lk[MAm=[YJ+o!DfKrpbeX)[BUEfX~>endstream
endobj
xref
0 37
0000000000 65535 f
0000000061 00000 n
0000000122 00000 n
0000000229 00000 n
0000000341 00000 n
0000000546 00000 n
0000000651 00000 n
0000000728 00000 n
0000000933 00000 n
0000001138 00000 n
0000001343 00000 n
0000001549 00000 n
0000001755 00000 n
0000001961 00000 n
0000002167 00000 n
0000002373 00000 n
0000002579 00000 n
0000002785 00000 n
0000002991 00000 n
0000003197 00000 n
0000003403 00000 n
0000003473 00000 n
0000003765 00000 n
0000003917 00000 n
0000005843 00000 n
0000007684 00000 n
0000009412 00000 n
0000011289 00000 n
0000011782 00000 n
0000013970 00000 n
0000014961 00000 n
0000017348 00000 n
0000019072 00000 n
0000020704 00000 n
0000022172 00000 n
0000024060 00000 n
0000026093 00000 n
trailer
<<
/ID
[<c8e42541392c429be860bac76f53b1b7><c8e42541392c429be860bac76f53b1b7>]
% ReportLab generated PDF document -- digest (opensource)
/Info 21 0 R
/Root 20 0 R
/Size 37
>>
startxref
27287
%%EOF

View File

@@ -0,0 +1,375 @@
# Les 11 — Vercel AI SDK
## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven)
**Lesvorm:** Tim demonstreert klassikaal. Studenten kijken mee, gaan thuis zelf aan de slag.
**Demo-app:** Polderfest 2027 — fictief muziekfestival met 500 verzonnen bands in Supabase. Studenten kunnen vragen stellen aan dummy data die LLM's onmogelijk vooraf konden kennen.
---
## Slide 1: Title
### Les 11 — Vercel AI SDK
**Visual:**
- Background: CREAM
- "Les 11" in BLUE
- "Vercel AI SDK" in BLACK
- Subtitle: "Praat met je eigen data — vandaag bouwen we Polderfest 2027"
---
## Slide 2: Terugblik
### Waar staan we?
**Vorige lessen:**
- Supabase geïntegreerd in je app
- Tabellen + relaties opgezet
- RLS policies bekeken (wie mag wat lezen/schrijven)
**Vandaag bouwen we niet voort op QuickPoll — we starten een nieuwe demo from scratch.**
We laten zien hoe je een Next.js app aan een verse Supabase koppelt en die data combineert met AI.
**Visual:** Twee icoontjes (database + Next.js logo) met pijl naar AI-icoon.
---
## Slide 3: Planning
### Vandaag — 180 minuten
| Onderwerp | Duur |
|-----------|------|
| Welkom + Terugblik | 10 min |
| Theorie: Wat is de Vercel AI SDK? | 30 min |
| **Live Demo 1** — Next.js scaffold + Supabase koppelen | 20 min |
| **Live Demo 2** — Seed script: 500 records in Supabase | 20 min |
| **Pauze** | 15 min |
| **Live Demo 3** — AI SDK installeren + chat-route | 30 min |
| **Live Demo 4** — Vragen stellen aan onze data | 15 min |
| Waarom data + AI samen krachtig is | 5 min |
| Lesopdracht + Huiswerk uitleg | 20 min |
| Vragen + Afsluiting | 15 min |
**Belangrijk:** Vandaag is **demo-driven**. Jullie kijken en luisteren. Thuis gaan jullie zelf aan de slag met jullie eigen thema.
---
## Slide 4: Wat is de Vercel AI SDK?
### Eén SDK, alle providers
**Content:**
- TypeScript-first SDK voor AI features
- Werkt met OpenAI, Anthropic, Google, Mistral, Groq, en meer
- **Unified API:** zelfde code voor elk model
- Streaming out-of-the-box
- React hooks (`useChat`, `useCompletion`)
- Tool Calling (volgende les)
- Open source · gemaakt door Vercel
**Code teaser:**
```typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
const { text } = await generateText({
model: openai("gpt-4o-mini"),
prompt: "Vat de Polderfest line-up samen",
});
```
**Visual:** Logo's van OpenAI/Anthropic/Google met pijl naar één AI-SDK doos.
---
## Slide 5: Modellen + Kosten
### Welk model wanneer?
| Provider | Model | Use case | Prijs (in/out per 1M tokens) |
|----------|-------|----------|------------------------------|
| OpenAI | gpt-4o-mini | Default — snel + goedkoop | $0.15 / $0.60 |
| OpenAI | gpt-4o | Multimodaal (vision) | $2.50 / $10 |
| OpenAI | gpt-4.1 | Reasoning, agents | $2 / $8 |
| Anthropic | claude-sonnet-4 | Coding, lange context | $3 / $15 |
| Google | gemini-2.5-flash | Snel + multimodaal | $0.075 / $0.30 |
| Groq | llama-3.3-70b | Ultra-fast inference | $0.59 / $0.79 |
**Vuistregel:** start met `gpt-4o-mini`. Upgrade pas als nodig.
**Voor vandaag:** `gpt-4o-mini`. Onze hele les inclusief Polderfest-Q&A kost ongeveer 1-2 cent.
---
## Slide 6: De 4 kern-functies
### Wat je vandaag gaat zien
| Functie | Wat het doet | Wanneer |
|---------|--------------|---------|
| `generateText` | Wacht tot AI antwoord klaar is — string terug | Korte server-only antwoorden |
| `streamText` | Stream karakter voor karakter | Chat UI, lange antwoorden |
| `useChat` | React hook voor instant chat UI | Client-side chat |
| `generateObject` | Type-safe data via Zod schema | Database inserts, classificatie |
**Vandaag gebruiken we vooral:**
- `streamText` + `useChat` — voor de chat UI
- Onze Polderfest-data als context — AI beantwoordt vragen op basis van onze 500 bands
**Volgende les (Les 12):** `generateText` + `tools` — Tool Calling, waar AI zelf besluit welke DB-query te runnen.
---
## Slide 7: Wat bouwen we vandaag?
### Polderfest 2027 — een fictief festival
**Het idee:**
Een fictief Nederlands muziekfestival met **500 verzonnen bands**. Volledig fictief — geen enkele LLM kan dit weten uit training. Dit is precies waar AI + jouw data sterker wordt dan AI alleen.
**Schema (Supabase tabel `bands`):**
- `name`, `genre`, `sub_genre`
- `stage`, `day`, `start_time`, `duration_min`
- `origin_city`, `members`, `bio`
- `tier` (headliner / mid / opener), `popularity`, `ticket_impact`
**Voorbeeld-vragen die we kunnen stellen aan AI:**
- "Welke bands spelen vrijdagavond na 22:00 op de Main Stage?"
- "Geef me 5 acts uit Groningen, gesorteerd op populariteit"
- "Vat de hip-hop scene op Polderfest samen in 3 zinnen"
- "Welke headliner is qua bio het meest interessant voor electronic-fans?"
**Visual:** Festival-poster mock-up met genre-tags + Supabase logo.
---
## Slide 8: LIVE DEMO 1
### Next.js scaffold + Supabase koppelen (~20 min)
**Wat ik laat zien:**
1. `npx create-next-app@latest polderfest --typescript --tailwind --app`
2. Nieuw Supabase project aanmaken (dashboard)
3. SQL Editor: schema runnen (zie schema.sql)
4. Supabase client installeren: `npm i @supabase/supabase-js`
5. `.env.local` met `SUPABASE_URL` + `SUPABASE_ANON_KEY` + `SUPABASE_SERVICE_ROLE_KEY`
6. `lib/supabase.ts` aanmaken (client)
7. Tabel-check via Table Editor: leeg, klaar om te seeden
**Wat ik NIET uitleg:** Next.js / Supabase basics — dat hebben jullie al gehad.
**Visual:** Badge "LIVE DEMO" in PINK + screenshots Supabase dashboard.
---
## Slide 9: LIVE DEMO 2
### Seed script: 500 records in Supabase (~20 min)
**Wat ik laat zien:**
1. `seed-polderfest.ts` openen — uitleggen wat 't doet:
- Procedureel 500 bands genereren
- Combinaties van adjectives + nouns + bio-fragmenten
- Insert in batches van 100
2. Service role key uitleggen — alleen lokaal, niet in client
3. `npm i tsx @supabase/supabase-js dotenv --save-dev`
4. `npx tsx seed-polderfest.ts` runnen
5. Supabase Table Editor refresh → 500 records zichtbaar
6. Een paar voorbeelden tonen — "De Tigers", "Lost Mirrors", "Sanne Van Dijk & The Echoes"
**Sleutel-inzicht:** dit zijn 500 namen die **niet bestaan**. Geen enkele LLM kan ze kennen.
**Visual:** Terminal log van seed + Table Editor screenshot.
---
## Slide 10: Pauze
### 15 minuten
---
## Slide 11: LIVE DEMO 3
### AI SDK installeren + chat-route (~30 min)
**Wat ik laat zien:**
1. `npm i ai @ai-sdk/openai zod`
2. `OPENAI_API_KEY` toevoegen aan `.env.local` (schoolkey via Brightspace)
3. **`app/api/chat/route.ts`** schrijven:
- Haal alle bands op uit Supabase
- Format als context-string
- `streamText` aanroepen met system + user messages
- Return `result.toDataStreamResponse()`
4. **`app/chat/page.tsx`** schrijven:
- `"use client"` + `useChat` hook
- Simpele Tailwind chat UI (messages list + input)
5. Naar `/chat` browsen → werkt
6. Eerste prompt: "Hoeveel bands spelen vrijdag?"
**Belangrijke uitleg-momenten:**
- Waarom we **alle bands meesturen** als context (volgende les: Tool Calling lost dit op)
- Hoe `streamText` zich aansluit op `useChat`
- System prompt — hoe je de AI 'rol' geeft
**Visual:** Code-mock-up + chat preview.
---
## Slide 12: LIVE DEMO 4
### Vragen stellen aan onze data (~15 min)
**Vragen die we live uitproberen:**
1. "Welke bands spelen zaterdag op de Beach Stage?"
2. "Geef me 3 headliners met de meeste popularity, en hun bio's"
3. "Hoeveel jazz fusion acts spelen er totaal?"
4. "Vat de electronic-scene op Polderfest samen — wat zou je aanraden voor iemand die houdt van techno?"
5. **Slechte vraag:** "Wie was de hoofdacts van Polderfest 2025?" — AI antwoordt eerlijk dat hij dat niet weet (data alleen 2027)
**Sleutel-inzicht:**
- AI is **slim**, maar pas écht nuttig met **jouw data**
- LLM weet niets van Polderfest 2027 — toch krijgen we precieze antwoorden
- Combinatie = `context (jouw data) + reasoning (AI)`
**Visual:** Chat screenshots met antwoorden.
---
## Slide 13: Waarom is dit krachtig?
### Data + AI > Data alleen, AI alleen
**Data alleen:**
- Supabase query: filter + sort + select
- Geen interpretatie, geen samenvatting, geen taal
- Gebruiker moet zelf SQL-denken
**AI alleen:**
- Gebrekkige kennis over jouw domein
- Verzint vaak (hallucinatie)
- Geen toegang tot live data
**Data + AI:**
- AI interpreteert en vat samen
- Antwoorden in natuurlijke taal
- Filtert + reasoneert + presenteert
- Schaalbaar — voeg data toe = nieuwe antwoorden mogelijk
**Quote om mee weg te lopen:**
> "Een LLM zonder jouw data is een gewone chatbot.
> Een LLM mét jouw data is een product."
---
## Slide 14: Lesopdracht
### Jouw eigen thema-app
**Voor thuis (niet in de les) — bouw je eigen versie:**
1. Bedenk een **eigen thema** met data die LLM's niet kunnen weten
2. Maak een nieuw Next.js project + nieuwe Supabase
3. Schrijf eigen `seed-XXX.ts` script (mag AI je bij helpen!)
4. Seed minstens **100 records** in Supabase
5. Implementeer chat-route + chat-pagina (zelfde flow als Polderfest)
6. Stel 3 vragen aan je AI die alleen kunnen door jouw data
**Voorbeeld eigen thema's:**
- Fictief restaurant-aggregator in een verzonnen stad
- Galactische bestuurders archief (sci-fi)
- Verzonnen scriptie-archief NOVI
- Fictieve museumcollectie
- Fictief NPO-programma overzicht
- ...
**Beperking:** **GEEN echte/openbare data** (geen Spotify, geen TheCocktailDB). Het moet fictief zijn zodat de demo-kracht zichtbaar wordt.
**Visual:** 4 voorbeeld-thema's als cards.
---
## Slide 15: Huiswerk
### Polderfest seed-script aanpassen + uitbreiden
**Voor volgende week (Les 12):**
**Verplicht — onderdeel A:**
- Pas het seed-script aan voor **jouw eigen thema** (gebruik gerust AI om te helpen)
- Run het tegen je eigen Supabase
- Push naar GitHub repo
**Verplicht — onderdeel B:**
- Voeg minstens **1 extra veld** toe waarvan je denkt dat het interessante vragen mogelijk maakt
- Update schema + seed script
- Stel een vraag aan AI die alleen kan dankzij dat nieuwe veld
**Verplicht — onderdeel C:**
- Schrijf `AI-CHAT.md` in je repo met:
- Jouw thema (wat is het, waarom kun je dit niet aan een gewone LLM vragen?)
- 3 leuke vragen die werken op jouw data
- 1 vraag waar de AI moeite mee had — wat veranderde toen je de prompt aanpaste?
**Bonus:** Deploy op Vercel — preview URL meesturen.
**Visual:** Workflow-diagram + checklist.
---
## Slide 16: Volgende les — Tool Calling
### Hoe schaalt dit?
**Probleem dat we vandaag introduceren:**
- We sturen **alle 500 bands** mee als context bij elke vraag
- 500 bands ≈ 30.000 tokens — dat is veel, kost geld, traagt
- 5.000 bands? 50.000 bands? Werkt niet meer
**Oplossing (volgende les):**
- **Tool Calling** — AI besluit zelf welke query te runnen
- Voorbeeld: AI ziet vraag "Welke bands spelen vrijdag?" → roept tool `searchBands(day: "Vrijdag")` aan → krijgt 60 bands terug → antwoordt
- Schaalbaar, slim, multi-step
**Daarna in deze leerlijn:**
- Les 13: Agents + `maxSteps` (multi-step autonoom)
- Les 14: RAG + embeddings (semantic search op groot corpus)
- Les 15-16: Testing + Deployment
- Les 17-18: Eindopdracht-werkdagen + Pitch
---
## Slide 17: Afsluiting
### Vragen?
**Wat we vandaag gezien hebben:**
- Vercel AI SDK basics
- Modellen + 4 kern-functies
- Next.js + Supabase + AI SDK end-to-end gekoppeld
- Live demo met seed-script (500 records procedureel)
- Vragen stellen aan een dataset die geen LLM kent
**Vragen? Feedback?**
**Visual:** Cream achtergrond, blauw rondje met "→ Tool Calling".
---
## Slide Summary
| # | Title | Type |
|---|-------|------|
| 1 | Title | Opening |
| 2 | Terugblik | Recap (Supabase + RLS, kort) |
| 3 | Planning | 180-min schedule |
| 4 | Wat is de AI SDK | Theorie |
| 5 | Modellen + kosten | Theorie |
| 6 | 4 kern-functies | Theorie |
| 7 | Vandaag bouwen we Polderfest | Intro demo |
| 8 | **LIVE DEMO 1** — Next.js + Supabase | Demo |
| 9 | **LIVE DEMO 2** — Seed 500 records | Demo |
| 10 | Pauze | Break |
| 11 | **LIVE DEMO 3** — AI SDK + chat | Demo |
| 12 | **LIVE DEMO 4** — Vragen stellen | Demo |
| 13 | Data + AI = kracht | Reflectie |
| 14 | Lesopdracht — eigen thema | Praktijk |
| 15 | Huiswerk — seed aanpassen | Praktijk |
| 16 | Volgende les: Tool Calling | Preview |
| 17 | Afsluiting | Closing |
---
## Bronnen
- Vercel AI SDK docs — https://ai-sdk.dev/docs/introduction
- generateText / streamText / useChat / generateObject reference — https://ai-sdk.dev/docs/reference
- Supabase JS client — https://supabase.com/docs/reference/javascript
- Next.js App Router — https://nextjs.org/docs/app
- OpenAI pricing — https://openai.com/api/pricing
- Tokenizer — https://platform.openai.com/tokenizer
- Vercel templates met AI — https://vercel.com/templates?type=ai

Binary file not shown.

Binary file not shown.

42
Les11-AI-SDK/schema.sql Normal file
View File

@@ -0,0 +1,42 @@
-- Polderfest 2027 — Supabase schema
-- Run dit in Supabase SQL Editor voor je het seed script gebruikt.
create table if not exists bands (
id bigserial primary key,
name text not null,
genre text not null,
sub_genre text,
stage text not null,
day text not null check (day in ('Vrijdag','Zaterdag','Zondag')),
start_time text not null, -- "21:30"
duration_min int not null default 60,
origin_city text,
members text[],
bio text,
tier text check (tier in ('headliner','mid','opener')),
popularity int check (popularity between 1 and 100),
ticket_impact numeric(6,2), -- bijdrage aan ticketprijs als extra
created_at timestamp default now()
);
-- Maak indexen voor de vragen die we vaak gaan stellen
create index if not exists idx_bands_day on bands(day);
create index if not exists idx_bands_stage on bands(stage);
create index if not exists idx_bands_genre on bands(genre);
create index if not exists idx_bands_tier on bands(tier);
-- RLS (we lezen public, geen edits voor anon)
alter table bands enable row level security;
create policy "Bands zijn publiek leesbaar"
on bands for select
using (true);
-- Optioneel: tabel voor straks (tool calling demo in Les 12)
create table if not exists user_favorites (
id bigserial primary key,
user_email text not null,
band_id bigint not null references bands(id) on delete cascade,
created_at timestamp default now(),
unique(user_email, band_id)
);

View File

@@ -0,0 +1,258 @@
/**
* Polderfest 2027 — seed script
* ----------------------------------------------------------
* Genereert 500 fictieve bands en zet ze in je Supabase `bands` tabel.
* Run:
* 1. Zorg dat `bands` tabel bestaat (zie schema.sql)
* 2. Vul .env.local met:
* SUPABASE_URL=https://<project>.supabase.co
* SUPABASE_SERVICE_ROLE_KEY=<service role key>
* 3. npm i @supabase/supabase-js dotenv tsx --save-dev
* 4. npx tsx seed-polderfest.ts
*
* Service role key is bewust nodig — alleen voor lokaal seeden.
* NIET committen, NIET in client gebruiken.
*/
import { createClient } from "@supabase/supabase-js";
import "dotenv/config";
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{ auth: { persistSession: false } }
);
// ────────────────────────────────────────────────────────────
// Deterministische random (zodat seed reproduceerbaar is)
// ────────────────────────────────────────────────────────────
let seed = 42;
function rand() {
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280;
}
function pick<T>(arr: readonly T[]): T {
return arr[Math.floor(rand() * arr.length)];
}
function pickN<T>(arr: readonly T[], n: number): T[] {
const copy = [...arr];
const out: T[] = [];
for (let i = 0; i < n && copy.length; i++) {
out.push(copy.splice(Math.floor(rand() * copy.length), 1)[0]);
}
return out;
}
function range(min: number, max: number): number {
return Math.floor(rand() * (max - min + 1)) + min;
}
// ────────────────────────────────────────────────────────────
// Bouwstenen voor band-namen
// ────────────────────────────────────────────────────────────
const adjectives = [
"Lost", "Velvet", "Iron", "Neon", "Silent", "Wild", "Glass", "Paper", "Sleeping",
"Honest", "Crooked", "Bitter", "Sweet", "Drowsy", "Drowning", "Restless", "Sober",
"Midnight", "Morning", "Yellow", "Crimson", "Hollow", "Heavy", "Floating", "Slow",
"Burning", "Frozen", "Cardboard", "Plastic", "Analog", "Digital", "Forgotten",
] as const;
const nouns = [
"Tigers", "Wolves", "Horses", "Rabbits", "Mirrors", "Clouds", "Echoes", "Ghosts",
"Lights", "Roots", "Stones", "Foxes", "Riders", "Ships", "Tides", "Anchors",
"Maps", "Letters", "Postcards", "Radios", "Telegrams", "Diaries", "Highways",
"Cassettes", "Polaroids", "Cathedrals", "Stations", "Lanterns", "Compasses",
"Saturdays", "Tuesdays", "Mondays",
] as const;
const dutchPrefixes = [
"De", "Het", "Van der", "Polder", "Noord", "Zuid",
] as const;
const soloNamesFirst = [
"Sanne", "Joost", "Yara", "Lex", "Mila", "Tess", "Bram", "Lotte", "Ravi", "Imani",
"Marit", "Stijn", "Liva", "Noor", "Casper", "Anouk", "Mees", "Pien", "Daan", "Olivia",
"Niels", "Fenna", "Tygo", "Saar", "Cas", "Maud", "Roos", "Vince", "Lieke", "Floris",
] as const;
const soloNamesLast = [
"Van Dijk", "De Boer", "Visser", "Jansen", "Bakker", "Hendriks", "Mulder", "Smit",
"Peters", "De Vries", "Kuipers", "Brouwer", "Postma", "Hofman", "Van Loon",
] as const;
// ────────────────────────────────────────────────────────────
// Fest-velden
// ────────────────────────────────────────────────────────────
const genres = [
"Indie Rock", "Electronic", "Hip-Hop", "Jazz Fusion", "Folk", "Punk", "Soul",
"Ambient", "Disco-House", "Experimental", "Singer-Songwriter", "Synth-Pop",
"Garage Rock", "Neo-Soul", "Drum & Bass", "Afrobeat", "Dream Pop", "Post-Rock",
] as const;
const subGenresByGenre: Record<string, string[]> = {
"Indie Rock": ["Shoegaze", "Lo-Fi", "Math Rock", "Slowcore"],
"Electronic": ["Techno", "House", "IDM", "Glitch", "Trance"],
"Hip-Hop": ["Boom Bap", "Trap", "Lo-Fi", "Conscious"],
"Jazz Fusion": ["Funk Jazz", "Cosmic Jazz", "Nu-Jazz"],
"Folk": ["Anti-Folk", "Sea Shanty", "Modern Folk"],
"Punk": ["Post-Punk", "Hardcore", "Surf Punk"],
"Soul": ["Neo-Soul", "Northern Soul", "Funk"],
"Ambient": ["Drone", "New Age", "Field Recording"],
"Disco-House": ["Italo Disco", "Nu-Disco", "French House"],
"Experimental": ["Noise", "Sound Art", "Avantgarde"],
"Singer-Songwriter": ["Confessional", "Storytelling"],
"Synth-Pop": ["Vaporwave", "Italo", "Darkwave"],
"Garage Rock": ["Surf", "Power Pop"],
"Neo-Soul": ["Alt R&B", "Jazz-influenced"],
"Drum & Bass": ["Liquid", "Jungle", "Neurofunk"],
"Afrobeat": ["Afro-Fusion", "Highlife"],
"Dream Pop": ["Bedroom Pop", "Ethereal"],
"Post-Rock": ["Cinematic", "Math-influenced"],
};
const stages = [
"Main Stage", "Tent Stage", "Beach Stage", "Acoustic Bar", "Late Night Tent",
] as const;
const days = ["Vrijdag", "Zaterdag", "Zondag"] as const;
const timeSlots = [
"14:00", "15:30", "17:00", "18:30", "20:00", "21:30", "23:00", "00:30",
] as const;
const cities = [
"Amsterdam", "Rotterdam", "Utrecht", "Groningen", "Eindhoven", "Den Haag",
"Tilburg", "Maastricht", "Nijmegen", "Leeuwarden", "Arnhem", "Breda", "Haarlem",
"Zwolle", "Enschede", "Delft", "Den Bosch", "Apeldoorn",
] as const;
const tiers = ["headliner", "mid", "opener"] as const;
// ────────────────────────────────────────────────────────────
// Bio-fragmenten — combinatorisch zodat 500 bios uniek voelen
// ────────────────────────────────────────────────────────────
const bioOpenings = [
"Begonnen in een garage in",
"Ontstaan tijdens een blackout in",
"Een vriendengroep uit",
"Doorgebroken op het kleine podium van",
"Geboren uit een jam-sessie in",
"Een collectief van producers uit",
];
const bioMiddle = [
"experimenteert met analoge synths en gefluisterde lyrics",
"balanceert tussen melancholie en dansvloer-euforie",
"mixt traditionele samples met breakbeats",
"gebruikt veldopnames als ritmesectie",
"schrijft songs in Nederlands en Engels door elkaar",
"speelt instrumenten die ze grotendeels zelf hebben gebouwd",
"draait alleen optredens op locaties zonder Wi-Fi",
];
const bioEnding = [
"Debuut-EP verschijnt eind 2027.",
"Hun laatste album werd genomineerd voor de fictieve Edison Polder Award.",
"Polderfest is hun grootste festival tot nu toe.",
"Vorig jaar speelden ze nog in cafés, dit jaar op Stage B.",
"Spelen voor het eerst op een buitenpodium.",
"Beruchte live-show met 12 backing vocalists.",
];
// ────────────────────────────────────────────────────────────
// Namen genereren
// ────────────────────────────────────────────────────────────
function generateBandName(seedIdx: number): string {
const pattern = seedIdx % 4;
if (pattern === 0) {
return `${pick(adjectives)} ${pick(nouns)}`;
}
if (pattern === 1) {
return `${pick(dutchPrefixes)} ${pick(nouns)}`;
}
if (pattern === 2) {
return `${pick(soloNamesFirst)} ${pick(soloNamesLast)}`;
}
return `${pick(soloNamesFirst)} & The ${pick(nouns)}`;
}
function generateMembers(): string[] {
const count = range(1, 5);
const out: string[] = [];
for (let i = 0; i < count; i++) {
out.push(`${pick(soloNamesFirst)} ${pick(soloNamesLast)}`);
}
return out;
}
function generateBio(name: string): string {
return `${pick(bioOpenings)} ${pick(cities)}, ${name} ${pick(bioMiddle)}. ${pick(bioEnding)}`;
}
// ────────────────────────────────────────────────────────────
// Hoofdfunctie
// ────────────────────────────────────────────────────────────
async function seed() {
console.log("Genereren van 500 Polderfest bands...");
// Wipe bestaande data (optioneel)
await supabase.from("bands").delete().neq("id", 0);
const bands = [];
const usedNames = new Set<string>();
for (let i = 0; i < 500; i++) {
let name = generateBandName(i);
let attempts = 0;
while (usedNames.has(name) && attempts < 10) {
name = generateBandName(i + attempts * 7);
attempts++;
}
usedNames.add(name);
const genre = pick(genres);
const sub_genre = pick(subGenresByGenre[genre]);
const tier = pick(tiers);
const popularity = tier === "headliner" ? range(80, 100)
: tier === "mid" ? range(40, 79)
: range(10, 39);
const ticket_impact = tier === "headliner" ? range(25, 60)
: tier === "mid" ? range(5, 25)
: 0;
bands.push({
name,
genre,
sub_genre,
stage: pick(stages),
day: pick(days),
start_time: pick(timeSlots),
duration_min: tier === "headliner" ? range(75, 120)
: tier === "mid" ? range(45, 75)
: range(30, 45),
origin_city: pick(cities),
members: generateMembers(),
bio: generateBio(name),
tier,
popularity,
ticket_impact,
});
}
console.log("Schrijven naar Supabase in batches van 100...");
// Supabase insert in batches (single call van 500 kan timeouten)
for (let i = 0; i < bands.length; i += 100) {
const batch = bands.slice(i, i + 100);
const { error } = await supabase.from("bands").insert(batch);
if (error) {
console.error("Insert error op batch", i / 100, ":", error.message);
process.exit(1);
}
console.log(`${i + batch.length}/${bands.length}`);
}
console.log("Klaar! 500 Polderfest bands staan in Supabase.");
}
seed().catch((e) => {
console.error(e);
process.exit(1);
});

View File

@@ -0,0 +1,109 @@
# Curriculum Klas B — 18 Lessen
**Versie:** 1.1 (concept)
**Doelgroep:** Studenten met al kennis van terminal/git, JS/TS, React/Next.js en AI tools.
**Focus:** Code kwaliteit · Veiligheid · Mooie ontwerpen
**Eindopdracht:** ongewijzigd (Next.js 14 + Supabase + Vercel AI SDK + externe API)
**Lesvorm:** Online, 2 uur per les
---
## Lesformat (elke les)
| Tijd | Onderdeel |
|------|-----------|
| 0:00 1:00 | Uitleg / live demo (60 min) |
| 1:00 1:15 | Pauze (15 min) |
| 1:15 2:00 | Praktijk / hands-on (45 min) |
Plus: **kleine huiswerkopdracht** elke week (~3060 min) — reinforced wat in de les is behandeld en bereidt voor op volgende les.
---
## Filosofie
Klas A had veel tijd nodig voor setup, basics en klassikaal volgen. Klas B niet. Daarom:
- **Geen setup-tijd:** terminal, git, npm, Cursor staan al. Controle in Les 2 in 10 min.
- **Theorie kort, demo-driven:** binnen de 60 min talk zo veel mogelijk live coden.
- **Standaarden vanaf dag 1:** elke les bevat één "professional habit" (types, validatie, a11y, security).
- **Huiswerk is verplicht klein:** geen weekend-projecten — wel iedere week een concrete oefening.
- **Eindopdracht parallel** vanaf Les 4 (eigen project naast lessen, niet pas na les 18).
---
## Roadmap — 4 Blokken
### Blok 1 — Solid Foundation (Les 14)
| Les | Titel | Kern-leerdoel | Huiswerk |
|-----|-------|---------------|----------|
| 1 | Introductie AI & LLMs | (Reeds gegeven 2026-05-12) | — |
| 2 | AI-Dev Workflow Mastery | Cursor + Skills.sh + .cursorrules + MCPs effectief inzetten | Maak een eigen .cursorrules voor je stack |
| 3 | Next.js 14 Architectuur | App Router, Server vs Client Components, folder conventies | Bouw een mini-app skeleton met juiste structuur |
| 4 | TypeScript voor Professionals | Generics, discriminated unions, Zod schemas, type-safe contracts | Refactor 1 functie naar volledig type-safe + Zod |
### Blok 2 — Design & Craft (Les 59)
| Les | Titel | Kern-leerdoel | Huiswerk |
|-----|-------|---------------|----------|
| 5 | Design Systems met Tailwind + Shadcn | Design tokens, theming, dark mode, componenten library | Setup eigen design tokens + 3 componenten |
| 6 | Mooie UI in de Praktijk | Smaak ontwikkelen: typografie, spacing, kleur, hiërarchie | Herontwerp 1 bestaande pagina, screenshot voor/na |
| 7 | Motion & Micro-interactions | Framer Motion, loading states, transitions, feedback patterns | Voeg 3 micro-interactions toe aan je app |
| 8 | Accessibility & Responsive | ARIA, keyboard nav, contrast, mobile-first | a11y audit op eigen project + 3 fixes |
| 9 | Clean Code & Refactor met AI | Naming, code smells, AI-assisted refactoring | Refactor 1 component van "werkt" naar "mooi" |
### Blok 3 — Data & Security (Les 1013)
| Les | Titel | Kern-leerdoel | Huiswerk |
|-----|-------|---------------|----------|
| 10 | Supabase + Database Design | Schema modelleren, relaties, indexes, migraties | ERD + schema voor je eindopdracht |
| 11 | Auth & RLS vanaf de Grond | Auth flow, sessions, JWT, RLS policies (vanaf dag 1!) | RLS policies schrijven + testen met 2 users |
| 12 | Server Actions & Validatie | Zod, sanitization, error handling, never-trust-client | Alle forms in je app valideren met Zod |
| 13 | Web Security in de Praktijk | OWASP Top 10, XSS, CSRF, secrets management, .env hygiene | Security audit eigen project + fix top-3 issues |
### Blok 4 — AI Features & Production (Les 1418)
| Les | Titel | Kern-leerdoel | Huiswerk |
|-----|-------|---------------|----------|
| 14 | Vercel AI SDK Basics | Streaming, useChat, generateText, structured outputs | Bouw een streaming chat in eigen project |
| 15 | Tool Calling & Externe APIs | Function calling, API integratie, error states | 1 AI tool koppelen aan een gratis externe API |
| 16 | AI Agents & maxSteps | Multi-step autonome agents, planning, tool orchestration | Agent maken die 2+ tools chained gebruikt |
| 17 | Testing & Deployment | Vitest + Playwright (AI-assisted), Vercel deploy, Lighthouse | Tests schrijven + deployen naar productie |
| 18 | Code Review & Pitch | Peer review, refactor sessie, eindopdracht presentatie | Pitch-deck voorbereiden (5 min) |
---
## Mapping naar eindopdracht-leeruitkomsten
| Leeruitkomst | Lessen die dit dekken |
|---|---|
| 1. TypeScript & Next.js Development | 3, 4, 5, 9 |
| 2. Database Design & Supabase | 10, 11, 12 |
| 3. AI-Assisted Development met Cursor | 2, 9, 17 |
| 4. Skills.sh voor Consistente Output | 2 (intro), terugkerend |
| 5. Prompt Engineering & Iteratief Werken | 2, 14, 16 |
| 6. AI Agents Bouwen met Vercel AI SDK | 14, 15, 16 |
Bovenop eindopdracht-eisen (kwaliteit/security/design): 6, 7, 8, 13, 17, 18.
---
## Verschillen met Klas A curriculum
- Geen losse "Cursor Basics" — Klas B start direct op niveau (Les 2 = workflow mastery).
- Geen "TypeScript Fundamentals" als beginnersles — vervangen door pro-versie (Les 4).
- Design krijgt **5 lessen** i.p.v. impliciet meeliften.
- Security krijgt eigen volwaardige les (13) bovenop Auth/RLS (11, 12).
- AI Agents krijgt 3 lessen i.p.v. 2 (1416).
- Testing + Deployment samengevoegd in 1 les (17) — past in 2-uur format.
- Eindopdracht-werk parallel vanaf Les 4.
---
## Open vragen voor jou
1. **Eindopdracht parallel vanaf Les 4:** akkoord, of pas later?
2. **Pair programming online:** breakout rooms in Zoom/Teams of laten studenten zelf afspreken?
3. **Huiswerk inleveren:** GitHub PR, Discord/Slack channel, of niet centraal verzamelen?
4. **Die ene gevorderde leerling** (recruitment-app): aparte uitdaging in huiswerk of normaal meeloopen?

View File

@@ -60,8 +60,18 @@ npm install gsap @gsap/react lenis
git init git init
git add . git add .
git commit -m "init: next 16 + gsap + lenis" git commit -m "init: next 16 + gsap + lenis"
# GitHub remote (maak repo eerst via gh CLI of dashboard)
gh repo create scroll-demo --public --source=. --remote=origin --push
# Vercel CLI + koppelen + eerste deploy
npm i -g vercel
vercel link # koppelt repo aan een Vercel project
vercel # eerste deploy naar productie URL
``` ```
**Check:** open Vercel dashboard → project bestaat → productie-URL werkt. Open GitHub → repo staat er → Vercel preview comment in PR werkt.
### 4. Test je setup ### 4. Test je setup
- Open OpenCode Desktop → File → Open Folder → `~/scroll-demo` - Open OpenCode Desktop → File → Open Folder → `~/scroll-demo`
@@ -228,7 +238,7 @@ Dat was de theorie. Nu zien jullie het live."
--- ---
### Blok 4 — THEORIE 2: AGENTS.md + opencode.json + plugin + stack (15 min) ### Blok 4 — THEORIE 2: AGENTS.md + config + plugin + stack + Vercel (17 min)
**Slides 8, 9, 10, 11, 12. Achter elkaar, zonder switchen.** **Slides 8, 9, 10, 11, 12. Achter elkaar, zonder switchen.**
@@ -290,10 +300,11 @@ Framer Motion is uitstekend — voor app UI. Modals, page transitions, micro-int
Daarom staat in onze AGENTS.md: 'Geen Framer Motion'. Het is geen anti-Framer, het is pro-GSAP voor dit type werk." Daarom staat in onze AGENTS.md: 'Geen Framer Motion'. Het is geen anti-Framer, het is pro-GSAP voor dit type werk."
#### Slide 12 — Onze AGENTS.md — 3 min #### Slide 12 — Onze AGENTS.md — 2 min
**Vertel:** **Vertel:**
"Hier zie je onze concrete regels voor dit project. Let op de structuur: "Hier zie je onze concrete regels voor dit project. Let op de structuur:
- Why this stack — context voor de agent
- Stack — versies expliciet - Stack — versies expliciet
- Hard rules — niet onderhandelbaar - Hard rules — niet onderhandelbaar
- Patterns — hoe we organiseren - Patterns — hoe we organiseren
@@ -307,9 +318,35 @@ Regels die opvallen:
Deze regels heb ik uit ervaring. In de volgende demo zet ik dit in, en zie je dat de AI ze volgt." Deze regels heb ik uit ervaring. In de volgende demo zet ik dit in, en zie je dat de AI ze volgt."
#### Slide 13 — Vercel + Preview Deployments — 3 min
**Vertel:**
"Laatste theorie-stuk voor we gaan demoen. Hoe sluiten we onze worktree-workflow goed af? Met **Vercel preview deployments**.
Het idee: je hoeft niet handmatig te deployen. Elke `git push` triggert Vercel om automatisch een deploy te maken. En het mooie — voor élke branch krijg je een eigen unieke URL.
- Push naar `main` → productie URL (`jouw-app.vercel.app`)
- Push naar `feature-hero` → preview URL (`jouw-app-git-feature-hero-jij.vercel.app`)
- Push naar `feature-gallery` → andere preview URL
Stel je voor: je werkt aan een hero-sectie. Je pusht naar je feature-branch. Twee minuten later heb je een live URL die je naar je designer kunt sturen voor feedback. Geen 'het werkt op mijn machine'. Geen screenshots. Live.
**Voor scroll-storytelling is dit goud** — animaties moet je zien om over te oordelen. Preview URLs maken die feedback-loop razend snel."
**Setup (kort uitleggen — komt zo in demo):**
- `npx vercel link` koppelt je repo aan een Vercel project
- Vercel installeert eigen GitHub app
- Vanaf nu: elke push = deploy
**Combinatie met onze workflow:**
"En hier komt 't mooi samen: 1 worktree = 1 branch = 1 Vercel preview. Drie features parallel? Drie live preview URLs. Verschillende stakeholders kunnen verschillende features tegelijk reviewen."
**Demo aankondiging:**
"In Live Demo 2 ga ik niet alleen onze SmoothScroll bouwen, maar ook pushen naar de feature-branch en zien dat Vercel automatisch een preview URL maakt."
--- ---
### Blok 5 — LIVE DEMO 2: Setup + Worktree + SmoothScroll (15 min) ### Blok 5 — LIVE DEMO 2: Setup + Worktree + SmoothScroll + Vercel preview (20 min)
**Slide 13.** **Slide 13.**
@@ -411,6 +448,45 @@ scroll storytelling. Kies GSAP voor timing-precisie + GPU-perf.
**Zeg:** "Zie je? Met goede AGENTS.md krijg je consistente output. Zonder zou de AI waarschijnlijk Framer Motion pakken of useEffect gebruiken. Schrijf je regels op, krijg je betrouwbare resultaten." **Zeg:** "Zie je? Met goede AGENTS.md krijg je consistente output. Zonder zou de AI waarschijnlijk Framer Motion pakken of useEffect gebruiken. Schrijf je regels op, krijg je betrouwbare resultaten."
#### Stap 6 — Push naar feature-branch + Vercel preview (4 min)
**Pre-setup check:** repo is al gekoppeld aan Vercel (deed je vóór de les). Open Vercel dashboard naast OpenCode op je scherm.
**In de feature-hero Session:**
- Type in chat: `Commit deze wijzigingen en push naar de feature-hero branch.`
- Of typ zelf in ingebouwde terminal:
```bash
git add .
git commit -m "feat: SmoothScroll wrapper"
git push origin feature-hero
```
- Wacht 5-10 seconden
**Switch naar Vercel dashboard:**
- Refresh — zie je nieuwe deploy verschijnen voor `feature-hero` branch
- Status: "Building" → "Ready" (duurt ~30-60 sec)
- Klik op de deploy → toon **preview URL** (iets als `jouw-app-git-feature-hero-jij.vercel.app`)
**Open de preview URL:**
- Laat zien: ja, het werkt live
- Scroll erdoorheen — smooth scroll werkt
- "Deze URL is publiek, je kunt 'm nu doorsturen naar een designer of stakeholder."
**Vertel:**
"Zie wat hier gebeurde:
- Lokaal: ik bouwde een feature met OpenCode
- Push: één commando
- Vercel: automatische deploy, eigen URL voor deze branch
- Designer/PM: kan reviewen zonder Git of localhost
- Iedere nieuwe push naar deze branch overschrijft deze URL
En als ik nu een tweede worktree maak voor 'feature-gallery'? Krijgt die zijn eigen preview URL. Drie features parallel = drie live previews. Dat is professionele scroll-storytelling workflow."
**Optioneel — toon git workflow visueel:**
- Open GitHub in browser
- Toon de feature-hero branch op repo
- Toon Vercel comment in PR (als er een PR is) met preview URL
--- ---
### Blok 6 — Pauze (15 min) ### Blok 6 — Pauze (15 min)

View File

@@ -10,15 +10,21 @@
## Doel ## Doel
Bouw een **kleine scroll-animatie landing page** met OpenCode, gebruikmakend van AGENTS.md, opencode.json en worktrees. Deploy hem op Vercel. Bouw **verder in de repo van de les**. Maak van je eerste sectie een complete kleine landing page met 3-4 secties. Deploy + previews lopen al via Vercel.
Dit is geen design-opdracht — focus is op de **workflow** en de **technologie**. Een simpele maar werkende site is genoeg. Dit is geen design-opdracht — focus is op de **workflow** en de **technologie**. Een simpele maar werkende site is genoeg.
> **Doorbouwen, niet herstarten.** Je werkt verder in dezelfde fork als in de les.
> AGENTS.md, opencode.json, Vercel — staat allemaal al. Je voegt secties toe.
--- ---
## Voorbeeld-thema (kies zelf!) ## Vertrek vanuit je les-werk
Suggesties: Je begint **niet opnieuw**. Open je laptop, ga naar je `scroll-demo` fork, en bouw verder.
### Thema kiezen (als nog niet gedaan)
Heb je in de les een type sectie gekozen? Bouw daaromheen je thema. Of kies nu:
- **Mini portfolio** (over jou — projects, contact) - **Mini portfolio** (over jou — projects, contact)
- **Restaurant landing** (menu, story, reserveer) - **Restaurant landing** (menu, story, reserveer)
- **Product launch** (features, prijs, signup) - **Product launch** (features, prijs, signup)
@@ -31,24 +37,26 @@ Eigen idee mag ook — even checken met Tim als 't iets totaal anders is.
## Eisen (verplicht) ## Eisen (verplicht)
### Code ### Code (bouwt voort op les)
- [ ] Next.js 16 (App Router, TypeScript, Turbopack) - [ ] **Doorgewerkt in dezelfde fork** als in de les (Next.js 16, TS, Tailwind)
- [ ] Eigen **`AGENTS.md`** (mag onze als basis) - [ ] `AGENTS.md` aanwezig — uitgebreid waar nodig (regels die je tijdens werk leerde)
- [ ] Eigen **`opencode.json`** met permissions + (optioneel) plugin config - [ ] `opencode.json` aanwezig (uit starter, mag aangepast)
- [ ] **Minimaal 3 secties** met scroll-animaties - [ ] **Eerste sectie afgemaakt** (uit lesopdracht) + **2-3 extra secties** = totaal **3-4 scroll-secties**
- [ ] **Minimaal 3 verschillende animatie-technieken** (bv. fade-in, parallax, SplitText, horizontaal, stagger) - [ ] **Minimaal 3 verschillende animatie-technieken** (fade-in, parallax, SplitText, horizontaal, stagger)
- [ ] **Lenis** smooth scroll werkt (geen "harde" scroll) - [ ] **Lenis** smooth scroll werkt
- [ ] TailwindCSS voor styling
- [ ] Werkt zonder console-errors of hydration warnings - [ ] Werkt zonder console-errors of hydration warnings
### Workflow ### Workflow
- [ ] **Minstens 2 worktrees** gebruikt (mag via plugin of handmatig) - [ ] **Totaal minstens 2 worktrees gebruikt** (de lesopdracht-worktree telt mee)
- [ ] Elke feature-branch gepushed → Vercel preview URL bekeken
- [ ] Branches gemerged naar `main` - [ ] Branches gemerged naar `main`
- [ ] Worktrees opgeruimd - [ ] Worktrees opgeruimd na merge
- [ ] Code op GitHub gepushed - [ ] Code op GitHub gepushed
### Deployment ### Deployment & previews
- [ ] **Vercel deploy** werkend, URL in README - [ ] **Vercel gekoppeld** aan repo (`vercel link` of via dashboard)
- [ ] **Production URL** werkt (deploy van `main`)
- [ ] **Minstens 2 preview URLs** (van twee feature-branches) — Vercel maakt deze automatisch bij elke push
- [ ] Geen broken pages / 500's - [ ] Geen broken pages / 500's
### Documentatie ### Documentatie
@@ -57,6 +65,7 @@ Eigen idee mag ook — even checken met Tim als 't iets totaal anders is.
- Welke regels heb je later toegevoegd aan AGENTS.md (en waarom)? - Welke regels heb je later toegevoegd aan AGENTS.md (en waarom)?
- Hoe heb je worktrees georganiseerd? - Hoe heb je worktrees georganiseerd?
- Wat zou je anders doen volgende keer? - Wat zou je anders doen volgende keer?
- **Screenshots van minstens 2 preview URLs** (Vercel dashboard view + browser view)
- Screenshot van `git worktree list` tijdens het werk - Screenshot van `git worktree list` tijdens het werk
- Max 400 woorden — concreet, geen fluff - Max 400 woorden — concreet, geen fluff
@@ -76,13 +85,15 @@ Eigen idee mag ook — even checken met Tim als 't iets totaal anders is.
## Tijd-indicatie ## Tijd-indicatie
- Setup (Next.js + deps + AGENTS.md + opencode.json): 30 min Setup is al klaar (gedaan in de les). Je begint bij sectie 2.
- 3 scroll-secties bouwen via opencode (3× ~30 min): ~90 min
- Polish + responsiveness: 30 min - Sectie 1 afmaken (als niet klaar in les): 15-30 min
- Deploy op Vercel: 15 min - 2 extra secties bouwen via opencode (2× ~30 min): ~60 min
- Polish + responsiveness: 20 min
- Vercel previews checken + screenshots: 10 min
- `WORKFLOW.md`: 15 min - `WORKFLOW.md`: 15 min
**Totaal: ~3 uur.** Loop je vast? Vraag in Brightspace of plan een korte 1-op-1 met Tim. **Totaal: ~2 uur.** Loop je vast? Vraag in Brightspace of plan een korte 1-op-1 met Tim.
--- ---
@@ -114,11 +125,12 @@ Eigen idee mag ook — even checken met Tim als 't iets totaal anders is.
## Inleveren ## Inleveren
1. **GitHub repo** — push alle code naar `main` 1. **GitHub repo** — push alle code naar `main`
2. **Vercel deploy URL** in README van je repo 2. **Vercel production URL** in README van je repo
3. **`WORKFLOW.md`** in root 3. **`WORKFLOW.md`** in root met preview URL screenshots
4. **Brightspace inlevering**: 4. **Brightspace inlevering**:
- GitHub URL - GitHub URL
- Vercel URL - Production URL
- Minstens **2 preview URLs** (van feature-branches)
- Korte note (1-2 zinnen): wat is je trots-punt? - Korte note (1-2 zinnen): wat is je trots-punt?
--- ---

View File

@@ -2,7 +2,7 @@
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource) %<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj 1 0 obj
<< <<
/F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 6 0 R /F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R
>> >>
endobj endobj
2 0 obj 2 0 obj
@@ -17,6 +17,11 @@ endobj
endobj endobj
4 0 obj 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 << /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 ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -25,14 +30,9 @@ endobj
/Type /Page /Type /Page
>> >>
endobj endobj
5 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj 6 0 obj
<< <<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font /BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>> >>
endobj endobj
7 0 obj 7 0 obj
@@ -72,42 +72,42 @@ endobj
endobj endobj
11 0 obj 11 0 obj
<< <<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260518122502+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260518122502+00'00') /Producer (ReportLab PDF Library - \(opensource\)) /Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260518125917+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260518125917+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 2 Huiswerk) /Trapped /False /Subject (\(unspecified\)) /Title (Les 2 Huiswerk) /Trapped /False
>> >>
endobj endobj
12 0 obj 12 0 obj
<< <<
/Count 4 /Kids [ 4 0 R 7 0 R 8 0 R 9 0 R ] /Type /Pages /Count 4 /Kids [ 5 0 R 7 0 R 8 0 R 9 0 R ] /Type /Pages
>> >>
endobj endobj
13 0 obj 13 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1631 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1980
>> >>
stream stream
GauHL939k-']/^ggg+VAW0[@EB@iG>3FtfU;n?Dt3_CT*>HTQql+`i@^Crp>9Gs"W+BeR/rU(UQf_D36B:K%9T>K=r$C&AK%b)dW9[D[BCAkFDo3(!t5TFZ'+Ti?<jp-ctQ%A*35@bo@D?kh"0"@Dac9i'Mmf`_$TF`f,@o4A"D[5&$_Pu^>s5&0Zpl(n[Q[$4dDVf=)j$p_DpoS_<B(JJG4@*1L4f[NTRsKVjdhE9U?X0/<8$rn>]MpA3n%')#;StA%!PCKUHrlW>e7&VPi`7<S:IV-f_t4/1DiCb0^?^GCQ3ZaNkFr(`+"LLNM.5#NIYegqMh$h,^7n=`+J6'!q@4f;kW.jbSHcQN]U)f>i&+QD+i-T<R-g@j,O>6^@DgfSf]j9;3lL_SC@2RY5&gWB6B=TMfUV5\ofKE9HCS*>Y^XV6d-O&#CtYaVJtMH+p<2f3$sH:3h+T6[bX(Y*6cH$WL8-X**-^NfU_E5PG8:aZgE%HHZ`lUC$gW!FpO:M2ms"+!boVQ:BC5uJ,9E\O%`DVh4;q\3F=Cg.9d[M95oIC.((;bFB$2TS0uI_`0F%YYdC+[H'Z"qMG%&%T!-O-kFbt'\_M0=He0ojGmo=K82ZeY;L^_G9:e>[EAMT7P'Tb`99P+[RUP3qL9+X<A<<fd?I%\f0cXm'+?^=t^mL4^$fsTtD05]Nn)\JMh8?-s=(DMf\6cE08"f^hJ_,[l=HVNb0<Zq[2Z,+0,F<18KMR5"]#)>F$`'p7'BmfD0Q$jrp:jJ`)m\%G`B2LZ%d4CEJGaumi!]HEN.94/W^o^3_R.6LG;s(Yd*2cAuZV20M<lHDo5?((NMX/m+Ld$a[=dEG`'I):akd,N*9)Z$k$/[D^i@h\R74aUFn`/X"e&hF)&!G6+ao8lF6_Ck02G1uo[V[;XDucqCGMrJ!E-[>1GBFXB+U-I.gB%WeI+o.pnurAS3Q%p9_sa^5%iVO/.OC5_m6!r3S2H-6f\7Ne0mHtL"kL^/-=\BGQ:XW$L5T;I*u%3&OXO:7jV(aPT1(&-BsbauK\Io-GGiKRVr,j"P>;&YL"BeH7W(h`!Qr#Q`<t'kPQ[->7/_k:83u@>f?#)P)''=H6FNRI:`e9oN@Qm-&sYsmAtkNJlqlfM,3gP^\VdH9*'p-WDQV6H&-k])H(s`\rD(?5e9L_JCFhT99%61bbo6eG9n/=?/eYO2'GY":TLnb3I&5>e;)_E_Z%m<=mesCM1r\XU&BX98UQ;-Njb)RiL<+Tn'#/WXjO!SHI5TkfVlj_F4K'N#-I"g)agr#871S&f3WI/o6EUYY=,BoNr-s-fYXH?fWgYoi$[!\'>tuMCR>taiUFu.8dscTASZF40^D*N)WZoT-<una%!(VcY!i8r:mg79rV'p58f[Z/[W6)e:ohO'OWIJVc/FosLGe&P%>XZ.i\eB1t]YQ7"=K6`rmpRJdH/>*n-h@80ZFO]HVB`_W*!+c=E1MBE$g[nJ@3%#?X@e.crPFMV]04L>7ftl3s,#6n-qfBe*"+USB"^@mQ_I:i(kP"_WlS.N1.o\@(3je9@=G^^:Z"?'*S5f0aI+a,A-#<1rt@)("*@4qXefps;jd\:#-Y6E$=/6a3f4QcS44l8\(PRR@/UJlcQC[~>endstream GauHLfll+.&:N_CbY(a&<(7.4moq[tDOib,'^*Fg0Ek%T8:oeETChG2+MgdAAN+>i,FT<#1O!g$c7fr+r1WdrQiOdf_M^2WPehGOe0[(I0Au6IphgZ*1=(,o=MAJQ$qOd*Iul=B'HEIL!=L=NVFQ$[#2?&opB)KSmdiR+5qZREgn6npfCXG0Lef&CGIR9M`+9?_gVe*f?us)CcWB'/`7<7DR*@9&_\H.'lE"/I0ra1n0MUKdj5oqo]U'"'KM*b(pfcf(>aB?HQj5:j:L=l+<0UhrDo>ht1.t#=5(i)?9V3hB"8fMA+B[M?gV4:'0R-qn`P'>oNS.*T&Z$K2^s6T:Bkte$3`+G&`![Omcf/?L%2\abL@$uqQ;*2*-6UNSeMi-I]=u8\HP]#D]:m^uVW1L\[t!NQbG,)[egLur<LpJa8SAOHNXbBWHXc^"bhcK9Ql&;OT6e2bW2I^&@qjjU'C,J=6L5,[@b%:E0cN=f41!;0jnV:1bTj!'3e!-\"aX23XF$\GG;gQ9mYTV$1OEd7)7qZd&;O/Up:tL]*#`4eZmDSuRS%gEL-AjB/4_=hX(B(SXDmH-bQ8;B1efV?0XrHI9B1!me.P&_M6IJY%7n:5$373BP1;^s76VAF6n-mtLdc<PMbHI[Gn*0:&BZ8V7>YCAF;C\\aS8(a=>]6e;ken51L/:l^LOTf_0<eu^M`Xq_4!4megXn:ha@js(<"l'NqERtVN@c&CEp5lSm:m;T;/'7;$G#mQ',Woe$8:q"`!DL"Nu9-LeBjd8=dT7:lb4Q!G.<'++V5P-UE#&pahG'nM$,9*Gck/Q845`J@1=P,+[9.L7>c7XJV$'dk[8r.P)eFYH,<tq9?655bE`$4H5I8\O_b]qBVU"U@N_T$X^(ii"*=5Gpm-MoBl,6n$@qqD^:`fq>S#=6W^c-p.a\Ee85n;^Au6U?*HIP>eS%1pU)F'OFZ^&=q;d20=q\IIaArp37<bKiHEnWk74@l,t5I*cb2ih)L*6i<RqeRg-4&>,LMr_46[:QUt8FR>-4E4?N6"ddq`/t-NlVP^8`!Bi!PUAE'PR]*f^AIL"2cTFVbZfJoth;+A\GS"[V+h@eg+e4j1f*'))R&%&#O"_`HeG).[Z+qROfl-KhjBJQ!2Gr&.Tc_@j^Fb1(O3)F!k&[qkb]Mfp,a+#eRZ\c"*`*Ai8G2Ka5D+ks[BD"X?Ar&_j1G_riIHE>It9P#YPXT?0pon)>TXQ'^t$/_=ACA`fQY_@c6B&K.L@;Li3G&.dJbgX8]*JAp"2bcY(\mZbI.,.$E*$PSTA%HgOB><H7b\rVj,+Ut;+Us`p3BSr`g+gh.A?.cW!VQF)TI,F,L4&qi`,fJS>iK;<j$Jqh<ht';md8'Z(DMr;-o-Ks-HKI90`nI6[?+i3]YpqW83#%WpG>eYf2=.lc0k^+.CMgX=,eccqe=c7&S!Vtl`Q,fG1P=`V"Vb^&A@No<;E1p?k2HO.^rBP5":t!V7PPbM+^j_.SQG!17Q(22a\HHdYk5NE=q=gN_a:3OdgOZF.E<"]'Y9drAr:kB#,ngZq9rQFak(+WMg:,;a4P31LLq@ePbbMMI`\k1b;sh)<^78cJ'c]1[-$VUFX]HG02q55J'#Q%J@BI6/j<M]E,CRp2boSilHm'N?FF13L=`mI>0[.&e>K?ACXXaQHM?@W+(A'+t\(B*lss<(i+Uarmo[GnQuS:OXfNr,eDh!a8le$eHFDlk!(2k_hH1`fKBa3n&O?n&&%Z/8Ec\7+V\otBt2q!HS0PG+l!AFHZY%0,$LdBFqAj^2#hmWXfk7SIP'$Z)N<5!o)`l%eo+5ln3n]!$6("[%[UX*`BkUD6G71U5'cid-K%Jjc_<0070,No+Q<,gPe1(ZD-fu1-UETc&g];m:e#Y>?uI]pUVZA\D]*I<I#3Gg-?'%J$]n9d-^m(S^k2qpGZD*71eR$ViqF'^h,V3Cl^7b1^iCDHMTsEK`kRuQj\>~>endstream
endobj endobj
14 0 obj 14 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1724 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2005
>> >>
stream stream
Gb"/'>BA7S&:XAWfXFeHe6);?]UUEI-prPQ)sBq$<*QW`.$B^XQLa@fg5b!r\M.I;\9*HPPGGLkg2AX:G"'3_(MQ's3P`fm]Qceo:`)uZGd[k;;`Gh&h^k6$2"Z1"h8jSG$?kdeJ"SSjJfiW29Oe-d'A?=IJJSn[MW+miXp[T0K'"b8D@s+j^^24rm"kF&'WeE\m=8*ig9^`#L``d,l5/"9`HJK8%b5CQ-k'aM-)b.Mle)d<6Qa<h3;E:5lT7(')6]3..03e7k"0Gu6>6]qmgbDYWi->%iAJC46BuUEbqg2D_>&;hf8=t%N=ft@hNr`7/"gje.&n5hB<Bg9=]S&tF@FJfZtUm9TM%^@-aI%0$.0PN%;r^iR)nq%07^T%ICoI2NsU`![c;V[ar$,(ZTO_1!F6`9]/b_uO5UT^&Q4PPLCgU-qp<N08NWhOTGpEu_="e:#X0PY&"R$ge*iH%cCG^Ps65eR0(*/L+A])oC1=2l[B>#LVN.2oGQU$2L<#1pLh"ni:_sD[FX'+c>Xhcl@[#Be&&23,#EAZ6VX*e9CAT9r\@j9i\>ZoR,N4n[q3EGm_po:14po#,:@l!M^4t!N:]&B&ejh\:[;0s(cAJo@j]]>%;_,4HpO`bY_g=GIC5H9h$2SI\1MGZMUm(](5P"9_^8bp(:(E<,'$f"DE'8gCD9:*;?'GPC0D)(j=YcI/gStS>--h$c]?SnQ[W:etY#qKCIYQ-djPT?PE:il!j7qE$Voo>$dK;>KS7SAS%h;94E643%g=Rib,4&'fgF]qVDo(KoV_Vqh2/s@,9YkqA&IgXc't_0Y(F,rCb6/Ic_b%s-jE#43jEb,TTds:50E<.rK,L?:CV'W2-3JWDA2.^j+TJhjXZ#1@'!6gHAG,#4dUpjT0HB:oY?qN_br)*EKb.LdQl&JDLB`fW=n!;9GQ6`3b#3[ja:XY11EkEF&Y,2^0OnXd&`cP#5EmuQIoPT+Q>o']Nk[AMg]7_igM9T[ST7l7#=gXQb2gb@J@[Kj4(-J;H1c'Y]oJ40/&Y.*doY_qE35?o(Z($9B"LkK#jRa1^#_e*$$):Dr/b]rF9QCdq/*,dg6I=1VBo\V:[?=;'^7,?T4/sM(Kc.oaoMlB3a=srU%E.NeBQ<s9$`F-5hN-7HlC$.2Jf[:]5Gt6f["Co*A`/D(WFTLl`3o7YU+(jMoi=i9X6$6r#9FIQ^Z#Aa6kbDF07=c?mT<o/"4,r&p%*pVbR++SWc@)G\AafAJP/3=heh\&IH*%bfSiW]iQhdS?G<6b42&:gA((7+o+DqhAqX;?h70ogBTZ7)en.<8KS=enbcnXrLQ.OQ0X)28/l'?&U[m5@WO5T05%38.`9s0dH_=`:RN&gEpusWKS;!OL4&^sI)l%uO7LW9<,&L2s%pUpeDLd,%4YN1`)*q\5J(6F;;3]Ap<LC1:M9.RS:8'%FD$<lVKo-N2)^lq4iH5^;kbWh&k=I`lp4U3EEfhhJ\oBb6`JVK1f`:HUUr;#dslhQ>Gtm1VKp0M"Za>N.h7.KY/)M+]Z-!H(mE<RDdp9%lb8XX0db4i_1.D5@o)kIMKc#r7K_s6V)'GQ,Iu[qhoBCRp@U'pn3\dBJCkTV>sYL;IFh)%c+Vj?1%'YgHPI,3];5$$]dBd"M<^PO[I,Fn9^c0s7:k&[c4j>oh/)6/1o^G<.uY#*4*D,E,$$bl:6\/gg.#\-ToEP\Z'\:q<#f<E#COFtk5~>endstream Gb"/(968fX&AI`dqQu.o;"U(+Wi`C6j2@p>SgNu]b9/ZOCe6u`"!mm=gS5Q%0F9b$:_dIB*bfr3^Zk"^\n-Fa0<9J]14X")Gk/]QbD+sY/D"iLGR<QNcKC3rqf^nD?oXkE$u@U%NrB'IFE6ku_]RM5$UQX'_S?=6[#?[YKOE9A+Ktp/#a7b!$]Mk[K?scHZogSF\)oc,DqhY+CO!QXfUQ=7(M`UIL0n^dC0&(pN:*obUKXXWCRCD(GVI@7qr[:D=)];q/>B*T.=>)-UT&j-D$TS&<W?c1n3UOlcZ+N8Qt;Yc1Dnuan<%$m9top%aOi2V0E2XmYi0':M^?O#9'eX*n=5'C-KdO#g'I9:>1(+o%EC1*g5EXpG8pZ2FBm_r+m[dP;13<%dZ,=bVWihN@s1u3jVh1XgZp(jF&1;"a68Er>*]rS%["<I^k5[r@o`91$8_hs.G0#D3u2,0<Ys"]OP-cLFj-*d.K.^F=\iq[=L1-T9KT(HkuY:c:L7_l.@o'L_V.0([`gK941&f//<]=P?<D*5[C.`C<Pc?D\L-2ejUVcH>J=klSi%Y*]dNiM(RS9qn?7W[7bt>:0,6$kO:&o7;Ck:M%ld)F`Em5ecLJGTQBrPmZt6(7G'dVdq'#!3<cduEe=$gL^=KkP[1ZY4JaUSdJpTP]CIt#]=gXHp`O7'nE`R2e_!4uJd!\Yk,cHc_!hMc<GF2,2X,2qIjf0gU]>2jh\k+,:E5'.Ab.&m6CO.\IU-HZH--tT\:uC/_K`F(N.a;-%K(AQqq4$?.Oi?&\Y^D:O4K5u.c;fI+M*-M=@hNeolUtN?#ng2l8%:i]r$KFrJ2/aRLbcu`!)X_#)F2PN#6H/_Qq)7oB_+[,#G`sq"GL;_U$P(b&q)@J)(X3C#X&-3&2\/C#f/Mn8lnhsUhH7N,f,GE>I=W(K`R_\0oI?QN&NeMa&([E8FG2[#O!j5<XUIejSTHZ.)s];WD;?),*F-Eqs/<I"7(W\"1D@u5[[sjS7gPP#8Vu42!&G/3Q2u8a2u'0,3"]*]Z"a>)g*MZ`]h^q0Op9B.JqPGX'*TqOPoNB<(MHcEXcPT4(aYK*8.3uf,O*%`c:mr_Y>G>I+r:k9F*MX]q^NNC7gna['Ei72.<D!lb;_P8Ei5LGp-)PUQe(\+=8f@8dA"/O])gW3R<oV"7Ub,Od>fgRS.B>oUGUrOndVM,C\7_@j\0WIZ*<uDX.Q5Oh;>uf#&tUA3?cCQ0To68YAoU73#(OQT<rk[ORTpA?C;:#8F$B1>L2aIRXB'0UeN0[mY;"WO]tH=L(&X&fe6'0r6)$`l$WZa7fE?V!9K"$"\c=bTnf#0&biG9,LT^C3%fQA7KDAo/nOS.6StEWYAp`2:J[)E#O5J(uJuiccDaa]#/Z;-H(j6_45/l<%*_ijeQ$[^/XF4jQ^2<DE[pp;l"15#(8PIF6$2>UiX0gq&F%Hpo59Nn-?V,,$=B;K]tI&lP9_:bS-rBl<p4AXdI?eh:Vu4M).G3E\e=\WjsS)\,.O@<Yq8lb_kXe0Y+qonnTP,SN+<H,+_:NoqmDOAT$$udS*")(G#j!#P5sh]Ml#Q?E]19)BIJ5c:2*!Lg&db%FJJ'l=@32'"[5%.N$2'##K@V*PbH2,F)iq=:4%W<2:n+EM%boRXZAT[b1c;-m)7_c:Q(#8a[13P)/6],O's/[iXGRp:&mtn7fRbqr,:+QZ''OFaJBKjnhp]?1Or/*]t'P>1??p<kA5":C@XYa]/>(,+GR3ThU(J&qTR;^KAPVLCC6\p/;ss^4"-<8l1*D(\AK0=hfAA5iE[RZL'<NdFg_TIn#@S5k?W32HI$U#)#ZP:1k4$hYfTg@fMlG\.`q#+Nn8Z"iQ#$D^!Q)ln.+XNVcC3!A\g5dB;TM<7&b:`$5(r)H,&?QBoBtU9=qURi&e3e]S$U$.6&LSW":I4.0d!<U9NmI^.]ACS'Q\;3djDPW@B3p4Wj#P:RJ)DK9V?EfH4HdF"ks8NNJ;*oLSUq&b7^j<X~>endstream
endobj endobj
15 0 obj 15 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1334 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1415
>> >>
stream stream
GauHK9j?@A&AJ$CbgB#B79Y,AEbHOcJ>@==J4_H.#\blo.r1DUaj;+(m@BMQ@r,Z,Zk"!-fJE!?]^ohV/:?*pmplWA'#Dt`%5roR%(8f6Lk]jeomZN=okE?15TFAr+U&K>jpP/>/&:b5+4AL?Ad*hk0"B+PN.hZgl32_ZJDr83hm7k:^(i=<#dZ[dq&B/?nL6oU0(F<R>hAHBA&c3DpmlWmB)@Qq-t.<c/Q!OT=LK#j&C$ktEI%Er^;u9!A0PRpIoM:QP6sdD#&sK_E3-Io'i3dETCZ<R1L=&L+IYXhhUl#JGF]:tA\iHc+kK!<9.fd!=+@Z_=)fRE(n;>2Doh/EaJYT(!VHcj//NB4S+7:I/F\YTUGHG_iIs0]l^u7mehHC4Z4aJ$4<M7`Aq&9],u\,@<ueH5[p*oJ=]eC$ct-'*oGQ;iFdUYGdchOSFa@R'_2DV\2XtUQfh]UG&R1bQ>XH&@PsPFLA@qnJh[SR]Ok$A>qN19Q.)a3s*//F-JB&mtaC6^+,?aR]OXWM@d5qOLm@-tWY]V&j^ONfrc./>bNSl>/kE6d97fBqZ!=ZX#%oiUN.2C@oTTXpaXuN44d5u*"6#^61rYHUk[LVBWr9!b]icH4X#u<d'E&!JIr/L6e3<u*'RHH9a'pMf55FE>Q]+6%)E)YnnMNFXr?!YEPee,Nb(sd[n@MZB^,j&%-.li5*08,('FQa0[pd</g0S>LgVI";Gaq'Tb.Q.VqMo/tAa2,XDJK,(e2Q8%/egC2jnW!<nL<r8-+O75U>a'O]'"oN'qM)nM^2>i8o<cMDJ=/5C`-&+RSi$9!#_1>W'd%Ln#HCcBZq<FOre@cLFec3I%Pi<[19Y\'DhO,EJeHOX8'%\8hpsnmmW6_Os7U^`VWg>[WCX88q#4:]>40/3>o5_Kj^oI)`PoB"*X`jeX=Z1Q1j>Glld),&kjJZbkUUUX[MP:uNi>$Rkk\/_OpN4;A^*qnS5`rM.P>&;"J#P=<TJ;%E,Mi%\AE)c@G4bHek`;bLN0@X-uX?1Qe6Br\7ir_is&,$BqC@2:G8F]1/%0+p.Tb_JQr*=0HCO5U=_*b"85P9eEsu$`^q<BXM'hj4ncs%`3?b,p=S\m*lFQB]V(CuP.ApQ%)r\tgcGRaNGH5J7&R[Z,-5l2oU2&Vr6(&e>HSU1mt'o6Ci:,3fZ8HT]`.;7Gkp0+A)![/"s7_Q2nf,>r_.[WArJe'ZkP_Z3R)1aL2@@Nk<7h@rN[qd:N^RSc/IgHOlV0ZEEqBW@puu7^m5$,&(*pKEL_N37gJ<_Oc:5>PfWff_`ZN#06!uO3&>dPM)$S_QZ9mag15im59UqYmJ~>endstream GauHK>Bf'r%"@A@k`O[R'75*GmmDH-gV#L^Z3edin1rVtL,C#r`[3]oaD\80JZqD4CjUcE*'hq"IF:Zl$P$H[o:@K5h@/B0]aHHJi<3DXq(TI3GJi9E4iTi1q#N0F3*sg[`ErKC1[X98KIi(-m"01#-6aOZiA)Qc!M&eVaGmtX9O@Et?r?,U_?VFC+;Edp^;iRc$/0#X(rk>,SU[AW04nYU*P'ieaV[UZ>u5=;T$JB8L]SDGT`<'om3h\-VeD9^ef"Bg)GF`hN&ZmVr6sTZ>%L?#iE'8nVMbW?jU2pN@JI.Jg9C$m0a0)%n*OJT"V)V)AadunCX$2-WQHN3)g8q,V^k+Xmi7=3a@+t]P`*GDDg:5+=Q?!d-e92BGa#N6)kC%G7X\R,9';#`kf%c8RguNf$=^a.JCFGP_DPo`1hFMT7Bt8Wi4@3J:A\h)>GJ=L,<(LOo[7e`i4?dR"P^?:g3L'KGkX$nkeC%pbe[??JXimKqA<9U;Oj[jNP*%_,c>[-3+Ifj-Ydt)$d99Tin9OP$(tt(NIhnrM%'=]p'N@>-(:C_98uT(2*n#:GfbI;0Ccc8@&^PbWYgtHONJ1ZeVQFc9*1h$lhA0`YeiG`LCZ?,=!5tgbS*ZWmr1#17f?'Ob7)367V<SiY_7U#ihuX.CA\G2"E')jPsZapq:CpQ3phaZ:J;<`+#,:I'8_!=fhsK]!cqqHD&meT*kX/QW4F^XC$I!3;nR^h19sq?=<*oW4q:Uq-ZX1"VR(<?.pFB.)r-;MB8@QP#@p<siRc11.^951k0M["q2b3gCJ66>$aA!#$>J;L!g=tG45l7sKu"'64#!ebTN+%?pLiiP5Lpb-iLlB].'e`MohO/H0,QQRG(a[Xn7Be,ofNb:s)4J>eN=kN4B2\.1457=7r6mO2R1l-mNpTcT'.+EWrf4'a:,do;Ar1('N6HMZ'Z_G82Ge4B7quP=XB$WS0glqY0kO<I*qH%^LS[pp&'f/E^5#RQHAu,a\^g^O^W7CAU:TZfkJFa;8X"G!P?'*C6p$&WEHe,p<'e*\r_@Uc,P$(X=mS1lA7[SMeJM*?IoN+lJ5O-1s8NAWXYflKeGUim^2<J9q-2?jCBFK2:Fg&(32hWZ)nuqR&4&I3S,-jK7W4$6KS++6_G*!bAeFfV3,'2=W$JiNo66r_J348K%Lre#S4cU;'U4[1M(2Urp.j#6W$]*5\lEGFNXM(!Yp#5FGhK^%&gWf(#E(aAsh)$2B)m1DJ-OCL=_i6&)tQfbh(&(hqh^FIV"FZDen*?54125A\C:bTBS6%0inYKd6Zo:O68BEH\.89>^ttB!:JDcFn&T2p"'UiSo19/,adbM6/=$a$\Zf/i8h=2e>8D_CO"c6<JKm_r<l>PIZCXs[ep;HNKho'U>M>a8[*nBE4ajjmiTA1R=G~>endstream
endobj endobj
16 0 obj 16 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1876 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1916
>> >>
stream stream
Gatm;=``=U&:W67fU%Pf___DNSa!M)i](YCUQ,u%p][C"$8Mo9+D'+!oj**D7+#K*]!8>h_o9+Z[n?cT^52_]$\6\\*5<db.#`(ppl5mF$TK)d^5j6Pd`;s7nRC*C-od&rr]k*@#smd.#u(]DDWJ`u%t73B3)3qUGV4kE"Id6QM\,t;$Jd-(_21_p`o2RASeG"ZWdQ6#_k_drYqJ#t,u3)pLM#-;#_itfEGCKuq2S+"."H:<LEAa/HOoRE`liJ$<"<7cc-of!f9AaDo)@jK/4E_3.D4V`4BIGmbN_*Hk:h?nFER)$00s72BH-u'QU'Nc;Nr]7Den"O//+1thnf*rA(h!<ZnXD3Tb@[6O1G&[OE[dF+Z+M+hdY)O>c:BNi@.i#:O+R'[\^F5GTEKnV2nP+nJik6-G`3NY0F(o2)5;qm>u<#PB7?3hsIBshN.%#:I1lG?K>Xp>C1mqSUPJ8qE(8%K1gE7\]2%:J/NQJUXH"O3b8m)F^845YI1==(<^0[MVHXint28Sij'fq>00eoJRhk1M'@l:]nA/k.aO0C'EtN?042O9f@Y-gcafR_pEbG!)c$fi]>q-5k\`E1)L,<@jS\B)pkr'EJMF^p5=>]/+]U;JS7@0n$CU#d>g;\t8tMbOf?a2*DLfj%Asug&[GWf?;sqk&-)!S@$a'kg<2*oK]Z?Gls8-mq:rBm"RUA;bM>SV.bH-rB7J]?janS7W`NA\$LHmni0W%-,a@$3Bb4^29J.HCUM7rqKl]gQdjbmC"K4285Ebpl-K7gs*S_U8AQ[c`_piL)Q[n`?n,*'C_e35`!$lr7+eX8Ni1kT/8S-.AHkn9"bG!FMZUmS_.dh3P)gnW-K;\sR+a/WT"hZMD>h<g"9K)Pl0o8uE"R!Qil'PF:UVe:tD%%$-%-\0a9C5WO(R*-J`=Jjt9AGjAi4?j5_fC'K2_R4n3##P?oa,XMf*R^*I4a+Q=I&aEe3W'6C9*\4W>aF%,H%g+O@3Xi1Xi1-lk8D$EL'sNfn[,PL,`_1[8`/URKVuXii;;`kKO_b_`?ps_SF.,f\QG4^"&BZ_58u'g"6E%_7g*-,mafhGF_1XYU(^kW6JfEJ0IQi(L7"1,!csL`H1RRcYi`F,53H+b'Yaa7E#Y5Ae$\nH0S?DB+@j`dD>&CpP"6c8p^&QPVG];SYNs#e\Z.#JT?MWqA.r0V.&i6i<n`CG)nJeRSO(1:32GCCqVO=iUquXagdZ0]@4MHOI_-;]H=X'&&esN=W1\F(<ZHt'+Y.&nDkT*)#c:?TAfk?Kfs%1Kb$_QW?FgUb:;fD;At"1k%r0)c*'0>eY:\!u-Cbt"97*io8#)tO+!W,d%kB`OC?PtqdbmDS7Z`$5mYb"C'#BOg2gpL_2,3*D=E'gNa;Il8PfO@EUfsFn(nOgE0LWuSl&;UIO*&47.+(NkC5ILHYk45)ROAqDUIhS9Xlfr"O[&')/5(G8F(8t+A-L8nS:%d(ffk?mp5[PoHWIGkZ0!U>qHtE@h\D/7gtdLGpG,141b20Sm5&(V(9tLbF.CE(-6=:3:>R$C'fR?Im]O7M1N/73fG&P_`^Za@R,NQDg0$s#r0]FMice#`@[05GoE[X&KS_WYLbt"?!rmHrIkPkjY0^A\f"b0YihW@Cd>])4p`D*\f9UTJS.$(;mkt\=Xepkm*I_u)9mh1<WcuVmp>&';>J9:b3kpSa3<M&XlFN3lRjj^A%N;M&5K.@5#cIX!3PA_W?S=&R\;iB2o'GG^LGN/UF1qt-DO9T`ES5\lbOfL$GF#gGc>'bpf(Ikr.Gjl]nEt#9SSF;3WY.</$+TEgp0##fk:b0r4!sl*bB!;8nf'T`n3]a'57^er!p"OKHDM@ni`"KJ#6Q4UUON9L,Y1Q~>endstream Gatm;>?BQ=&:Vs/R$Sk-!Fs7G4_N(n[$8!l02'db_MdCZ8&:Khfh%M/qAm\$ie4gM]-TC`4Sn4>c'X$>04M]F!oSG3VlpC<$H"qJ^>m-#$8.'cDZbcL2"C`f]\)li$;TTK5LA(K64c1d65'F*)`_p!K-(aSNft;.Sr4d*J`^qG@R._c68sdME+0H5EVSb1kCf(oC>T?70\JYIfG>Lu&gqLs6Sn$eJtW<]S6DV4^>kA!$=HuOiJ3J$hng=tnM6X`Q,Aq\o5mmH/Ys&L]feA+92S`;%'TGXkA"%:=ZqQ!'S\9"Xq&27-0T.bD;npm&h(D\MfQr4I1?(1AO80A4#h_"9O"0O2MO[XFQ1+/0c*%aH,g7.6DO;I2+Gj1L"`=CSo^b#R--eJ)f!!B<tPdEqD?/(cVY=X'7>Dsg>&,u,=CtQ%N-WK8bZWi[lkJGqiQ=,B99+)(HL15efNa_Vk4p<r_BBbi6n0&Y&J5&@)cL,W3g8l:0E68?.TkQ9._Wr`OtE6i\pJ3r27*,\EjL5<jG^t+R.KLU<$eg7o6'djnT4HU0s8EjY8<r`h!ihcK,nWbo'L5GZ+&.4bg((GKZ]2^p=)TQPU`uT:KF"N6*CG/i#D<<68*\NX^FcA=>T!1O1r/H&=N952W9K!@GLKBjq7qN6>1[\MGh&KEAR+\1Rn^M3?ub/>$GPn'ffPp$6X6JpB!pnrkm]ZW-cBkmJ@Vj!-]'pVtDF!pOO>Qj+]W',BRXg[AhYhQ&ddpAS-r;kCmocZ8[YL>i!MYsiO?p&YJAEc4-Js7Q6Rj,M1;.V6&G3;P[5qp[GVZqlZ_BCZ\_%">0r%U<eG9/qXI_cUE2@d97Q94Z7eO#CoP?.>hnWf"'/%oea8[j,JjqAk$^",FfLooP"Zc:lPZcPX21XtL#T!6Z,-(2L30QT9H%9YYYipkeHaIM`p)h^oCn.fhF7TRLg?*PWXWUa;^\m!Mh/=CHi'p`73r#L8U)Qe:PJKl+Y7,c4Bi`Uo2R).,)8"]"N2>$D[Ra*".fgZ8$\Vnl!`gYgb@A%t=K7[R_VmM2P.R$g)>:DMGF)_)X+j[ZgN"C;N)`mWjhAcW+1'4/mLhS&P8-iP$=R$4WRF-5I-o:%\Ck'qStcF&0OrWXe02DXs7C[Fi)!]Mbt%\=Qlh"D%I,)L!k-b*!@8m)\;Xp6?:Wh(?fAA0oef!-NUP3R:0Un,$(U$HgT)+4mb6g>\#qc@Eb1jHaMD<,"<[PBQ*pN(nb8%+j26#hpC$C[;q`K<\1Z81#,(4'8;@-V9>p>N-A%;X\$$oZ;oV<af56jscBVWl/!TOoajTi3u@daCg#_O9r,[.i9p1jWp/+_%2EYdB%`L*#;&%3F#/"PHl8^,SS.&PQ;U[gd>`U1mj6$a)8q3C=./')A5P<&:lr_fH4p6\MP*:7*N?Ofl2.-2K#UbqY;9QJODc'?*XZ1o[mdp&#pknd!nto9*J9h3f0:=b4cBB:j!b2@lRI]jnGJR?_T3W]<_QD6OQKT*iU[mV]h_#FV;`RMDZep)1&;Mca;B.F.$$'9<\U-Z9K\MOP1_Md%k,bX^\&2TB%2Yn8Dh-9*-*Fqt]!IqDEWpd44NXO9at*l3Vl0_'GqO[d_ji;]tADe4sT'maB+l<28$'Qhhg\T),pR/SI+rKX3b-7Ac1m(:LE:TN_7>_aEVm3Y$0'\/]K#lAM:<it5#FIh=B]UXM?%Wu&LZcs1XoEQs+$c"E7RQeRPi9-VJ.X\,DGb3W#j3.,G,PQ1q?!j,8H@K@]H-4`)g?l?C9AK64i3rB$DKcl.?-[2]Wd8+)Hm/:5"JrHtPs95Yr"@W[VmsHV%`Wj$5M%*K<$eh,;KUahmF-;-$GSmU%d<`Y<d>Ye4mGK@T;'C5L$q?#UrS*6WrV>!Pd`7b<Z5I+_^DrY%XJBl#6+`5@9j$~>endstream
endobj endobj
xref xref
0 17 0 17
@@ -116,22 +116,22 @@ xref
0000000122 00000 n 0000000122 00000 n
0000000229 00000 n 0000000229 00000 n
0000000341 00000 n 0000000341 00000 n
0000000546 00000 n 0000000446 00000 n
0000000661 00000 n 0000000651 00000 n
0000000766 00000 n 0000000728 00000 n
0000000971 00000 n 0000000933 00000 n
0000001176 00000 n 0000001138 00000 n
0000001381 00000 n 0000001343 00000 n
0000001451 00000 n 0000001413 00000 n
0000001743 00000 n 0000001705 00000 n
0000001821 00000 n 0000001783 00000 n
0000003544 00000 n 0000003855 00000 n
0000005360 00000 n 0000005952 00000 n
0000006786 00000 n 0000007459 00000 n
trailer trailer
<< <<
/ID /ID
[<51c9ef4a08589d21f13f527085c33787><51c9ef4a08589d21f13f527085c33787>] [<095815a8cc0df89fff07917977950c37><095815a8cc0df89fff07917977950c37>]
% ReportLab generated PDF document -- digest (opensource) % ReportLab generated PDF document -- digest (opensource)
/Info 11 0 R /Info 11 0 R
@@ -139,5 +139,5 @@ trailer
/Size 17 /Size 17
>> >>
startxref startxref
8754 9467
%%EOF %%EOF

View File

@@ -3,8 +3,8 @@
**Vak:** AI-Assisted Development **Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht **Opleiding:** NOVI Hogeschool Utrecht
**Duur:** 35 minuten (in de les) **Duur:** 28 minuten (in de les)
**Inleveren:** niet — je code uit deze opdracht is je basis voor het huiswerk **Inleveren:** preview URL in chat — code is basis voor het huiswerk
--- ---
@@ -19,6 +19,10 @@ Bouw één scroll-animatie sectie in een Next.js 16 project, met:
Het hoeft niet groot — een werkende sectie van 1-2 schermen is genoeg. Het hoeft niet groot — een werkende sectie van 1-2 schermen is genoeg.
> **Belangrijk:** dit project is **jouw repo** vanaf nu. In het huiswerk
> bouw je in deze zelfde repo verder met meer secties. Niet klaar in de les?
> Geen probleem — thuis ga je verder waar je bleef.
--- ---
## Vereisten (al opgezet vóór de les) ## Vereisten (al opgezet vóór de les)
@@ -46,6 +50,26 @@ Geen scroll-demo repo? Vraag in chat, of clone: `git clone <link uit Brightspace
## Stappenplan ## Stappenplan
### Stap 0 — Starter forken naar jouw GitHub (3 min)
Dit doe je **eenmalig** aan het begin. Daarna werk je in jouw eigen repo — ook thuis voor het huiswerk.
1. Ga naar de starter repo (link in Brightspace) → klik **Fork** → kies jouw account
2. Clone naar je laptop:
```bash
gh repo clone <jouw-username>/scroll-demo
cd scroll-demo
npm install
```
3. Koppel Vercel (eenmalig):
```bash
vercel link # kiest of maakt Vercel project
vercel --prod # eerste productie deploy
```
4. Open in OpenCode Desktop: **File → Open Folder → scroll-demo**
Klaar. Productie-URL staat live. Vanaf nu = elke push = automatische preview.
### Stap 1 — Worktree aanmaken (3 min) ### Stap 1 — Worktree aanmaken (3 min)
**Met opencode-worktree plugin** (in main Session): **Met opencode-worktree plugin** (in main Session):
@@ -120,6 +144,14 @@ git commit -m "feat: [jouw sectie]"
git push origin [jouw branch] git push origin [jouw branch]
``` ```
### Stap 7 — Vercel preview ophalen (2 min)
- Open Vercel dashboard (link in Brightspace)
- Vind de deploy voor `[jouw branch]` — status "Building" → "Ready" (~30-60 sec)
- Klik → preview URL openen
- Test scroll-animatie op de live URL
- **Plak de preview URL in chat** zodat anderen 'm kunnen zien
### Stap 7 — (Optioneel) Cleanup ### Stap 7 — (Optioneel) Cleanup
Klaar met deze worktree? In de main Session: Klaar met deze worktree? In de main Session:
@@ -156,12 +188,18 @@ Sluit ook de feature-Session in Desktop (right-click → Close).
## Klaar zijn = je hebt: ## Klaar zijn = je hebt:
1. Een werkende **scroll-animatie sectie** in je app 1. Een **eigen fork** op GitHub + Vercel gekoppeld
2. Gebruik gemaakt van **OpenCode + worktrees** 2. Een werkende **scroll-animatie sectie** in jouw app
3. De **regels uit AGENTS.md** gevolgd 3. Gebruik gemaakt van **OpenCode + worktrees**
4. **Smooth scroll** werkend via Lenis 4. De **regels uit AGENTS.md** gevolgd
5. **Smooth scroll** werkend via Lenis
6. Minstens 1 **preview URL** live op Vercel
Deze code is je basis voor het huiswerk — daar bouw je 'm uit naar een complete landing page. ## Aansluiting op het huiswerk
**Niet helemaal klaar in de les?** Geen stress. Je staat al goed — Vercel is gekoppeld, AGENTS.md staat klaar, één sectie werkt (of bijna). In het huiswerk maak je de eerste sectie af én voeg je 2-3 secties toe naar een complete landing page.
**Wel klaar?** Stop hier en zet je laptop dicht. Thuis bouw je gewoon door in dezelfde repo.
--- ---

View File

@@ -2,7 +2,7 @@
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource) %<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj 1 0 obj
<< <<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R /F5 7 0 R /F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 7 0 R
>> >>
endobj endobj
2 0 obj 2 0 obj
@@ -17,12 +17,17 @@ endobj
endobj endobj
4 0 obj 4 0 obj
<< <<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font /BaseFont /Symbol /Name /F3 /Subtype /Type1 /Type /Font
>> >>
endobj endobj
5 0 obj 5 0 obj
<< <<
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << /BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -30,11 +35,6 @@ endobj
/Type /Page /Type /Page
>> >>
endobj endobj
6 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj 7 0 obj
<< <<
/BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font /BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
@@ -42,7 +42,7 @@ endobj
endobj endobj
8 0 obj 8 0 obj
<< <<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << /Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -52,7 +52,7 @@ endobj
endobj endobj
9 0 obj 9 0 obj
<< <<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << /Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -62,7 +62,7 @@ endobj
endobj endobj
10 0 obj 10 0 obj
<< <<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << /Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -72,78 +72,116 @@ endobj
endobj endobj
11 0 obj 11 0 obj
<< <<
/PageMode /UseNone /Pages 13 0 R /Type /Catalog /Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
12 0 obj 12 0 obj
<< <<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260518122502+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260518122502+00'00') /Producer (ReportLab PDF Library - \(opensource\)) /Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
/Subject (\(unspecified\)) /Title (Les 2 Lesopdracht) /Trapped /False /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
13 0 obj 13 0 obj
<< <<
/Count 4 /Kids [ 5 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages /PageMode /UseNone /Pages 15 0 R /Type /Catalog
>> >>
endobj endobj
14 0 obj 14 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1679 /Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260518125917+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260518125917+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 2 Lesopdracht) /Trapped /False
>> >>
stream
Gb!;c>BcPr&:WeDbbNq&$5,;FQeaKb2Wo2:[.^@MISm>,M#gJhFgo,77t:V%[c;VemV2%GK90^-S'*_m"VX,-\crJVX"g,N;4n?;T7P_7L@JKX*#i5n_Hc6V%M4PfQHt*VQMuM!1fA9tiqgls^eKl/cN]k;_R\!DiAo5Z*omqt5;su%cS[k'"6%aL\bk<t79mi/[7qXmof(2Up2Or@:Y`rN1pd1U0\er5:p!^?-73jt#K@&YhS$\;L,UTH75.XiL;1o:#."phN-TAcrSjn,U8!lN\<('/XKk6kjYIb2_t^dneWXo-:-9IkXiZ&C$XeKdPtc>I9"kmGPR='H;'4/e.p4K?Ciq=%4Q!Ct*.**O\9KV\CsT($D*j1?5%1=hTC`3CEf5)7/d+Xj6`ELJa.`?G\)n=HUHZ'5..un3Xg3*r0RAdiUJ]UFR%6(?;\g2sRM"Sfg#)_k<D5biAmhD@jd]J0SRJs/<Sh]e`fJ$_//2fD]T8V/`8mf547(10G.#KH'ofo?BsG,tAgbjiPl57]>*qYs8ic9hfn;B8a)Y<_RJt^TE-d4!>[9"M=KEs=?4pb1;pSngG\EgcH@@N(X2OC:np20Q6pW>tYkC]_M9i-/!<p59M^:o^64r9.NaEJU-mK$q=tM@,50%].Ntr&Ep,(*'On:T>(%9eVh=/B;X2c`?TOh_H2/C<NFl#Lmoaq=+J(H+QP=rg?:2PY`$otg4o9X5^pT=!@QSe\/A^beTO)ra\3YO&n'SuKd/>HpDL6"\^<5Sp)*Se73$8[`pEj8a-O/"[6p]_),bTods,pqthJ=/Ka&BePRC`eV2:gBnQ`]KOO!Dnf77jOStF>"e6#UCnX:11BD=/Br)iT&T8.[Di^5;;'6L+-t^3nOr\7k\^YB=@mZCpi6\7.B_(/hSZOZQTA(oc''H(e36`g>$TMmN#<YI,_S0YN?/BUlLW.:3Un#*G\C0%_$5q>Pqg4i5BZ7rbo>DUer<m)m5Ca48H!(pB=A0Z<Z5G1Is2Pmk^B'l]>Dn@U?'N=Da@$f]1B<$HPIs9-^uL8f1-1'9s>0MRt%E*hD-&$*nps'dt8[Z&%CNPYTOp,BA;V/E[R/Ceqf)3k^dU>\__[#]li2FKCo3-Cmi.XZO7S'^,pZhO;P+),TKGJlOQg^eW)QE>`eT-)p-2QbS>BAYK$^k$toY;WO[upJ=.7PFAjkd(p`[_lB[VMIATS9W7.A1_M0iHGJ`d4cNLRN,:\/SiY%Mpo'93o-'(malFkkUT>HDq6n=X][o2o53QM2o]s\Eit4k[<HY>6]>SD7P]<r5<nBfj5-,u5[R-G1SH7cMdL'04eJHaMZ3ORb>CSiD,Q%7q#j)2$Od?Up!r;F`rH"E#&R\e^RLo5Pqh=NS<=e3A;GOEs7VWGu$j)]).`F]Y3#s;9XKtdSAOosOX&O91(r.mC=]SPe4;Jdf\NjI/jW1e/D0<Cqo/#"o@4_:I@%UAbDeKrmLe.PjS5cKRrB@$g4.^Ou4>3b\hLodCGd-n]e.:dMguT^hB([9jY6U*!O8duegJ0p,7Ok.4ktZ4V=LeGg\3Jj:m4&&?q*$OqT2m2`B%XRgN5%:lhl8XeS"9ooN,@0D-\G5kKNG]TO:D1e,mJT"MaH^nFbg\SG,`0[rq1++7.B@h8J(G!A;an$W=ZUh-*'[`#QKDG0=[&<XT~>endstream
endobj endobj
15 0 obj 15 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1751 /Count 6 /Kids [ 6 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages
>> >>
stream
Gb"/'94Q^7%)2%/i95=t6W0.:mFJC4gTB^Mn^^>cN'uEXp$a4QC&@3$l9WO8&uAmC;rbfZ-j';HHj%5=T_Ms_GFSd5]S)JI#)pH?"\]I.bLc7LdeVW@4Q)q&JU!?(&I/qojp.oo;h,sO+Q<t88OF[h!CAOcAL)i%>SsaX.LiGV%Kd=J1mA&tA:>YR,C,=QSE&%Lpd#H3b4k\,JJXHA7u&e1?W;b9=DsGSl3TeF+8W4c@I`LI?a&C!\^ko9.E`/!CapHD)S"]fKP.LDB6BcrNDAEB^<p)obB;2pJ`RUtT:3/7q?<32'I^Aes0]"\"+HTKUf(7b41.fk"am'04h>d_(%<8[81!dg)IbZIoXX"UDkn`^&O>c'T6.Pc/9?&,-l$bXL3U<L6!+unl%hRJM&e[42bYu=<8!rNk=%R:h&bnE=9-6(Mu\Y1b[L:4NA%827-uJ/6Uqh.5$3o[YVfp"H0f@;Q<*.s7K;Gud&5uD5\/3&8^nS'>Vb^J6bT)`AK*KbM$ma_Rh)\9rB[JK,5bQXi5B7?;Kkq:LU,VXd537OjD(`RNYNVrBe`,7+JP/7Ag9Y:]2*VBQW^9C]sQP-#2A-22c/pa&XS>tD[-ugll,*o(CU/`'n2]hUjZBN4S`\%c/!V45]@6X'P.u<`GKIq*E?=$@NHZ)F%5SR0^M\h*QoEGW=^,*Cu2rB":_7REgRM%G0K!KP2Q_t0MGc)<Y($N/F"+U#]h_2%&IcqW0O&GQ_C&ldErOp0/DN,DssKp#>lEBH5?Z.&oknnHSt^\;ec[YBjjAN;jJ0"Ztc`omH3Y5iRU&-;GCcfJid]QcPP'l>t9hniA;=.].1-T<s5*F<FHNX$.!b-D9C]5CpD+6&&\S\D-mfkZAWd.B*NX'[Pq<-D\g"/+MMb*,POM=ZqoGo9#p3X,pUcn2du6'Q>WDH5-DKl!T*%p@6=-8#O)D&BsFcnb8UjH3\f%"`#@ntd?glE(G(m#DrM<,#_2?IoLFe8n'E.p1&&ItbF^"toE/PEi<At.K;neg.+Cu&f"/^;<>kNO0_>D7I1tdhO)U]b<Vu2YWip=m*SQJ.5`WBZUVR,P)qDH<=Z#tbEs1J#I>Q[u=u=>(&!jU8TP$u3'7b"&YYdiPX!V+^h,%6;'TjS.R(!t9nYk^r,#$*@OPd+Z\#US9FA18YOou%NWT1#&1,mU+l#(TCJpV4TM0Y[C,JqKeNeZnI,Po/5UAR5n7*jU7!k,*-4>`c0JL5J$6Q%I=*G/'!e/[AcXlj_5:Og'%g,Y\[22=T^=GXC,>^NFuQrP-sYL"f\XBtn19g_>B)k'>\O?^C9_=H&MhgM;2!@&3LI67+S]'&SI"oY-&R!D`t1eY$\)kA;hOi)o(H2o#R:9ZBG[P5,PI$^\S&k`_gHD=IuLUiaYl>>?BJh5)ee*+q3XfU:5_i:hgi/R-jXBhC*P>csgGu9MpeR\GG9`U?a?r"=u=2VUA';ukX?LROnG8\heHLA5cb.JDd&K<WPV6K.HFm*AUJ549"r^Xj"_qp`&PNHG=RY&.#X7S2+!GXM,^%BK6+@Fde4m3@fD0TPj-2!3/SYG-e4OsqEm3WT%<0le6;nMBk<VW@RcBusO5$M<-SL%uYg*";46/9HBbOhgDCW6e/qc@$[]%#E3VM;Un`\qXn6Cl%Jl.$.:hVp/3NCG;R#u;:il!^i>C/(1K*doJXPIuC_^FQB-;K<;S1_6GPgAeiXbhMX-$]"LrCX[5d-8tI__0Op:K(BLp>aIM~>endstream
endobj endobj
16 0 obj 16 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1729 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2059
>> >>
stream stream
Gb"/'968iG&AIa;m,V%lD'6IsK"c>s1i_<m,gKNZ--h&g@>Qm6-m\1:rUjHX,"O/+ChG*;Toh/U\H)=(I/%kN"r+hRqbKk8B60HRAAC1Kc[eY3@K%0Ob_(rWiJL,\"<oZ;0aA=!pORW&<"LOR@NI>P1a4uD+>kBI`u6ptM]rp&D;b=`^jnh17Tol@/6#f$0jYF13LZ6Z<%?`G^\1GbpL9lJhP$G;&!^Q6[LUGq'p;B/7NouKfR*5`mj!'g(T'cjee[aV>FT`f_C=X[*#q+)BUZSJ9a6*7CK&kcBr^k]bV*B'`7^UCQX#_Fii!=bY>#*=nDCCS3q8Z"P=8e(+==,FiNs*+'OU_U;,)_:&MD+*k<';82d/`u'pJ_08Up&;7DL-7WRMS!qV0mu_BQssP&TWK#%!$C9Jbg1Gu9P!iXib?_>6@PJO,BlK>W*L0Pk<1,P?qm[upr;oV0,+n)Sf!%q?5t)D$LN<pn_Grp6J6:PW:GoGSm:<F_84c+!b.HA)iO[qaB7+fq<umH[0TlL`,MJ#Qdq'!`@J]:o878?/SabtO1Q&B_Ys$OSA\*XZce6N>dC3R3gEd-Y#jmono'hGFH"AZE*n\_"2[h*PGab55Vn@@j!?/YP&k.;p,'R-O>6re=K5qo/G<fg1j"$l6=[^\1Lk+Gr9HGX!'W+JYm5">;8,WMpgrMEGf*67%Vg&j>kY""t#o0T8lcW>]<9:lZV7P_\e?PP;jc:t>@!:`=%2Br7X\3neU>eRK#0oQ<<GN+T`9&Z>cjDT1g63/pMR)+V%A&dciR(kFCV]Pg$<$oB;JF7TE1>HVdr6j6_2i7\s72(^*+(DjY@$WAl@;qPK2j5b>UlAQiZ%LujYR@fQr,;?U8K$qFI2*1Br_^AAf+rf\K10p:nQZ-A^l\/\)10gB0_io^_3&j*RKfQ3;nm9Ab@S!,l'Hb\2M^2Kfc0ojo'.Q-b+1t<S)[p0]NPJ,$$Fm7&"]"RrI5C&*DG55>UmL01N=gmW-3X:2\p$d&niKeEZ`Ca[-JZl67Ga_o#:qtSP`D(lrNhi2ZfRF,C>H0Q#0ra.":cGaOUoU1b3V:4>\ZRAR&V6(k#.;.L*<%L\Q"5:'erMH10Sl?4W#H)bl*/AF"a5^1-4.C0putLlna"]aV"0^3:b/q3[]G/T<sWE8schH\Ml;uebppTa'j++9^S&iX4;$?Zprd3+8FH"$!%.pn4STE"3]R;/hpG7#4(-'gkBPn)5.Y%J(!+IBeK>/1cOl(rrM5\U^2+?iucSe@\WqAdhl_6XT*qla[2qd-A39D!q3*16_1A#F2E%F<iZku.p'/bh_k@!F$KUFDoaDV?5W:P,R;0B><Ef4.7UC#ln-t8[6L,q2GK(*D;'D(.G`-)HWVMh[4M^ec^,ul]KO/kVf/R^Nmb(UQlr$>\:;m[N5mta%GC>RP.<$0nu\.uU?GW*EP$u72tt8AK/3*kBl1J8VOJ4"Ja5rkmdG:@lfmG6XWm!$mQqTE<eUD8Z;XZ_AV)oKg,`Rl9PlOI<He'+`KbdC!i=_!5I):YVn7k/[.U(_&)Ddd[8g]E"/`5<h0^fI*QI^<i@I3+2(kC\]&aAR@ClBRgq2<56EZF#igBa>n2jA6l8pBgkBJDr>PJq6=3\%soAVrZSn"AT6Lc.ikPob0HPX+t8X9C&^tTee-LI/rBaKjG:"&IIq[:I,ldnRdmXKt1au[%7jF99]oQngJUe6sq2LknGJ#`<@$3~>endstream Gb!;d96\iE']/^gg^WEL86&<3ZXF3;V<;qm+e`_XE?-?FCJ2Zj)MVT`=10>8Bp+2Z0S_i^b*MJmIeRn4R.sCVCQnn5)8bn]"j144!GW8eL6qCIBRDZCF.$Z&5V1g"!X9pSe/.1E6\+202dcXiht2`nfjO]ApjV-'_fMD>4ifWW?5OB4kF.u\"7`ANqX1-M(3(3ZeM_DP2cr<%f]OdV!oc,hTpbQa$H!mkECrm3Cgp;_F'tA%n*7g8"h^1#?:6Ru'DPhp4IVNh@a'IgMkcK1Ll7FWiKQRr6tRkaj`;9q_=30,eWFc+T6N8c>*4<V$XeKdR>pf79>1n6Ym>o(7"*"X/nMSpZ.661nH-bJFR4ssiSqDtYW\loZj@$k!l@W44m=6-oe"at?&/p&T[Y0_j%sm,^#p$OUB,%BPQpe5X\@W/N6^J%BU%aCAh2,BPspFaAgDV<p,=)Y/s`1c1GI:2qED74GD=XW<H[>31k)%bbX.GXkBD>e*\i)lnQXmRd-T3'oH!&*?>/6h*OD#C[`\TUmn/s7*(kY53bB&fciDg3fk'H\DMI1-eD[EV_89YU_fUbQS"+d?ZbSpA:"'J279gRFVik-9TE5I(!JRp[E^0[qPmo/:9]l(GaZ2PkerKJob]0+bl_8a1^m!ENr34X=Pfk=4.X4q4#E$TCrXeZ)I/\6`A.ta,T#*d0ipI#18<1seL9H>74^R7Y24KkCVSRJ%hF%\C&N$Ne=bSshIMHI[oL;B*i4YWLrmcnR?)n%.Fd8SIA^aZ4:LiOe3YO(E+=B?`$'3-3Qp0o[^)GR-feOLi\AHIB>WJA$o;B:S=884#b_J"jo9'IuojRr3%LFdXcZcp>O;A]W_0kENm,?)QSm8`V#O"]8$_(iK8TZF7i&8NjK*NiHR.1f$o=.olr;Mg=+4n`bSa,/"Aj0\k>,SeJ&8daQ*j_2/kBM&FTs>+VfP]%91*eR+/C=(m/T<)>>)A<@'2WZjes)<Xak"hJ)BET^j-CB-aW#9i7a;f(Z5fln`=b^/&Z;=SdKWQ'3umSRMLR.u>liGD\<M44$rY<mLQ%/#.V5Nj\1_FEY;YusMUs/WpQpp4e2%^.q1?M7,I0j;5T/GPiI#Tb%52]FM7>e_!et-R/7L>_3uF&Bia=eD`*m^9^/sDBq4puePq^IZChch8X,;pqYn"fA."E?nN7NsNLk_p1d94Shg[/hAWnXrQfe#_VVui'"SF7<B.#B`KRM?,q?K,]4<7)K5Z2@T7`%8B'U=9YrU?[hH.?\/L6BM1[l1fIUBCR3\,Z[mF3FXI0OW%$jfPEIq=0o1gJf/r]!Q')9;0q:2$-^1DZ)`s6365Q":cCbZGMJ5S8%>KhcJCo%'1a);=iiH4.nk&UhQ!o^H,%$9<P-BN(>FCuIU*,EgnG3u6s@hRlro4J/PruqmH^=_V/?_nMEB?oFX'Qo28[3:-QqOS0#S*mJPlo#<MtiPG7NMU1)[,@AeGZ.a#pP//`f+HSQ&[#6/Y@<H_5t"%XK81L3$A+BFJL*Vfa_\8fp_oV5]Tg:oA93G"j$EmoF,0fZsZ2)k8[@@5JY"%XPj!V!2K#MY:d8p+@7*QK2jh_`*Xt&bWf=7\XTSO`,C@(gUk'[YY9Ek9S7/PJl^XpL3hm`Wo?^B4YF[jc6#0*=DT-SN`9UT"hlU%S/X`b$BnnWX#C`.<`5Od_T;Zeq)=LieUR8#4j94j][D&,Hf)E%bFmEq!23IOH3_'d=$h@q@u\^&82#MlJjke@>&3[W+!E`^Ye83L*9uLB8#)3mD=3'P%c6H^@2iWK+pnnCeui3;AS0D=32BX@?H=J<coj#0rFY.$kq\*$JIH3j]6&kJb.?`oa>g*<+<P7?&0\uGl9It#+e_2]70s^DU0]dPg=R(0Q'dt<8:]`e41s9$+G1.U07F-;N:9olMfHbWu:do%n!-DMDZN[ec1:?ot&Y#k=k\9_"S(Trae[Yf@.J0D<K6"iu=C[b@YA'*-9k0e5:.a.,e;MVMGG+b7YKDLO$_dRo^]hAoLB0bk:EplQ:6JX\;Z8q'BSC@Kp:5D6"H/K"k^k$P^\1_Z~>endstream
endobj endobj
17 0 obj 17 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2366
>>
stream
Gb"/(>BAOW(4Ol=3&<YFELIL`!+*Ibm`qWQ3pAOodooIQncG#:7#N<iP-USVqE5rlOHs]UG+ItmI$$r*pi(+1k;s@]++J#!R[*s2cf*P+"j15e!#gq!k&90Bo@Y3A_BKa#+V68RShM8n2a:+lB,-(3M%Z+N&:R;@*'bVp&L%h&E*XI4(hs54DeBbT*;QiFJ-[&_,2-8I8]2H`*KVt4P25fEI):]+OJnKU`O(+#L,3MHPXFO5nS&hq0JDDGh9R0lK\(OLRWPBK!Pc%M8lmTlA1WHO5C+uO-?!(Q_+SpM$G@lgeNSQ["jF_=HVNu3-:f-7Wb_BJ<!*Qu<H#0H3op$UR9($R"R6d[l[T_T?ImaFKQB@IGMHp()+WEe*^i9sjc"o3?FmCN`a[s)JXDCi]_3Nl1lg41,TPsB1H1%)<)@>&ge`UoOq8TkVfn(6?l2F3^u;kl;>cZ=+8QJgGIsY,J!?\C)^f12fVpUQ4IrTd?!*M%lhtIo-@EE"NJQdCAE#K3'.,-f5S9?@:ip%8-[m5gRX+XUJff.1$"<`+!@&a^0QaMWT=(tB<=K=AY>kakPVl_0m1m:VgXk2$)^??F]K-`L;<#1fq.$uj>0LeO]//34@c'_51>9Kh#q%Ct(,E#M6:&(VVbB&:7&d2N7DA-/LZKm+AHaU7_R[Mh-L_X:a$@@:p"I4BQ.87`9@U"C)f9`FH4B7VRl>;cFkm(c40slN;fSh540ani9pG:^NsG7eP#:A)e$*.D`9i^LNm>A\7OqU$S^?+[%/g++%m/Ur"0\MCI\re&A&9/OUr]UE1$A:R^0J(j7],J2PJKuk\ptl*)!s;_rrgnA^><r!.>HZMA+'r!@rCVF/^m[;YN<ah<Y>s+MBgacNYlt\B"ek'8"1S-?)dfG.OaATeJiaj%R@K;Ki5oZ)c^IL8l=t;1hD`B.6g-tp99YU8T-MeJ.JYT<UkHUHSECSAN()Gr:WJ<l#!aiDc>ug%]_^L;1^$OnhBP4N35$nc+O(LnM%[Vbo.hb3Lo5c])U1An4n+gWHqA$?Znh]WBB7/apm/][@[4s238ojGAFFceg$k4bZ:!L(<!K><c5"+"i<2+a!!sg,2riV4<5$OS9-[7MFgL:0^,0aZ%OARm`:n%O(gi#8`3&LC:l:_4r.VuYs3j9j'JVBMbs_/PdOj_C,92Q=RcJa3bS4H'n!>DAOq,6'(OV?j5nEqh6]fVs"la\gGTBH>H.W#X@[25'OIY/I5CI6+?:f(B?Xh^),(g_2r?m..>N:k2\&3=)kPgC9[4mT5QBqFD[UjlV3L;S5KeRin3Wt\d*i#CGb8BudlUrIY!^'A^p7h2k/ns`6@V$a6pf%$o\TKCrD[1a-1HB9e,/Oa2l7_?()=S`"?sn!/tgq8^H`NLP]#6Wn60^6?-5&qnno^RQas]=4\7V9K*\Gf^r!:VqMVXoqO(LZmmq5uI8blqQI6?1j*>_X`%E>'*PQHSc>$&K.@(J4C62n'ClondQ,Aqdildb,Al;\PT0,=^7'ZCo'0l?n(B_s[AH83(7ZhHsW&u!i5EED"c_E5Xj0e@,p]g$lVtk&<H],uYdp!"$79q^T:pSp.at*(Ab%m6`5NGYN@*CY*gcJctgVC&(H]Z3NFEG?A<&e;/+bq!^N[80Lk8k)\j7)VL)Pl;BD"X\)jXIi@=2T%3A#Z>)2Wsqb;E?(ErPSo/4B(E`T9;>qHU"B'Gpn"u:"QO2G3S/HC?m?pM9S+?4@<Vi';;%fjWDtJ4H/C:bLQ=/KgV33>:;4MgSYeN9"&'+X&f20hoafug0'S41VoVnD%r'ne`jFM=hmX1R:.pFX;eHYL*BOW.&M8ndN2CO=;[L<ppGE1@eEjcA$Q<,AO+>D2=OYHf&3+2EanGIPn=-HJr8V>&cJFlP^R-fHIe2ae#!"#OAL**$$BfA)5H0+="Lk?q;]Tg=Q$nkl&B38,gf=b87K;J5tuR[qaE5$L`;-Ti3M99gqhXp\]Vk?(`Ijif2(P$2X9EOQU[2bIMfYq^WTj8@35*fc%h^*[E=F*@_G\8oKh%\QV)-3:XBU8[E/NJWgmp`]U`Oe5ufS']VXk:,59:DC5o?DL>c(0[T0_J33$Vp!?62uFciRCd`R/)3t8(hT2[%<RLJYY@"PW/*_pQ%CLC<.RH_bYbA6eN-8kXrgfs)2QZhV;8Ar/pZk'\VL<:"IDc-5h5>lmSHW)h>?Ks"!Klj0Wr%BVhnt76(eZX`3Kc6QDf]JiJDCXtR3M6N<2`ou#23L^p=6B="rMeOo,AXrUIictP/pso`=/s@4b)dPU:r@#Yn'CT`?E40#,kh`nPLoelrth&+KjYlV(lLhu].\L7jtul+eVo0#25I0kbR8Ys^faIA@.lfeKE,N~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 550
>>
stream
GasIda`c,u&A7<Zl59(u-F5AGYQVfNCBRnP#chgca<Mo9o()O6j9:YWXHr"u<E!2"(>u11Gg=mUdi@P;!^\*'6YN?Z?fOSHrQ(A5dV/f\4BFh#"Z8DU(KlErbQ\pf4Eo"2%IF>j5'Y/%F&"^%K_]=/^msN3CG7E#p%ADm0e6hH=u0Z*\<#P3o7O]klqUiL\U>,ol4CS$%]i8Y/rVU-f/S'Qmdh0"2%/\]bGM9/WGcLZm."(JLPr4>Mls+lSk;,C?W8tR29#P$neL`EiN<L%QC^+-:k/"B;[8/$2Oh!(%a=C"7263/$.XfAlN=8L9BH]8.?7o@o47G7VpD/&Qorm$7I'o]N.r[oq&$Ws)*/Y`6HO5Mr,+n%p-Dfs!]O`Kq5&>S0P*7CMS";sRtdrS>('aU<DF*QCu:-a#`ia/,L9(koGqPgce)0C7,o'QXBNlJm8-i:B%'[a;oEMb,>j@KiYta#*Z8Et3kmLMI4$b0W-bhZ,X^:he$mJen6W+/X%]Re`hi/RBRG2q0n\5/E9kUeC"0Xa^W]NqIKMBAg4]~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1956
>>
stream
Gb"/(968iG&AIa;m.=a7XW[ZI6-tsYSL1=?:ZB;b/o_@X$&c/]66U'Tm\.D/$W;B)nOo:T12/=S,SC7RKb*&n+9iP;XSIlsJDDYJ?85h-VC0.^&MW$rQW*(*6b)3kn:8$+\cE$K\Fp*fbUL>k6j>b]TVtYN00gpTPR8+D*9[4CLJl%3F,'ed-2!N'U6m<nr+`l;,j2+0&5UI,qY6n8b*j+X\[2jkCd0<VHa'>F]i8&EaunR6Bb$VNhL=74hp"hr;.<3'+6l`o8I\1[qlmE9[8U(><U1KNe)rbJMd.(k1ROcNoXWL0OtX<h^;*TIjR;CA38h8K`+@Ro4pYe8&V:jVHA>qrA4aMBeYe;70(j3FmYn^:@Vh8ZUl^["g`u2thj-:X-^4rqC.!@&$UL.5q7+E0qF"*9L)3N<AjuAe!VQm`k[hB-mF*$$2+2+cGF^`&T<dE!m01mD9B=I9M@=EL,F=%02b'B/:?"+7ZL;u1-a,(K)Z34%Q3\AQ)P_s9`a)t#f2\DWB$D@@$Xmn>YQ'XDb=6qGpt,'?-QWX,\l[r7A7;a.Rom.5n/tMbqk,&6g-`\/5\NA`6&orj$CXsfgJr$+g>"3o/9DI].Q=Ch/8Ica=S-d(':=Np?ui2'>bkAr1L1t`gV..Ic$&W#<AkD"'V33Sh>ao[D'4]u5^46,g_%9X;F99,3C6S)E+nN^6P:lI5Aj>(T?-9O49Q(&PW,>J2FT%7(NdO-@+hNgTPm+n_i7*aYLMbj*JDg+QNaB-I%Z+obOL3D_rZkC'bE2X7W6.X5Q]4jU>7-0bP;,_d(4a'p#SM-V5$*%T-5uc_A#O?&>pGl9bn[R6\p/1U.[Yu=`lNSF\4UtYF/:h"C\V-qW442+j"3q;e$0U2'aTtU1YDtF>cWI:,?Fl4:LClWde_%moIm8=rjdWG7J$D?k6lG\3*Dg)2UYG/rL!R:h,J1>,9]PpF[+/H8r(?9_L>)dCDa)Ygs\^Qf/??4@.`o$(PZL+,A76gqPO*cc:^3Su@"<e1D`0\p)KK/08diHOtILDU;g#APcQ:DWFT"\Q>CCEDJ3Z>T3dcek?.K/S:+Uo!!qp"3=^qQZrOrhk/,H9*4C(CeWtX'#p`nH86<9ZI<2If4)k7^ZikQDkVe;3p9a'R:"q+?#t0t$$rI#q8=;E'0qioCJ.=2:"_\?<tis"_^RL_@:cQ`Bn_4u`FOP,7dB7lptN_1<WdEqa>%+2q-1`VF%*49mOcM.a;0@>lI4[Jk13.GhBUM)[$gk-b#:S<l:_B=T/k[W"X*6Q3+G>SkK8?,>hRJW.gL[ACYFiC<n<^"@D$3171SFI/_eS>$br`e+9!oPA<f&+%!c<HK4+:&D@O/k,F0?.PIb(::F;Kd_CREA^$S]iY@X1tXHB7*A/Tq7M2p`-B:*-%:$'Gmg-"d)e0d!-+#J*"Yn!++p:QZ$DG$f(>0:BiJ#e^Agc^CMH_bGR9RW9I;^YC!+i,),SE"dBZ`./iT&$C]6)W;sRepqja<*<%KghGcR$40qe(IE]3lYH:;_Oc7fi&O)c*d/*l^i..5#2Dh.s_!FDQ"aX<$p(ebL2X*>n4!aT5Sp$\Vs1O34j<+/"FI)q+BJTgIadbOk#&65:-*4-@#@-$2t*G$ITiAL;d;\[!CMV8=o"3kIE,=pWrI2]AoJ#c;tk'5ZTkXZ"'MOWjd$DksU="2LIQnCq`"aqWZsc#V3!-Ma@?_D+#o;\."9Dq0X9'QotpfdX0u9[IT.Ra?9#jQ]]R$;<maP?XQ$YKNYd(i-,aTn0a:K^d@r8$c8dF?:H=QX5qodAW.pDQ54PR&)Y?KPgn5-N:`0;+F["BfH+D<HBFUg3&d_A#Ic9FZuDu/YWf*qUH(Y6MqQ,8a''GNFVnJ'?jQdPl'A-2`9rT>%o@6ERAKN*,LJ*Q&+B7^^NO_h,?Ll<\7)pdg?Nl"\@4^gAa-b6?J\SbK(CXbhmm+~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 470
>>
stream
Gas2E:QO3`&B4,6'^o)6;?r9CIlVDsY.>6\GH9((Q\ZJL(E>?T;Me1VP5ktZC2@%=SG/L3o,&Z(ma(\5pmbjl^gN"H!EQ"X:[g5;?5&Jk>,=Na'58dZ=aKki/<^'NaCI&93+9p3"m),Ab8b'B=j%S?a5S^[O>Xm9"`g](1H5/pN0LTS,&L$IYQ!b3*UI?JNp`Xd@7OGtK9&`tWMq)Xg1o[fN5#>eon[PZd^SgJ?*?!M)F%SOQq;"3s7kimD6(,:-,0/F?e=%l&)Bb:^QICoC0g=oFC\BN?1B;VKg\&hD:oV9nl)fp632LcHB)LKnS1uo:(%fsLsMfF*non3&UcA(*6Unq-TrmWPLG5.!Hg(XIZPpfTCcg:em:ALoU%CJB#gfI\%I:_:*k2@bn?eLFn((3A)$cajo8`bF+PlL^++m5^@sS'^+P<R74<TE"$UPjneOR)m408rr&sc^a^5iIV;h~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1412 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1412
>> >>
stream stream
Gat=*gN)%,&:N/3lsqG!Ei+Qtn-2'&DX0:(A8F2*m2*H$'L^=Q!=#@$\6n+s8DS)RmaRCGP/<eR]@]7S$3@l>s3*!Dha_YZR0$qRRK8C="JbF)0cKs0&=0OWMLgFC#VuZ1$f)oW@\=l>8<U6:!Y82Gdg.-m$0N02L3@9';m6mH*sNEMRKP(#J8cDS4F(K[P(YQ3*K`<QZBt8J5OR`H,jEu*c0gR!\:XE?X\lul">ik`(f`9&]"\%K?VDW4Hu=mp^KW5[Gme-%;E4]&HJUJie&j-;.A*CkFX;0W!i#u;I;lp/n0>qE)PP[krcd#q"8k"Ke;?-SN@7CN+PMGD$e)](8m'CsAI7=8&E.dPch\E>!#"Jkln^!6VhY"q)r=/cSrBq8#(r:8W3)I9M:Xg7[V;gA(B7GSg=bG[oo>Z=o@7Hbf4n$spT5U,X+CmhI]oiIk\XiGr"%j>GnW0n)q>n;dG?R$E7scKI#[/]#/1=F?fPAFSH)C_L(A5#L<=7\IF9Yh/8"4uf<mA9gq)-A*r*,nY8"8e:?0^=//ieZH[Z'."5:+)@(+F->8mGEMr%^j'pQ:DKi7E>BLI8\V8s1S(j]b]2uE0)h;l46<Ap+Uo/S@Ue[s5qF5,Ha6e[X&'"dt1K4r*F!2!Q@Ahn:uXIl-rll_L"e_h?50<<gZ(8S@X^;6JO]..A"T:0pX/^<P(b-IDDB?GjPKdc$#0$6]RNt.<1e77=segND]APku+\pQVJYi=$!\TAK8$R]oe7P.H"`k,@5':>.7A8kp8'8TQtFXa/AVD>$>6fSc=1[*)$]g_)sQ<)Kmo(Um9C;@G;-(`AU-#N8>&:q'dUO1KJP\td#3YaT"+s0\CNg&[$]>u^D\06Q*@Z$je2-ea2s'\_Or=<s`Yt48u_X,G1]Qr'geA@*Cl&#MEjKB?\mPpE3:L^fp@O]I.8(k1h])3$;*sRC=c9`4QL9i].;j=%&_iMtAM.*qqfP704mT.0+(\k\--Dl$QU<'%Hn!sJV\68<Bn9uQedTV3ZK9CpOKABM=iJgn%2bc?J!IB`'*6rQF+kSd]KNjla6b^+IdAaSB;@G54JFHs!Nn6;FEp'8djGnA$&ic[,^DpS,0BbS%cSL>:gj];eG`T9'.Me0N)98.815:RJ%)9DofBY?PShkW?l1RaRIA,Db][FkaosrkGh3"$/l:89!Id-nCO'EP$O>DTd9DN*eJ[IQ\;X>S*>?A>2<d8Yi;NFV$/NH^+#MHj(_c>?Dm;PbJ'^d8je><TV*Z8brQ"q!Wn\P%_`RX1<<)].k%K@:>qa^+lTlh2r7OiW1:dUdT%]-'7Xn'o29ZAVYf&V"'J]XWc;j]#?">/?H_Q*1cn[5-=+Y7Y9^k7;O0*Ont=I5+U8mPZQD?4K(?K:S=!Rpc4e`iuI&K(ER,O`Gq~>endstream Gat=*gN)%,&:N/3lsqG!Ei+Qtn-2'&DX0:(A8F2*m2*H$'L^=Q!=#@$\6n+s8DS)RmaRCGP/<eR]@]7S$3@l>s3*!Dha_YZR0$qRRK8C="JbF)0cKs0&=0OWMLgFC#VuZ1$f)oW@\=l>8<U6:!Y82Gdg.-m$0N02L3@9';m6mH*sNEMRKP(#J8cDS4F(K[P(YQ3*K`<QZBt8J5OR`H,jEu*c0gR!\:XE?X\lul">ik`(f`9&]"\%K?VDW4Hu=mp^KW5[Gme-%;E4]&HJUJie&j-;.A*CkFX;0W!i#u;I;lp/n0>qE)PP[krcd#q"8k"Ke;?-SN@7CN+PDAC$e)](8m'CsAI7=8&E.dPch\E>!#"Jkln^!6VhY"q)r=/cSrBq8#(r:8W3)I9M:Xg7[V;gA(B7GSg=bG[oo>Z=o@7Hbf4n$spT5U,X+CmhI]oiIk\XiGr"%j>GnW0n)q>n;dG?R$E7scKI#[/]#/1=F?fPAFSH)C_L(A5#L<=7\IF9Yh/8"4uf<mA9gq)-A*r*,nY8"8e:?0^=//ieZH[Z'."5:+)@(+F->8mGEMr%^j'pQ:DKi7E>BLI8\V8s1S(j]b]2uE0)h;l46<Ap+Uo/S@Ue[s5qF5,Ha6e[X&'"dt1K4r*F!2!Q@Ahn:uXIl-rll_L"e_h?50<<gZ(8S@X^;6JO]..A"T:0pX/^<P(b-IDDB?GjPKdc$#0$6]RNt.<1e77=segND]APku+\pQVJYi=$!\TAK8$R]oe7P.H"`k,@5':>.7A8kp8'8TQtFXa/AVD>$>6fSc=1[*)$]g_)sQ<)Kmo(Um9C;@G;-(`AU-#N8>&:q'dUO1KJP\td#3YaT"+s0\CNg&[$]>u^D\06Q*@Z$je2-ea2s'\_Or=<s`Yt48u_X,G1]Qr'geA@*Cl&#MEjKB?\mPpE3:L^fp@O]I.8(k1h])3$;*sRC=c9`4QL9i].;j=%&_iMtAM.*qqfP704mT.0+(\k\--Dl$QU<'%Hn!sJV\68<Bn9uQedTV3ZK9CpOKABM=iJgn%2bc?J!IB`'*6rQF+kSd]KNjla6b^+IdAaSB;@G54JFHs!Nn6;FEp'8djGnA$&ic[,^DpS,0BbS%cSL>:gj];eG`T9'.Me0N)98.815:RJ%)9DofBY?PShkW?l1RaRIA,Db][FkaosrkGh3"$/l:89!Id-nCO'EP$O>DTd9DN*eJ[IQ\;X>S*>?A>2<d8Yi;NFV$/NH^+#MHj(_c>?Dm;PbJ'^d8je><TV*Z8brQ"q!Wn\P%_`RX1<<)].k%K@:>qa^+lTlh2r7OiW1:dUdT%]-'7Y4C#39ZAVYf&V"'J]XWc;j]#?">/?H_Q*1cn[5-=+Y7Y9^k7;O0*Ont=I5+U8mPZQD?4K(?K:S=!Rpc4e`iuI&K(ER2MAmo~>endstream
endobj endobj
xref xref
0 18 0 22
0000000000 65535 f 0000000000 65535 f
0000000061 00000 n 0000000061 00000 n
0000000132 00000 n 0000000132 00000 n
0000000239 00000 n 0000000239 00000 n
0000000351 00000 n 0000000351 00000 n
0000000456 00000 n 0000000428 00000 n
0000000661 00000 n 0000000533 00000 n
0000000738 00000 n 0000000738 00000 n
0000000821 00000 n 0000000821 00000 n
0000001026 00000 n 0000001026 00000 n
0000001231 00000 n 0000001231 00000 n
0000001437 00000 n 0000001437 00000 n
0000001507 00000 n 0000001643 00000 n
0000001802 00000 n 0000001849 00000 n
0000001881 00000 n 0000001919 00000 n
0000003652 00000 n 0000002214 00000 n
0000005495 00000 n 0000002307 00000 n
0000007316 00000 n 0000004458 00000 n
0000006916 00000 n
0000007557 00000 n
0000009605 00000 n
0000010166 00000 n
trailer trailer
<< <<
/ID /ID
[<de44488731e8e5bd8c7e304038250f1a><de44488731e8e5bd8c7e304038250f1a>] [<9ede7c3cd6e0a19e0d40b08da394d3dc><9ede7c3cd6e0a19e0d40b08da394d3dc>]
% ReportLab generated PDF document -- digest (opensource) % ReportLab generated PDF document -- digest (opensource)
/Info 12 0 R /Info 14 0 R
/Root 11 0 R /Root 13 0 R
/Size 18 /Size 22
>> >>
startxref startxref
8820 11670
%%EOF %%EOF

View File

@@ -16,9 +16,10 @@
5. [`opencode.json` — config & permissies](#5-opencodejson--config--permissies) 5. [`opencode.json` — config & permissies](#5-opencodejson--config--permissies)
6. [Plugins & `opencode-worktree`](#6-plugins--opencode-worktree) 6. [Plugins & `opencode-worktree`](#6-plugins--opencode-worktree)
7. [De scroll-animatie stack (Next.js 16)](#7-de-scroll-animatie-stack) 7. [De scroll-animatie stack (Next.js 16)](#7-de-scroll-animatie-stack)
8. [Project setup van A tot Z](#8-project-setup-van-a-tot-z) 8. [Vercel & Preview Deployments](#8-vercel--preview-deployments)
9. [Workflow patterns](#9-workflow-patterns) 9. [Project setup van A tot Z](#9-project-setup-van-a-tot-z)
10. [Bronnen](#10-bronnen) 10. [Workflow patterns](#10-workflow-patterns)
11. [Bronnen](#11-bronnen)
--- ---
@@ -479,7 +480,88 @@ export default function RootLayout({ children }: { children: React.ReactNode })
--- ---
## 8. Project setup van A tot Z ## 8. Vercel & Preview Deployments
Vercel maakt **automatisch een unieke URL** voor elke branch die je naar GitHub pusht. Voor scroll-storytelling is dit goud — animaties moet je *zien* om over te oordelen.
### Hoe het werkt
| Branch | Type deploy | URL pattern |
|--------|-------------|-------------|
| `main` | Production | `jouw-app.vercel.app` |
| `feature-hero` | Preview | `jouw-app-git-feature-hero-jij.vercel.app` |
| `feature-gallery` | Preview | `jouw-app-git-feature-gallery-jij.vercel.app` |
| Pull request | Preview met PR-comment | Unieke URL per PR |
Iedere nieuwe push naar dezelfde branch overschrijft de bestaande preview deploy.
### Setup (eenmalig)
```bash
# 1. Vercel CLI installeren
npm i -g vercel
# 2. Inloggen
vercel login
# 3. Koppel project (in repo root)
vercel link
# 4. Eerste productie deploy
vercel --prod
```
Of via dashboard: vercel.com → New Project → Import van GitHub.
Vercel installeert een eigen GitHub App. Vanaf nu detecteert het automatisch elke push.
### Combinatie met worktrees
Je worktree-workflow + Vercel preview deployments past perfect samen:
```
1 worktree = 1 branch = 1 preview URL
```
Drie features parallel ontwikkelen?
- `feature-hero` worktree → eigen preview URL
- `feature-gallery` worktree → eigen preview URL
- `feature-footer` worktree → eigen preview URL
Drie verschillende stakeholders kunnen tegelijk reviewen.
### Pull request flow
```
1. Push feature-branch
2. Vercel deploy (preview URL)
3. Open PR op GitHub
4. Vercel bot plakt preview URL in PR comment
5. Reviewer klikt URL, ziet live versie, geeft feedback
6. Merge naar main → automatische productie deploy
```
Voor je eindopdracht: gebruik deze flow. Het scheelt enorm in feedback-rondes.
### Environment variables
Heeft je app secrets nodig (API keys etc.)?
- Vercel dashboard → Project → Settings → Environment Variables
- Voor scroll-animatie sites meestal niet nodig
- Maar handig om te weten voor latere lessen
### Wat NIET commiten
```gitignore
.env*.local
.vercel
```
`.vercel` folder bevat link-info — staat al standaard in `.gitignore` van Next.js starter.
---
## 9. Project setup van A tot Z
### Stap 1 — Next.js 16 project aanmaken ### Stap 1 — Next.js 16 project aanmaken
@@ -545,7 +627,7 @@ Plugin maakt worktree → open nieuwe Sessions tab op die folder → agent bouwt
--- ---
## 9. Workflow patterns ## 10. Workflow patterns
### Pattern 1 — Plan eerst, dan Build ### Pattern 1 — Plan eerst, dan Build
@@ -573,7 +655,7 @@ Eén Session = orchestrator (in main). Worktrees met aparte Sessions = workers.
--- ---
## 10. Bronnen ## 11. Bronnen
### OpenCode officieel ### OpenCode officieel
- Hoofdsite: https://opencode.ai - Hoofdsite: https://opencode.ai
@@ -611,6 +693,11 @@ Eén Session = orchestrator (in main). Worktrees met aparte Sessions = workers.
- React README: https://github.com/darkroomengineering/lenis/blob/main/packages/react/README.md - React README: https://github.com/darkroomengineering/lenis/blob/main/packages/react/README.md
- Homepage: https://lenis.darkroom.engineering/ - Homepage: https://lenis.darkroom.engineering/
### Vercel
- Preview Deployments: https://vercel.com/docs/deployments/preview-deployments
- Git Integration: https://vercel.com/docs/git
- Vercel CLI docs: https://vercel.com/docs/cli
### Combinaties / patterns ### Combinaties / patterns
- GSAP forum: Next 15/16 best practices: https://gsap.com/community/forums/topic/43831-what-are-the-best-practices-for-using-gsap-with-next-15-clientserver-components/ - GSAP forum: Next 15/16 best practices: https://gsap.com/community/forums/topic/43831-what-are-the-best-practices-for-using-gsap-with-next-15-clientserver-components/
- Tutorial: Next.js + Lenis + GSAP: https://devdreaming.com/blogs/nextjs-smooth-scrolling-with-lenis-gsap - Tutorial: Next.js + Lenis + GSAP: https://devdreaming.com/blogs/nextjs-smooth-scrolling-with-lenis-gsap

View File

@@ -2,7 +2,7 @@
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource) %<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj 1 0 obj
<< <<
/F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 8 0 R /F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 8 0 R /F5 14 0 R
>> >>
endobj endobj
2 0 obj 2 0 obj
@@ -17,7 +17,7 @@ endobj
endobj endobj
4 0 obj 4 0 obj
<< <<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -32,7 +32,7 @@ endobj
endobj endobj
6 0 obj 6 0 obj
<< <<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -42,7 +42,7 @@ endobj
endobj endobj
7 0 obj 7 0 obj
<< <<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -57,7 +57,7 @@ endobj
endobj endobj
9 0 obj 9 0 obj
<< <<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -67,7 +67,7 @@ endobj
endobj endobj
10 0 obj 10 0 obj
<< <<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -77,7 +77,7 @@ endobj
endobj endobj
11 0 obj 11 0 obj
<< <<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -87,7 +87,7 @@ endobj
endobj endobj
12 0 obj 12 0 obj
<< <<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -97,7 +97,7 @@ endobj
endobj endobj
13 0 obj 13 0 obj
<< <<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 31 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -107,17 +107,12 @@ endobj
endobj endobj
14 0 obj 14 0 obj
<< <<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
15 0 obj 15 0 obj
<< <<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 32 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -127,7 +122,7 @@ endobj
endobj endobj
16 0 obj 16 0 obj
<< <<
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources << /Contents 33 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans << >> /Rotate 0 /Trans <<
@@ -137,141 +132,209 @@ endobj
endobj endobj
17 0 obj 17 0 obj
<< <<
/PageMode /UseNone /Pages 19 0 R /Type /Catalog /Contents 34 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
18 0 obj 18 0 obj
<< <<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260518122502+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260518122502+00'00') /Producer (ReportLab PDF Library - \(opensource\)) /Contents 35 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Subject (\(unspecified\)) /Title (Les 2 Lesstof) /Trapped /False /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
19 0 obj 19 0 obj
<< <<
/Count 11 /Kids [ 4 0 R 6 0 R 7 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R /Contents 36 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
16 0 R ] /Type /Pages /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
endobj endobj
20 0 obj 20 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1817 /Contents 37 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 23 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>> >>
stream
Gatm<>B?8n'Ro4HS<rZ>m7,J,K\2XIUt<h6g9bkSR@Uu%/3>+4EWF.Nrq\bZ$okC1f'>;J,GB2S`;.+l_LUX$<S(acE&N<3[gX+/d0,h4Hl.9^2^@eJ,@'fd%tFpS7DsaF_Yl_`oeHduOV*Wk-lN^5qVWknP_p/oZjT5:6EXbf_IGht8OMSA;(Rf$DV,=@3QKK1$)EM$gGepWQX3'jP9$N's.V;S_&?.P=5ah8JhV`&%$!N#qK<A;m8OnDNO8u)QFTb.9"<kCK[]PQe"TBk;NKeEIaH*[B0%Q8_rqk*03kTMJ)?S<$N4r"rs)g0#J:hKS^^t!ib=k#^j9l^j-HkG8D`DV"X@Fi#^d=JpY1mF2tQr$W&L,r_*LIKQ0qCco;*)4)bW`7G/0If1^KJTmH,Z++cE#d``Q3DC4ioMM:]@V]I$`B6T]4n$^Q#OdkH#`ei-rR(/*#dnaIt@VA'i&SR\LPWb)t5`o4.CJM'BiqUg'aW1nk.Yk+ereHTf%*;4R&@W,mmkDaZ$r\o#`/0Ft'MKQ^^_?1Ps<E9NLT:-$5D_Zug>f";:%9[u\N!*dehuZ5=OjYrK'l[0_TB81]A]G]AEkBVJ8gRT9&n7d)Ld[cD,gc.LKgFTtaCt?do/e5SCA:82:aES/juCZ7=^7gBMR:lPD&5j94\6V%(HUA<iq+%o$5!!F?#]8[lLntR0NhQ[iN:b7lm[7Z9b:h5].Pg?E"8;jkf-2jQq-&n]0Fl0mUm8CICUpIlC4pQITiMt_bgt[0+ac<7]59'Rm%9mBS8lP[V51@.%K]/j_keK:SSclS8C<B1UoKOR7T4540iM(aKM3(K!F28ai6AKl?kbqb$IVB^;&7<?"rIZ3D\PGq)Gr2ZZ/-5Y#G)Njlsb+#G%^17T,j#-.h]F^lHMOWn\i5Fph.0<XUh3$t%lMjKd3$"Z,XZ<(eB,0jO]]!/-55L\KZ3CQ0>Pb4s\(eBOeB`2%Hu&95quoj5)OV.p5j&8JnhE$;h:HL1nF7GIHi:B(4;%<Ii9!,dmk^&'^I>PC'nm:bT8EDhH?BX=SMdOO,WDW(C"aLl.H)fK+.&WILD]gEYIYY1(V5#4$6K_9=)]2@BR#"p4B%Vpr=XhYcbY/KVBW[bRE(>Fm-Y4:_'Cpe!LhM(W<S$n-heQ8*`d1m$V37H>=]uAERAK9lV##!cpY.I[5`&fE9P6V/m(3s?[b.[CGgL$=-4#pN@AdgJ.K$42B5N]uF<YUJXG`(bnC%bJlAWp;1gMm<A%'t1D@haO:,Y_1=%8:Suh5o"UcHKaD$-"0uL!:aFfd*-*9U@Hbq(+sW[oouTjESX.R%&f1i\W1.fGL/hk_Cu!S,@L>0[AJU!u7&p>'6h1.+i.PqDs)<-Qi#B&sEDEiFPGtr2N<%C=?1,[e&@Q$t?pV6PB3D[$CI(BC1;AD]NWD-<n0b?4D-2BBRs-?&Z7-ALe7NCL,0#V2t7KikOUNCL(R`CQ:Sq?7gJsp60U"'Mbnh<!U$H>[jgF^"mF6$U2Bk[q:.=jZu`6+XlH$-q@XjiM#JC/Vd2m]8HUlU>1UO56sG-"J<FN^.tCt>%r9h-Y\la5:ds3ILood&7BlM@/[D85-l1Za]^g16m/L!2V$qnUV^TsM2Y!i6'):<OKLoeqHGhh'uk^_3a.mJ$>lR8^U:)F2pY;@`kuJL)urk'L[_&'7g7o*kZ']P*Ca;N!O2:IQIgHSe[1:!47)QEQeZT7a&hnc5Z@h.mOKs$]lb^B6C3Tj4N&,-6>9*fB*YBQBM1/^e=P+Jrl$QGFp[aWcR=(IMd(sNj)b9grNMgR2TrP#ZbZGSrgWN;~>endstream
endobj endobj
21 0 obj 21 0 obj
<< <<
/PageMode /UseNone /Pages 23 0 R /Type /Catalog
>>
endobj
22 0 obj
<<
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260518125917+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260518125917+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 2 Lesstof) /Trapped /False
>>
endobj
23 0 obj
<<
/Count 14 /Kids [ 4 0 R 6 0 R 7 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 15 0 R 16 0 R
17 0 R 18 0 R 19 0 R 20 0 R ] /Type /Pages
>>
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1862
>>
stream
Gatm<gN&cS&:N^lqB[R<=c__I_ltf+G&HX"<8thg;RPU&N(,Q\RSa&ffmNbl!!6\"1==6b+V454b]KIpb^e].<S(acE&N<32[gOYd0,h4Hl0PLf-BaR8%@WR*rln1MMM@kK_p=Jl59jQOV*Wk-lN^5qVWknP_p/oe-eVZ6EXbfgg`T98OMSA;(Rf$DWhHP3Ch[b$)EM$l8S5^(LBL?'-2g1s.V64?k''cWpg=+5o@CN#"S,HHoZtslr4eCNO9XO/p!T=P]4Te#cHk+VaZ^`V&mN)IBPFgT4q4fK@)90Si^:Bqn=^T'`-e"rHGgQ%X9Qt4/o>g`7*5S^j9TVig-b68D_96"X@Fi#X"!8qs?u)RsBH"e=0,T_aQsQQ0q4^o;*)4>>%N"G/9Og1^KJtmI!Oi65Dk&NNoFJC7VbV'!M$ho,;9/*SOYkS[680(e0837_\\=G;097)\5`40Vu9Ell7fM<&p^m2\(V6$T';!e]5s&659r/LRgqp<9pX!j06"N(41C`G1L?im!iMh`W?c,VH=)I-TkcKQHH5EgnucXP;h\m\luqgi_?]M&f;"HKn9[<0gnXOW0b;"idR81o?rXV>i^(A"tGpH'ha@RVBZ^nk(5kAT,&<u^;>b^Y;X/5_HSU!CBI"ggFPFH9fe3U<'ue6)QrI:WQh2lKC2(H4'4IjJX-<<T=4mZ4BamSm1BOq.j2JN]9PQK$ag=NXFJhN"r#9*EuWpPW>P6j976L2>1!5H,0V^L,suOUFk_sue@I^K6L=s9_`ZE43&T1*Ta^V`!6Q'0h>r>Z]qG:o`PpjcL)O]o`o.?/Ni^?#UOU2H=Y0-EB=hR<G9CM!o;0aq"/,"7UojAm]!s9X]IqLY\$+\M0$.p70:2"$qtBUQioHXgBsA9"`s9Ggobq5Yj9f&X6+qFCaE&k_c'dP>>J.MJpe\d8Hl:I6Lf=eL5:6H)U=S6]n&_uWNXTKHSS@00UkC!dkACiNbm!oL[`tX`#;Qj*]t&)*]-UYJ((<brlRs:WqjeVe1fk1B,!gYOSMb$8hmCq/Q,1C9&PCD>VS:J.h?7`SlZ4c:'5b`.Qq0Ui7<.dXL'-cT,c^$aVOt(VNaoPugW3c[`6;XaZE5DiUWfQ:SQJl==5@?_=Q:<\cGrsu%Gs7VI]_'G0t@/Hi]4SZ@0gLlR]!W^]!..Rgs0<,]?YPcl6#dH-$+*&i8l.9CgNNRQ>6AV/2ekgM_lBjbc.91TDJFIZa^ahi]<YT]0^7]kd#"klZBbm5RPCWmSOjY?nt<;<9jr>2TK^dE'^C60t[X;Ok[_k8?A5T;jce^juT-2aR7Un>?$Vao`t))iNbF#72]a+cI)<5)M:bu56Mhd:*>/r8l8/clIlWSfs/8O+7@6trOp/-J4Xu_PrO4m7D<Nc<HknChPL^]HkD)Yo)36;jU7]g!^#:#f!8#YHs+-$n=%YJc&H"f[rQ%Q%W4>pHk&o"j8g`Z/^C![3[7j=oP*o3d;u];V.6BAMOgFZm$;XJ\W6K/X0Gjt"TR*m!Kr[.#YI8\A^Am[TcAJEL\mgt[N%c-h,X,T+Ng8Mj?`9l+o(,Ipc=9nBrU>#S>7Lf4tIj2ruf,T+i+$SJG!O_4L5u#adP>q6m/L!2V$Kg7YW_R$Q5)tK-1VX+C2P3['sdn.uaGHFgOp>(#"E:T=1i)2eT'1`l!mt*.j=@L[h,(8-L3kkZ0cq*JRn;!P%i6QWJM)jg9u147)TFTA20i.l[sTVu[IkIOFkYk6VV(:[q.dhktdW</N6uYC!P0Q'6tHQtQK&4o+1`\$p5j/p+Xk_gQ@Z'M;TH5<D+NL.`@(dae(Zjo0T^am9Z&0ofVOfl]]<!Ae7Sfal8/DN3=16A"qR$SNd8~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1814 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1814
>> >>
stream stream
Gb!;d>>s99'Roe[3;^(i9nXhMj?>.'D932bg*,k/m-l2=`JDT5C-,k5^OD71fJ-*nd+Mh387Gf3Nm@ImSXV)'peRC:X8r$:$A\(!:^5B/d#`o74bIrWn!cgLb`XX]&o^'+)3?[oIo,:!JLDjbb#/T%:C'`_#WX3$V]6Q!9+m[Y.:N50UHk!p:O!.udjXAKcMW42N8`2.M;gMnEW/ElmaOm$][75>Cd0<VT:d-uHDs2$Hl=:C9FEiTGlDgeV`'lAG&=@906J_MlP_An9AjJ=5,p)4eT(jAE\4\)(=L^9@!Vq(f5@^Ar%O($RY8dgq.Hu7#5DEQG2>ji,][H!Y^r1f#\bY6AfElHT9CDe'ASHMnb*%&Ka2$+`_^n2,40)/VEL4Vh[fNjN:G.k+e_KOL*KRi9]l"XGX=Ee:/jHBicAf:^$L9Xch%Qd!uWT@SSO;G?+R`6pO`<lQ-Aj/>%kS4Q/#($bnsQSH.Q@i.JM`fr8DumSVFa2i>ZqKG)Z9NEQS?U-o^&rDg%'Lp4^j/jr]Wdq/IWD-#?Y3a@J]tV!T2uk=]NLm7`*e^jHptN!=87pG?iL#OU\,\aeEFDqrl]h:3r2=KMWR%9U7a=*@SU''TjnSLKb`-R\?g`'3UbrrtZcqK-i`<@u@N^^"!JZ)D)CD$_!p;psoWn>Wd123[-(jMk6.64[f;IOe']:rjR1l:7$M*1?Z7ZqH[Di.JGJQ?XtJ[h3;c&6_DO)JY8sXd+EXX[OC&J<ttS-+O-)IY]3I(l6&B#^qtL0"&8n>R=7O+=<;q)MG9OGZ@dX2#?UE(+[>M!BC5"UO7El;LOph3#a:-2n(m*oI@(/U3?,g9'GWOfB;mr`U91;8X&4A-.2T6MS@ZF[4AHrZP>Pe[:L)[/8Ebg>'/+h+Eq'p9/As5[*9@rAaWQQ6bo>?ibkkG>25<i+]mRi1"n%Or*!(@*b!t^LoC1hQU\.=XAh.M#;BQbo3p"JogB3@Z;N:?n[_]0Z&Qrna'gF?\]bTl(G`q/[Y:ZF2'Y)P*$1`2@&[4p!NV!kGXa65$&[uLPY`*(+jJa^&5HcDW9k3M&&(^i?b&-SrA)3i>*]>(IXV$,8$1,3KsiB:LS=+OI'aEG2Z5MTn"A.qB^'VC$SrG?#A_%HBNVr<Y?cR.nCL%p8*%KiK#%$uDA49V^DOdo$KK'A21dVq<rW^jZ+!2,0l+(G93@RX,SB<\[^kpaPRu54A^11"Sr%sa$>[G0>[,DZ\7(GSpEBFQG`N%!Za%2Jl\T_5Y:rOXA\8uWZndQ>Fo*\G,m+>U")hRb2$Q_/dSf2Yof1DM>AW:b<\+!-n%XDFGG=5@,cARIB*XMbbo?iq,&:2\UstKm<gpqSg0%>h/[S]?7HWlo_c#?!W-m:/nb?;;YS9Rl*Af><U"j14dMC$;!oqES/>bga\!;8r#nboQ9PbGC=;kG$>qui$_j.N`Kg_S+J[I+ZTQ9Y/6,[0t>iXCEh2G*0rUrK%IK@Q1@WHa*=1>]]?-:o/-oC;+>%rXehGji%_k9M2`ZY6[1?21%oBtm0N(n$5!(<cHD'"DuZ/(P>3=iBAASiAgG[ZcB01,Lim=j>>'=U@-gQIs!Tq;``GkXnooj_Dq;Ep%P,H;\Mj_10Wcq\hkH"a6E2j-09P?nG>$0Qrqr`5E$?-BY>Hu"ALe[:n(>I-KKgS+q72B1WDld/Rn1<Qi[8%[QGT;I/tb5rB:o=d$u$h]8j25[#EDe;$Zh9cPnk5MIZ%Ogf?q8mcthbqeJ$)GCX(E:E`m:Bmf!qW5d^$:A2'esmd<mKD]H#[gbJuYf[X%h^RA(^t@Ig>D0OT~>endstream Gb!;d>>s99'Roe[3;^(i9nXhMj?>.'D932bg*,k/m-l2=`JDT5C-,k5^OD71fJ-*nd+Mh387Gf3Nm@ImSXV)'peRC:X8r$:$A\(!:^5B/d#`o74bIrWn!cgLb`XX]&o^'+)3?[oIo,:!JLDjbb#/T%:C'`_#WX3$V]6Q!9+m[Y.:N50UHk!p:O!.udjXAKcMW42N8`2.M;gMnEW/ElmaOm$][75>Cd0<VT:d-uHDs2$Hl=:C9FEiTGlDgeV`'lAG&=@906J_MlP_An9AjJ=5,p)4eT(jAE\4\)(=L^9@!Vq(f5@^Ar%O($RY8dgq.Hu7#5DEQG2>ji,][H!Y^r1f#\bY6AfElHT9CDe'ASHMnb*%&Ka2$+`_^n2,40)/VEL4Vh[fNjN:G.k+e_KOL*KRi9]l"XGX=Ee:/jHBicAf:^$L9Xch%Qd!uWT@SSO;G?+R`6pO`<lQ-Aj/>%kS4Q/#($bnsQSH.Q@i.JM`fr8DumSVFa2i>ZqKG)Z9NEQS?U-o^&rDg%'Lp4^j/jr]Wdq/IWD-#?Y3a@J]tV!T2uk=]NLm7`*e^jHptN!=87pG?iL#OU\,\aeEFDqrl]h:3r2=KMWR%9U7a=*@SU''TjnSLKb`-R\?g`'3UbrrtZcqK-i`<@u@N^^"!JZ)D)CD$_!p;psoWn>Wd123[-(jMk6.64[f;IOe']:rjR1l:7$M*1?Z7ZqH[Di.JGJQ?XtJ[h3;c&6_DO)JY8sXd+EXX[OC&J<ttS-+O-)IY]3I(l6&B#^qtL0"&8n>R=7O+=<;q)MG9OGZ@dX2#?UE(+[>M!BC5"UO7El;LOph3#a:-2n(m*oI@(/U3?,g9'GWOfB;mr`U91;8X&4A-.2T6MS@ZF[4AHrZP>Pe[:L)[/8Ebg>'/+h+Eq'p9/As5[*9@rAaWQQ6bo>?ibkkG>25<i+]mRi1"n%Or*!(@*b!t^LoC1hQU\.=XAh.M#;BQbo3p"JogB3@Z;N:?n[_]0Z&Qrna'gF?\]bTl(G`q/[Y:ZF2'Y)P*$1`2@&[4p!NV!kGXa65$&[uLPY`*(+jJa^&5HcDW9k3M&&(^i?b&-SrA)3i>*]>(IXV$,8$1,3KsiB:LS=+OI'aEG2Z5MTn"A.qB^'VC$SrG?#A_%HBNVr<Y?cR.nCL%p8*%KiK#%$uDA49V^DOdo$KK'A21dVq<rW^jZ+!2,0l+(G93@RX,SB<\[^kpaPRu54A^11"Sr%sa$>[G0>[,DZ\7(GSpEBFQG`N%!Za%2Jl\T_5Y:rOXA\8uWZndQ>Fo*\G,m+>U")hRb2$Q_/dSf2Yof1DM>AW:b<\+!-n%XDFGG=5@,cARIB*XMbbo?iq,&:2\UstKm<gpqSg0%>h/[S]?7HWlo_c#?!W-m:/nb?;;YS9Rl*Af><U"j14dMC$;!oqES/>bga\!;8r#nboQ9PbGC=;kG$>qui$_j.N`Kg_S+J[I+ZTQ9Y/6,[0t>iXCEh2G*0rUrK%IK@Q1@WHa*=1>]]?-:o/-oC;+>%rXehGji%_k9M2`ZY6[1?21%oBtm0N(n$5!(<cHD'"DuZ/(P>3=iBAASiAgG[ZcB01,Lim=j>>'=U@-gQIs!Tq;``GkXnooj_Dq;Ep%P,H;\Mj_10Wcq\hkH"a6E2j-09P?nG>$0Qrqr`5E$?-BY>Hu"ALe[:n(>I-KKgS+q72B1WDld/Rn1<Qi[8%[QGT;I/tb5rB:o=d$u$h]8j25[#EDe;$Zh9cPnk5MIZ%Ogf?q8mcthbqeJ$)GCX(E:E`m:Bmf!qW5d^$:A2'esmd<mKD]H#[gbJuYf[X%h^RA(^t@Ig>D0OT~>endstream
endobj endobj
22 0 obj 26 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1690 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1690
>> >>
stream stream
GauHK>BcPr&:WeDbfcTAp=+sF,UAt)HeE;ce'lL[:%oea(s<Bu"q'!sqs/HZ!+X'&[L\?<DP$[d)pL&m@R0J$RU`u2GW(?KQOF^dq$NK3!p4FZn%&23,<XmV80GXDI`R(n^AW\)m:@CB&qiAXTW',=#nWO;X[<O;oornm8cu-7*Wr,<WjDc#R1+1V:Z!N#k@e$1)$Jp'I`kMkH6ESuG"C)1&I]sflVrr1$UU7\'':,mHi*6r!aau(;oL\@T#/OZ2"=9Lf:4@9H80bM<Cp2[-+konPNKj(AG%17Itmc/CMSGR6pO'be04f1;ZR+c<cu,sW:-L(P"":>?p<M3FuTM@Gq,hNmo+q6G]:>Z+4isY+qOck4>)K&m%:*q=+jJF#LBntV$(3Vi56qJPDR!P).##AR"afIKd[a0kcoQuA9n=5K,=o<f;Zg[=18-kp-?!o&pn`H,/p%CA?4Lt1%7=!DS.<mm)MQor2!,Y-?fQdg[#bFN)@GLj$#08osN:D`:tH-KXkYX4qI.8#!XC25/,rCZ4PLY!.Y"<!d5=@;fu=-`CLX6aXWF!QNRu**U;UYr`u"5dD';\RPZdN4s`Q_qu3:E8*<W?LhUBpIfVP<-@s]=Y9n5-8N.0!q85C=&Z/9U:W+(@;$BZfT2pZ&8*U/NP&SG8_O(>$-+Q@aOSQTNr>_MjBLe(Kp]*8;[hZuKn[@5@.=/,oASmU[Pce%UBo3(1QUGe.^+JoS>Y\.7;6J@2p9m?A-.US6oX$)Ok+QiT=fj@`]>WK!EG[SiVbrUCD8+S_A8.3dppatgl<ROZWIZ@!S6:39*$n*\+b,p4Pus:H?"L;jHEGI-Y1m<id]?,VZIi,Tlis<eqiHBt$21)^'TKEIbf("f97lenW3d-U=m;MbX#8dN^VW!!W)]TY;f8fmV,Jq#-M+<j.Tr4Z3('[8puO"_F^q-l0&qbESmSPi3gu_ib.TYWEQ8:BZLLk"hDP`[D<lRM("(i:#[>8>$EY:GJEsP2-sLS`3a(KX7Z6S&A#roJ?H-PSf[8[gfr\.GX9hY\O&9q,cV.col%,r'0!FgF1U]0C\W:T\j6Ykd;jbSE!rr8[&IEZ0T@iMrUV0C\&t@C+e'j2]ZQ;UcCfDbcKU0<eQ\j#A.8soG[jf5=%AC5[SUHC$%*KRER1ulqrcujZ:\IXO_h%L@13up[p\Gd?-<op>@F5ac"2`Y`WDSg<$V?)::N,P_TS(q8ijds]jgo`HApK\kK?@Si03oL`h)]UGG\p+Ys5>_Fq(c*6P0r4RhreullR+iEIW>Ab"VaO:Csa>E\E_qS,hqE8^6T!-pt0@WJ"P>`Xe56sWRn1!)tCCn.bb0d1KGSJ=daS?4QQ$NFSM4#mAkDm@tU@iX3o^?b'M[T>^_B5PeduTdk,IHmSQ*LA,G)U<.dO-a24qFKFof?-0*:TL,_m7]15:A/<;9"jcO"l'FOT-dap_p2SB#s^m,K"DfDbE%dlZb_I=$"q&UQ[A(8Irg*5N&m8U#QMX_t^BA*^&,Ah\FE!)^R6g">UZK45VYl'969+g/1gieC#JsO<Z)fOMVr55?E6d&6DSZ/C"2hc[m+2HRAm(:+0F,S(ESsL9o]X$!'(,V4cbIgrC&IqE]DM[=PZCr?c9flDi\BIW]BAM;$iJdKKNN.7%";V1Tar0QcWYq\m/K2%gnF?ED7T'Z-rrLYKr6>~>endstream GauHK>BcPr&:WeDbfcTAp=+sF,UAt)HeE;ce'lL[:%oea(s<Bu"q'!sqs/HZ!+X'&[L\?<DP$[d)pL&m@R0J$RU`u2GW(?KQOF^dq$NK3!p4FZn%&23,<XmV80GXDI`R(n^AW\)m:@CB&qiAXTW',=#nWO;X[<O;oornm8cu-7*Wr,<WjDc#R1+1V:Z!N#k@e$1)$Jp'I`kMkH6ESuG"C)1&I]sflVrr1$UU7\'':,mHi*6r!aau(;oL\@T#/OZ2"=9Lf:4@9H80bM<Cp2[-+konPNKj(AG%17Itmc/CMSGR6pO'be04f1;ZR+c<cu,sW:-L(P"":>?p<M3FuTM@Gq,hNmo+q6G]:>Z+4isY+qOck4>)K&m%:*q=+jJF#LBntV$(3Vi56qJPDR!P).##AR"afIKd[a0kcoQuA9n=5K,=o<f;Zg[=18-kp-?!o&pn`H,/p%CA?4Lt1%7=!DS.<mm)MQor2!,Y-?fQdg[#bFN)@GLj$#08osN:D`:tH-KXkYX4qI.8#!XC25/,rCZ4PLY!.Y"<!d5=@;fu=-`CLX6aXWF!QNRu**U;UYr`u"5dD';\RPZdN4s`Q_qu3:E8*<W?LhUBpIfVP<-@s]=Y9n5-8N.0!q85C=&Z/9U:W+(@;$BZfT2pZ&8*U/NP&SG8_O(>$-+Q@aOSQTNr>_MjBLe(Kp]*8;[hZuKn[@5@.=/,oASmU[Pce%UBo3(1QUGe.^+JoS>Y\.7;6J@2p9m?A-.US6oX$)Ok+QiT=fj@`]>WK!EG[SiVbrUCD8+S_A8.3dppatgl<ROZWIZ@!S6:39*$n*\+b,p4Pus:H?"L;jHEGI-Y1m<id]?,VZIi,Tlis<eqiHBt$21)^'TKEIbf("f97lenW3d-U=m;MbX#8dN^VW!!W)]TY;f8fmV,Jq#-M+<j.Tr4Z3('[8puO"_F^q-l0&qbESmSPi3gu_ib.TYWEQ8:BZLLk"hDP`[D<lRM("(i:#[>8>$EY:GJEsP2-sLS`3a(KX7Z6S&A#roJ?H-PSf[8[gfr\.GX9hY\O&9q,cV.col%,r'0!FgF1U]0C\W:T\j6Ykd;jbSE!rr8[&IEZ0T@iMrUV0C\&t@C+e'j2]ZQ;UcCfDbcKU0<eQ\j#A.8soG[jf5=%AC5[SUHC$%*KRER1ulqrcujZ:\IXO_h%L@13up[p\Gd?-<op>@F5ac"2`Y`WDSg<$V?)::N,P_TS(q8ijds]jgo`HApK\kK?@Si03oL`h)]UGG\p+Ys5>_Fq(c*6P0r4RhreullR+iEIW>Ab"VaO:Csa>E\E_qS,hqE8^6T!-pt0@WJ"P>`Xe56sWRn1!)tCCn.bb0d1KGSJ=daS?4QQ$NFSM4#mAkDm@tU@iX3o^?b'M[T>^_B5PeduTdk,IHmSQ*LA,G)U<.dO-a24qFKFof?-0*:TL,_m7]15:A/<;9"jcO"l'FOT-dap_p2SB#s^m,K"DfDbE%dlZb_I=$"q&UQ[A(8Irg*5N&m8U#QMX_t^BA*^&,Ah\FE!)^R6g">UZK45VYl'969+g/1gieC#JsO<Z)fOMVr55?E6d&6DSZ/C"2hc[m+2HRAm(:+0F,S(ESsL9o]X$!'(,V4cbIgrC&IqE]DM[=PZCr?c9flDi\BIW]BAM;$iJdKKNN.7%";V1Tar0QcWYq\m/K2%gnF?ED7T'Z-rrLYKr6>~>endstream
endobj endobj
23 0 obj 27 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1726 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1726
>> >>
stream stream
Gatm;bBDVu&Dcq.As6^`[:'%<5qneXDA"<[3I+b%]#Y)M+Ghuu$45C&^V0g>+\J&poX'oX'&[0[%WLSS"r';G?df)7C'#3T(bcG`3A<`%9]!Y08an;S>k1ub5XIoWKEFe4pBU@8-m=aA-9P_0BM;AV5U]*d8ec5L(WS(]g;*W#^js@d`_$5./<WUh=s"C!3LU^/Ott]rhr.KD?:&1X%UleQ</)$eF)'&?$:7l07kcCMo\KDjDr>cG0MT-[WZ6Js[7Q]#^2b:4jdYkbVTB`lB(3[mS%Mo>VR/S(j;'(<SZS$'+XBWg&T?ubCDu,]f>^k_<pL"(a@ZnNUuin>Bs1%t%VJHM#t#Wk;1*T&K+RcGKgH+Q7DL.r4q;XZ^gf^1:np%R3[m?s/GXN;l=JCOR)90+(iu^3W<D=G#R9*.CLm\MrBuMCqr^(Lpm,c'dQnHcXJ>mQTZD+,JF"$^DN$\RK4WsijZ`(R?bCF/n_BBtFmKs9(o)a7`^=.42cPq1-Ph`fKs5`iGOHdTdd'[7J#m!r'!VRR];>NpY02a(FdC?+IPH>Tnd;r*1L+I%AreZ5.QT9CgC\C@Lh5PhNqU?YKSKU&%<s-p<&:jlID:3@AKPXM^BY1Y%;li72u%i5b$WO)/<-'%YRe`uZV;AR'0V&L!,LL9CDrD+P5G;&Y"!rM5VTt-X_mO>a"kNeD8G0+#4O'P%;$G?40Qg)0*2e\%*^RmEs@QVXRMZ[dkc5&e%)aJP/+I[-(%hI)gph?S#+5JS?FVhgfIJbdopB1-@m)EVK_D!hUHbH/@0Be,L/&jaB<N4f:?F.EHYsVHC\7AQ[*Ja3e'H]P/'f'XKu+nXqXs/TdQ^\cUFPN=Te!6*l^A(gF*[ZgeXdOdiV[T78'"`;-fcq>h6qq"c-9hV1M$o"3g%-kQ^&['$AHQJoifajI;3"$!%kI;%iDYG'm&)/`-%n,<jP)$$D\7]LDUaFoq&rHCQE8iQG.JK.!Od4n=:P1f4r[:r9K;-$X?l[P)OJj6/4hj.NMce8JQ`=GGJe6cg@SQ>j0W#TrMsJ@-X^<&fsQVQmA*F_\7)6OU0B*I#(>^0LL*e%*_@B7Nc.N1tA`/-9at[M[lP&L05m"ieAG2fl0,8S"<OQ9!0KR8%gM8$F*["7_XL!?DTRSNFRV8FGuUHNR2:m)L[TGUSFn0)rC#HF'37C\6%5a.kn=Z'@Dcdm$]#7LVNE@VD+>g?(e4^/'D-Y)Pb4G4+01ToSF"ik$C_g"U-%BWi\)D/Ep^Fg8\[8f0jl_ml'2U(=V14QOGiH\5^/)H2]MGG?KF$nLo=ESB%",LAL9:ONt.9r^C/[9IqI7I(j1lU'Tlk9kdilVhY4T:bIOJc/9p@RNtZ>3AC0G1'I"_5`BImTM@e_:Pf8dAHiM38s_N?oDt=d*"fGOqq>e<pKig#&LF*[AbtKV@EH/RCd2k_d'N1L9Fm.ni=V3Wa.X%[%V?sF,@%++lhC"JbC&^hdRi]`C#S4NkZr.s3!`;N5;FXJ4/nRa%WaHGO1Lmc/D>>(cJ'm.Mo@:"[Wq7!rk-Cq#7J#XOZiQ+I$BjLRS=QY8MR`\e@l+g-4g9DV3.kiULTgN?X=+,5e`@O*X&EJYC2$;I*J9F_,PmSREKTa0jV5oF14?4i9"?mDXB/e&W(7]tAgiDK8+%4mDmLs&c9kHiNP<d2/'fEJX7%!&6Ii]-:on3A^IOPaPZ=?tHEWZ)6uAJ`%X4Z'"D~>endstream Gatm;bBDVu&Dcq.As6^`[:'%<5qneXDA"<[3I+b%]#Y)M+Ghuu$45C&^V0g>+\J&poX'oX'&[0[%WLSS"r';G?df)7C'#3T(bcG`3A<`%9]!Y08an;S>k1ub5XIoWKEFe4pBU@8-m=aA-9P_0BM;AV5U]*d8ec5L(WS(]g;*W#^js@d`_$5./<WUh=s"C!3LU^/Ott]rhr.KD?:&1X%UleQ</)$eF)'&?$:7l07kcCMo\KDjDr>cG0MT-[WZ6Js[7Q]#^2b:4jdYkbVTB`lB(3[mS%Mo>VR/S(j;'(<SZS$'+XBWg&T?ubCDu,]f>^k_<pL"(a@ZnNUuin>Bs1%t%VJHM#t#Wk;1*T&K+RcGKgH+Q7DL.r4q;XZ^gf^1:np%R3[m?s/GXN;l=JCOR)90+(iu^3W<D=G#R9*.CLm\MrBuMCqr^(Lpm,c'dQnHcXJ>mQTZD+,JF"$^DN$\RK4WsijZ`(R?bCF/n_BBtFmKs9(o)a7`^=.42cPq1-Ph`fKs5`iGOHdTdd'[7J#m!r'!VRR];>NpY02a(FdC?+IPH>Tnd;r*1L+I%AreZ5.QT9CgC\C@Lh5PhNqU?YKSKU&%<s-p<&:jlID:3@AKPXM^BY1Y%;li72u%i5b$WO)/<-'%YRe`uZV;AR'0V&L!,LL9CDrD+P5G;&Y"!rM5VTt-X_mO>a"kNeD8G0+#4O'P%;$G?40Qg)0*2e\%*^RmEs@QVXRMZ[dkc5&e%)aJP/+I[-(%hI)gph?S#+5JS?FVhgfIJbdopB1-@m)EVK_D!hUHbH/@0Be,L/&jaB<N4f:?F.EHYsVHC\7AQ[*Ja3e'H]P/'f'XKu+nXqXs/TdQ^\cUFPN=Te!6*l^A(gF*[ZgeXdOdiV[T78'"`;-fcq>h6qq"c-9hV1M$o"3g%-kQ^&['$AHQJoifajI;3"$!%kI;%iDYG'm&)/`-%n,<jP)$$D\7]LDUaFoq&rHCQE8iQG.JK.!Od4n=:P1f4r[:r9K;-$X?l[P)OJj6/4hj.NMce8JQ`=GGJe6cg@SQ>j0W#TrMsJ@-X^<&fsQVQmA*F_\7)6OU0B*I#(>^0LL*e%*_@B7Nc.N1tA`/-9at[M[lP&L05m"ieAG2fl0,8S"<OQ9!0KR8%gM8$F*["7_XL!?DTRSNFRV8FGuUHNR2:m)L[TGUSFn0)rC#HF'37C\6%5a.kn=Z'@Dcdm$]#7LVNE@VD+>g?(e4^/'D-Y)Pb4G4+01ToSF"ik$C_g"U-%BWi\)D/Ep^Fg8\[8f0jl_ml'2U(=V14QOGiH\5^/)H2]MGG?KF$nLo=ESB%",LAL9:ONt.9r^C/[9IqI7I(j1lU'Tlk9kdilVhY4T:bIOJc/9p@RNtZ>3AC0G1'I"_5`BImTM@e_:Pf8dAHiM38s_N?oDt=d*"fGOqq>e<pKig#&LF*[AbtKV@EH/RCd2k_d'N1L9Fm.ni=V3Wa.X%[%V?sF,@%++lhC"JbC&^hdRi]`C#S4NkZr.s3!`;N5;FXJ4/nRa%WaHGO1Lmc/D>>(cJ'm.Mo@:"[Wq7!rk-Cq#7J#XOZiQ+I$BjLRS=QY8MR`\e@l+g-4g9DV3.kiULTgN?X=+,5e`@O*X&EJYC2$;I*J9F_,PmSREKTa0jV5oF14?4i9"?mDXB/e&W(7]tAgiDK8+%4mDmLs&c9kHiNP<d2/'fEJX7%!&6Ii]-:on3A^IOPaPZ=?tHEWZ)6uAJ`%X4Z'"D~>endstream
endobj endobj
24 0 obj 28 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2485 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2485
>> >>
stream stream
Gau`T>BAQ-&q8H9fGRJ_:*RLS#_JjbEi*a6>WQTYA*Gg'IXt']U.pb$8QenOm;MFp]ZQA5SE\6XQ6h,*G-T]up-o6#5Ks+].tF7)E;#"ej"Qr)'1BY&"'j,@^Q/Yeg_2:Oa/;\JJHV#/K46Q-_3ICm4CI)GO8,:HiA9KjJI`ab4HFbaBUD"+0d*'`ShGWhba#p?L>F):F50q1=3BGUI3YA&`!_B;UdHm&Iq49a=ECF$X!7m]Y33Z<"fEG,rUq;hAIDdub:H<E5?4DpAHb#_HZ8M(E`&-B=m62ulN`tkq+@R81RR"7m((eb^g@@=!`+'Qr'0BX38e^XAb2AVQ!2,*!dDHBhl+thO5u:j*qbBM:_-9?:u<K&p#8&"$43aFG=asak)p>N#c5B[b]1C#,ma)f+3qC`RW-d$iIL`_)-5QmJEm7Q3o1@If5q)$'-R_8Nft'mVlYA=4%?U3,ok<=M?IiY+WKG/(k\$-UZKQ`b'[sIT=lu+Z.P>[DpU5C(o)c]N'"h:YN*7/[?qf`Ks4b*O08ZbdddnSs-CFX`+8tdG)-+:gWPYa.9T(s)O@sdCd*otpr7<E6mK&T3k<dPV:+QE7&21lL`!%CqL9=VppE1f=/NAU)dru7`$MKaE?Ab]']Khp1uq)KVo*7IA6B(5XPm*\doT\J6*72(V5b@Q`iR_4]nS46/,c;r7&d+/nB%7ll_KgGFIj4<6f@N.SsW"_4QljmWUI7N&c"n!Lf3/p=6lU)MB>J6TcFm67$hFS(&O7>r1PkUR]b8%W)_:i`T#s#[a?"V/AuVpo#\#GOQ(S.`sr?+,6a/&!HGtF(CM2?RO,i.[tFl27cI8d>oOt?%e=H;>9dMd>g`5@7Hg"op4kV&0aDZa[gPF$1tB/S/>dT%],QC\l-3$JL'Ka)@!H52L>YZ?/NVkQ'X:U#4<)0>j)\);R1UjYH+'?31]eh]^Wm*OFri<^15'e+Yl8MhC'JU?lrL(g*jVjIPpDp<k]B(.OR`d.mgtJmcQ.ZAa)'qk]]UM1i;2hs\?K\,^?R;+G6pqpi:`o$Q+2oKh`u)Z!lsO@r)7gLr)2c'D.6G#Qan_FD```&6;A[E4KN&3+H0bl`7&q^g-KNj6hD!ta33"(G[@/"FtOX=T,PRqWmd4Y<)@Kmj0rg@;)dZI:19*%bGq-)$0Cpe/TWja(3L731WZT20o=-GrP<!t1SanT-C=Q,)/([=6f4N5+7@Bb:?,#i?RcFS^_o(+76H@s3n'V7Zr5jGfDrK)eh%oO@.HEQpQHm_/$!M"Vi)p.U8;_16W/o;40H$([/9A:g^YD2;OH++_7HFQ'+\];Y@dQP$P0LoY<Sn'X]73W9OVRtD6?L`e3OI.SEL3C<o9$B"S7@'rZbM@%3rkEVT,Qp*G&>`8MX.WCiX8,7ZP[1:iD_@f`0j438u!sUGqfq/h%CV%6D8@PZTU(,j8j!?Ze;u?Ze03B1Dd3rl9K,*D'FnT>4HjI]gH'YrM0sWGMhL\K<1/X#l'"'DT5q6FFb+X>Kp'A-7_3Tn*C%l+;2FSEMeE@"n5UZ)r+Wr"tXZ`FcGR79#klq0W3Wr1MU:jr$2'*toH1&P[E%RpK@9$Z'HR'^hdu/(cm_+kNT&b4A#^1%5lsN`Y86([oC7[_OBD%K-&.(L`#L_/-)JoJ(a7_JGL*WA2'*&f'`['jRWg6(ngYk]DKc'\6V$X.QpB60!f&s3p"CB*.V_ZW\popk[HR\LUB9J83AN\B`Zm1<2*'k'@r?1+F9IMq]dQek_PY*HUS#!`+].q+ScL-;L=;djZbLH=5"U&E^^*mq[8XPO[W<?]8LEUW9`-?,EDlC#ba=mW[`R^d12#brC9`)GBrdKj%odr=poOV.5Ip$@@Xc2jPE#`M_s/NYkJ1=#>'W"P0D--bh[\cSKhA0DOSR1&QT)a!0s,'IE5rc<D-_=Vb(ni,V$=%B"Q]OT?$qla(2m>+=V3%F8H[Wgm%.GcqLLU<*$UV?+8/o5g;"Ust0&],i[3?iuc=7a=;:'Zuam<)5Mlm_UNea)8d7mP%Rur6k-5_cGQp"A[l4<lXY"^Sh2P$SJncToAXuE>C"Wm2-07e\D<b2@duhGd0LCU594_0!4qI"jo5./(7!H%:iX*;(Qg;Q5>pYLB+c>pkN(5E<%J2CYNG0aC,_EjolAq@4@1E`F^$GQ*`eSkrQP\Zhq4/NpL[i#R@>i)5?QEe?GZ[,%](@a!&mj-##?uOL?FaE!f<.LlZ8VT`1cgc4H<2q)P7G+&(I=Hkm@A^o%6m;I.LS,j7%riB7a%Ji@hqpUCNVeFN9Y`PMajXY+fIQQ!&MNF`J7Af*Ydf#$ZeD3EHQlTS7cT_jD]3fL=2Z5,;aLmgb6hHB(9dUDFh`<r+&10(laj?P:.&-t<s@)Usd]"#B(l`]$TX>')PC/6cA'2/Y.%72Rs-""2X.]'.d=N5QPT6*ssQ?TMVFM+jH*9&G+("XEea2``<h&OJ455VcJX;`o3r=-(@%0u~>endstream Gau`T>BAQ-&q8H9fGRJ_:*RLS#_JjbEi*a6>WQTYA*Gg'IXt']U.pb$8QenOm;MFp]ZQA5SE\6XQ6h,*G-T]up-o6#5Ks+].tF7)E;#"ej"Qr)'1BY&"'j,@^Q/Yeg_2:Oa/;\JJHV#/K46Q-_3ICm4CI)GO8,:HiA9KjJI`ab4HFbaBUD"+0d*'`ShGWhba#p?L>F):F50q1=3BGUI3YA&`!_B;UdHm&Iq49a=ECF$X!7m]Y33Z<"fEG,rUq;hAIDdub:H<E5?4DpAHb#_HZ8M(E`&-B=m62ulN`tkq+@R81RR"7m((eb^g@@=!`+'Qr'0BX38e^XAb2AVQ!2,*!dDHBhl+thO5u:j*qbBM:_-9?:u<K&p#8&"$43aFG=asak)p>N#c5B[b]1C#,ma)f+3qC`RW-d$iIL`_)-5QmJEm7Q3o1@If5q)$'-R_8Nft'mVlYA=4%?U3,ok<=M?IiY+WKG/(k\$-UZKQ`b'[sIT=lu+Z.P>[DpU5C(o)c]N'"h:YN*7/[?qf`Ks4b*O08ZbdddnSs-CFX`+8tdG)-+:gWPYa.9T(s)O@sdCd*otpr7<E6mK&T3k<dPV:+QE7&21lL`!%CqL9=VppE1f=/NAU)dru7`$MKaE?Ab]']Khp1uq)KVo*7IA6B(5XPm*\doT\J6*72(V5b@Q`iR_4]nS46/,c;r7&d+/nB%7ll_KgGFIj4<6f@N.SsW"_4QljmWUI7N&c"n!Lf3/p=6lU)MB>J6TcFm67$hFS(&O7>r1PkUR]b8%W)_:i`T#s#[a?"V/AuVpo#\#GOQ(S.`sr?+,6a/&!HGtF(CM2?RO,i.[tFl27cI8d>oOt?%e=H;>9dMd>g`5@7Hg"op4kV&0aDZa[gPF$1tB/S/>dT%],QC\l-3$JL'Ka)@!H52L>YZ?/NVkQ'X:U#4<)0>j)\);R1UjYH+'?31]eh]^Wm*OFri<^15'e+Yl8MhC'JU?lrL(g*jVjIPpDp<k]B(.OR`d.mgtJmcQ.ZAa)'qk]]UM1i;2hs\?K\,^?R;+G6pqpi:`o$Q+2oKh`u)Z!lsO@r)7gLr)2c'D.6G#Qan_FD```&6;A[E4KN&3+H0bl`7&q^g-KNj6hD!ta33"(G[@/"FtOX=T,PRqWmd4Y<)@Kmj0rg@;)dZI:19*%bGq-)$0Cpe/TWja(3L731WZT20o=-GrP<!t1SanT-C=Q,)/([=6f4N5+7@Bb:?,#i?RcFS^_o(+76H@s3n'V7Zr5jGfDrK)eh%oO@.HEQpQHm_/$!M"Vi)p.U8;_16W/o;40H$([/9A:g^YD2;OH++_7HFQ'+\];Y@dQP$P0LoY<Sn'X]73W9OVRtD6?L`e3OI.SEL3C<o9$B"S7@'rZbM@%3rkEVT,Qp*G&>`8MX.WCiX8,7ZP[1:iD_@f`0j438u!sUGqfq/h%CV%6D8@PZTU(,j8j!?Ze;u?Ze03B1Dd3rl9K,*D'FnT>4HjI]gH'YrM0sWGMhL\K<1/X#l'"'DT5q6FFb+X>Kp'A-7_3Tn*C%l+;2FSEMeE@"n5UZ)r+Wr"tXZ`FcGR79#klq0W3Wr1MU:jr$2'*toH1&P[E%RpK@9$Z'HR'^hdu/(cm_+kNT&b4A#^1%5lsN`Y86([oC7[_OBD%K-&.(L`#L_/-)JoJ(a7_JGL*WA2'*&f'`['jRWg6(ngYk]DKc'\6V$X.QpB60!f&s3p"CB*.V_ZW\popk[HR\LUB9J83AN\B`Zm1<2*'k'@r?1+F9IMq]dQek_PY*HUS#!`+].q+ScL-;L=;djZbLH=5"U&E^^*mq[8XPO[W<?]8LEUW9`-?,EDlC#ba=mW[`R^d12#brC9`)GBrdKj%odr=poOV.5Ip$@@Xc2jPE#`M_s/NYkJ1=#>'W"P0D--bh[\cSKhA0DOSR1&QT)a!0s,'IE5rc<D-_=Vb(ni,V$=%B"Q]OT?$qla(2m>+=V3%F8H[Wgm%.GcqLLU<*$UV?+8/o5g;"Ust0&],i[3?iuc=7a=;:'Zuam<)5Mlm_UNea)8d7mP%Rur6k-5_cGQp"A[l4<lXY"^Sh2P$SJncToAXuE>C"Wm2-07e\D<b2@duhGd0LCU594_0!4qI"jo5./(7!H%:iX*;(Qg;Q5>pYLB+c>pkN(5E<%J2CYNG0aC,_EjolAq@4@1E`F^$GQ*`eSkrQP\Zhq4/NpL[i#R@>i)5?QEe?GZ[,%](@a!&mj-##?uOL?FaE!f<.LlZ8VT`1cgc4H<2q)P7G+&(I=Hkm@A^o%6m;I.LS,j7%riB7a%Ji@hqpUCNVeFN9Y`PMajXY+fIQQ!&MNF`J7Af*Ydf#$ZeD3EHQlTS7cT_jD]3fL=2Z5,;aLmgb6hHB(9dUDFh`<r+&10(laj?P:.&-t<s@)Usd]"#B(l`]$TX>')PC/6cA'2/Y.%72Rs-""2X.]'.d=N5QPT6*ssQ?TMVFM+jH*9&G+("XEea2``<h&OJ455VcJX;`o3r=-(@%0u~>endstream
endobj endobj
25 0 obj 29 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1620 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1620
>> >>
stream stream
Gau`T95iQS&AIa;%#goWK4q[]?2Bj0:"ge.h+#X<(Rh(03M\'eXFm6<IsYgpjH>fTYtb:#OsP**o?I.u1SFMRr]Mh5?7o[VRK;HQ]E0F:K]QG5IMps2KPPJ$![9010aA=!pOI!!D*_m1@idG1e.?C@8BZgsYm$/8`\7Kp#$9+p#/k1iX9<"#dT/Tj(IW>)U*WZ?a8>e*Ib(J7'A[AXC:5bu6OMB^'i9T0PUWA8=hsBj?f1kh5EtK8:9VhrLHh4W9<T<0^q#"45L*WcoqlW4'th]WZFns-fs^\dQ8c$qk%'J@V:6bKdbMlmS(5:Ths7/PC/HXIok'\D*l7c/0(!X8n+8Hd&g=3i:n70%,g\2F81c\5&Qbe,)U@o0)a^O2UGHdL.S6#TI!hZ7RPY@apk;kj*)nnTOY0ZGYQ\fVJcbm:M@0(7o8)A/D"s+s+<%&O&KS7"nKKC70aBL(QD`,QjAX2agW2$S]@?A]X,65L@h^Xe$+Gj$@#->7Z9D`4'GnVi2t)6Vn?p;t$%Iaa3Dh?I[(V6%m]p\5Xtk#rQeODCdW0]B=i7_QbGo$()4/lPae:9qa`0D9-OXK;SH^=JGc/dBQ>+e6b8a.aV5#I]90Lf.-l>,^rg_/.o3HkdM6r%/8F1X'>-[hT0l_IO[gro?(*3_hVRZ>^\4BYU9:8`FF^4I.n6Lo;)j+#of?7V7*uBY!E!Tuo0C0?]4/Rq;JmDC5AEf5BURbp@r+A=L'FI_Z-*@DsPF%V-P)j9^!rR_$2jdt'Ee0A8fW9dFgD490^V>V]JkpeGM]jd!Q,>\U7gJWe`I_3pXimNX"Bkt\Y>+MQ#rZe5/'I35bOX324:f<*7I?<S^@a7=7>s-[cSjO+lbugK]Ii[uP/_U`pLeasYtgF$3!p_TKM$"!-aopEUOgQ!_mGRJW]uD5b`/6q/\#40;:$GU+OkAh]!&A^oJ7N'kTJ&+?p!"+#@0Fj_4'?K_$2I%g7X5tYER0ic6,Q_0Pu%3qf&_3Ud>V;3,`L3cG%-"SJB9"3LjA6c)s6;%%3`((2[(H)E<(m)OWCJDT;=i"[_8[fARn&HIpAP3A3;Fc.Qg,rmLmrT'G@aV0SDA[g-rAq45]I@D8GoS7u?\)J22q*T=$"MBlo]_96j_Db4To475#-XBKjQk*o@@cst<+]^KV[gR_3MIpqB"ZMRpHYN$,jf(IFiM/f>M>>4L=f`u-5;'g4U:^ks2Of[K?KAc)[pqnd-'kPc):M7urlc\ZU@1&o.B?D@_;5PVnk.3$dRrjeTae,VMNodpud&p;Z4eYk_ACS1?D\m\j'ZjSk0fS`FI[=%l/n0,"5j$HK.Pr)EjO[,JMo3a$6+NNGqWVnBrq?H8n-4P=ai4FGiB;lo8q[5e0NanpBFmqoJ.*6&5_]q5JC-[6'+[W'(%HqP5>4W'X14%hm,qK#97[cG*%#pXImV7pqZM;:>pf7;1`u,s;W<EJplP*8S"3.LEGFnk6_$\*k$;mDB_h84r%s*]?6;4YWbD3JXSk"2XnO^\JBs;1'.0Y"[0pqtGL[GYUN_8jeL#q(rKErcRm>lVB&8UGe,T52ZMNT<On7hT7DdkD^b0>iXP1+`2RqOn%mmNQ>[^R3a^/2&k[-:j#h/~>endstream Gau`T95iQS&AIa;%#goWK4q[]?2Bj0:"ge.h+#X<(Rh(03M\'eXFm6<IsYgpjH>fTYtb:#OsP**o?I.u1SFMRr]Mh5?7o[VRK;HQ]E0F:K]QG5IMps2KPPJ$![9010aA=!pOI!!D*_m1@idG1e.?C@8BZgsYm$/8`\7Kp#$9+p#/k1iX9<"#dT/Tj(IW>)U*WZ?a8>e*Ib(J7'A[AXC:5bu6OMB^'i9T0PUWA8=hsBj?f1kh5EtK8:9VhrLHh4W9<T<0^q#"45L*WcoqlW4'th]WZFns-fs^\dQ8c$qk%'J@V:6bKdbMlmS(5:Ths7/PC/HXIok'\D*l7c/0(!X8n+8Hd&g=3i:n70%,g\2F81c\5&Qbe,)U@o0)a^O2UGHdL.S6#TI!hZ7RPY@apk;kj*)nnTOY0ZGYQ\fVJcbm:M@0(7o8)A/D"s+s+<%&O&KS7"nKKC70aBL(QD`,QjAX2agW2$S]@?A]X,65L@h^Xe$+Gj$@#->7Z9D`4'GnVi2t)6Vn?p;t$%Iaa3Dh?I[(V6%m]p\5Xtk#rQeODCdW0]B=i7_QbGo$()4/lPae:9qa`0D9-OXK;SH^=JGc/dBQ>+e6b8a.aV5#I]90Lf.-l>,^rg_/.o3HkdM6r%/8F1X'>-[hT0l_IO[gro?(*3_hVRZ>^\4BYU9:8`FF^4I.n6Lo;)j+#of?7V7*uBY!E!Tuo0C0?]4/Rq;JmDC5AEf5BURbp@r+A=L'FI_Z-*@DsPF%V-P)j9^!rR_$2jdt'Ee0A8fW9dFgD490^V>V]JkpeGM]jd!Q,>\U7gJWe`I_3pXimNX"Bkt\Y>+MQ#rZe5/'I35bOX324:f<*7I?<S^@a7=7>s-[cSjO+lbugK]Ii[uP/_U`pLeasYtgF$3!p_TKM$"!-aopEUOgQ!_mGRJW]uD5b`/6q/\#40;:$GU+OkAh]!&A^oJ7N'kTJ&+?p!"+#@0Fj_4'?K_$2I%g7X5tYER0ic6,Q_0Pu%3qf&_3Ud>V;3,`L3cG%-"SJB9"3LjA6c)s6;%%3`((2[(H)E<(m)OWCJDT;=i"[_8[fARn&HIpAP3A3;Fc.Qg,rmLmrT'G@aV0SDA[g-rAq45]I@D8GoS7u?\)J22q*T=$"MBlo]_96j_Db4To475#-XBKjQk*o@@cst<+]^KV[gR_3MIpqB"ZMRpHYN$,jf(IFiM/f>M>>4L=f`u-5;'g4U:^ks2Of[K?KAc)[pqnd-'kPc):M7urlc\ZU@1&o.B?D@_;5PVnk.3$dRrjeTae,VMNodpud&p;Z4eYk_ACS1?D\m\j'ZjSk0fS`FI[=%l/n0,"5j$HK.Pr)EjO[,JMo3a$6+NNGqWVnBrq?H8n-4P=ai4FGiB;lo8q[5e0NanpBFmqoJ.*6&5_]q5JC-[6'+[W'(%HqP5>4W'X14%hm,qK#97[cG*%#pXImV7pqZM;:>pf7;1`u,s;W<EJplP*8S"3.LEGFnk6_$\*k$;mDB_h84r%s*]?6;4YWbD3JXSk"2XnO^\JBs;1'.0Y"[0pqtGL[GYUN_8jeL#q(rKErcRm>lVB&8UGe,T52ZMNT<On7hT7DdkD^b0>iXP1+`2RqOn%mmNQ>[^R3a^/2&k[-:j#h/~>endstream
endobj endobj
26 0 obj 30 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2009 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2009
>> >>
stream stream
GauHK=c_;q&:W67(na.S,W./O'M0m6(296pS#2[u8RE5]'U_/9ZKOib2Z\g8p'u4lII;Zh]p;%qLNCd#'O:R_i\t$@r%`rA$OW-7E"%sE%`?ofI->rt#STmP+,Hh"047$Hci-3kE>%aE&HN7f-s%$-3H.ED;Lnn(qkU##;@VY6%L"&1.hr;`W%XCfg@#Wi(`pqY019c#IjL_miWV_Ld\3&i(RZEMXd!S1HCVLpQTbfod(=Poko=#uM9_ip0en(";]SSR\7A:MZh+1&/=U4]*l2c@X^e\BkTkWd_^cQfZ"J4SF"U!+\_Q9G>%bVNJ$[ph9<N3NaXd`aG1aGpPLb*aX5oMJ,+dsiFN$Om4!**EH!G:h$]"PR]PZ5/LIpL:>]nVbWi.nN;R#SH?dhD<Z\CD<;/05k9**HE4uBo8>1,5/e-ciq=Pq./`OE4&kr+LUk*3r,>:F$/BS<HF2emFkO.<1JKTuG"SfbVLTi8n4+KC<ZNlS(kR%he,ML7SKX28A@itk$T1Z,igpks`R5hIuEcA56u%,26jDrVT/5D/F@/9MSnOmZSGFH1@=O%"^-ga45ZFAG4:=l-MVcm!%,.i&IlIa^l:0!)qnMK9-h$aSW:'UcL,P&Jm/H@HGXH]_G^<llom#)dH'diZAm3J<bD3X$1F"eBJpOY+IA?ToeU]'S!;W/`ck:#.;a=[`_OBj$_&b.YR,*(EE\0\-!FH]N&:J7.3.h;WSEdoq%>RVGpm`rbC(ddGH%D7?m<Y,&b,;XP841W-,X#L-EKrQKH\\IPLOUK!#ZhKGm#6&RFekE*p[l<;*g1eaVm4GA)sIr+,,:A9X%G5*0(E'Gk3FKG4>_8c1"LM.SQfDs7u;67ZG[G2D[h;0!GpW1FjC3.EB$M0ZLWtWs_`VapFm(7M(9uBp@l9lT@g'roK]cYG@1,W,o.bdnjVt.,b;0mNn_8/P4'tZi-[QZ?;E)D.4k=LtpEGX0ZodmHrjL>1p.DgL1G!bJ'9h"-i>g(JE3[$g!_TJ=ZE4D,Yl=eaXLF^toCmQ&R,)FEJfj.H4:#n-H19c;Ioa=eX*F:miq+rkK?:E@7d&\@jp;&XSWDr&ggG3j)ZYVj*XU]5!CULq-VS1g=%m:1*?b!`%@6al`)$0e0I-u9ekZ6TWF.Fl!GW%Q4b5JF6T2'O7)[=ht\5.c*bE`sHdMVi@m)*V+k4pbo)[pQ++X=ZA+O`WqP[m_f#I<T&`h#\ra&p0'58?OI>-(gT;)Q,DkdAg%A$X]f]@SMQN^dC[DXn;6rGcQfNAdc-+A,>BO]nksSX`?^M.g*_S#W\KLZi@+I':q&Bb#'c*;k]U!"_lU-9NH>&=.Eu^/m4BdOF2<=cDQ(S*+AilO8Lic]:"mO)O<5@%)$X&8b>=)/k7b.^CX_A@kB'@##(glGg6c`77`!N#s=E6/X(tkSR(Q$r_i,ZNr)<m\lmcg5,H;dS0ge#T6X\%rb^P#V2ZoLi4K*@)#rqR2S$2!eb!F)SOpm+cWSRp/g2b;30)WF=useb"[^_!0Q@E#G`3f)M<><L?t)!@W5;0MXEh"EJ(LHZ32I%MG03!YB^.dO'3cAF>tI48bb&77LO$`'?&KEE&=(&S-Q@Q8:M,ubJtV=2KN3`rtX[q;U!#m@5old`SdgOC#JsY%e8Mh:4bnRY4r^KL],R'.m2qreko,Wh#o]cf*(h"'Gftm@rmWP$F[iq:Q[nefFd$W.%Rs+6f:nS^RAX-.VJ\AW*<`Go)tr1N(<ihs(>P'fqUbDUP$S^Wp)Q[@o=.RSutuITBapf7gC`ZH4V=*'9(;0FK7T+2<Aio3IUT4&#,D7JmRGS^AT:Jc*uL('JB#8<8QIM8'hZErZ=t,5D\=s"NC16`HMet(6H=iepgs7Ws*r^"D#6V6!Crf:pZ`fCeIC;0Ejj)\!To4`)-F>L15@br/2:`ok1e_38J1),]NTbL8b@-EBTt)83G^!p&bXdLq_jOIIF%NOV%=<b:l%nFV-d3Dm*f`(iT&um_fR[It$K5?i~>endstream GauHK=c_;q&:W67(na.S,W./O'M0m6(296pS#2[u8RE5]'U_/9ZKOib2Z\g8p'u4lII;Zh]p;%qLNCd#'O:R_i\t$@r%`rA$OW-7E"%sE%`?ofI->rt#STmP+,Hh"047$Hci-3kE>%aE&HN7f-s%$-3H.ED;Lnn(qkU##;@VY6%L"&1.hr;`W%XCfg@#Wi(`pqY019c#IjL_miWV_Ld\3&i(RZEMXd!S1HCVLpQTbfod(=Poko=#uM9_ip0en(";]SSR\7A:MZh+1&/=U4]*l2c@X^e\BkTkWd_^cQfZ"J4SF"U!+\_Q9G>%bVNJ$[ph9<N3NaXd`aG1aGpPLb*aX5oMJ,+dsiFN$Om4!**EH!G:h$]"PR]PZ5/LIpL:>]nVbWi.nN;R#SH?dhD<Z\CD<;/05k9**HE4uBo8>1,5/e-ciq=Pq./`OE4&kr+LUk*3r,>:F$/BS<HF2emFkO.<1JKTuG"SfbVLTi8n4+KC<ZNlS(kR%he,ML7SKX28A@itk$T1Z,igpks`R5hIuEcA56u%,26jDrVT/5D/F@/9MSnOmZSGFH1@=O%"^-ga45ZFAG4:=l-MVcm!%,.i&IlIa^l:0!)qnMK9-h$aSW:'UcL,P&Jm/H@HGXH]_G^<llom#)dH'diZAm3J<bD3X$1F"eBJpOY+IA?ToeU]'S!;W/`ck:#.;a=[`_OBj$_&b.YR,*(EE\0\-!FH]N&:J7.3.h;WSEdoq%>RVGpm`rbC(ddGH%D7?m<Y,&b,;XP841W-,X#L-EKrQKH\\IPLOUK!#ZhKGm#6&RFekE*p[l<;*g1eaVm4GA)sIr+,,:A9X%G5*0(E'Gk3FKG4>_8c1"LM.SQfDs7u;67ZG[G2D[h;0!GpW1FjC3.EB$M0ZLWtWs_`VapFm(7M(9uBp@l9lT@g'roK]cYG@1,W,o.bdnjVt.,b;0mNn_8/P4'tZi-[QZ?;E)D.4k=LtpEGX0ZodmHrjL>1p.DgL1G!bJ'9h"-i>g(JE3[$g!_TJ=ZE4D,Yl=eaXLF^toCmQ&R,)FEJfj.H4:#n-H19c;Ioa=eX*F:miq+rkK?:E@7d&\@jp;&XSWDr&ggG3j)ZYVj*XU]5!CULq-VS1g=%m:1*?b!`%@6al`)$0e0I-u9ekZ6TWF.Fl!GW%Q4b5JF6T2'O7)[=ht\5.c*bE`sHdMVi@m)*V+k4pbo)[pQ++X=ZA+O`WqP[m_f#I<T&`h#\ra&p0'58?OI>-(gT;)Q,DkdAg%A$X]f]@SMQN^dC[DXn;6rGcQfNAdc-+A,>BO]nksSX`?^M.g*_S#W\KLZi@+I':q&Bb#'c*;k]U!"_lU-9NH>&=.Eu^/m4BdOF2<=cDQ(S*+AilO8Lic]:"mO)O<5@%)$X&8b>=)/k7b.^CX_A@kB'@##(glGg6c`77`!N#s=E6/X(tkSR(Q$r_i,ZNr)<m\lmcg5,H;dS0ge#T6X\%rb^P#V2ZoLi4K*@)#rqR2S$2!eb!F)SOpm+cWSRp/g2b;30)WF=useb"[^_!0Q@E#G`3f)M<><L?t)!@W5;0MXEh"EJ(LHZ32I%MG03!YB^.dO'3cAF>tI48bb&77LO$`'?&KEE&=(&S-Q@Q8:M,ubJtV=2KN3`rtX[q;U!#m@5old`SdgOC#JsY%e8Mh:4bnRY4r^KL],R'.m2qreko,Wh#o]cf*(h"'Gftm@rmWP$F[iq:Q[nefFd$W.%Rs+6f:nS^RAX-.VJ\AW*<`Go)tr1N(<ihs(>P'fqUbDUP$S^Wp)Q[@o=.RSutuITBapf7gC`ZH4V=*'9(;0FK7T+2<Aio3IUT4&#,D7JmRGS^AT:Jc*uL('JB#8<8QIM8'hZErZ=t,5D\=s"NC16`HMet(6H=iepgs7Ws*r^"D#6V6!Crf:pZ`fCeIC;0Ejj)\!To4`)-F>L15@br/2:`ok1e_38J1),]NTbL8b@-EBTt)83G^!p&bXdLq_jOIIF%NOV%=<b:l%nFV-d3Dm*f`(iT&um_fR[It$K5?i~>endstream
endobj endobj
27 0 obj 31 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1975 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1975
>> >>
stream stream
Gb!;d>BA7Q'Roe[3%m@hd&<+TPBh^5"h$3`b*$XLqP)\YU;f-.VZD8F*r-R!Bj/Y5h4a)cQ>D!4IZYIDr3?):-3.t]E:f.kj"S.Njq%cp+5lP9qJ$W1ESkbefRhfX#85\O%uQd?K'O"%huNpGI3O%D*5RFBJH1_rI%U42.(F_D0d+2#IGk/<bk6XuZed#Mk=F;MY3,uClWa@A@4ra0b8mnAr$*&-_-3OY<8/R\\\8I#"@f;]r]PhIZ^E;\H'ZEg?54r[=)c9l&!N4IUb$XkW<eO7XbsGI;/(.Qdi4Z<2m`eaNoQ8;GPk25&#oaBGV,u_\j8KC1S%,L".7E5dLO=X/Y>3A`3uY+VRT^bQAp2&[c9@h,SkSqo@VC.(AA-3NYou[d=10gk'MJ8j*'=5<^2?M9JC(OqPaJ0$XDo#D:X*`1XJjLaC+`^>7BDpoh4!@d@NsDd5;^p%XKJV`P!)ja[9i]eLkE^NTH5X):tj9f-@$_Ne,.d-!W-A"%-<t\>$F!5HkN(NLI?F"+7;l%g_(3G;sSFJ8VuMJ#o:OM_'n/1=_5_1la((/fo$<:XIr=^-9=MlN>fp1^Ao:eM'$ah^WYF)X]7KM:00gpFZt7=Lah,DhA@<`;4ro<MSP#FjsC:FZ!*U$pt<+7?q/X_U*E3]IB(Mk]@$f>c&?q:mk,M*uZA$_6$C^\M*\4f*A`7S,m]<Ibe/,B8-(?XBR=hTp',-PjM?5:qTH`qg4`!Is2cVG>DujY^orp%^ZV=8bkLY@<-P^XFseXjK[*,UQ`*),7(VlTiH@u\i_s*\WW()g-I>!R?WJ'.TqGrZ[)a=oM#\W&nHu.B."QuqBc9SP.K?tN<!0FS]WusO&XeS=^5Mu7M\DW=t\iM7pEH*<oD/*5^0hDl8Z@F?r!=hi&.Y6j(f#bd8:UYPq3WK8nb&D[M[r*f=opA%=+(aqi32q;o045i0S8/RRP@E2GQj5@b9J::fttdLk"9JmTdsf^FSJR86KI0]`0`IK,</PUq1sHE5Ha7n(/6@Z*$?Y&7+!BCPkgGnkK)!RB60iWAlf82p70_N)P/*jH@<X<Y[_q.Xd%i9T?'96WD[;1dY%`ne95'nHe9dguJNncI>k!e;FYX?0t]o?N;)FMZj*-e0f>(Nn.\J5b?#2a.mFNT\Z>R=CI(G[51+d[1,TV4faOHGn%*eg2($',Pr!"JR>EDkBg/^a0*:8D_h-C3r\IYp*j)-jCCdA6l$/1RAbO)`rDfK4k?l>.kFh##tOLQ@KJ'NVO1]4]BsuMkaRip].V&Jdm'X5Z+FO2.l'esb=H:-9@t`;UIeQr15>$u@@BB5%p>8GR@-*hIoMgW_D/VfO^Ut0S)TH+W<,6B_:gg""qQpZ?WRRhTTWB]_VI)(:DiEQCrUHp!fl_,4*V)e%WJcAg1E%og"YA0kX)d#i)VfYN-sFQ4)5BnfBDuLi`]ch1l0"fh(GB*.E%^lU[8<2Q.C>;0SbSLOBR.51tn(GU#CFf3WU?XDS,:M"g'fK\/XJ$3F)@DnpqQgGFIFFnpOQnIr?"J[g8t4^$i0k`spHe[!7nNlD)2Kp*!r46'uI>1W=jCX0PV2UQE(\b<1ROP9u`]/<VaU$*$=hV)BjHVQ(T^d7<80J%r591#+1UFFp9-Nb[.AY4M,ZZh&1>MX(;mE@ohd9qcl^`[b%?%\(aW5d-d9m5"AnCGu'<c.X'/9)#46SgsI`TtY28Bc+hf55JE3D\>fn8qS`lG]Opf=:Voam;Mr.CcT#Aa+`iYhC1TS*U4/J7/'DholE$p6+RZF0_ENueO4%?e2G>nNk(SL(gi)FMY:ha_(qoB@a,p!F3uUCo=9=_o+]3!TN3YXlOYBc!GC3Rn)fEObnJGQG+.W:ZVorTg0B[G,r(]qdlBX7=MeA#>NO6Il&CR33l=Rrf\KruEe1&'\(5e<?eX>Z2.mkNCK/>qK)'\)`TnlHO0<P117M?8G9U95C^0uCrrIjK#,_~>endstream Gb!;d>BA7Q'Roe[3%m@hd&<+TPBh^5"h$3`b*$XLqP)\YU;f-.VZD8F*r-R!Bj/Y5h4a)cQ>D!4IZYIDr3?):-3.t]E:f.kj"S.Njq%cp+5lP9qJ$W1ESkbefRhfX#85\O%uQd?K'O"%huNpGI3O%D*5RFBJH1_rI%U42.(F_D0d+2#IGk/<bk6XuZed#Mk=F;MY3,uClWa@A@4ra0b8mnAr$*&-_-3OY<8/R\\\8I#"@f;]r]PhIZ^E;\H'ZEg?54r[=)c9l&!N4IUb$XkW<eO7XbsGI;/(.Qdi4Z<2m`eaNoQ8;GPk25&#oaBGV,u_\j8KC1S%,L".7E5dLO=X/Y>3A`3uY+VRT^bQAp2&[c9@h,SkSqo@VC.(AA-3NYou[d=10gk'MJ8j*'=5<^2?M9JC(OqPaJ0$XDo#D:X*`1XJjLaC+`^>7BDpoh4!@d@NsDd5;^p%XKJV`P!)ja[9i]eLkE^NTH5X):tj9f-@$_Ne,.d-!W-A"%-<t\>$F!5HkN(NLI?F"+7;l%g_(3G;sSFJ8VuMJ#o:OM_'n/1=_5_1la((/fo$<:XIr=^-9=MlN>fp1^Ao:eM'$ah^WYF)X]7KM:00gpFZt7=Lah,DhA@<`;4ro<MSP#FjsC:FZ!*U$pt<+7?q/X_U*E3]IB(Mk]@$f>c&?q:mk,M*uZA$_6$C^\M*\4f*A`7S,m]<Ibe/,B8-(?XBR=hTp',-PjM?5:qTH`qg4`!Is2cVG>DujY^orp%^ZV=8bkLY@<-P^XFseXjK[*,UQ`*),7(VlTiH@u\i_s*\WW()g-I>!R?WJ'.TqGrZ[)a=oM#\W&nHu.B."QuqBc9SP.K?tN<!0FS]WusO&XeS=^5Mu7M\DW=t\iM7pEH*<oD/*5^0hDl8Z@F?r!=hi&.Y6j(f#bd8:UYPq3WK8nb&D[M[r*f=opA%=+(aqi32q;o045i0S8/RRP@E2GQj5@b9J::fttdLk"9JmTdsf^FSJR86KI0]`0`IK,</PUq1sHE5Ha7n(/6@Z*$?Y&7+!BCPkgGnkK)!RB60iWAlf82p70_N)P/*jH@<X<Y[_q.Xd%i9T?'96WD[;1dY%`ne95'nHe9dguJNncI>k!e;FYX?0t]o?N;)FMZj*-e0f>(Nn.\J5b?#2a.mFNT\Z>R=CI(G[51+d[1,TV4faOHGn%*eg2($',Pr!"JR>EDkBg/^a0*:8D_h-C3r\IYp*j)-jCCdA6l$/1RAbO)`rDfK4k?l>.kFh##tOLQ@KJ'NVO1]4]BsuMkaRip].V&Jdm'X5Z+FO2.l'esb=H:-9@t`;UIeQr15>$u@@BB5%p>8GR@-*hIoMgW_D/VfO^Ut0S)TH+W<,6B_:gg""qQpZ?WRRhTTWB]_VI)(:DiEQCrUHp!fl_,4*V)e%WJcAg1E%og"YA0kX)d#i)VfYN-sFQ4)5BnfBDuLi`]ch1l0"fh(GB*.E%^lU[8<2Q.C>;0SbSLOBR.51tn(GU#CFf3WU?XDS,:M"g'fK\/XJ$3F)@DnpqQgGFIFFnpOQnIr?"J[g8t4^$i0k`spHe[!7nNlD)2Kp*!r46'uI>1W=jCX0PV2UQE(\b<1ROP9u`]/<VaU$*$=hV)BjHVQ(T^d7<80J%r591#+1UFFp9-Nb[.AY4M,ZZh&1>MX(;mE@ohd9qcl^`[b%?%\(aW5d-d9m5"AnCGu'<c.X'/9)#46SgsI`TtY28Bc+hf55JE3D\>fn8qS`lG]Opf=:Voam;Mr.CcT#Aa+`iYhC1TS*U4/J7/'DholE$p6+RZF0_ENueO4%?e2G>nNk(SL(gi)FMY:ha_(qoB@a,p!F3uUCo=9=_o+]3!TN3YXlOYBc!GC3Rn)fEObnJGQG+.W:ZVorTg0B[G,r(]qdlBX7=MeA#>NO6Il&CR33l=Rrf\KruEe1&'\(5e<?eX>Z2.mkNCK/>qK)'\)`TnlHO0<P117M?8G9U95C^0uCrrIjK#,_~>endstream
endobj endobj
28 0 obj 32 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1636 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2229
>> >>
stream stream
Gb!#[gMYb*&:N/3%#dX&D.B"nO]7q+7@S1GU@"]3I9>Q!OVqA_DO$GmYirjj:m*7fbsi!NUh^p4392&#EXkTL"r+hR]-]PRDeiIG0OPmOkJ/@*Y^Q[-f>4A8E5f3nJI"V3`.:=nIA#F$"b_c3Ldqj^e\l,-dP,-(A:UGIekAGJiVZ9_J7qBZO['<_Pu0d@$_fW$W?hk9MQZddr/@Be&N-d[Rf*c5E!.*qj\^^WP_D/OFUjJi^AI4Rr*F8XPO-FIK0S,E-G($g3S3cup%!CWUePV`nA9X_=]sjpBRba/7p%uqF%u/#<$]mi+6(&?%<0*ue!B/[lMGgq(,82jQp5O_pGe$@4>eua#c]Tn2stl4E*nQc8h.ma*g6NMgTl?F6E)dT)`PY_8XNP+C'jWEP/,iAJJlHIPW)#j/dfF)$eCC/"SRQQ$1=FlJ71)M4/iIZI[27P,DusLT&ehrQSDWg''t3F;XW:0IWaYGdqMkq1T4dr0h)I.N!j$L.njWfLm^h>#j\tql@"8f3rFg'+7UX)+cB1*#PdcRntSo_BP`#u_<8=YkKoMHr&pq_AZ44uV\s6nT4I15Sa8bR&!&r^EJ<5>*oG\T/c"&/V,TUe"")W@EI3I2Z-t%p\4Sm,rn_Ftdj?,b_Y[W["0$F8#J;Vr,d%Ip8!?^aMGQn;e7/,]P/,g=<!m-"G>NXU+dD!lp>B@9%.Xh>"eiqjPG^<NdS34c3mQh$C;r-XOmCpiY%E'o<hTSh/hHWUJOUpEN2+T#d=Usr5=P5[P1mpX>o"9fAR&6]12JfrWMD/QCtYVdL""N:e'oG&j'Hj?FI3'OAc&UH,ZpsElPac3I7]>+4sE0sD3LfMlRBtf;^]a_>.\40oiYG:ai2MGmI+qmA2C&BVtn\YYHrT'4RY0-_H++tfHNn(1U$LKW%OqHI'nk7S`?2o1H*B\W);J>Fj_%?'K&'QmmA,H"7(33K;-L3/XeTC1>"K%R:ks!6=FAAb\WD"ZeE4hj$"seU(,Lcj&OeWZh,#9:eFCpTb0Lr(K-?NL*<%d"7$Q=P\+5C;@eCL#()8lMJmaUGRGP.kW%/rX61U8D1;uBN8U3r]\*:97utGCOttAR[`i1=OH./R;th&Tg'dIc0>kc!%](C.W8M!(.#7*Ze]YGf%j^$t*#EmJ@>e><OreSY6F$Fda5jB+l:_"lo(Q:7!B]&QkH_28XN`YBM&V+jnbK%r0/([(/LIj/;3tiek%P,RmWXT+IZ$hBi^p\c=MC$/fH&6dOeMUnjDcLj_ITI>kESUi',<D(]J;T`h&t%Z>MOMs%=_]tKoTP_SEkaP.qAA+5guYnIK/)M:`0]K;kY22Oi9!KGKf4>iYWHC[]Q"Ye]Pon:01"iS>Y*olAMF*'Hh?@On$LQ6=^SXi??^ZC6u52k*Q54TJ01dPUP#^Wj6c=ir;@kE==W2<"Y8@(d@_hpK?;<k5@V@gm$%[U$!(Z5Ai(h8$HrhXD_`Sq.4FI1*fJ*1Bhjgi@+aXlG8#4dC7*>GR&r%KDU+qU2sr=1ja_EbJGgirbUIbMG\eZH&".pRI5cG0&!TlK)2d'ea;W(V@PZB/K:=XR>#O=eVQET3p!%RHEK7#FAL%qA*^`g:C;.EY7!mE0tnF8`El:0&GupW-3P[~>endstream Gatm<>B?;m'Ro4HS=*-@D*"^g63r^A:+'mrDDR-=39a(B0IuUt#"4!L-iN^$![4e]lZFkpm".3`:HIZd3e@CO?e0^=-,;0HnFRFt`o67cQllIUhSRIBi+kTfjKXMkKSkKj#Ejc6#F]d35pV:\Geu_nR&F,;:lHff.,_$?a"2s7_o7gi!Jr+^PlRO\,aH$;*tl[U\eZQS1:aOUn0n_u+k!&OR):!a6ODm%jfO8h:_q"^S<^='p\saj]J43XAfG4eps_/^gN;q=o,+'k/>'CghKF2K0D(M^9O6q`qZ5!NE;%6HRb8KDOsCh5:&AjO$QSZBmEEOWoe]t;<=6jlbTF?jUE.GscBp'oL,?nHgq2E/M!Ld%+bbS)i"6hZrE'[$Qr#9^ClBkl,m]DTT8uC89G[CX]O6UOCW[/6.$m?[D22qhnGpR[$iuC?7DSCARiYIGLjC^W5UR6LhKY,WfhZ*9o.8MsI7i2M+nAE9g!o8-<taW#@hpdg$!3(@^sHtt,1T1Wc/M(BYfsSkBD,nT59]s-IDJLn&kq%!p-7jidfk+0ViAF5oeg>^?'oOgZ.`\:+_0b8ip5A7rqFuH?_Vtr;@<nA%=cXVHS3e>Z@d8NR54/Mb^7^JJmFZ-9+@LW]S.kG)UJZESMt8:cW@Z3XbJ<")gIotDg(;?n(LK+i%p7NTGMIrR%d\lVDmBn>nhRlL:ftB%b>7$)R;T0EpFLaU,9HfApaKSY9F3snQ2kD2`=E@!N$!@V&SVTTZ`TQF@C)Kf)G=TC:7IC7rqj+@97K84FQKGhA?\@7!u,+bO2$=cS?*qF4^0V-0fJ2OlVZ5c]9]HX\9r5M3,[H-I1`([o81[;7f0n$bK[!CNh=i5'Um>Bj`C=I[M"eoI75@;A2N=[DLQ(T7*atXSR7rYJD[U;EWrF_rc$2G+ENNR0N&t8"f)00M]T54(Xj^7mK,We&JJ4=dPT[nfFNhhjWBo19G>4eX%Z"bPd;hc#Kq,U!mo2kiG8mjp3<3iMblE+=B8Q>-@I73VTr:k1^-6`3c,2>c1#aRfi3O5b6?@J3)Z&5b5Ll@SI'1];!(TrjpSGJ^*$29]e%f7l^IObY3;l/+Ao2`D7/jf#VI$+(e]B7"3&R/J==H4@HX9fGfNmQ_]^-i5=l26uY<;5rmI<hA,ObkG#XX(pT/N/sU"C]`LMh?JrRRj[\.oCA>*MD5BpaI/W/^j%LJ6C,a>0fjPT(9q8Zl*_<VUG*$qS+1B]T&90hVhiAM':t-si6[k.CTn:;U&N.^AJKJA6`]AuZDp",paP]U=UN%&<K9/U]8'ob-A,Dg!WS(cB)5EBKg[_:W%WoSQoV^A9Q[;r.d#f4%fC:+3iJ]#W@]Pd4EhLMBcj.);i7GFcOB6;N(Qgo"GbZ".]jFHgbc^4DE`fJQ.(UXl1a5U_^&PuJ$*I3kW"C='E)IE_]fq/YUD?>9Oi.5);"#NX4#NE;+gCJ(#8P4rg1Cgo8Dn\jfBAoGbZ8A$E`hB;P"BM`0rDRFqg6PDLr_8N2k]<`jo%e[8E,_PF[&dLCdlCL6RiASe.&!\/CK!&\pl7';/kDKKZg2LENE=)9)4XY*K]_XpGK*PY;`hmMt\$,Q)CiYgWd9#S(0<]=9Rl3C`12dPqP7_[i.d2"Pt;)T=P%$A*&o[2GFEXC#up5?S5ifBRFWcBUrXR3[*Ar5KCJ93!"%kkH"9J#l%J"T&UF37_#fGX20h(dCO`+$:,aH1U/lL^M3.OBeM(GA%,"<<jg[<=4;-o40uB%)rZL19:Or!Zf@T6Mrtp!SSP<CkgK&hA(dQHXN52S('1g^LVsj)cd*:a8.d4;7\Km0j-<"ll`;>PAfCr\9)]=$%OojGO`Iaff,<jG]+#8dnWJn^e6:#a/&K?V#n3H5a-jaJrd4cIn46W]4*)PO9bd<-[La\EI?I;$Ts[$J(uWZcMr/DlDtW,=\mk/Jo^79NTXaMWM&F<f"6H5/$nRbV5Q$0]gu6XeNj6X=a4H]gI6P&9T,m`61ml"[X0\5BfKq+(<8D\GH"de>5=7)V]RYdi*1p7^4a5G>5lb]m;K#766:&-:<>F(uVq8N$\"77LK,Qr<:T8,6c&_gC%nmFD^9-nkN1MNt)!adQGH:RJs2B_:!WPf8cb!2]20QWC''\7@o^3fk)H\$=dIZZ)`,fBqL\VJVAp@H:H2:%$F1Q-GA!S[lDkA=oqGQgtb_A>i2ZIYMN[Y3f`("4%bBnR!+k\"5)\X\D&-~>endstream
endobj endobj
29 0 obj 33 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1324 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1044
>> >>
stream stream
Gatm:gN)%,&:N_Cm%`Ul0EL%Gb]MQ`C+#Eh*Of)/(V`5r8qKJD$33YgIXRP!1UQ163/K5H%"dEsm+J]Mj:Kap\%fldi+rUq(EBr8f`HEB@[LH<q(2:\0Th5D:4PbgN,eqMO.&;;bFjS8ASEc;%Pl7qb[QASB2[k)"?i)_49LMG3"@pP/.FBQ"-Yi=AbssK&PIhNX^dZmC&[c#*hR9,jXffH&^9Z9_.X#@P8^/b0ai8i,r")hdFF"Jn)9g?U+99a<:J:>/63h[KZN)rqi`taC$2aS5B2aLfl"%fY@)MaZf>Z$;>`Bh/0T8!j'SM_!VG]j=`dNEePb!+GW:TP2f\[*-l"nl/<h]C_Mi7"9u;O=LlkQ)`[P^*Og&^_bZ058=;]%u1achK.V:e9`HW_T=Ao@?*"BF=]EpGhjg6X9@#,inDTr1>@A4lp&*#.tFI.Kf)%M57@@&/D^S%Oh@K>(G4JX^S]EhP(X/Y71Q2^EMd7u4KPf[5QUQY//$->U=SDm!nO_r52;-b%-BR]f-'L-Hpk=g7D^JP-[8[jc?h\s+5<9=0bjZV>OI8:*t(kW;^Ig6T.=u^7TCD$-?I2dQ(fA3%*U=3SJC-]/OrsC%]cZZlL+5qJd;%ep%OW<-E;[?WO(`K8A(s\3]!*&(QI0haa&r;a2.?VHC;BAF5[A6!=Sb<dNBa=s4?D3.flB['G%,N0\@"DG*<O$/c=0BeVVWQ7&E-`*\hY9hkBn,;#,YIcDhPAFDj;s-oYD28@EIJ]MS<>1LO:<@Ld8GG9mjq5L`@'1]I^UN2B\-'r@TZVF=Vb"KUNFc=SB1.B^i-$5&\@CNDH0uD)#U5]CTR1,RUg9)+;Of@2SI^eTN-?@i;0&PlmDPOZQ&RTrq?8)G/H3ai8.Ep;SA]Q-i7ma:I<&i(cE083XLbu_EELk7aOQ2"%?rV8_J4$@-M&p73GPU/?'#+LJJ,@i!m-<#4N&X;3eM(afjo)W_4a7Z"jiHJ`!]bnJb%G02HWDOo,I`]<)KuAT*Op19,+q0./G*_Op=^.uh.C6:!4Z<t9ck>M=I]^,6F442RP,R2nPQS8V'g6#Qa]ntbju?2o\E)7@9)QC]1oEo[\+j<dDXcK0tjSFj7LG,+<H`70hDe/'XXP,)ZsdtO3?9MV>YH)kW7]-cOGf%aco?NUnS@`Lb)<j(t$CW]rA24bedr3'<p+ae0cKjJFte#St;8dYQ#C9Qb7:*DE=Gr!j/bT#o:FLi'F-"@lFXN=iO\G.HHlqP5b6(8fdolT%?:E=P$e6DJ!.gEJrof?:,fJ%<di59fMo=LP>7JD"7DQ#)NVu/mN5Mb5ei;~>endstream Gatm99lo&K%)(t.i0_bG$qX;A[C:>5Cbh2KgPjn#oHHr4UKA:;M/bb/rBc\?@AiO*1U0'*"se`noKYYAVb=DkF:I_O#3Qd(KS8"[KOP:#fC3YnHjuImd\mFRBR(Ff?uaisTNVOG7%3@B`W@\MbSgZ-$>+-k6'@ip_E`gZ3"6!EUUdW:QkbalqN69K[$6eYYt43Ud!phs2Xk1QGN]<EpSVE+6gR>Q;+&=4LeIrX24WI7s*P!:DM`L^k?J%[I"ggULG)ODG^X`.\utYY@g6jkMhToeZ1NO]llKc:9s.'8V'Ls)hL>B<X?cpDC[W$Ee.9YV0,"nE''CKfdml[2iQObrOVY=JgZ=GoWsWcFOb5!o^BGX$0#(7t!h=mXTUoN35%^ke8]^T*:*D+$UGuk;mXHLhfFRq0K-boT"h0&tEe!m=5FT]GH4_pt$"VQWaF(uW`<,2I_kot*FK]YQj#?aTHOY8<o.5%MK>KWZ.:7a>oE:KNGq<l<:0>?4aGD-]HNcWToMFUQKIp5INiuiI\F?^fbiVL.q@2<uTrXeNpN^TT`bL?,-:nC,KJ[N:S6r5n?!h-<G:NptO?@cs:4,fR"fWVLp5o+j+i@%5IQTsqnGp7gI1(klU]p\R_K;r4HN_R9b\aX8TuQdR8MmUlneFId8PQR2h\]U]N"&T74CIERF]5`^LhDu'1TZXel4L*s;m<![o'gp0>1I)k/bXZ,%l[SYB$_Ngggg0;l"NJkDbYO)\;3#eQf6WJ=#41b)G!OkA<1.>-tBjA1t[[N=l),uVI>!h`YCF2Ms^87_-g1bMg6"n2H4VD9F,J`BHQ$r2N7ug9A2Wf6O^ei[?G/c#303rPU;&(3UdVrmm"#o?dmpcBErNuos5rt\5e-XcYg6d"H-D6L"%6%=#5fi.EA[JT!H508b)-*fR*;;enNksc6H8,PZ?C&euuh:SZ^Tae6VT?%`s0[nU-kB_A*s]Z8lfdVLt/7Zb0PQDEcZ9+X>gdN6ZnWj72S:S?QSmo-KqAHk^jqP]2944hIne(mW/,nE<.,0+b36Z2~>endstream
endobj endobj
30 0 obj 34 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1582 /Filter [ /ASCII85Decode /FlateDecode ] /Length 1740
>> >>
stream stream
Gb!;d8T3?E&AII3bbYhFdEj-b;,g=E9imuY2n/7=MaGgO&t*i13D'8L)c=$h-ZM$Y!Gi^@\BW5V)i%rhn]UjFRKL3j_"^%p`aLV+(6JkLTAeTOp`0nuGS7t\6:o(Z"%gDm"4\qV&.9[[ShcBa+YnlsP)Mr"9&R338QZ%ZL?Zm3!D*DgA-><+=@dFAI7OM(a>tNM&F&l\/,<^#?:r..3Y\q!+YfM'<hMLR@72N%P8GWf(`e&#oa*h9R<IlFg(JkcPuae?*-7^e_5I56k6b;V*8]rg?/!8&RsGSlB/"Van/BZ7,#2S]Dj105m=5<<]@p"aWaE)8@5;5,5j'*OFA5<qQ(SGK,ReC=[^]8p6&:D\@3d#to74lWII6mRJ1$#u8.PL+>g8-39*?@1`9TE5kkae&8]DZH>uupTdMP%!+`$7_!j"Ht$FkO<",<SP+/Bo#g2TsjmA259d0[?egqagM=2AV/IB*+OVB<QkI)e=P+EHQb"-X&@B$.YP>G.^%/A9.B*>jO8<mTBi!sZUu8.!&QTQ(:(U>p,FGZ.aT[(4N\6IO0W^[DbfFcdV#/-`mQm*V/R2MX3BE'1GTBqYm9bN$8F$&13WZ%>h3OFd8e20@>(odk?S(=qdtTSp6fDti!<@^e1BWYlh`,hXXmH;Mi4?O"09rG<_'2Mo2Vb.S";RSg"$Oqm!sHcB:c8\e=!CnQ)\Ns'T>8t4qoJ[N18@#N"q@I&2%$JY;dM:qNGFR\8FdGtP#kJqqYfWT%%h.=Q%4s/4Ef;"EK=u.aB[t%D+i%B,A4s.A-f;$]@/XNsLhA1HrG2uDGcNru2kWT^Ra5tEYQkf;Ynq8DQ+62KJ?=U[bp=hIu?2fmPq:G1eX)cS0\j'<BbjWmU;'MWuq0*sof!a5G[+./PI!W\6kIIn]g6b+b)tS57m]<u$I8t(C$>X%@R;Ab$D:$h`/dL?ijkM%Y%l1D^q!\9CikJQf*MHo_RDWe1!MN)W(n*PEUMib_qjp)jS2k&Zg%Y\3`_Cg[%b9or8,cc;WXK:)fs8]l#"$)rAW+O[&%V5YcRd:VI%dSf<,Y.<eLACq;o;>G>(>l:DnFc`<qbS3/,Y$t=&ZX+h(kf(Bl:H`V5Mp5mLK^^QS!RtnTMMMGJ)GtaM-t6eQ>s-SSm.U?/<>u['RqJTk!q%4'm8[\=X._Rp^:S`^m-<#Hfbk6]sRgJF;^HjB7c:QgKB1c9FLK2`<riG$\<5f*4l8XfEf/PdcDDojlO*4:d5!"#!Dk;e4J48ZAE3kY\?2(<qOSY*rih.G@uk$7.T.,-\q$j;m"cL8&V+T_aCNc(_?UP!h]$&KuJcpo\Qf50=U/I&6-T[%X;b&`YQN9kaR+QW(^Lr:^gtn3WWB"r<BM<72'>b#p8nI("4lpQj=PZ'KAO)soNEK2Wq^MXam`GK>0oNk3HZI-0;NCG>Trb8JI+TfFh\.Xb1X95iC9J"-1ZpOU?:]jUib140P'9O6>!WTlgfiNdL4C@<g<._<r2=/sWGS6@/8e')2O8_3M2)&t7KF@Z"*E+nDBlZDf7m9ZQ;pFADW'c(32]n4GO1gUHS\i`K_es9j),=:la%t-!i~>endstream Gb!ksD3*1)&H88.1"oZa!pfG^*9SRmW$j;3!\1X+W;)P!qKBb'ECdA?W8bR9S7J0A>VCkS!i0F0M*8*cO(Y%%:Dcq+Z0n2!!5G_qj=pbV@<E&R&dbW%nAd#'*tYT",XW\@;\='nZ#CN/S"PF2&Jao)JYj%,OT_X$d5ojoEF'?a#\jJ)G6.so;Dcf7Lh<@/lDs`4.*cgH8OB950BWlBBr>92e=[h^g&DWm8$HE#*`Y`t=Vujs.cC#YQZ&J3;nFbY]!XGO^Y7D;gD8Xd*l5a,bD/fU26C$!,BI:6>8=#OItQ95=P[t4:%5"?,Xh<cnXl9T38hJam<Och.bS?D!fTimr^<4sJ.s(>NMn0jcj5&>V837bL=&=;WLsNA8G]3MjT1DJV,C*">S!ST@?KPJ8U))9#%!#nVAXGO?52G03!>9o=?jeUE"JhDi1Z\H%,\A\-1sOQYEB*3e0;qn=)s\p[kZQ])Jk%d;sr8LroC?E:.JVZoK\Iob8;O11G7#jBP8FOc=QOa080"1rpi(c<ul``pt,;$&M-E.*oXl6m5pN?d$Su`_*q8=6YL<EJ'[XNC$PN`IRZ3W6Wpuh-^Nu_E;OdI<;=$Lj9tRa-1;b]Q)`I'8<_5>EONbem't.cmLk0*5C8E6gr/D(]XL3>a=-<eJX>JL`*M??;<5';,#m!(.aa#+C#EPG15.I*4/dd*'?]is"*bXnK=hCpJl#std503snsYSHFBm!D<0)C!UTVN4EAqD!;MGO741ES[#TAno1QF:.,SHXAr$e\-OObamc*Ba`/X*2\OCPI[8u8r?o4=,n#2[)-4AQtO3SLM)TYgn70&'/:B#-O/>1Z?Or'OMfZ&/7#2bfm/mS.D^9q<og=m^t2lSD`kl0diE%SsQ5\t5o^a8&PQca+SL4RY<1aAf$ARki5^1XGbkW%P"JGd`MTT&W\c)&m9Be4=PP3bf$[OrE*)c_1j747d^>K<csL/"8H=F4`>5R0W//K#G[aR%#jHB"<KUj2!6)_@=n6j%7grB%]&ITTkfj67mXK0;K^F%7<$bLK&p]'erJp)%D3f%1X"@(#SP7nIdm7d;c;WbatSg[JANJ1PBg*P4]_!gq!ZlPM2bHa0L5L6TPC;:>I@D[jU7mSq`.`*_N?_^k*NaTcD5$-\&d2]dC]:P,'XlSLLbY8:%'>(5grJ58JGh:g_t$MndDu*GeFX(Y7/M`oLpfUOfX'5$p89IYZCXEhX=mC27$/YEk!_c'g'I9k5u:Vflp]I'M;-%d0(t;#LNBS;EUe)67Ja15TS:U>!X<_as(tGn%F)`N/J&GkD\1K$eS?mi8NiiMSt0j"C3-\N3P)%1,t,*3,.?YP:7mMI^]6Mf%3Lc0<?,pN;XiUp`D32P&G9pL[<R^n&Et,8UWJ4'[Pr:n+Ij9"QW."jB9+UI6MmVup"1l6=+V6r`.[Ai?Ss,,Nk<R#A;sP%Hd?08t]kP50A_o8Za6Bm,D]Y],H#n)q7[0Xh-%&g\_b"`rkr'EApA+MdR3e)G,TUVbNsIK$+<F(Y&$H[hX1_CXh5[VmG1-smEGh4NY7nA`tWa^Jh_b2h0r`Jej^@dt+BS,_;'dUilpWU0,FFS&kG>ZUa.4A2s,fKHh<5/CiJ$JV$H[a$dUWjsk/p)BrXd;oCSO`iE`^C3eMHVNu8*uK*Jc4?kP9l8Je<+G1t\)BaP[*9#\9pOI/F5[^(cc$i[Ni8#!Q^C0H*Uq-,]@L$WT\c#SE_5p4_7sGmL$o~>endstream
endobj
35 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 414
>>
stream
Gat$ra_nsL&A@6WhN9!:fT*H57b'ss6D[4e:L$^MO=DM4M>I0nQ<&[1C0P`s^Lc8@0Rn@LdkgKT?ljB.J`He`8Kq<r.1&0?&m?-O5bVat,X>5Z*`U1QJC:5<&TLoba;PA!3E[;(#`gcr)cR<&Bu!#(1Lh`PEi#o;K+CN]iM@hLXeT+ui6Sk*,1;**DmOqbbl?V=:qs**,fZ*H8C!U8XP]@h/s!/1:M#sSXf5QOC9SGi@'Q4Rl8ksb\&Y9@Ed;pj]:ui'npt?*C0g@&.7e`2A,_ao+lI.lP_Yk'Fa)&eSPSp9BB(fH+jsgRC-kWq1$No07?7A,a't.4:q%h'n#=Q?&neaBV_F1n\@!&D&"OXNee_C10(ZcWrXZ0\Wm(';SMcG3/<HHHbrt2lOYntaFV>nm"s:46#l~>endstream
endobj
36 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1326
>>
stream
Gatm:gN)%,&:N_Cm%`Ul0EL%Gb]MQ`C+#Eh*Of)/(V`5r8qKJD$33YgIXRP!1UQ163/K5H%"c[YZY.T:a<@>iDh!ZS^tB,k00!nOZN]ic`A"oWo3(QC?m6=gS,eIX)!(g$*]8FTQU)'Pb0jPU*+YEkRDf_1cD8Z1#^S,HGQnplE?&q*=W1m-"t"VYb4Mat,F/[&TjsCaC&[c#*hR9,jXffH&^9Z9cH(V38I]"$R"[ZE&t!Ookj$'`p[HHZd1n0A.er3Z(+To>6=fTJrQ-#A[.@D:T=DqaCc>Jn="q7Afl7[@j?JY.9*jBas-Mq7!<"rs(7j&m[FDSc*f^*q9q4C,8js7I$RL_*0V_g!POK\=@Up1NE=/1#,ck2[HW;1D(5B(6m0VHM`JeH/)"/^\2FT=dd=P$:)cspX:3LmaKC=V?2?SJbNIh-oXK-m#?2iqt3&h8#-q+\r5uH%K!4%*!87Rm0&ZgRb;pD3jrTQbGSQ<BPPJ'(J,tj*>>R1Y+r'iH(D97#^.Lb>XcVOS%!i0[`S;FEO1m6cW$27;I>@Z9CarFfi]LQ2<mg4P+@h?N?mm,l"%nB?u(*W/C\&!91h8LXt:5kI<!RWt./!RD'gZb_cK]dVK8N]I1H>L>l+`Q+CTTXsQ$ti*O!35Vd^'%VV$,js*PfVc\.1[_V>#M-3:AYBb["JNuYL(0nFlo>;K=?XE=<NoN<NI&qQ>:Q/WB%=7S6j-p"5eq\mI*/l7*-LpS(-,Eg`u@KeIZ"DcL7K\;mIY[RR7?b[$73R4,XbknOYTpT.S/:g,7BK(fjX*1Q/9=;7^@Y:?;-\i&=QV#[MX7\#M&]%/s/j2-0R&9sV3eP6uc43PF$h+KN1VE;e)cG*Djcfo>iJrq?8)G/H3ai8.Ep;SA]Q-i7ma:I<&i(cE083XUi!_EELk7aOQ2"%?rV8_J4&@-M&p73GPU/?'#+LJJ,@i!m,,"*b%=W(PdOjB$FQeKpHWfJP&*^i.jnr'jT+bEEoi,l`GqDC\(`fc+G5N9XTub6^k#n0_hjQ,_V\TqEhi<t9dV>7/)4h[U0.42RP,R2nPQ-Jt,d#JC0ib*-`:kk[N>bbt\_[e0J?Pp:2q,r\-4E@tUgjeO!QBK)Ad9harQ:OTLB/4j#G4'e3[aQ+VHT-"g8oWF%\XYoiTpt%caYLkRk]Tm9pYVkOFn/>(4<;tM:6m6qqMhPJN>gp<SWCh12=DG&%'i#o.H$M0oNm&I/R<6"PO/9ZNN3FX]$/jg8i3OO%4UMO6/Ci3pX#tOOh,-Comp7`Ym74?)X(DbRLC?A*c(3bZ/1J0F$Ze[@;*X.YK)Ykgp$s7~>endstream
endobj
37 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1681
>>
stream
Gb!Sm>B?8p&:WeDlq>[#SQ[-:6KIC,kq+]Z2Jq9AQb3kQ4#C&[!@R9Lmn25_j#FNiAB)[`(j+NgDf8e[K`Jm;rQ?(oRka^,.8U6/$.+Jr0Kt58A+,aS(hZDS!sqC-V@6!6g(..]:IE>4:K^:U":5Z3"Hf?FLf=AlF\+n5W"_p3Gn`fdK1&*m"HBtM)s]l.)+2seM)eA8Y@#F"XEZ2Q9Bf9sp4pS*#$liGW.kljBUm!pT#'9]cTdHJURA,cSf:VFpSLjZ]Ra#$&i_ih*Ro4/2_'Tll;EmZ5#]%<$0.X3IV!d6kXQ400oEG8s&dge!<;B)9NnGDfsK*DJf<#3Ji[0LibU&W&dT,)6%ZPT<T.8?M@j:8,7ZMj`MC37qFZep,R>SKk[S'^.j?h/CtKE)k")]A_]('R[A:C:'^);IFL4EhcKPI6F\!3cF^KG/7qk1)FG,7+MtDU-M@pl!ONYT`Mudt`SU8Y[m'ejcs5*U4F'_V[dQId]j/C4e0``!QL*@DYA<[Q46YNO`ldl.>mMMaQD)/=J5uVm\2$RtKUUANoR@fD11]OX?"JK,^7[D!n\:_KhH*6e5aVC?6RE;7dKi7$sb;BT\13BdQ;)aR[E5O3iW9:t$?sQr<XVZL?g5<T"DmtPYdWDI#n!#0@<[#kC@Z(.?i!&Ld$ZWaQr2l+S4rDe.7*Ugpn8msd\+12_WU/;'ot2=Ol:RBibHD"`HZ-_9oB4$Bo6LtVYDX%"l07'@LNfB1hoI?5I<gJD.QcrS.u*'ZOS6=.K_3XT8MRmZS9+kBTjtq1U$Hte#i/(_^`DpAfUK>p\)Ma(G`P2cTkHVBcLt[M2!9cQ,0e?Pc/2l:h-g-A)Z)/ea.[a`r84LoPVi%T/8&R=BDs:kO3@G_&7-kkN35isJH_ZLZdqPtc%XUBO2!Df\)YcThk5EW9%SdE0["Pr<Rj"ko(OaAiL8lnGp_uk@Q3iV!?/pQ=.jt._iUKJ<g37`c/=_X\H>%lVDPgOC"1/KR[@jTT4*LngS7tPZND9EI6Obg/AS0A,$/Och9MA3kIqn8rIp_Dkc''2.N>BH+lDRLiS(8>;m=j1HN2af[^_TN4#he@nFW`=qJNbN"84^`gk!o&Bd.a+L2!4'>nV.#]*rnre9&dGW_><Q=YFN$gU=9Q?rj9cMb9^R\lh+$HTJ[WKu?^4*elDtR\GPJd!:Tr=hFsPDDIbq<B_ne"hU:S>S+)UJr&cf11mC?QA/"32WL#\LAKJ3K34a^#Q1:Bai_6E%&;5M[P0kDe7G>Vi2rH7k)XrALaK`UMWn&7h@KJSG/7k=\)[!]1iT60jT<op:%[Yb],ZR-Qc%ePd3^\Q(3bL>(Bu]7(4e-aPWdn]fib;I;OG[jf\AY`a&OoXbjkB-HiPHN;SqsCBk88HQk+K"N'[.H:@4dY'p=5?4V>tdl?Ft%/RHRG7D'(19*.aXi[)V'?oaqZ&"K.&cJcN>TY2.6k8RMo)PeR5d$BX14N2)O>PH8_E,r$j*+>Uj6ZDMdJEEQlL70QeY%W^(U5?IUBrc`WKhXQrq%_P;<Qk/ZB!qQkP,idEYf?)7F0>tgI2`3PaVV63'HAa*g/RI?"/Lh21./f*'Ij@*7-X;:j"ZRqk./N>E*5R&PjC7IRb?KGWd;@!YJ#EUQ("WYcT\@X%9Jt+)ZX-BncGd/;j&_m#i8nqD>@H~>endstream
endobj endobj
xref xref
0 31 0 38
0000000000 65535 f 0000000000 65535 f
0000000061 00000 n 0000000061 00000 n
0000000122 00000 n 0000000133 00000 n
0000000229 00000 n 0000000240 00000 n
0000000341 00000 n 0000000352 00000 n
0000000546 00000 n 0000000557 00000 n
0000000651 00000 n 0000000662 00000 n
0000000856 00000 n 0000000867 00000 n
0000001061 00000 n 0000001072 00000 n
0000001138 00000 n 0000001149 00000 n
0000001343 00000 n 0000001354 00000 n
0000001549 00000 n 0000001560 00000 n
0000001755 00000 n 0000001766 00000 n
0000001961 00000 n 0000001972 00000 n
0000002167 00000 n 0000002178 00000 n
0000002373 00000 n 0000002294 00000 n
0000002579 00000 n 0000002500 00000 n
0000002785 00000 n 0000002706 00000 n
0000002855 00000 n 0000002912 00000 n
0000003146 00000 n 0000003118 00000 n
0000003277 00000 n 0000003324 00000 n
0000005186 00000 n 0000003530 00000 n
0000007092 00000 n 0000003600 00000 n
0000008874 00000 n 0000003891 00000 n
0000010692 00000 n 0000004043 00000 n
0000013269 00000 n 0000005997 00000 n
0000014981 00000 n 0000007903 00000 n
0000017082 00000 n 0000009685 00000 n
0000019149 00000 n 0000011503 00000 n
0000020877 00000 n 0000014080 00000 n
0000022293 00000 n 0000015792 00000 n
0000017893 00000 n
0000019960 00000 n
0000022281 00000 n
0000023417 00000 n
0000025249 00000 n
0000025754 00000 n
0000027172 00000 n
trailer trailer
<< <<
/ID /ID
[<a6147630729a0aa699ee753548d84bc4><a6147630729a0aa699ee753548d84bc4>] [<a03f72edec5402346255e0fe29756f08><a03f72edec5402346255e0fe29756f08>]
% ReportLab generated PDF document -- digest (opensource) % ReportLab generated PDF document -- digest (opensource)
/Info 18 0 R /Info 22 0 R
/Root 17 0 R /Root 21 0 R
/Size 31 /Size 38
>> >>
startxref startxref
23967 28945
%%EOF %%EOF

View File

@@ -43,10 +43,10 @@
| Welkom + Terugblik | 10 min | | Welkom + Terugblik | 10 min |
| **Theorie 1** — Waarom OpenCode + kern features | 20 min | | **Theorie 1** — Waarom OpenCode + kern features | 20 min |
| **Live Demo 1** — Desktop tour | 10 min | | **Live Demo 1** — Desktop tour | 10 min |
| **Theorie 2** — AGENTS.md + opencode.json + plugin + stack | 15 min | | **Theorie 2** — AGENTS.md + config + plugin + stack + Vercel | 17 min |
| **Live Demo 2** — Setup + worktree + bouw SmoothScroll | 15 min | | **Live Demo 2** — Setup + worktree + bouw + Vercel preview | 20 min |
| **Pauze** | 15 min | | **Pauze** | 15 min |
| Lesopdracht: bouw scroll-sectie | 35 min | | Lesopdracht: bouw scroll-sectie + preview | 28 min |
| Huiswerk + Afsluiting | (eind) | | Huiswerk + Afsluiting | (eind) |
**Visual:** Timeline met YELLOW pauze-rij + PINK demo-rijen **Visual:** Timeline met YELLOW pauze-rij + PINK demo-rijen
@@ -298,32 +298,66 @@ scroll storytelling. Kies GSAP voor timing-precisie + GPU-perf.
--- ---
## Slide 13: LIVE DEMO 2 — Setup + Worktree + SmoothScroll ## Slide 13: Vercel + Preview Deployments
### Wat je nu gaat zien (~15 min) ### Feature branches = automatische preview URLs
**Het idee:**
Elke push naar GitHub → Vercel maakt automatisch een **unieke preview URL** voor die branch. Geen handmatige deploys, geen "stuur me een screenshot".
**Setup (eenmalig per project):**
1. `npx vercel link` — koppelt repo aan Vercel project (of via dashboard)
2. Git remote staat → Vercel installeert eigen GitHub App
3. Klaar — elke `git push` triggert deploy
**Hoe het werkt:**
| Branch | Wat krijgt Vercel? | URL |
|--------|-------------------|-----|
| `main` | Production deploy | `jouw-app.vercel.app` |
| `feature-hero` | Preview deploy | `jouw-app-git-feature-hero-jij.vercel.app` |
| `feature-gallery` | Preview deploy | `jouw-app-git-feature-gallery-jij.vercel.app` |
**Waarom dit goud is voor scroll-storytelling:**
- Stuur preview URL naar designers/stakeholders per feature
- Vergelijk verschillende secties side-by-side
- Geen "het werkt op mijn machine"
- Comment in PR direct met working URL
**Combinatie met onze worktrees:**
- 1 worktree = 1 branch = 1 Vercel preview
- 3 features parallel = 3 live previews
**Bronnen:** [20] [21]
---
## Slide 14: LIVE DEMO 2 — Setup + Worktree + Vercel preview
### Wat je nu gaat zien (~20 min)
**Wat ik laat zien:** **Wat ik laat zien:**
1. **AGENTS.md** invullen met onze projectregels 1. **AGENTS.md** invullen met onze projectregels
2. **opencode.json** maken met permissions 2. **opencode.json** maken met permissions
3. Tonen dat `rm -rf` geblokkeerd wordt 3. Tonen dat `rm -rf` geblokkeerd wordt
4. **`ocx` + worktree plugin** installeren (was al klaar — kort tonen) 4. **`ocx` + worktree plugin** verifiëren (was al klaar)
5. Via prompt: **worktree feature-hero** aanmaken 5. Via prompt: **worktree feature-hero** aanmaken
6. **+ New Session** → openen op worktree-folder 6. **+ New Session** → openen op worktree-folder
7. In feature-hero: **SmoothScroll wrapper** laten bouwen 7. **SmoothScroll wrapper** laten bouwen — agent volgt AGENTS.md
8. Bewijs dat **AGENTS.md regels gevolgd worden** (useGSAP, geen Framer Motion, etc.) 8. **`git push origin feature-hero`** vanuit worktree
9. Open Vercel dashboard → toon **preview URL** verschijnt automatisch
10. Open preview URL → scroll-animatie werkt live
**Visual:** Mockup Sessions sidebar + diff-viewer. PINK badge "LIVE DEMO". **Visual:** Mockup Sessions sidebar + Vercel preview URL. PINK badge "LIVE DEMO".
--- ---
## Slide 14: Pauze ## Slide 15: Pauze
### Pauze! ### Pauze!
**Visual:** "Pauze" groot, "15 minuten" **Visual:** "Pauze" groot, "15 minuten"
--- ---
## Slide 15: Lesopdracht ## Slide 16: Lesopdracht
### Bouw een scroll-sectie — 35 minuten ### Bouw een scroll-sectie + preview — 28 minuten
**Setup (al klaar in starter):** **Setup (al klaar in starter):**
- Next.js 16 starter - Next.js 16 starter
@@ -332,22 +366,27 @@ scroll storytelling. Kies GSAP voor timing-precisie + GPU-perf.
- SmoothScroll wrapper bestaat al - SmoothScroll wrapper bestaat al
**Jouw taak:** **Jouw taak:**
0. Fork starter naar **jouw** GitHub + `vercel link`
1. `/init` runnen — check AGENTS.md 1. `/init` runnen — check AGENTS.md
2. Vraag agent: maak worktree voor jouw feature 2. Vraag agent: maak worktree voor jouw feature
3. Open de worktree in nieuwe Sessions tab 3. Open de worktree in nieuwe Sessions tab
4. Plan-mode → review → Tab → Build 4. Plan-mode → review → Tab → Build
5. Test op `npm run dev` 5. Test lokaal op `npm run dev`
6. Commit + push 6. `git push origin <jouw-branch>` — Vercel deployt automatisch
7. Open preview URL → animatie werkt live → plak in chat
> **Niet klaar in de les?** Geen probleem — je werkt thuis in dezelfde fork
> verder. Het huiswerk bouwt voort op dit punt.
**Sectie keuze:** hero (SplitText) · features-grid (stagger) · testimonials (horizontaal) · gallery (parallax) **Sectie keuze:** hero (SplitText) · features-grid (stagger) · testimonials (horizontaal) · gallery (parallax)
--- ---
## Slide 16: Huiswerk ## Slide 17: Huiswerk
### Bouw je eigen scroll-animatie site ### Bouw verder in je les-repo
**Voor volgende week:** **Voor volgende week:**
Bouw een **kleine landing page** met 3-4 scroll secties. Doorbouwen in dezelfde fork als in de les — eerst sectie 1 afmaken (indien nog niet), dan 2-3 extra secties tot een **kleine landing page** met 3-4 scroll secties.
**Eisen:** **Eisen:**
- [ ] Eigen `AGENTS.md` (onze mag als basis) - [ ] Eigen `AGENTS.md` (onze mag als basis)
@@ -355,16 +394,16 @@ Bouw een **kleine landing page** met 3-4 scroll secties.
- [ ] **2+ worktrees** gebruikt (Sessions sidebar) - [ ] **2+ worktrees** gebruikt (Sessions sidebar)
- [ ] **3 verschillende scroll-animaties** - [ ] **3 verschillende scroll-animaties**
- [ ] Lenis smooth scroll - [ ] Lenis smooth scroll
- [ ] Deploy op Vercel - [ ] **Vercel gekoppeld** — main = productie, branches = previews
- [ ] `WORKFLOW.md` reflectie (max 400 woorden) - [ ] `WORKFLOW.md` reflectie (max 400 woorden) met **screenshots van preview URLs per feature**
**Lever in:** GitHub URL + Vercel URL + screenshot van `git worktree list` **Lever in:** GitHub URL + Production URL + minstens **2 preview URLs** (per worktree-branch) + screenshot `git worktree list`
**Bonus:** Eigen sub-agent · MCP server (context7) · Lighthouse 90+ **Bonus:** Eigen sub-agent · MCP server (context7) · Lighthouse 90+
--- ---
## Slide 17: Afsluiting ## Slide 18: Afsluiting
### Volgende les — Introductie Cursor ### Volgende les — Introductie Cursor
**Vandaag gedaan:** **Vandaag gedaan:**
@@ -374,6 +413,7 @@ Bouw een **kleine landing page** met 3-4 scroll secties.
- opencode-worktree plugin via ocx - opencode-worktree plugin via ocx
- Live: Next.js 16 + GSAP + Lenis scroll-site - Live: Next.js 16 + GSAP + Lenis scroll-site
- Parallel agents via Sessions sidebar - Parallel agents via Sessions sidebar
- Vercel preview deployments per branch
**Volgende keer — Cursor:** **Volgende keer — Cursor:**
- De commerciele tegenhanger van OpenCode - De commerciele tegenhanger van OpenCode
@@ -401,11 +441,12 @@ Bouw een **kleine landing page** met 3-4 scroll secties.
| 10 | Worktree plugin | Theorie | [12] [13] | | 10 | Worktree plugin | Theorie | [12] [13] |
| 11 | Demo stack | Theorie | [14][18] | | 11 | Demo stack | Theorie | [14][18] |
| 12 | Onze AGENTS.md | Theorie | [14] [15] [19] | | 12 | Onze AGENTS.md | Theorie | [14] [15] [19] |
| 13 | **LIVE DEMO 2** | Demo | — | | 13 | Vercel preview deployments | Theorie | [20] [21] |
| 14 | Pauze | Break | — | | 14 | **LIVE DEMO 2** | Demo | — |
| 15 | Lesopdracht | Praktijk | alle | | 15 | Pauze | Break | — |
| 16 | Huiswerk | Praktijk | alle | | 16 | Lesopdracht | Praktijk | alle |
| 17 | Afsluiting | Closing | — | | 17 | Huiswerk | Praktijk | alle |
| 18 | Afsluiting | Closing | — |
--- ---
@@ -439,3 +480,7 @@ Bouw een **kleine landing page** met 3-4 scroll secties.
**Lenis:** **Lenis:**
- [19] Lenis GitHub — https://github.com/darkroomengineering/lenis - [19] Lenis GitHub — https://github.com/darkroomengineering/lenis
**Vercel:**
- [20] Vercel Preview Deployments — https://vercel.com/docs/deployments/preview-deployments
- [21] Vercel + Git Integration — https://vercel.com/docs/git