pax_global_header00006660000000000000000000000064151644707420014524gustar00rootroot0000000000000052 comment=33200e60f5a977c8afae051152dcc2ea85aa0cc7 sptlrx-1.3.1/000077500000000000000000000000001516447074200130625ustar00rootroot00000000000000sptlrx-1.3.1/.github/000077500000000000000000000000001516447074200144225ustar00rootroot00000000000000sptlrx-1.3.1/.github/workflows/000077500000000000000000000000001516447074200164575ustar00rootroot00000000000000sptlrx-1.3.1/.github/workflows/release.yml000066400000000000000000000010011516447074200206120ustar00rootroot00000000000000name: goreleaser on: push: tags: - '*' permissions: contents: write jobs: goreleaser: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/setup-go@v6 with: go-version: '1.26.1' - uses: goreleaser/goreleaser-action@v7 with: distribution: goreleaser version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} sptlrx-1.3.1/.gitignore000066400000000000000000000005041516447074200150510ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ dist/ # Built executable sptlrx # IDE files .vscode/sptlrx-1.3.1/.goreleaser.yaml000066400000000000000000000006171516447074200161600ustar00rootroot00000000000000version: 2 before: hooks: - go mod tidy builds: - env: - CGO_ENABLED=0 goos: - linux - windows - darwin - freebsd goarch: - "386" - amd64 - arm64 - arm ignore: - goos: windows goarch: arm archives: - files: - README.md - LICENSE - man/sptlrx.5 checksum: name_template: "checksums.txt" sptlrx-1.3.1/LICENSE000066400000000000000000000020461516447074200140710ustar00rootroot00000000000000MIT License Copyright (c) 2022 Denis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sptlrx-1.3.1/Makefile000066400000000000000000000002071516447074200145210ustar00rootroot00000000000000.PHONY: build test man build: go build -ldflags '-w -s' test: go test ./... man: go-md2man -in man/sptlrx.5.md -out man/sptlrx.5 sptlrx-1.3.1/README.md000066400000000000000000000143501516447074200143440ustar00rootroot00000000000000

sptlrx

Synchronized lyrics in your terminal

