PNG  IHDR!@ PLTE>O`jqv tEXtPage
Add archive and scores - metalympiada
git clone git://metalympiada.org/
Log | Files | README | LICENSE

commit d47dfc43edbb0a6017c0bd022a98734d0bae8eb0
parent 0782632336050355058c69e9ae7f0ec000664859
Author: Metalympiáda <metalympiada@matfyz.cz>
Date:   Sat,  1 Apr 2023 23:58:32 +0200

Add archive and scores

Diffstat:
MMakefile | 3+++
Aarchive/0/problems | 27+++++++++++++++++++++++++++
Aarchive/0/solutions | 10++++++++++
Massets.scm | 32+++++++++++++++++++++++++++-----
Mdb.scm | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mdb.sql | 8++++++++
Ametatron13.scm | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mroutes.scm | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mstyle.scm | 15+++++++++++++++
9 files changed, 362 insertions(+), 21 deletions(-)

diff --git a/Makefile b/Makefile @@ -12,3 +12,6 @@ meta.sqlite: db.sql stagit: stagit.c ${CC} $^ -lgit2 -Ofast -flto -march=native -Wpedantic -Wall -Wextra -o $@ + +metatron13: + guile metatron13.scm diff --git a/archive/0/problems b/archive/0/problems @@ -0,0 +1,27 @@ +Boris riešil prvú úlohu Metalympiády. Jej zadanie spomínalo Borisa, ako rieši prvú úlohu Metalympiády a pýtalo sa na odpoveď na ňu. Pomôžte mu. +~Ivan trpí me(n)tálnym problémom. Zakaždým, keď prečíta nejakú vetu, nahlas vysloví počet slov v nej. Ivan práve kontroloval svoje odpovede na Metalympiáde. Prečítal si odpoveď na šiestej úlohe a vyslovil číslo 1003. Následne si utrel okuliare a zahľadeľ sa znovu. +Ktoré číslo vyslovil po opakovanom prečítaní? +~Istému človeku sa nezdali úlohy Metalympiády dosť meta, a tak sa rozhodol, že si vymyslí vlastnú. Odpoveďou na jeho úlohu je číslo, ktoré sa spomína v zadaní úlohy Metalympiády s poradovým číslom takým, ktorého počet písmen v jeho slovenskom zápise je rovnaký ako samotná hodnota daného čísla. +Ktoré číslo je odpoveďou na úlohu v tejto úlohe? +~Vladislav riešil Metalympiádu, ale jej úlohy sa mu zdali príliš komplikované. Preto si ju svojím prazvláštnym spôsobom zjednodušil na nasledujúcu sadu výrokov. Keď vám ju predstavil, začudovali ste sa, ako súvisia s Metalympiádou, ale napriek tomu ste sa rozhodli, že mu pomôžete. +Rozhodnite, či je daný výrok pravdivý alebo nepravdivý. (Prípustné odpovede sú Áno, Nie; oddelené od seba medzerou.) + - Protagoras v eponymnom Platónovom diele navrhol hru, ktorá môže byť považovaná za prvú verziu Metalympiády. + - Na medzinárodnej vesmírnej stanici ISS sa nachádza 23 okien, cez ktoré bolo už aspoň raz možné vidieť budovu, v ktorej vznikla táto úloha Metalympiády. + - Mäkkýš rodu Helix bol spozorovaný autormi Metalympiády počas písania tejto úlohy. + - Tento výrok je tautológia. +~Boris cestoval vlakom. Vystrčil z okna svoju šesťdesiatmetrovú teleskopickú anténu. Vzápätí ju tým istým oknom vyhodil von, pretože si uvedomil, že tento kontext je pre súčasnú úlohu Metalympiády nepodstatný. Položil si len otázku:"Koľko správnych odpovedí má táto otázka?" +Koľko správnych odpovedí má otázka, ktorú si položil Boris? +~Ivan trpí obsesiou - počíta cifry každého čísla, ktoré vidí a v prípade, že je tento počet nepárny, prudko vynadá autorovi daného čísla. Keď si prečítal svoju odpoveď na druhú úlohu Metalympiády, udrel sa päsťou do čela, vytiahol z vrecka mobil, spustil aplikáciu "Fotoaparát", prepol si ju na prednú kameru a na svoj obraz z plných pľúc zakričal:"Ty korheľ jeden skazuvziaty!" +Koľkociferné číslo ho tak pobúrilo? +~Umelá Inteligencia Metatron 13 kontrolovala Vladislavove riešenia Metalympiády. V opdpovedi na Úlohu 7 mal napísané "Kôň", čo však Metatron 13 vyhodnotil za nesprávne, keďže táto úloha sa pýta na dvojnásobok súčtu všetkých číselných odpovedí na úlohy tohto kola Metalympiády. +~Ivan trpí predstavou, že dokáže komunikovať s bohom. Ivan nevedel vyriešiť prvú úlohu Metalympiády, a tak poprosil boha o pomoc. Boh sa nad ním zľutoval a prezradil mu tajomstvo vesmíru, v ktorom sa Ivan nachádza:"'Byť časťou niečoho' je tranzitívna, reflexívna a antisymetrická relácia." Ivan strávil nasledujúcich 1,38 stotín sekundy premýšlaním nad týmto tvrdením, než sa dovtípil, že mu s jeho problémom neveľmi pomôže. Odvrkol:"Vrelá vďaka, ty šumichrast!" +Boh nezniesol váhu vysloveného pomenovania a podal mu papierik s vaším riešením prvej úlohy. To však Ivanovi pripomenulo jeho poruchu zo šiestej úlohy. "Ty chňupák jeden dvojdvorský!" odkazuje vám. +Medzičasom začal Vladislav naliehať na Ivana. Tiež sa zaujímal o riešenie prvej úlohy, ale nezabúdajte, že má problém so spracovávaním čísel, ktoré si nedokáže ukázať na prstoch rúk. +Keďže Ivan sa na vaše riešenie nedokáže ani pozrieť, vyžaduje od vás ďalšie číslo, ktoré môže odovzdať aj Vladislavovi. +~Ivan trpí depresiou spôsobenou jeho neschopnosťou systematizovať mereológiu. Autori metalympiády ho preto poslali na terapiu k tej úlohe Metalympiády, pri prečítaní názvu ktorej im nevynadá, keďže sa im nechcelo vymýsľať ďalšie slovenské pejoratíva. +"Čo trýzni tvoju skľúčenú dušu?" opýtala sa ho táto úloha. +"Moja neschopnosť systematizovať mereológiu," odvetil Ivan. +"Na cestu neľahkú si sa vydal, ale čímsi nenáročným započatá byť musí. Tvojim prvým schodíkom na zikkurate mereologickej gnózy nech je odpoveď na nasledujúcu otázku: Aká je mohutnosť množiny všetkých neprázdnych podmnožín množiny všetkých úloh tohto kola Metalympiády?" +Napíšte prvý schodík Ivanovho zikkuratu mereologickej gnózy. +~Vladislav pil toluén. Zrazu všade okolo seba uvidel Úlohu 9. Vladislav ju oslovil:"Hskjdlksjf!" +"Pomlč," odvetila mu, "nie si ty ten, ktorému prihováram sa, lež Metalympiády riešiteľ má si moje slová k srdcu vziať. Prijmi vrúcnu vďaku za tvoju snahu pri pokúšaní sa zistiť správne odpovede na úlohy Metalympiády. Pamätajte, život je krásny, hoci ste to azda pri riešení úloh Metalympiády (aspoň o nejakých jeho výtvoroch) spochybňovali. Tu mali nasledovať ďalšie vety na rozlúčenie sa s vami, ale neboli vymyslené." diff --git a/archive/0/solutions b/archive/0/solutions @@ -0,0 +1,10 @@ +Odpoveď môže byť ľubovoľná, keďže podľa zadania musí len byť odpoveďou na túto otázku. Podľa zadania úlohy 8 to ale musí byť číslo s nepárnym počtom cifier. +~Počet slov v odpovedi na úlohu 6. +~Tri. +~Každý výrok je pravdivý alebo nepravdivý. Odpoveď je vždy Áno. +~Jednu. Ak by mala nula, bola by "nula" správna odpoveď. Ak by mala viac, muselo by to byť viac rôznych čísel. +~Počet cifier v odpovedi na úlohu 2. +~Riešenie rovnice 2(Σ + x) = x, kde Σ je súčet číselných odpovedí na zvyšné úlohy Metalympiády. +~10 - jediné číslo, ktoré sa dá ukázať na prstoch rúk s párnym počtom cifier. +~V každej podmnožine každá úloha môže, alebo nemusí byť nezávisle na ostatných. Počet podmnožín je teda 2^10 a neprázdnych o 1 menej. +~Podľa úlohy 8 je 'byť časťou niečoho' vo vesmíre, kde sa nachádza Vladislav, antisymetrická relácia. Úloha 10 je časťou úlohy 9 a úloha 9 je časťou úlohy 10, teda ide o tú istú úlohu s rovnakou správnou odpoveďou. diff --git a/assets.scm b/assets.scm @@ -1,11 +1,19 @@ (define-module (assets) - #:export (text-logo logo style problems rules-text stagit-sources)) + #:export (text-logo logo style archive archive-problems archive-solutions + problems rules-text stagit-sources)) (use-modules (srfi srfi-1) + (srfi srfi-9) (style) (sxml simple) (ice-9 textual-ports) (ice-9 ftw)) +(define-record-type <archive-round> + (make-archive-round problems solutions) + archive-round? + (problems archive-problems) + (solutions archive-solutions)) + (define text-logo " __ ___ __ __ _ __ __ / |/ /__ / /_____ / /_ ______ ___ ____ (_)_/_/ ____/ /___ @@ -67,10 +75,23 @@ (define logo (logo-generator 500 logo-ratio 11)) -(define problems (string-split (call-with-input-file "texts/problems" get-string-all) - #\~)) +(define (read-string file) (call-with-input-file file get-string-all)) + +(define archive-dir "archive") + +(define (tild-split file) (string-split (read-string file) #\~)) -(define rules-text (call-with-input-file "texts/rules" get-string-all)) +(define archive + (let ((text-file (lambda (dir name) + (string-append archive-dir "/" dir "/" name)))) + (map (lambda (dir) + (make-archive-round (tild-split (text-file dir "problems")) + (tild-split (text-file dir "solutions")))) + (scandir archive-dir (lambda (f) (not (string-every #\. f))))))) + +(define problems (tild-split "texts/problems")) + +(define rules-text (read-string "texts/rules")) (define stagit-dir ".stagit") @@ -87,5 +108,6 @@ (if (equal? flag 'regular) (hash-set! stagit-sources (substring name (+ (string-length stagit-dir) 1)) - (cdr (xml->sxml (call-with-input-file name get-string-all))))) + (cdr (xml->sxml (read-string name))))) #t)) + diff --git a/db.scm b/db.scm @@ -1,10 +1,15 @@ (define-module (db) #:export (decrypt-user select-user + select-all-users insert-user login-user select-answers - insert-answers)) + select-answers-content + insert-answers + insert-results + insert-score + select-results)) (use-modules (ice-9 binary-ports) (rnrs bytevectors) (srfi srfi-1) @@ -33,8 +38,8 @@ (list #f (case code ((1299) "Niektoré povinné polia chýbajú") ((2067) "Užívateľ s týmito údajmi už existuje") - (else (begin (display code) - (display errmsg) + (else (begin (display code (current-error-port)) + (display errmsg (current-error-port)) "Niečo sa pokazilo v útrobách metaserveru! Skúste znovu"))))) (define (select-user name) @@ -44,6 +49,12 @@ (sqlite-finalize stmt) result)) +(define (select-all-users) + (let* ((stmt (sqlite-prepare db "select name from users")) + (result (sqlite-map (cut vector-ref <> 0) stmt))) + (sqlite-finalize stmt) + result)) + (define (encrypt-user user) (base64-encode (encrypt (string->utf8 user) SECRET_KEY))) @@ -97,14 +108,68 @@ (list #t))) err->msg)) +(define (insert-results results user round) + (catch 'sqlite-error + (lambda () + (let* ((stmt-text (string-append (fold (lambda (i so-far) + (string-append so-far ", (?,?,?,?)")) + "insert into answers (name, round, problem, correct) values(?,?,?,?)" + (iota (- (length results) 1))) + " on conflict (name, round, problem) do update set correct=excluded.correct")) + (stmt (sqlite-prepare db stmt-text))) + (for-each (lambda (a i) (begin (sqlite-bind stmt (+ (* i 4) 1) user) + (sqlite-bind stmt (+ (* i 4) 2) round) + (sqlite-bind stmt (+ (* i 4) 3) (+ i 1)) + (sqlite-bind stmt (+ (* i 4) 4) a))) + results + (iota (length results))) + (sqlite-step stmt) + (sqlite-finalize stmt) + (list #t))) + err->msg)) + (define (select-answers user round) (catch 'sqlite-error (lambda () - (let* ((stmt-text "select problem, content from answers where name=? and round=? order by problem") + (let* ((stmt-text "select content, correct from answers where name=? and round=? order by problem") (stmt (sqlite-prepare db stmt-text))) (sqlite-bind stmt 1 user) (sqlite-bind stmt 2 round) - (let ((res (sqlite-map (cut vector-ref <> 1) stmt))) + (let ((res (sqlite-map identity stmt))) (sqlite-finalize stmt) (if (null? res) #f res)))) - (lambda (key who code msg) (begin (display msg) #f)))) + (lambda (key who code msg) (begin (display msg (current-error-port)) #f)))) + +(define (select-answers-content user round) + (catch 'sqlite-error + (lambda () + (let* ((stmt-text "select content from answers where name=? and round=? order by problem") + (stmt (sqlite-prepare db stmt-text))) + (sqlite-bind stmt 1 user) + (sqlite-bind stmt 2 round) + (let ((res (sqlite-map (cut vector-ref <> 0) stmt))) + (sqlite-finalize stmt) + (if (null? res) #f res)))) + (lambda (key who code msg) (begin (display msg (current-error-port)) #f)))) + +(define (select-results round) + (catch 'sqlite-error + (lambda () + (let* ((stmt-text "select name, score from results where round=? order by score desc") + (stmt (sqlite-prepare db stmt-text))) + (sqlite-bind stmt 1 round) + (let ((res (sqlite-map identity stmt))) + (sqlite-finalize stmt) + (if (null? res) #f res)))) + (lambda (key who code msg) (begin (display msg (current-error-port)) #f)))) + +(define (insert-score name round score) + (catch 'sqlite-error + (lambda () + (let ((stmt (sqlite-prepare db "insert into results values(?, ?, ?) on conflict(name, round) do update set score=excluded.score"))) + (sqlite-bind stmt 1 name) + (sqlite-bind stmt 2 round) + (sqlite-bind stmt 3 score) + (sqlite-step stmt) + (sqlite-finalize stmt))) + err->msg)) diff --git a/db.sql b/db.sql @@ -13,3 +13,11 @@ create table if not exists answers ( unique(name, round, problem), foreign key (name) references users(name) on delete cascade on update cascade ); + +create table if not exists results ( + name text not null, + round integer not null, + score real not null, + unique(name, round), + foreign key (name) references users(name) on delete cascade on update cascade +); diff --git a/metatron13.scm b/metatron13.scm @@ -0,0 +1,99 @@ +(use-modules (db)) + +(define (preprocess answer) + (string-delete #\. (string-downcase answer))) + +(define (slovakoczech->number s) + (cond ((equal? s "jedna") 1) + ((or (equal? s "dva") + (equal? s "dvě") + (equal? s "dve")) + 2) + ((or (equal? s "tri") + (equal? s "tři")) + 3) + ((or (equal? s "štyri") + (equal? s "styri") + (equal? s "čtyři") + (equal? s "ctyri")) + 4) + ((or (equal? s "pat") + (equal? s "päť") + (equal? s "pět") + (equal? s "pet")) + 5) + ((or (equal? s "sest") + (equal? s "šesť") + (equal? s "šest")) + 6) + ((or (equal? s "sedem") + (equal? s "sedm")) + 7) + ((or (equal? s "osem") + (equal? s "osm")) + 8) + ((or (equal? s "deväť") + (equal? s "devat") + (equal? s "devět") + (equal? s "devet")) + 9) + ((or (equal? s "desať") + (equal? s "desat") + (equal? s "deset")) + 10) + (else 0))) + +(define (str->number s) + (let ((decimal (string->number s))) + (if decimal decimal (slovakoczech->number s)))) + +(define (count-decimal-digits n) + (if (eq? n 0) + 0 + (+ 1 (count-decimal-digits (quotient n 10))))) + +(define (eval-round0 a) + (let* ((results (map (lambda (i) 0.0) (iota (length a)))) + (answers (map preprocess a)) + (numerical (map str->number answers)) + (sum (apply + numerical))) + (begin + (if (eq? (modulo (count-decimal-digits (list-ref numerical 0)) 2) 1) + (list-set! results 0 1.0)) + (if (eq? (list-ref numerical 1) 1) + (list-set! results 1 1.0)) + (if (eq? (list-ref numerical 2) 3) + (list-set! results 2 1.0)) + (let* ((is-yes? (lambda (s) (or (equal? s "ano") + (equal? s "áno")))) + (partial (map (lambda (s) (if (is-yes? s) 0.25 0.0)) + (string-tokenize (list-ref answers 3)))) + (total (apply + partial))) + (list-set! results 3 (* total total))) + (if (eq? (list-ref numerical 4) 1) + (list-set! results 4 1.0)) + (if (or (eq? (list-ref numerical 5) 1) + (equal? (list-ref answers 2) "jednociferné") + (equal? (list-ref answers 2) "jednociferne")) + (list-set! results 5 1.0)) + (if (eq? (list-ref numerical 6) (* 2 (apply + numerical))) + (list-set! results 6 1.0)) + (if (eq? (list-ref numerical 7) 10) + (list-set! results 7 1.0)) + (if (eq? (list-ref numerical 8) 1023) + (list-set! results 8 1.0)) + (if (eq? (list-ref numerical 9) 1023) + (list-set! results 9 1.0)) + results))) + +(define (eval-user-round user rnd evaluator) + (let ((answers (select-answers-content user rnd))) + (if answers + (let ((results (evaluator answers))) + (insert-results results user rnd) + (insert-score user rnd (apply + results)))))) + +(define users (select-all-users)) +(for-each (lambda (u) (begin + (eval-user-round u 0 eval-round0))) + users) diff --git a/routes.scm b/routes.scm @@ -22,10 +22,10 @@ (define deadline "380. narodenín Isaaca Newtona") (define after-deadline #t) (define announcement (if after-deadline - "Čoskoro pribudnú výsledky a nové zadania. Deadline bol do " + "Čoskoro pribudnú nové zadania. Deadline bol do " "Riešenia je možné posielať do ")) -(define current-round 0) +(define current-round 1) (define footer `(footer "Stránka Metalympiády je slobodný softvér pod licenciou AGPL: " @@ -40,19 +40,21 @@ (aria-label "ASCII logo Metalympiády")) ,text-logo))) (nav (@ (id "mainav")) - (a (@ (href "/pravidla.png")) "Pravidlá")) + (a (@ (class "navlink") (href "/pravidla.png")) "Pravidlá") + (a (@ (class "navlink") (href "/archiv.png")) "Archív") + (a (@ (class "navlink") (href "/vysledky.png")) "Výsledky")) ,@body ,footer))) (define (title-template title body) - `((title ,(string-append title " | " site-name)) + `((title ,title " | " ,site-name) ,(main-template body))) (define login-form `(form (@ (class "login") (action "#") (method "POST")) (label "Meno:" (input (@ (class "short") (name "name") (required)))) (label "Heslo:" - (input (@ (class "short") (name "password") (type "password") (required)))) + (input (@ (class "short") (name "password") (type "password") (required)))) (input (@ (class "button") (type "submit") (value "Prihlásiť"))) (a (@ (href "/register.png")) "Registrovať"))) @@ -69,7 +71,7 @@ (p (@ (class "text")) ,text)))) (define* (problem-page #:optional (err #f) (user #f)) - (let ((answers (if user (select-answers user current-round) #f)) + (let ((answers (if user (select-answers-content user current-round) #f)) (submit-available (and user (not after-deadline)))) (values "Zadania" @@ -87,17 +89,76 @@ `(input (@ (name ,i) (class "block") ,(if answers - `(value ,(list-ref answers index)) + `(value ,(list-ref answers + index)) `(placeholder "Odpoveď " ,i)))) '()) (hr)))) - problems (iota (length problems))) + problems + (iota (length problems))) ,(if submit-available `(input (@ (class "button block") (type "submit") (value "Uložiť riešenia"))) '())))))) +(define* (round-list title link #:optional (user #f)) + (values + title + `(,(if user (greeting user) login-form) + (h1 ,title) + ,@(map (lambda (i) `(p (a (@ (href ,link "/" ,i ".png")) "Kolo " ,i))) + (iota current-round))))) + +(define* (archive-page a rnd #:optional (user #f)) + (let ((answers (if user (select-answers user rnd) #f)) + (title (string-append "Kolo " (number->string rnd)))) + (values + title + `(,(if user (greeting user) login-form) + (h1 ,title) + (form (@ (action "#") (method "POST")) + ,@(map (lambda (text solution index) + (let ((i (number->string (+ index 1)))) + `((p (@ (class "text")) + (span (@ (class "problem-num")) ,i ".") + ,text) + (p (@ (class "text")) + (strong (@ (class "start")) "Správna odpoveď:") + ,solution) + ,(if answers + `(p (@ (class "text")) + (strong (@ (class "start")) + "Vaša odpoveď:") + ,(let* ((a (list-ref answers index)) + (v (vector-ref a 1))) + `(,(vector-ref a 0) + (strong (@ (class "start " + ,(if (> v 0.5) + "good" + "bad"))) + "Hodnotenie:") + ,v))) + "") + (hr)))) + (archive-problems a) + (archive-solutions a) + (iota (length problems)))))))) + +(define (score-page rnd) + (let ((results (select-results rnd)) + (title (string-append "Výsledky kola " (number->string rnd)))) + (values + title + `((h1 ,title) + (table ,@(map (lambda (result i) + `(tr (td ,(+ i 1) ".") + (td ,(vector-ref result 0)) + (td ,(vector-ref result 1)))) + results + (iota (length results)))) + (p "Blahoželáme!"))))) + (define* (register-page #:optional (err #f)) (values "Registracia" @@ -191,6 +252,19 @@ (map (cut string-split <> #\=) (string-split (utf8->string q) (char-set #\& #\;))))) +(define (not-found) + (values (build-response #:code 404) "404")) + +(define (correct-round-path rnd) + (let* ((split (string-split rnd #\.)) + (n (string->number (car split)))) + (if (and (eq? (length split) 2) + (equal? (cadr split) "png") + n + (< n current-round)) + n + #f))) + (define (router request body) (let* ((method (request-method request)) (path (request-path-components request)) @@ -214,13 +288,31 @@ (("pravidla.png") (render (lambda () (text-page "Pravidlá" rules-text user)))) + (("archiv.png") (render (lambda () (round-list "Archív" + "archiv" + user)))) + (("archiv" rnd) (let ((n (correct-round-path rnd))) + (if n + (render (lambda () + (archive-page (list-ref archive n) + n + user))) + (not-found)))) + (("vysledky.png") (render (lambda () (round-list "Výsledky" + "vysledky" + user)))) + (("vysledky" rnd) (let ((n (correct-round-path rnd))) + (if n + (render (lambda () + (score-page n))) + (not-found)))) (("register.png") (render register-page)) (("logout.png") (logout-response)) (("git") (redirect "/git/index.png")) (("git" . rest) (let ((s (hash-ref stagit-sources (string-join rest "/")))) (if s (render (lambda () s) main-template) - (values (build-response #:code 404) "404")))) + (not-found)))) (("favicon.ico") (values (build-response #:code 410) "")) - (_ (values (build-response #:code 404) "404")))) + (_ (not-found)))) (_ (values (build-response #:code 402) ""))))) diff --git a/style.scm b/style.scm @@ -70,6 +70,9 @@ (footer (font-size "1rem") (text-align "center")) + + (table + (min-width "50%")) (.textlogo (font-size (min "2.2vw" "0.795rem"))) @@ -85,6 +88,9 @@ (h1 (margin "2rem 0 0.8rem")) + + (.navlink + (margin-right "1rem")) (.text (font-size "1rem") @@ -159,6 +165,15 @@ (.h (color "#aaa") (text-decoration "none")) + + (.start + (margin "0.5em")) + + (.good + (color ,(color->string foreground-color))) + + (.bad + (color ,(color->string error-color))) (.ghost (visibility "hidden"))))
ORAIDATxoGvbzO{  A6S^Ctu>>OP0q&~ \l6E.RlK1g<&5ӿzU_x W&D20!@0 Pd8&@&RRԠބXA tYrIhZ6 /dF8+D$1?+x0%LbG=D{L({0Anb\AKƷai0 G %.^?Bw|<>oՀvZslC 8L 2(LW2nH3c*uj04L AD1-B>0SyKx؀|D1P 6R~;/(n6`K A!.BVFh>!(Bm5a+JHBۑh8¦;pƐb93=:@@u6ۊ0DP1и|Ҋ}R9#A;HkULuƈ)Z}Й `|kF.X^\?n~}C PpZGVC`D)bLh>rV@MVG 訉sh9zhJߏIQۧzoFTpվK#Z2>7 Zd fЪa_Ef} .#h(.MGJ (}g3RH:g11ESN3^C|0"sL(1%\ H$b Kq e1z'^la!҅E-_Gh/)| ײ61ѼbѬ÷oʥҠW"}FHW<Z 3+K>:EfeҠ42;HVgʽX0@fcnNGGJTЃfF)ԡ` ɘ34?REtStdґ,lڎ##eU/&9vim:2 aZcl<ش3lih:t!f1N,p/ 7 ]A@~Xn(!ӆmt.=(*/0Mcp"l.#@jٺ(aӫ;LPR~AƔ F SpP`NÕ9p#G+Ŕ\ ^*EO`:ȀonS"VfN=` nda;fN='^Db@.W ^eWQ]T 00F%a`t4~`'xCHqK Hހj8`YA94dOl|``3 )> < ]BO|74X8 % 6SPC¦!M q&OfH8M^?)Ey쯒og=+y$z*zJ)zO{"_8f{ldlE^Ji* pl*y$%] J3KtXvS PN6>@W>;70E%d }z|O!1uw%&X&gɏ ԠL&/x_k߰ٿJe3 ׃~ +Yi0N5KI .oܷ%vkįv ٳɼ5ZɓN)rwOhdD33ɋͳ՛Tlp9w7|/Ϟwq0=GB]7}Nxq6MgIR dE\ z-|:])_Z!Tt^jHc2;xPU흢 RT h_ʧR޺N\(t~1uYTC.=,Qy@9#${Վ(vB; :ŸlVDEsFam*3(>Ig^f5WW(4=I3 kT%.| S(Q\M.v&SwL,ɥ1zYt[Ybm`!jˏjVM^l\dS?=seb>ͦjw|^`|D7>T!{Y*N`^3o|R CNRԢLYoN%MPn3/6l|et<u,T=lHQJиUEuQ4<׏~.}r\߲jd*W]31u΍-(\˅lֶR|vfV@s| [eK`i-ZH Xx@b/re3' KsZŚml9Ix'oԷ$ܢ/x 칝ɩ#@\XM_WoSwLcׇT 4][N027[ZB5B'פ<ķ;z1nMvT/yzZ++ E (}$+p/)m&nm.3n.Jr9-q}NErc k<$6nqWzn/R@l.%/>=N-cFXזZV\NяjXr~IuYs9 Kg5,W7ȘUݐM{>V ϓY213bj!}n G`zt60 =Mn翇~G$E=Ly?ٍP&5)=aeR7|͢.4y_L9PB@5!x^y@ejCyE;+o:96zWk%I YnW$#K* >tnhؔVPШʣK{M5:v}:~j3'rWn7P׫Oɣm&_\7cYPNA7f{$Q=FÙƐ[1v}݊ JiC,#M&sCJ=&kz4-^Km؀<Ԑ6J"]_C-:ظbtJ36ꀞgXoN$,CԜvnjPڪ.gyL/e Yޕv j9vZMGE# 8<ղ) 5,Rq; +y b.W_0in XF鱗\z;h_lv.+ PF"Z5:ޔF3/+[@5X˨DTpYs:_^{#A)J2 *NV/=r?k㪸MBP߇ 㣥2֒ժ>0h4h~'[16bp7쌅FC4i D؃ \_Њ^45" 1~P wlG0- #./@higYS'Ch†0x8(aנk4zq/d-(G &Rlƅ6>lHDZod@]SyafՁ̀?71+^=@ `M8 har ؅ lrfרvBʤ*%#azE/a:p}\ 0 X}Ƕ -Ah!sɧt׎ӤJWyE]!@־"sрJ?IENDB`