From 599712fab9127013e2d89dadabd839c847730637 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Sun, 1 Jul 2018 13:20:01 -0700 Subject: rip out mage --- Dockerfile | 13 +- box.rb | 36 ----- cmd/site/gopreload.go | 9 ++ cmd/site/gops.go | 13 ++ cmd/site/hash.go | 14 ++ cmd/site/html.go | 73 ++++++++++ cmd/site/main.go | 205 +++++++++++++++++++++++++++ cmd/site/rss.go | 64 +++++++++ gopreload.go | 9 -- gops.go | 13 -- hash.go | 14 -- html.go | 73 ---------- mage.go | 80 ----------- mage_helpers.go | 75 ---------- main.go | 205 --------------------------- rice-box.go | 365 ------------------------------------------------- rss.go | 64 --------- scripts/deploy.sh | 7 + scripts/setupremote.sh | 6 + 19 files changed, 403 insertions(+), 935 deletions(-) delete mode 100644 box.rb create mode 100644 cmd/site/gopreload.go create mode 100644 cmd/site/gops.go create mode 100644 cmd/site/hash.go create mode 100644 cmd/site/html.go create mode 100644 cmd/site/main.go create mode 100644 cmd/site/rss.go delete mode 100644 gopreload.go delete mode 100644 gops.go delete mode 100644 hash.go delete mode 100644 html.go delete mode 100644 mage.go delete mode 100644 mage_helpers.go delete mode 100644 main.go delete mode 100644 rice-box.go delete mode 100644 rss.go create mode 100755 scripts/deploy.sh create mode 100755 scripts/setupremote.sh diff --git a/Dockerfile b/Dockerfile index 6380f81..1cacffa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,15 @@ -FROM xena/christine.website:1.1-47-g3228e3b +FROM xena/go:1.10 AS build +COPY . /root/go/src/github.com/Xe/site +RUN GOBIN=/root go build github.com/Xe/site + +FROM xena/alpine EXPOSE 5000 RUN apk add --no-cache bash +COPY --from=build /root/site /site/site +COPY ./templates /site/templates +COPY ./blog /site/blog +COPY ./run.sh /site.sh +COPY ./static /site/static + +HEALTHCHECK CMD curl --fail http://127.0.0.1:5000 || exit 1 CMD /site/run.sh diff --git a/box.rb b/box.rb deleted file mode 100644 index 625eed9..0000000 --- a/box.rb +++ /dev/null @@ -1,36 +0,0 @@ -from "xena/go:1.10" - -### Copy files -run "mkdir -p /site" - -def put(file) - copy "./#{file}", "/site/#{file}" -end - -files = [ - "gops.go", - "hash.go", - "html.go", - "main.go", - "rss.go", - "run.sh", - "templates", - "blog", - "rice-box.go" -] - -files.each { |x| put x } - -copy "vendor/", "/root/go/src/" - -### Build -run "cd /site && go build -v" - -### Cleanup -run %q[ rm -rf /root/go /site/backend /root/sdk /site/*.go ] -run %q[ rm -rf /usr/local/bin/go* ] - -cmd "/site/run.sh" - -flatten -tag "xena/christine.website" diff --git a/cmd/site/gopreload.go b/cmd/site/gopreload.go new file mode 100644 index 0000000..6829ae5 --- /dev/null +++ b/cmd/site/gopreload.go @@ -0,0 +1,9 @@ +// gopreload.go +package main + +/* + This file is separate to make it very easy to both add into an application, but + also very easy to remove. +*/ + +import _ "github.com/Xe/gopreload" diff --git a/cmd/site/gops.go b/cmd/site/gops.go new file mode 100644 index 0000000..184b656 --- /dev/null +++ b/cmd/site/gops.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/google/gops/agent" +) + +func init() { + if err := agent.Listen(nil); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/site/hash.go b/cmd/site/hash.go new file mode 100644 index 0000000..ed6112c --- /dev/null +++ b/cmd/site/hash.go @@ -0,0 +1,14 @@ +package main + +import ( + "crypto/md5" + "fmt" +) + +// Hash is a simple wrapper around the MD5 algorithm implementation in the +// Go standard library. It takes in data and a salt and returns the hashed +// representation. +func Hash(data string, salt string) string { + output := md5.Sum([]byte(data + salt)) + return fmt.Sprintf("%x", output) +} diff --git a/cmd/site/html.go b/cmd/site/html.go new file mode 100644 index 0000000..ba304c5 --- /dev/null +++ b/cmd/site/html.go @@ -0,0 +1,73 @@ +package main + +import ( + "context" + "fmt" + "html/template" + "net/http" + "time" + + "github.com/Xe/ln" +) + +func logTemplateTime(name string, from time.Time) { + now := time.Now() + ln.Log(context.Background(), ln.F{"action": "template_rendered", "dur": now.Sub(from).String(), "name": name}) +} + +func (s *Site) renderTemplatePage(templateFname string, data interface{}) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer logTemplateTime(templateFname, time.Now()) + s.tlock.RLock() + defer s.tlock.RUnlock() + + var t *template.Template + var err error + + if s.templates[templateFname] == nil { + t, err = template.ParseFiles("templates/base.html", "templates/"+templateFname) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + ln.Error(context.Background(), err, ln.F{"action": "renderTemplatePage", "page": templateFname}) + fmt.Fprintf(w, "error: %v", err) + } + + ln.Log(context.Background(), ln.F{"action": "loaded_new_template", "fname": templateFname}) + + s.tlock.RUnlock() + s.tlock.Lock() + s.templates[templateFname] = t + s.tlock.Unlock() + s.tlock.RLock() + } else { + t = s.templates[templateFname] + } + + err = t.Execute(w, data) + if err != nil { + panic(err) + } + }) +} + +func (s *Site) showPost(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/blog/" { + http.Redirect(w, r, "/blog", http.StatusSeeOther) + return + } + + var p *Post + for _, pst := range s.Posts { + if pst.Link == r.RequestURI[1:] { + p = pst + } + } + + if p == nil { + w.WriteHeader(http.StatusNotFound) + s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r) + return + } + + s.renderTemplatePage("blogpost.html", p).ServeHTTP(w, r) +} diff --git a/cmd/site/main.go b/cmd/site/main.go new file mode 100644 index 0000000..e0cc20d --- /dev/null +++ b/cmd/site/main.go @@ -0,0 +1,205 @@ +package main + +import ( + "context" + "html/template" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/GeertJohan/go.rice" + "github.com/Xe/jsonfeed" + "github.com/Xe/ln" + "github.com/gorilla/feeds" + blackfriday "github.com/russross/blackfriday" + "github.com/tj/front" +) + +var port = os.Getenv("PORT") + +func main() { + if port == "" { + port = "29384" + } + + s, err := Build() + if err != nil { + ln.FatalErr(context.Background(), err, ln.Action("Build")) + } + + ln.Log(context.Background(), ln.F{"action": "http_listening", "port": port}) + http.ListenAndServe(":"+port, s) +} + +// Site is the parent object for https://christine.website's backend. +type Site struct { + Posts Posts + Resume template.HTML + + rssFeed *feeds.Feed + jsonFeed *jsonfeed.Feed + + mux *http.ServeMux + + templates map[string]*template.Template + tlock sync.RWMutex +} + +func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ln.Log(r.Context(), ln.F{"action": "Site.ServeHTTP", "user_ip_address": r.RemoteAddr, "path": r.RequestURI}) + s.mux.ServeHTTP(w, r) +} + +// Build creates a new Site instance or fails. +func Build() (*Site, error) { + type postFM struct { + Title string + Date string + } + + s := &Site{ + rssFeed: &feeds.Feed{ + Title: "Christine Dodrill's Blog", + Link: &feeds.Link{Href: "https://christine.website/blog"}, + Description: "My blog posts and rants about various technology things.", + Author: &feeds.Author{Name: "Christine Dodrill", Email: "me@christine.website"}, + Created: bootTime, + Copyright: "This work is copyright Christine Dodrill. My viewpoints are my own and not the view of any employer past, current or future.", + }, + jsonFeed: &jsonfeed.Feed{ + Version: jsonfeed.CurrentVersion, + Title: "Christine Dodrill's Blog", + HomePageURL: "https://christine.website", + FeedURL: "https://christine.website/blog.json", + Description: "My blog posts and rants about various technology things.", + UserComment: "This is a JSON feed of my blogposts. For more information read: https://jsonfeed.org/version/1", + Icon: icon, + Favicon: icon, + Author: jsonfeed.Author{ + Name: "Christine Dodrill", + Avatar: icon, + }, + }, + mux: http.NewServeMux(), + templates: map[string]*template.Template{}, + } + + err := filepath.Walk("./blog/", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + fin, err := os.Open(path) + if err != nil { + return err + } + defer fin.Close() + + content, err := ioutil.ReadAll(fin) + if err != nil { + return err + } + + var fm postFM + remaining, err := front.Unmarshal(content, &fm) + if err != nil { + return err + } + + output := blackfriday.Run(remaining) + + p := &Post{ + Title: fm.Title, + Date: fm.Date, + Link: strings.Split(path, ".")[0], + Body: string(remaining), + BodyHTML: template.HTML(output), + } + + s.Posts = append(s.Posts, p) + + return nil + }) + if err != nil { + return nil, err + } + + sort.Sort(sort.Reverse(s.Posts)) + + cb, err := rice.FindBox("css") + if err != nil { + return nil, err + } + + sb, err := rice.FindBox("static") + if err != nil { + return nil, err + } + + s.Resume = template.HTML(blackfriday.Run(sb.MustBytes("resume/resume.md"))) + + for _, item := range s.Posts { + itime, _ := time.Parse("2006-01-02", item.Date) + s.rssFeed.Items = append(s.rssFeed.Items, &feeds.Item{ + Title: item.Title, + Link: &feeds.Link{Href: "https://christine.website/" + item.Link}, + Description: item.Summary, + Created: itime, + }) + + s.jsonFeed.Items = append(s.jsonFeed.Items, jsonfeed.Item{ + ID: "https://christine.website/" + item.Link, + URL: "https://christine.website/" + item.Link, + Title: item.Title, + DatePublished: itime, + ContentHTML: string(item.BodyHTML), + }) + } + + // Add HTTP routes here + s.mux.Handle("/", s.renderTemplatePage("index.html", nil)) + s.mux.Handle("/resume", s.renderTemplatePage("resume.html", s.Resume)) + s.mux.Handle("/blog", s.renderTemplatePage("blogindex.html", s.Posts)) + s.mux.Handle("/contact", s.renderTemplatePage("contact.html", nil)) + s.mux.HandleFunc("/blog.rss", s.createFeed) + s.mux.HandleFunc("/blog.atom", s.createAtom) + s.mux.HandleFunc("/blog.json", s.createJsonFeed) + s.mux.HandleFunc("/blog/", s.showPost) + s.mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(sb.HTTPBox()))) + s.mux.Handle("/css/", http.StripPrefix("/css/", http.FileServer(cb.HTTPBox()))) + + return s, nil +} + +const icon = "https://christine.website/static/img/avatar.png" + +// Post is a single blogpost. +type Post struct { + Title string `json:"title"` + Link string `json:"link"` + Summary string `json:"summary,omitifempty"` + Body string `json:"-"` + BodyHTML template.HTML `json:"body"` + Date string `json:"date"` +} + +// Posts implements sort.Interface for a slice of Post objects. +type Posts []*Post + +func (p Posts) Len() int { return len(p) } +func (p Posts) Less(i, j int) bool { + iDate, _ := time.Parse("2006-01-02", p[i].Date) + jDate, _ := time.Parse("2006-01-02", p[j].Date) + + return iDate.Unix() < jDate.Unix() +} +func (p Posts) Swap(i, j int) { p[i], p[j] = p[j], p[i] } diff --git a/cmd/site/rss.go b/cmd/site/rss.go new file mode 100644 index 0000000..15e9163 --- /dev/null +++ b/cmd/site/rss.go @@ -0,0 +1,64 @@ +package main + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/Xe/ln" +) + +var bootTime = time.Now() + +// IncrediblySecureSalt ******* +const IncrediblySecureSalt = "hunter2" + +func (s *Site) createFeed(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/rss+xml") + w.Header().Set("ETag", Hash(bootTime.String(), IncrediblySecureSalt)) + + err := s.rssFeed.WriteRss(w) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + ln.Error(r.Context(), err, ln.F{ + "remote_addr": r.RemoteAddr, + "action": "generating_rss", + "uri": r.RequestURI, + "host": r.Host, + }) + } +} + +func (s *Site) createAtom(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/atom+xml") + w.Header().Set("ETag", Hash(bootTime.String(), IncrediblySecureSalt)) + + err := s.rssFeed.WriteAtom(w) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + ln.Error(r.Context(), err, ln.F{ + "remote_addr": r.RemoteAddr, + "action": "generating_atom", + "uri": r.RequestURI, + "host": r.Host, + }) + } +} + +func (s *Site) createJsonFeed(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("ETag", Hash(bootTime.String(), IncrediblySecureSalt)) + + e := json.NewEncoder(w) + e.SetIndent("", "\t") + err := e.Encode(s.jsonFeed) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + ln.Error(r.Context(), err, ln.F{ + "remote_addr": r.RemoteAddr, + "action": "generating_jsonfeed", + "uri": r.RequestURI, + "host": r.Host, + }) + } +} diff --git a/gopreload.go b/gopreload.go deleted file mode 100644 index 6829ae5..0000000 --- a/gopreload.go +++ /dev/null @@ -1,9 +0,0 @@ -// gopreload.go -package main - -/* - This file is separate to make it very easy to both add into an application, but - also very easy to remove. -*/ - -import _ "github.com/Xe/gopreload" diff --git a/gops.go b/gops.go deleted file mode 100644 index 184b656..0000000 --- a/gops.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "log" - - "github.com/google/gops/agent" -) - -func init() { - if err := agent.Listen(nil); err != nil { - log.Fatal(err) - } -} diff --git a/hash.go b/hash.go deleted file mode 100644 index ed6112c..0000000 --- a/hash.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "crypto/md5" - "fmt" -) - -// Hash is a simple wrapper around the MD5 algorithm implementation in the -// Go standard library. It takes in data and a salt and returns the hashed -// representation. -func Hash(data string, salt string) string { - output := md5.Sum([]byte(data + salt)) - return fmt.Sprintf("%x", output) -} diff --git a/html.go b/html.go deleted file mode 100644 index ba304c5..0000000 --- a/html.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "context" - "fmt" - "html/template" - "net/http" - "time" - - "github.com/Xe/ln" -) - -func logTemplateTime(name string, from time.Time) { - now := time.Now() - ln.Log(context.Background(), ln.F{"action": "template_rendered", "dur": now.Sub(from).String(), "name": name}) -} - -func (s *Site) renderTemplatePage(templateFname string, data interface{}) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer logTemplateTime(templateFname, time.Now()) - s.tlock.RLock() - defer s.tlock.RUnlock() - - var t *template.Template - var err error - - if s.templates[templateFname] == nil { - t, err = template.ParseFiles("templates/base.html", "templates/"+templateFname) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - ln.Error(context.Background(), err, ln.F{"action": "renderTemplatePage", "page": templateFname}) - fmt.Fprintf(w, "error: %v", err) - } - - ln.Log(context.Background(), ln.F{"action": "loaded_new_template", "fname": templateFname}) - - s.tlock.RUnlock() - s.tlock.Lock() - s.templates[templateFname] = t - s.tlock.Unlock() - s.tlock.RLock() - } else { - t = s.templates[templateFname] - } - - err = t.Execute(w, data) - if err != nil { - panic(err) - } - }) -} - -func (s *Site) showPost(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == "/blog/" { - http.Redirect(w, r, "/blog", http.StatusSeeOther) - return - } - - var p *Post - for _, pst := range s.Posts { - if pst.Link == r.RequestURI[1:] { - p = pst - } - } - - if p == nil { - w.WriteHeader(http.StatusNotFound) - s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r) - return - } - - s.renderTemplatePage("blogpost.html", p).ServeHTTP(w, r) -} diff --git a/mage.go b/mage.go deleted file mode 100644 index ee31edd..0000000 --- a/mage.go +++ /dev/null @@ -1,80 +0,0 @@ -// +build mage - -package main - -import ( - "context" - "fmt" - "os" - - "github.com/magefile/mage/mg" -) - -func do(cmd string, args ...string) { - shouldWork(context.Background(), nil, wd, cmd, args...) -} - -// Setup installs the tools that other parts of the build process depend on. -func Setup(ctx context.Context) { - // go tools - do("go", "get", "-u", "-v", "github.com/GeertJohan/go.rice/rice") - - do("git", "remote", "add", "dokku", "dokku@minipaas.xeserv.us") -} - -// Generate runs all of the code generation. -func Generate(ctx context.Context) { - shouldWork(ctx, nil, wd, "rice", "embed-go") -} - -// Docker creates the docker image xena/christine.website using box(1). -func Docker() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - mg.Deps(Generate) - - shouldWork(ctx, nil, wd, "box", "box.rb") -} - -// Deploy does the work needed to deploy this image to the dokku server. -func Deploy(ctx context.Context) error { - mg.Deps(Docker) - - tag, err := gitTag() - if err != nil { - return err - } - - do("docker", "tag", "xena/christine.website", "xena/christine.website:"+tag) - do("docker", "push", "xena/christine.website:"+tag) - - const dockerfileTemplate = `FROM xena/christine.website:${VERSION} -EXPOSE 5000 -RUN apk add --no-cache bash -CMD /site/run.sh` - data := os.Expand(dockerfileTemplate, func(inp string) string { - switch inp { - case "VERSION": - return tag - default: - return "" - } - }) - - os.Remove("Dockerfile") - fout, err := os.Create("Dockerfile") - if err != nil { - return err - } - - fmt.Fprintln(fout, data) - fout.Close() - - do("git", "add", "Dockerfile") - do("git", "commit", "-m", "Dockerfile: update for deployment of version "+tag) - do("git", "push", "dokku", "master") - do("git", "push") - - return nil -} diff --git a/mage_helpers.go b/mage_helpers.go deleted file mode 100644 index 08c4526..0000000 --- a/mage_helpers.go +++ /dev/null @@ -1,75 +0,0 @@ -// +build mage - -package main - -import ( - "context" - "log" - "os" - "os/exec" - "strings" - - "github.com/jtolds/qod" - "github.com/pkg/errors" -) - -var wd string - -func init() { - lwd, err := os.Getwd() - qod.ANE(err) - - wd = lwd -} - -// must end in a slash -const pkgBase = "github.com/Xe/site/" - -func output(cmd string, args ...string) (string, error) { - c := exec.Command(cmd, args...) - c.Env = os.Environ() - c.Stderr = os.Stderr - b, err := c.Output() - if err != nil { - return "", errors.Wrapf(err, `failed to run %v %q`, cmd, args) - } - return string(b), nil -} - -func gitTag() (string, error) { - s, err := output("git", "describe", "--tags") - if err != nil { - ee, ok := errors.Cause(err).(*exec.ExitError) - if ok && ee.Exited() { - // probably no git tag - return "dev", nil - } - return "", err - } - - return strings.TrimSuffix(s, "\n"), nil -} - -func shouldWork(ctx context.Context, env []string, dir string, cmdName string, args ...string) { - loc, err := exec.LookPath(cmdName) - qod.ANE(err) - - cmd := exec.CommandContext(ctx, loc, args...) - cmd.Dir = dir - cmd.Env = append(env, os.Environ()...) - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - log.Printf("starting process, env: %v, pwd: %s, cmd: %s, args: %v", env, dir, loc, args) - err = cmd.Run() - qod.ANE(err) -} - -func goBuild(ctx context.Context, env []string, dir string, pkgname string) { - shouldWork(ctx, env, dir, "go", "build", "-v", pkgBase+pkgname) -} - -func goInstall(ctx context.Context, env []string, pkgname string) { - shouldWork(ctx, nil, wd, "go", "install", pkgBase+pkgname) -} diff --git a/main.go b/main.go deleted file mode 100644 index e0cc20d..0000000 --- a/main.go +++ /dev/null @@ -1,205 +0,0 @@ -package main - -import ( - "context" - "html/template" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "sort" - "strings" - "sync" - "time" - - "github.com/GeertJohan/go.rice" - "github.com/Xe/jsonfeed" - "github.com/Xe/ln" - "github.com/gorilla/feeds" - blackfriday "github.com/russross/blackfriday" - "github.com/tj/front" -) - -var port = os.Getenv("PORT") - -func main() { - if port == "" { - port = "29384" - } - - s, err := Build() - if err != nil { - ln.FatalErr(context.Background(), err, ln.Action("Build")) - } - - ln.Log(context.Background(), ln.F{"action": "http_listening", "port": port}) - http.ListenAndServe(":"+port, s) -} - -// Site is the parent object for https://christine.website's backend. -type Site struct { - Posts Posts - Resume template.HTML - - rssFeed *feeds.Feed - jsonFeed *jsonfeed.Feed - - mux *http.ServeMux - - templates map[string]*template.Template - tlock sync.RWMutex -} - -func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ln.Log(r.Context(), ln.F{"action": "Site.ServeHTTP", "user_ip_address": r.RemoteAddr, "path": r.RequestURI}) - s.mux.ServeHTTP(w, r) -} - -// Build creates a new Site instance or fails. -func Build() (*Site, error) { - type postFM struct { - Title string - Date string - } - - s := &Site{ - rssFeed: &feeds.Feed{ - Title: "Christine Dodrill's Blog", - Link: &feeds.Link{Href: "https://christine.website/blog"}, - Description: "My blog posts and rants about various technology things.", - Author: &feeds.Author{Name: "Christine Dodrill", Email: "me@christine.website"}, - Created: bootTime, - Copyright: "This work is copyright Christine Dodrill. My viewpoints are my own and not the view of any employer past, current or future.", - }, - jsonFeed: &jsonfeed.Feed{ - Version: jsonfeed.CurrentVersion, - Title: "Christine Dodrill's Blog", - HomePageURL: "https://christine.website", - FeedURL: "https://christine.website/blog.json", - Description: "My blog posts and rants about various technology things.", - UserComment: "This is a JSON feed of my blogposts. For more information read: https://jsonfeed.org/version/1", - Icon: icon, - Favicon: icon, - Author: jsonfeed.Author{ - Name: "Christine Dodrill", - Avatar: icon, - }, - }, - mux: http.NewServeMux(), - templates: map[string]*template.Template{}, - } - - err := filepath.Walk("./blog/", func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() { - return nil - } - - fin, err := os.Open(path) - if err != nil { - return err - } - defer fin.Close() - - content, err := ioutil.ReadAll(fin) - if err != nil { - return err - } - - var fm postFM - remaining, err := front.Unmarshal(content, &fm) - if err != nil { - return err - } - - output := blackfriday.Run(remaining) - - p := &Post{ - Title: fm.Title, - Date: fm.Date, - Link: strings.Split(path, ".")[0], - Body: string(remaining), - BodyHTML: template.HTML(output), - } - - s.Posts = append(s.Posts, p) - - return nil - }) - if err != nil { - return nil, err - } - - sort.Sort(sort.Reverse(s.Posts)) - - cb, err := rice.FindBox("css") - if err != nil { - return nil, err - } - - sb, err := rice.FindBox("static") - if err != nil { - return nil, err - } - - s.Resume = template.HTML(blackfriday.Run(sb.MustBytes("resume/resume.md"))) - - for _, item := range s.Posts { - itime, _ := time.Parse("2006-01-02", item.Date) - s.rssFeed.Items = append(s.rssFeed.Items, &feeds.Item{ - Title: item.Title, - Link: &feeds.Link{Href: "https://christine.website/" + item.Link}, - Description: item.Summary, - Created: itime, - }) - - s.jsonFeed.Items = append(s.jsonFeed.Items, jsonfeed.Item{ - ID: "https://christine.website/" + item.Link, - URL: "https://christine.website/" + item.Link, - Title: item.Title, - DatePublished: itime, - ContentHTML: string(item.BodyHTML), - }) - } - - // Add HTTP routes here - s.mux.Handle("/", s.renderTemplatePage("index.html", nil)) - s.mux.Handle("/resume", s.renderTemplatePage("resume.html", s.Resume)) - s.mux.Handle("/blog", s.renderTemplatePage("blogindex.html", s.Posts)) - s.mux.Handle("/contact", s.renderTemplatePage("contact.html", nil)) - s.mux.HandleFunc("/blog.rss", s.createFeed) - s.mux.HandleFunc("/blog.atom", s.createAtom) - s.mux.HandleFunc("/blog.json", s.createJsonFeed) - s.mux.HandleFunc("/blog/", s.showPost) - s.mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(sb.HTTPBox()))) - s.mux.Handle("/css/", http.StripPrefix("/css/", http.FileServer(cb.HTTPBox()))) - - return s, nil -} - -const icon = "https://christine.website/static/img/avatar.png" - -// Post is a single blogpost. -type Post struct { - Title string `json:"title"` - Link string `json:"link"` - Summary string `json:"summary,omitifempty"` - Body string `json:"-"` - BodyHTML template.HTML `json:"body"` - Date string `json:"date"` -} - -// Posts implements sort.Interface for a slice of Post objects. -type Posts []*Post - -func (p Posts) Len() int { return len(p) } -func (p Posts) Less(i, j int) bool { - iDate, _ := time.Parse("2006-01-02", p[i].Date) - jDate, _ := time.Parse("2006-01-02", p[j].Date) - - return iDate.Unix() < jDate.Unix() -} -func (p Posts) Swap(i, j int) { p[i], p[j] = p[j], p[i] } diff --git a/rice-box.go b/rice-box.go deleted file mode 100644 index 3be82fc..0000000 --- a/rice-box.go +++ /dev/null @@ -1,365 +0,0 @@ -package main - -import ( - "github.com/GeertJohan/go.rice/embedded" - "time" -) - -func init() { - - // define files - file2 := &embedded.EmbeddedFile{ - Filename: "hack.css", - FileModTime: time.Unix(1517162200, 0), - Content: string("html{font-size:12px}*{box-sizing:border-box;text-rendering:geometricPrecision}body{font-size:1rem;line-height:1.5rem;margin:0;font-family:Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif;word-wrap:break-word}h1,h2,h3,h4,h5,h6{line-height:1.3em}fieldset{border:none;padding:0;margin:0}pre{padding:2rem;margin:1.75rem 0;background-color:#fff;border:1px solid #ccc;overflow:auto}code[class*=language-],pre[class*=language-],pre code{font-weight:100;text-shadow:none;margin:1.75rem 0}a{cursor:pointer;color:#ff2e88;text-decoration:none;border-bottom:1px solid #ff2e88}a:hover{background-color:#ff2e88;color:#fff}.grid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.grid.\\-top{-ms-flex-align:start;align-items:flex-start}.grid.\\-middle{-ms-flex-align:center;align-items:center}.grid.\\-bottom{-ms-flex-align:end;align-items:flex-end}.grid.\\-stretch{-ms-flex-align:stretch;align-items:stretch}.grid.\\-baseline{-ms-flex-align:baseline;align-items:baseline}.grid.\\-left{-ms-flex-pack:start;justify-content:flex-start}.grid.\\-center{-ms-flex-pack:center;justify-content:center}.grid.\\-right{-ms-flex-pack:end;justify-content:flex-end}.grid.\\-between{-ms-flex-pack:justify;justify-content:space-between}.grid.\\-around{-ms-flex-pack:distribute;justify-content:space-around}.cell{-ms-flex:1;flex:1;box-sizing:border-box}@media screen and (min-width:768px){.cell.\\-1of12{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%}.cell.\\-2of12{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%}.cell.\\-3of12{-ms-flex:0 0 25%;flex:0 0 25%}.cell.\\-4of12{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%}.cell.\\-5of12{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%}.cell.\\-6of12{-ms-flex:0 0 50%;flex:0 0 50%}.cell.\\-7of12{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%}.cell.\\-8of12{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%}.cell.\\-9of12{-ms-flex:0 0 75%;flex:0 0 75%}.cell.\\-10of12{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%}.cell.\\-11of12{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%}}@media screen and (max-width:768px){.grid{-ms-flex-direction:column;flex-direction:column}.cell{-ms-flex:0 0 auto;flex:0 0 auto}}.hack,.hack blockquote,.hack code,.hack em,.hack h1,.hack h2,.hack h3,.hack h4,.hack h5,.hack h6,.hack strong{font-size:1rem;font-style:normal;font-family:Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif}.hack blockquote,.hack code,.hack em,.hack strong{line-height:20px}.hack blockquote,.hack code,.hack footer,.hack h1,.hack h2,.hack h3,.hack h4,.hack h5,.hack h6,.hack header,.hack li,.hack ol,.hack p,.hack section,.hack ul{float:none;margin:0;padding:0}.hack blockquote,.hack h1,.hack ol,.hack p,.hack ul{margin-top:20px;margin-bottom:20px}.hack h1{position:relative;display:inline-block;display:table-cell;padding:20px 0 30px;margin:0;overflow:hidden}.hack h1:after{content:\"====================================================================================================\";position:absolute;bottom:10px;left:0}.hack h1+*{margin-top:0}.hack h2,.hack h3,.hack h4,.hack h5,.hack h6{position:relative;margin-bottom:1.75rem}.hack h2:before,.hack h3:before,.hack h4:before,.hack h5:before,.hack h6:before{display:inline}.hack h2:before{content:\"## \"}.hack h3:before{content:\"### \"}.hack h4:before{content:\"#### \"}.hack h5:before{content:\"##### \"}.hack h6:before{content:\"###### \"}.hack li{position:relative;display:block;padding-left:20px}.hack li:after{position:absolute;top:0;left:0}.hack ul>li:after{content:\"-\"}.hack ol{counter-reset:a}.hack ol>li:after{content:counter(a) \".\";counter-increment:a}.hack blockquote{position:relative;padding-left:17px;padding-left:2ch;overflow:hidden}.hack blockquote:after{content:\">\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\\A>\";white-space:pre;position:absolute;top:0;left:0;line-height:20px}.hack em:after,.hack em:before{content:\"*\";display:inline}.hack pre code:after,.hack pre code:before{content:\"\"}.hack code{font-weight:700}.hack code:after,.hack code:before{content:\"`\";display:inline}.hack hr{position:relative;height:20px;overflow:hidden;border:0;margin:20px 0}.hack hr:after{content:\"----------------------------------------------------------------------------------------------------\";position:absolute;top:0;left:0;line-height:20px;width:100%;word-wrap:break-word}@-moz-document url-prefix(){.hack h1{display:block}}.hack-ones ol>li:after{content:\"1.\"}p{margin:0 0 1.75rem}.container{max-width:70rem}.container,.container-fluid{margin:0 auto;padding:0 1rem}.inner{padding:1rem}.inner2x{padding:2rem}.pull-left{float:left}.pull-right{float:right}.progress-bar{height:8px;opacity:.8;background-color:#ccc;margin-top:12px}.progress-bar.progress-bar-show-percent{margin-top:38px}.progress-bar-filled{background-color:gray;height:100%;transition:width .3s ease;position:relative;width:0}.progress-bar-filled:before{content:\"\";border:6px solid transparent;border-top-color:gray;position:absolute;top:-12px;right:-6px}.progress-bar-filled:after{color:gray;content:attr(data-filled);display:block;font-size:12px;white-space:nowrap;position:absolute;border:6px solid transparent;top:-38px;right:0;-ms-transform:translateX(50%);transform:translateX(50%)}table{width:100%;border-collapse:collapse;margin:1.75rem 0;color:#778087}table td,table th{vertical-align:top;border:1px solid #ccc;line-height:15px;padding:10px}table thead th{font-size:10px}table tbody td:first-child{font-weight:700;color:#333}.form{width:30rem}.form-group{margin-bottom:1.75rem;overflow:auto}.form-group label{border-bottom:2px solid #ccc;color:#333;width:10rem;display:inline-block;height:38px;line-height:38px;padding:0;float:left;position:relative}.form-group.form-success label{color:#4caf50!important;border-color:#4caf50!important}.form-group.form-warning label{color:#ff9800!important;border-color:#ff9800!important}.form-group.form-error label{color:#f44336!important;border-color:#f44336!important}.form-control{outline:none;border:none;border-bottom:2px solid #ccc;padding:.5rem 0;width:20rem;height:38px;background-color:transparent}.form-control:focus{border-color:#555}.form-group.form-textarea label:after{position:absolute;content:\"\";width:2px;background-color:#fff;right:-2px;top:0;bottom:0}textarea.form-control{height:auto;resize:none;padding:1rem 0;border-bottom:2px solid #ccc;border-left:2px solid #ccc;padding:.5rem}select.form-control{border-radius:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none}.help-block{color:#999;margin-top:.5rem}.form-actions{margin-bottom:1.75rem}.btn{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;cursor:pointer;outline:none;padding:.65rem 2rem;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;z-index:1}.btn:active{box-shadow:inset 0 1px 3px rgba(0,0,0,.12)}.btn.btn-ghost{border-color:#757575;color:#757575;background-color:transparent}.btn.btn-ghost:focus,.btn.btn-ghost:hover{border-color:#424242;color:#424242;z-index:2}.btn.btn-ghost:hover{background-color:transparent}.btn-block{width:100%;display:-ms-flexbox;display:flex}.btn-default{color:#fff;background-color:#e0e0e0;border:1px solid #e0e0e0;color:#333}.btn-default:focus:not(.btn-ghost),.btn-default:hover{background-color:#dcdcdc;border-color:#dcdcdc}.btn-success{color:#fff;background-color:#4caf50;border:1px solid #4caf50}.btn-success:focus:not(.btn-ghost),.btn-success:hover{background-color:#43a047;border-color:#43a047}.btn-success.btn-ghost{border-color:#4caf50;color:#4caf50}.btn-success.btn-ghost:focus,.btn-success.btn-ghost:hover{border-color:#388e3c;color:#388e3c;z-index:2}.btn-error{color:#fff;background-color:#f44336;border:1px solid #f44336}.btn-error:focus:not(.btn-ghost),.btn-error:hover{background-color:#e53935;border-color:#e53935}.btn-error.btn-ghost{border-color:#f44336;color:#f44336}.btn-error.btn-ghost:focus,.btn-error.btn-ghost:hover{border-color:#d32f2f;color:#d32f2f;z-index:2}.btn-warning{color:#fff;background-color:#ff9800;border:1px solid #ff9800}.btn-warning:focus:not(.btn-ghost),.btn-warning:hover{background-color:#fb8c00;border-color:#fb8c00}.btn-warning.btn-ghost{border-color:#ff9800;color:#ff9800}.btn-warning.btn-ghost:focus,.btn-warning.btn-ghost:hover{border-color:#f57c00;color:#f57c00;z-index:2}.btn-info{color:#fff;background-color:#00bcd4;border:1px solid #00bcd4}.btn-info:focus:not(.btn-ghost),.btn-info:hover{background-color:#00acc1;border-color:#00acc1}.btn-info.btn-ghost{border-color:#00bcd4;color:#00bcd4}.btn-info.btn-ghost:focus,.btn-info.btn-ghost:hover{border-color:#0097a7;color:#0097a7;z-index:2}.btn-primary{color:#fff;background-color:#2196f3;border:1px solid #2196f3}.btn-primary:focus:not(.btn-ghost),.btn-primary:hover{background-color:#1e88e5;border-color:#1e88e5}.btn-primary.btn-ghost{border-color:#2196f3;color:#2196f3}.btn-primary.btn-ghost:focus,.btn-primary.btn-ghost:hover{border-color:#1976d2;color:#1976d2;z-index:2}.btn-group{overflow:auto}.btn-group .btn{float:left}.btn-group .btn-ghost:not(:first-child){margin-left:-1px}.card{border:1px solid #ccc}.card .card-header{color:#333;text-align:center;background-color:#ddd;padding:.5rem 0}.alert{color:#ccc;padding:1rem;border:1px solid #ccc;margin-bottom:1.75rem}.alert-success{color:#4caf50;border-color:#4caf50}.alert-error{color:#f44336;border-color:#f44336}.alert-info{color:#00bcd4;border-color:#00bcd4}.alert-warning{color:#ff9800;border-color:#ff9800}.media:not(:last-child){margin-bottom:1.25rem}.media-left{padding-right:1rem}.media-left,.media-right{display:table-cell;vertical-align:top}.media-right{padding-left:1rem}.media-body{display:table-cell;vertical-align:top}.media-heading{font-size:1.16667rem;font-weight:700}.media-content{margin-top:.3rem}.avatarholder,.placeholder{background-color:#f0f0f0;text-align:center;color:#b9b9b9;font-size:1rem;border:1px solid #f0f0f0}.avatarholder{width:48px;height:48px;line-height:46px;font-size:2rem;background-size:cover;background-position:50%;background-repeat:no-repeat}.avatarholder.rounded{border-radius:33px}.loading{display:inline-block;content:\" \";height:20px;width:20px;margin:0 .5rem;animation:a .6s infinite linear;border:2px solid #e91e63;border-right-color:transparent;border-radius:50%}.btn .loading{margin-bottom:0;width:14px;height:14px}.btn div.loading{float:left}.alert .loading{margin-bottom:-5px}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.menu{width:100%}.menu .menu-item{display:block;color:#616161;border-color:#616161}.menu .menu-item.active,.menu .menu-item:hover{color:#000;border-color:#000;background-color:transparent}@media screen and (max-width:768px){.form-group label{display:block;border-bottom:none;width:100%}.form-group.form-textarea label:after{display:none}.form-control{width:100%}textarea.form-control{border-left:none;padding:.5rem 0}pre::-webkit-scrollbar{height:3px}}@media screen and (max-width:480px){.form{width:100%}}"), - } - file3 := &embedded.EmbeddedFile{ - Filename: "solarized-dark.css", - FileModTime: time.Unix(1517162200, 0), - Content: string(".solarized-dark{background-color:#073642;color:#78909c}.solarized-dark h1,.solarized-dark h2,.solarized-dark h3,.solarized-dark h4,.solarized-dark h5,.solarized-dark h6{color:#1e88e5}.solarized-dark h1 a,.solarized-dark h2 a,.solarized-dark h3 a,.solarized-dark h4 a,.solarized-dark h5 a,.solarized-dark h6 a{color:#1e88e5;border-bottom-color:#1e88e5}.solarized-dark h1 a:hover,.solarized-dark h2 a:hover,.solarized-dark h3 a:hover,.solarized-dark h4 a:hover,.solarized-dark h5 a:hover,.solarized-dark h6 a:hover{background-color:#1e88e5;color:#fff}.solarized-dark pre{background-color:#073642;padding:0;border:none}.solarized-dark pre code{color:#009688}.solarized-dark h1 a,.solarized-dark h2 a,.solarized-dark h3 a,.solarized-dark h4 a,.solarized-dark h5 a{color:#78909c}.solarized-dark code,.solarized-dark strong{color:#90a4ae}.solarized-dark code{font-weight:100}.solarized-dark .progress-bar-filled{background-color:#558b2f}.solarized-dark .progress-bar-filled:after,.solarized-dark .progress-bar-filled:before{color:#90a4ae}.solarized-dark table{color:#78909c}.solarized-dark table td,.solarized-dark table th{border-color:#b0bec5}.solarized-dark table tbody td:first-child{color:#b0bec5}.solarized-dark .form-group label{color:#78909c;border-color:#90a4ae}.solarized-dark .form-group.form-textarea label:after{background-color:#073642}.solarized-dark .form-control{color:#78909c;border-color:#90a4ae}.solarized-dark .form-control:focus{border-color:#cfd8dc;color:#cfd8dc}.solarized-dark textarea.form-control{color:#78909c}.solarized-dark .card{border-color:#90a4ae}.solarized-dark .card .card-header{background-color:transparent;color:#78909c;border-bottom:1px solid #90a4ae}.solarized-dark .btn.btn-ghost.btn-default{border-color:#607d8b;color:#607d8b}.solarized-dark .btn.btn-ghost.btn-default:focus,.solarized-dark .btn.btn-ghost.btn-default:hover{border-color:#90a4ae;color:#90a4ae;z-index:1}.solarized-dark .btn.btn-ghost.btn-default:focus,.solarized-dark .btn.btn-ghost.btn-default:hover{border-color:#e0e0e0;color:#e0e0e0}.solarized-dark .btn.btn-ghost.btn-primary:focus,.solarized-dark .btn.btn-ghost.btn-primary:hover{border-color:#64b5f6;color:#64b5f6}.solarized-dark .btn.btn-ghost.btn-success:focus,.solarized-dark .btn.btn-ghost.btn-success:hover{border-color:#81c784;color:#81c784}.solarized-dark .btn.btn-ghost.btn-info:focus,.solarized-dark .btn.btn-ghost.btn-info:hover{border-color:#4dd0e1;color:#4dd0e1}.solarized-dark .btn.btn-ghost.btn-error:focus,.solarized-dark .btn.btn-ghost.btn-error:hover{border-color:#e57373;color:#e57373}.solarized-dark .btn.btn-ghost.btn-warning:focus,.solarized-dark .btn.btn-ghost.btn-warning:hover{border-color:#ffb74d;color:#ffb74d}.solarized-dark .avatarholder,.solarized-dark .placeholder{background-color:transparent;border-color:#90a4ae}.solarized-dark .menu .menu-item{color:#78909c;border-color:#90a4ae}.solarized-dark .menu .menu-item.active,.solarized-dark .menu .menu-item:hover{color:#fff;border-color:#78909c}"), - } - - // define dirs - dir1 := &embedded.EmbeddedDir{ - Filename: "", - DirModTime: time.Unix(1517162200, 0), - ChildFiles: []*embedded.EmbeddedFile{ - file2, // "hack.css" - file3, // "solarized-dark.css" - - }, - } - - // link ChildDirs - dir1.ChildDirs = []*embedded.EmbeddedDir{} - - // register embeddedBox - embedded.RegisterEmbeddedBox(`css`, &embedded.EmbeddedBox{ - Name: `css`, - Time: time.Unix(1517162200, 0), - Dirs: map[string]*embedded.EmbeddedDir{ - "": dir1, - }, - Files: map[string]*embedded.EmbeddedFile{ - "hack.css": file2, - "solarized-dark.css": file3, - }, - }) -} - -func init() { - - // define files - file6 := &embedded.EmbeddedFile{ - Filename: "favicon/android-icon-144x144.png", - FileModTime: time.Unix(1517162200, 0), - Content: string("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x90\x00\x00\x00\x90\b\x06\x00\x00\x00\xe7F\xe2\xb8\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\v\xfca\x05\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\tpHYs\x00\x00n\xba\x00\x00n\xba\x01\xd6ޱ\x17\x00\x004\rIDATx\xda\xed\x9dy|\\G\x95\xef\xbfuoo\xdaWK\xb2-\xefv\xbc/\xb1\x1d'\x8e\xe3x\xc9\x06I\x9c\x05\xb2\x91\x84!\x84\xc7d\x1e\x03\f\f\xcc0\xf3f\x06\x06>\xccJ\x12\xde\xc0\xc0\x9b\x85\x00\x81\f!\v\xb1\x9d\xe0,\x10'!vb'\xde\xe4}\xdf$Y\xb6dYR\xab\xd5{ߪ\xf7G\xf5\x95Z\xb2\x96^%H\xfc\xfb|\xfac\xab\xfbֽu\xeb\xfe\xee9\xa7\xce9uJ0\xc2X8e\x01\xc0x\xe0G\xc0\r\xc01\xe0\xc1\x9d\xc7\xeb\xde\x1b\xe9\xbe}P\x11\x1f\xf3\xc9\xc0\xf7\x81\x9b\xed\xef\xa5%\x99s\xf9\\\xfe\xe9\xff\xfe3\xb5\xe3k\x01xꉟ\xf3\xef\x8f~\x9fH$\x82\x10b\x0f\xf0\xc0\xce\xe3u\xfb\xec6\xc6H\xdf\f \x80\xab\x80\xcb\xe3\u007f\xe7\x01%\U0005bf04,#>\xaee\xc0\xff\x06\x96\xf7\xfaQ\x80\xdfׅ\xd7\xeb\x05 \x1a\x89\xd0v\xa1\x8dX,f\x1fq\x008\x9b\xd8\xe4\xf7\x81@\xc5\xc0\x1d@e\xfc\xef\x02\xa0z\xa4;\xf5AD\x9c\x0e8\f\xc3`Ս\xaby\xe4\v\x8f\xe0\xc9ϣ\xa0\xb0\x80\x1f<\xf6\xef\x84B\x83Z\v\x1dh\x15v\x11Fr\x16\xb6H)uuYE9\xff\xfb˟\xe3ۏ\xfd\x03\xabnX\x8d0\x04h\x02\x95]\x9aʧ\x8f\xf8ؙ\xc0]\xc0\x17\x80R\xa5\x14\x9e<\x0fW.\xbb\x92\xa2\x92b\x9cN'W\\\xbd\x84\xf2Q\x15\xfd\xaa\xb2\x04\x1c\x01\x8e\xf6\xb5\u007f`\xe4T\x98\x1b\xb8\x01E\xf5\x95W_ɚ\x8f\xddFAa\x01\x86!سk7\xe7\x9a\xce\x15\v!*3\xbeʇ\x1b\x02X\x04|\x9e\x04\xb7\x88i\x9ax\xf2\xf2\xba\x0fr\xbb\xdd8\x9d\xce\xc1Σ\x80\xedh)t\x11\x86]\x02\xc5ߌj\xe0*a\b1z\xech\xdcn7\x00\xe3'M`\xcc\xd81(\xa5\xf2\xd131\x91\xf6\x85>\xa4X8e\x81=Ɨ\x01\u007f\v̳\u007f\xd3\xd3\xf4\x10'\x8e\x9e\xc0\xb2,\x00\xeaO\xd5\xd3\xd1\xde1\xd8);\x81m@\xa4\xbf\x1fGJ\x02]\t̴\x8d\xe7D\x9a\xc4E\xa9\vM \x13\x88\xa5s\x81\x0f9ʀ\xcf\x017\xa1ǰ\x1b\xd1h\x94\xd7_\xfe\rs\x17̥\xb4\xac\x94\x17\x9f_Og{\xc7`Ft\x03p\x10-\x89.\xc2H\x10\xa8\x18\xb8\x15(F\xc1铧\xf1w\xf9))-\xe1\xf0\x81\xc3464\"\xf4\u074c\x89\xf7\xef\x12\x81\x92\xc4\xc2)\x97\x03\xca\t\xdc\x03|\x02\xfd\"\xf6\x82\x10\x82\xc3\a\x0f\xf3Ϳ\xfa\x06.\xb7\x9b3\xf5\x8dH\xa5\x06#\xd0~\xa0\xa9?\xfb\a\x86\x99@q\xd1:\x13-\x81\x04\x02\xb6o\xdd\xc6\xff\xfc\xf8)\xa6N\x9f\xca\xfa\xe7\xd6s\xe1\xfc\x05\xfbfFsə\x984\xf4\xd8\n\x01\xeaZ\xe0\xcb\xf4v\xce\xf6\x82R\x8a3\rg\x00M\xa8A\xc8\x13\x06\xea\x18\xc0\xfe\x81\xe1\x97@N\xb4\xe3p\xbc\xdd\xf9No'O=\xf1s\xdc\x1e7]\xbe\xae\xc4\xd9@\r:V\xd39\xcc}\xfc\x03\x86\x9c\x0e\xfc%pY⬪?\x82\f\xe1\xf7\xb1ц\xf6\xffX\x03\x1d0\xdc\x04\xaa\x04\xaeE\a\xf4\xbao$\x14\n\x11\f\x06\x13\x1d\x89\xa0ߠ\n\xe0\xcc0\xf7\xb1\x1b\x91-\xa7\x11خ|\x85D\xe1\xbaj\xfcHug@\xc4%{\t\xf0Y\xf4\v*&N\x9eȂE\x97s\xec\xf0Q\x0e\xec;0\xd44} \xb4\x03\x8d\x03\xa9/\x18F\x02\xc5or\x1czv\xd0\v\x03\x88\xd1\x12r\x1c\x95\xb7\xb64\fyL\xe9\x1b}1\x9c\x12\xc8D\xdb>\xe3\xa0{\xb65\x98(u\xa3%PV1\x00\x01\x9c\xe8\xd8[)Zu\x8eFK\xc0R\x03\xf2P\xca\x14\n\t\x04\xd0\xf6@3Z2\x9e\x8b\xff\x1d\x04d\u2e47\x99L\xcb\xd0A\xd2j\xa5\x14\xa3kǰ`\xf1\x02\xdc\x1e7s\xe6\xcfe\xe1\x92E\x9c8v\"\xd5s\xc6\xd0\xf6υ\xc1\x0e\x1aN\x02\x15\x03\xab\x00\x0f\x02\xf2\xf2\xf2\x89\x84\xc3X\x965\x10\x89\f\xa0x\xe1\x94\x05\f&B\x93E\x1f\xe2x\xd0\x04\xa9DK\xc49\xc0ltp\xb7\n\x1dFq\xa2I/\xe2\x1f\x15\xffXh\x9fH'Є\x9e\xe2\xee\x00v\xd2C\xaa\xb0\xb5\xa5!\xe7$\x8aK\x9fZtr\xd84\xd03\x93\x82\x82\x02\xf2\xf3t,\xda\xe5rQZV\x8a0\f\x94\x94\xc9\xda>\xa0_\x96]\be\rvа\x10(~\xa33\x80ņap\xd5\U000a5b3eq5;\xde\xdb\xc1\xc6\xd7^'\x1c\n\xf7wcN`\x02i\xe6\x05\xf5#i\x1ch\xe9\xb7\x10\x9d\x01\xb9\x00\x18\x1b\xff\x14\x91\x9a\xd32\x0f\xadbǡ\xa5\xea'\xd0d:\x05l\x06~\v\xd4Y[\x1a|v\x83l\x93)!\xce\xf5\xa7\xe8\xb4T\x01\xa0Ptz;\xf1\xf9|TVU\x12\f\x06i9\u05cc\xb4\xacTUX\x13\xb0\x0f9x\x9b\xe1\x92@\x02\xb8ZJY5n\xc28\x1ez\xe4\xd3,Z\xb2\x88\x05\x8b\x16\xd0X\xdf\xc0\ue77b\xfb#\x90\x03-\x11\xf2\x81\xaed/ԇ8\x0e49\xe6\x03ס\r\xf8\x19hɓM/\xbc;\xde\xd7Ihu\xf2\t`#\xf0\v`\x17\xe0\xd3\xfd\x12\x98KkӾ\x88\x8dES\x16\xa0t\xff\xd7\x00\x0f\xa13\x17\x000\x84\xe0\\\xd3Y\xea\xb6\uf8aa\xa6\x8a=\xbbv\xb3k{]:\x979\x044\xed<\xb1kЃ\x86\x8b@\x15\xc0\xb5J)\xf7e3.cʴ)\x00\x8c\x9f8\x819\xf3簷n\xef@\xed\xc6\xc7\xdb\x0eI\xa0>\xc4)D\xfb\x9b\xae\a\x96\xa2\xa5N\x15Z\xaa\xe5\x1a.`*:i\xfd\xa3\xf4\x10i\v(\u007f6T\x9b\x04\x84V\xbb_@\xdbl\xdd\x10\x86A\xa7\xb7\x93'~\xf8#6\xbf\xb5\x99\x86S\xf54\xd67\xa4*}\xec\xfc\x1f\xffP\a\xe6\x9c@qQ;\x05\x98)\x10\xb8=nLS{\xd7\r\xc3\xc0\xe3\xf1 \x84\x18h\x9aY\x03\x8cY8e\xc1\xe9\xfe\xec\xa0~\xd4T):Ev\r\xb0\x1a\xfd ]\x8c\f\f\xf4\v\xf0\x10zj\xfdK\xe0'\xc01\xbb\xdf\xe9\x10)!\x15\xe6\v\xe8`i\xbfhjl\xe2LÙ\xa1\x1c\x85\x03\xc1\x8b\xf6\xff\f\x19\x05\x18.\t4\t\xa8P(\x1a\xeb\x1biok\xa7\xa8\xb8\b\xaf\xd7˩\x93\xa7\x91\x96\xb4\xd38\xfa\xa2\x1cM\xbe-\x89_\xf6\xa3\xa6j\xd1\x0f\xe9&\xb4\xc4\x19\xcf\xefǂ\x01\xd0\xea{2\xf0\xe7\xe8\a\xfe$\xb0\x01\xe8LU\x1a%$\xc5\u007f\x8ax~π\x17M\x8f86\xea\xd1˫\x86\xc4p\x10ȍ6XK\f\xc3\xe0\xf0\xc1#\xac{v-7\xdfq\v[7oa\u05f6\x9d\x83\x99\xaf\xc5\xc0,\xb4\x14\x89@/\xf28\xd1ļ5>\x98s\xe9\xb3\xca\xe0\xf7\f\x1e4\xc1\x17\xa1#\xe4\xff\x0f\xa8OC\xa5-A\xaf\xa8(\xcba_\xcf\x00\x17\x92\x99\xfd\xe6\x94@\t\xe9\x94\v\x00S\bA8\x14\u2e67\x9eeӛ\x9bh=\xdfJg\x87w\xb07\xc5\x04f+T\xb9\xf5ޙsH\tڨ\x9e\x8b6\x8aoF\xaf'\xfbCʟ\xae\x04\xbe\x88\x96\xac\xff\x06l\xb5\xb64X0\xb8J\x8b\x8f\xe5\x04\xb4Ꚗ\xc3\xfe)t\x04\xbe_\xbb\xf3\xf9\xb9\x8fa(\x15\u007f\xe7հH\xa0I\xe8\x99O7\xfc~?G\x0f\x1dI\xcc\xf8\x1f\x10Rʙ\xd1Xl<\x86ю\x94\v\x81\xfb\x80\x1b\x81\x89\xe8\xb7\xfa\x0f\x11\xf9h\xa99\x05\xf86Z\xa5E\x06:8N\x9eB\xe0O\xd0/M.\xd5s\x10\xd8G\x82\x01\xfd\x8b9\xdf\"ώ>)I\x88\x12<\xf8L\x04\xae\\\x13\xc8@\x8b\xeb\xde3\x05!\xba\r\xe9\x81`\x1b\xd5%Eŵ\x1f\xb9z\xc5\xfdX\xd6}h\xe3x\x12}r\\\xfe@a\xa0g\x87\x8f\xc5\xc7\xe7)kK\x83o\x00)d\xa0g\x94\x0f\x92\x10G\xcc\x11.\xa0\xa7\xf0j\xfd\xfcǑR\x02\n\xa9$\x06\x86\x13py\xf0\x16\x82(E\xa9\xe2\\\x13\xc8\x03,F\xdbAI\xc1&NqA!\x8bg\xcfg\xd5\xe2\xa5y\x8bg\xcd{\x04\xa5l\xcf\xf0\a\r\x93\xd0\xcbm*\x80\x1fZ[\x1aڠG\x9d%8a\xff\x14\xed\xf4\xcc5\x1a\x80\x13\u007f\x93w?\xae\xfc|B]]&\x90o`\x14\xa3\x9d\xa7\x05\x80\a\x94\t\xb97\xa2\xcb\xd1!\x02@\x93Þ\xb2'\xaa.\x05()1\f\x83\xe2\xc2\".\x9f>\x8b\x1b\xae\xba\x96+fϧ\xb2\xb4\f\x01\x1e\x99^4\xf9\x0f\x055\xc0W\xd1/\xdcw\x89ǟ\x16L\x9a\az\"\xf1'ģ\xec9\xee\x87\x02\x8e\x8e6\xca[]\u0091\x17\xf2\xf9J\xd0\xc6zQ\xbcof\xc2q@\xee\t4\x15m\xab\xe0p8X\xbc\xf4\njkDze\xf3\x16\x1a\xeb\x1b\x11B \xe3\xc4\x19S]Õs.\xe7\x9a˯`\xde\xd4\x19\x94\x97\x94\"\x00\xa9\x14\x1fh\xea\xf4\xa0\x04\x9d\x00\xef\x04\xbe\xab\xf6v\x9c[x\xfb\n\x03m+=\xc0\xf08A\xa3\x15F\xf1\x99Ϻo\xae\x8d)\xab\x10\xad.\rz\xc7\x02{!g\x04\x8a\x8b\xde\xe9@\xb1R\x8aYsg\xf3\x95\xbf\xf9*\xb5\xe3jY\xf7\xdcZ\xfe\xed_\xfe\x8dp0ȸ\x9a1,\x9b\xbf\x98\x15\x8b\xafb\xee\xd4\xe9:\b\x18ϕ\xfe\x90\x10'\x11%\xe8\xc0\xa8\x03!\xfeA*9\xd6\x14\xe6gВ<\xe700B\x13\x8d\xea6\aFU\x14+Q\xda\r\xf8(r'\x81\x94\x10\b5\x1a0M\xd3d\xe9\xf2\xa5L\x984\x01\xd34Y\xbe\xe2\x1a\x0e\xbd\xb7\x87\nW!W\xcd]ȌIS)\xc8\xcbӤ\x91\x1f\xfaz\nEB\x88\xcf47\x9c\xa2\xaa\xacbTkG\xfb\x92\f\x1c\x82)\xc1\x89y~\xac\xa8l\x92)\xbc\xba\xb9#\x90Py\xc0$\xa5\x94\x03\x01e\x95e\x18\x12\xe8\nQ\x15\xf1\xf0\xa5\xbb\x1f\"\xcft\xe1t8Q\xa8\xb8\xb5\u007f\t\x00B\x88\x92s\xad\xe7?\x9f\xe7\xc9\x13J\xb59\x87\x8b@n\xe1\xac\x1fkVtHT\xd2\x17̺?\xc1\xdaҀz\xef\fQ+V!\xa5\x9c\xe4t8Ą\x9aZʤ\auڋ<ގ8\x1f\xa4ȕ\x87i\x9aH%\xd3M\xb7\xfc@B\b\x81/\xe0\xe7\xe5\xcdo\xb8Ξoq\xa6\x91E\x986<\xc2\xd5\\,\xf2#\xa9\xb4ɚ\x04\xea\x15\x9f*\xaf\x14\x8bf̝\xe0q\xbbG_1{>K\xe6\xccg\xe2\x98q\xd0\x1a\xe8>\xe4\x12e\xfa\x87T\x8aM;\xdf\xe7\xf5\xf76\x13\xb3b\x99ijR\x82\x80X>\xee\x16\x17N+\x15\xeb3c\x02\xf5\tl\x96\x005\xea\xc2\xf9E\x8f\u007f\xe5\xef\xee\x89YVmiQ1N\x87\x83\xeeE\x84\x970 \x84\x10\x1c\x18\x88\xae\nQ|\xd6\x14\x86\x8a\xaa^\x92&\x02\xf8йA\x9d\b\xe17\f#\"\xa5T\xf68\fE\xa0=\xc0\xf7й(\v\xf9}H\x13\xedK\x0e\xfb#\xfb\xfc\x9e\xca\xf9b\\,u\x06\x82\x85~\x17#\t\xd7\n\xd0#\x8d<\xe8xu>\x9aT\x83\xf0A \xd8{\xf4\x10Ͻ\xbe\x01\x9f\xbfkXg]\x890\x10\x91\x02\xe1\t\xc6\xff\f\xa1%M\x87\x10\xa2C)\x15\x00aI\x19S\x86\xe9\xd0k\xcbL\x93;\xea\xbe\f\fM\xa00\xb0\x1e\xbd\xbc\xf5\x11\xf4,\xab\x96\xe1*\xfcԟ4Ʉ,}\xcfm\xd1C\x9c\xa1\xcee\xab\xb60\xfd\x97\x1a\xb0\xcf\xe7G\x13\xca\r\xb2(\x86*P\x98.\x87.\x9c\x91\x00!\x04g/\xb4\xf0\x8bW\xd7q\xf2Lʫ&\xb2\n\x892[\xa57\x94'ܧ\x82*\xe2\x17Bt\x81\x8a\xaa\x84Y\x90i:\xc0\xe9\xe0\x8e\x9d_\xea\xd5vP\x02\x99K\xc7ami\x90\xc0V\xe0(ڀ\xbe\a\x9dZ0\x06\x1d\xad\xbd\x88LC%t_4\xad\x1fH\xaa\xf4\xa7\x822E\xaaā\x1e\xe2${\xbc\x02B\x8a\x98\n!\xc3Qd\x9e\x1b\xb3\xc0\x8d\xe1ԺM\x00\x96e\xf1ڻo\xf1N\xddv\x14J\u05fa\x19!İ\x8cC\xb2\xa1}E\xcd#\r/6=\x8e\x1d\v\xbbs\xdf_\f\xd9v\xc8\xd9S\x9cD\xa0sT~\x03\xbc\x8bNӸ\x12mXOE/3q\xa1\x85\xb8#\x18\n\x15\xf9\x82\xfe2)\xa5\x88Y\x96&\x8cR\x84\xa3\x11\x02\xa1 \xa5\x85%\x8c\xad\xac\xc6\x14f\xee\x88\xd2\x17\xb6䲉\x93\xack*\x8a\x96()\xba\xb2\x94\xd0\xf5\xc6U\xb5\xfc\xf0\x9e\x9fOs\x98\x8e\x1es^)\xf2\xdcn\xae_\xbc\x9c\xbbW\xde\xc2\xc4ѵ\xc3\xe3d\xb4\x89\x13%u\x12\xc8\xf8\xddY)\xb6\x8bC\t\x85\x12\xbd\xefQY\x92XW\bC\xc0\xecѓ\xf8]q\x19-\u07b6\x11\x99\xba\xf7\x81\x03\xc8K\xa7\x90E\xda\x0e@\xe3\xaa\xfe\xd7x/\x9c\xb2\xc0\xf0\xb8\xdcn\xe236)%c+\xab\xb9w\xf5\xad\xdcz\xcd\xf5T\x14\x97\xe6\x96<骪\xbe\xe7\xb0m\x9et\xbb!$\xfd\xe9c\xa5\x14B\t\x96\xcfZ\xc8ٶ\x16~\xf6\xc6K\x84\xa2\xe1\x11\xb5\x81Ў\x87\xb4\x92\xf5\xb3*?\xd7\xcey\x94\xb0\xae\x86\xef\x00\x84R\x8a\x89\xa3\xc7\xf1\xc8\xed\xf7s\xef\xf5krO\x1e{\x8a\x1dDO\xb3\xd3\r\xc1E\xe2\xe7ɠ\xabZ\x02\r\xf0\x1b\n\xb7\xd3\xc5͋\xae\xe5\xdaً\xb4-8\xb2\x10ğY\xaa\r\xb3F\xa0\xb5s\x1e\xa3@x\x8c?\xf2|\xc4)\xa54\x84\x10\\1s>\u007f\xf1\xc0#ܼt\x15\x1e\x97;7\xe4\xb1m\x9c\x10\x99\x13\a\xb4\xd4ɐ<=\x1d\x1b\xe4W\xa5\xa8(.偕\xb7\xb2h\xea\xac\xec\x8f\xcb0!k1,\x85\x12\x01\x15.\x9fl\x8d\x1a3\xb5x\\\xf9\xb4\xd9S\xf9\xf4\xcdw3\xb5v\xa2\xfe=\xdb\xe4ITUQ\xb23{\xcb\xd0\xee\xe9\xd5=\xd1o\x0ez\xefc\x94bJM-\x0f\xac\xbc\x95s\x1d\xad\x9cnn\x1a)\u007f\x90\"\xcdQ̊\xec\\7\xef1\x04\xc2-\xa55\xa1\xb0\xba\xa8\xf6\x86\xbb\xaf\xbf\xe5\x86e+f\x8c\xaf\x1e#rB\x1cۏ\x13!{U\xa4m\xbb'\x9a\xe9\x894\xa4\x19C\x9aC\x9fL\x01\xa3\x8a\xcb\xf0\xb8\xdc\x1c9s\x9a@88\x12Fu\x1b\xf04p\xf0l\xfb\xb9\x94\x1afL\xa0u\v~\x80!b\u008aYU\xc2\x10c\xe6\xdf{\xc55\x97-\xb8쮢\x82\x82\xfc\xac\x92\xc7\x0e9\xd8a\x04\x8b\xec\xfa\x8c\xa2h铥s&K \x00\xd30\x99X=\x06\x81\xe0@\xe3\t\xa2\xb1\xe8p\x92\xc8\x02\x9e\x03\x9e\xd8y\xbc.\x90j\xe3\x8c\xe5\xa5\f\x9c'\x16\x95\xf9Ғ5S\xaf\x9fyYՌ\x9a\xbb\x84)*\xb2B\x9eD5\x15D?\xe0d\xbd\xc1\xa9\xc06\xbeG(}I\xa1\xf08\xdd|d\xd15\xac\x98\xbdHo\x047|\x9d\xd9\v\xfc\ap>\x9d\xc6\x19\x11h\xfd\xbc\xc7\x10\xae\"\x87\x15\x8e\x8d\xae\x9cV5z\xc2Փow\xe6\xb9fdrN\xa0GM\xd93\xaa\\\x11ǾV\x86S\xf6l@*IyQ\t\xf7\xaf\xbc\x95\xab.\x9b7\\\xd3\xfa\x06\xe0_\xd0e\xf8\xd2BF\x04r:M\x94\x92ņCTN\xbcf\xea\xfc\xbc\xb2\x82\xa5\x88\f\r\xf3\x18\xbdgT\xd9VU}\x11a\x90\xba\x18\xc3\v\xa5\x14\x13\xab\xc6\xf0\xc9\xd5k\x98R3\x8e\x1c/\xe7\x0e\x00?\x05^\x02b\xe9V\xc2M\xdb\x06Z7\xefq,)\xddV46~\xe25SgL\xbav\xdag\x1c\x1e\xe7\x142\tP\xd8\xe4\xc95i\xfa^/\a)۩\xd8@}QYRNQ^\x01\x87Ϝ\xc4\x17\xf4\xe7\xc2\x1eR\xe8J\xb2\xff\b\xb4dRF9-i\xf1\xe2\xe2\uf8e21\x11\x8bD+\xf2\xcb\v\xaa'-\x9fv\x83\xab\xc0=\x97L\xc8cO$\x87+\xff\xdeV]Y\x98\xb2\xf7\x87H4\n\x0e\x95\xd6\xc37\x84\xe0\xdaًh\xeb\xea\xe0ɍ/\xd2\x19\xe8\xca6\x89N\xa0\x8b[\x9dL\xb5\xe1\vs\x1fEGh\x14\xa8t\vL\t\x81\xb4\xacB+\x12\xab\x99t\xed\xb4\x19\x855ū\x10\x19f,&\x93\t\x98-dy\xca\xdekh\x10\x84c\x11\x8e_\xa8g\\m\x05ng\xeaâ\x94\xc2\xe5t\xf2\xd1E\xcbinoc\xed\xd6\u05c9ZV\xb6\xac\xa26t\x92\xe0\uf024b_/\xce\u007f\x94\xf8\xfet\x18\x96\x854L\x03!\x9c\bQ\x98\x16\x81\x94\x94\xc4\xc2ђ\xe2ѥ\x155s\xc7.w\xb8\x1c\x99\x15?\xb6gZ\xc35\xf1\xc8\xe1\xacK\b\xc1\xf9\xae6\xde=\xba\x93۫\xae\xc5\xe3t\xa5u\x19\xa5\x14\xc5y\x05\xdcs\xcdM\x9ck?\xcf\xe6\x03;\xb3\xd1]\t\xbc\x82\xf6\xf9D\x86\"\xcfڹ\x8f\x82RX\x96B\bC(\xa5\\\xd20\n@\x95\xa2(\x05\xf2ӓ@RbEb\xdeYw̯)\x18U\xb8,#\xc3\xd9V]\xc3)}2\rw\f\x81c\xcd'\xd9\xdbp\x98\x1b\"K(-,\x864\x8da\xa9\x14\xd5e\x15|r\xf5\x1a:\x83~v\x9f<\x9ci\xd7\xf6\x02\xff\xcd\x00S\xf6\x17\xe6>\x1a/w\xaf\xec>\xdbI\xbayJ\xc9\"z\nLu\x8b\xd54\x1f\xbc\xe2ާ\x1e\x96V\xc4Z.\f\x91\x99\xf4\xb1w\x9e\x18\f\xb6\xecΆ\xc4\xc8\xe1\xacK\b\xad\xbe\xea\xea\x0fp\xea|#^\u007f\x17c˫2˼U\x8a\x19\xb5\x93\xb9\u007f\xc5-\xb4x\xdb8s\xa19\xdd\x04\xb46\xb4\xdd\xf3.\xf4\xa8\xae\xf5\xf3\x1eG\xc5\xeb\x13\b\xcbB\x99\xa6\x10B\xb8\x94\x10E(U\x82&\x8d\a\xbd\x80Ծpf\x05\xa6n}\xfc.dL.\x14\x86\xb8\x89L\\\x01\xc9D`\xec\xb3gCb\xe4\xd8a(\x10\xb4t\xb6\xb2\xa7\xf1\x10\x1d~\x1f\xcd\x1d\x17\x985nJV.\xb8x\xeal\xee^v\x13?ݸ\x0e\xafߗ\xaaQm\x01/\xc4?ѝ\xc7\xebX\x17'N,\x16\xc14\x1d&\xe0T\x86\x91\x87R\xa5J\xaf\xf5+\xa0w\x84>\xab\x05\xa6\x9c\xc0G\xd0y\xd1\xe9c(\xc3\xd9D\x13(\x1b\xea\xcdV]9\x9au\xe9K(\x0e\x9c=Ɖ\xf3\xf5D\xad\x18\xa7[\x9a\xb0\xa4$\xd3\t\x94R\n\xa7\xe9\xe0\xe6E\xd7\xe0\r\xf8x\xe6\xedW\tFB\xa9\x90\xa8\x0e\xf8\xd1ζ:\xefڱ߁\xd9\xf7C|\x85\xb1a\x98\x85J\xa9Rz\xca\xd8\xd9kI\x92J2NYzXo6\x80.\f\xb9\x9aL\xa2\xf9C\x19\xceF\xfc\xec\xd9\xf2\t\xc5ȹ\xc30\x12\x8bRW\xbf\x1f_\xa8\v)%\xa7Z\xce\x10\x8a\xa6\xf4\xa0\a\x84BQ\x90\x97Ϛ+V\xb0x\xda\xecT\xce٪P\xff\x05bǺ\xb1\x8f\x19\b\xe1\xc20ʕ\x92\x93Qj&zI\xfaX\xb4\xaarw_.I\xa4C\x00\x13\xbd\xbcgv\x1am{\x10c`i`\xa2߃l\xf9\x85\xecŸ94\x9c\x85\x10\xb4uup\xa0\xe9hw\x1c\xebt\xcbYZ:ژ\\S\x8b\x95\x05\xaf\xb2\x94\x92\xaa\xd2\n>\xb9j\rހ\x8f=\xa7\x8e\f\x15\xf2\xb0\f\xc4\xf33\xcd\t/\xdd\xe7ZU\x16!Z\x86\xea^\xb5\xe6d\b\xf5\x94\fR\xdb\xc2EK\x9fJt\xcd\xe2\xf4\xb7\x15\xb0#\xeb\xfdu\xd9@\x93ǎ\x87e\x03\xc34\xcb\xdb\xdbt\x88S\xad\rq\xe9 h\xf1\xb6q\xaa%\xbb[\xbej\xa3z\x12\x0f\xae\\\xc3\xe8\xb2Q\x83\xe6Y91\xeb\x178\xa6\xbcy\xafkeu\x84\xe8t\xb4\xc9QJO\xc1δ\x89c#\x1d\x03x6z9O\xfaH\\\x16\x9c\bA\x8f\x06N\\\xf9\x99\t\xec\x05\xba9\xf41\t!\xf0\x87\x03l>\xb2\r_؏@tW\x1a;\xd0p\x92P4\x92\xf5p\xc4⩳\xb9m\xc9J\xf2=y\xfd\x92\xc8\xc0\xe8\x1ckT\xbex\xbdc\xe1\x85\x18V\x19=\xda&\xab#\x91*\x81\x9c\xe8R.\xe9\x17\xbc\x1eh\xe6e\x93\xc7Av\xfd4\xd9L:\x1b\x00B\x18\x1cn>\xc1\xae\xfa\xfd={\xc1\x021\x19\xe3\xf8\xd9z:\x03\xfe\xacG\xd7]\x0e\a\xb7.Y\xc9\xf5\xf3\xaf\xc2qq\xfa\x87U*\n6\xadr.\xd8X <1\x95\xae#*\t\xa4J\xa0<\xf4Fg\xe9'\xa2I\xfa\x97>\x0e4\x81\x06\x92N\xe9 \x19\x1fS\x86\x10B\x10\x8e\x86\xd9rl'͝\xad\xbd$\x8d\x10\x82\xfa\xf3gi8\u007f6\x83+\xf4\x0f\xa9\x14e\x05Eܽ\xecF\x16L\x9e\xd1\xeb\x85t᨟a\x8e{e\xb2Q\xe3\x8d\xf5.כu8\xd6\xce\xf9\x0e \x10\x86\xc9U\xe5\x1f\xa5\xe6\xadA\xd3yj\xe8\xb3qJʰ\xb8X\xba\xd8\xe4ɦ\xba\x19\x86i;\xf4\xf8~\xb6\x9f\xdaC$\x16\xc14z\xde-C\x18\x9c\xf7\xb6\xf1\xfe\x91\xbd̝0m\xc8\xfdAR\x85\x8c\xa7\u007fܽ\xec&\xea\xcfk\x83\xdd\x10F\xa8\xda({\xe5jǬ\xc3Q\xac\\$X'\x1a\xde\xd2\x00\xf2@8\u0081V\xb6\\x\x89u\xf3\x1ecݒ\xef]\xd4*n@\xcfB\x17WH\x0f\xb6\xf1\x9c\b\xdbh6Ъ-[\x0f\xdcv\x13\xe4\x18\n\xc5\xfe\xa6#\x9cjm\xec\xd7ClI\xc9\xd6\xc3{8\xd3֒\xb34\xd5\xc5\xd3fs\xe7\xd2\xeb)\xf0\xe4\xcbB<\xef-w\xcc}\xbdP\xe4E\xb3\x9c\xd5hw>\x8a^\x9d\xdc\f\x1cs\x00\xb3@\xfa]\x9e2/(/\x82\x90l\xed\x94Z2i\xc4\x17\xd9\x1b\xe8BS\x05\xa9]\xb7\xd7h\xf7\x96.\x89v\x8f\xbd\x824[\x18\x0e\xe9#\x04\x1d\x81N~\xbb\u007f\x13\x9d\xa1\xfe\xbdö\x1a\xdbt`'c+\xaa1\x13V]\bDƩ\xab\np9\x9cܼh9~\u007f\xa0\xb1~[\xfd\xaf\xa6\xa91m\xd6\xc0\x95\xc6ҁ\x85N@\xf3\"D'(?\x880\xe5F́\xdẹ\b\xd4(\x14\x01e\xc9N\x91\xefnG\xef\x8b\x1e3MS>=\xee[\x18\xa6\x91/-9\x83L\x9d\x87\x89\xeaˁ6\xcb\xd3^T2\xc8\xed\x0eS\x96\xe1\xee\x86\x03\xec\xaa?0\xe81\xe1X\x84w\x0e\xecd\xd5\xdc+\xa8\xad\xacA\xc6\xf7o\xef\xf0\xfbp\x99\x0e\xf2ܙm{\xa6\x94\xa2\xac\xa8\x98\aV\xdc\x1a\xa9w\x9c\n6\xbc]\xaf\f\x97\x91\x0e\x81\x12Փ\x1d\xf8\xf1\x02\x1d\b\xe13\x84\b\xc7bQ˰U\xb1O\xe2\xa0籙t\x93\x89j4\xe3\xba,\xcb\xf2*\xa5:C\xde`\x95\xab\xd0=\x81L\x92\xc6\x12\xbdʶ\xea\x12d\xdfO\x93\xe3h;\xc4\x1d\x87~//\xefy\x93\x8e\xa0w\xd0Y\x96@p\xb2\xf9\f\xef\x1f\xd9\xc7\xe8\xb2Q\x18\x86A$\x16\xe5\x95\xedo\xb1x\xda\\\xa6\x8d\x99\x88\x8aW'K;\x8dUAaA\xde\xc4IK&}\xd3e&\n\t\xfb\xd41 \x00\xa2\x13m\x10\xfb0D\x88X\xc4\xc2Д((\x865\xbb\xb2P`*\xde)\xdc\x05\xae\"!D\xfai\xabv\xb1\nAOfI\xb63\x11\x87-\xb3Q\xb0\xbb\xfe\x00o\x1fي%\xad!\xf3s\x04B\xef%`\xc5xu\xe7f\x1aΟ\xe1\xb9w^\xe1\xe6Eײ\xf5p\x1dO\xbf\xfdk:\xba:\xf9\xc6'\xbeHui%\np\x18\x90h\x92X2\xf9=EL\x97cB\xf5\xec\x9a5\xad\xc7Z\x1b\xdbN\xb4y\r\x87!\xe8\xa9vԉ\xa0\x1d\xf0\x81\x88\xe2\xf2HB~P\x06\x98n\xee\xdc\xfb\xe7I]\xc3\xc6\xd0\x04R \f\x03\xd3\xed\xc8\xcf8\xf3P\xa1\xa5\x8f}\x96\\H\x9f\\{\x9d\x11\xb4\xfb;\xf8\xf5\xee\xd79\xefk\x1b\x92\xb6\xe30\xd7\xd2G\xc0\xd6\x13\xbb\xd8r|瀵\r\xa5RDb\x11\x02\xa1@\x9c\x04A\xa2\xb1(RZXR\"\x95~k\x8e6\x9d\x02\xa0\xa2\xb0\x94\x1f\xbd\xf6,?{c\x1dN\x87\x03C\x18\x18\xc2 \xdf\xedalE5\x8b\xa7\xcdeb\xd5X\"\xd1H\xd2I\xfa\xc2\x10\xc5%\xe3Jn\xbd\xf2\xb3W\xbd\xf0\xc2\xe7\xd6\x1e+\x9fR\"\x9dn\x9d\xad\x11\xc97\xb8{\xe7W\xb32\x1c\xc9I\x14\x03!2\xad\xc5&\xe9)\xca\r\x83\xa7s\xa4\x83l\x9f\xaf\xbfa\x10\x82\xf3\xbe6^?\xb0\x19_\xa8g\xbd\x96@ \x95$jE\t\x86\x83t\x06}t\x05\xfd\x84\xa3\xe1n\xb2\xd8p8\x1c\x8c\xaa\x18E~~>\xa6i\x12\f\x06\xe9\xe8\xe8`\xc7\xf1\xfd\x94\xe4\x171\u007f\xd2\f\xa6\xd7NbRu-ե\x95\x14x\xf2\xb1\xa4\xc5\xfbGv3o\xe2tf\x8dO~\xcbx\xc34\xa6\xe4\x95\xe7\xad\xf9\xe4s\x0f\xd6\x1d|\xe9`p\xf1O\xee\xcd\xfa\x98$G\xa0l\xed@iWy\xef\xcf#\x9d)\xfa\v\x91d\x11\x02AԊ\xf1\xdb\x03\x9b\xd8~rw\xaf\xdfB\xd10\x1d\xfe\x0e:\x03\x9d\x04#!b\xd6\xc07WRR\xc2\u05fe\xf65\x96,Y\x82\xc3ᠣ\xa3\x83W_}\x95'\x9ex\x82\x12O\x11ե\x15t\x06\xfc\x1c>s\x92\x86\xd6s\x14\xe7\x172\xa6\xbc\x8a9\xe3\xa71\xb1z\xdcP\xdd\xec\v'\x8a\xfbdL\xbe3\xfd\xa3\xd3_\xf6}\xb4N\x15ݳ \xab\xe32$\x81\x04\x02˲\x94\xb4df\xe9]\xb6\xfdc\x9bsٔ\x16\xb9 d\xdfq\x10\x82\xa3\xcd'y\xa9n#\xbe\xb0\x1fC\x18X\xd2\xc2\xeb蘆\xb3\x95@$\x88\x94\x12\x8f\xc7CYy\x19\xe1p\x18\x9f\xcfw\x91\xe1\x1b\n\x85(..fٲe\xdd\xdf555a\x1a\x06\xd7Ͽ\x9a\xbf\xbb\xefs(\x051ia\x1a&\x1e\x97\x9bBO\x1eNӡ7 N\xddO4\x01\xf8\x14\xb0\x03H\xadvK\x12\xb0\xd5\xd2`^0\x94%\xb1±\x00*\x83\xc7$譾\xb2i\xab\xe4X}\tt\xbe\xcf\xda]\xafq\xb4E/\xe6\xec\f\xf8\xa8?\xdf@Ck#\xee\x02\x0f\x8b\x17/fҤI|\xfd\xeb_\xe7\xf9\xe7\x9f\xe7\xf3\x9f\xff<.\xd7\xc5\xf6\x8a\xdf\xefgӦM\x04\x02\x81\xee\xbfw\xec\xdc\x011ɪyWQS:\x8a\xea\xb2Jj+\xaa\xa9)\xab\xa44\xbf\x10S\x98)\xcd\xc2\xfa\xc0\x00n\x04n!\a;\r8Ь,B\xa7j\x98\xf4\xf3h\x95T*\x1a\x88\xf8\x94R\x91\xb4\xf2Z\x14=\xeak\xa0t\x8eL\x90sߏ\xe2\xfd\x93\xbby\xfb\xf0{\xc4,\x8b\xce@'ހ\x97p4\x82\xdb\xe5\xe6\xe1O≠\x89\xe3شi\x13\x9f\xf9\xccg\xa8\xaa\xaab\xf7\xee\xdd\x17\x9dE\b\x83\xa2\xc2B\x9cN'Ѩ\xf6v655\xb1s\xc7N\xaaK*\x99=~\x1a27\xfbǖ\x00\x1f\x036\xf8\x9e\xad;\x97M5\xe6\x10\xc28\xa6\x94\xf2\x80*B\x13\xa9\b\x9d3k+\x1c\x00BޠWY*\x94\xd6<\xcc\xdeG\xc2V_\xd9\x1c\x9f\\\x102\xb1\xebBp\xbc\xe54O\xbf\xb7\x9e\xf3]m\b\x01\x1e\xa7\x1b\x95W\x8c\xcb\f1v\xf4\x18\xee\xb8\xfd\x0e\xb6n\xdfJ\xa7\xb7\x13\x85\xc2\xeb\xed`\xdb\xf6mD\": \xe70\x1c,\x9f\xbe\x92\xa5\v\xaef\xd6\xe2\x99\\q\xc3B\n\v\vQJ\xb1c\xc7\x0e\x8e\x1c>B\xb9\xa7\x84\xb7\xf6n\xa3\xba\xb4\x92҂\xa2\x8b\x8c\xef,`\x19p3\xba\"G\xd6N\xee\x00b\x02\xd5\x15\xc5\xd1\xe5 ֬\x10.\x01Š\xcaб1\x8f0\r\xe1k\xee\xec\x901\xcbk\xba\xcc\xf4\x96\xf2ز-\xdb\xea+\x9b\th}\xa0C\x10~\x9eݶ\x81]\xf5\xfb\x00\xedQ\xd6u\f\x15e\x85\xa5L\x9f6\x83\x89\x93'\xf2\xc6\xef\xde`\xf7\xeeݼ\xf1ʛ\x14\xb9\x8a\xd8\xf4\xe6\xa6n\x95\xe30\x1c\xdc2\xeb6\x1e\xba\xf9a\xf2\xa6\xbb\xf0\x8cwb\x98\xe0\xf3\xf9ذa\x03\xa1@\bg\xc1(\xd6n݈\xcb\xe1\xe4\xaee7Q\x94\x97\x9fm\x12\x95\x00\x0f\x02o\xfa\x9e\xad;\x99-)dܱ\xf7+X\xc2\xc0\xd4OA\nT\xc8A\xacE\tq\f!\x0e\"8&\fq.p\xa1\xabA\xc6dSFW\xeb\x1b\x0f\xcb\x06r8\xfb\x92R\xf2α\xed\xfc\xee\xf0\x16b\xd2\xea\xf6\xf9\x98\x86IIA\t\x1e\x97\x87i3\xa6RZV\xca\xe5\xb3\x16\xf2\xa9\x8f|\x9a\xbc#%D\xdf2q\xf9{\"\xec\x02\x03\x97偰\x81\x15\x92Xa\v\x14\xd4\xd5ձy\xf3fJ\xf2\x8bp;\xddt\x05\x03<\xff\xeeoyf\xd3+\x19\xc5\xc4\x06\xc1B\xe0&\xb2X\\\xd5\x01\xf0\xf1\xbd_\xe9\xf5\xe5\x8f\xe7\xfe\x80R\x82\x16\xd0\xd5\xe5\rty<\xae\x96h(\xda\xe8\xf08\xdeGo\xb4\x92^\a2^\x03\xd0\xcf\xf9r(}\xf6\x9f9\u0093\xef\xfc\x8a\xf3]\xbd=Φa\"\x108]Nf͙\x8d'\xcf\xc3\xeak\xaecY\xd5J\xac\xe3\x82\v\x91v\x16\x8cZ\xccѶ\xc3(\x14\x13K&3k\xd4\xdc\xee\xdc\x1f\xa1\x04\xb1X\x8c\xd7^{\r\xef\x05/c\xcbFc\b\x81\x02\xbc~\x1f\xcfn~\x95\xa8\x15\xe5\xbeko\xa6\xac\xa08\x9b\x92\xa8\x04\xbd t\xad\xefٺ\xe6lH\xa1~\x89\xf0\xf0\xde?\xed\xfe\xff\xfa\xf9\x8f\x11\rF,\x81\b\x98.\xc7\x0et\xb8\xbf8\xad\xab\xf5\xcd\a\xca\x14\xb9\x90hh5\xd5\xe4m\xe6\xc9w\x9f\xe7H\U000c92fc\xcdN\xa7\x93H$BYE9SgL\xc30\f<%nTQ\x94\x90'JQy\x11\x9f\x98\xffGt\x84\xda\xf1Ǻ\xb8\u007f\xeeC\\V;\x1d#\x1f\f\x97\xc0\xf0\x18\xecڳ\x8b\xd7\u007f\xf3[J\xf3Kp%T\xf0\xd0+