## Features - Compatible with Spotify, MPD, Mopidy, MPRIS and browsers. - Works well with long lines & Unicode characters. - Easy to customize. - Allows piping to stdout. - Single binary & cross-plaftorm. ## Installation **Linux** - Arch Linux ([@BachoSeven](https://github.com/BachoSeven)) ```sh yay -S sptlrx-bin ``` - Debian / Ubuntu ([@mdosch](https://github.com/mdosch)) ```sh sudo apt install sptlrx ``` - NixOS ([@MoritzBoehme](https://github.com/MoritzBoehme)) ```sh nix-env -iA nixos.sptlrx # or if using nixpkgs nix-env -iA nixpkgs.sptlrx ``` **Windows**, **MacOS** & **Other** Download the binary from the [Releases](https://github.com/raitonoberu/sptlrx/releases/latest) page or [build it yourself](./building.md). ## Configuration Config file will be created at the first launch. On Linux it's located in `~/.config/sptlrx/config.yaml`. Run `sptlrx -h` to see the full path.
Show config contents (with descriptions) ```yaml ### Global settings ### # Player that will be used. Possible values: spotify, mpd, mopidy, mpris. player: spotify # Whether to ignore errors instead of showing them. ignoreErrors: true # Interval of the internal timer. Determines how often the terminal will be updated. timerInterval: 200 # Interval for checking the position. Doesn't really affect the precision. updateInterval: 2000 ### Style settings ### style: # Horizontal alignment of lines. Possible values: left, center, right. hAlignment: center # Style of the lines before the current one. before: # The colors can be either in HEX format, or ANSI 0-255. background: "" foreground: "" bold: true italic: false underline: false strikethrough: false blink: false faint: false # Style of the current line. current: # The colors can be either in HEX format, or ANSI 0-255. background: "" foreground: "" bold: true italic: false underline: false strikethrough: false blink: false faint: false # Style of the lines after the current one. after: # The colors can be either in HEX format, or ANSI 0-255. background: "" foreground: "" bold: false italic: false underline: false strikethrough: false blink: false faint: true ### Pipe settings ### pipe: # Maximum line length. 0 - unlimited. length: 0 # How to handle overflowing strings. Possible values: word, none, ellipsis. overflow: word ### MPD settings ### mpd: # MPD server address with port. address: 127.0.0.1:6600 # MPD server password (if any). password: "" ### Mopidy settings ### mopidy: # Mopidy server address with port. address: 127.0.0.1:6680 ### MPRIS settings ### mpris: # Whitelist of MPRIS players. First available is used if empty. players: [] ### Browser extension settings ### browser: # Port on which the server will be started. port: 8974 ### Local lyrics source ### local: # Folder for scanning .lrc files. Example: "~/Music". folder: "" ```
### Spotify ```yaml # config.yaml player: spotify ``` If you want to use Spotify as your player, you will need to log in first. 1. Go to [developer.spotify.com](https://developer.spotify.com/dashboard), create a new app, and set the redirect URI to `http://127.0.0.1:8888/callback`. Grab your Client ID and Client Secret. 2. Run `sptlrx login`. You can pass Client ID and Client Secret in one of three ways: - As environmental variables: `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` - As CLI parameters: `--client-id` and `--client-secret` - Interactively: run `sptlrx login` without providing credentials and you will be prompted to enter them 3. Spotify login page will open. Log in and wait for the success message. You only need to do this once. Your credentials will then be saved to `$XDG_STATE_HOME/sptlrx/spotify-auth.json`. ### MPD ```yaml # config.yaml player: mpd mpd: address: 127.0.0.1:6600 password: "" ``` MPD server will be used as a player. ### Mopidy ```yaml # config.yaml player: mopidy mopidy: address: 127.0.0.1:6680 ``` Mopidy server will be used as a player. ### MPRIS ```yaml # config.yaml player: mpris mpris: players: [] ``` Linux only. System player that supports MPRIS protocol will be used. You can also specify a whitelist of players to use, example: `players: [rhythmbox, spotifyd, ncspot]`. Run `playerctl -l` to get the names. ### Browser ```yaml # config.yaml player: browser browser: port: 8974 ``` You need to install a [browser extension](https://wnp.keifufu.dev/extension/getting-started). If you don't change the default port, no further configuration is required. Otherwise, create a custom adapter in the extension settings. **You can only run one instance on one port.** ### Local ```yaml # config.yaml local: folder: "" ``` If you want to use your local collection of `.lrc` files to display lyrics, specify the folder to scan. The application will use files with the most similar name. All other lyrics sources will be disabled. ## Information ### Source Primary source is [lrclib.net](https://lrclib.net). It is also possible to use local `.lrc` files. ### Piping Run `sptlrx pipe` to start printing the current lines to stdout. This can be used in various status bars and other applications. ### Flags You can pass flags to override the style parameters defined in the config. Example: ```sh sptlrx --current "bold,#FFDFD3,#957DAD" --before "104,faint,italic" --after "104,faint" ``` List of allowed styles: `bold`, `italic`, `underline`, `strikethrough`, `blink`, `faint`. The colors can be either in HEX format, or ANSI 0-255. The first color represents the foreground, the second represents the background. Run `sptlrx --help` to see all the flags. ## License **MIT License**, see [LICENSE](./LICENSE) for additional information. sptlrx-1.3.1/building.md000066400000000000000000000004521516447074200152020ustar00rootroot00000000000000## Building sptlrx Make sure you have [Go 1.18+](https://go.dev/) installed. ### Clone the repository ```sh git clone https://github.com/raitonoberu/sptlrx cd sptlrx ``` ### Fetch dependencies ```sh go get ``` ### Build it ```sh go build -ldflags '-w -s' ``` ### Run it ```sh ./sptlrx ``` sptlrx-1.3.1/cmd/000077500000000000000000000000001516447074200136255ustar00rootroot00000000000000sptlrx-1.3.1/cmd/login.go000066400000000000000000000037651516447074200152770ustar00rootroot00000000000000package cmd import ( "bufio" "errors" "fmt" "os" "strings" "github.com/pkg/browser" "github.com/raitonoberu/sptlrx/services/spotify/auth" "github.com/spf13/cobra" ) var ( FlagPort int FlagClientId string FlagClientSecret string ) var loginCmd = &cobra.Command{ Use: "login", Short: "Login to Spotify", RunE: func(cmd *cobra.Command, args []string) error { if FlagClientId == "" { FlagClientId = os.Getenv("SPOTIFY_CLIENT_ID") } if FlagClientSecret == "" { FlagClientSecret = os.Getenv("SPOTIFY_CLIENT_SECRET") } if err := interactiveLogin(); err != nil { return err } if FlagClientId == "" || FlagClientSecret == "" { return errors.New("client_id and client_secret are required") } auth := auth.New(FlagClientId, FlagClientSecret) url := auth.GetAuthUrl(FlagPort) fmt.Println("Login URL:", url) browser.OpenURL(url) if err := auth.Login(cmd.Context(), FlagPort); err != nil { return err } if err := auth.Write(); err != nil { return err } fmt.Println("Success! You can use sptlrx now") return nil }, } func interactiveLogin() error { if FlagClientId == "" || FlagClientSecret == "" { reader := bufio.NewReader(os.Stdin) if FlagClientId == "" { fmt.Print("Enter spotify client ID: ") clientId, err := reader.ReadString('\n') if err != nil { return fmt.Errorf("failed to read client id: %w", err) } FlagClientId = strings.TrimSpace(clientId) } if FlagClientSecret == "" { fmt.Print("Enter spotify client secret: ") clientSecret, err := reader.ReadString('\n') if err != nil { return fmt.Errorf("failed to read client secret: %w", err) } FlagClientSecret = strings.TrimSpace(clientSecret) } fmt.Println() } return nil } func init() { loginCmd.Flags().IntVar(&FlagPort, "port", 8888, "port to use for login callback") loginCmd.Flags().StringVar(&FlagClientId, "client-id", "", "spotify client id") loginCmd.Flags().StringVar(&FlagClientSecret, "client-secret", "", "spotify client secret") } sptlrx-1.3.1/cmd/pipe.go000066400000000000000000000033251516447074200151140ustar00rootroot00000000000000package cmd import ( "fmt" "strings" "github.com/raitonoberu/sptlrx/config" "github.com/raitonoberu/sptlrx/lyrics" "github.com/raitonoberu/sptlrx/pool" "github.com/muesli/reflow/wordwrap" "github.com/muesli/reflow/wrap" "github.com/spf13/cobra" ) var pipeCmd = &cobra.Command{ Use: "pipe", Short: "Start printing the current lines to stdout", RunE: func(cmd *cobra.Command, args []string) error { conf, err := loadConfig(cmd) if err != nil { return fmt.Errorf("couldn't load config: %w", err) } player, err := loadPlayer(conf) if err != nil { return fmt.Errorf("couldn't load player: %w", err) } provider, err := loadProvider(conf) if err != nil { return fmt.Errorf("couldn't load provider: %w", err) } ch := make(chan pool.Update) go pool.Listen(player, provider, conf, ch) for update := range ch { printUpdate(update, conf) } return nil }, } func printUpdate(update pool.Update, conf *config.Config) { if update.Err != nil { if !conf.IgnoreErrors { fmt.Println(update.Err.Error()) } return } if update.Lines == nil || !lyrics.Timesynced(update.Lines) { fmt.Println("") return } line := update.Lines[update.Index].Words if conf.Pipe.Length == 0 { fmt.Println(line) return } switch conf.Pipe.Overflow { case "none": s := wrap.String(line, conf.Pipe.Length) fmt.Println(strings.Split(s, "\n")[0]) case "word": s := wordwrap.String(line, conf.Pipe.Length) fmt.Println(strings.Split(s, "\n")[0]) case "ellipsis": s := wrap.String(line, conf.Pipe.Length) lines := strings.Split(s, "\n") if len(lines) == 1 { fmt.Println(lines[0]) return } s = wrap.String(lines[0], conf.Pipe.Length-3) fmt.Println(strings.Split(s, "\n")[0] + "...") } } sptlrx-1.3.1/cmd/root.go000066400000000000000000000103201516447074200151330ustar00rootroot00000000000000package cmd import ( "errors" "fmt" "os" "strings" "github.com/raitonoberu/sptlrx/config" "github.com/raitonoberu/sptlrx/lyrics" "github.com/raitonoberu/sptlrx/player" "github.com/raitonoberu/sptlrx/pool" "github.com/raitonoberu/sptlrx/services/local" "github.com/raitonoberu/sptlrx/services/lrclib" "github.com/raitonoberu/sptlrx/ui" tea "github.com/charmbracelet/bubbletea" "github.com/spf13/cobra" ) const banner = ` _ _ ___ _ __ | |_ | | _ __ __ __ / __|| '_ \ | __|| || '__|\ \/ / \__ \| |_) || |_ | || | > < |___/| .__/ \__||_||_| /_/\_\ |_| ` var ( FlagPlayer string FlagConfig string FlagStyleBefore string FlagStyleCurrent string FlagStyleAfter string FlagHAlignment string FlagVerbose bool ) var rootCmd = &cobra.Command{ Use: "sptlrx", Short: "Synchronized lyrics in your terminal", Long: "A CLI app that shows time-synchronized lyrics in your terminal", Version: "v1.3.1", SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { conf, err := loadConfig(cmd) if err != nil { return fmt.Errorf("couldn't load config: %w", err) } player, err := loadPlayer(conf) if err != nil { return fmt.Errorf("couldn't load player: %w", err) } provider, err := loadProvider(conf) if err != nil { return fmt.Errorf("couldn't load provider: %w", err) } ch := make(chan pool.Update) go pool.Listen(player, provider, conf, ch) _, err = tea.NewProgram( &ui.Model{ Channel: ch, Config: conf, }, tea.WithAltScreen(), ).Run() return err }, } func loadConfig(cmd *cobra.Command) (*config.Config, error) { if cmd.Flags().Changed("config") { // custom config path config.Path = FlagConfig } conf, err := config.Load() if err != nil { if cmd.Flags().Changed("config") || !errors.Is(err, os.ErrNotExist) { return nil, err } // create new config conf = config.New() fmt.Print(banner + "\n") fmt.Printf("Config file location: %s\n", config.Path) config.Save(conf) } if FlagVerbose { conf.IgnoreErrors = false } if cmd.Flags().Changed("player") { conf.Player = FlagPlayer } if cmd.Flags().Changed("before") { conf.Style.Before = parseStyleFlag(FlagStyleBefore) } if cmd.Flags().Changed("current") { conf.Style.Current = parseStyleFlag(FlagStyleCurrent) } if cmd.Flags().Changed("after") { conf.Style.After = parseStyleFlag(FlagStyleAfter) } if cmd.Flags().Changed("halign") { conf.Style.HAlignment = FlagHAlignment } return conf, nil } func loadPlayer(conf *config.Config) (player.Player, error) { player, err := config.GetPlayer(conf) if err != nil { return nil, err } return player, nil } func loadProvider(conf *config.Config) (lyrics.Provider, error) { if conf.Local.Folder != "" { return local.New(conf.Local.Folder) } return lrclib.New(), nil } func parseStyleFlag(value string) config.Style { var style config.Style for _, part := range strings.Split(value, ",") { switch part { case "bold": style.Bold = true case "italic": style.Italic = true case "underline": style.Underline = true case "strikethrough": style.Strikethrough = true case "blink": style.Blink = true case "faint": style.Faint = true default: if style.Foreground == "" { style.Foreground = part } else if style.Background == "" { style.Background = part } } } return style } func init() { rootCmd.PersistentFlags().StringVarP(&FlagPlayer, "player", "p", "spotify", "what player to use") rootCmd.PersistentFlags().StringVar(&FlagConfig, "config", config.Path, "path to config file") rootCmd.Flags().StringVar(&FlagStyleBefore, "before", "bold", "style of the lines before the current one") rootCmd.Flags().StringVar(&FlagStyleCurrent, "current", "bold", "style of the current line") rootCmd.Flags().StringVar(&FlagStyleAfter, "after", "faint", "style of the lines after the current one") rootCmd.Flags().StringVar(&FlagHAlignment, "halign", "center", "initial horizontal alignment (left/center/right)") rootCmd.PersistentFlags().BoolVarP(&FlagVerbose, "verbose", "v", false, "force print errors") rootCmd.AddCommand(pipeCmd) rootCmd.AddCommand(loginCmd) } func Execute() { if err := rootCmd.Execute(); err != nil { os.Exit(1) } } sptlrx-1.3.1/config/000077500000000000000000000000001516447074200143275ustar00rootroot00000000000000sptlrx-1.3.1/config/config.go000066400000000000000000000100261516447074200161220ustar00rootroot00000000000000package config import ( "fmt" "os" "path" "strconv" "strings" "github.com/raitonoberu/sptlrx/player" "github.com/raitonoberu/sptlrx/services/browser" "github.com/raitonoberu/sptlrx/services/mopidy" "github.com/raitonoberu/sptlrx/services/mpd" "github.com/raitonoberu/sptlrx/services/mpris" "github.com/raitonoberu/sptlrx/services/spotify" gloss "github.com/charmbracelet/lipgloss" "github.com/creasty/defaults" "gopkg.in/yaml.v2" ) var ( Directory string Path string ) func init() { d, err := os.UserConfigDir() if err != nil { panic(err) } Directory = path.Join(d, "sptlrx") Path = path.Join(Directory, "config.yaml") } type Config struct { Player string `default:"spotify" yaml:"player"` IgnoreErrors bool `default:"true" yaml:"ignoreErrors"` TimerInterval int `default:"200" yaml:"timerInterval"` UpdateInterval int `default:"2000" yaml:"updateInterval"` Style struct { HAlignment string `default:"center" yaml:"hAlignment"` Before Style `default:"{\"bold\": true}" yaml:"before"` Current Style `default:"{\"bold\": true}" yaml:"current"` After Style `default:"{\"faint\": true}" yaml:"after"` } `yaml:"style"` Pipe struct { Length int `yaml:"length"` Overflow string `default:"word" yaml:"overflow"` } `yaml:"pipe"` Mpd struct { Address string `default:"127.0.0.1:6600" yaml:"address"` Password string `yaml:"password"` } `yaml:"mpd"` Mopidy struct { Address string `default:"127.0.0.1:6680" yaml:"address"` } `yaml:"mopidy"` Mpris struct { Players []string `default:"[]" yaml:"players"` } `yaml:"mpris"` Browser struct { Port int `default:"8974" yaml:"port"` } `yaml:"browser"` Local struct { Folder string `yaml:"folder"` } `yaml:"local"` } func New() *Config { config := &Config{} defaults.Set(config) return config } func Load() (*Config, error) { file, err := os.Open(Path) if err != nil { return nil, err } defer file.Close() config := &Config{} err = yaml.NewDecoder(file).Decode(config) return config, err } func Save(config *Config) error { err := os.MkdirAll(Directory, os.ModePerm) if err != nil { return err } file, err := os.Create(Path) if err != nil { return err } defer file.Close() return yaml.NewEncoder(file).Encode(config) } // https://stackoverflow.com/a/56080478 func (c *Config) UnmarshalYAML(f func(interface{}) error) error { defaults.Set(c) type plain Config if err := f((*plain)(c)); err != nil { return err } return nil } type Style struct { Background string `yaml:"background"` Foreground string `yaml:"foreground"` Bold bool `yaml:"bold"` Italic bool `yaml:"italic"` Underline bool `yaml:"underline"` Strikethrough bool `yaml:"strikethrough"` Blink bool `yaml:"blink"` Faint bool `yaml:"faint"` } func (s Style) Parse() gloss.Style { var style gloss.Style if s.Background != "" && validateColor(s.Background) { style = style.Background(gloss.Color(s.Background)) style.ColorWhitespace(false) } if s.Foreground != "" && validateColor(s.Foreground) { style = style.Foreground(gloss.Color(s.Foreground)) } if s.Bold { style = style.Bold(true) } if s.Italic { style = style.Italic(true) } if s.Underline { style = style.Underline(true) } if s.Strikethrough { style = style.Strikethrough(true) } if s.Blink { style = style.Blink(true) } if s.Faint { style = style.Faint(true) } return style } func validateColor(color string) bool { if _, err := strconv.Atoi(color); err == nil { return true } if strings.HasPrefix(color, "#") { return true } return false } // GetPlayer returns a player based on config values func GetPlayer(conf *Config) (player.Player, error) { switch conf.Player { case "spotify": return spotify.New() case "mpd": return mpd.New(conf.Mpd.Address, conf.Mpd.Password), nil case "mopidy": return mopidy.New(conf.Mopidy.Address), nil case "mpris": return mpris.New(conf.Mpris.Players) case "browser": return browser.New(conf.Browser.Port) } return nil, fmt.Errorf("unknown player: \"%s\"", conf.Player) } sptlrx-1.3.1/demo.gif000066400000000000000000006315321516447074200145070ustar00rootroot00000000000000GIF89a X1       !!"$ #!$"%'#$'%)',2(.+// - ,!+! H"+"/$".$"1$"6%"3%#5%$8&$0&%6'$3'$5(%4(&2)&5*(8+)5,)9,*6.+<.,90-<1.?2/=30A42>52C63B85F86B;8G<9J<:H=;J=;P=;L?OB?MDAPDASECZEElFDXGDUGEYHDWHETHEVHFZIFWIG[IIxJFZJGVJGXJH[JH\KHYKIWMJ\NKZNL`OL]OMaOP|PL`PM\PM^PN`PNdQMcQN]QN_SP`URbUSgUSiVRfVSdVSVThVTjWTcWTeWTgYVg[V[Xg[Xj\Zh^Z^[k_\m`]l`]`^ta^oa_sb^tb_pb_b`vbarbatc_sc`oebsebwfcrfctfdxfdzgduge{hevhezie{ifwifyig}jgujh|jh~khyki}kilh|lizli{ljxlj~ljmimj{mkmkml}nj~nk|nlznlol}olonplpm~pnpnqn}rnrospurwtyvzw|y}z~{}ôƸȻ̽! NETSCAPE2.0!, X       !!"$ #!$"%'#$'%)',2(.+// - ,!+! H"+"/$".$"1$"6%"3%#5%$8&$0&%6'$3'$5(%4(&2)&5*(8+)5,)9,*6.+<.,90-<1.?2/=30A42>52C63B85F86B;8G<9J<:H=;J=;P=;L?OB?MDAPDASECZEElFDXGDUGEYHDWHETHEVHFZIFWIG[IIxJFZJGVJGXJH[JH\KHYKIWMJ\NKZNL`OL]OMaOP|PL`PM\PM^PN`PNdQMcQN]QN_SP`URbUSgUSiVRfVSdVSVThVTjWTcWTeWTgYVg[V[Xg[Xj\Zh^Z^[k_\m`]l`]`^ta^oa_sb^tb_pb_b`vbarbatc_sc`oebsebwfcrfctfdxfdzgduge{hevhezie{ifwifyig}jgujh|jh~khyki}kilh|lizli{ljxlj~ljmimj{mkmkml}nj~nk|nlznlol}olonplpm~pnpnqn}rnrospurwtyvzw|y}z~{}ôƸȻ̽?H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνËOӫ_Ͼ˟O_t(h`ς 6`?(Vhfv ($h(&B :(ch8<@)$.3BC6PF)TViE(;H&W)dih v`)Dp&pΩgwpfJlމ瞄f,)(hu/ 4@.Ov\^p @p@))֪J/$q3P (Jj9B!)qḓO?#($p y0),"ZV;t 8 ,F*/L2ɼ|D;d JD,qwȷ-k.2PDL?TP\A )2@A, xD4`I>r M_0йn\A $ H@CO8(B`u[w7lkBeߴO=caym9k+-?D`2ݠ=޴t]|Jt&PAm@<<`>W$aP;`Ry h.i`N$03ا- ْ>Gt8^cp:qV4-i"1s>xC BЁi (̴evA$Cܘ69ץ!HvLDX !A 9!ɇ@`$H%@IKE82bJ\V6hE+U+zɍx$L*/nIie<.K#)H^$](GQ<19da@( RF8*SIʉ @Z$,wC%/>If:Ќ4IjZ̦6nz 8IrL:v~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ PJԢHMRԦ:PTJժZXͪVծz` XJֲhMZֶp\J׺xͫ^׾ `KMb:d'KZͬf7z hGKҚMjWֺlgKͭnw pKMr:ЍtKZͮvz xKMz|Kͯ~LN;'L [ΰ7{ GL(NW0gL8αw@L"HN&;PL*[Xβ.{`L2hN6pL:xγ>πMBЈNF;ѐ'MJ[Ҙδ7N{ӠGMRԨNWVհgMZָεw^MbNf;ЎMj[ζn{MrNvMzη~NO;'N[ϸ7{ GN(^OW0=8Ϲwe@ЇNG3Oҗ;PԧN[XϺַ{`NhOq!,W!,W!,W(($@6ǰÇ pȱ 1L#Ǐ!E^h?ȜI0ZBDӦO6t?JhŤ>2eXOQN( 0M[_>IcҨ!3Z7vnxRUӀ5z}FNqV;tLSg ^C ACNU{~@ J moaw%>p.sg>;˴#۽q ^Zu2I7|g:2Q' p;<- |ɧN|ZԝC*"=df&5@&ώ##u&g8<ld59g05LOfXWEv&qEgSi~YfU{&)V0.4u{BNM,|ĠR"@i4piV# 3&T꫰%Q]AeS뮸VH Hl !,W!,h(($;H@~)\Ȱ?k83H BV3 ⇏ ^̈qcG CZLHҡRtCFd2LjSN3 rqm ѤKen*<ԀRWՔVwbE@t 0`9ǒ !jwڵlr&l֏WO0 L*]㦘lڸa@jo殓~Lp`Ѥ85fĠ7[tR- ݪ{ ᕫ vN߃N%,6pH*͓O;5 KF^0rUmlP AxC VhAƠ*݅!Kvhj!d%8 Q 4DS6M5BXРcDid?dPF)P.iW~cpe`~d@!,W!,W!,('$uH[Ȱ~Iyt:iCG\y@:ywoWѷ>qA3s5_nC)q<,Р]v@6D>挰f#)s.-0;)w2Du\ 4$= Cx/0+`1 2yӃR$&P\IޖQyfCة&RiʩO) @~'48"PjāV6Vfxf)E}ҧ *ʩ!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,($n;H*D@@ŋ3j8q?j )#ɒOreK/K9cM77dϋ;9L9bPEA&U(ХN6俫bʕҭ!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,)(" H*\`JHŋ !FȱǏj ɓ7I˗?p͙0sdx͞; ҔO,o)SGy-:ODFZ՘_O*E*Tϱdv6-[f-VXhm*,\rb ߼[o6Fpⵏf;ɔ 8t-fZeҭVf-3ճ;=aW}ܝ' 0^¡Ke>[2]}CC}͑bawxSE5hdMT]~ojn c Ηbzh!(U5>EuThX:&. TPVIVfZv^Z!,W!,W!,W!,W!,W!,W!,W!,{( H*\p$papȱG `@(SJ 0  xCCJB #]ʴ pYfAk0ördhxJĀ-đW퉂H5`lCIB͡R8*NH(҉C, Cl˘6.Cnzp.D>{ !*F0hg_y݌LPyg}v 6b,Q 9"A'mo [?/ 'DQ59ÎlpB 3=۸,J;U:sE6(9T;xS_ Q p퀼Q. āa A7: H1 LPpL l@ aPM H1&n4}H}FsAJMt-t@E7k 0R>K,BcB 4384O@bL3gJ L:,2q9j)фS$8`!0̄HfphȈ^4A}o8>uPL4xP,p>@@Pp0 sn9D ) !h#`mh0L+ #FآT-̈ % "3l3Nq tB/:q@"cP\A8P+b\ ! '\  $a:#Ot0 ̀7R6xY~c8 B@"y>~C cY<$~ (Bc|ste?1|y[F;ymHMẓ?1Zy>A Lp r.h&R=$ Ha8`#`@6Z|a6!{RHG6*X G/}AMCt:&Pl@^6H_<|ChA89 Hi( 2^UCL, G;!{C8B0qv`R6`$<;[v")Qwv8h)IC:xp1!@3L (W-(` (qQ1[H71]wádPi|ȇ5P̣Hx=AvwwPvC:6±Gg\,H bucX:r[p 2FqaazF  $xHIpPL/`8̎Ct@M828 :r@ eج@ `u%{ s;шFb+&8p!0'7aR@@aqBث:P ğՁ A$S)J A 61qc :V8#["HBÜ分" >#1Jێn|FBhDG5Lh; JΐSBdcD1rK-, GAr HGJbG4āT&`xBƚsCRh@\[;!q36(1oxF/6cdu٨AؼeXڨ$SAbxvZ@5 @{AUlV*@ s | d'nЍ| *{ OP0| Fv  aixMG=ȡ k;7GqL@y X}f:6r3 hG[XJS,ccbp% /rh'x6pW|8~13P`Y80 GaK)e`h9%bY6 >f `x/,WUg( HА Ȉa\@ 8P## p 3@u[ P 0* tKpz?OP\f@ #]mLbh8 @ 0q8@}(|CApEm>03Ui\/1Ѐ@ ,0 f=:0nZ SW6/8D('P0pP xX rA1`,1i q7P"U  ycu ,f `(%}کPuv3#vօ,p rF CIIy 0 z )DjhIzLʙ!)@mY]EQ p`*iap P q se }E `Pw .!0!(h07,9-6$up * nipc :!8D{ri0X(EpJaPZڐmZ5:(ኘX銤ٮQ 0L7kN _szYph# 0"& ,?Z` @ .f!+y ,@-+/ےwt xZ=ۓIpp  y 1 0 Z<Nе*l @[yG / # % )o{  Gx z+ʣ[;Gz -p7\#(b>$(Qm p R CpA%op%בwig DXph|kr {ƪ{@ ` sI[+4(P0P"zmL~ ;'T `Pp:hf0 Z 0 a|0!P!  ' X|yۈ@I p %lgspL0 z:-pZ n9)!ǫiQ;A` %@`XEcq,jp\!ņs1x <*vP  Np ӠWҀɠGQ p p "}4 0Mq` ۀ"P1ǍxFb $qݠ N=*0b ` p[ vޠ [<w  PZ P 9"a)͈{*R _ЀѶ".͍͹9L ذٵ.O/fG`N; "  X>Q ń+ 'ШVU]-* F] mA uZ @&`i=iM&p@0mE]=3Hz0⠜]gqB9|c^s`m,L>J`Q}06m`Z*)p` tV ]ڐQ=٠Lpى+ +LY0֦52A?pPFDZu cg Noeq gb8fb#kp TCh J @ 0( QHGv*1C@ @ tP\PF@R͹@נPFa`ꝮTP @ Y0y cgՀ5P`Y Roz~[PpqL8!` J@a+pZQE,X,``0b=_ A$ E T@#f rIprI2r?9.pEL} JtMو'PNKJ`G\L@8GCEoHU6H0PKJp{)C6PGPLP#@0Mr? F 9JPJbAE3K0O60NP¦81b!q!fL4aEQ6w]F=b 4I@@TRdF 7؁2CpB7b0>UTU^tC~ F \U/3x -\{gC (xPN;`fVzU\#_MC {jxA Fܑċ7= +la$z% avU` v衻U U⽟>4X 9f}MwuZ~@{a0B '0yy2}Ʊ OD1EWOy&P@Pdzޑ"?2H!PwAk J+lywġH0quG˲M7̒nGo%Q "yY28%0Aygva|g(h34SM7('QaygkvpSW_5t"hGyg{!,5Xa倂-zGz  aXkeP"Rd(„~6]u`hgyEj_8@ %_P8p@P/F `||Jc,89*5mdS֠enYtr(X3H|Թ9H%PN1#h ):7Y9N ވG#Kjšk*Ii!khuk`em9kB)&7qNHHb<ڀ$f7fA*Ə(Sjd jop+g{N hglb `$L~R"q `j'q"8D>f %Nѥ@浄 π)2a䥈%ED $Y^4 (T#? ka$a̯~?0op3x@"b% l AIh[؆$)d!8'P#ИBGj}VaB~ßUD!E pF XD>N%` (^[#@E+b#  nde|d6q}M |Hb8)Nrle)BJZe؂3qt+BJĒ;f'IZEtS8q ɎҔtC䞳UOF#PNRi̦Sx,Fśb4! R#&$f8Ϡ6b"I 3$a6,Pż8 [U#B3h1N&Eh1 UHGJ1 =:B1`+Txi"$o38r?,eHG@P w0(PO43R 0A=!#X4t eja;j{, cD" Ei8?DXHU,nvJ <  p8Mx%9[P )hͨ^$-:fP-f8B2^Yh,qA ihv"Cn6Ptdj\8 FCA$HhLAq 4(#9[7p* P6 aHI@P hh [8^OxG,׌AWR*kVSm`FWHalFT)1]O ХaA@ h7g[I` Q=A@Ki,iC Q>liLc Ϋ784Ԋs$lGc Y40^jI(u2<0h`{}j )¼T~F#j0H0DZրVv H)t.B% )0!|P5,EȲȎ0hψ-A xg -? @^`PF-.Y`ihO4A 0)`πd5p3"@O4$adApNБ lA8H1G4CRp?4o"p&fL+j3;)#[g?{ 8 `O+h?REhxc @kS?)(i-'-`jx̚)pqCxiX #H(PA:fx$Жʤl% hsڀ%Q ]L'x»+8(p_`F H[- '1. hjhūkЂ%8fH=l h׻ ðijp!hjx90g%ЍPUE( P`) hh- D k8 pPLDh>P#Gjl[5 3E `M()fEmw`R?GG7-XTDE B`P8Xh#yi_CFɍBf?-ج&N;sEHHPC`ԍHRC.H5B`8"خ1CSDF%Xh\# Sr kp  ܏V87HpH\]^4 Sl#&bigilpi:;!أ H? 'fҀ`7Q Pcوh҂O? H"3 ip&( P{ Hkp.yB$FH&4%PDfˬ ̆_ ī-CD HxH<i -a`%hfiCa4#10p ;Pd#ț,1h`M2xS0=Ҁj` `Z$u$8kxQ΢EH\khG 8h8i(W_'юp3fp Mlz pPܰ؂h0K5R7KЀ`.%Cl;,(=5)8&8` `>Pϫ#kS'Ph pz)Y-Gu͎ؔ12H pb.KTc)p#X'O*銃aSj  \hPV`OE|h.()JPPh8FRԃҲ8oMPh߼j5'RH+FPu +߹lul)xXqX8lXu5f@َһC" %T݈LYY2fPSL !#c(Ԧ!Hhj`-_j&h"8,Z+^Tb[p-H `k[zHOԲՁF?]?*1~|n~,Xg8,h&Ȃ{chH]2i88$mP&|XJ*H>isNo.ih`|-XLA@hT\XضɁfkZXkbO҆A9%J-fke^D3٦p#hl_+Hq _,0:c2-j,Wjx~TyH hZ0;>ng1R_hfܦThՀh 8P1ki8.X\6)6XǑڽ@]X@*\j!VZj[jIJqo"#HZx1؀çȫ;z xRc] & ۄqXG,&-zl87x*pkh1ރhY8hλeYgvYfx&, $ XpkP di;"5C 58j@LIX1r)*w#3P&x(>hCX)rA h]-؀q)c(쌀*<8#u:hm3耊Q BHop,ؾ!  ca r,PS1"Pp `8#‹PIkؚS*eL Fظa< x X? ,*`M8Xg,hkpr hvlnP]tq;`Ps܎=G-xfh 8_y%H&1\[ P ϊ#ڂXo"qTX8b8;H#@P+B8A|0 =,'u f%K3%O8CRz\={$;Ⲟ~$""C*9 P @c适Jԣф(Lc*ұ@Q $ )ӡ ~XCL5( ]PxiTtf=+Z35C5MU+\BӲ,`x&x`̆hJ2fX " S\ c Z$awfiiS2(Px+kc h%ZH 08e+܏јB J!M4*8 [p5: Sтb:E0F :ge[+kԘ'A 0pΗ24!-[`4&:xA oQ'}[1%ZC3,`.~1yؽ($K CEKsWk :15| )ڪҏp4cBfP,fֲ4p70?E$}Yjf Jf8gAm51- a4¨r:Sp(`qC[:*PPo6jQ`%Y?Q ̧5\-Qk.vV_[~6hScu5#2VA jzuz[```kLxF7 +-$ -P90!Vx{90[cƁ t4"f p ø@:`9(5!r R(-Q>mDcS QSl \CX^ͧU84 vB 0?&H%Uzv 5pVum/x{4@~,გ,1P\mks@BBPѧr'j# Y e?fZV`~Ax.d 0t@y%"D#Y}ʔmZA/Tfe',@-şU `3XCR`4@T fp06\\@!B@ B6("h& @(N$.a!!6LTaz *!RF ހ &\1L W\!rNp*(@#2$JQ#&"'.]L#B&'`n]@@A1mAHA85DNOX&T3 P4 ̍/JA <A@J@"O E (( S!4Pq @HVN@3"AY<"B]@/BA  K""DA R̎ c`GPMf G\I 0\b{T-An] l*̤X-HNxLY 7}D65ȉ6XC5>c˓0CDJUGH4T 48裵0lC,@C^,C5H0TM3HR>ޡ #Q]B5ˉP  0 bAM$p $!h85 5Dre#$Er!P?Z`A#(W6HAD#$`A2  2/ B5Z}o4PC+$ \W37vDC4H0Zս/a,3NCD-HPB@(X80SWZ,̖A`$l8C$HC)t@ .0$4ā4# q qQqnKD [DJ,H@D(A0$h/C)xqCP/$IXc^4 1(`/ U"DGDn 1Tf,3/S5(W5^q@;I7X_X3Y' 0V7O@iX hADkyb ت8-`-$8C ݳB$Og'Am Hi9eD04l;xLcD @?â j9y@4xJnH|=9@$TC#!`Q2$(Ɓz6_xz'-NA_H|yHt@$d)w/Hv-`CB@ wЍI -09*$(A @ B<3dJXf#0AlXL @# {OXr۬/ @ $,]bA5 4kio{W֛mA pJ d,4A p28A 4"=d p}L4hP2pOD5`~p@uuSOX&-A $H`@B X37@8`A&TaC!F-iѤeԘ'!:PLchˆdFx`@耏3Ӝii ` C~]-4EAAxIrГg&KV@"+ˬE!S Т#8Φ ŨN:-΃"`3r#L vfR 5s0"ifhh5F\ j$M / 426ԑ eGlK5\ J"W3R$O/^ O7n& <1j֙)IO>; fCf$"` h,H0C*h"gih%Z 0d&PқA2H#i>"DBgN,%@ EDaN#30"ge(A/=Fh7#4(!j(R0`d-FϠ \0aL`ofF)  ` e"B zgFO,ǫ[qJ_KažBVX:W_dyAa `YJЖb%V^y 7"e\s]6Xg XfEv\i{_p ke(%ބ!^`k!F s-m倃 6PYe-e\fǛ F]!B:h}6HWnji&{~z! `&#hDn~!4Oh:сּ\ /u(` f 1\ `3%J69O]You9 ia (߁^BffO^1A'hb z_.){/Y pdߏp X@i@[B8 [(lA .@5a &p>A`.Цl&8B8A҈ -DE_llfa ?D\i S% 8@(W0`9 وFƄ8@I#Y@ >, ې\M]% ,HՈD. ĠРA(LfjZt;I!B6M.P@X5 <`s3?hՀתVQMg,Q{Ɔ.h'0PтēIpF6Q, )xdҋQŨxl*RSGxP3Ӥ4!E4-5NeFс P@85` 8Eu8dl3` P x01`m}k\ ԀFz(/uG;fq0v Z;tdqn\G*3¶5.lp8I (hř !jaa;@l^(!PAd̀ t nu}]UX%`#C?P#wz^ "?0cX;!wMABP j@(x$3jPBOH,O$h A%6` ]*5-`h x&1dlA _jh0(14Ɛ\ {9'H7<03 Fp@ B]j|=5*)=f \.0`@0e` ԭla-t-S E rim:@h-@f@D *@A8FO 8B) "tָ H|&PLXX.BA?ehH# ,!M Qdp]݈h(E5*Q(tM`u7l"sQsY(E5l'[xP!@aqA ,[*"C# ,|$ gsXTI*0DDl`xW1 EȀO085@(D̀0A 6Z" tAI *Pp g\*)o`@PpB)ސA:"j IJTJ@ P*b@2Cp(`  #B  Kp !#BXDE`N! A@S/ !1Tm &qP 0 M`BzFq .D QL J BN4@pXEa ֺK*aC" q)'8I˂`Y&:maТ.zf Qia`J".0 u;ƨ5!-1C )8 0g4"` @Q$?@ )fZlZ` ư'çt((e膀` *^6!` | -,2*rA.J .S0q 1<1O',3"F fo $2.4WJ!ZA"$`!rŸxS!@D!|`/@p:Le K͠6! 6!ړ-޳)"*!ֲ4I`?? +Oy s0031932%.'4C=s@t4pЬV@aT!Xr&)b܀82>aܠ7TI(T02 # Lxa T,3M糵)v?[@O0A ;PQ2B1!r#;@RsUCB  r @EEMiT0Uu !. !`ޤ >V'lh>Jb8d`AD$)$!nZt"uMԧ1\?bVKuuP33^# s"`BU"(" j Cr`I`65mE{EGՈ&V7Q5*nhNDYI'o5̠H: ]v!uT$}T5@ lN LBZMhQD ((R vNk`@c@Ak1l,lf: &H>V`-4i@o66_TbMUpp j&o@ A* "Ws6 `s cm`arBb ,O@21v[iWMq8 R |Z4_:$ $` @5صyz#   z_xjQ f R  W,NNL`"  B}f rsf< `f@ bfc!}2N!!y>0{&KHޔtA &P)$,ɶ!@G"AlAb"8t._ ALC AL5ka AIlFlQ{y!6SAJa ,rИ8>(ab$OA6Tޠ$@ES $f% lGr8 `hw<kna (fޠej"8Vf6 $alY,D @a `!88Dq?u%P" a!AnB!ACT "$X FbZyAd bUo aA[Z S>d r􉥡s` xB@ ; ̠`i" (@ ` `a נ ~HW ) \@@BN v @  ZR@ ͂w HI⽙@Z ր h.  Ȁ @ XnJ \w ao< @ @ۜ` L` ZB l| , F a)[z8G!xf_ b~l} .~~& fV'f4@|=jFO!!ާ8gP&}<4`gc#cFVBg `Ώ;@hOܰa .6ͣ'& "!n1@)!V]ٽ` F=]|ٽݽ)~@@! }0A #>9@!`-~. @N!|/>E^0|'p+ǀ#8kT~iHmxHldž}@lrJGalG?@6zݍ>>WDSaQF  :4B+VuCAܾz  C` 8:6~ 4p5 vo 4mq V""A { Z~x mG> ``^I_4@6%`N:^k\TE< 0 ; 4}sUtRna ma`;?y <0… :4x!4?.4<ؐl[`\A0h[:|<2ʕ,[|AhPx,pA;L(vkp^W:+eҴ8"ݑ@3O$$>O8F$T T]4$<3*xI(Rl@S #HMx"L$HqȰ!~8 j0ZPZmC327:2  #`Ҍ4|2a)[ @!##[H@!i1r tx 5TBa(j`AL 4B'4!‚P(YDq( 88##|AAP$عjto0*A",q!V$4RȨ ,@pmy5PY 6H9&PM6\1~Ц@0\3/5 C`4,6q0আPcMA"XM5BtZ#  8H ;Ac T 0 8r5Al5ج26|Y!T6cp$3(7,S 6/GP"MZ/|x4XM5p6yLh#47%l@CD `BA4 g#54`@J; CHy6s E03m+3ϵC+̈́xp3Ẍ16Y{VHDڣ q0ֈP BEPQ 5hAB^p4`(XYC'(V$`B M!n 2 0 2!w 4Ȣig/1ʏ3s-nP 0 w@ pif[l29vw luGo ;2cr` spC I jekio @  ] ڧ-@@ 2u0 Or 1' P'p &  4-p"2UVڨ٪ f3@R;tYp Z057vXz wA ?@K 0%Xc @{.0(њ  KCE_`.$ A@  n `CD\5)U G3S0 P0t ?~X< ັ.a3k3|@I P`i-sP PZ0L 37pOj&FA@ ? q eE<x}M?c P Ovh)ƚ2?Qv:0 r$a(` Mw`Λ{HZd {  -Z'cKoT6G ?LJ~dʖ:] `PBH7~ 0À[K ` p `\0S  7@yW-\ `Q*oqΐ!&6 oy>  0ɀ: >SS0p=S  C O l  3\+zvr/̏- b zc )I   7sS R z 3pW0 P.ؐ鬧 :MЊ\R7Ie0 0wLsI5˪PȆP͠; fDan ` LT y3P @͏! ˠ <(L k`M  CpCW Z \\4@M @ M)0z )}@ P dbJ02C [k o@͠|dP wP G1 p }PbC0 ːi9\9d qO.! f!m=aJcuiI<np }PBLp P%ƀ3M(ߤVc ^ ^0hFV+$n b^1Nh( n-Nh63>b3րqZ a(vlIh a1ae0Aa`g!>jf⏑uVkj.tv^nm^jn~Q| }^bl֕N>cn꧎ꩮ.Nn뷎빮.Nnǎɮ.Nn׎ A` ༮yU`AwMq` i~CpfЫ~DcNua ` Qw(sNp`͐ ׿(8xHZGP^w[0n/c0>P,r;π JCR9Gʩ4=?J)4/H'J ! J_`[p. P)~ q) }y|pR bRF!pWLT lKM/~t J\9Hsb^pT75ÑpoOp_VsA5 @āXX1oh/1z@@_+o&s!4,05ÕfdO֏_HIA/ó^A4\!„ NLDC ;$YI)UdeJPj$?I$'.dȲk[,a*? ?=SI"28∑\],1 "I%ёU%K:ZIq "GrbN#j萑8uSr AdIjϔ\e ,πm)s/ӈ O(9bV!W6Wlfp"H'bgD !HXuh D2&p`dIX'i{χsfMxr6ڵk e  kf7@(_Y#@)&h9"BpfD(#ikF lI!,h`F9E :7kE0`?g(^@a 0F aFX*# NB #&`(h7ĩxs&hD0Pͅ1tfވ!9)FxJi&R4 A Qs@hf!D&X"e~>fuYh?%ZljhXtiFi᪃;TiZѢ ahEg85C)Dcb9BbfT,V `)f<FEҖ!Ehwqy=.A`(9ERY!bٓXD]}CGe" 9dĭƖ#g _rlQ-]H%i.@`TH䠄VqF_X~-;h#A^ci}%kWbF5tS'>cc/ #OxH f v("X((h`?b(e B'_H! p ,X:H"fhvM|`F3DCpB"hPP?g~,X0y /ЁP`~(18s(jDBo$8E40\`$6h" @'IrB`fPZ[(1 8`-`(%,F`3` ˘'fHa\Aiv[5@/t5f,EROLX klJBZ)[  )`Fd[Aa\pF],Bj4?THE)#4ORy60!'4_qA@L&x%1 > pB\̡mz$O084 XA  ^ 1L@ ]H:&cKB+AZ8VT`peq%:'<3qn:J`ըA-s9!%0cFX0/"BOa p$&{G@"؀',wL5%6#&xۥwoK\40 cJ "4hs"x56@|HN^7|'4B! TA?.JH>i]lq2:sf,c@ JL@CЌf8[@a X6}hrP08>Z0kx;Zة86!F*;L̎ObۂgH2ghL$݂Wl_P8Wu[;6-{p&0epƣ]L$ªR`# 1w ?!: # wn*e(%@5@H60 4\@"blhf{m&_ϝu׮\Xte)c=Ȃ"~iBx75 Uk4 ȼ4۹.=\nQ>pEJ{U惘ycg8+񹨓I]#,,qg@A ԇܑ?KSP \X/PUjw{oknx%ڠc#+7z#h23 icx*6>L iU7 pkA?>C[;t;,;yC@%2$( H kH" $!+.ڀ؄3+I' :̸O2>"`hpjU XlВ"\pi" (ueWύ$&(=] ""=Ot(` P'(&py߅(p&8;P&P^'H"P^ՄHm^E^U(8Ѐ^ !`(x%E&P**!@^m#KT(M !`'!(|8UIX& ep'8Mv\"npHb%N橒-ljb` x0E(Blb%v*f$1>0Z%v10v"4*>4ր1:02b+֥IcA%=mcHyk P* tb@cr0bu@JN&0J"neSaX>YvYge_e_}efLe` hk Hfifjfk$X H> 8kgr.gs> d>xGcOCgzg{g}%HNQ`e|h.hVS7PH@,)d>hhXI1z.hh li%刮?h^i%u \n`va ꭘhc9,kHV"/OІd0C_PĆP`+(jjAk6QphYa_ր[eLeW`g: j#ji(l% h`TEWրj"Xhep5k% ̉#-av}ܢ - hi ghneSnb&Y`n.(GʶF$n (o^I8ibEJo QojpO>dzo W `ou oZ g_"qpVokHX+~ P;E3)RЗJ;"I(VZ7Vm>E!y. <8[%P0„)?9,`OPZЅSP)7cfB$OxH7[,PGHrQ c)F&F#([?0ŤF\ߐfeXua``>ּ[H/q?9HHt^V/qPzkVM ;hb2'p81XfA>0[ L.2UxThFL6qޛCxi"k78g gC XP!-`PdGy(1f@ SDblvAaS:l_hg(O5R "7O­ &pEHX܋X!(&oPj02W%8k8%8ȈP,&*"h?"M(l-( [J,'hQH|{"ÅJ0?(B磌-p35C#o7G"?@I (p+x2M`N?j_ gE .d`0?xHA %8seB/Z4D=JTbG!<6Ȇ %D5DE )BTP ,low2gXT0"Ǧitt  j&ŁE%Ehj<̙J4f7Ҥ-hD ȁj6HZ 7+Z)Px噪0r ǵf5V!JeP"mg' .|8Ə#O|9 J`9˂[-ՄŸВ V]hМ(x0 '$fXOxi.0a $м1]A!\H dĔS"50iEDXcuVZ"Li@,p*hxu 50Xa%cAd̐5@ # `>XL( 5c`32sYwt1 Dq0A 0@cEzI{iC@RqkJґ J,a4ADS!0(E@(ā))! "D3 er)b.!#EՁO"!4N,p(b(5)zɺދEulUL[ XnXx-źQP_dtHF0lA31h1Vd˨~tŅ 8E WH]O28~񼳅z@kwDI5.P I 5 4#~c1R{ٚ-\ Z$@bQvH-o̼U?9 l8`5!3Z]&0:X@WV  & $  -@ OFRʑ fC"~ M` P:Z$F3pU @DjAB X!я2IU,!,PP@YIZ9jpA`B@QX2^Lt-JW(ٚJ@'H `0jF+rp3$E`v8<Avk8RP dUY%̆ X|U¨`!:'HH n00dՐ(`"m،i8Ќg̡JPYHKA$ [5 U40$( 2>cV" ,,Zh]-lfH.P3) TkS' 6A jաr07 #tEDήj@MH1em ]$"Yq"+_>n!h݁!! Al(#BX39#8˵-Vtvg& ?8k T!Ӏ5a tnA`31o;5 6$h1 [hwBM5݌-J_r Z"R*Pj&qD)6- ASBxC&pDl1CC%75!h#NqJAq@ QB}`^14-*;bQ@2dC H C 's\x8EZdJDH 0@CJ LA58G #T3H@dRf@#HA0 hȎԤR !&jV8 H#`3\Aqe\f6`B5h $U 3h8ɥKd("C4E8H$ 圴%0f6e^^Y_Vr`n%gn`6xB)VdV%^\fogjEY9Z`津&LA6HmZ&a"o6gw.gf"T3Lcw(d4j{{rzȧ{ʉz'} %QJLTAz^VI,$rghB%@@A6D8{Bm* $hrTzP(~zU~VnEW(0ΧzzzwRxBB @48@`88`crp4AJ<hT%,\@i&HNHhLX4r)(hA\<X"\-DA` dH@UAj'/GdԌa$B4@d<N4HA`@叜N@** $Ar* L*D)i $6]@5d5<$<Z h'@CQ!HZ_5$A(Lüѫ b *x4~B@ @-"@W$F  \]bhq  , P L2x4DC tȀ),d$?l@ EObWh&T@ؐ٦$@f%@Kv l4@ R ,A44 0d@ @x30-4(0 IH:Z6C TDeг=3$pNHB)95l10|N$@!@ @5`@7FBċ (4kaF[q#2!FQdlR-FhjLÖi& &f:7x>`:A9Dc$FH*& 57J"`,>Ѝ;{ q+O XeZm`N#fhiA& 4 9YbFj!" 5 FćeFE%g)%腤kf0h(9\(T.-Iq񶲥]iB 0h|+." S/0Myz@A +x-iI]nr8>0 !a !R[3.G(`Ak#8pΰFlED;13ХDCU `L 4!8)EZĪ-A4g+-4,pk " !'A APEb0,a#C_dAt MF51A Blx(0F@5LNX I'q0YF FB+hH p DkT:SkbwG   c8!E40f$#] *`[H5TlF?B c!PTE9Xt6P9`QƍhrDzg+F5`:l.ҢHbd38jh3p)gH" y;/53O0(6fXmDyFhXa-fX-xiXY #p0`5JF"i!2PD7ҁDsQv .!!4hA> pF5Agcn'kD!!hmFw4P)5?aR/FC0aj;ep  pL0Q\{"אh@0Rb C&@+w.:#pC4 C0q_a @`zn}5t_C)qH y9 AAr?`c@ , `l\q@ d!@ jj"aA9 CA4)oz64@u`EAG60 \ h a\00\!2xjP@Y4>=C?ڞFrj! @ T-l30wDhh[ցԲыv-]dΖ*`Eµ~~mZuI]'U/i\ )#k)9 |0vINqG9>"pk5TY#;j^?'O Es@:˓u=8;An%'^wukX6mq` ?vx_ƃ|V&?gutV2(yӟWY  ?8 j@bŭy{^kD30FO|?ї~HbЈo>}:/'!sЂr?~?G,_!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,{)= H*\p!  \hpD3j,ItD%:qN H>ܬpNyW@7SO>\cO=ޏ !?l@&V#O8PA ʃ~3y= !C$1;SN5F(p?,q;3;ؿ# VSdިN9@FP7R`HpuC4A 1_AkcXG<228'1 07O .'8=c`:^@ ьoh $"yc5h a;alL r{׋G5臙##_4'LU|7ֱqx $!tOD 4;pH!FyR2 ?X2 x{;0ͣU<\Zǎ܇K?6` wc &$ġ-f}و~1,n؈k:USk8OJrl} ` 0]`=si؈59XG걫wliW0-.F+p(B")2hC Ќd$2 HoMk NzCL ؔ8v @|sȃFzоw|C82$P :;#X ?pvGbuZ9T[tA1|C÷8 4 GBu8L`T wh7H0G8aգH#X@ {/)Ar0vԃaJ!jj1t`KP,̈jgEA څD ; DBX#Ɂ  `Cr$aU l +4 0  =`dH"PG yC ؅;A wt#v78.:p@8( @cBHǍBp`F&  " n  87w \/'tOs6Lffa  @p]!g70R# #p"+ 6 p"oPH*xO@T%Qy+[p'@k R tZPvMU2Lt" ܐH;{/D m| P n*owp~\`0XVaNW1P-   UhԂ0@ipE-@Q3OJSTZpYWEc)1Y@Xf'0 ,Q .[a8vc   +o(0_00{=%1u$Eg*87艠h}B, ~T!WA0,A0c~΀qA`sj>. Uqp'¤0?1 p - pЎDwx3`fd3 aXw Ww`w Y+B &U 8Z|%T7#i%9 u  cpX1fԐ M9Ah|>؉Ǖa bQ~(8b$-" J@ Kxq3 ` B  B `:P % ݠpf'LQq;`m\ 0\0++031᠛[w]5թP ސ") 6)`p/qq8 "B݅ 4 I5jh^`يRʠe9Z%>$ٰ 2zC%l ޠ P@ T]\f߀ pY)@5R[SJ3xXvp]4 kʄ' 0Y%qPwdH  eN:1@  zP W1EP ( 3-j.W9ZZxAfsXPH*U1r=@~5 ~ P+ gz)]Pp QP< Թ 3 7KPpu U1P ᐣ {1 U 5 P F0)Gb8#A. ͡ -` `k ˋ"`  >зë% JF' 4|TXI[yb+f@XKak*#pk%`ҠP!y Ka' 'Y}۷EwMy0l.vp $4Q`XJPMP `LN,S8S@ )('°i l 71 l d@YA@ \L%"\m8@k0`p [ץDLw X@ W"!P @Y `URJ JVPƚU9Z%@ PM bՌnu` |5 # ȐEн1,94r@@~@`\0 ~% R\0KtFw6 !Ne[2Y@@8P0}P^@Z'1w& ɠ:@M U0,!# |f  C" 6;P|_ rJpVr qǁ0 ` %PM"pf{L<   $Fpu  @}֧M* =iMpӍZ7@8I>@߀ $` ! W z87%N'pMC"qPM9@-m`i4ӣb3HpM+Qp- K Aа}@{@b wbAUp0TM0#B̐0#H?\p>u @~ڐv ly OL0HQ>Q A n {6p)0 `1q&(YPP)ѐb`'nA Yݠ[moY P2P P~\pxp$V ; PP: qtv d]{ͅ , 5Pـ@]U ս PY` 0! . @` N 0 pjCL0 / ]P @ CPб#@̐F 00xM ( p #@}L<]O ` ` ^; Cɗa$؏ ~d\p%0 P *@, ۠a @, 2{ C@op PHoq p+rᆺ _P8`@A @CA :0$0|/B8R%C8VH l@@9 `.б(2!D8s`,$!AB\0A$N޾o)24ؠ@L r*!: p:V @t:(aAFZj֭Ow[삳mG& 81Gwrq8\za'|2J)ЙlHNzɨD3M5t uGpykp\O?\-eRruGA4RI'u"YB7Tz4TQGmSN#I5>7bpԁPw̩& d6X; iaGzaGRxRXiZjAa& &pG`aABW揧 4U>hEF?ҁ!ppCx䚊Hr|DqqƱr`!~ =FՀ \PBpCbR.7Pi<X0d` j PqCHNC4Ni@'h  N p-NP"`Ml HJQB\ a.PM%(jft1(rPyzī;q@) X R&bԔf<QjTKJ R*E9I9Fn`bPVCA7(D(@Z=0N;GJVwLJhF%.l 2 ՠ禼ӟ- fb#A ahcXG6 gxb";f,#o8Th ?l& ;7ˆ6D8- `|b vi8S< & ?A#J f 0.ڂ!>P9h-6d$ь:hښ L; Ў #8 )h Ϥ>H}ȣhF+諀 h<*vSAh-`"ɀ"0p44fH)1 `\h7 X!d)9@ (nϴr!`fآPlZԆG$PdhRD2!Li?C(a% 4T=ᄒ7\8p>я~c v:~8PĊ-xC\9n8\ݐ?_B8f:o|F@9p8CpDv[=8f-1}܃ۀ  uÁG=?0`pA%|[;,sF;ooA?@0t8*Qdc\&pHB8{<'g@G>AvCvGLZG?D3{=qϣj) wc D Ybj~G;{ki= ʐL;H5GюnʅB7!}  .DFϙ{l1lPvxXuPu(yhd8"؂,! hx~ȓX# |8Ѐ&|0@vhp[ `ۆ^}xjXzXvXዢciy { ~wBvi))`pkd)ypqh;ֈ?={pwP7bb3H}v}pM||f8 Ƞ@HXxdzCXpB-dw~@X#' Cz(A0f \s8XphHh:b1X}2dwo@>+'Q' ܇v aۇs؂ ~||w،Qjxp pr$[pBuK=$ext@?PEaePFm!w( (9E!@ fo(Ji#z[pO0rЂ[}tM].pʆuȇn(!HȇwhTYWJjx{ x,ilj]ph}o0J(i0M  Ȃu(oNpyDր$A]kPp3AGqqRxtP٢DQ92r@oxM)ޢGh8H؄mppۂz(lNhnfEjvz%0a!xW(|XgЃDP8F)r X8p8r8t3i~shKa)LsDGkyWӇs%pwN@nt;HrO`o؟xTw"p{t4qЇv8,Pq؇q`Xx,)x(8*kl؀Hع ʂ f؇wp8Pw  /p0ȆH0X 3oO-p8hwTsPH@xmniH(Y<&xC=p ܆h@"pP9@Op!QBahGxr` Ή+nTp02[( %x?xt(H x(;$8U^o,(HqLq({-pwhHΚ#5NmVKGk7j*lgk(t?xCmho8>}&(PAxqXx5Y*%.ڟ/PnӸ(>ʆԲpȒ(`ӂ`x|mp"Gנ*p`'0w٪Kz@'2x pdz؂dg&ϫ 4H,#0k嵁ȡma, s&@}xP˪AܔD`I%` yh1! M 8P| kvyo}ЂFH0&{n y8/Ƞq"\2c&G Ho1u™ i Ouh#]b{@:|<|0 PBۇ{}:j7.p|h:{>؀f="0XDl @oȒ-PMpdQDoKLSO0 zX Xz;thԐ#ezMbq|,v `Be^ pha.y ÄƬh,_؆3fs&@Pp ,Hv0?T`L08|8mJL`W І AL"rĨ^sv&a8'6ޖ~(%![;w{r jP7iww@h!yxqzhDzj@{pz ؀&;ix*W8lk,ɂkFdv_ʵ?%}$QvlUlS "xqPq7-hP{(jh Cp_e7`vmlHAYؔlX#aQ($ip :qxt-OȔxw77@o2xo$B[|Vyxxx@nC20,{%xV鉉ppAAH9q@,&qX;$@ox؆fg0xhp3Xvh@&8q7Z@8|v&Wo xEJhocz܂p& Sr8k/zdu&1GAEPΒ/icr9 HS%ۆ >Gupk=rv؂Dw mh,th}tLit2e!\o`m-\TM% eAili>q ~ݲa^;)`$rqin7;lPh9|% f=$}H7H9ˎFF=p byםՀxqvj(>GO⃽ u+9VOZ({# QћgQϜ 2ؐ7 6>trp,OhTEt:~4U47TVZqt_8H 聎>hsGK3;{sN7cNB@7SMAܴF M>@CBrPJ* 1TNpSO?aPIRM=% Tu8Gyn= %u`Y \05uֳE:AhWI@=x@O=$FPRQv[ ԙ=h250QMЌgrq$|3EZܳ $E9sJmtuSO6=P ZyzqtA)`)kO918pvpSr437hi:\k9I[jVn3xqÉM6 1*%P>hѡ!l GpI.r SO51MXjYjoC<`CDAGcOۄN5$mJ*L4$C+TcM;=i5AY 0Z6#Xg4t:aJU8:^W@Pod(O )fP D?| ?1h'ag `M.13 nc9SWj@ fTrP58J=@s,e5 pZ(IRW:@Izp:8D3T޵؂VaNfP}|C T pt x  t 9T5-A?@s#h%|@E|XM +}"|83K5 6\ u x8@" ؆@,L 9c=P_MR.xH5G$l ލ@phAA%ցx>u0Eq(6A8xBN,BYʢI ʺ iG6p'&J#EU7Y:qv%C7lsX# : 7gp?ܡ86Vр0yG6Pf츆*>CS-az7@ E[F9sC0Tpرk 08q nlv LC0'!%A&&(a.?!%0|_5 .8?ݤN`:J81˔xwBLz`̈́"h$ Jx"pbod M t\[ | sءWt1 2AqA7u u6Ђ" L8JYhG{jMAf %8a 3PΞ ACw3A %Z3}JPV/gxP(P ] E-nEظ㄁z@O7 DoT $xcMtRFDyMP\mmD?Vl_`QHH Nzm4,y |8C;A=C@̃3$ *Sԃ'hF̥8XRl ПVEU GID{H VtZi܀ H?aP^ )lyaT_ra<."#6#>"$F$> ;X`6C;8UID@8:`HB$]CO, $b>\!./N!./#11#$VbOT( @Dh0$7u`*8<d݃D2b#?#-3=4B$`3|:|[8M("-1S$"*!C5$T2p9`?JD:<>;Cqj tIʤΣVv"$zY%Z>>9H  9;3-2X@%xC:[Ȝ6%#cF&T#=LNIfn&?<)04@2Px$3 &tg>"D2&2q&qpjf(3 `aq6s"@@:@ :2u@ @I"LwuNs"gZ@4+e }@YN@LID@'M6c~cFx"'{ c8愦.#]Vj]0E@Aب_HBy8hM84)inYg}J>Le~jE"il@@X$m68I!d^C606)5?j 6H V 0T6H-4ܢzd>ᅊ鐚c @4#v@Ā4H)Z(`C3X&lB'<5`-&4@cj)*= 6@tpNE5@./8k򌪨ZVbz5LAv/^(#R(I4DBF2T0,@.\ܥ49z@ >bp+bW lA1&<4A]Fj$r"k-0<>B2hCA6)B%8@zFǂX(E#8C6r ƖkR4 p϶,K+?mm-(3hA>-0`xJdC(dmnmJ&V*hIقB#45-˪V>Th$H\\$*n?nᢆ*g.m~j_T.<.l6A >A6|ll/jABlJP/F鞮}.GT@3\ ʭ.t~/T.'LQkl o?_oP^ vlo*o>لDF6('HA6JÍ@m+w D1lg p+no/n+`!qoAp @4=Fq ?W?CqPq v0ߍj0.T2xCx8X( u~L6@bd`jm:r Y;<X d* V2A,GGomDHC%([Jhg!@$44!s4N0/+8դH &.OuJ`ş(x&,ztK#Salpo |4ZTLp07DC)@u@4yB5TBiCC 6,34;ѮE:#lS5+DA5Bc \U8$dCB@~{^n(,@f>̀"P"DM@P >sim3 ,@T5ܜ2+ɓXʧvyl4v>kh5B?53z_}қ@$PXYV`տA 8T1P@l߿\:2 )L`Ԛ#ؐEh,D 5Bņ/a~8_L4m漩3gA>YSh̙FieA 8=q Ih9 }AJhHACәa;B!)ӟu@jA\i~A2_Ϣmi`OwAZ1~&-_j&Bՙ]d]0L {R 8X``4ZsiRhf ſċ8lC$j >M{c] (gԴ1%L:(8*a jhξ a!*bkpAAn.;0tê$zb0DDj zehftdj˰"kA k l< l 3l (M49%iH%d-pӍ74T\*a E< %`yNIN ~.bF|IOy@#-&RBq3,H@ڱ-eS?m`fB\8%,.EQ' [o$WGb2?aݪZ*fQgs/h ?Jh$1-<"B?PIJ s,,Hh6qB?fB[)ް&f0f#V;Aφ- tޒ-8p)zlA2UTRFfHTWmOh&pAeFLi~ hdh 9!h& *Qvw^rް]8'^. X)*hl!*?p!#E~ %7\'?K-3l$GBp 駟Nj j0L!.`bFO yP>tɢ X+`bJf 4=h^X;ɚ)*۬h́pBaL T,+Y?0Ѹ  87@FN9D\D4ߎ8hnsKtK^ xCBEDca ( pvk@gbS)JX.Ƽ:@r6?i cHRKa>H};Knva~k" '8Vd`B'3w9~6 TEAYl; 0f aH&wkaR<`S2l׉y3>N91:.`ِDZr4#jN8H0݉}(8p qDF,H@ c$Ѐ#]Qyۉąi8lLzҀ-8GX?@hDAB& 8o e7 oC5PNH* RXP !@D) &"ܲJ`cO7Q6dtX Stc P `lC V@U`# !$L-:MrZDLWg=̆3k'T,`?) Xo8~j< dp - *J2P %˰ DqT! E@.  BjG*y.@ӌ12XY$ ͘4hi3|0J  F)Ԉ  f4~1D  B4T٭!B ˃2uQ`7`? Ȑ-cꭻ|BW)6A5@~U3;EPcx`p' #,0cT ҔƮrhIvEPF%`A fP`+P$9 ,f ERɘE8SXCC%"!Q ?(wk$8}PYcrUX#pfha3tv 0_, fTJ4dHpAOK(HiH fݕ$xqa 21J5]XC$njz]@\XO{ Z1[2A9Dӈ\CЌj YXQ_UY409(B͸,i8 a,pYOnrsַW lCe6a x7$13ܦs7j `5\6 :4AS5wx&`A֜NCw$z$!6Q Ea)f: 2ÎRRP$(`3mh pF6 x5!D]K2!d4(tLհEmuha85A77%ɪĂA`C0&! fL.nvP~aiA&VK"P:ꐈPa%6g&a ``, ('.` Ԁ` l& ѸPB &i%  ` x/.<4 |!\"Z W Aa _Z` ` .4@ ߪ^  P@ T& 0Ԡ ZÂ`a )``VvA ~H)8 A JAl% Ơ(` B` cC / c @ )2 V f0Q1x&P͔V[@!@WM W5 3n>2 P`^{Lc"a_\ s!0-h!]L'ΐ2b@ wUT_Ua_>C fG aLev01v@ ̠jJ ~.b07 :" cCUEWKU'*B,efuh27<$u 6h6TV0!FM j Nz'vdhq7-kkEYoTA6xva?6sn!tvoQWGU`Ԧ@ʶx#Z$*) hXr"JF vk]8@J8lky %xx[ qZKx#x{g~z"l|5, G\ &W@!vAm?xU4LnsS=/5m |!B A&@  RS'@ `@,"@xa"Q#t@ ^q-`a!jbk%U l ~@zBT8 lQ>( a}s Yb \ /&.r`` `0`kKě+A u'aBA kB kL"a Hlb!Հn!R"@H$E aA@W?ZA DhH9LjCɜbVp~9^%"B :% (2֘sAO_⺘@I:#$bm5b@a=,  DHB৭C #>@ ` <1 `mHp@ 6aR J"A~A`h%/P;N:AS{ e+A"azZ%a|N'0``N' ZaN N .[@ 5l` !,ݠƃa ij fZ`ր@#hAN@ ` $B@5w怵Ѥ*#W 0V Rc <@1Za9a""A.@4u) *rI@ `<@NǨht V`p{c+%CՊ2A 8 +W VpT#J  0{ R1'7Gs< @'""M Tf6?wǿs:3Ee|~ȥR 8`&9zf ꊳH˳0l/a@">!Prє(d9S@`<>!}آ@4f ⇼y` A  6x.:&Z@D!&g `'=?Ʒ/qFJ"&CWٻ*8` f%TNۡG AQ ~g@A `RBmCq6ߩ bR Gd& %2;">`)A 6Rkqϻ#H  @ 6!)`taA 0>B[!dng'G$it0ZJ'L!ĉ&xDZ"r(*$npё MQnt%#<{"05'6B  yVZ3֭\z 6رdϢ 5QemJzf9' &Q ڛ5@ilȅ `c䁧:6gKU!F5hf!\yFKIĉ"L[7Φ]Ӗ-xk 5ǁDhFPVCJIr\![|TpVAAA~1Zu{ Al=#3+C%&B 0u@(DJ! ,FwRJn` WEV?5Z>1 5jjd eUieHc6ވc|m%\t a3[ŗ_:D3vLligOM(8@";`ҡ=t]Uj5)4sWӥizycâ ZrȍI5J)/@d2Gx}\ MwzU+DH/< dn D3^\2CTTSIL4$L[ 1  >$`M|$_ SlQ[Vq , h5qnI@ ! gdM~HhS>H#HA2Ӛ$ `(kόSByHAhF|'AZ0M%NZf :PQD(,b3@S#10eACM(?ȽMAZІa|uF@0`9pcw# E5Aa &A%jw)F&/P#!3NĀ jK B kTtI x v7Ai$CAdcr2" F 0q͕ہT!.0Y,X>$J­N@a9*5V1::t!-ƫ lb"cL3DV0QruY ^yf.jjxi0F%0҈v8̀ $PP:xB4_V [@OchAFfbzgъ50Dd?{IɃmVKg"zyBteH-M.yCNs-kBÊ5VEhbiKīm75╈΂_}(Sod #M2ZV Fr|Z/VW=R "[0/ڕ A Cr+_/I-M(KGԟ a8س>Ϗo?"@ p .D6@(@R.0UP@23F{(H nTX!(#H%-7 Q\\Cr=q}&(3H50GPG3v1hCHEhˇK}3vD DuP @Zh[ܢC9ytTFf[m@20Z@!cɗ#Oq% 8uFV'ĆneN` 0g]BNS0%7yzx#N` PWRЀf? ׀ <}8xRfs"蒉 ?"[a Zg7H#  ` X61ʘ-8 $ p HqYDo@>k&QgI0sp}rsIq!Gx4-qV#0GPxf p x"` " ̠ 3G2-pQ'#5 cm6C @]X6]ēW@fY Sq8XTH2(#Ϣ аOh3i}Ŗn9],ə @ 45vi$әJ83&0b?RDHN)F\Yɛ1P) Ey^A$ #I#90` Ѐ7Ĺ6Uyiz / )xH$Ȗр o83P_5' 2`<&$P8[ 4301` 5<.&f0&r -:._F!0ТhA0ClP RmQ.˿+g $2=O kSokq0+`-fEP |0F հ fPd+P Vbi* 9PnJw l@ p} Ȼp =ĥ?~Π 8JA03@ V v> + PʧG0 ~-: NPJ  LG02ـ@54Iƈ|˒V a8 ,IƖs?U J;!1@ \ {gPMMs6q PjV@ Հ*4s?`ɥT/r@/J!+& Հ#~+g%N(;  MJ˔Lyt wXY; @`b`, @:j   R8 Ҁ$%2gt  @!?qа @Q@P<0@2P Ԁ,!|]b4 !;|mW \c~:Y SPo А!Xّ@ M;Os $!gPWVEt7>weVP@~X p ;@<I#G ,tIS' y|øz-;/`M!`}zCau=  X  p\ pGAJ@ ΀ 00 @~b}@ @a}#P } q*̱L C0 pfG  0; 2@Zm#|<םm+Mj~&` hذ E紓@P Gll8x .; Z` Q2JC H\\D}ë ,4[&p0 I  FaSwIJF`fp l k\Kse0 ' ?.3`Eq}(^=˃mu=,ej#/⠄1b+[,-Tltf2(q4(*f0f%j JPC8!>8Pd"W1j"X)*"=Jh(h>l0?ab ,W a$ +0 CՀSqݞXf)79ŰZf"pCGdGjW T9™fXW#mzҨ%b2T)f59 P:ލ FX! Պfi/ZcM|S1/L2x`[#дfHEd0<umn3gb-7ꬻ5\Q e;8/UY>_"Opl-R/rB )C 9U:SQpN$GZF!7#ddH콠hN3 th$9<`vJ&Xi]*F\nz{K 0AjLrhH)Sf2=PR$e Ҁ8@΀&p"3>6Ɛ7o;UoVj&Vwu5d<`$tI6Q ?`M,е !0 @IWźtMF3Ѐqdk$и^/EFH%q/N1p( @hb1E{~F$r.zq{4! hhS-`ָ(pke="qxFE<PTf,`0ȖR  x3aM`Q0[4ьPGpL5$@ MVGLHB6(h{"vVĊLh윐Ӥu#J0F?J\.&8 .t-4Tw։ 35Tq,AR4 C pI jqb9"CؓP i@ #߲M`>8B DV)9db9, 2N=bg|5hqȬ8 ā̟͹ S|*i Adp4a\@kF0*GPm"]ME9%E0̘LÃqcFh\8&Nvj'ьJ@xFcdO?D+  Jb!E5TOh3p !T- [/e4BX*Ƈ#D5 HBB)Lqp_X% te0CDԸ n( - *: 'oUD MͲ&+b9 ЌkbG _\7 ldC&M`q`_6Ae@bh5ɷ!4! g\#x'F-EJ1E@D1 U4AXЁ0 s8pFJ0H{  h<"\4̨F6! Z3Jp~alLCRA"j$J0FA\K$dB)j . 0玁$<3]_S伸 @4>PqK$3%jpV0O`o0 'Xөt S kԺ@cb LO  o~)tkCpzԟ~ `"0%d `Bj;I ;R.<(@.X#4 D <7 €A",NzX <@`% .%` `˂"X["#'8-PC &0 (, 1a'X@& 04 $t;2P4d 3#S-x ̂!X ?& K%2Ӎ;&x[-sCP`DAD;| h@l &2 h@B .Dt9 Gdpy%DRtآ@ࡿ `ZO\V2dCE$Xly [dF:JJ?TݐE%\DaԿQi&be PL[tEK%Nl? Dl qTFf[\L DmT[8>G(@Ȁ2ܵ$4҂#HHN\HH&>t) d3ǵnjI,IJ-ـ9e ;08;XUI`1X ]cesaU70}ɥOePpML`P"յ+0G? B Fʙ'7Ѳ؄HXUW7H~W%CpICx (FJp? @޸E?)!`CuPM}JPY@)Udqs6(W+` jhhc`Zg[x:*`kD j@9lR8 1˂_ȯg՟Q%amh` m;f#g8-ϞX&g0`(O!0lP5ZhعC:g XP[i- Xr;cPRji`Y!ȴ`a JlYBԠ_%d]h]8PSm$i>a0(,hgu[&@ڞh3xj(.p,1*%_E= SC0(X 9&8+p;`TAa(j(y*gD_eP`P]p`ʉ+-@唄lpA % Exl`2fD(pH10Pe kbş^dPg>R&e?0 <0H*.-Q삀(x[`;)F lH2ۑ^2͂`f$8ȆOXsEPX`xVt4 &O'XPD9& 8 HE0dP5 `H_HGQ` #khp:G15fh(hX䰅8Hd&_."WONfĆblUE&賤C]e\(h Q.X $(@ Pf̱ ;.aЀ8ІFPg 1h` H iXǐ]&H Д18h0.Վ#.A+i0(;!i7܀(eFN(i# DiB;;+)T'5MDM F07o6Seiрj.e6`FgZ!b kf8jVu!HXtEot^{tkG`X"U6l`1pv-u%G6b8`pRQ]gQ^Z_pjCA@A ,8Y)b΍j7 j8 ?HxPj8*sr 47R&Q0MUDx|_hl7ngt>|tG"XaI87~Vx2MhEPmyr aF6Fg٠w$85;8Dx8u-heXͯ7 hjri*uPDIqw|OEL&M9'r A?ٯ&;8_<$@C905(r$ɒ&!*ddB!Hk& $(کř!I5M D- 4va-ӠzrU% jQYre28kvD@CÂ?0)QZJ86d EDA{9fͤPX̘lPOQ!i0YA!|6ΟC7VzqbWGGfXBJL9pСn&~sBeIAM3N,PD 5sTE~A sa7Z3DMWIqUD5[l BT6TCx$X%t$9Y%3DaL5PXB0#gEZkXWtDM4`I5Psp!$[-gzZbt!b HGEgH:w!TL2]E cr@B ``J  eKH(@%8Ƞ6̐ciFk\z4?`сU $4yINܟZW]7]Jl1G#CX)eH.Q 4.3,tPHccbӾ5˜ (!ym>QQاk3I(3O~R v@md&) ` Ɠq Gh4< MX&?XZx 1f HlP,`'`f\yƟ pAF$21$!`B3*Aŀ 02T($$8$=> G_I ?BC %JPD1񨩍Ͱ.WM" `s4a "SAֱ$$C1ʟBz\OR. `JAdHQ1ԩYUy P `H]*Y+Up LLO[S EPjX?ā `h3Huru+R7ƪdtj#d j$H٫V?)eUdTAT{s kiWV|Jj{`Gmfڙak[V ʀl> ׵-sK]|9ry[ -{7}1J/80z%L/C8], aP4l1 8"1K,a p3h(&1kl9y4J,ɱ92l#1A\ R2lhZB/9-gz4 5wm.܀(9yih8( @"qY$8 d .TϚ%t4 rS?Oh6kxz3e"̀P`Dh|~Mfu_Uq@sKrHH6Q[BYkxƾon0E?@`,A$iB1oԵ.\啝Htnf͢gsD<DH'y.ejZ ^jrGMjd6"ˢg۞*BI4PA>?*=x_y֋^Y|ӣ 5{z}tO ~NA&xgDmiY4p, ȀA$耬1Ƣ&,9h&әA\$?K L t uZ(,ATs@ * @v PX,AB  *?!, ZZA.|` $ HߍџAA@Ou4\58&}853H?@_P|Y541$Jb%̹Eaq h*P I@-@)R0 3p5D1 LR#XC6`6_0Fh.@ 5 |9F#A|HB3L5L-AP\4Y5T]&5D. 4 76A\,6\C``C6d6`dT@$h$"Z4Q `!$/U)X- |#4/B{4@0,=N!b9 O.CPJ#5=|R0l2-2D58$F.44-1NMˌc0nA5@D(A3DQ̪@---,F+^hXn@* >Q`VD$@d#Dkl.yExn\MT< A)4T 4`,ښ&3@.*́3H\e/cl00pj$G:Jښm֭3onK@(,4A͙)DfCA5\@d5Clx0i#$sp ),14P(K cΚr pNI-4Bx/@3(sqKx1"o1AϱpW._ #0]{\}I$$O2@%h9{A p n*2I@)@CquL<-Z]I P?@ x(sSi33oJRs5m6qҫ6C 3:HH@6.N Wl3NQ@TB5ƪJEL M($t84X+:4A0@+0w!P'ho&Du\@,C54EƢ40H@XAL3&4SN~c+6 u~$&&mB\3BA3&{uE ,0C(4nD#$9(43;>??6PcHjL [].P6X(AS@ LA%<AX`B GD A62 t@/~*' 5~+؇@\ 34!8gE0L7ZM3l\2}xOA5ګUHvp7ʸ--jX0Ę4hհU3Ȇ (Hάaf A)Ns XXDl3=^횖[~AI$hת5;KG0Q6{ZeV[vlXcɖ5{gU-_:m\(_ZmAÉ"M8QDZ}0褉"^j -AbDҺ=MqIEa?(P鐖<9a}d`#(d&I\dᄒ'0&,ѤI'RB Nȫ\F˜H ӕbC b^;Bh-䊋  x" BЊvpBkl!+4 U\]lq3_zik?hڀ"+ J񺴈<$xH%{#@G3IHx:ˊ/k$)6QNk&~D6K3)$MFp= Ӂ3*Ҹ*_\uݵO`yғ_WMV`-VX) \>dr=w,_vYtuU+dy2Ƭ]ev &Ѐ 0Ȣ_xoކ~%YQiBʊ2EU^]&Jb,:~97X碍>'Y!FRΊYȣhD"i>gC ;F" Ǿ^.8h`rhrGKƸao/:^4χ/Oz@|]m5Kt AbcKijx!/KTf}^FDy&CY.–gIpwJh 1aB?!5! y$W0i@ba2̚a3oJӠlā4 ,_Af x"ƕA @`@e3rvT:?D+ 040,* vDf@()d!d`!iՒZÂh%ޑYM􃬢،iLaAs!X.a8b8F2ÌِC:)|n:"1 5g*w*36$YS|IRdQ@Ӏ1 6 @SJ3!́ɐ5[@5u>|qGRa7mͮhԀ3y 3h@odRIn6"Ap3+TD Mw JO E`ДXNg>|?9QkfE%9s ׈ t001HB$ z~`,tq Idq/A Q0BiX^df?`)%#E)8(?60&0B'$qh(L|"!MO V8'! ϾAw`4> c@!=Td`Ӏ#(p#qנ4N=|XdեuY!l ?rJıX M8*A0aNX=CBCQ(B iPc 'haLõ¶X0NZp0 بD2 V`w"l f[(F&̚r , -J0lD;hDbթkТ ><?DfP0eXc?h8eD),Xb\2P7,h@1<8~Q `A3 2Vo)HXY8 X%F)S' YKBgQ4q[ hW&zp\O?A K#(A*0 $l] Q 'M(cp@ "! OHb t+ 5J! &8d1T oneDv@ND䀠.@ U<FTD!J ePV+@k@ ( z )Bp*` $X !1ԕ1`84,}%y0nVo%O\c!Ƞ)`X@ ]- R!_mJ2@bU"2/`\a !`b31ZıtcG3M״MC"Aղ@ed  G|`tF`Q>Q%` @ATAw|HsjETM" "e BA(A !  r`H V"a4@quٜ"@IARrma]eh6 @h  Tp)Z8FA4`0 x*; x/7L%ƹx,jG4+B lG &C&/@͡2 !\/ ΔABjٜZ`Ye Ө!?2!zl H o_N@*>4qw:` yG7 4,8@@0vm "̸o4@7,>7#cN@T"zCZ0@ggyvpt@B"t`@` @}_\>`D 'u੣8<``a `@J\@@:e*k6 J@Aހ=`a #~ (lg wG;I~ 6aFUAZZD Bj!  Pvcn | R!8AH| !j%T2"B  `܋ )Ja8`~ N&1͈~! ✯F0}78۷̢aZA@ n R#(BCTè@˟3Hu`  N/!Ll+: z`#8 $*!κ=@Ae> P@ԓ>, X#x{!Ճ#T;y|w%>Uլ >}bB$! ➦?| D>! :8 aN0f!#(s`?:J!!|c J 2"0&70Zr* (~0:Ka :4,maF@%( Na HA>tpPP~Z4H(p":U(ɡO*Ң -Ƥ0-88I>A2CH*]ʴӧPJvp"ЁhDyU (~pmAx+T!BZ($UV4E[VrmbСڸxg5a:00kA'zfs`uݯHީ v, `77_ v,*JӫwzM3弞wf(&i%ـyE}%8}L9XUhf_EaR"Ea#*u8bAhti`rITJ)DilTx`6#\v` HADPb^Wim-ix0@R 4F# $喆.E狉6裐Nu,M4lWhpꨤ(!B 10ojHc_J뭸z$j+戙kO͊8u.+SlB^liJmmBhڨ(B.j [ڊԸ>/!7p{',1KCܰ=.llnx/˱n'˰s8 <-9W溜kpt'ÜζMP6 4D/}\[&Hs^GrvD"l6.[&}s^6لWtM2ރCta]yݒ,{w.a֒í驫N;҆}cӽ{G NcJ;؉vn<ޖC}3N|I_~ck.>܏; >@/3#_ۇ}g|^mt+{U_?y֗ |SI>oy ?덎0xmv3^A.pK!f(tG*Oe(~k(<=jqS7'GJsq#ŕC:H HF&K+4E0FR}$"A" ~]ĴRh)$b2OK^MgѴdg6:~-90]0<'{(K1v$4(ܹL'+>ڱ|f99wBqb;MhxdBr>O|p$=Y1 󣒣%ЧVvj|G9)Qsz-Bvix\NzҞ-d<ȓ7^W:p:i7ȃaup(6ǀ:%C4O<$dc.4׬7p='l=s7ш.V"H>;쓏J% ۠KM=L@88A6r^#a" qvBPL=B( |e7$1@#u;pjm q ƒkD!, p悬23up95 4]As~MQ  2|rʂKmhyR/ @c<M04bl;ESAMP#(҈"[@l 4|#|ꀒ" xH @IzF ̰s0|$س1(3\Fs\@~J@рHa5ڡ$P0HHu< B [)H@?4oNB1) 4( `(يG s(bx"}KR1{2 G(x9 ^+AJ~I{#)=N#<||:h@ X>>@|4E3l  [PLR]+z@E[pR pq7 Lр* (TbNZb0+LM p`-VgAbُ\ iu9 @%P 0p.͙#IT(Zp9cX1  0'>i)W/Ӑ  jNcYjm١%!jA&u(ڗ\0ТZJCC pi HbJzn א ڀ @XT%&Պ9Sy:X [0,ay-Ω|*s7AqDRpjPa瀋cjЈPV|BJ@ W@o ISYJʪ]_I|:hl󠫼JڜX&+*JsP [ `< 0 WhP | 0!0c:W4\g @` + N7AhG@s zzo"B %P  %_([[y70/+Pp}q'U?%=ʕ2 z|MkZl ri:w׵_{7  J SEPqP8s΀ 3!`rA.P100QvV۠@`@ FH 6 ?\N `Tgu @ U p Y0@C@`P"Qspd`Kq'{A @7:Yzw[M;{5w{rx  {t*6 3' 6!L+ x +REpJ~| .а q H'!ޠKqt\0s'  8 1!o@`5@ᙯ/L10@ÒV9|;Ǡ 0G p` Ƞ V"\ɜ-.vp8pP picN/YbZԀ㍛",FPfϘ1>UTU^ŚU* ~Mb_EZ 6u1f p^i& V*z)g0)E5UUW;pi$cCZنXy AV6X=/0zGuQ|7 ZkT 2'|QuGzI"Kleݪpšrؙ'}'n(]+*tܱ|qǜj@ 'xUp Q٦ni"d*8n+"X@oƹ6x@ H ŜFzR 9h 8 NMjj&Ӄ (+ xT^E˂enmAΊo.i`U(4 "0:O%("(PBn18toI]\opo.f x‰(8wTA!"N(M%|A8Рq '" mN\v~xagq٧ |=|d7#=9`zJ3Á#pZ yQ1@3q|#P4[`?Ǐo$Z A 0iHҖmݘ<1 [a}<'@( P$F0G751+A=aEAj6ɍ!5x#<|p.lcXPҁshYoa@a:Iq1l+9ꡏeԓ/o~{".9#kܜ+BA5b r$#~eg\PYEbÑEn4@tXXqcT$(8ѱ RȆ6J;f\[q,Xl5"DK*0BLcjgJ>axb3φ }s*#FD{4݅w&`O! |@q?BVePqb(g G=m&-o`1ؙqcz*A dU18pcMI ?ޟxG՛uD"m`x fb_֑!F< Sk{G6 npo(Xm(9"3)q ؀lpK#%pqQI%hGI?:zhp+s# *uu]u XF؆phzhpo6qoxh:r!oz h~PoqfPІy?;37 @up up? nƀ|xh<Pwpv:w!0s;&wMj؇/CpxHHj1 ЀCsntwp$s Lnꫛc!+(</tt 8sv`Ăm(Lt;'wX NP|PD'td:KJ)P%:@H#`$6hGx*Yk"`,+)tc xZx\-Hp@lTVpr o=%h +| h&tL rЇS,H+'(P59w@zX ytȂ5Q X&{Ja%L0wn.0 njf؇Q 8%BRLʆ{VCI,t ؂up`iUp/⌕xX Rpy$ wκ,lmi>dxU*tyA3YxqĄO0]{ XYzxިhwЇmp xd(Ȏ:s2t <-jPian@hTjnQzx "$TȉȇlkHlXҪ 8nȇvhV-R hQ mp܆9 [Cz=PsB(w %zCH{'ɀH$ʙK| P@BP}H!LLx;%*ڽ 8KeH؂@\ hIXЇxXc@"z ҵP-R~oUVVVUeU]eA,j&8~Їw@!(f=}fH؆& f wȇOPt~u,wxgxqphoHPDXJjh&)svPނH@ Yl|@,r`"v⾡?A@ 'tv؂Xx".'UL+s}Ն.5RVzt(sq?iE 9zqs`sNkmn`8q { x2Q(LKzPNO6.U]Rnf..ܽݍeZvjeJl({ )oxv"q{`3k x?^\njnoxny XXцl(yLb4? Xˆ lhul=8] ?r.5Zx2t= t8sӓ 4RSq:uP M:gp9s8F.HD^u@2cw?DaH5*.CO"wx"ć(0>(B2D̝{c u]Upj.1'R3wllW|s9_-CUx#X/tXoƆp]up,xt#eu;q|Hnuv&uy޾ m~phs({>p{(!h>13r ~f (dλH7Xer/E𴺒p-Ѧ'9wi)|Ioif0$n9`x5&A@X  Pz W(Pz8}h7Ϟ9, 1R.;4c\ 6>v0PKwdF¿ 0`P "@O(qA.# Eq6̳ =6FDT64S+Ac}hOΰ]43NY4_pUJꡎtCmv#Ё:α|cPǐb٣8nx`+9qtzk8@1}! ĺ%xCy8;#=5x@#zkF.0g >*eRDsC:H EC`=!Hnd<졏)3pͣ:x [a7pFu L>q`)԰>{$d܁s]QG?{ZAэCD2yڸG=H|pG:TN K q-xܣrbJ:%=h !&pd`=ғ#pị/hr_!m0AW@3{xdAX>Ъm6a@ vw8VʓG;@iQupAı 9@5u8h#HH`cF+n8݀QڎsDj Xxh0qPF )T5;AL Qњl %C91r@(36> 88XpY@Qg K::aBroü5<*$_vSa  @pܶ8ã@` 5lXDT84~(3UwcH:!(lp1'P8XAC:Qfq >!@W`\a< 5>B\p߰F@(xqp{`;H(JprsG? +h8bL2`ƽ6nž/{R3loFFj4@8b ܸEE7 jȁ6v*]n4 䡎c_873(o<0"GPVX 9oH We ՟N$`^;@P,@n<lZ@nu sEuiCuQ@@9w&7X@ >v tRKL UJ@z Rr$!p@Sˈ<<(:׿XhߺVP]mDU]A<\ @ eE\Trb l fݖ`x|`^` `Ҭ[l` ~]F$7;du$ t> M0:L@2<0YG8h@5C<8X%r@5)1=.h."06 _01#2&%@78l HdC=Rh A9`U5؀<1:( :€BX2c/<#>ϙ ?#@p8_7tC7<\;LCQhC8CP:];`Qóȃ7@. "֣ᩤK*0L$M2b,&LdU4$7;DB u@$O$.$#dJUJL^Vn%Wn@8AC9Hx@!q;6 C"tC9m!r%/Ο_fV(;F b.=V@ @ x7E Ѐ !V`@dF @!%cJĭf` @ k֦m"b,^b^bVfJ&omj g` q&r.'s6sr/=*t#08n卟V8x[hw'DmctE%"9@]=JM`u>bO4{##4C9hLUBpC:!(V5#M,$g>hI48Cb d C4,{NvYKExC8(/j@9kUmf>V&!>Tf d>hC !G0C_=؂V#8"EW0z"jeA:#N8H~Uq8?QWAlqM>4 r2>exG *{Xv_7āi7@5C<<=,3=A$'C?|NT'VC[7`nBG}@i0??h ܗDCtgGX}=`(8yAXb |dC-7ÁLT!vg_w_#Bxqoj0\:<&`C;lDP{):;x+H(8-.nt5^8h (B#( X8C;L $B"tc`!4&\C:C<`QD=ă7?|;8(a"G>10M6Ā` A35T 5[[߭LB3$7pD<(58PB"Ph$3HC8nC44%dN40l$%p( CAf 4B%H4Å`Bn6L7%5ó|8*a(pBC2hk4AC2>XC d4Aowx:U:w(?8%!%C iz8C-\g 6.@^ucЀ6:A%0P';tA_#D9?,97<@V@7(489#6|.K˺7ykrw7D'­$$@yO T!Fd8_0+q"tE _YC߇ xW܍ <@߆d%^ضϝ& ^;#aKPz u o@y( C mY7^}XຄN}to޴ P/57lƈ bNgeFX{H a( wF v㈥c4̀bHC|IŇ1$O z ! dzxX y٦-t.j-7?7s@BG9r`qQ'Wa-|iv|ɇ~$GuPzǛl{H?U,Y#lT 'Xq"I4qJ /aA}ZHF$%HY'Ǒ2*ݹҡ &S@kAJ8`ű=k]]VE=.k^Ll2wIh|ahZr@ oޑ{7 ޥ|p[@ypIn96!cy-glF2(mIm~)!]I@Awe@pbt'|ǝIFV½x1#ߥ2޸(,b'Apܩ'}ys!Tʕ>fxSv0&2@}d ֎@YmEY >G5EIPyy$#28D)%qvPX&7wsC.qa b+r; )J1͠㨈`V6am5 g?Oj ".×cu܃NqD O/]V0QAyR_?Pyؔ$H>Gv<!@AoSa}: =DE/  *8G:€HCx$6K!>1hwIvҐ8fLA{ 6a&Q!ɐ<CWL-|1Xa(Eɐp̹@ QME*3A=A"@.EOaѴ rf8@DXD gE: o``JPm!4PANa aODit8!yxZ@l*@K΁~!f~kdiΪ 4F  B) 3 r\EP<3 0e'83` D8/( 4@ 4l)Bf@ `%:@ B (s<dz 5FZ`: `0m"6= //Fe3v$t3S<ɳ;k^8EF`BmO86ASk6`3O2Z g4,3"x S/F;w34 igj"(n1uH;^""`* 6@J #`4(`ר8K);vԋ2*"*zb("M/洠oJ!1P 6/.5&tQOO *LH+(u"40P%I9N-ULQ4A" V'AB#;0՗XIX%sYYYZZZeS`[5\u\ցa \\U2]u^^[UY5R__D !0)`5^6b_%bu_Y)b9cT@NA`<\!ve]Hcee^0[ucg6gVt`ޡb@tvY[6iWVߘi 'ijݕN@́DҁVIul%j6gfmmU* .Nni[a}oevbZWpL@l1#be+_5&w՗Lo2\6s7rp3 ` S's'wvui 3 7[mqL2A vseSsDSv mA@ vizwIUI&`{/Ԥs/y%\5tGi14@ yZ Pf^D (o{#uu: p`6lmv\ Z tA Fb_ 8O]@h7uZ` >mV3"Cc] U0!0a t ywoYhw}w)bJ u׋T%} xĚ`f %j~.wCZ9@8όX؋6L[YE؏Xcc^n`$y5`T N!2V>9YZ]Wu+KWc9xj7, ڎ9ED@a ma mNԂ'6@eH0Z _,)BXNJ`nӠcד[J`z36DD.`U(CEzGR?` `lZA4"Zvou֘ C/%(`>BDzFD>ĚD@/ZYȫpXE pft >t*"z {yBy鬿z5{"bL {7֚=ۗ@Ě!:I  !µ# "Zoø!"@ Av"@.ؗJ@ h\V "0`)@ RyR4>!\f%P0A`y`a `A` ™ %aai1 A<@7sI  NX  @l?@`#`!^`H\Bi2oAaT9 !@+" 'iYY P!'b`< D`-`-"B &`)"*A35c9!6I Jʲ@A' 8=bC ͪ9߂9RC>6 # 3$?| %JbTa ZBL <;8MQ@g PSr QR>4J1Y@aG%hs&4H8(ce95gkF$ "E=5 V}vhCK)1[MB0K%EsK h Υ$)yK|?ãa>˛?>}w ~ Mi𒂏MPA%.QC)،@F5THS HD$.,PD5MH]q6=c[ -JD0 nHRip0(q$…qUWE"kxE]n5h&{ni՜ҁh$qК@A<~Ӕ'|/QYXsJYlBR0\'p.9E6$ ,?,E1k+(|ݗ?XK0C.詁 xlV3ő$1̠5@'Чy/ +PM7:U#?lь4B ,LRkE͔XC嗷7\u݅&_k~P 0ҩ@t@Ӈ*IKKL 41)Dl=jpaD`sE15A塚2n w"ׇk~ A3`4l}jgf-N4Y-6pD5` >Bs04S"pL 5d{6`c$s_U:x-{l&K!0q# "+J 3\L ٌ;8l&H;"xW}a]*F0@XÄ5 p`|Hrm$=9oȢ ,椃mqa3!9hlECitH5M#vLXh#(aJ`!IbCƼ*^DYz6"I׽,;zd>lpF5;kPCTl<&@N`.LTc Pۼ,Pr<ڊEaX6, Ba)j BVj|B,P f` b-I"#``f>  D& 8psIycd,42a[]072̑f#HDi@ 0`&(KZPWP&'0VЍ)K9ʔ4n(e[EKA]w.T,2A# 8M^fCZQ0=AN4jL_;J"6,@N YgbNQx_ԘK#zv~.gTS G94̟[ (I i ,iТJh \M4T/%6$1hhB;VYk_ ;B57,Yt,HcȽ5 P 3O.P`LZ _{# (Ҡ OYp m Q 1KU kQbD``~P >K015 : q p wU2xzUZWKG?c[!y,SpLpY/o|-j?P]0G[|,nc x2p^ ̡(qjqsC!PpM@~0p-|P2j0=s` &NpR@ZkDd@/Cp1 q7wI0 w2ȋwRpYSp p SVȐ*5zaXOzRz aԤ!A YWJё'] :qI1i= BKsbɔM20|.kSIB6_9ZL4UGZLi0'[ d9ِybtT#2[=wᘿ2pR '-)7)oԘZFQҚ'Q8}P (7ɛ雿 0M װ P )IM` (X)b)IiD @ " %tmsi9P a&jU96[*JdVgYbJ |Sqӡ@*U''  $J ư! +q D@'O ɐ uIƣ<&Q 0?{ ZyV p"R `uTřeJ!Tr@ 4>-z 9R` |@U}_s 9W!? ij96A !\\Gx$Fp?0 M`szT[w o0 c] da*ʩYJZu2-4@ 7$6)0J*ZҀ z S:, y[Щ+0D)/k [2"#r)R'+P z bjq(p {/+W~ 9 70 [ZAT:+kn-zOi ൚ip^R[z04 @p0 X `-Zhdk&쑥% [> '5'0k[@ q &unUpڀW 0ƀ̩is4& Y`+ui5jҼyKo4˹ ⩵0+[|Њ :#`}q}I D~]ѶoW~P;0?0羪z^0A!qo /!RMc|0)3,N`*P0o /2"o*RP%s0ЅV 53qLns$N`8OP 0+>NJ p<.5# soB>jp~~Z p52-Y0] APɣ2Q@jB,F36 P[o !@ pP3n *M а; K3 Hp 2M' s az hc;ͭ0$.110` P 00gH` 0 Ƹ[ } vl2` @ 'DX Ѷ#P ]9]ϒ' 2]6 'H4 ` pְ  hWm%7p( [pb4f p;ڀ n3F{Ӱ}v ׀ Ӱ$ Fq};S `) [!#pՐ w( [p=| 0fM` .+C@ @ s<` k܈ o -`K ~`n L$P0`fqakѰqk@=.!I\ ~I F⻙[!M 6e D0)]|` pk$ k>@I}P F(0MPuj@Ku s@nC?Zy^ 'sPI0dK0[@ P no`x  nP2 Nb ] !no @d^2NF8n@:`0}tx v' k`~--p^L - Ǽб - Mi۸mP' Հ LPOq88*& :L[P  ` LДU8%pi >n,5`%0/1j P0 LpL~@# LNN2  nP026ߟ HP{y@ s )r<0 p 0YP+& @M8 Q Y> fE3, P5@W~r#|R\5%[0 @a`  l_fpjwg=@pͰ D.~ F@ ڠ =Zpԕ- JԹ[;S Ai 0G &%`0+E,E.L,.Q`&y .a"w kpubHа AK  WM&P-@  L!~1 qg8 M?4LJr i@X̅E TU"XJ2f7׌ql[tIW/1XL!*v" BABu"if$l8ڵdߣT9PЇ p@)!g,/xxT45!6P̚Clɦ9j6?)4  2=;uٵo8*hͲ@"ZҶg[>àX#"ph#J&8! i8* #(qF Ҁ f1 (eHh?E&X XgX ?i8`Gdk$`f:h #~` ]bFni"9ᨤjhd\:n4o*RB*k `GHa.:J,& l '(g"h&l4AK# m: H0'``Jd06R`*lI;(;t]vh+/;flfl jO?08jebD LkTC)Bl(/c:|F Ɏ`f#DVїkiƕxfw \WTƍ{e5QeˏB2L)Bad&lJTyƌys™fh6OzxЬZm,hF&cR0 M9Pc'Eh&ao `p]E# bIMh!lqf Q ha gdI(gBtwzׅW^ IL6M<{ FXa xcx~c# *GXT(4 El6Ùx# |Ez~PH`UHX\Lb5q[C"_Nzh6 d@o!3h0#w,ti`#Ҁ,mM`5ERعyK  $'лy0}KذäobͨX"G ]8R#eUA`3t, P@3$p&4l¨yɅR &ʤ^3xBCض'y*Xъ#xAi8/-Q6 ;#h,S#J6bb814j0 Sja>{ド- ]#'(%,܂;Ak-CcXrh X$7( (D@-Q : 1;t.hkHCXPA|FHB'؀!xc)0 Ox(uـ:s PPf88ZR| _r[Kgxf h,hRx!x+-xG R%(iPZ8H`h'? 89H-FUH&pґ;x@59i;kFHr lDRIPd8 Z%F7+7?AF,E:d h?t2K˽| reӾ-E؀1h Ѩ䀎zHpFh ͈),M$ͳ;KM8`*ߑͦtMcM:|;MKNu NҼN+K; "Y*tG˘;1NԢ?W NNvK("S?S@ M| TB-TC=TDMTEA]TG}THTImTJTLTMIMNTP UQU5R=UTMUU]KV}3PX9;TWU9mD U%UaMU#т7`SS)e}FOהY-U/}vӕM IHPUZӋ;1"ZHU*kWmZZ':!%8aꤲZS[uȁX >h`%߄& 05 V(v`G(axa;ea:P`~v ##VTM('Ef&jRD}`×'`0Ё8kFzPkkh!Xw<-hi@Ox(V $HADl dŨh jg O`d8%8Ujp@a׉q( s PA`Ђң VhhNnIX0od`P[jxRe|,Ѕj`ـ8P.,FKao5>`>΁(hq 0fXm=),lkmNv(Zܟ+P I 04"GB)a"*Kg& l\M̨uM `kE>EX\eRrhh`M7(t<%ihңGR-R\݀8 YR>P 8SivA)Xh qf0ـaY #w,P lpXF vNlj$G % `rݪ3aohhq='-_2_i j( ltsP;x׎pZ%u%xu.(D 7uu[jf&u- UV/x j Agq[o `hx;uЂHE$)TheHg싩Yz?;'9hakIJL*rhYy, t X t)'!(=xuF؂F3@0Cf iHSc( Mܒ_\XgP!X„wH$Z6~vc0 glOijl7U폧3؀?Hm(k46w>hZހ0Ca࿂/dqfkrѩd̒A8`Ҭf$-@!˖.r՜msFda;ΦUti5G v6 `ʶA˝2]6MUNM4PTPJF :t(8fR(7p&&¡keX;tbKN :xuNңx5֮_78l:KWѸY ĭsa {(l1Q90iHțJ#Rb ݊Т3iI]Cm l$4`S8|Ŝi0AM7dK?5A)MЃƐA!( $QЄ0@AWTXeAiyq5E]x]cvXjNpdVÌGL[` 8цWboV%O٦o xH#pw\rZtq@uA)PCTB BDC*DCJ`B 0x PBXAȐ\E-0b`Z`5 €;D#h"&l2"Jl`F9@,-\! P@ #:@]1 sE@cXf'Oif-"1ah2}rQ6wS \%fMNBFNR6Eb Mp I(B`5 [!p h8`-*CH,Hd!DL%jmZbpj@ 40 WV!B4a G"ΰ,Tl`F3e\C\)rD mS`fV]sD@]D0kHC\9B 3:k3~=Ȇ6:*i@Qqƛĉd?1fDO`#a^`Bl5\lI * %qB4jTR>'4q hDb4Q4TEkT lTB R>4֠$P˿h@s4[ a!Xl4‹5PA $k:&Au^;11pa%Nrdž,t7!JV" %8f'U`. @ a `&?Pl^N Ò0 QL0 N[x0-!V` !"d \"i$<0â[2*c `؂z` Q2K`,0 $L(p V:1'aNn ``@Є'%XPMiO_j=ɝ;O)ـhl1 F7sg7~[~Ki]j>c%w&@p_`lZ }cI4y >, nppklEWlzFnr;ոoSR[\=섄Q;K0g,Mt=z;my*#>_<x A$ i <;σ>,,A,~AE>=l@Є&ph?WLc@C?_Ζ#oD>ws9Yǔ?WD3@@`' O^_ ! qSe8IB5H4P'@$\.D9 p@D%@% RD kLJN- 'X2H!Q45 JAA(`3hB0`\) A`@ l`, '`3@\_2ԜSLMa!. $5W"vQNOa Ubc(46B>` *A!s A x)PC2!b1Kd 3A Pb*1lb'_"212߲5[U-zcQTl@-),8CZE@3z`%*֠dip7ܣL@9NZ5C[LqBBȠL \Ap"GLlMDJtdUܻCǥd_?8@DB.@\uD \9q$CX$LW}@E@ 4O%@Jc?r^3D@cFcv%1$̀] <8$H!h |O] !l4͉]:h HH$VL8B$(heO9B @0B`V#\"KLT'xB(LE<#<褌]2Dg8h Lxxb2&B06~&g( f'4@}ާ~ A)6CᆞB6tFR ++m4A7m @3 0MT$$s.W04ǫ4ϫh@85(Wgh5D 8\Pg'33n:,MAoh>?@,C3LPmh6DDO4Z٤PFWrGWN>H+N6H4 |tLՠA5ܺxl-R[S#ɼ3luؙFQ! H(!Wwx5[ǵ:]K] ^ϳ^3_s?k``1zlAKl>48lg4%cǜftl8@@.dT,8Krj[0244(T5B%$TAn Gos S4T- r(oBBBx%)A1(@BBB[{77ysI~='zs{;{t||D.v~CC3w_v- H+EB,@! 窅_δ,KB4B(H340mx7K t?s3j C$ \S\5j7ؗ 6A.ACGve?ܙ66_v^1$fAX:ޱ塷尃a \)y@0+5 T/|U໥$)(ml8:WҀ0uwwȕGwD_9y{CXM`ч0vd!d8FCa@QSDm0h#X3@ ;촛9*8<{i@ W4@H0DAH `D |R'(0=0.XM5,v ؂4^ 0[|CuB0A@A?@ۼ3XA-D48A `!KW>G@N K.(5YOc}U0r h(|k8thf̘/7$=9 D|MDGRLRhڳCIh9j!<C Qăá˜af҆X:)^`1c 11C G, !˞͙ɡiXA4c\N@$ 2M4Z >Є1!o(|$=*i}Hhxywy}zٷw~|ח_Go]VMi[$ Bhohy%ih CRZq!ҺF.P9|ΑEfJ.gxˤK3جi!GR,?L2 3͌fHB 8"ׄ[hshAadx;E&TĠIsF`*=ޘfK4Ͼ\uݕ^}g9Yg /6A`YO" !cP ċ` fKH?R٠=iuG`+>aZ AnĘe`QD8X&QetE% pI*E .[|Λ` `fh9 f'>`cхq ,D),g2KaJfN'RY(ZH(A[0p-|P=_D $= w+ٯUB; 2q>-s:G&g|'%\gGOw\j0mG]͚1p'<ӥ6|='>M<"2'yXE+^Yb˲+F@iTF7&nA27GRpk# YHCYQz渽 $>0!!x IO~_9bᑢ<fi4##6>Ue'-;=A vT gT C Kg>"7 @ p0"% YNsDzx[<ݙN{ `0q,j d& P*L:Pև.\R "!B 6E< |;ŇT (B(4X OȀ!+X D OEb2U30/SChHS'OS*nJnI D~`gl9UcIeOM:`8TAjʶ2[MNX`1`[XC ")飁ZjP; h{"[aqj7Dlb&q x6?@6H<~6kox q@ gIqfU& U^W~0y@ЌiX![rN>sS{0lH /$ުjj\o[\ 7@p(|1/:J -P1-3G44qm& ZvL7BeGlv*vHӳHA0|6" q=\x1I ͕Ir%wiD\~Ls$^{4@žİ#R L@cNȯt`$,*2`ΉQ$(! FV ; 82Fp `i G`!! P`bt#a 0XliA#X׿ P+0lbJX6hl  H`-%VHPn,G E=dvг2KG$ ܗ ! Tqx;aHѐnG)PH S83&DtB82v7 /$"#_{)sӀ-T#E@ iHB{Á.K1oր〯cJ 4 8" O#F4;g׸4Hs2up+@љP럡]LB) <Sxpf cyp4y45@s&Ѕ[%1SpPa g褡hƪ%Bo.a0>NDaP0 &(Zm A60 $!S@`*~o@ `fe|`%ހY"=P``* Ԡb ]L"lY 8 ##@J` [ AV9: ld@`i <^rd4RtN # A90 G @>@h pr\b{(`fu ZxB$A'@hA`.@ B@TfP p,!f!"@AJ%Bdc "Q16&@DA |8 f ot j YGa̿Fg`%oާs< 00@ x +lb0j 4k+H@I!! B` @tF!0 A@fBhp`:&u6$K3g> a3Z vv a2v@Rx4t r X'AK@ 4X7Z7F~ dQ:F&T Ed@K`@a @L F \ 4&ܥ:'̶dZB T9`@a *J`b` t%F >`  ԰H LUk A"\BC9( %H` VTK @ E]Jba4pB H `4A$`:h賜A VI ru8+&@8<$ 8 AZAV @ |a/@^wͩvlVNYJoN'GG6@J\uN!,@') $iDB80F=>t/6=ksA|0_hT!B6/ZE 0!Del)@&O.z~  \AhVaQ+!i#$`j/%gA Lor%1{Ii임 RTV }.6CiWKLڤ!`ga =`PUG'x4K)x )X 5AgWY#pRxX U(|! dsFoR   I^Kk`WvsF 9 AJ!F!@(5AyaZ`D`i(Zaidعrz0|aK!v $̒L5v C61'=F m)ЖMRCũ ;7Tp UpOYJfR.(YY%!]E8{se2 !xl![8Ay F#`Ru @xnfa 'G`[aa l(vz< \@~Kjq`4BՂ;N$e# ~?`1GF T [zz YhhaEAKLj"^0Y| 뵵c,#![OFZT ╞G'PvVˤT8 ¡q&"GTg! ( ] x+ R/7MR DzFL &F,W: F jI6| lb<81 "A ]v+OkD)F̯3:v#e!` U0  V܎`4*ڠ1#3L泗AvS Hإ{F<#}@`BF˙U b  J̄%Gg`V}.$!q`N@@6AWk0UTJOFNpzU*J@;@TE<N]4&G0J@  3BDe>%A7'0 -QjN)j8EN4J aL DcN_ ]3bÇ9̚3Uh- CMS4B 2];IIԯgԦ40I͛8sɳϟ@ 2if4p0"j\,@aRaKv Z5lب #SM :~4!Ϊ`߿~ivP̩h׶vaC@͸hAeӸ8@!E^l44gY0ԭ A$Up(ͦmiR[lخ];N !H 5uᄟlJ?D(>tذ5loNaDDͬ @85lCsAHh4jA Mf59E05?H6$դd"!WC@)OD1h` N$Bu]0LL$4N@q!En`NuO]LɄX*C G8 a0PDLxT 6m&g A I41IEN MM( ?D gmp‘1ZMHa}i I8Pa;]V}p9fC#?:`~n;߾rɹrpPP  Ч۪AJ㰺gwP !pXJ5$6Y~q/o "qa5PB=p'â:0(YR`!{` į xdhDߍpa _HPwjsp4\HYsfD*P+8= RqV ^+z "&C0q89ʘhD1pE:1aQLc(G6 f 2a<Ǡ44! BF{4iHN^`"EP^oe#*JS򆢴$&JŐUd-wYCNztG\Md>ZkLf*-z,;&*Mp|an eO̙Jp-|f8i9be&5s4aJ%7Lvҳd93Ѓ1D0٤]%Fo2բΐ:T-iC1:τn#M9!Qmv Ҏƞ5)A)Q~'E)7WTԥ(CzԦ4~8)HuLޔT]iV *jU{P_j(MIWjݪ.?jөVe)WԵ mCV5(Oi_aISz"i@kVú]I |Vv}{-kIўedՒmq!+V}n2ٚڼ gv}m*uӺ˚άnL׺~/lKTf/mFzU^n侷jˮNWoKY¬'~[VdpiT;M\S{W ώ1ʺָhjikׁ%NJK\ˉ2(YіX62Ry\e#w8ɕP;Uem[i=E@>578|_F4 h,؟yQ2wK~(\ҐҿssF5!1aA Xq%z 8C؁ "8$X&xs`& $<+8<;*80=J*(X? KpW?-BKF'( FpD CIU"`0=(#28i~(P*i ,I; PN  ")H;"D9aH= [51J UI;ȕ-y9_ ]| +p:<0710 8*81EL@x!0 j@ s`!(ө7L7  NPwXcNP 0B}G 0I"Y PP};@"OʂɚРyIL`JC Ѡ p,@qa, sPJ@ JEFC<   Q5  f p ȏ  p#P @ N@$ S!0Uq` ;Q P| _aҩ S)\ Ln Pt, 0 sP \ %LP fBu)$  /!.| X0J  Ҡ8s@ W)  R3pԠ̻0 D` p*BἺ  q%0p 7d 0fˢ q#pP [ @ۡZP}0 ௓iz5@` IbvBP60p~ 6"Ţa>` jA# 20pک 4È q[! 1ٺ `Pݎt] ;=}0 Z+ ٰ53ގ\=fm9 Q#@ْ@W \j p?@ u܎XR ِ^v,}q 7Np =#@>$ Faڪځ } b"&G*`n b B *:-mm! 0 y `p] % р$`>F` РRbcPLy Iy61ҎЬtob \`Q` }T.? yF^6g.g| (]5p8>ٰ c%#%}psө9 = O s`{2<IBeN isE[@͍9:70 {w !8~ *Pbqa;>p #  )(!L |v6nP yqb Uk? >tAJ3]L-fg̈@`8,hjP0O4U!605, D4Q:@lAPs)Ila ѤQ5E24hEVZmݾW.Z[-{@&,l5E,l@ 4cKn$XtTTv &^ T+edC0zAr4&Є) C Xp$m~|D` 4i&m@ăA_ͤ,q:Dh%$0`K",H#<!jƚ+4 ,1 iQ(k̻-1Ҁ&!V&rtEQ$і"r & T& 92K-ˆ&kTiHa "Bb?f.`ތa´*:0 K %Cf!\2X;Bh&p -|9`hӠf1;>&$\h(‚܃o/ikh`.+(:hS%6F`Z)  Xx %`6#H `8p#f8~@!@F ƚ%2eWfy gyF 4eB-f& rk&GC7č'Ɠ-HZ؎6FN7 H&58$f!褣OJ™klC;*"^ M>SZ0))`f~aQE P[Zf+*'bf-pq-҆.C$g$`t0 (c-h) j?`( ide?e @`B$kLcCfԀ'xJT)Iud l KpQ lXgSHÁQc !P $")"! lCBjcс(}!B)alpiK039k!+4JXH k3nQ lՁ8!#E@D@  %!W)Ej0#30pkdҠEZA(&FĠrF51җ ,AR8`TD#98$` h%+  Cdta07@bo30!Fyp?8\6n: T0|EM`P0C@ (9pC#wCp*.4 A Av`; 131+Ȃ19mȀi! .C(p9TB0Q-Im @;#a 0l`8` p@]]C qJ [<RltX65-R|Vep,b/),:2lfRtMZB:]+E)k`XB8ںֶF@hߒ8nS6bmle˖VQ1!)׾5J~xE -W*4Z*8]?;JT7&p\l ?$YhF3Y 氃O<)fˊR0quc: L0PDG^ _ |~ 2b'A8TA祷.R< ]s*1 #Al<|#:O`n#Me:ft 8pr԰ 84bX-򉢊FQ#8*c5]AVEmB˥ccO;Z0V#u0@'I 5DB$$/FZ7:_ 1h:z,lйȭh)$.8/9'A.KksF T{m @0B4\D.=  i`\IK(@eMumc@+0FT~O;D@DT0Z$pv^ NoA 0ݜ9@ 04%A$pVN= V |NP^08Stirtػ]5iA;S_LpQk*09oN5۸Aܐ~DN9891 KHB8@D L  9įCGuOۨHucAK` n!a̜oCȇ7,`8~]m_:Ɓp)Bc#8! `ApfF @ j@QpPG<qr1@3hBtF@`(Ц>`>QidQ5He$vG-6q$A ioQAmhh=9m Oa9ʺ΢V~ jC hA3)~#x@ :#ۘs#Լ8 IXR:Tq!LN0؆o4< 81j0A!3! qYD%*dhGRqj#01a}CHGlB)91w+F8© *<43;"  @ Ї8HD akhJD8=(@!؇:A \4}t5 jgń@K!`)DbW:P.$ (4m,"p@\q)RTAPvE06 pCJ@<{C x& RcpF w$ D cU5 FЀ0bqt!7tpsC DI7 h B  @=0jv"da4XP9P[-1r- (U Q@[ڰ% g V@ V P AQ `/yw"ڠ@XExfx/Xlump)0~T'-#p` 0'Jr)0,# pz,DžOgU31}2 W rGݐ ,4?%`  3O P[ݰ #  'x _@ q*@zX 0$H&H(Xl\A.P& 7H0`[װ~p&` ؐ P P[⠐s4@f<"RP h脇6 ՕP#8ݰsC)2pX*A`fp   Vp  W h y| cx8`   b9(G0ڈX ` 8B*'3`a0 ϐQIY0x0p&`pNНP` @lg(Ǔt?x[`N``P8`Yw0|`E 0>%Ƨ4AΠOSpykXd8lI  6!eٙ wyЎ!%8s<~' D"p9а p&'uC@ ݉M)O@f3ӓ y;J0B7 ߀O4T)A)p% ad]hyk )  c~@CaPd'D'褘=QIWJY(\w0gd!f #RߐÒݠQPVp& m 0|ېOn J`G L90z5 + PWypGP 9o@c怢r12* @8HI:%B*Ř 5٤b:Zሥ K!eg*)ВߤYv&Ap)$y p @y˫zLp% 0yG&ܠgސpE;g:#M'`P @OyZ:" Py拸`{0tY`-`~k˚ݠ@  w 0 R A@ {!%f 9@Xk nnPy 3A4 c(zVj p3K˛[c' V`"ސTc C4PL2:!040-i l Ip;>$GIʮA}EK0ipJF@)@ lInMWKJkwG m`Fڶ!!0D OR k qL v | znZE ܰO$Ua2pKpPY[̹`bL)F@P $h4Dƺ  Ƞ4|!0Gp)~I` j@PbYe9|(r I`|3Z@\ 0Ea" 9 X0 LbEX 17@PJ$#1p vYwPpˉS=Bw _ -@)6`1i@Ӥ,` qZu@t%R@1 ߠ@gXl0ژiQVy~f BId8}\ pP!@ I%LAZc o.`0v7n"$ G`wԲLKa0:@`!]aKnV` sٱ9P!+A ӔM HW0e6@` "mJ(@N GW@ p<5c (!`T00gP_AcH5>x .w} q|p%`6`f(!U~{ =* 5n;5je ^JX0~ JUƠi? `aHUP[ @(٠"$ 8G' b K0SPp& 0M ` @ =@ H -0 p B[ 2D=SP ,`Pc0ڠ1paD-P dP!0)n !ݰp 0M[$ 5 ְ0`X @MS` N"4`TvJ `q0$ PcK̰@7[. xcpw,P[Oypu({ɣN <߿bȑ$@yI-]vP"J+_Z\N,;Pxb$ Pe)TS* f^|jP67 CvDÆ ;o6)+:ꑏghǓwM&ЁFD6{!44=09au_az #@ԁsdኲqq0;q =qp%x:!(? `z(7ļp?`fa8YEB~C $P G>ԁTz) <Ѷhȇ9p8@ 9p2quYxALAqd#=vqCp@Ti' NT9H $} \b7 ,ʥ OK` ISЀ88F  `끎@ h>p:@TZBP( /f ?yCX.S*$дi@Ԧ2cIҤ >:ԭ(ܞԟդR*Sʱĝn UzEFcW @pLT{ncTeL`>>q4U`m "?2-lK&Pn$Rkdݖٞu'hS#ے[r(a, `PD#8P" 4/ң ơc ?~Cُ# 6t Up= #t f"Q~dzۇ-@e 9D 14 + `k@?KFard(0aWX) +7 >B(YG$P o~E9h"~a! 1(9HX\'9f ` Uh](I@A b> 9qKf 9An+tG/{E`V  ~C %$GY -f?`R:}MDl"w)p ;q}o8*∇>ꡎhtD 8Ŀ &N0o :'u#DD@h` > >)>pzj8ArjQ3p41`;n7ux ɠ?F6Fxl= rp 6 vC?Dsқ"?Q؈I2*tuL@;э7 kFk@!S@Qx lx= HXt | 84X@pw`vps870Ryxw [k@wh6z y~x {h%'lyAHSr@!%P+4bvȇmҀ P= scqxyS:WȠ&v ~xAk= vЇxHx0+ 10}xxyCvs=L?}`Ax.{;3vH|J A[@zBjBwC~H 'B0P.2-x &4@wP7@i+H|wHP?X͠,@=Sp ⃵1sfqSDPBRf؆Tp0N+0 dAluYs^ PExsv$xʸрq is3H<' hf]Ɇn'W3>qxsdxȇrPh"Ȍp@i8Ah@l(|& |?htXȆ&}peio 0%@&0zځ:І 3俞|gX(Px(G fHTaXz˹}zPD(oH޴ɹI4m˂.܃;щ5xz@n,ٛ L|#pmBWHlGG]8!9&Ṳxn9d <,*z?`:kқ /08>X P2+xy3|n 8hfSa<-xM$`}3] `rxz$ExZ+q|P/ѱU*84Wl0ɿ@o-;[+#:cAc+\B.SC>SM䙂n1 V1P] H{(-$r(kr}БnAXeKݏY GekJH}k0fb΂EgpЈixjXDb&P7wЌZwx]\08k ؆xȇA쿈Fv@Qn ve܃dvPV ^x`"=ba58I!{p H1pgAh<*+ hW5<ɂxd1&yS$j!jj R@kA,VܞE/kH^K(ȆsH@X FsMhm8Lmʖ˶Оfy(mdl1yh1)R(3r8n@fR0{n>nv$L\ h|Ύ5'y~&σMz WMktv`YsnHSyvp-olllvІivm'߈p 0ΈyXDc5KoY$ :nqHeF\'^D^ gd"SQ(,08؀sh6{4ޣ{`.ۃ}ej,h\vop1(l]8'f,Jpؼ?O;F g (Xzr惰xw}kHfmp=&ȥ9h+h0!>g?Mkdi5;٫nD4$h+YJDY!Р?Kz4 <}F$P=p&9uc`sF;z8p|UpB|u8ۻ|XPJpÆjP,zh+2BzpۧтEZu#`9&QFo>0P>u|m-^>f) s谁A 9͛7Æ8 p@}0Ȼy\w{@!\u{,H\>4XC7-gڌ (qCO:GL`714@COIAA4#^47+~!f=z5ԏi y#TS<Aڊ4M?u64,,Diđ!TI0QM6X?iF:cf5dP 8PM3nG8:{e_sO;,Pd.#HS 4=Bt3ȓM Pؔs:N0,Ai$Z!4Î$2@AN=8 D  P' ' xS65h|gPwC~&ӎ7|Ou\ANCH 9':@ X1( 2̰b4sK=} #6r]X.PA]l 3|<\X:C)f ZC<<Ӎ;CeQGII=585wA8>E~ϸ6P)rqr8J 5*SN XsM4ۜs:8桟,|-8=@y0A& @򀓼:: 2 N!6Bp?3W2[3j XQVB?F=JxLs( B3DDtnRA@ FAMFbjc.A. FZ鎁$ijJ=;u  rP: +]{JAq#=<P ܰ(E9\Oq@ `â?:1!T\H?>C , GJ*!|#YxF@"kRvf30|رpL \q#PG3 ʃH 9TNJ~dwCm?0]N-p9#,;\qvN p5ֱwx !p\}r!8cX3?jd4Ѹ}8P?Ѐ`"~d؇7B&Dp!UL 0[1>RnA;f+ q &0<(pqG<;s( -Qa`=(iY9! 6?M6`tHC 4* BJ2! P4 mC0dP(8, ]f<=~Gxx@="@@$AA$P@ C3xP`P=$47؛)5(M7@@FFA"!""$Lc\L֤M%M$OO$,@4>6 `5L(4:“b8;7!(%$@;PLdZ%P\%]ehvO:;K\9==Ct] P;@A;&iH.Ii&kfA*@`9;7Vp@ C> 0_$ A3:̗>=ă:C$khNu0Yë\w~wZA)T7tC7dC3A @%pC6@<"!TB 6@"B'@$hP@l6"(Fh[:@e%rhv$D@\h@$&J0(L ,[樎d(dZgc@#h@@aƒB) B<) &N)Oh@Ti*&[OP\ hd$ (h)A4X?@**8 LgA8d80A)q\£N* Mj@jp|>,w2C?C3f>hW>C4PVP4?ò#PK"D4A8=xbWh A 9;}^g0D5hAx<3t;7Fe4<0K%XAfB֚KSC\6]C}6|9B.і&lh2@$ 8PMR6+ì8&B<,>z,p4X^D@C>^i,$$VU|ڮ"p5mD:܌`@bj+1QjJ@.P®;!@.JÒ"THe<LQTV6$V4qE7A6 eij8Â[W790d܃5T<4>q^)J2thC|S2*Rv2eWnw_64k6":b5?88A[t8:|C7x74v Ŗsi8=8g7pC qo9|7j# (C8<8CV^M8>|TEtE-96 @#|7 ewPB98|A8`jc/7t? :( d'4T6@r\{cA_I;wH ( ]??c@i;d@$( 9 @:p @Axqk 2@  A(;C4 sD <<JEP<lH )Դd?(dc(Ƀ<@|8TH(䁣Ё!ƒ ԠӇʁ '  D@i/ۍ(lŃ٦oݜ@;$0>vn Pp`;|:pۼ{P,=f ]@I}Ν8# ͛g䇛 j87"ZTyF4*WT PWX@c>vXAл{h(&Z0 )>h*H@ ('thРs$'7AG}y@(_`pIw@)l YjB7(6g>a`0vMDlF*GE[9.00ɧ0ީ$$l 0TR`O"OwVA@ %Gy7:q8"1 ZE|QP2D6X`K@F a',&F|@wu`=(Jr.k0j@qb,}'{ ~G6x'u1fc&HuԱ'L53zCP'` fFotq"{du([!]CE_!0Grzzfh#PtnVqvp`pu?sb!@88puF  `_qzW+BQb}f8e=.pAoazΩy) JupKUٙ>)-AA1x9Tq p7R8cDМ@#L pyp8׉aC ͻ1v@ ;hk$< bJ _{C8d !^y@g 0G;  @̀q "c̀І]``"pv8YКisQ@(@fw zA ;FiAmqՌ_j<9 @9F?eJS)V D+W/Y*TC(7("k:eiFD@UjT#PA:3ԗKe*PCJun+*`H "@O&Y,7HpIe@@o5cG;% @i;"@+Le2s,Ұ7J.H|j {x|X.`$ ̀^nFLܛh0@pG2f1. DjqqvqatnJt4c@=s]xQJ^;(Y \ ot@^Gc:RzVN0[A[yc<,{BP \<ı,4)F0hdBP ̳H0S# \P"+6MHA*G>@e63;N`0 h,:yf ,0G7);:@A3!LP!r! nC nM+FI@ 2AB6TB! Mwd&$ fEY,fft?q! RzݒQ;^p@'\B8 ><d}DS;}+oZ vSE|o9}rSpsࣟj@L xG:Qn(Pich{uF6iܗ:֡A8\E!Ah~j-С:a@4me>ơaaFA6`0A@SENA<(*ArVpAt! @RMq`ZmʡJc AAujB! @!!ȁz6x¼MmЈƱ4i J*" !s dp ȡYd Ԥ&PA`)AJi pAh"a @!bbp%t @ 3 >arqiԡXNn" !N)$^K0A!*b qAPqA& R! 4BR7:AH@*p!1% ". !,ġ:ցfb A ZLl'L`1"Өئ! 6a !D4Pam A ) Aġ^ 3 4`<BA njp|7`*< .;8`aV0!ʁ2p>  !@1 @4N!6rB/`|F(@PnṴc2z0;D^ AA L>(P Bn7b! ANA22ɇ`JHhH{@m2  xt  QǑD'`*@9 @3RbRPQOC T u@ LWME>Wm+.m@ EFRU07Y[i&3@t X=S W Q 0T  z^`@)\!( 6.[-@!2[u$a^c[df8VBcGYMCb<6?T3UVeCVN7$fdS`"gc6cdgyvefOVhzn!VYVg`"6Eb3jspNjq6n""gSg6Nm6nvnmn6ovo%[=Vn-fvp p 73: 0A( B  YwrVp)r176s9s!45nsIt/`bZlLwr6vin-vqpo7tEvswN`ehaNogys-7zWztn}zs53C0a<mr|2|)3m(w~ r D|jx}ww{ ؀Xn=Vh@X]r/x6Xcp=Ed᥄XEvUxY]a8~7s5`sdvd S|o66T2Wn;^5P@bU{7s@rux7w%E!ƁWڶn5u^>!޳v8n4\ 0̷8n-T 02mRN `vCp8~= |^(`9< `44">a N-"N5@&'4p @ɕBG > \2pHaDA`B5CX$! @qY!~, basA1{GV(haA!A eb`"A8!ZځRvmU8`r3#!H47p& ^3(3!~,nV`&ȍ5e(Xd^~@HAA` i!6I&S=vz'N   qց.kjA /߾a0@/f$fayp1 @ ̤oԈ)$aށ/a<6A0~d(@!jR~>!4*A߻u맨7P;x{/ {.#=}-XOC|[ݼ{Ia?ԭ7/>h4 &4E2[(%;ĠXǒՈ6ڵlۺ} 7ܹtn4k-޽y) n&PSo([+ROѳG…fs 3u1[qmYDmt*:|l%R )z^H~+H3rAp]>uY"0ݼvkw{ ˖E4cE:#":FōN=|Ì&P8EP6!ASNbHWRL)E1AFН:H&5\K]"<t? +3z$H Aӎ 6:cte]~ h^nm? FXQ 8e94@?\?lO<`:@ ؃@D8CXN20gİF_%k!z?$P+ȣ ,M;L b5:S˳|x86jZ!P@\jN0:TOr8\ܔAh6<6;prK#!Dp+N>@?9)5c f@\>du83EL@g{ wr='VC9R*:CSBЄ:Fs LPDWq@$OD50:l`̳VQeیEmC8E!;`\D=ͣN |#>q J;\v+ 1'Gc9,,]Vi5@śPlz%@=}@x0@}C 0J6r`9 BBH@ :.-G4jp, 4mLlb>mo1Ied;BC$ixqC&G G=!oty XHv NP؇>1ˠX '| o}B޷&m8G׶Xja8ا,rHYB |P8 $< Px6c ! ?C .@EɟPCt!g֑p4UJ:1Ӊ mCP 0;Ca}h{CH}BA֡AN"3<3?E h@0i,v2IIj@hI0i;;xC7T |pe(Y F 1`Q%إ0cɖ>4 @G{|FcN <]8j6a60 `ҡr,HC \Bp=KE÷d  6ѐ 3#(JY\&Pnx%7LPy|# ٖ!K} Hb+EjUqբdպ@4q`h8C @YϚֵu|o=j_2K"-x:W7po-dc(dfX=Zx>NmhVC\h 6|(PE< NFNK 4p0ERX:ocn`6Mv{k@#IzDuQ"Z8Z<M-##_ZQnD"PhM*'ysZ:-$承zcmiqQ^\jD;q iHch1r yQu<ڼhG:`6Cu :9ބ/|CnadDo-b w<H8HשJ#@{C E98'Db<#5TdSy› P`(9v- ; woP I(l9cE2lag(d<0vҐ2>m5mM'DAB%CIt {T̓Y}fx%nUo cSgy6 3 3A;XD_ {T= 0X{IFr ^!}Jje}n]P K` FZp~~N PaYp `mFiwkO ՠ 0Ї :4bub` mpoX Rq%ݰ*pp8 0DN30MPbuEU0xI 08@:*P{ PxF10 \ x`.)QbP cqbRnPP G z <{lK NMPYsc0G -@OkoPR[cp b`Wʂ@l $f5P}"IMJP8 `M"[JEJ2@cMU9Rxk<>z8(Z9[*A@>0?Z3@EC8IăR1LNਏڣJ:E^E$0Np8b_E? )ik*m4P?>c >JMCQ\C0ClQ3 vBZٷ67sRAj6jusj!vk!edws6yhLFU'xh!j jhQP9Rdg3Xij׭:[0v(Lmފ]j!{p 1 $fx٪K˴MO p? * BL[˵]_ q3$` F#q+sKuیp P 9 :4jk;` "2p`Zĩ Vثo π 7o˻+( @ptېP`r*DEA296vL Q˘tPDʣ1^Kr%`N}2 `1ۤAԿu竴tM` `$)qR0 RKkD9P 0 '3D~ p  $MG`Lx"` %`S- [ ' ΰ*)0#Pł2` WؿuÅjP ![xDlHvj<F Z{Xl CPC5Ȫh1SJ+X`P,@'x^w mb5I ~G`@ՏF- = #@"pk"@؊M#EL l7"0 ؠ ؋Ȏ(GI#0"3ͿEA$QX\-ۉ]ۭ0 ~P΍ة$0 `P-t$ [ ۈذ=؝w!ے}x=ٗە|-Mt+؄ Ie Р`0Bu)0 ư҄lѱ|  ʦl0 Ͽ`̀` ci~ pCp ~ 5E!M @ ~D q[` K. }0̠l03>. e ˠ)p| ̰[. fp:0 z.=c{n6 Y|Ap  [M0[ENL &^ Mۿ&`}! Mg!Y 0p쇀r+ՠ!0p p p1 2l|8` /1 :@P2㙽 PѠ` fP1p D Ӡpc1j Ґ*/ ` 8׍.j `? h }oy Dp ,}/ 0 Jp@ 0P <' ^ `@o <H ʦW#/2> c0wRp+?0|>sOiX[ 6^2`nc@0<@ !8 #P#  %CP |0#pA ʟ06 ؿGP ПI OP % pFo*CR_PR ̅%N8CѢAR" Gf`aBS`)IYO>D!>*U$iͶDC!6McD#>LaiͰL2$6L%b<4 ZD)ORطb>Ai|M*UTfAtaf a"b)2TԩUKPZSPR%:B 4hN<$<|&tD'NE-ƪ}"JԖE)!Y4`-8Dd(ۖ4Q L:ˁ8$ jha00CONy 6 a@ k>"+jR;m~Q5p/,C#Cs"F"*ؠ,i& 8Ep%Ton:5ZD7fy Hf# k/;BOy& |ue#@ga8Q` 5,N t@Fa$E"dWg`Al Ԃ$h F〞 DMX@ ]bF}堅7E8"dhS_6&~iF 9\R: 8Z𨦔 di"AT% `-I>ނu]F-eN"bk\>t掉2Z> a[i%10p Ě78`|KBdЈl">N4!RHbj`ZG„41~`AP j>2T3PM3;P<G ",~hh""$,cIn< hF5lL? -`*J`4 (h*hADZ,#ЛI~ )[шĩ C(ЁQ7p B+HnP:jĎ#0XRDG֥ <`LIMOXֻD c4c 4#Q @bhke1Sla p@"dJԀ3! _#@p`ܑxȨxԁ",&R!"Xj@o` "fTaE@FI lXdUa#ċ1XZ Fڌ16R%h#|OEdj pF'h!!  Lf !a NntL%fRjD "`Pp3[5@<2 5Z)8CL 0 UD|L{z" X3 cK gB OO*",mD NĴj 0u@ 9q F ӂ#Phy7Q 2A d^Sj,!Mt1ӫ5#F_$#XVDHHq4#khm_!K&V:8&R@`HA fuu%^K6(y668-*\y^j4YTJ: B jidA X5 - _@́.[}&?i3](<eքX bW8DD8 Y w3 fHXPia `BXMeN+Y V ?RҀT5P?O`:i&rd(g0&q@5$U< > /=e#'R $&t6(ŖI.bA @B3޶`ӈDKOT(tp+X\}J h ))1cRH j|yR9:0I*5"\>"@TsUbi@J._`DGD=IWo[C*WD|tVyyxž_YS@c ]$;z70@pBd4&O } ̤:/_s?I}ـQ s1 L"s(3-|4tRCGs&bV.pQ>( ` ȗA*0S* lP ӂaےBe kX p`,?Ȇ g1m1ұoţ;h+`pUC>#V-z14HX`"(3=#Ex@ iʷ  8e-0a!yڣ q-UHрjT 7xiPшf8k)J`ZEhqJ 5%xP['/&x E8`..+IQ5qDɵp&8p!#@ c+Ae{ o„&с'XO+Eh 8hB6&B"B+Nj 聗8F%UBS21bg0*C&:h!Pj`iX1gÀp ah@sEF -?1`~uSg-?x'@!p>Ú2PU H?Ђp 3!pCsR ,u(@J/3;8hp'[-p;Ҁ)8% h)>0t,h)`Ζ71)7P hA] 8AXZ(Esg]%H}"'(@-#(ؾ p89P'Ҁ 5' T?5m_YX_}$puYRu,p_Ms"o 8`Mx_r `u_! 8`Gd%)!UB`Npxz;_N:Z_,D4z`g őb  *|_)!Οarr_9"c;c<:=6uš?(;cB:.dDNdE!>^ d䗉udKdLdMdNnNeQeR.eS7=eU^eVneW~eudXeZe[埫F\e^e_ed`fb.fcdY>fcb\^da<|f;NfeZf`[8hpd]v xHIgT.ct~[mg@fg}gdW)eprYl)ۭ1eFNeЂyy#dh`P ^ހ#pl^O_(\7dhUHFѫ韃k@)Fj ^؁(@Vp`8*ܼ'.燎hifnKvj-#@^k)jBjjzv߲%b``ndY hkͣ)k]ikf"_h0n0kh[d;R E{킩RmZUۦE J+Zl.hiOZ#S"@gg^{飈 `|Sn&A^mŠ n[jИ-[ oPn]6d^`ocmNW)P;PZ?x)N5j?H,81胺mAEȀ`?9( x^h-[8T18Ձ_)x7Eqp!],0]8 P]Ђg`Dq7C ,>`0-x>7HP1Y'x |p5pvUput^e;srюaڃ- lIFH4cHjzfP1-hhxXx`hғ h>P,mG 3.PxW{yGuf؆li&!p\#kQhIv?0EVY)`iH t* hlNqL0CRbnXa ^n;7~0y4.S$(LGZB(qj"BXM"!׆&&Dbа5 *Dp5ѭ"02 !Rs7J;)5Z؜M a ظA"7D7Єp/H@`㬁Ѳ"KҌ!H i@c  cc""u ΨF3tqS銶s60-AϘ 2B@yV l d~ P  XA#)1LEx S]Xk9&1um6i@is#l@18wr 1&@#2Q !43T"^f7/0OD#(}~ѹ'eT# J-"qNfҠX:8ڣBwʕ.Lǂp2!mCp[<(ZkhSi4 H!J@{8  SGЃ(}E-u+n(AN<`N$0@ARwy3ܰH!x -4A h n# `ATC8ssǿFun\9g|E6/ ? s QLB7G(qfhw X il"A*q{G2 ۻ"pC +0X#(2Lj%E=o %yλ W$Ӑ!+fE^ih?%u'r>$Wa{h `v{ @d]gyJ l@AgneGj8>(XY*j4A3l6d6B`T>$6w~ 4d*@Ԅ*i <L)@Xbg2[\!AC颅>W\B68(tOb5xt')}hc(V5\C6YTY&ipVd,4ڈbCr ' %"l D!ӚrUz `B$ 4W%4ԗ={R)|P86jc,x(G,T6d3h2k{̍E`@Y@3XŲ)#Ju<be:JNet̜546kFP(AfTQX$HF%TFdRcFU*JH`,dN)U ,e2[ZlVlVZhD̎Ɗ֣FZfܫmSZuĊ  <-΀ ؙB- Rr@ֺ@=zm `-  أʀ Xǎ - `ymԦ@ TXl \DmZ (.C l k (ERXߖErЪfAz H'<5@30 ($"#AHY54$<V` \Bo20rBJ`5A(44@o+l, 08g-ā T&!P5Q2BeHB=0](0FT4XHtp5\Tʅ&e(aY-A"6d4`0\d$`4Xg2җ ]-,Ҫ Z4h&FCS4PT4834D D@*.9Qdq5N@dWC(A383D5hͩ\`MT]-) A34GmT9ED6H/?C +IΈ l5JAor]r50w&N*œ\>++FZRV݄ @o\IC$@ᩞ1@Ctel6nL/M@D42؀9i d3,L5R8馜:4-$&*&+!ؠ XpÄ  d&P=4hcz1.N &CЌ ;`"!&&7(;J5ظx6pӍ7 x05N F3 Khv!,CR#j;rMo^c݌WÈk6YF"Q:Ґ8Q(hzF NoN0 0f8tD.Q4qg Sf@TQ lL= XV`^ chB9KV/ [L$=UЄ ,G CJ khBp1mBJ˂@-8ḓ@z 0l >虴h86 YB (] \m‚-l!. lb k %  ` 1, PAº %h DD\Zv `‘< ! B<  T?ACҀGF +' D"H VGl1!NfД0`p#;UH47Yf|^׀Ǵs@yd/ 6g( xA䢝I|2LS:$X B3А ׸Ώdׅ)r1a `z}\$> K]esdzByjۧO hX]ns>\gtY`@ a o7VVPoh@?pDOX)^q__&Qo d\|k YrC615GHߔ.A~b () -0t_YWC8@]ixlR~* y/J0Õـ 8 D~R! nM~|J g<כ5؄iHcO.Nь%0w^\_{ -dE/0hDbq-a7 ({__S 4@BwKx{Sf|o1 H$$R^K8cY"m޷ > `$@@A F~~B` &N$@ $/?8na+{,PODj D&PA ~h, b Lp$PtO+af Cpa%}O1pP0 OZ݈0 D/$@̦a$8^( rҏ!J Q m"@>J^pj P. dbP@"AϦ^Mrl:Ê#6  <.9W$@qSLʨ7BR8$DFBH` A$`b~J 4hT2(d$H`$G` 9@a DB B VD-HA9Gso_LD M@t &g5J* ` BZdD.u.h-N`>`LR 6!'L  *! ' | !m(! &Cu` `@~Nf ]#R" pU!@ \J N_@ rH ` 3Y.Z( !:# rTD@fʏ <B6?VK tguV $>K'UD@` !j 82hb  f`` !A%N Q,l5x4` ef Rp&k0@yrv;Tc FH(Mb6`Aj#@rft!j@  n] `"z$Um "˒ `dō6@AA >so0`]1 ;: ĵ`/ *`]n0Uз"Wb NP=  LVW@5ѣ!u\\#p!rpS@s >  G*@P$!m!f6A`7(tʧ0mlOO@ gM0$k:7a A6`bOS@5{%!yJAA@ rF! m~`UVU3t!(@! F 88M$( N !JC^ A`(V# Aj$ "U@P >ƢY+`@mO'!2g8 x  o d:x CmԖ0@ $AN&8` DxA RƬ &ʘb \Z bZD;G&Oe@nNր _gv$a>gH@ @ x@P%$6$jM#NqNZq!lx ^t }LIv:U @g`L'!P#PB^&G% 16zI(B03 @ ~_¦#5zj~A8a eAD`$!A1ͺ`  a3Sb~ T!aA !Ala J Q nvJ]\3{4c |@w@J;5\M mUN` a(O! @q`@aN)z,O ᐺ[":@MF` j!t2Ӈ1 pA"Ae:J@ ڴ+۔8 ȇu z۵]>a|F`Jpc  @\aa]j̹B!ܠY`A{P' "a`r 'e+7S0>Bb2S' A@@P%l?5"W" A'*&օA ͐ u"X@-x AA$0a(|b`6i*_o${`JBl"f) Ɂ! P >!|zV`ix8K$CcF 2>!D==8i'``A TlbUQ%Fܻ![we HmI߿ AӲÇ ~_CP D-`ᄢhBphɓ(S\ɲ˗0cʜI$)͢Ekd=H)ZC&Ќ-p6 {0v쿃p5I:8*L #T餦ȅ CJUf E5sq1)*i0Ě '} X@)j0`} ?"kҁk\z? 0k6פ3EtMj : a;a8,B4g2spGZ"d{, 5[Й9H^ȷPDUtQF1$@DlA5,bLH4TH48c? B'I@e TD PEB `# Mj 3ZlWZLC]CP"!D4X(:IDC!4V7RXC P:E?W:a1 Ch IEW &ċ! pnЀϐH53(H@"4qA{QnD "A%.(nCְk /%)lBWh#k0bhk C ):M38`"#Ep#U4 hC VXG Cd =d:tB āl$5 )t 0g2"`LI<`a@qP c0C-`3h8B$[JX&tAC)qSD dH̠ 9`Q  )(+" fcķ&pjyh ˈsP I88i3 `"`-0 wURbl%&\+'ˊY%=ul3]!>ӎ=4pB~|c)KX%)q'P5t:?~vIMB5_0 [ I[δ7-g@B2e50NۯҖ6WV PDLo^5p"*3[]czЎvtj/G̮n&i2j8NQn;&IMtswom{'8x&W]};=9w\Eϸ3]q/#IKp)9Kr~\/8u^ˆ@3|ݔ74߽xΥS}З~u]eT:׺u<\=6Կײ|'g#=t{wY<.pY|ET]Y|CN ͅ;~#y‹|7_znwU[ϲSz;yK7>ky;_=EO1Mzc?}ϑv>__sn_&;O{G=zS'yWypз||?wcG{X~kN7}z}^<'vǀ h+yw(zՅxM[(|ȁn%׃xDz~HgW xoׄT}8v((p2ȃ8v\zDŽSIh b8]xcxJX^Ȇfixnm8fvf?X7|׉x:׈u(QxtƂ[848U臊hrO~Lj?cB>xBr&yhE(8_xH؍shȌ}'hhtȂK؊ɨ`8G捖Ȏw÷ȏȍ,8~ؐ{7 )zň|LJ'zHO7h%(*ys7:a&Zx}zY-HL9zI9舺z|zhoږZbM).e'Yyx#I'hih&gxgBg/'ٌSIt)fyq_.2k̸9Y2יٚo9טyٛ9uf+O"ę9f" YԜ<Fb5Ӡ Y.ќ̐#9eJ"߉ Is074jZ $J#Z(pР):ICRk4ڣR ;e >zYL` '԰ 2RZG):A Q:\ ׀Ѡ[ڥfpl4 M* LppQ` ° P qFPE @BƧ: e:Zzکq  K$  jZ :g"Gg`w pZIOp9&:`w K@B0jZ1$ #:Z0 ׀ AzA.k f !`?-C`@` X@KꣴRppo'Y! ;A).D  n xP N ġ@t:` ` Y;,qq  6P ` o 4?~А :0# E\X`ZQ~ PY] ! = q?` C5X`'wp ,p00[J. x0YP +ȝ pe,lK A0_\OL@KV"p [{ Y@B!q7w Lp ̠:P_f ;NOPĎ7%u,X 4#2 LAj! 2` !Z@U3#S> ĠmP 0 QsqB0CaYy@B$D+5 YK2AH%MDRJ-]9N"aJ ;!aL@BO,091(>-shX` K3fP B,YKv+R^ >|8hڟ C|6DB 2Q JIb80pW3) f?rAAD /& `3N@x!KgEU"Bĉ&,c @NYIgpE͵RJF@J&H|@+ /0C 7AYs 6DiހQ8MTSNk9`)AjDvO郄jEO eAj@ UjlQg֤lFY(7qW|X]\Fܤ怆gqiw0y~)`5VVfI(Ҥ*w 2hJ6qmgf?yyО0>"g8%Kmtuꪯ^oFU!=[;0vw pM|cH-ҍ$"0f,A$`5U|I:ŒjT + c0b.]f4 GF$&Wh?`UvV23~QxN G<4Oe?4P;f/4*P[H! 8`.&>icN15<NuVhĬgJNxh1Z2ҥ$!\!7JVRɆ'vxh-@ \b%s&:6h4#i>҄ bTy'N&I3<`Z " ,ϋ{j ig@ۀ3D~*Ŵ=-R{S L`F3p!&#U3&ڔЬs@ҁ@\'ԕ7if h܁4@4A%te W 퇃a6%L3Ԡ^4kXMCI(f<`3Td0.Pk@;72ړz5 "V'K,4)z{*h`#iEԁDCjPTFF }*Tra؁5Q~& V2˘D0[d it! p#4[AB Mi |I4xj 3El.@ ҉6O-8n[HAA@aOvMԦi`0|z^aF)! jje=ā( F(:f0sa hF2"gtK4 ` vBQ gx"nIFgD@ h#YK5@6uӑ)f*'qu3A 9:/ %4UȒ*MNa88lsT7(bXF2N P$,5.j lKQ k {ESnBKB;HmBȄja{5@ '5(wl%B% BCq5pk !| :pH:bF0-Т%[&D#P"/hbF`Q$Q#E"j&>-]hF$p0Ah',g5C_+6oHfX W F51L VMpa ?|UDp Xj ``YE!&l!!B1 T8Xj" 1J1AA PYo,A\ %CA1T;I@`% X hJ""X [?؀!> xY" @"ڀ$l˹K( @ p7!( K h @-* d,@%@lp7|> 9* C " 9 C?90:9ĔCB,CDR  xDϲCID9a%DM\JN40CTœDLX|EZDLd1\|8f#38Dd Qf\ |FZ;Zh6eeTmԐ߂UH%g$r4sDGϚktd%AkyȀH)8 !gc A)0$Fgn{ȍȎH8f8HPMȼ-A ɴShEh jȂSɚɛ$G(V8lapHBThԅ5?tʧJ 8,x9x&J #ЂT&Ů4˳DKq X3LK Y˶\K `ś؈q P KoAL| 9TLCuUR#e%$MO3 &?'(t+͐t-*T)5P&XI$-0plT"F`#=MSrg`ih*jmFH%EeFMGuIe֑ L='$% dp(Ќ 1U AUUt0) X| HEȆ> V؀ ,ermVmĀ<p oTqtT "]Ypf*#UƤXWGXUxX1F[ @X,쀗]֘X蓈x7„؀IrVuLTYٜEQԖUxi Jpڏ?0hpo1ڄ]ؔ u[}d ZZqyHZ-WoWLܗ ]ۺܗ[%b+HUMוZe`MEEړZ)J]5Õ (hh`q( U,Z-Gm+[P;"[   dsphp_kK)@Q%OSaPԕ 1fM`(_ 8k}EK_ >]DWOgGHL'~fd-Gr#w.=W&PЂCf- 2V?H=MńQB,hpEH i\[3cYuMl0:ur6. R-0Q;,1$T$mQ_1``gVjixf:8]RԫCxa10`$Hj)-iyFW;YEWh2_S<_ exdh-CN0dhuW{7ʼ3{^-01&cfp Xz(:mF575YКF(Q97+S Tq4C HEfduvi&Pp7xiPP'hex7&xU [a  P#jth?H?(iQ,^3@$v`%8gXV<YHizFiI8 k'`tNb!jIP(MS8 ؐ(^ـRT7C88?xX܄PAxigȂC6̒"JFЏԉ hjxhkU{"XgeLk%p-sbc)rx `]+ЉGb ܀kg>ZOD~p0xUj#(tyh kjhpoH?HD8x+8o#0n8i !8g~H䘐Y3)D92qBjJDHքB9- d,hR p4DM< I6.4$ԀxA ."A3@QG%A" 00NA&W^19n"Zx+*JpEW[ BPI5\4È6ГeF3xh54$A90pmWqpDsgU3YTP"P?šN%pH6xpxV8| Ե5 GhED |2DrX81rL.qM!4RhpD0,̺/֨}9%طi `dq6FU+X[`#`)1衋~l g[Qy> cd} +AvcR65,B ,PsolH$ ^貟0lִ A :h21Z00$ Ą,GK $2! %P` _&p-<1H@$m/~כ hh! - d 47%] HCx4>@K0>7%2L!Ä<#uꞣ;QIPC[D@ @58LH3 ?C Z84QNq,"G%$b9 k< ´=$ Ia 5"x #C"/APG Z) jlb Y u4W4a?eVG3(%!e$ ?d ;M4Ѐl `5-܁ ą27!xC5s'@cJp0$N:fND `ոB) ͸5 '$M: `kdFEHAA-Bٸƚ`R+&mƘO\!X3 lTx;!e `iZ^M)xCZ`k@vZciЌg@a/Q4xhհ6 " BdD i L~!bic83({i,C2bC41\ . ?YAqnPDNԁ 0 Ʌ\XP. v8шF(qB N%MhbCa-oPXbD+`a9 Nǃ@"`qV?<Y]84"1I@>q1 D#` ?q$qiBV?A |%2F8pd3;p`ZD>#4 -@ "hs@ ~1(`>4{ُh@a|, .Y@´ ;C64eVt6pM#L6EJ[nʡ94$I0LB  `8Wl ЮA !~`pMaD4@K9؂.A ?t>PA PO"OEXN7vlӬ"k<#& !|J 5>kqwX? iE ۮmA@J.@$ ?1ǥ Vz8uL PE.& f"{p0hx9;'8@#|s`7ow+7p0c9a?> IHD|  hDlFc xLӯ3] 3"`{{Wo3" B̀44( צ_%=u9 oD"D@!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W!,W;sptlrx-1.3.1/go.mod000066400000000000000000000031201516447074200141640ustar00rootroot00000000000000module github.com/raitonoberu/sptlrx go 1.26.1 require ( github.com/Pauloo27/go-mpris v1.4.0 github.com/adrg/xdg v0.5.3 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 github.com/coder/websocket v1.8.14 github.com/creasty/defaults v1.8.0 github.com/fhs/gompd v1.0.1 github.com/godbus/dbus/v5 v5.2.2 github.com/muesli/reflow v0.3.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/spf13/cobra v1.10.2 golang.org/x/term v0.41.0 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.3.8 // indirect ) sptlrx-1.3.1/go.sum000066400000000000000000000172351516447074200142250ustar00rootroot00000000000000github.com/Pauloo27/go-mpris v1.4.0 h1:KWNTZuXeOdOdIVdzwG/JOOZHlveNiMjiSaK0AWi220c= github.com/Pauloo27/go-mpris v1.4.0/go.mod h1:+9otYxTLPRTVZ6i2k6VrG1Y0RzMbBXGuEUQM4ZSvjxU= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fhs/gompd v1.0.1 h1:kBcAhjnAPJQAylZXR0TeH+d2vpjawXlTtKYguqNlF4A= github.com/fhs/gompd v1.0.1/go.mod h1:b219/mNa9PvRqvkUip51b23hGL3iX4d4q3gNXdtrD04= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= sptlrx-1.3.1/lyrics/000077500000000000000000000000001516447074200143675ustar00rootroot00000000000000sptlrx-1.3.1/lyrics/lyrics.go000066400000000000000000000017011516447074200162220ustar00rootroot00000000000000package lyrics import ( "strconv" "strings" ) type Provider interface { Lyrics(artist, track string) ([]Line, error) } type Line struct { Time int `json:"time"` Words string `json:"words"` } func Timesynced(lines []Line) bool { return len(lines) > 1 && lines[1].Time != 0 } func IsTimestampLine(line string) bool { if len(line) < 10 { return false } return line[0] == '[' && line[1] >= '0' && line[1] <= '9' && line[2] >= '0' && line[2] <= '9' && line[3] == ':' && line[6] == '.' && strings.IndexByte(line, ']') > 6 } func ParseLrcLine(line string) Line { m, _ := strconv.Atoi(line[1:3]) s, _ := strconv.Atoi(line[4:6]) closeBracket := strings.IndexByte(line, ']') msStr := line[7:closeBracket] ms, _ := strconv.Atoi(msStr) if len(msStr) == 2 { ms *= 10 } else if len(msStr) == 1 { ms *= 100 } words := strings.TrimSpace(line[closeBracket+1:]) return Line{ Time: m*60*1000 + s*1000 + ms, Words: words, } } sptlrx-1.3.1/lyrics/lyrics_test.go000066400000000000000000000041761516447074200172720ustar00rootroot00000000000000package lyrics import ( "reflect" "testing" ) func TestIsTimestampLine(t *testing.T) { tests := []struct { name string input string expected bool }{ // positive {"valid 1-digit", "[01:02.3] lyrics", true}, {"valid 2-digit", "[01:02.34] lyrics", true}, {"valid 3-digit", "[01:02.345] lyrics", true}, {"valid without space", "[01:02.34]lyrics", true}, {"valid empty", "[01:02.34]", true}, // negative {"missing ms", "[01:02]", false}, {"missing leading bracket", "01:02.34] lyrics", false}, {"missing closing bracket", "[01:02.34 lyrics", false}, // tags {"title tag", "[ti: Song Title]", false}, {"artist tag", "[ar: Artist Name]", false}, {"offet tag", "[offset:0]", false}, // edge cases {"empty string", "", false}, {"just brackets", "[]", false}, {"spaces before bracket", " [01:02.34]", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsTimestampLine(tt.input) if result != tt.expected { t.Errorf("IsTimestampLine(%q) = %v; want %v", tt.input, result, tt.expected) } }) } } func TestParseLrcLine(t *testing.T) { tests := []struct { name string input string expected Line }{ { name: "1-digit precision", input: "[00:01.5] lyrics", expected: Line{Time: 1500, Words: "lyrics"}, }, { name: "2-digit precision", input: "[01:02.34] lyrics", expected: Line{Time: 62340, Words: "lyrics"}, }, { name: "3-digit precision", input: "[00:10.500] lyrics", expected: Line{Time: 10500, Words: "lyrics"}, }, { name: "empty", input: "[00:05.00]", expected: Line{Time: 5000, Words: ""}, }, { name: "with spaces", input: "[00:02.00] lyrics ", expected: Line{Time: 2000, Words: "lyrics"}, }, { name: "large", input: "[99:00.00] lyrics", expected: Line{Time: 5940000, Words: "lyrics"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ParseLrcLine(tt.input) if !reflect.DeepEqual(result, tt.expected) { t.Errorf("ParseLrcLine(%q) = %+v; want %+v", tt.input, result, tt.expected) } }) } } sptlrx-1.3.1/main.go000066400000000000000000000001311516447074200143300ustar00rootroot00000000000000package main import "github.com/raitonoberu/sptlrx/cmd" func main() { cmd.Execute() } sptlrx-1.3.1/man/000077500000000000000000000000001516447074200136355ustar00rootroot00000000000000sptlrx-1.3.1/man/sptlrx.5000066400000000000000000000045031516447074200152610ustar00rootroot00000000000000.nh .TH sptlrx 5 .SH NAME \fBsptlrx\fP \- Synchronized lyrics in your terminal .SH LOCATION The config file will be created at the first launch. It is located in \fB~/.config/sptlrx/config.yaml\fR\&. Run sptlrx -h to see the full path. .SH SPOTIFY .SS FORMAT .EX # config.yaml player: spotify .EE .SS NOTES If you want to use Spotify as your player, you will need to log in first. .IP " 1." 5 Go to developer.spotify.com \[la]https://developer.spotify.com/dashboard\[ra], create a new app, and set the redirect URI to \fBhttp://127.0.0.1:8888/callback\fR\&. Grab your Client ID and Client Secret. .IP " 2." 5 Run \fBsptlrx login\fR\&. You can pass Client ID and Client Secret in one of three ways: .RS .IP \(bu 2 As environmental variables: \fBSPOTIFY_CLIENT_ID\fR and \fBSPOTIFY_CLIENT_SECRET\fR .IP \(bu 2 As CLI parameters: \fB--client-id\fR and \fB--client-secret\fR .IP \(bu 2 Interactively: run \fBsptlrx login\fR without providing credentials and you will be prompted to enter them .RE .IP " 3." 5 Spotify login page will open. Log in and wait for the success message. .PP You only need to do this once. Your credentials will then be saved to \fB$XDG_STATE_HOME/sptlrx/spotify-auth.json\fR\&. .SH MPD .SS FORMAT .EX # config.yaml player: mpd mpd: address: 127.0.0.1:6600 password: "" .EE .SH MOPIDY .SS FORMAT .EX # config.yaml player: mopidy mopidy: address: 127.0.0.1:6680 .EE .SH MPRIS .SS FORMAT .EX # config.yaml player: mpris mpris: players: [] .EE .SS NOTES System player that supports MPRIS protocol will be used. You can also specify a whitelist of players to use, example: \fBplayers: [rhythmbox, spotifyd, ncspot]\fR\&. Run \fBplayerctl -l\fR to get the names. .SH BROWSER .SS FORMAT .EX # config.yaml player: browser browser: port: 8974 .EE .SS NOTES You need to install a browser extension \[la]https://wnp.keifufu.dev/extension/getting\-started\[ra]\&. If you don't change the default port, no further configuration is required. Otherwise, create a custom adapter in the extension settings. \fBYou can only run one instance on one port.\fP .SH LOCAL .SS FORMAT .EX # config.yaml local: folder: "" .EE .SS NOTES If you want to use your local collection of \fB\&.lrc\fR files to display lyrics, specify the folder to scan. The application will use files with the most similar name. All other lyrics sources will be disabled. sptlrx-1.3.1/man/sptlrx.5.md000066400000000000000000000042761516447074200156670ustar00rootroot00000000000000sptlrx 5 ======== ## NAME **sptlrx** - Synchronized lyrics in your terminal ## LOCATION The config file will be created at the first launch. It is located in `~/.config/sptlrx/config.yaml`. Run sptlrx -h to see the full path. ## SPOTIFY ### FORMAT ``` # config.yaml player: spotify ``` ### NOTES If you want to use Spotify as your player, you will need to log in first. 1. Go to [developer.spotify.com](https://developer.spotify.com/dashboard), create a new app, and set the redirect URI to `http://127.0.0.1:8888/callback`. Grab your Client ID and Client Secret. 2. Run `sptlrx login`. You can pass Client ID and Client Secret in one of three ways: - As environmental variables: `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` - As CLI parameters: `--client-id` and `--client-secret` - Interactively: run `sptlrx login` without providing credentials and you will be prompted to enter them 3. Spotify login page will open. Log in and wait for the success message. You only need to do this once. Your credentials will then be saved to `$XDG_STATE_HOME/sptlrx/spotify-auth.json`. ## MPD ### FORMAT ``` # config.yaml player: mpd mpd: address: 127.0.0.1:6600 password: "" ``` ## MOPIDY ### FORMAT ``` # config.yaml player: mopidy mopidy: address: 127.0.0.1:6680 ``` ## MPRIS ### FORMAT ``` # config.yaml player: mpris mpris: players: [] ``` ### NOTES System player that supports MPRIS protocol will be used. You can also specify a whitelist of players to use, example: `players: [rhythmbox, spotifyd, ncspot]`. Run `playerctl -l` to get the names. ## BROWSER ### FORMAT ``` # config.yaml player: browser browser: port: 8974 ``` ### NOTES You need to install a [browser extension](https://wnp.keifufu.dev/extension/getting-started). If you don't change the default port, no further configuration is required. Otherwise, create a custom adapter in the extension settings. **You can only run one instance on one port.** ## LOCAL ### FORMAT ``` # config.yaml local: folder: "" ``` ### NOTES If you want to use your local collection of `.lrc` files to display lyrics, specify the folder to scan. The application will use files with the most similar name. All other lyrics sources will be disabled. sptlrx-1.3.1/player/000077500000000000000000000000001516447074200143565ustar00rootroot00000000000000sptlrx-1.3.1/player/player.go000066400000000000000000000005571516447074200162100ustar00rootroot00000000000000package player type Player interface { State() (*State, error) } type State struct { // ID of the current track. ID string // Artist is the name of the artist(s). Artist string // Track is the name of the track. Track string // Position of the current track in ms. Position int // Playing means whether the track is playing at the moment. Playing bool } sptlrx-1.3.1/pool/000077500000000000000000000000001516447074200140335ustar00rootroot00000000000000sptlrx-1.3.1/pool/pool.go000066400000000000000000000046151516447074200153410ustar00rootroot00000000000000package pool import ( "github.com/raitonoberu/sptlrx/config" "github.com/raitonoberu/sptlrx/lyrics" "github.com/raitonoberu/sptlrx/player" "time" ) // Update represents the state of the lyrics. type Update struct { Lines []lyrics.Line Index int Playing bool Err error } // Listen polls for lyrics updates and writes them to the channel. func Listen( player player.Player, provider lyrics.Provider, conf *config.Config, ch chan Update, ) { stateCh := make(chan playerState) go listenPlayer(player, stateCh, conf.UpdateInterval) ticker := time.NewTicker( time.Millisecond * time.Duration(conf.TimerInterval), ) var ( state playerState index int lines []lyrics.Line lastUpdate time.Time ) for { changed := false select { case newState := <-stateCh: lastUpdate = time.Now() if newState.ID != state.ID { changed = true if newState.ID != "" { newLines, err := provider.Lyrics(newState.Artist, newState.Track) if err != nil { state.Err = err } lines = newLines } else { lines = nil } index = 0 } if newState.Playing != state.Playing { changed = true } state = newState case <-ticker.C: if !state.Playing || !lyrics.Timesynced(lines) { break } now := time.Now() state.Position += int(now.Sub(lastUpdate).Milliseconds()) lastUpdate = now } newIndex := getIndex(state.Position, index, lines) if newIndex != index { changed = true index = newIndex } if changed { ch <- Update{ Lines: lines, Index: index, Playing: state.Playing, Err: state.Err, } } } } type playerState struct { player.State Err error } func listenPlayer(player player.Player, ch chan playerState, interval int) { for { state, err := player.State() st := playerState{Err: err} if state != nil { st.State = *state } ch <- st time.Sleep(time.Millisecond * time.Duration(interval)) } } // getIndex is an effective algorithm to get current line's index func getIndex(position, curIndex int, lines []lyrics.Line) int { if len(lines) <= 1 { return 0 } if position >= lines[curIndex].Time { // search after for i := curIndex + 1; i < len(lines); i++ { if position < lines[i].Time { return i - 1 } } return len(lines) - 1 } // search before for i := curIndex; i > 0; i-- { if position > lines[i].Time { return i } } return 0 } sptlrx-1.3.1/pool/pool_test.go000066400000000000000000000015431516447074200163750ustar00rootroot00000000000000package pool import ( "math" "testing" "github.com/raitonoberu/sptlrx/services/lrclib" ) func TestGetIndex(t *testing.T) { service := lrclib.New() lines, err := service.Lyrics("Death Grips", "No Love") if err != nil { t.Fatal(err) } test := func(pos, curIndex, expected int) { if index := getIndex(pos, curIndex, lines); index != expected { t.Errorf("failed getting index for pos %d with curIndex %d: expected %d got %d", pos, curIndex, expected, index) } } for i := 0; i < len(lines)-1; i++ { line := lines[i] dif := lines[i+1].Time - line.Time pos := line.Time + (dif / 2) for j := 0; j < len(lines); j++ { test(pos, j, i) } } // edge cases test(0, 0, 0) // 0 if pos == 0 test(lines[0].Time-1, 0, 0) // 0 if pos < first.Time test(math.MaxInt, 0, len(lines)-1) // last if pos > last.Time } sptlrx-1.3.1/services/000077500000000000000000000000001516447074200147055ustar00rootroot00000000000000sptlrx-1.3.1/services/browser/000077500000000000000000000000001516447074200163705ustar00rootroot00000000000000sptlrx-1.3.1/services/browser/browser.go000066400000000000000000000053571516447074200204140ustar00rootroot00000000000000package browser import ( "fmt" "github.com/raitonoberu/sptlrx/player" "io" "net" "net/http" "strconv" "strings" "sync" "time" "github.com/coder/websocket" ) const helloMessage = "ADAPTER_VERSION 1.0.0;WNPRLIB_REVISION 2" type state int const ( stopped state = iota paused playing ) func New(port int) (*Client, error) { c := &Client{} return c, c.start(port) } // Client implements player.Player type Client struct { state state position int title string artist string updateTime time.Time stateMu sync.Mutex connMu sync.Mutex } func (c *Client) handler(w http.ResponseWriter, r *http.Request) { // make sure we only have one connection c.connMu.Lock() defer c.connMu.Unlock() conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{ InsecureSkipVerify: true, }) if err != nil { return } defer conn.Close(websocket.StatusInternalError, "internal error") writer, err := conn.Writer(r.Context(), websocket.MessageText) if err != nil { return } writer.Write([]byte(helloMessage)) writer.Close() for { t, reader, err := conn.Reader(r.Context()) if err != nil { return } msg, err := io.ReadAll(reader) if err != nil { return } if t != websocket.MessageText || len(msg) == 0 { continue } c.processMessage(string(msg)) } } func (c *Client) processMessage(msg string) { spaceIndex := strings.IndexByte(msg, ' ') if spaceIndex == -1 { return } msgType := strings.ToUpper(msg[:spaceIndex]) data := msg[spaceIndex+1:] // we are not doing global locking here because // we are not interested in most of the messages switch msgType { case "STATE": c.stateMu.Lock() switch data { case "PLAYING": c.state = playing case "PAUSED": c.state = paused case "STOPPED": c.state = stopped } c.stateMu.Unlock() case "TITLE": c.stateMu.Lock() c.title = data c.stateMu.Unlock() case "ARTIST": c.stateMu.Lock() c.artist = data c.stateMu.Unlock() case "POSITION_SECONDS": pos, _ := strconv.Atoi(data) c.stateMu.Lock() c.position = pos * 1000 c.updateTime = time.Now() c.stateMu.Unlock() } } func (c *Client) start(port int) error { l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { return err } server := &http.Server{ Handler: http.HandlerFunc(c.handler), } go server.Serve(l) return nil } func (c *Client) State() (*player.State, error) { c.stateMu.Lock() defer c.stateMu.Unlock() if c.state == stopped { return nil, nil } id := c.artist + " " + c.title position := c.position if c.state != paused { position += int(time.Since(c.updateTime).Milliseconds()) } return &player.State{ ID: id, Artist: c.artist, Track: c.title, Position: position, Playing: c.state == playing, }, nil } sptlrx-1.3.1/services/local/000077500000000000000000000000001516447074200157775ustar00rootroot00000000000000sptlrx-1.3.1/services/local/local.go000066400000000000000000000043421516447074200174230ustar00rootroot00000000000000package local import ( "bufio" "fmt" "io" "io/fs" "os" "path/filepath" "strings" "github.com/raitonoberu/sptlrx/lyrics" ) var replacer = strings.NewReplacer( "_", " ", "-", " ", ",", "", ".", "", "!", "", "?", "", "(", "", ")", "", "[", "", "]", "", ) type file struct { Path string NameParts []string } func New(folder string) (*Client, error) { index, err := createIndex(folder) if err != nil { return nil, err } return &Client{index: index}, nil } // Client implements lyrics.Provider type Client struct { index []*file } func (c *Client) Lyrics(artist, track string) ([]lyrics.Line, error) { query := artist + " " + track f := c.findFile(query) if f == nil { return nil, nil } reader, err := os.Open(f.Path) if err != nil { return nil, err } defer reader.Close() return parseLrcFile(reader), nil } func (c *Client) findFile(query string) *file { parts := splitString(query) var best *file var maxScore int for _, f := range c.index { var score int for _, part := range parts { for _, namePart := range f.NameParts { if namePart == part { score++ break } } } if score > maxScore { maxScore = score best = f if score == len(parts) { break } } } return best } func createIndex(folder string) ([]*file, error) { if strings.HasPrefix(folder, "~/") { dirname, _ := os.UserHomeDir() folder = filepath.Join(dirname, folder[2:]) } index := []*file{} return index, filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error { if d == nil { return fmt.Errorf("invalid path: %s", path) } if d.IsDir() || !strings.HasSuffix(d.Name(), ".lrc") { return nil } name := strings.TrimSuffix(d.Name(), ".lrc") parts := splitString(name) index = append(index, &file{ Path: path, NameParts: parts, }) return nil }) } func splitString(s string) []string { s = strings.ToLower(s) s = replacer.Replace(s) return strings.Fields(s) } func parseLrcFile(reader io.Reader) []lyrics.Line { result := []lyrics.Line{} scanner := bufio.NewScanner(reader) for scanner.Scan() { line := scanner.Text() if !lyrics.IsTimestampLine(line) { continue } result = append(result, lyrics.ParseLrcLine(line)) } return result } sptlrx-1.3.1/services/lrclib/000077500000000000000000000000001516447074200161545ustar00rootroot00000000000000sptlrx-1.3.1/services/lrclib/lrclib.go000066400000000000000000000052321516447074200177540ustar00rootroot00000000000000package lrclib import ( "context" "encoding/json" "net/http" "net/url" "strings" "time" "github.com/raitonoberu/sptlrx/lyrics" ) const userAgent = "sptlrx v1.0.0 (https://github.com/raitonoberu/sptlrx)" func New() *Client { return &Client{} } type Client struct { http http.Client } // Client implements lyrics.Provider func (c *Client) Lyrics(artist, track string) ([]lyrics.Line, error) { if artist != "" && track != "" { return c.get(artist, track) } return c.search(artist + track) } func (c *Client) get(artist, track string) ([]lyrics.Line, error) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() u := "https://lrclib.net/api/get?" + url.Values{ "artist_name": {artist}, "track_name": {track}, }.Encode() req, err := http.NewRequestWithContext(ctx, "GET", u, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", userAgent) resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, nil } var response lrclibTrack err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { return nil, err } return parseTrack(response), nil } func (c *Client) search(query string) ([]lyrics.Line, error) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() u := "https://lrclib.net/api/search?" + url.Values{ "q": {query}, }.Encode() req, err := http.NewRequestWithContext(ctx, "GET", u, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", userAgent) resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var response []lrclibTrack err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { return nil, err } if len(response) == 0 { return nil, nil } return parseTrack(response[0]), nil } type lrclibTrack struct { PlainLyrics string `json:"plainLyrics"` SyncedLyrics string `json:"syncedLyrics"` } func parseTrack(t lrclibTrack) []lyrics.Line { if t.SyncedLyrics != "" { return parseSynced(t) } if t.PlainLyrics != "" { return parsePlain(t) } return nil } func parseSynced(r lrclibTrack) []lyrics.Line { lines := strings.Split(r.SyncedLyrics, "\n") result := make([]lyrics.Line, 0, len(lines)) for _, line := range lines { if !lyrics.IsTimestampLine(line) { continue } result = append(result, lyrics.ParseLrcLine(line)) } return result } func parsePlain(r lrclibTrack) []lyrics.Line { lines := strings.Split(r.PlainLyrics, "\n") result := make([]lyrics.Line, len(lines)) for i, line := range lines { result[i] = lyrics.Line{Words: line} } return result } sptlrx-1.3.1/services/mopidy/000077500000000000000000000000001516447074200162065ustar00rootroot00000000000000sptlrx-1.3.1/services/mopidy/mopidy.go000066400000000000000000000037221516447074200200420ustar00rootroot00000000000000package mopidy import ( "bytes" "encoding/json" "fmt" "github.com/raitonoberu/sptlrx/player" "net/http" ) func New(address string) *Client { return &Client{address: address} } // Client implements player.Player type Client struct { address string } func (c *Client) get(method string, out interface{}) error { body := requestBody{ JsonRPC: "2.0", ID: 1, Method: method, } bodyBytes, err := json.Marshal(body) if err != nil { return err } url := fmt.Sprintf("http://%s/mopidy/rpc", c.address) req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() return json.NewDecoder(resp.Body).Decode(out) } func (c *Client) State() (*player.State, error) { var state stateResponse err := c.get("core.playback.get_state", &state) if err != nil { return nil, err } var current currentResponse err = c.get("core.playback.get_current_track", ¤t) if err != nil { return nil, err } var position positionResponse err = c.get("core.playback.get_time_position", &position) if err != nil { return nil, err } var artist string for i, a := range current.Result.Artists { if i != 0 { artist += " " } artist += a.Name } return &player.State{ ID: current.Result.URI, Artist: artist, Track: current.Result.Name, Position: position.Result, Playing: state.Result == "playing", }, err } type requestBody struct { JsonRPC string `json:"jsonrpc"` ID int `json:"id"` Method string `json:"method"` } type currentResponse struct { Result struct { URI string `json:"uri"` Name string `json:"name"` Artists []struct { Name string `json:"name"` } `json:"artists"` } `json:"result"` } type stateResponse struct { Result string `json:"result"` } type positionResponse struct { Result int `json:"result"` } sptlrx-1.3.1/services/mpd/000077500000000000000000000000001516447074200154655ustar00rootroot00000000000000sptlrx-1.3.1/services/mpd/mpd.go000066400000000000000000000025051516447074200165760ustar00rootroot00000000000000package mpd import ( "strconv" "github.com/raitonoberu/sptlrx/player" "github.com/fhs/gompd/mpd" ) func New(address, password string) *Client { return &Client{ address: address, password: password, } } // Client implements player.Player type Client struct { address string password string client *mpd.Client } func (c *Client) connect() error { if c.client != nil { c.client.Close() } client, err := mpd.DialAuthenticated("tcp", c.address, c.password) if err != nil { c.client = nil return err } c.client = client return nil } func (c *Client) checkConnection() error { if c.client == nil || c.client.Ping() != nil { return c.connect() } return nil } func (c *Client) State() (*player.State, error) { if err := c.checkConnection(); err != nil { return nil, err } status, err := c.client.Status() if err != nil { return nil, err } current, err := c.client.CurrentSong() if err != nil { return nil, err } elapsed, _ := strconv.ParseFloat(status["elapsed"], 32) var title string if t, ok := current["Title"]; ok { title = t } var artist string if a, ok := current["Artist"]; ok { artist = a } return &player.State{ ID: status["songid"], Artist: artist, Track: title, Playing: status["state"] == "play", Position: int(elapsed * 1000), // secs to ms }, nil } sptlrx-1.3.1/services/mpris/000077500000000000000000000000001516447074200160375ustar00rootroot00000000000000sptlrx-1.3.1/services/mpris/mpris_unix.go000066400000000000000000000042601516447074200205650ustar00rootroot00000000000000//go:build !(windows || darwin) package mpris import ( "github.com/raitonoberu/sptlrx/player" "net/url" "path/filepath" "strings" "github.com/Pauloo27/go-mpris" "github.com/godbus/dbus/v5" ) func New(players []string) (*Client, error) { return &Client{players}, nil } // Client implements player.Player type Client struct { players []string } func (c *Client) getPlayer() (*mpris.Player, error) { conn, err := dbus.SessionBus() if err != nil { return nil, err } players, err := mpris.List(conn) if err != nil { return nil, err } if len(players) == 0 { return nil, nil } if len(c.players) == 0 { return mpris.New(conn, players[0]), nil } // iterating over configured whitelisted players for _, p := range c.players { // adding the D-Bus bus name prefix p := "org.mpris.MediaPlayer2." + p for _, player := range players { // check for the name with and without the instance suffix if p == player || strings.HasPrefix(player, p+".instance") { return mpris.New(conn, player), nil } } } return nil, nil } func (c *Client) State() (*player.State, error) { p, err := c.getPlayer() if err != nil { return nil, err } if p == nil { return nil, nil } status, err := p.GetPlaybackStatus() if err != nil { return nil, err } position, err := p.GetPosition() if err != nil { // unsupported player return nil, nil } meta, err := p.GetMetadata() if err != nil { return nil, err } var title string if t, ok := meta["xesam:title"].Value().(string); ok { title = t } // In case the player uses the file name with extension as title if u, ok := meta["xesam:url"].Value().(string); ok { u, err := url.Parse(u) if err == nil { ext := filepath.Ext(u.Path) title = strings.TrimSuffix(title, ext) } } var artist string switch a := meta["xesam:artist"].Value(); a.(type) { case string: artist = a.(string) case []string: artist = strings.Join(a.([]string), " ") } id := artist + " " + title return &player.State{ ID: id, // use artist+title as id since mpris:trackid is broken Artist: artist, Track: title, Position: int(position * 1000), // secs to ms Playing: status == mpris.PlaybackPlaying, }, err } sptlrx-1.3.1/services/mpris/mpris_unsupported.go000066400000000000000000000005151516447074200221710ustar00rootroot00000000000000//go:build windows || darwin package mpris import ( "errors" "github.com/raitonoberu/sptlrx/player" ) func New(players []string) (*Client, error) { return nil, errors.New("darwin is not supported") } // Client implements player.Player type Client struct{} func (p *Client) State() (*player.State, error) { return nil, nil } sptlrx-1.3.1/services/spotify/000077500000000000000000000000001516447074200164025ustar00rootroot00000000000000sptlrx-1.3.1/services/spotify/auth/000077500000000000000000000000001516447074200173435ustar00rootroot00000000000000sptlrx-1.3.1/services/spotify/auth/auth.go000066400000000000000000000077211516447074200206420ustar00rootroot00000000000000package auth import ( "context" "encoding/json" "errors" "fmt" "net/http" "net/url" "os" "strings" "time" "github.com/adrg/xdg" ) const authPath = "sptlrx/spotify-auth.json" type Auth struct { ClientId string `json:"client_id"` ClientSecret string `json:"client_secret"` AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresAt time.Time `json:"expires_at"` } func New(clientId, clientSecret string) *Auth { return &Auth{ ClientId: clientId, ClientSecret: clientSecret, } } func Load() (*Auth, error) { path, err := xdg.StateFile(authPath) if err != nil { return nil, err } f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() auth := Auth{} if err := json.NewDecoder(f).Decode(&auth); err != nil { return nil, err } return &auth, nil } func (a *Auth) GetAuthUrl(port int) string { return "https://accounts.spotify.com/authorize?" + url.Values{ "client_id": {a.ClientId}, "response_type": {"code"}, "redirect_uri": {getRedirectUri(port)}, "scope": {"user-read-currently-playing user-read-playback-state"}, }.Encode() } func (a *Auth) Login(ctx context.Context, port int) error { code, err := getAuthCode(ctx, port) if err != nil { return err } return a.refresh(ctx, url.Values{ "grant_type": {"authorization_code"}, "code": {code}, "redirect_uri": {getRedirectUri(port)}, }) } func (a *Auth) GetToken(ctx context.Context) (string, error) { if time.Until(a.ExpiresAt) > 5*time.Second { return a.AccessToken, nil } if a.RefreshToken == "" { return "", errors.New("refresh_token can't be empty") } if err := a.refresh(ctx, url.Values{ "grant_type": {"refresh_token"}, "refresh_token": {a.RefreshToken}, }); err != nil { return "", err } return a.AccessToken, a.Write() } func (a *Auth) Write() error { path, err := xdg.StateFile(authPath) if err != nil { return err } f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { return err } defer f.Close() return json.NewEncoder(f).Encode(a) } func (a *Auth) refresh(ctx context.Context, form url.Values) error { if a.ClientId == "" || a.ClientSecret == "" { return errors.New("client_id & client_secret can't be empty") } reqBody := strings.NewReader(form.Encode()) req, _ := http.NewRequestWithContext(ctx, "POST", "https://accounts.spotify.com/api/token", reqBody) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.SetBasicAuth(a.ClientId, a.ClientSecret) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 400 { return fmt.Errorf("status code %d", resp.StatusCode) } var response refreshResponse if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return err } a.AccessToken = response.AccessToken a.ExpiresAt = time.Now().Add(time.Second * time.Duration(response.ExpiresIn)) if response.RefreshToken != "" { a.RefreshToken = response.RefreshToken } return nil } func getRedirectUri(port int) string { return fmt.Sprintf("http://127.0.0.1:%d/callback", port) } func getAuthCode(ctx context.Context, port int) (string, error) { mux := http.NewServeMux() ctx, cancel := context.WithCancel(ctx) var code string mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { code = r.URL.Query().Get("code") if code == "" { w.WriteHeader(400) w.Write([]byte("error! no code in query")) return } w.Write([]byte("success! you can close this tab")) cancel() }) server := http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: mux, } go func() { <-ctx.Done() server.Close() }() if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { return "", err } return code, nil } type refreshResponse struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` } sptlrx-1.3.1/services/spotify/spotify.go000066400000000000000000000036051516447074200204320ustar00rootroot00000000000000package spotify import ( "context" "encoding/json" "errors" "fmt" "net/http" "os" "strings" "time" "github.com/raitonoberu/sptlrx/player" "github.com/raitonoberu/sptlrx/services/spotify/auth" ) func New() (*Client, error) { auth, err := auth.Load() if errors.Is(err, os.ErrNotExist) { err = errors.New("you must run `sptlrx login` first to use Spotify as a player") } if err != nil { return nil, err } return &Client{ auth: auth, }, nil } // Client implements player.Player type Client struct { auth *auth.Auth http http.Client } func (c *Client) State() (*player.State, error) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() token, err := c.auth.GetToken(ctx) if err != nil { return nil, err } req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.spotify.com/v1/me/player/currently-playing", nil) req.Header.Add("Authorization", "Bearer "+token) resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode == http.StatusNoContent { return nil, nil } if resp.StatusCode >= 400 { return nil, fmt.Errorf("status code %d", resp.StatusCode) } var state state if err := json.NewDecoder(resp.Body).Decode(&state); err != nil { return nil, err } var b strings.Builder for i, a := range state.Item.Artists { if i != 0 { b.WriteByte(' ') } b.WriteString(a.Name) } return &player.State{ ID: state.Item.ID, Artist: b.String(), Track: state.Item.Name, Position: state.ProgressMs, Playing: state.IsPlaying, }, nil } type state struct { IsPlaying bool `json:"is_playing"` ProgressMs int `json:"progress_ms"` Item track `json:"item"` } type track struct { ID string `json:"id"` Name string `json:"name"` Artists []trackArtist `json:"artists"` } type trackArtist struct { Name string `json:"name"` } sptlrx-1.3.1/sptlrx.tape000066400000000000000000000003141516447074200152670ustar00rootroot00000000000000Output demo.gif Require sptlrx Set TypingSpeed 100ms Set FontSize 27 Set Padding 40 Set FontFamily "mononoki" Set Theme "Rose Pine" Set Width 800 Set Height 600 Type "sptlrx" Sleep 1s Enter Sleep 15s sptlrx-1.3.1/ui/000077500000000000000000000000001516447074200134775ustar00rootroot00000000000000sptlrx-1.3.1/ui/ui.go000066400000000000000000000077241516447074200144550ustar00rootroot00000000000000package ui import ( "github.com/raitonoberu/sptlrx/config" "github.com/raitonoberu/sptlrx/lyrics" "github.com/raitonoberu/sptlrx/pool" "os" "runtime" "strings" tea "github.com/charmbracelet/bubbletea" gloss "github.com/charmbracelet/lipgloss" "golang.org/x/term" ) type Model struct { Config *config.Config Channel chan pool.Update state pool.Update w, h int styleBefore gloss.Style styleCurrent gloss.Style styleAfter gloss.Style hAlignment gloss.Position } func (m *Model) Init() tea.Cmd { m.styleBefore = m.Config.Style.Before.Parse() m.styleCurrent = m.Config.Style.Current.Parse() m.styleAfter = m.Config.Style.After.Parse() switch m.Config.Style.HAlignment { case "left": m.hAlignment = 0 case "right": m.hAlignment = 1 default: m.hAlignment = 0.5 } return tea.Batch(waitForUpdate(m.Channel), tea.HideCursor) } func (m *Model) Update(message tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := message.(type) { case tea.WindowSizeMsg: // does not work on Windows! m.w, m.h = msg.Width, msg.Height case pool.Update: m.state = msg if runtime.GOOS == "windows" { w, h, err := term.GetSize(int(os.Stdout.Fd())) if err == nil { m.w, m.h = w, h } } cmd = waitForUpdate(m.Channel) case tea.KeyMsg: switch msg.String() { case "q", "esc", "ctrl+c": cmd = tea.Quit case "left": m.hAlignment -= 0.5 if m.hAlignment < 0 { m.hAlignment = 0 } case "right": m.hAlignment += 0.5 if m.hAlignment > 1 { m.hAlignment = 1 } case "up": if m.state.Playing && lyrics.Timesynced(m.state.Lines) { break } m.state.Index -= 1 if m.state.Index < 0 { m.state.Index = 0 } case "down": if m.state.Playing && lyrics.Timesynced(m.state.Lines) { break } m.state.Index += 1 if m.state.Index >= len(m.state.Lines) { m.state.Index = len(m.state.Lines) - 1 } } } return m, cmd } func (m *Model) View() string { if m.w < 1 || m.h < 1 { return "" } if m.state.Err != nil && !m.Config.IgnoreErrors { return gloss.PlaceVertical( m.h, gloss.Center, m.styleCurrent. Align(gloss.Center). Width(m.w). Render(m.state.Err.Error()), ) } if len(m.state.Lines) == 0 { return "" } curLine := m.styleCurrent. Width(m.w). Align(m.hAlignment). Render(m.state.Lines[m.state.Index].Words) curLines := strings.Split(curLine, "\n") curLen := len(curLines) beforeLen := (m.h - curLen) / 2 afterLen := m.h - beforeLen - curLen lines := make([]string, beforeLen+curLen+afterLen) // fill lines before current var filledBefore int var beforeIndex = m.state.Index - 1 for filledBefore < beforeLen { index := beforeLen - filledBefore - 1 if index < 0 || beforeIndex < 0 { filledBefore += 1 continue } line := m.styleBefore. Width(m.w). Align(m.hAlignment). Render(m.state.Lines[beforeIndex].Words) beforeIndex -= 1 beforeLines := strings.Split(line, "\n") for i := len(beforeLines) - 1; i >= 0; i-- { lineIndex := index - i if lineIndex >= 0 { lines[lineIndex] = beforeLines[len(beforeLines)-1-i] } filledBefore += 1 } } // fill current lines var curIndex = beforeLen for i, line := range curLines { index := curIndex + i if index >= 0 && index < len(lines) { lines[index] = line } } // fill lines after current var filledAfter int var afterIndex = m.state.Index + 1 for filledAfter < afterLen { index := beforeLen + curLen + filledAfter if index >= len(lines) || afterIndex >= len(m.state.Lines) { filledAfter += 1 continue } line := m.styleAfter. Width(m.w). Align(m.hAlignment). Render(m.state.Lines[afterIndex].Words) afterIndex += 1 afterLines := strings.Split(line, "\n") for i, line := range afterLines { lineIndex := index + i if lineIndex < len(lines) { lines[lineIndex] = line } filledAfter += 1 } } return gloss.JoinVertical(m.hAlignment, lines...) } func waitForUpdate(ch chan pool.Update) tea.Cmd { return func() tea.Msg { return <-ch } }