├── .github └── workflows │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── Dockerfile ├── LICENSE ├── README.md ├── admin.html ├── config.sample.toml ├── go.mod ├── go.sum ├── home.html ├── main.go ├── schema.sql └── static ├── app ├── css │ ├── app.css │ ├── normalize.css │ └── skeleton.css └── js │ └── feather.min.js ├── custom └── .gitkeep ├── demo.png ├── demo_admin.png └── icon.png /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | jobs: 8 | goreleaser: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Set up Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.16 19 | - name: Login to Docker Registry 20 | uses: docker/login-action@v1 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | - name: Run GoReleaser 25 | uses: goreleaser/goreleaser-action@v2 26 | with: 27 | version: latest 28 | args: release --rm-dist 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.log 4 | tmp/ 5 | *.db 6 | *.bin 7 | dist/ 8 | config.toml 9 | static/custom/* 10 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | env: 2 | - CGO_ENABLED=0 3 | 4 | before: 5 | hooks: 6 | # You may remove this if you don't use go modules. 7 | - go mod tidy 8 | 9 | builds: 10 | - env: 11 | goos: 12 | - linux 13 | - darwin 14 | 15 | archives: 16 | - format: tar.gz 17 | files: 18 | - README.md 19 | - LICENSE 20 | 21 | checksum: 22 | name_template: "checksums.txt" 23 | snapshot: 24 | name_template: "{{ .Tag }}-next" 25 | changelog: 26 | sort: asc 27 | filters: 28 | exclude: 29 | - "^docs:" 30 | - "^test:" 31 | 32 | dockers: 33 | - goos: linux 34 | goarch: amd64 35 | ids: 36 | - linkpage 37 | image_templates: 38 | - "rhnvrm/linkpage:latest" 39 | - "rhnvrm/linkpage:{{ .Tag }}" 40 | dockerfile: Dockerfile 41 | extra_files: 42 | - config.sample.toml 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | MAINTAINER Rohan Verma 3 | RUN apk --no-cache add ca-certificates 4 | WORKDIR /linkpage 5 | COPY linkpage . 6 | COPY config.sample.toml config.toml 7 | CMD ["./linkpage"] 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, Rohan Verma 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LinkPage [![Zerodha Tech](https://zerodha.tech/static/images/github-badge.svg)](https://zerodha.tech) 2 | 3 | LinkPage is a FOSS self-hosted alternative to link listing websites such as LinkTree and Campsite.bio 4 | 5 | ## Features 6 | 7 | - Self hostable and open source 8 | - Responsive and customizable design 9 | - Admin panel with custom link ordering 10 | - Fetch details (thumbnail, description) directly from the link using OpenGraph tags 11 | - Minimal JavaScript with cached Go templating for the homepage 12 | - Anonymized link click tracking 13 | - Simple sqlite3 setup for getting started instantly 14 | - Basic Auth for admin endpoints 15 | - Social Icons 16 | 17 | ## Demo 18 | 19 | ### Home 20 | 21 | 22 | 23 | ### Admin 24 | 25 | 26 | 27 | ## Get Started 28 | 29 | 1. Download the latest release 30 | 2. Decompress the archive 31 | 3. Run the app using `./linkpage --init`, this will generate an empty sqlite database and config file in your local directory. 32 | 4. Now you can run the app using `./linkpage`, goto the `/admin` page to add new entries. 33 | 5. Default login for admin page is "username" and "password". 34 | 35 | ### Using Docker 36 | 37 | You can also use docker to run linkpage. Running the following command in 38 | will initialize the config file and database file for you in a 39 | docker volume called `linkpage`. 40 | 41 | `docker run -v linkpage:/linkpage -p 8000:8000 rhnvrm/linkpage:latest ./linkpage --init` 42 | 43 | After this, you can run the following command to start the app. 44 | 45 | `docker run -v linkpage:/linkpage -p 8000:8000 rhnvrm/linkpage:latest ./linkpage` 46 | 47 | ## Developer Setup 48 | 49 | 0. `git clone https://github.com/rhnvrm/linkpage.git` 50 | 51 | 1. Initialize SQL schema from `schema.sql` by copying the schema using sqlite: 52 | 53 | ``` 54 | sqlite3 app.db 55 | 56 | sqlite> (paste and run schema) 57 | ``` 58 | 59 | 2. Edit `config.toml` 60 | 61 | 3. Run the app 62 | 63 | `go run main.go` 64 | 65 | 4. Insert new entries under `/admin` page. 66 | 67 | ## Websites using LinkPage 68 | 69 | - [links.zrd.sh](https://links.zrd.sh) 70 | -------------------------------------------------------------------------------- /admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{.Title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 |

{{.Title}}

23 |
{{.Intro}}
24 |
25 | 26 |
27 | {{.Error}} 28 | {{.Success}} 29 |
New Link
30 |
31 | 32 |
33 |
34 | 37 |
38 |
39 | 40 |
41 |
42 | 43 | 44 |
47 | 48 | 49 |
52 | 53 | 54 | 55 |
56 |
57 | {{ range .Links }} 58 | 59 |
60 | Up 61 | 62 | ({{.Weight}}) 63 | 64 | Down 65 |
66 | Delete 67 |
68 | 69 | 83 | 84 |
85 |
86 | Clicks: {{.Hits}} 87 |
88 | 89 |
90 |
91 |
92 |
93 | 94 | 95 |
96 | {{ end }} 97 | 98 | 99 |
100 | 101 | 102 | -------------------------------------------------------------------------------- /config.sample.toml: -------------------------------------------------------------------------------- 1 | http_address="0.0.0.0:8000" 2 | read_timeout="3s" 3 | write_timeout="3s" 4 | dbfile="app.db" 5 | 6 | page_logo_url="/static/icon.png" 7 | page_title="LinkPage" 8 | page_intro="Your friendly neighbourhood self-hostable FOSS link page." 9 | 10 | static_files = "static/custom" 11 | 12 | [auth] 13 | username="username" 14 | password="password" 15 | 16 | [social] 17 | facebook = "" 18 | instagram = "" 19 | linkedin = "" 20 | twitter = "" 21 | youtube = "" 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rhnvrm/linkpage 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gorilla/mux v1.8.0 7 | github.com/jmoiron/sqlx v1.3.3 8 | github.com/knadh/koanf v0.16.0 9 | github.com/otiai10/opengraph/v2 v2.0.2 10 | github.com/spf13/pflag v1.0.5 11 | github.com/urfave/negroni v1.0.0 12 | modernc.org/sqlite v1.10.3 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 5 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 6 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 7 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 8 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 13 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 14 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 15 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 16 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 17 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 18 | github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= 19 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 20 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 21 | github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 22 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 23 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 24 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 25 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 27 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 28 | github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= 29 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 31 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 32 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 33 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 34 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 35 | github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= 36 | github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 37 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 38 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 39 | github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= 40 | github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 41 | github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 42 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 43 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 44 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 45 | github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 46 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 47 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 48 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 49 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 50 | github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= 51 | github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= 52 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 53 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 54 | github.com/jmoiron/sqlx v1.3.3 h1:j82X0bf7oQ27XeqxicSZsTU5suPwKElg3oyxNn43iTk= 55 | github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= 56 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 57 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 58 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 59 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 60 | github.com/knadh/koanf v0.16.0 h1:qQqGvE8hs/y5pZTG5kT354vqUqsDKQcXX8IOq2Rg11Y= 61 | github.com/knadh/koanf v0.16.0/go.mod h1:DMZ6jQlhA3PqxnKR63luVaBtDemi/m8v/FpXI7B5Ez8= 62 | github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 63 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 64 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 65 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 66 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 67 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 68 | github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= 69 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 70 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 71 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 72 | github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= 73 | github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= 74 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 75 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 76 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 77 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 78 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 79 | github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= 80 | github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 81 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 82 | github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= 83 | github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 84 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 85 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= 86 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= 87 | github.com/otiai10/marmoset v0.4.0 h1:Hg59lQI7qQowBEdsAJ/+VDTEospTBzXX/A1Gsw4mlvA= 88 | github.com/otiai10/marmoset v0.4.0/go.mod h1:t2q6dXWZ9YcFdRREDApX4bCmfQnL3isJ2dgl8ychlXg= 89 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= 90 | github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= 91 | github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 92 | github.com/otiai10/opengraph/v2 v2.0.2 h1:rZ8dVgA1zNb/GuJceNgYvXKzsXrsjXQy+GPHti6meQQ= 93 | github.com/otiai10/opengraph/v2 v2.0.2/go.mod h1:0vXmGIAGOVtlJ1Ucwa6wVaD+qkmjTq8c9WGHGiu5RrI= 94 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 95 | github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= 96 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 97 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 98 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 99 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 100 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 101 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= 102 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 103 | github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= 104 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 105 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 106 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 107 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 108 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 109 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 110 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 111 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 112 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 113 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 114 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 115 | github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= 116 | github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= 117 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 118 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 119 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 120 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 121 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 122 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 123 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 124 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 125 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 126 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 127 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 128 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 129 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 130 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 131 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 132 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 133 | golang.org/x/net v0.0.0-20200923182212-328152dc79b1/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 134 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= 135 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 136 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 137 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 138 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 139 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 140 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 143 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 144 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 145 | golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 146 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 147 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 149 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 151 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 152 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 155 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= 156 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 157 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 158 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 159 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 160 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 161 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 162 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 163 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 164 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 165 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 166 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 167 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs= 168 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 169 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 170 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 171 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 172 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 173 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 174 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 175 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 176 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 177 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 178 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 179 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 180 | google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 181 | gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= 182 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 183 | gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 184 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 185 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 186 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 187 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 188 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 189 | modernc.org/cc/v3 v3.32.4 h1:1ScT6MCQRWwvwVdERhGPsPq0f55J1/pFEOCiqM7zc78= 190 | modernc.org/cc/v3 v3.32.4/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= 191 | modernc.org/ccgo/v3 v3.9.2 h1:mOLFgduk60HFuPmxSix3AluTEh7zhozkby+e1VDo/ro= 192 | modernc.org/ccgo/v3 v3.9.2/go.mod h1:gnJpy6NIVqkETT+L5zPsQFj7L2kkhfPMzOghRNv/CFo= 193 | modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= 194 | modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= 195 | modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= 196 | modernc.org/libc v1.9.5 h1:zv111ldxmP7DJ5mOIqzRbza7ZDl3kh4ncKfASB2jIYY= 197 | modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= 198 | modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 199 | modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= 200 | modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 201 | modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= 202 | modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= 203 | modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= 204 | modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 205 | modernc.org/sqlite v1.10.3 h1:jLp01ok0Y+FaDqOJ3qaGugZjqo2AYpWxGOH3/LPkBpM= 206 | modernc.org/sqlite v1.10.3/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs= 207 | modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc= 208 | modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 209 | modernc.org/tcl v1.5.2 h1:sYNjGr4zK6cDH74USl8wVJRrvDX6UOLpG0j4lFvR0W0= 210 | modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo= 211 | modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= 212 | modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 213 | modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= 214 | modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc= 215 | modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= 216 | -------------------------------------------------------------------------------- /home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{.Title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |

{{.Title}}

25 |
{{.Intro}}
26 |
27 | {{ range .Links }} 28 | 42 | {{ end }} 43 | 44 |
45 | 46 |
47 | 56 |
57 | 58 | 61 |
62 | 74 | 75 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "embed" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "os" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | "text/template" 16 | "time" 17 | 18 | "github.com/gorilla/mux" 19 | "github.com/jmoiron/sqlx" 20 | "github.com/knadh/koanf" 21 | "github.com/knadh/koanf/parsers/toml" 22 | "github.com/knadh/koanf/providers/file" 23 | 24 | "github.com/otiai10/opengraph/v2" 25 | flag "github.com/spf13/pflag" 26 | "github.com/urfave/negroni" 27 | _ "modernc.org/sqlite" 28 | ) 29 | 30 | //go:embed home.html admin.html 31 | var templateFS embed.FS 32 | 33 | //go:embed static 34 | var staticFS embed.FS 35 | 36 | //go:embed schema.sql config.sample.toml 37 | var setupFS embed.FS 38 | 39 | type Config struct { 40 | HTTPAddr string `koanf:"http_address"` 41 | ReadTimeout time.Duration `koanf:"read_timeout"` 42 | WriteTimeout time.Duration `koanf:"write_timeout"` 43 | DBFile string `koanf:"dbfile"` 44 | 45 | PageLogoURL string `koanf:"page_logo_url"` 46 | PageTitle string `koanf:"page_title"` 47 | PageIntro string `koanf:"page_intro"` 48 | 49 | StaticFileDir string `koanf:"static_files"` 50 | 51 | Auth CfgAuth `koanf:"auth"` 52 | 53 | Social map[string]string `koanf:"social"` 54 | } 55 | 56 | type CfgAuth struct { 57 | Username string `koanf:"username"` 58 | Password string `koanf:"password"` 59 | } 60 | 61 | type App struct { 62 | Data Page 63 | DB *LinkDB 64 | Templates Templates 65 | sync.RWMutex 66 | } 67 | 68 | func (app *App) UpdateLinks() error { 69 | links, err := app.DB.GetLinks() 70 | if err != nil { 71 | return fmt.Errorf("error while getting links: %v", err) 72 | } 73 | 74 | app.Data.Links = links 75 | 76 | if err := app.Templates.Home.Save(app.Data); err != nil { 77 | return fmt.Errorf("failed to save template: %v", err) 78 | } 79 | 80 | return nil 81 | } 82 | 83 | type Link struct { 84 | ID int `db:"link_id"` 85 | URL string `db:"url"` 86 | Text string `db:"message"` 87 | ImageURL string `db:"image_url"` 88 | Weight int `db:"weight"` 89 | Hits int `db:"hits"` 90 | } 91 | 92 | type Page struct { 93 | LogoURL string 94 | Title string 95 | Intro string 96 | Links []Link 97 | 98 | Error string 99 | Success string 100 | 101 | OGPURL string 102 | OGPImage string 103 | OGPDesc string 104 | 105 | Social map[string]string 106 | } 107 | 108 | type cachedTemplate struct { 109 | *template.Template 110 | rawData []byte 111 | sync.RWMutex 112 | } 113 | 114 | func newCachedTemplate(tmpl *template.Template) *cachedTemplate { 115 | return &cachedTemplate{ 116 | Template: tmpl, 117 | rawData: nil, 118 | } 119 | } 120 | 121 | func (ct *cachedTemplate) Save(data Page) error { 122 | var out = bytes.NewBuffer([]byte{}) 123 | if err := ct.Execute(out, data); err != nil { 124 | return err 125 | } 126 | 127 | ct.Lock() 128 | ct.rawData = out.Bytes() 129 | ct.Unlock() 130 | return nil 131 | } 132 | 133 | func (ct *cachedTemplate) Write(w io.Writer) error { 134 | ct.RLock() 135 | defer ct.RUnlock() 136 | 137 | _, err := io.Copy(w, bytes.NewBuffer(ct.rawData)) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | return nil 143 | } 144 | 145 | type Templates struct { 146 | Home *cachedTemplate 147 | Admin *template.Template 148 | } 149 | 150 | type LinkDB struct { 151 | db *sqlx.DB 152 | } 153 | 154 | func (l *LinkDB) GetLinks() ([]Link, error) { 155 | links := []Link{} 156 | if err := l.db.Select(&links, 157 | "SELECT * FROM links ORDER BY weight DESC, link_id ASC;"); err != nil { 158 | return nil, err 159 | } 160 | 161 | return links, nil 162 | } 163 | 164 | func (l *LinkDB) UpdateWeight(id int, action string) error { 165 | var queryAction string 166 | switch action { 167 | case "up": 168 | queryAction = "+ 1" 169 | case "down": 170 | queryAction = "- 1" 171 | default: 172 | return fmt.Errorf("unsupported action: %s", action) 173 | } 174 | 175 | res, err := l.db.Exec("UPDATE links set weight = weight " + queryAction + " where link_id = " + strconv.Itoa(id) + ";") 176 | if err != nil { 177 | return err 178 | } 179 | 180 | if c, _ := res.RowsAffected(); c == 0 { 181 | return fmt.Errorf("item not found: %d", id) 182 | } 183 | 184 | return nil 185 | } 186 | 187 | func (l *LinkDB) InsertLink(text, url, imageURL string) error { 188 | query := `INSERT INTO links (message, url, image_url) VALUES (?, ?, ?);` 189 | 190 | _, err := l.db.Exec(query, text, url, imageURL) 191 | if err != nil { 192 | return err 193 | } 194 | 195 | return nil 196 | } 197 | 198 | func (l *LinkDB) UpdateLink(id int, text, url, image string) error { 199 | query := `UPDATE links SET message=?, url=?, image_url=? WHERE link_id=?;` 200 | 201 | _, err := l.db.Exec(query, text, url, image, id) 202 | if err != nil { 203 | return err 204 | } 205 | 206 | return nil 207 | } 208 | 209 | func (l *LinkDB) DeleteLink(id int) error { 210 | query := `DELETE FROM links where link_id = ?;` 211 | 212 | _, err := l.db.Exec(query, id) 213 | if err != nil { 214 | return err 215 | } 216 | 217 | return nil 218 | } 219 | 220 | func (l *LinkDB) IncrementHit(id int) error { 221 | _, err := l.db.Exec("UPDATE links set hits = hits + 1 where link_id = " + strconv.Itoa(id) + ";") 222 | if err != nil { 223 | return err 224 | } 225 | 226 | return nil 227 | } 228 | 229 | func initConfig(configFile string) Config { 230 | var ( 231 | config Config 232 | k = koanf.New(".") 233 | ) 234 | 235 | if err := k.Load(file.Provider(configFile), toml.Parser()); err != nil { 236 | log.Fatalf("error loading file: %v", err) 237 | } 238 | 239 | if err := k.Unmarshal("", &config); err != nil { 240 | log.Fatalf("error while unmarshalling config: %v", err) 241 | } 242 | 243 | return config 244 | } 245 | 246 | func writeInternalServerErr(w http.ResponseWriter) { 247 | w.WriteHeader(http.StatusInternalServerError) 248 | w.Write([]byte("500 - Internal Server Error!")) 249 | } 250 | 251 | func writeBadRequest(w http.ResponseWriter, message string) { 252 | w.WriteHeader(http.StatusBadRequest) 253 | w.Write([]byte("400 - " + message)) 254 | } 255 | 256 | func basicAuth(cfg Config) negroni.HandlerFunc { 257 | return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 258 | user, pass, _ := r.BasicAuth() 259 | 260 | if cfg.Auth.Username != user || cfg.Auth.Password != pass { 261 | w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) 262 | http.Error(w, "Unauthorized.", http.StatusUnauthorized) 263 | return 264 | } 265 | 266 | next(w, r) 267 | }) 268 | } 269 | 270 | var ( 271 | appMode = "run_app" 272 | configFilePath = "config.toml" 273 | ) 274 | 275 | func init() { 276 | flag.StringVar(&configFilePath, "config", "config.toml", "path to config file") 277 | initApp := flag.Bool("init", false, "app initialization, creates a db and config file in current dir") 278 | 279 | flag.Parse() 280 | 281 | if *initApp == true { 282 | appMode = "init_app" 283 | } 284 | } 285 | 286 | func runApp(configFilePath string) { 287 | cfg := initConfig(configFilePath) 288 | 289 | db, err := sqlx.Connect("sqlite", cfg.DBFile) 290 | if err != nil { 291 | log.Fatalln(err) 292 | } 293 | 294 | app := &App{ 295 | Data: Page{ 296 | LogoURL: cfg.PageLogoURL, 297 | Title: cfg.PageTitle, 298 | Intro: cfg.PageIntro, 299 | Social: cfg.Social, 300 | }, 301 | DB: &LinkDB{db}, 302 | Templates: Templates{ 303 | Home: newCachedTemplate(template.Must(template.ParseFS(templateFS, "home.html"))), 304 | Admin: template.Must(template.ParseFS(templateFS, "admin.html")), 305 | }, 306 | } 307 | 308 | // Initial setup of links 309 | if err := app.UpdateLinks(); err != nil { 310 | if strings.Contains(err.Error(), "no such table") { 311 | log.Println("schema not initialized, attempting to initialize schema") 312 | 313 | if err := execSchema(db); err != nil { 314 | log.Fatal(err) 315 | } 316 | 317 | if err := app.UpdateLinks(); err != nil { 318 | log.Fatal(err) 319 | } 320 | } else { 321 | log.Fatalf("error while trying to update links: %v", err) 322 | } 323 | } 324 | 325 | r := mux.NewRouter() 326 | admin := mux.NewRouter().PathPrefix("/admin").Subrouter().StrictSlash(true) 327 | r.PathPrefix("/admin").Handler(negroni.New( 328 | negroni.HandlerFunc(basicAuth(cfg)), 329 | negroni.Wrap(admin), 330 | )) 331 | 332 | r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 333 | if err := app.Templates.Home.Write(w); err != nil { 334 | log.Printf("error while writing template: %v", err) 335 | writeInternalServerErr(w) 336 | } 337 | }) 338 | 339 | r.HandleFunc("/hits/{id}", func(w http.ResponseWriter, r *http.Request) { 340 | rawID, ok := mux.Vars(r)["id"] 341 | if !ok { 342 | writeBadRequest(w, "id missing") 343 | return 344 | } 345 | 346 | id, err := strconv.Atoi(rawID) 347 | if err != nil { 348 | log.Printf("error while getting links: %v", err) 349 | writeBadRequest(w, "bad id, got "+rawID) 350 | return 351 | } 352 | 353 | if err := app.DB.IncrementHit(id); err != nil { 354 | log.Printf("error while incrementing hits: %v", err) 355 | writeInternalServerErr(w) 356 | return 357 | } 358 | 359 | w.WriteHeader(http.StatusOK) 360 | w.Write([]byte("{}")) 361 | return 362 | }) 363 | 364 | renderAdminPage := func(data Page) func(w http.ResponseWriter, r *http.Request) { 365 | return func(w http.ResponseWriter, r *http.Request) { 366 | if err := app.Templates.Admin.Execute(w, data); err != nil { 367 | log.Printf("error while writing template: %v", err) 368 | writeInternalServerErr(w) 369 | return 370 | } 371 | } 372 | } 373 | 374 | renderAdminPageWithErrMessage := func(msg string, p Page) func(w http.ResponseWriter, r *http.Request) { 375 | p.Error = msg 376 | return renderAdminPage(p) 377 | } 378 | 379 | admin.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 380 | app.UpdateLinks() 381 | renderAdminPage(app.Data)(w, r) 382 | }) 383 | 384 | admin.HandleFunc("/links/{id}/weight", func(w http.ResponseWriter, r *http.Request) { 385 | keys, ok := r.URL.Query()["action"] 386 | 387 | if !ok || len(keys[0]) < 1 { 388 | renderAdminPageWithErrMessage("action is missing", app.Data)(w, r) 389 | return 390 | } 391 | 392 | action := keys[0] 393 | 394 | rawID, ok := mux.Vars(r)["id"] 395 | if !ok { 396 | renderAdminPageWithErrMessage("id is missing", app.Data)(w, r) 397 | return 398 | } 399 | 400 | id, err := strconv.Atoi(rawID) 401 | if err != nil { 402 | renderAdminPageWithErrMessage("bad id, got: "+rawID, app.Data)(w, r) 403 | return 404 | } 405 | 406 | if err := app.DB.UpdateWeight(id, action); err != nil { 407 | renderAdminPageWithErrMessage( 408 | fmt.Sprintf("error while updating link: %v", err), 409 | app.Data)(w, r) 410 | return 411 | } 412 | 413 | if err := app.UpdateLinks(); err != nil { 414 | renderAdminPageWithErrMessage( 415 | fmt.Sprintf("error while updating link: %v", err), 416 | app.Data)(w, r) 417 | return 418 | } 419 | 420 | renderAdminPage(app.Data)(w, r) 421 | }) 422 | 423 | admin.HandleFunc("/links/{id}/delete", func(w http.ResponseWriter, r *http.Request) { 424 | rawID, ok := mux.Vars(r)["id"] 425 | if !ok { 426 | renderAdminPageWithErrMessage("id is missing", app.Data)(w, r) 427 | return 428 | } 429 | 430 | id, err := strconv.Atoi(rawID) 431 | if err != nil { 432 | renderAdminPageWithErrMessage("bad id, got: "+rawID, app.Data)(w, r) 433 | return 434 | } 435 | 436 | if err := app.DB.DeleteLink(id); err != nil { 437 | renderAdminPageWithErrMessage( 438 | fmt.Sprintf("error while deleting link: %v", err), 439 | app.Data)(w, r) 440 | return 441 | } 442 | 443 | if err := app.UpdateLinks(); err != nil { 444 | renderAdminPageWithErrMessage( 445 | fmt.Sprintf("error while updating links: %v", err), 446 | app.Data)(w, r) 447 | } 448 | 449 | renderAdminPage(app.Data)(w, r) 450 | }) 451 | 452 | admin.HandleFunc("/links/{id}/update", func(w http.ResponseWriter, r *http.Request) { 453 | rawID, ok := mux.Vars(r)["id"] 454 | if !ok { 455 | renderAdminPageWithErrMessage("id is missing", app.Data)(w, r) 456 | return 457 | } 458 | 459 | id, err := strconv.Atoi(rawID) 460 | if err != nil { 461 | renderAdminPageWithErrMessage("bad id, got: "+rawID, app.Data)(w, r) 462 | return 463 | } 464 | 465 | r.ParseForm() 466 | 467 | text := r.Form.Get("text") 468 | url := r.Form.Get("url") 469 | imageURL := r.Form.Get("image_url") 470 | 471 | if url == "" { 472 | renderAdminPageWithErrMessage("url is missing", app.Data)(w, r) 473 | return 474 | } 475 | if text == "" { 476 | renderAdminPageWithErrMessage("text is missing", app.Data)(w, r) 477 | return 478 | } 479 | if imageURL == "" { 480 | renderAdminPageWithErrMessage("image_url is missing", app.Data)(w, r) 481 | return 482 | } 483 | 484 | if err := app.DB.UpdateLink(id, text, url, imageURL); err != nil { 485 | renderAdminPageWithErrMessage( 486 | fmt.Sprintf("error while updating link: %v", err), 487 | app.Data)(w, r) 488 | return 489 | } 490 | 491 | if err := app.UpdateLinks(); err != nil { 492 | renderAdminPageWithErrMessage( 493 | fmt.Sprintf("error while updating links: %v", err), 494 | app.Data)(w, r) 495 | return 496 | } 497 | 498 | renderAdminPage(app.Data)(w, r) 499 | }) 500 | 501 | admin.HandleFunc("/links/new", func(w http.ResponseWriter, r *http.Request) { 502 | r.ParseForm() 503 | 504 | text := r.Form.Get("text") 505 | url := r.Form.Get("url") 506 | imageURL := r.Form.Get("image_url") 507 | submitType := r.Form.Get("submit") 508 | 509 | if url == "" { 510 | renderAdminPageWithErrMessage("url is missing", app.Data)(w, r) 511 | } 512 | 513 | if submitType == "Fetch Data" { 514 | ogp, err := opengraph.Fetch(url) 515 | if err != nil { 516 | renderAdminPageWithErrMessage( 517 | fmt.Sprintf("error while fetching link: %v", err), 518 | app.Data)(w, r) 519 | return 520 | } 521 | 522 | ogp.ToAbs() 523 | if len(ogp.Image) > 0 { 524 | imageURL = ogp.Image[0].URL 525 | } 526 | 527 | p := app.Data 528 | p.OGPImage = imageURL 529 | p.OGPDesc = ogp.Title 530 | p.OGPURL = url 531 | renderAdminPage(p)(w, r) 532 | return 533 | } 534 | 535 | if text == "" { 536 | renderAdminPageWithErrMessage("text is missing", app.Data)(w, r) 537 | } 538 | 539 | if imageURL == "" { 540 | ogp, err := opengraph.Fetch(url) 541 | if err != nil { 542 | renderAdminPageWithErrMessage( 543 | fmt.Sprintf("error while fetching link: %v", err), 544 | app.Data)(w, r) 545 | return 546 | } 547 | 548 | ogp.ToAbs() 549 | if len(ogp.Image) > 0 { 550 | imageURL = ogp.Image[0].URL 551 | } 552 | } 553 | 554 | if err := app.DB.InsertLink(text, url, imageURL); err != nil { 555 | renderAdminPageWithErrMessage( 556 | fmt.Sprintf("error while inserting link: %v", err), 557 | app.Data)(w, r) 558 | return 559 | } 560 | 561 | if err := app.UpdateLinks(); err != nil { 562 | log.Printf("error while updating links: %v", err) 563 | writeInternalServerErr(w) 564 | return 565 | } 566 | 567 | p := app.Data 568 | p.Success = "New link inserted!" 569 | renderAdminPage(p)(w, r) 570 | }) 571 | 572 | r.PathPrefix("/static/app").Handler(http.FileServer(http.FS(staticFS))) 573 | 574 | if cfg.StaticFileDir != "" { 575 | r.PathPrefix("/static/custom").Handler( 576 | http.StripPrefix("/static/custom", http.FileServer(http.Dir(cfg.StaticFileDir)))) 577 | } 578 | 579 | srv := &http.Server{ 580 | Handler: r, 581 | Addr: cfg.HTTPAddr, 582 | WriteTimeout: cfg.ReadTimeout, 583 | ReadTimeout: cfg.WriteTimeout, 584 | } 585 | 586 | log.Println("starting server at", cfg.HTTPAddr) 587 | log.Fatal(srv.ListenAndServe()) 588 | } 589 | 590 | func initDB(dbFilePath string) { 591 | file, err := os.Create(dbFilePath) 592 | if err != nil { 593 | log.Fatal(err) 594 | } 595 | file.Close() 596 | 597 | db, err := sqlx.Connect("sqlite", dbFilePath) 598 | if err != nil { 599 | log.Fatal(err) 600 | } 601 | 602 | if err := execSchema(db); err != nil { 603 | log.Fatal(err) 604 | } 605 | 606 | if err := db.Close(); err != nil { 607 | log.Fatal(err) 608 | } 609 | } 610 | 611 | func execSchema(db *sqlx.DB) error { 612 | schemaFile, err := setupFS.Open("schema.sql") 613 | if err != nil { 614 | return err 615 | } 616 | 617 | schema, err := ioutil.ReadAll(schemaFile) 618 | if err != nil { 619 | return err 620 | } 621 | 622 | if err := schemaFile.Close(); err != nil { 623 | return err 624 | } 625 | 626 | if _, err := db.Exec(string(schema)); err != nil { 627 | return err 628 | } 629 | 630 | return nil 631 | } 632 | 633 | func initApp(dbFilePath string) { 634 | initDB(dbFilePath) 635 | 636 | outCfgFile, err := os.Create("config.toml") 637 | if err != nil { 638 | log.Fatal(err) 639 | } 640 | defer outCfgFile.Close() 641 | 642 | setupCfgFile, err := setupFS.Open("config.sample.toml") 643 | if err != nil { 644 | log.Fatal(err) 645 | } 646 | defer setupCfgFile.Close() 647 | 648 | if _, err := io.Copy(outCfgFile, setupCfgFile); err != nil { 649 | log.Fatal(err) 650 | } 651 | 652 | log.Println("config.toml and app.db generated.") 653 | } 654 | 655 | func main() { 656 | switch appMode { 657 | case "init_app": 658 | initApp("app.db") 659 | case "run_app": 660 | runApp(configFilePath) 661 | default: 662 | runApp(configFilePath) 663 | } 664 | } 665 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE links ( 2 | link_id INTEGER PRIMARY KEY, 3 | url TEXT NOT NULL, 4 | message TEXT NOT NULL, 5 | image_url TEXT NOT NULL, 6 | weight INTEGER DEFAULT 0 NOT NULL, 7 | hits INTEGER DEFAULT 0 NOT NULL 8 | ); 9 | -------------------------------------------------------------------------------- /static/app/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Inter", sans-serif; 3 | } 4 | 5 | .container { 6 | max-width: 800px; 7 | } 8 | 9 | .header { 10 | margin-top: 6rem; 11 | text-align: center; 12 | } 13 | 14 | .link { 15 | margin-top: 10px; 16 | background: #4361d8; 17 | border: 1px solid #7d93e8; 18 | border-radius: 3px; 19 | color: #fff; 20 | height: 100px; 21 | text-align: center; 22 | } 23 | 24 | .link:hover { 25 | background: #1d2d2c; 26 | border-color: #273d3b; 27 | } 28 | 29 | .link a { 30 | color: #fff; 31 | } 32 | 33 | .text { 34 | font-size: 16px; 35 | padding: 20px; 36 | text-align: center; 37 | width: 100%; 38 | } 39 | 40 | .thumbnail { 41 | width: 100px; 42 | height: 100px; 43 | } 44 | 45 | .stats { 46 | margin-top: 10px; 47 | display: flex; 48 | } 49 | 50 | .push { 51 | margin-left: auto; 52 | } 53 | 54 | .stats a { 55 | text-decoration: none; 56 | margin-right: 5px; 57 | } 58 | 59 | .admin form { 60 | margin-top: 10px; 61 | color: #000; 62 | } 63 | 64 | .admin form input { 65 | background-color: transparent; 66 | border: 1px solid #d1d1d1; 67 | } 68 | 69 | .error { 70 | font-weight: bold; 71 | color: red; 72 | } 73 | 74 | .success { 75 | font-weight: bold; 76 | color: green; 77 | } 78 | 79 | .data { 80 | display: flex; 81 | width: 100%; 82 | } 83 | 84 | .row .linker { 85 | display: flex; 86 | text-decoration: none; 87 | } 88 | 89 | .social { 90 | margin: auto; 91 | width: fit-content; 92 | } 93 | 94 | .footer { 95 | color: dimgray; 96 | width: fit-content; 97 | margin: auto; 98 | } 99 | -------------------------------------------------------------------------------- /static/app/css/normalize.css: -------------------------------------------------------------------------------- 1 | *! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /static/app/css/skeleton.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V2.0.4 3 | * Copyright 2014, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 12/29/2014 8 | */ 9 | 10 | /* Table of contents 11 | –––––––––––––––––––––––––––––––––––––––––––––––––– 12 | - Grid 13 | - Base Styles 14 | - Typography 15 | - Links 16 | - Buttons 17 | - Forms 18 | - Lists 19 | - Code 20 | - Tables 21 | - Spacing 22 | - Utilities 23 | - Clearing 24 | - Media Queries 25 | */ 26 | 27 | /* Grid 28 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 29 | .container { 30 | position: relative; 31 | width: 100%; 32 | max-width: 960px; 33 | margin: 0 auto; 34 | padding: 0 20px; 35 | box-sizing: border-box; 36 | } 37 | .column, 38 | .columns { 39 | width: 100%; 40 | float: left; 41 | box-sizing: border-box; 42 | } 43 | 44 | /* For devices larger than 400px */ 45 | @media (min-width: 400px) { 46 | .container { 47 | width: 85%; 48 | padding: 0; 49 | } 50 | } 51 | 52 | /* For devices larger than 550px */ 53 | @media (min-width: 550px) { 54 | .container { 55 | width: 80%; 56 | } 57 | .column, 58 | .columns { 59 | margin-left: 4%; 60 | } 61 | .column:first-child, 62 | .columns:first-child { 63 | margin-left: 0; 64 | } 65 | 66 | .one.column, 67 | .one.columns { 68 | width: 4.66666666667%; 69 | } 70 | .two.columns { 71 | width: 13.3333333333%; 72 | } 73 | .three.columns { 74 | width: 22%; 75 | } 76 | .four.columns { 77 | width: 30.6666666667%; 78 | } 79 | .five.columns { 80 | width: 39.3333333333%; 81 | } 82 | .six.columns { 83 | width: 48%; 84 | } 85 | .seven.columns { 86 | width: 56.6666666667%; 87 | } 88 | .eight.columns { 89 | width: 65.3333333333%; 90 | } 91 | .nine.columns { 92 | width: 74%; 93 | } 94 | .ten.columns { 95 | width: 82.6666666667%; 96 | } 97 | .eleven.columns { 98 | width: 91.3333333333%; 99 | } 100 | .twelve.columns { 101 | width: 100%; 102 | margin-left: 0; 103 | } 104 | 105 | .one-third.column { 106 | width: 30.6666666667%; 107 | } 108 | .two-thirds.column { 109 | width: 65.3333333333%; 110 | } 111 | 112 | .one-half.column { 113 | width: 48%; 114 | } 115 | 116 | /* Offsets */ 117 | .offset-by-one.column, 118 | .offset-by-one.columns { 119 | margin-left: 8.66666666667%; 120 | } 121 | .offset-by-two.column, 122 | .offset-by-two.columns { 123 | margin-left: 17.3333333333%; 124 | } 125 | .offset-by-three.column, 126 | .offset-by-three.columns { 127 | margin-left: 26%; 128 | } 129 | .offset-by-four.column, 130 | .offset-by-four.columns { 131 | margin-left: 34.6666666667%; 132 | } 133 | .offset-by-five.column, 134 | .offset-by-five.columns { 135 | margin-left: 43.3333333333%; 136 | } 137 | .offset-by-six.column, 138 | .offset-by-six.columns { 139 | margin-left: 52%; 140 | } 141 | .offset-by-seven.column, 142 | .offset-by-seven.columns { 143 | margin-left: 60.6666666667%; 144 | } 145 | .offset-by-eight.column, 146 | .offset-by-eight.columns { 147 | margin-left: 69.3333333333%; 148 | } 149 | .offset-by-nine.column, 150 | .offset-by-nine.columns { 151 | margin-left: 78%; 152 | } 153 | .offset-by-ten.column, 154 | .offset-by-ten.columns { 155 | margin-left: 86.6666666667%; 156 | } 157 | .offset-by-eleven.column, 158 | .offset-by-eleven.columns { 159 | margin-left: 95.3333333333%; 160 | } 161 | 162 | .offset-by-one-third.column, 163 | .offset-by-one-third.columns { 164 | margin-left: 34.6666666667%; 165 | } 166 | .offset-by-two-thirds.column, 167 | .offset-by-two-thirds.columns { 168 | margin-left: 69.3333333333%; 169 | } 170 | 171 | .offset-by-one-half.column, 172 | .offset-by-one-half.columns { 173 | margin-left: 52%; 174 | } 175 | } 176 | 177 | /* Base Styles 178 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 179 | /* NOTE 180 | html is set to 62.5% so that all the REM measurements throughout Skeleton 181 | are based on 10px sizing. So basically 1.5rem = 15px :) */ 182 | html { 183 | font-size: 62.5%; 184 | } 185 | body { 186 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ 187 | line-height: 1.6; 188 | font-weight: 400; 189 | font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, 190 | sans-serif; 191 | color: #222; 192 | } 193 | 194 | /* Typography 195 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 196 | h1, 197 | h2, 198 | h3, 199 | h4, 200 | h5, 201 | h6 { 202 | margin-top: 0; 203 | margin-bottom: 2rem; 204 | font-weight: 300; 205 | } 206 | h1 { 207 | font-size: 4rem; 208 | line-height: 1.2; 209 | letter-spacing: -0.1rem; 210 | } 211 | h2 { 212 | font-size: 3.6rem; 213 | line-height: 1.25; 214 | letter-spacing: -0.1rem; 215 | } 216 | h3 { 217 | font-size: 3rem; 218 | line-height: 1.3; 219 | letter-spacing: -0.1rem; 220 | } 221 | h4 { 222 | font-size: 2.4rem; 223 | line-height: 1.35; 224 | letter-spacing: -0.08rem; 225 | } 226 | h5 { 227 | font-size: 1.8rem; 228 | line-height: 1.5; 229 | letter-spacing: -0.05rem; 230 | } 231 | h6 { 232 | font-size: 1.5rem; 233 | line-height: 1.6; 234 | letter-spacing: 0; 235 | } 236 | 237 | /* Larger than phablet */ 238 | @media (min-width: 550px) { 239 | h1 { 240 | font-size: 5rem; 241 | } 242 | h2 { 243 | font-size: 4.2rem; 244 | } 245 | h3 { 246 | font-size: 3.6rem; 247 | } 248 | h4 { 249 | font-size: 3rem; 250 | } 251 | h5 { 252 | font-size: 2.4rem; 253 | } 254 | h6 { 255 | font-size: 1.5rem; 256 | } 257 | } 258 | 259 | p { 260 | margin-top: 0; 261 | } 262 | 263 | /* Links 264 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 265 | a { 266 | color: #1eaedb; 267 | } 268 | a:hover { 269 | color: #0fa0ce; 270 | } 271 | 272 | /* Buttons 273 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 274 | .button, 275 | button, 276 | input[type="submit"], 277 | input[type="reset"], 278 | input[type="button"] { 279 | display: inline-block; 280 | height: 38px; 281 | padding: 0 30px; 282 | color: #555; 283 | text-align: center; 284 | font-size: 11px; 285 | font-weight: 600; 286 | line-height: 38px; 287 | letter-spacing: 0.1rem; 288 | text-transform: uppercase; 289 | text-decoration: none; 290 | white-space: nowrap; 291 | background-color: transparent; 292 | border-radius: 4px; 293 | border: 1px solid #bbb; 294 | cursor: pointer; 295 | box-sizing: border-box; 296 | } 297 | .button:hover, 298 | button:hover, 299 | input[type="submit"]:hover, 300 | input[type="reset"]:hover, 301 | input[type="button"]:hover, 302 | .button:focus, 303 | button:focus, 304 | input[type="submit"]:focus, 305 | input[type="reset"]:focus, 306 | input[type="button"]:focus { 307 | color: #333; 308 | border-color: #888; 309 | outline: 0; 310 | } 311 | .button.button-primary, 312 | button.button-primary, 313 | input[type="submit"].button-primary, 314 | input[type="reset"].button-primary, 315 | input[type="button"].button-primary { 316 | color: #fff; 317 | background-color: #33c3f0; 318 | border-color: #33c3f0; 319 | } 320 | .button.button-primary:hover, 321 | button.button-primary:hover, 322 | input[type="submit"].button-primary:hover, 323 | input[type="reset"].button-primary:hover, 324 | input[type="button"].button-primary:hover, 325 | .button.button-primary:focus, 326 | button.button-primary:focus, 327 | input[type="submit"].button-primary:focus, 328 | input[type="reset"].button-primary:focus, 329 | input[type="button"].button-primary:focus { 330 | color: #fff; 331 | background-color: #1eaedb; 332 | border-color: #1eaedb; 333 | } 334 | 335 | /* Forms 336 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 337 | input[type="email"], 338 | input[type="number"], 339 | input[type="search"], 340 | input[type="text"], 341 | input[type="tel"], 342 | input[type="url"], 343 | input[type="password"], 344 | textarea, 345 | select { 346 | height: 38px; 347 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ 348 | background-color: #fff; 349 | border: 1px solid #d1d1d1; 350 | border-radius: 4px; 351 | box-shadow: none; 352 | box-sizing: border-box; 353 | } 354 | /* Removes awkward default styles on some inputs for iOS */ 355 | input[type="email"], 356 | input[type="number"], 357 | input[type="search"], 358 | input[type="text"], 359 | input[type="tel"], 360 | input[type="url"], 361 | input[type="password"], 362 | textarea { 363 | -webkit-appearance: none; 364 | -moz-appearance: none; 365 | appearance: none; 366 | } 367 | textarea { 368 | min-height: 65px; 369 | padding-top: 6px; 370 | padding-bottom: 6px; 371 | } 372 | input[type="email"]:focus, 373 | input[type="number"]:focus, 374 | input[type="search"]:focus, 375 | input[type="text"]:focus, 376 | input[type="tel"]:focus, 377 | input[type="url"]:focus, 378 | input[type="password"]:focus, 379 | textarea:focus, 380 | select:focus { 381 | border: 1px solid #33c3f0; 382 | outline: 0; 383 | } 384 | label, 385 | legend { 386 | display: block; 387 | margin-bottom: 0.5rem; 388 | font-weight: 600; 389 | } 390 | fieldset { 391 | padding: 0; 392 | border-width: 0; 393 | } 394 | input[type="checkbox"], 395 | input[type="radio"] { 396 | display: inline; 397 | } 398 | label > .label-body { 399 | display: inline-block; 400 | margin-left: 0.5rem; 401 | font-weight: normal; 402 | } 403 | 404 | /* Lists 405 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 406 | ul { 407 | list-style: circle inside; 408 | } 409 | ol { 410 | list-style: decimal inside; 411 | } 412 | ol, 413 | ul { 414 | padding-left: 0; 415 | margin-top: 0; 416 | } 417 | ul ul, 418 | ul ol, 419 | ol ol, 420 | ol ul { 421 | margin: 1.5rem 0 1.5rem 3rem; 422 | font-size: 90%; 423 | } 424 | li { 425 | margin-bottom: 1rem; 426 | } 427 | 428 | /* Code 429 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 430 | code { 431 | padding: 0.2rem 0.5rem; 432 | margin: 0 0.2rem; 433 | font-size: 90%; 434 | white-space: nowrap; 435 | background: #f1f1f1; 436 | border: 1px solid #e1e1e1; 437 | border-radius: 4px; 438 | } 439 | pre > code { 440 | display: block; 441 | padding: 1rem 1.5rem; 442 | white-space: pre; 443 | } 444 | 445 | /* Tables 446 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 447 | th, 448 | td { 449 | padding: 12px 15px; 450 | text-align: left; 451 | border-bottom: 1px solid #e1e1e1; 452 | } 453 | th:first-child, 454 | td:first-child { 455 | padding-left: 0; 456 | } 457 | th:last-child, 458 | td:last-child { 459 | padding-right: 0; 460 | } 461 | 462 | /* Spacing 463 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 464 | button, 465 | .button { 466 | margin-bottom: 1rem; 467 | } 468 | input, 469 | textarea, 470 | select, 471 | fieldset { 472 | margin-bottom: 1.5rem; 473 | } 474 | pre, 475 | blockquote, 476 | dl, 477 | figure, 478 | table, 479 | p, 480 | ul, 481 | ol, 482 | form { 483 | margin-bottom: 2.5rem; 484 | } 485 | 486 | /* Utilities 487 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 488 | .u-full-width { 489 | width: 100%; 490 | box-sizing: border-box; 491 | } 492 | .u-max-full-width { 493 | max-width: 100%; 494 | box-sizing: border-box; 495 | } 496 | .u-pull-right { 497 | float: right; 498 | } 499 | .u-pull-left { 500 | float: left; 501 | } 502 | 503 | /* Misc 504 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 505 | hr { 506 | margin-top: 3rem; 507 | margin-bottom: 3.5rem; 508 | border-width: 0; 509 | border-top: 1px solid #e1e1e1; 510 | } 511 | 512 | /* Clearing 513 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 514 | 515 | /* Self Clearing Goodness */ 516 | .container:after, 517 | .row:after, 518 | .u-cf { 519 | content: ""; 520 | display: table; 521 | clear: both; 522 | } 523 | 524 | /* Media Queries 525 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 526 | /* 527 | Note: The best way to structure the use of media queries is to create the queries 528 | near the relevant code. For example, if you wanted to change the styles for buttons 529 | on small devices, paste the mobile query code up in the buttons section and style it 530 | there. 531 | */ 532 | 533 | /* Larger than mobile */ 534 | @media (min-width: 400px) { 535 | } 536 | 537 | /* Larger than phablet (also point when grid becomes active) */ 538 | @media (min-width: 550px) { 539 | } 540 | 541 | /* Larger than tablet */ 542 | @media (min-width: 750px) { 543 | } 544 | 545 | /* Larger than desktop */ 546 | @media (min-width: 1000px) { 547 | } 548 | 549 | /* Larger than Desktop HD */ 550 | @media (min-width: 1200px) { 551 | } 552 | -------------------------------------------------------------------------------- /static/app/js/feather.min.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.feather=n():e.feather=n()}("undefined"!=typeof self?self:this,function(){return function(e){var n={};function i(t){if(n[t])return n[t].exports;var l=n[t]={i:t,l:!1,exports:{}};return e[t].call(l.exports,l,l.exports,i),l.l=!0,l.exports}return i.m=e,i.c=n,i.d=function(e,n,t){i.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},i.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},i.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(n,"a",n),n},i.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},i.p="",i(i.s=80)}([function(e,n,i){(function(n){var i="object",t=function(e){return e&&e.Math==Math&&e};e.exports=t(typeof globalThis==i&&globalThis)||t(typeof window==i&&window)||t(typeof self==i&&self)||t(typeof n==i&&n)||Function("return this")()}).call(this,i(75))},function(e,n){var i={}.hasOwnProperty;e.exports=function(e,n){return i.call(e,n)}},function(e,n,i){var t=i(0),l=i(11),r=i(33),o=i(62),a=t.Symbol,c=l("wks");e.exports=function(e){return c[e]||(c[e]=o&&a[e]||(o?a:r)("Symbol."+e))}},function(e,n,i){var t=i(6);e.exports=function(e){if(!t(e))throw TypeError(String(e)+" is not an object");return e}},function(e,n){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,n,i){var t=i(8),l=i(7),r=i(10);e.exports=t?function(e,n,i){return l.f(e,n,r(1,i))}:function(e,n,i){return e[n]=i,e}},function(e,n){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,n,i){var t=i(8),l=i(35),r=i(3),o=i(18),a=Object.defineProperty;n.f=t?a:function(e,n,i){if(r(e),n=o(n,!0),r(i),l)try{return a(e,n,i)}catch(e){}if("get"in i||"set"in i)throw TypeError("Accessors not supported");return"value"in i&&(e[n]=i.value),e}},function(e,n,i){var t=i(4);e.exports=!t(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,n){e.exports={}},function(e,n){e.exports=function(e,n){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:n}}},function(e,n,i){var t=i(0),l=i(19),r=i(17),o=t["__core-js_shared__"]||l("__core-js_shared__",{});(e.exports=function(e,n){return o[e]||(o[e]=void 0!==n?n:{})})("versions",[]).push({version:"3.1.3",mode:r?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=o(i(43)),l=o(i(41)),r=o(i(40));function o(e){return e&&e.__esModule?e:{default:e}}n.default=Object.keys(l.default).map(function(e){return new t.default(e,l.default[e],r.default[e])}).reduce(function(e,n){return e[n.name]=n,e},{})},function(e,n){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(e,n,i){var t=i(72),l=i(20);e.exports=function(e){return t(l(e))}},function(e,n){e.exports={}},function(e,n,i){var t=i(11),l=i(33),r=t("keys");e.exports=function(e){return r[e]||(r[e]=l(e))}},function(e,n){e.exports=!1},function(e,n,i){var t=i(6);e.exports=function(e,n){if(!t(e))return e;var i,l;if(n&&"function"==typeof(i=e.toString)&&!t(l=i.call(e)))return l;if("function"==typeof(i=e.valueOf)&&!t(l=i.call(e)))return l;if(!n&&"function"==typeof(i=e.toString)&&!t(l=i.call(e)))return l;throw TypeError("Can't convert object to primitive value")}},function(e,n,i){var t=i(0),l=i(5);e.exports=function(e,n){try{l(t,e,n)}catch(i){t[e]=n}return n}},function(e,n){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,n){var i=Math.ceil,t=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?t:i)(e)}},function(e,n,i){var t; 2 | /*! 3 | Copyright (c) 2016 Jed Watson. 4 | Licensed under the MIT License (MIT), see 5 | http://jedwatson.github.io/classnames 6 | */ 7 | /*! 8 | Copyright (c) 2016 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | !function(){"use strict";var i=function(){function e(){}function n(e,n){for(var i=n.length,t=0;t0?l(t(e),9007199254740991):0}},function(e,n,i){var t=i(1),l=i(14),r=i(68),o=i(15),a=r(!1);e.exports=function(e,n){var i,r=l(e),c=0,p=[];for(i in r)!t(o,i)&&t(r,i)&&p.push(i);for(;n.length>c;)t(r,i=n[c++])&&(~a(p,i)||p.push(i));return p}},function(e,n,i){var t=i(0),l=i(11),r=i(5),o=i(1),a=i(19),c=i(36),p=i(37),y=p.get,h=p.enforce,x=String(c).split("toString");l("inspectSource",function(e){return c.call(e)}),(e.exports=function(e,n,i,l){var c=!!l&&!!l.unsafe,p=!!l&&!!l.enumerable,y=!!l&&!!l.noTargetGet;"function"==typeof i&&("string"!=typeof n||o(i,"name")||r(i,"name",n),h(i).source=x.join("string"==typeof n?n:"")),e!==t?(c?!y&&e[n]&&(p=!0):delete e[n],p?e[n]=i:r(e,n,i)):p?e[n]=i:a(n,i)})(Function.prototype,"toString",function(){return"function"==typeof this&&y(this).source||c.call(this)})},function(e,n){var i={}.toString;e.exports=function(e){return i.call(e).slice(8,-1)}},function(e,n,i){var t=i(8),l=i(73),r=i(10),o=i(14),a=i(18),c=i(1),p=i(35),y=Object.getOwnPropertyDescriptor;n.f=t?y:function(e,n){if(e=o(e),n=a(n,!0),p)try{return y(e,n)}catch(e){}if(c(e,n))return r(!l.f.call(e,n),e[n])}},function(e,n,i){var t=i(0),l=i(31).f,r=i(5),o=i(29),a=i(19),c=i(71),p=i(65);e.exports=function(e,n){var i,y,h,x,s,u=e.target,d=e.global,f=e.stat;if(i=d?t:f?t[u]||a(u,{}):(t[u]||{}).prototype)for(y in n){if(x=n[y],h=e.noTargetGet?(s=l(i,y))&&s.value:i[y],!p(d?y:u+(f?".":"#")+y,e.forced)&&void 0!==h){if(typeof x==typeof h)continue;c(x,h)}(e.sham||h&&h.sham)&&r(x,"sham",!0),o(i,y,x,e)}}},function(e,n){var i=0,t=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++i+t).toString(36))}},function(e,n,i){var t=i(0),l=i(6),r=t.document,o=l(r)&&l(r.createElement);e.exports=function(e){return o?r.createElement(e):{}}},function(e,n,i){var t=i(8),l=i(4),r=i(34);e.exports=!t&&!l(function(){return 7!=Object.defineProperty(r("div"),"a",{get:function(){return 7}}).a})},function(e,n,i){var t=i(11);e.exports=t("native-function-to-string",Function.toString)},function(e,n,i){var t,l,r,o=i(76),a=i(0),c=i(6),p=i(5),y=i(1),h=i(16),x=i(15),s=a.WeakMap;if(o){var u=new s,d=u.get,f=u.has,g=u.set;t=function(e,n){return g.call(u,e,n),n},l=function(e){return d.call(u,e)||{}},r=function(e){return f.call(u,e)}}else{var v=h("state");x[v]=!0,t=function(e,n){return p(e,v,n),n},l=function(e){return y(e,v)?e[v]:{}},r=function(e){return y(e,v)}}e.exports={set:t,get:l,has:r,enforce:function(e){return r(e)?l(e):t(e,{})},getterFor:function(e){return function(n){var i;if(!c(n)||(i=l(n)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return i}}}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=Object.assign||function(e){for(var n=1;n0&&void 0!==arguments[0]?arguments[0]:{};if("undefined"==typeof document)throw new Error("`feather.replace()` only works in a browser environment.");var n=document.querySelectorAll("[data-feather]");Array.from(n).forEach(function(n){return function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=function(e){return Array.from(e.attributes).reduce(function(e,n){return e[n.name]=n.value,e},{})}(e),o=i["data-feather"];delete i["data-feather"];var a=r.default[o].toSvg(t({},n,i,{class:(0,l.default)(n.class,i.class)})),c=(new DOMParser).parseFromString(a,"image/svg+xml").querySelector("svg");e.parentNode.replaceChild(c,e)}(n,e)})}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t,l=i(12),r=(t=l)&&t.__esModule?t:{default:t};n.default=function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(console.warn("feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead."),!e)throw new Error("The required `key` (icon name) parameter is missing.");if(!r.default[e])throw new Error("No icon matching '"+e+"'. See the complete list of icons at https://feathericons.com");return r.default[e].toSvg(n)}},function(e){e.exports={activity:["pulse","health","action","motion"],airplay:["stream","cast","mirroring"],"alert-circle":["warning","alert","danger"],"alert-octagon":["warning","alert","danger"],"alert-triangle":["warning","alert","danger"],"align-center":["text alignment","center"],"align-justify":["text alignment","justified"],"align-left":["text alignment","left"],"align-right":["text alignment","right"],anchor:[],archive:["index","box"],"at-sign":["mention","at","email","message"],award:["achievement","badge"],aperture:["camera","photo"],"bar-chart":["statistics","diagram","graph"],"bar-chart-2":["statistics","diagram","graph"],battery:["power","electricity"],"battery-charging":["power","electricity"],bell:["alarm","notification","sound"],"bell-off":["alarm","notification","silent"],bluetooth:["wireless"],"book-open":["read","library"],book:["read","dictionary","booklet","magazine","library"],bookmark:["read","clip","marker","tag"],box:["cube"],briefcase:["work","bag","baggage","folder"],calendar:["date"],camera:["photo"],cast:["chromecast","airplay"],circle:["off","zero","record"],clipboard:["copy"],clock:["time","watch","alarm"],"cloud-drizzle":["weather","shower"],"cloud-lightning":["weather","bolt"],"cloud-rain":["weather"],"cloud-snow":["weather","blizzard"],cloud:["weather"],codepen:["logo"],codesandbox:["logo"],code:["source","programming"],coffee:["drink","cup","mug","tea","cafe","hot","beverage"],columns:["layout"],command:["keyboard","cmd","terminal","prompt"],compass:["navigation","safari","travel","direction"],copy:["clone","duplicate"],"corner-down-left":["arrow","return"],"corner-down-right":["arrow"],"corner-left-down":["arrow"],"corner-left-up":["arrow"],"corner-right-down":["arrow"],"corner-right-up":["arrow"],"corner-up-left":["arrow"],"corner-up-right":["arrow"],cpu:["processor","technology"],"credit-card":["purchase","payment","cc"],crop:["photo","image"],crosshair:["aim","target"],database:["storage","memory"],delete:["remove"],disc:["album","cd","dvd","music"],"dollar-sign":["currency","money","payment"],droplet:["water"],edit:["pencil","change"],"edit-2":["pencil","change"],"edit-3":["pencil","change"],eye:["view","watch"],"eye-off":["view","watch","hide","hidden"],"external-link":["outbound"],facebook:["logo","social"],"fast-forward":["music"],figma:["logo","design","tool"],"file-minus":["delete","remove","erase"],"file-plus":["add","create","new"],"file-text":["data","txt","pdf"],film:["movie","video"],filter:["funnel","hopper"],flag:["report"],"folder-minus":["directory"],"folder-plus":["directory"],folder:["directory"],framer:["logo","design","tool"],frown:["emoji","face","bad","sad","emotion"],gift:["present","box","birthday","party"],"git-branch":["code","version control"],"git-commit":["code","version control"],"git-merge":["code","version control"],"git-pull-request":["code","version control"],github:["logo","version control"],gitlab:["logo","version control"],globe:["world","browser","language","translate"],"hard-drive":["computer","server","memory","data"],hash:["hashtag","number","pound"],headphones:["music","audio","sound"],heart:["like","love","emotion"],"help-circle":["question mark"],hexagon:["shape","node.js","logo"],home:["house","living"],image:["picture"],inbox:["email"],instagram:["logo","camera"],key:["password","login","authentication","secure"],layers:["stack"],layout:["window","webpage"],"life-bouy":["help","life ring","support"],link:["chain","url"],"link-2":["chain","url"],linkedin:["logo","social media"],list:["options"],lock:["security","password","secure"],"log-in":["sign in","arrow","enter"],"log-out":["sign out","arrow","exit"],mail:["email","message"],"map-pin":["location","navigation","travel","marker"],map:["location","navigation","travel"],maximize:["fullscreen"],"maximize-2":["fullscreen","arrows","expand"],meh:["emoji","face","neutral","emotion"],menu:["bars","navigation","hamburger"],"message-circle":["comment","chat"],"message-square":["comment","chat"],"mic-off":["record","sound","mute"],mic:["record","sound","listen"],minimize:["exit fullscreen","close"],"minimize-2":["exit fullscreen","arrows","close"],minus:["subtract"],monitor:["tv","screen","display"],moon:["dark","night"],"more-horizontal":["ellipsis"],"more-vertical":["ellipsis"],"mouse-pointer":["arrow","cursor"],move:["arrows"],music:["note"],navigation:["location","travel"],"navigation-2":["location","travel"],octagon:["stop"],package:["box","container"],paperclip:["attachment"],pause:["music","stop"],"pause-circle":["music","audio","stop"],"pen-tool":["vector","drawing"],percent:["discount"],"phone-call":["ring"],"phone-forwarded":["call"],"phone-incoming":["call"],"phone-missed":["call"],"phone-off":["call","mute"],"phone-outgoing":["call"],phone:["call"],play:["music","start"],"pie-chart":["statistics","diagram"],"play-circle":["music","start"],plus:["add","new"],"plus-circle":["add","new"],"plus-square":["add","new"],pocket:["logo","save"],power:["on","off"],printer:["fax","office","device"],radio:["signal"],"refresh-cw":["synchronise","arrows"],"refresh-ccw":["arrows"],repeat:["loop","arrows"],rewind:["music"],"rotate-ccw":["arrow"],"rotate-cw":["arrow"],rss:["feed","subscribe"],save:["floppy disk"],scissors:["cut"],search:["find","magnifier","magnifying glass"],send:["message","mail","email","paper airplane","paper aeroplane"],settings:["cog","edit","gear","preferences"],"share-2":["network","connections"],shield:["security","secure"],"shield-off":["security","insecure"],"shopping-bag":["ecommerce","cart","purchase","store"],"shopping-cart":["ecommerce","cart","purchase","store"],shuffle:["music"],"skip-back":["music"],"skip-forward":["music"],slack:["logo"],slash:["ban","no"],sliders:["settings","controls"],smartphone:["cellphone","device"],smile:["emoji","face","happy","good","emotion"],speaker:["audio","music"],star:["bookmark","favorite","like"],"stop-circle":["media","music"],sun:["brightness","weather","light"],sunrise:["weather","time","morning","day"],sunset:["weather","time","evening","night"],tablet:["device"],tag:["label"],target:["logo","bullseye"],terminal:["code","command line","prompt"],thermometer:["temperature","celsius","fahrenheit","weather"],"thumbs-down":["dislike","bad","emotion"],"thumbs-up":["like","good","emotion"],"toggle-left":["on","off","switch"],"toggle-right":["on","off","switch"],tool:["settings","spanner"],trash:["garbage","delete","remove","bin"],"trash-2":["garbage","delete","remove","bin"],triangle:["delta"],truck:["delivery","van","shipping","transport","lorry"],tv:["television","stream"],twitch:["logo"],twitter:["logo","social"],type:["text"],umbrella:["rain","weather"],unlock:["security"],"user-check":["followed","subscribed"],"user-minus":["delete","remove","unfollow","unsubscribe"],"user-plus":["new","add","create","follow","subscribe"],"user-x":["delete","remove","unfollow","unsubscribe","unavailable"],user:["person","account"],users:["group"],"video-off":["camera","movie","film"],video:["camera","movie","film"],voicemail:["phone"],volume:["music","sound","mute"],"volume-1":["music","sound"],"volume-2":["music","sound"],"volume-x":["music","sound","mute"],watch:["clock","time"],"wifi-off":["disabled"],wifi:["connection","signal","wireless"],wind:["weather","air"],"x-circle":["cancel","close","delete","remove","times","clear"],"x-octagon":["delete","stop","alert","warning","times","clear"],"x-square":["cancel","close","delete","remove","times","clear"],x:["cancel","close","delete","remove","times","clear"],youtube:["logo","video","play"],"zap-off":["flash","camera","lightning"],zap:["flash","camera","lightning"],"zoom-in":["magnifying glass"],"zoom-out":["magnifying glass"]}},function(e){e.exports={activity:'',airplay:'',"alert-circle":'',"alert-octagon":'',"alert-triangle":'',"align-center":'',"align-justify":'',"align-left":'',"align-right":'',anchor:'',aperture:'',archive:'',"arrow-down-circle":'',"arrow-down-left":'',"arrow-down-right":'',"arrow-down":'',"arrow-left-circle":'',"arrow-left":'',"arrow-right-circle":'',"arrow-right":'',"arrow-up-circle":'',"arrow-up-left":'',"arrow-up-right":'',"arrow-up":'',"at-sign":'',award:'',"bar-chart-2":'',"bar-chart":'',"battery-charging":'',battery:'',"bell-off":'',bell:'',bluetooth:'',bold:'',"book-open":'',book:'',bookmark:'',box:'',briefcase:'',calendar:'',"camera-off":'',camera:'',cast:'',"check-circle":'',"check-square":'',check:'',"chevron-down":'',"chevron-left":'',"chevron-right":'',"chevron-up":'',"chevrons-down":'',"chevrons-left":'',"chevrons-right":'',"chevrons-up":'',chrome:'',circle:'',clipboard:'',clock:'',"cloud-drizzle":'',"cloud-lightning":'',"cloud-off":'',"cloud-rain":'',"cloud-snow":'',cloud:'',code:'',codepen:'',codesandbox:'',coffee:'',columns:'',command:'',compass:'',copy:'',"corner-down-left":'',"corner-down-right":'',"corner-left-down":'',"corner-left-up":'',"corner-right-down":'',"corner-right-up":'',"corner-up-left":'',"corner-up-right":'',cpu:'',"credit-card":'',crop:'',crosshair:'',database:'',delete:'',disc:'',"divide-circle":'',"divide-square":'',divide:'',"dollar-sign":'',"download-cloud":'',download:'',dribbble:'',droplet:'',"edit-2":'',"edit-3":'',edit:'',"external-link":'',"eye-off":'',eye:'',facebook:'',"fast-forward":'',feather:'',figma:'',"file-minus":'',"file-plus":'',"file-text":'',file:'',film:'',filter:'',flag:'',"folder-minus":'',"folder-plus":'',folder:'',framer:'',frown:'',gift:'',"git-branch":'',"git-commit":'',"git-merge":'',"git-pull-request":'',github:'',gitlab:'',globe:'',grid:'',"hard-drive":'',hash:'',headphones:'',heart:'',"help-circle":'',hexagon:'',home:'',image:'',inbox:'',info:'',instagram:'',italic:'',key:'',layers:'',layout:'',"life-buoy":'',"link-2":'',link:'',linkedin:'',list:'',loader:'',lock:'',"log-in":'',"log-out":'',mail:'',"map-pin":'',map:'',"maximize-2":'',maximize:'',meh:'',menu:'',"message-circle":'',"message-square":'',"mic-off":'',mic:'',"minimize-2":'',minimize:'',"minus-circle":'',"minus-square":'',minus:'',monitor:'',moon:'',"more-horizontal":'',"more-vertical":'',"mouse-pointer":'',move:'',music:'',"navigation-2":'',navigation:'',octagon:'',package:'',paperclip:'',"pause-circle":'',pause:'',"pen-tool":'',percent:'',"phone-call":'',"phone-forwarded":'',"phone-incoming":'',"phone-missed":'',"phone-off":'',"phone-outgoing":'',phone:'',"pie-chart":'',"play-circle":'',play:'',"plus-circle":'',"plus-square":'',plus:'',pocket:'',power:'',printer:'',radio:'',"refresh-ccw":'',"refresh-cw":'',repeat:'',rewind:'',"rotate-ccw":'',"rotate-cw":'',rss:'',save:'',scissors:'',search:'',send:'',server:'',settings:'',"share-2":'',share:'',"shield-off":'',shield:'',"shopping-bag":'',"shopping-cart":'',shuffle:'',sidebar:'',"skip-back":'',"skip-forward":'',slack:'',slash:'',sliders:'',smartphone:'',smile:'',speaker:'',square:'',star:'',"stop-circle":'',sun:'',sunrise:'',sunset:'',tablet:'',tag:'',target:'',terminal:'',thermometer:'',"thumbs-down":'',"thumbs-up":'',"toggle-left":'',"toggle-right":'',tool:'',"trash-2":'',trash:'',trello:'',"trending-down":'',"trending-up":'',triangle:'',truck:'',tv:'',twitch:'',twitter:'',type:'',umbrella:'',underline:'',unlock:'',"upload-cloud":'',upload:'',"user-check":'',"user-minus":'',"user-plus":'',"user-x":'',user:'',users:'',"video-off":'',video:'',voicemail:'',"volume-1":'',"volume-2":'',"volume-x":'',volume:'',watch:'',"wifi-off":'',wifi:'',wind:'',"x-circle":'',"x-octagon":'',"x-square":'',x:'',youtube:'',"zap-off":'',zap:'',"zoom-in":'',"zoom-out":''}},function(e){e.exports={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=Object.assign||function(e){for(var n=1;n2&&void 0!==arguments[2]?arguments[2]:[];!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),this.name=n,this.contents=i,this.tags=l,this.attrs=t({},o.default,{class:"feather feather-"+n})}return l(e,[{key:"toSvg",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return""+this.contents+""}},{key:"toString",value:function(){return this.contents}}]),e}();n.default=c},function(e,n,i){"use strict";var t=o(i(12)),l=o(i(39)),r=o(i(38));function o(e){return e&&e.__esModule?e:{default:e}}e.exports={icons:t.default,toSvg:l.default,replace:r.default}},function(e,n,i){e.exports=i(0)},function(e,n,i){var t=i(2)("iterator"),l=!1;try{var r=0,o={next:function(){return{done:!!r++}},return:function(){l=!0}};o[t]=function(){return this},Array.from(o,function(){throw 2})}catch(e){}e.exports=function(e,n){if(!n&&!l)return!1;var i=!1;try{var r={};r[t]=function(){return{next:function(){return{done:i=!0}}}},e(r)}catch(e){}return i}},function(e,n,i){var t=i(30),l=i(2)("toStringTag"),r="Arguments"==t(function(){return arguments}());e.exports=function(e){var n,i,o;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(i=function(e,n){try{return e[n]}catch(e){}}(n=Object(e),l))?i:r?t(n):"Object"==(o=t(n))&&"function"==typeof n.callee?"Arguments":o}},function(e,n,i){var t=i(47),l=i(9),r=i(2)("iterator");e.exports=function(e){if(void 0!=e)return e[r]||e["@@iterator"]||l[t(e)]}},function(e,n,i){"use strict";var t=i(18),l=i(7),r=i(10);e.exports=function(e,n,i){var o=t(n);o in e?l.f(e,o,r(0,i)):e[o]=i}},function(e,n,i){var t=i(2),l=i(9),r=t("iterator"),o=Array.prototype;e.exports=function(e){return void 0!==e&&(l.Array===e||o[r]===e)}},function(e,n,i){var t=i(3);e.exports=function(e,n,i,l){try{return l?n(t(i)[0],i[1]):n(i)}catch(n){var r=e.return;throw void 0!==r&&t(r.call(e)),n}}},function(e,n){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,n,i){var t=i(52);e.exports=function(e,n,i){if(t(e),void 0===n)return e;switch(i){case 0:return function(){return e.call(n)};case 1:return function(i){return e.call(n,i)};case 2:return function(i,t){return e.call(n,i,t)};case 3:return function(i,t,l){return e.call(n,i,t,l)}}return function(){return e.apply(n,arguments)}}},function(e,n,i){"use strict";var t=i(53),l=i(24),r=i(51),o=i(50),a=i(27),c=i(49),p=i(48);e.exports=function(e){var n,i,y,h,x=l(e),s="function"==typeof this?this:Array,u=arguments.length,d=u>1?arguments[1]:void 0,f=void 0!==d,g=0,v=p(x);if(f&&(d=t(d,u>2?arguments[2]:void 0,2)),void 0==v||s==Array&&o(v))for(i=new s(n=a(x.length));n>g;g++)c(i,g,f?d(x[g],g):x[g]);else for(h=v.call(x),i=new s;!(y=h.next()).done;g++)c(i,g,f?r(h,d,[y.value,g],!0):y.value);return i.length=g,i}},function(e,n,i){var t=i(32),l=i(54);t({target:"Array",stat:!0,forced:!i(46)(function(e){Array.from(e)})},{from:l})},function(e,n,i){var t=i(6),l=i(3);e.exports=function(e,n){if(l(e),!t(n)&&null!==n)throw TypeError("Can't set "+String(n)+" as a prototype")}},function(e,n,i){var t=i(56);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,n=!1,i={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(i,[]),n=i instanceof Array}catch(e){}return function(i,l){return t(i,l),n?e.call(i,l):i.__proto__=l,i}}():void 0)},function(e,n,i){var t=i(0).document;e.exports=t&&t.documentElement},function(e,n,i){var t=i(28),l=i(13);e.exports=Object.keys||function(e){return t(e,l)}},function(e,n,i){var t=i(8),l=i(7),r=i(3),o=i(59);e.exports=t?Object.defineProperties:function(e,n){r(e);for(var i,t=o(n),a=t.length,c=0;a>c;)l.f(e,i=t[c++],n[i]);return e}},function(e,n,i){var t=i(3),l=i(60),r=i(13),o=i(15),a=i(58),c=i(34),p=i(16)("IE_PROTO"),y=function(){},h=function(){var e,n=c("iframe"),i=r.length;for(n.style.display="none",a.appendChild(n),n.src=String("javascript:"),(e=n.contentWindow.document).open(),e.write("