diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 213c4876..f4d57c6e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: [ '1.25', '1.26.x' ] + go-version: [ '1.26.x' ] steps: - uses: actions/checkout@v4 @@ -33,3 +33,11 @@ jobs: - name: Test run: go test -v ./src/... + + - name: Check TypeScript + run: go tool tsgo -p src/rawdata/js + + - name: Ensure JS/CSS are transpiled + run: | + go run . bundle + git diff --exit-code public/ diff --git a/README.md b/README.md index 00199615..4d4f26da 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ We want the website to be a great example of Handmade software on the web. We en You will need the following software installed: -- Go 1.25 or newer: https://go.dev/ +- Go 1.26 or newer: https://go.dev/ You can download Go directly from the website, or install it through major package managers. If you already have Go installed, but are unsure of the version, you can check by running `go version`. diff --git a/go.mod b/go.mod index 8fb379f7..d59acf2a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.handmade.network/hmn/hmn -go 1.25.0 +go 1.26 require ( github.com/HandmadeNetwork/golorem v0.0.0-20220507185207-414965a3a817 @@ -37,6 +37,7 @@ require ( require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect @@ -54,21 +55,27 @@ require ( github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/mackerelio/go-osstat v0.2.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect + github.com/microsoft/typescript-go v0.0.0-20260727225212-70c2f5e51856 // indirect github.com/mitchellh/copystructure v1.1.1 // indirect github.com/mitchellh/reflectwalk v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/teambition/rrule-go v1.7.2 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect golang.org/x/text v0.40.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -77,3 +84,5 @@ replace ( github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8 => github.com/HandmadeNetwork/bbcode v0.0.0-20210623031351-ec0e2e2e39d9 github.com/yuin/goldmark v1.4.1 => github.com/HandmadeNetwork/goldmark v1.4.1-0.20210707024600-f7e596e26b5e ) + +tool github.com/microsoft/typescript-go/cmd/tsgo diff --git a/go.sum b/go.sum index 03721a4f..823f3526 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= @@ -110,6 +112,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 h1:KZaTBSyshWX3MP5jukJcNSuXDQTO+rNpt0J564dX/eg= +github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -130,6 +134,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -189,6 +195,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -197,6 +205,8 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mackerelio/go-osstat v0.2.7 h1:TCavZi10wF49bT6iQZ9eT2keGZQpC69MTDfdJej5e94= +github.com/mackerelio/go-osstat v0.2.7/go.mod h1:dwpYh5pIPmvk+IEwBKNIWRFMB92mrC08CmXOhDC7nQk= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -210,6 +220,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microsoft/typescript-go v0.0.0-20260727225212-70c2f5e51856 h1:DRFuVdNc2TFEU7hRdfcxofj8OxZ7dXwEMhCuP0LaRes= +github.com/microsoft/typescript-go v0.0.0-20260727225212-70c2f5e51856/go.mod h1:gV4G6M8xxLNqQSRHiFOgKHlY+5YJgYPM6QwFLWllC+8= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= @@ -229,6 +241,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peter-evans/patience v0.3.0 h1:rX0JdJeepqdQl1Sk9c9uvorjYYzL2TfgLX1adqYm9cA= +github.com/peter-evans/patience v0.3.0/go.mod h1:Kmxu5sY1NmBLFSStvXjX1wS9mIv7wMcP/ubucyMOAu0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -297,6 +311,10 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01 h1:0SJnXjE4jDClMW6grE0xpNhwpqbPwkBTn8zpVw5C0SI= github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01/go.mod h1:TwKQPa5XkCCRC2GRZ5wtfNUTQ2+9/i19mGRijFeJ4BE= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -377,6 +395,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -440,6 +460,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 1c3f5488..7d5a333e 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( _ "git.handmade.network/hmn/hmn/src/admintools" _ "git.handmade.network/hmn/hmn/src/assets" - _ "git.handmade.network/hmn/hmn/src/buildcss/cmd" + _ "git.handmade.network/hmn/hmn/src/bundle/cmd" _ "git.handmade.network/hmn/hmn/src/discord/cmd" _ "git.handmade.network/hmn/hmn/src/initimage" _ "git.handmade.network/hmn/hmn/src/migration" diff --git a/public/hsf/hsf.css b/public/hsf/hsf.css deleted file mode 100644 index 507a1d2c..00000000 --- a/public/hsf/hsf.css +++ /dev/null @@ -1,7687 +0,0 @@ -/* src/rawdata/css/hsf/tachyons.min.css */ -html { - line-height: 1.15; - -webkit-text-size-adjust: 100%; -} -body { - margin: 0; -} -h1 { - font-size: 2em; - margin: .67em 0; -} -hr { - box-sizing: content-box; - height: 0; - overflow: visible; -} -pre { - font-family: monospace, monospace; - font-size: 1em; -} -a { - background-color: transparent; -} -abbr[title] { - border-bottom: none; - text-decoration: underline; - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; -} -b, -strong { - font-weight: bolder; -} -code, -kbd, -samp { - font-family: monospace, monospace; - font-size: 1em; -} -small { - font-size: 80%; -} -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} -sub { - bottom: -.25em; -} -sup { - top: -.5em; -} -img { - border-style: none; -} -button, -input, -optgroup, -select, -textarea { - font-family: inherit; - font-size: 100%; - line-height: 1.15; - margin: 0; -} -button, -input { - overflow: visible; -} -button, -select { - text-transform: none; -} -[type=button], -[type=reset], -[type=submit], -button { - -webkit-appearance: button; -} -[type=button]::-moz-focus-inner, -[type=reset]::-moz-focus-inner, -[type=submit]::-moz-focus-inner, -button::-moz-focus-inner { - border-style: none; - padding: 0; -} -[type=button]:-moz-focusring, -[type=reset]:-moz-focusring, -[type=submit]:-moz-focusring, -button:-moz-focusring { - outline: 1px dotted ButtonText; -} -fieldset { - padding: .35em .75em .625em; -} -legend { - box-sizing: border-box; - color: inherit; - display: table; - max-width: 100%; - padding: 0; - white-space: normal; -} -progress { - vertical-align: baseline; -} -textarea { - overflow: auto; -} -[type=checkbox], -[type=radio] { - box-sizing: border-box; - padding: 0; -} -[type=number]::-webkit-inner-spin-button, -[type=number]::-webkit-outer-spin-button { - height: auto; -} -[type=search] { - -webkit-appearance: textfield; - outline-offset: -2px; -} -[type=search]::-webkit-search-decoration { - -webkit-appearance: none; -} -::-webkit-file-upload-button { - -webkit-appearance: button; - font: inherit; -} -details { - display: block; -} -summary { - display: list-item; -} -[hidden], -template { - display: none !important; -} -.border-box, -a, -article, -aside, -blockquote, -body, -code, -dd, -div, -dl, -dt, -fieldset, -figcaption, -figure, -footer, -form, -h1, -h2, -h3, -h4, -h5, -h6, -header, -html, -input[type=email], -input[type=number], -input[type=password], -input[type=tel], -input[type=text], -input[type=url], -legend, -li, -main, -nav, -ol, -p, -pre, -section, -table, -td, -textarea, -th, -tr, -ul { - box-sizing: border-box; -} -.aspect-ratio { - height: 0; - position: relative; -} -.aspect-ratio--16x9 { - padding-bottom: 56.25%; -} -.aspect-ratio--9x16 { - padding-bottom: 177.77%; -} -.aspect-ratio--4x3 { - padding-bottom: 75%; -} -.aspect-ratio--3x4 { - padding-bottom: 133.33%; -} -.aspect-ratio--6x4 { - padding-bottom: 66.6%; -} -.aspect-ratio--4x6 { - padding-bottom: 150%; -} -.aspect-ratio--8x5 { - padding-bottom: 62.5%; -} -.aspect-ratio--5x8 { - padding-bottom: 160%; -} -.aspect-ratio--7x5 { - padding-bottom: 71.42%; -} -.aspect-ratio--5x7 { - padding-bottom: 140%; -} -.aspect-ratio--1x1 { - padding-bottom: 100%; -} -.aspect-ratio--object { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 100; -} -img { - max-width: 100%; -} -.cover { - background-size: cover !important; -} -.contain { - background-size: contain !important; -} -.bg-center { - background-position: 50%; -} -.bg-center, -.bg-top { - background-repeat: no-repeat; -} -.bg-top { - background-position: top; -} -.bg-right { - background-position: 100%; -} -.bg-bottom, -.bg-right { - background-repeat: no-repeat; -} -.bg-bottom { - background-position: bottom; -} -.bg-left { - background-repeat: no-repeat; - background-position: 0; -} -.outline { - outline: 1px solid; -} -.outline-transparent { - outline: 1px solid transparent; -} -.outline-0 { - outline: 0; -} -.ba { - border-style: solid; - border-width: 1px; -} -.bt { - border-top-style: solid; - border-top-width: 1px; -} -.br { - border-right-style: solid; - border-right-width: 1px; -} -.bb { - border-bottom-style: solid; - border-bottom-width: 1px; -} -.bl { - border-left-style: solid; - border-left-width: 1px; -} -.bn { - border-style: none; - border-width: 0; -} -.b--black { - border-color: #000; -} -.b--near-black { - border-color: #111; -} -.b--dark-gray { - border-color: #333; -} -.b--mid-gray { - border-color: #555; -} -.b--gray { - border-color: #777; -} -.b--silver { - border-color: #999; -} -.b--light-silver { - border-color: #aaa; -} -.b--moon-gray { - border-color: #ccc; -} -.b--light-gray { - border-color: #eee; -} -.b--near-white { - border-color: #f4f4f4; -} -.b--white { - border-color: #fff; -} -.b--white-90 { - border-color: hsla(0, 0%, 100%, .9); -} -.b--white-80 { - border-color: hsla(0, 0%, 100%, .8); -} -.b--white-70 { - border-color: hsla(0, 0%, 100%, .7); -} -.b--white-60 { - border-color: hsla(0, 0%, 100%, .6); -} -.b--white-50 { - border-color: hsla(0, 0%, 100%, .5); -} -.b--white-40 { - border-color: hsla(0, 0%, 100%, .4); -} -.b--white-30 { - border-color: hsla(0, 0%, 100%, .3); -} -.b--white-20 { - border-color: hsla(0, 0%, 100%, .2); -} -.b--white-10 { - border-color: hsla(0, 0%, 100%, .1); -} -.b--white-05 { - border-color: hsla(0, 0%, 100%, .05); -} -.b--white-025 { - border-color: hsla(0, 0%, 100%, .025); -} -.b--white-0125 { - border-color: hsla(0, 0%, 100%, .0125); -} -.b--black-90 { - border-color: rgba(0, 0, 0, .9); -} -.b--black-80 { - border-color: rgba(0, 0, 0, .8); -} -.b--black-70 { - border-color: rgba(0, 0, 0, .7); -} -.b--black-60 { - border-color: rgba(0, 0, 0, .6); -} -.b--black-50 { - border-color: rgba(0, 0, 0, .5); -} -.b--black-40 { - border-color: rgba(0, 0, 0, .4); -} -.b--black-30 { - border-color: rgba(0, 0, 0, .3); -} -.b--black-20 { - border-color: rgba(0, 0, 0, .2); -} -.b--black-10 { - border-color: rgba(0, 0, 0, .1); -} -.b--black-05 { - border-color: rgba(0, 0, 0, .05); -} -.b--black-025 { - border-color: rgba(0, 0, 0, .025); -} -.b--black-0125 { - border-color: rgba(0, 0, 0, .0125); -} -.b--dark-red { - border-color: #e7040f; -} -.b--red { - border-color: #ff4136; -} -.b--light-red { - border-color: #ff725c; -} -.b--orange { - border-color: #ff6300; -} -.b--gold { - border-color: #ffb700; -} -.b--yellow { - border-color: gold; -} -.b--light-yellow { - border-color: #fbf1a9; -} -.b--purple { - border-color: #5e2ca5; -} -.b--light-purple { - border-color: #a463f2; -} -.b--dark-pink { - border-color: #d5008f; -} -.b--hot-pink { - border-color: #ff41b4; -} -.b--pink { - border-color: #ff80cc; -} -.b--light-pink { - border-color: #ffa3d7; -} -.b--dark-green { - border-color: #137752; -} -.b--green { - border-color: #19a974; -} -.b--light-green { - border-color: #9eebcf; -} -.b--navy { - border-color: #001b44; -} -.b--dark-blue { - border-color: #00449e; -} -.b--blue { - border-color: #357edd; -} -.b--light-blue { - border-color: #96ccff; -} -.b--lightest-blue { - border-color: #cdecff; -} -.b--washed-blue { - border-color: #f6fffe; -} -.b--washed-green { - border-color: #e8fdf5; -} -.b--washed-yellow { - border-color: #fffceb; -} -.b--washed-red { - border-color: #ffdfdf; -} -.b--transparent { - border-color: transparent; -} -.b--inherit { - border-color: inherit; -} -.b--initial { - border-color: initial; -} -.b--unset { - border-color: unset; -} -.br0 { - border-radius: 0; -} -.br1 { - border-radius: .125rem; -} -.br2 { - border-radius: .25rem; -} -.br3 { - border-radius: .5rem; -} -.br4 { - border-radius: 1rem; -} -.br-100 { - border-radius: 100%; -} -.br-pill { - border-radius: 9999px; -} -.br--bottom { - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.br--top { - border-bottom-right-radius: 0; -} -.br--right, -.br--top { - border-bottom-left-radius: 0; -} -.br--right { - border-top-left-radius: 0; -} -.br--left { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.br-inherit { - border-radius: inherit; -} -.br-initial { - border-radius: initial; -} -.br-unset { - border-radius: unset; -} -.b--dotted { - border-style: dotted; -} -.b--dashed { - border-style: dashed; -} -.b--solid { - border-style: solid; -} -.b--none { - border-style: none; -} -.bw0 { - border-width: 0; -} -.bw1 { - border-width: .125rem; -} -.bw2 { - border-width: .25rem; -} -.bw3 { - border-width: .5rem; -} -.bw4 { - border-width: 1rem; -} -.bw5 { - border-width: 2rem; -} -.bt-0 { - border-top-width: 0; -} -.br-0 { - border-right-width: 0; -} -.bb-0 { - border-bottom-width: 0; -} -.bl-0 { - border-left-width: 0; -} -.shadow-1 { - box-shadow: 0 0 4px 2px rgba(0, 0, 0, .2); -} -.shadow-2 { - box-shadow: 0 0 8px 2px rgba(0, 0, 0, .2); -} -.shadow-3 { - box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, .2); -} -.shadow-4 { - box-shadow: 2px 2px 8px 0 rgba(0, 0, 0, .2); -} -.shadow-5 { - box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, .2); -} -.pre { - overflow-x: auto; - overflow-y: hidden; - overflow: scroll; -} -.top-0 { - top: 0; -} -.right-0 { - right: 0; -} -.bottom-0 { - bottom: 0; -} -.left-0 { - left: 0; -} -.top-1 { - top: 1rem; -} -.right-1 { - right: 1rem; -} -.bottom-1 { - bottom: 1rem; -} -.left-1 { - left: 1rem; -} -.top-2 { - top: 2rem; -} -.right-2 { - right: 2rem; -} -.bottom-2 { - bottom: 2rem; -} -.left-2 { - left: 2rem; -} -.top--1 { - top: -1rem; -} -.right--1 { - right: -1rem; -} -.bottom--1 { - bottom: -1rem; -} -.left--1 { - left: -1rem; -} -.top--2 { - top: -2rem; -} -.right--2 { - right: -2rem; -} -.bottom--2 { - bottom: -2rem; -} -.left--2 { - left: -2rem; -} -.absolute--fill { - top: 0; - right: 0; - bottom: 0; - left: 0; -} -.cf:after, -.cf:before { - content: " "; - display: table; -} -.cf:after { - clear: both; -} -.cf { - zoom: 1; -} -.cl { - clear: left; -} -.cr { - clear: right; -} -.cb { - clear: both; -} -.cn { - clear: none; -} -.dn { - display: none; -} -.di { - display: inline; -} -.db { - display: block; -} -.dib { - display: inline-block; -} -.dit { - display: inline-table; -} -.dt { - display: table; -} -.dtc { - display: table-cell; -} -.dt-row { - display: table-row; -} -.dt-row-group { - display: table-row-group; -} -.dt-column { - display: table-column; -} -.dt-column-group { - display: table-column-group; -} -.dt--fixed { - table-layout: fixed; - width: 100%; -} -.flex { - display: flex; -} -.inline-flex { - display: inline-flex; -} -.flex-auto { - flex: 1 1 auto; - min-width: 0; - min-height: 0; -} -.flex-none { - flex: none; -} -.flex-column { - flex-direction: column; -} -.flex-row { - flex-direction: row; -} -.flex-wrap { - flex-wrap: wrap; -} -.flex-nowrap { - flex-wrap: nowrap; -} -.flex-wrap-reverse { - flex-wrap: wrap-reverse; -} -.flex-column-reverse { - flex-direction: column-reverse; -} -.flex-row-reverse { - flex-direction: row-reverse; -} -.items-start { - align-items: flex-start; -} -.items-end { - align-items: flex-end; -} -.items-center { - align-items: center; -} -.items-baseline { - align-items: baseline; -} -.items-stretch { - align-items: stretch; -} -.self-start { - align-self: flex-start; -} -.self-end { - align-self: flex-end; -} -.self-center { - align-self: center; -} -.self-baseline { - align-self: baseline; -} -.self-stretch { - align-self: stretch; -} -.justify-start { - justify-content: flex-start; -} -.justify-end { - justify-content: flex-end; -} -.justify-center { - justify-content: center; -} -.justify-between { - justify-content: space-between; -} -.justify-around { - justify-content: space-around; -} -.content-start { - align-content: flex-start; -} -.content-end { - align-content: flex-end; -} -.content-center { - align-content: center; -} -.content-between { - align-content: space-between; -} -.content-around { - align-content: space-around; -} -.content-stretch { - align-content: stretch; -} -.order-0 { - order: 0; -} -.order-1 { - order: 1; -} -.order-2 { - order: 2; -} -.order-3 { - order: 3; -} -.order-4 { - order: 4; -} -.order-5 { - order: 5; -} -.order-6 { - order: 6; -} -.order-7 { - order: 7; -} -.order-8 { - order: 8; -} -.order-last { - order: 99999; -} -.flex-grow-0 { - flex-grow: 0; -} -.flex-grow-1 { - flex-grow: 1; -} -.flex-shrink-0 { - flex-shrink: 0; -} -.flex-shrink-1 { - flex-shrink: 1; -} -.fl { - float: left; -} -.fl, -.fr { - _display: inline; -} -.fr { - float: right; -} -.fn { - float: none; -} -.sans-serif { - font-family: - -apple-system, - BlinkMacSystemFont, - avenir next, - avenir, - helvetica neue, - helvetica, - ubuntu, - roboto, - noto, - segoe ui, - arial, - sans-serif; -} -.serif { - font-family: - georgia, - times, - serif; -} -.system-sans-serif { - font-family: sans-serif; -} -.system-serif { - font-family: serif; -} -.code, -code { - font-family: - Consolas, - monaco, - monospace; -} -.courier { - font-family: - Courier Next, - courier, - monospace; -} -.helvetica { - font-family: - helvetica neue, - helvetica, - sans-serif; -} -.avenir { - font-family: - avenir next, - avenir, - sans-serif; -} -.athelas { - font-family: - athelas, - georgia, - serif; -} -.georgia { - font-family: georgia, serif; -} -.times { - font-family: times, serif; -} -.bodoni { - font-family: Bodoni MT, serif; -} -.calisto { - font-family: Calisto MT, serif; -} -.garamond { - font-family: garamond, serif; -} -.baskerville { - font-family: baskerville, serif; -} -.i { - font-style: italic; -} -.fs-normal { - font-style: normal; -} -.normal { - font-weight: 400; -} -.b { - font-weight: 700; -} -.fw1 { - font-weight: 100; -} -.fw2 { - font-weight: 200; -} -.fw3 { - font-weight: 300; -} -.fw4 { - font-weight: 400; -} -.fw5 { - font-weight: 500; -} -.fw6 { - font-weight: 600; -} -.fw7 { - font-weight: 700; -} -.fw8 { - font-weight: 800; -} -.fw9 { - font-weight: 900; -} -.input-reset { - -webkit-appearance: none; - -moz-appearance: none; -} -.button-reset::-moz-focus-inner, -.input-reset::-moz-focus-inner { - border: 0; - padding: 0; -} -.h1 { - height: 1rem; -} -.h2 { - height: 2rem; -} -.h3 { - height: 4rem; -} -.h4 { - height: 8rem; -} -.h5 { - height: 16rem; -} -.h-25 { - height: 25%; -} -.h-50 { - height: 50%; -} -.h-75 { - height: 75%; -} -.h-100 { - height: 100%; -} -.min-h-100 { - min-height: 100%; -} -.vh-25 { - height: 25vh; -} -.vh-50 { - height: 50vh; -} -.vh-75 { - height: 75vh; -} -.vh-100 { - height: 100vh; -} -.min-vh-100 { - min-height: 100vh; -} -.h-auto { - height: auto; -} -.h-inherit { - height: inherit; -} -.tracked { - letter-spacing: .1em; -} -.tracked-tight { - letter-spacing: -.05em; -} -.tracked-mega { - letter-spacing: .25em; -} -.lh-solid { - line-height: 1; -} -.lh-title { - line-height: 1.25; -} -.lh-copy { - line-height: 1.5; -} -.link { - text-decoration: none; -} -.link, -.link:active, -.link:focus, -.link:hover, -.link:link, -.link:visited { - transition: color .15s ease-in; -} -.link:focus { - outline: 1px dotted currentColor; -} -.list { - list-style-type: none; -} -.mw-100 { - max-width: 100%; -} -.mw1 { - max-width: 1rem; -} -.mw2 { - max-width: 2rem; -} -.mw3 { - max-width: 4rem; -} -.mw4 { - max-width: 8rem; -} -.mw5 { - max-width: 16rem; -} -.mw6 { - max-width: 32rem; -} -.mw7 { - max-width: 48rem; -} -.mw8 { - max-width: 64rem; -} -.mw9 { - max-width: 96rem; -} -.mw-none { - max-width: none; -} -.w1 { - width: 1rem; -} -.w2 { - width: 2rem; -} -.w3 { - width: 4rem; -} -.w4 { - width: 8rem; -} -.w5 { - width: 16rem; -} -.w-10 { - width: 10%; -} -.w-20 { - width: 20%; -} -.w-25 { - width: 25%; -} -.w-30 { - width: 30%; -} -.w-33 { - width: 33%; -} -.w-34 { - width: 34%; -} -.w-40 { - width: 40%; -} -.w-50 { - width: 50%; -} -.w-60 { - width: 60%; -} -.w-70 { - width: 70%; -} -.w-75 { - width: 75%; -} -.w-80 { - width: 80%; -} -.w-90 { - width: 90%; -} -.w-100 { - width: 100%; -} -.w-third { - width: 33.33333%; -} -.w-two-thirds { - width: 66.66667%; -} -.w-auto { - width: auto; -} -.overflow-visible { - overflow: visible; -} -.overflow-hidden { - overflow: hidden; -} -.overflow-scroll { - overflow: scroll; -} -.overflow-auto { - overflow: auto; -} -.overflow-x-visible { - overflow-x: visible; -} -.overflow-x-hidden { - overflow-x: hidden; -} -.overflow-x-scroll { - overflow-x: scroll; -} -.overflow-x-auto { - overflow-x: auto; -} -.overflow-y-visible { - overflow-y: visible; -} -.overflow-y-hidden { - overflow-y: hidden; -} -.overflow-y-scroll { - overflow-y: scroll; -} -.overflow-y-auto { - overflow-y: auto; -} -.static { - position: static; -} -.relative { - position: relative; -} -.absolute { - position: absolute; -} -.fixed { - position: fixed; -} -.o-100 { - opacity: 1; -} -.o-90 { - opacity: .9; -} -.o-80 { - opacity: .8; -} -.o-70 { - opacity: .7; -} -.o-60 { - opacity: .6; -} -.o-50 { - opacity: .5; -} -.o-40 { - opacity: .4; -} -.o-30 { - opacity: .3; -} -.o-20 { - opacity: .2; -} -.o-10 { - opacity: .1; -} -.o-05 { - opacity: .05; -} -.o-025 { - opacity: .025; -} -.o-0 { - opacity: 0; -} -.rotate-45 { - -webkit-transform: rotate(45deg); - transform: rotate(45deg); -} -.rotate-90 { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); -} -.rotate-135 { - -webkit-transform: rotate(135deg); - transform: rotate(135deg); -} -.rotate-180 { - -webkit-transform: rotate(180deg); - transform: rotate(180deg); -} -.rotate-225 { - -webkit-transform: rotate(225deg); - transform: rotate(225deg); -} -.rotate-270 { - -webkit-transform: rotate(270deg); - transform: rotate(270deg); -} -.rotate-315 { - -webkit-transform: rotate(315deg); - transform: rotate(315deg); -} -.black-90 { - color: rgba(0, 0, 0, .9); -} -.black-80 { - color: rgba(0, 0, 0, .8); -} -.black-70 { - color: rgba(0, 0, 0, .7); -} -.black-60 { - color: rgba(0, 0, 0, .6); -} -.black-50 { - color: rgba(0, 0, 0, .5); -} -.black-40 { - color: rgba(0, 0, 0, .4); -} -.black-30 { - color: rgba(0, 0, 0, .3); -} -.black-20 { - color: rgba(0, 0, 0, .2); -} -.black-10 { - color: rgba(0, 0, 0, .1); -} -.black-05 { - color: rgba(0, 0, 0, .05); -} -.white-90 { - color: hsla(0, 0%, 100%, .9); -} -.white-80 { - color: hsla(0, 0%, 100%, .8); -} -.white-70 { - color: hsla(0, 0%, 100%, .7); -} -.white-60 { - color: hsla(0, 0%, 100%, .6); -} -.white-50 { - color: hsla(0, 0%, 100%, .5); -} -.white-40 { - color: hsla(0, 0%, 100%, .4); -} -.white-30 { - color: hsla(0, 0%, 100%, .3); -} -.white-20 { - color: hsla(0, 0%, 100%, .2); -} -.white-10 { - color: hsla(0, 0%, 100%, .1); -} -.black { - color: #000; -} -.near-black { - color: #111; -} -.dark-gray { - color: #333; -} -.mid-gray { - color: #555; -} -.gray { - color: #777; -} -.silver { - color: #999; -} -.light-silver { - color: #aaa; -} -.moon-gray { - color: #ccc; -} -.light-gray { - color: #eee; -} -.near-white { - color: #f4f4f4; -} -.white { - color: #fff; -} -.dark-red { - color: #e7040f; -} -.red { - color: #ff4136; -} -.light-red { - color: #ff725c; -} -.orange { - color: #ff6300; -} -.gold { - color: #ffb700; -} -.yellow { - color: gold; -} -.light-yellow { - color: #fbf1a9; -} -.purple { - color: #5e2ca5; -} -.light-purple { - color: #a463f2; -} -.dark-pink { - color: #d5008f; -} -.hot-pink { - color: #ff41b4; -} -.pink { - color: #ff80cc; -} -.light-pink { - color: #ffa3d7; -} -.dark-green { - color: #137752; -} -.green { - color: #19a974; -} -.light-green { - color: #9eebcf; -} -.navy { - color: #001b44; -} -.dark-blue { - color: #00449e; -} -.blue { - color: #357edd; -} -.light-blue { - color: #96ccff; -} -.lightest-blue { - color: #cdecff; -} -.washed-blue { - color: #f6fffe; -} -.washed-green { - color: #e8fdf5; -} -.washed-yellow { - color: #fffceb; -} -.washed-red { - color: #ffdfdf; -} -.color-inherit { - color: inherit; -} -.bg-black-90 { - background-color: rgba(0, 0, 0, .9); -} -.bg-black-80 { - background-color: rgba(0, 0, 0, .8); -} -.bg-black-70 { - background-color: rgba(0, 0, 0, .7); -} -.bg-black-60 { - background-color: rgba(0, 0, 0, .6); -} -.bg-black-50 { - background-color: rgba(0, 0, 0, .5); -} -.bg-black-40 { - background-color: rgba(0, 0, 0, .4); -} -.bg-black-30 { - background-color: rgba(0, 0, 0, .3); -} -.bg-black-20 { - background-color: rgba(0, 0, 0, .2); -} -.bg-black-10 { - background-color: rgba(0, 0, 0, .1); -} -.bg-black-05 { - background-color: rgba(0, 0, 0, .05); -} -.bg-white-90 { - background-color: hsla(0, 0%, 100%, .9); -} -.bg-white-80 { - background-color: hsla(0, 0%, 100%, .8); -} -.bg-white-70 { - background-color: hsla(0, 0%, 100%, .7); -} -.bg-white-60 { - background-color: hsla(0, 0%, 100%, .6); -} -.bg-white-50 { - background-color: hsla(0, 0%, 100%, .5); -} -.bg-white-40 { - background-color: hsla(0, 0%, 100%, .4); -} -.bg-white-30 { - background-color: hsla(0, 0%, 100%, .3); -} -.bg-white-20 { - background-color: hsla(0, 0%, 100%, .2); -} -.bg-white-10 { - background-color: hsla(0, 0%, 100%, .1); -} -.bg-black { - background-color: #000; -} -.bg-near-black { - background-color: #111; -} -.bg-dark-gray { - background-color: #333; -} -.bg-mid-gray { - background-color: #555; -} -.bg-gray { - background-color: #777; -} -.bg-silver { - background-color: #999; -} -.bg-light-silver { - background-color: #aaa; -} -.bg-moon-gray { - background-color: #ccc; -} -.bg-light-gray { - background-color: #eee; -} -.bg-near-white { - background-color: #f4f4f4; -} -.bg-white { - background-color: #fff; -} -.bg-transparent { - background-color: transparent; -} -.bg-dark-red { - background-color: #e7040f; -} -.bg-red { - background-color: #ff4136; -} -.bg-light-red { - background-color: #ff725c; -} -.bg-orange { - background-color: #ff6300; -} -.bg-gold { - background-color: #ffb700; -} -.bg-yellow { - background-color: gold; -} -.bg-light-yellow { - background-color: #fbf1a9; -} -.bg-purple { - background-color: #5e2ca5; -} -.bg-light-purple { - background-color: #a463f2; -} -.bg-dark-pink { - background-color: #d5008f; -} -.bg-hot-pink { - background-color: #ff41b4; -} -.bg-pink { - background-color: #ff80cc; -} -.bg-light-pink { - background-color: #ffa3d7; -} -.bg-dark-green { - background-color: #137752; -} -.bg-green { - background-color: #19a974; -} -.bg-light-green { - background-color: #9eebcf; -} -.bg-navy { - background-color: #001b44; -} -.bg-dark-blue { - background-color: #00449e; -} -.bg-blue { - background-color: #357edd; -} -.bg-light-blue { - background-color: #96ccff; -} -.bg-lightest-blue { - background-color: #cdecff; -} -.bg-washed-blue { - background-color: #f6fffe; -} -.bg-washed-green { - background-color: #e8fdf5; -} -.bg-washed-yellow { - background-color: #fffceb; -} -.bg-washed-red { - background-color: #ffdfdf; -} -.bg-inherit { - background-color: inherit; -} -.hover-black:focus, -.hover-black:hover { - color: #000; -} -.hover-near-black:focus, -.hover-near-black:hover { - color: #111; -} -.hover-dark-gray:focus, -.hover-dark-gray:hover { - color: #333; -} -.hover-mid-gray:focus, -.hover-mid-gray:hover { - color: #555; -} -.hover-gray:focus, -.hover-gray:hover { - color: #777; -} -.hover-silver:focus, -.hover-silver:hover { - color: #999; -} -.hover-light-silver:focus, -.hover-light-silver:hover { - color: #aaa; -} -.hover-moon-gray:focus, -.hover-moon-gray:hover { - color: #ccc; -} -.hover-light-gray:focus, -.hover-light-gray:hover { - color: #eee; -} -.hover-near-white:focus, -.hover-near-white:hover { - color: #f4f4f4; -} -.hover-white:focus, -.hover-white:hover { - color: #fff; -} -.hover-black-90:focus, -.hover-black-90:hover { - color: rgba(0, 0, 0, .9); -} -.hover-black-80:focus, -.hover-black-80:hover { - color: rgba(0, 0, 0, .8); -} -.hover-black-70:focus, -.hover-black-70:hover { - color: rgba(0, 0, 0, .7); -} -.hover-black-60:focus, -.hover-black-60:hover { - color: rgba(0, 0, 0, .6); -} -.hover-black-50:focus, -.hover-black-50:hover { - color: rgba(0, 0, 0, .5); -} -.hover-black-40:focus, -.hover-black-40:hover { - color: rgba(0, 0, 0, .4); -} -.hover-black-30:focus, -.hover-black-30:hover { - color: rgba(0, 0, 0, .3); -} -.hover-black-20:focus, -.hover-black-20:hover { - color: rgba(0, 0, 0, .2); -} -.hover-black-10:focus, -.hover-black-10:hover { - color: rgba(0, 0, 0, .1); -} -.hover-white-90:focus, -.hover-white-90:hover { - color: hsla(0, 0%, 100%, .9); -} -.hover-white-80:focus, -.hover-white-80:hover { - color: hsla(0, 0%, 100%, .8); -} -.hover-white-70:focus, -.hover-white-70:hover { - color: hsla(0, 0%, 100%, .7); -} -.hover-white-60:focus, -.hover-white-60:hover { - color: hsla(0, 0%, 100%, .6); -} -.hover-white-50:focus, -.hover-white-50:hover { - color: hsla(0, 0%, 100%, .5); -} -.hover-white-40:focus, -.hover-white-40:hover { - color: hsla(0, 0%, 100%, .4); -} -.hover-white-30:focus, -.hover-white-30:hover { - color: hsla(0, 0%, 100%, .3); -} -.hover-white-20:focus, -.hover-white-20:hover { - color: hsla(0, 0%, 100%, .2); -} -.hover-white-10:focus, -.hover-white-10:hover { - color: hsla(0, 0%, 100%, .1); -} -.hover-inherit:focus, -.hover-inherit:hover { - color: inherit; -} -.hover-bg-black:focus, -.hover-bg-black:hover { - background-color: #000; -} -.hover-bg-near-black:focus, -.hover-bg-near-black:hover { - background-color: #111; -} -.hover-bg-dark-gray:focus, -.hover-bg-dark-gray:hover { - background-color: #333; -} -.hover-bg-mid-gray:focus, -.hover-bg-mid-gray:hover { - background-color: #555; -} -.hover-bg-gray:focus, -.hover-bg-gray:hover { - background-color: #777; -} -.hover-bg-silver:focus, -.hover-bg-silver:hover { - background-color: #999; -} -.hover-bg-light-silver:focus, -.hover-bg-light-silver:hover { - background-color: #aaa; -} -.hover-bg-moon-gray:focus, -.hover-bg-moon-gray:hover { - background-color: #ccc; -} -.hover-bg-light-gray:focus, -.hover-bg-light-gray:hover { - background-color: #eee; -} -.hover-bg-near-white:focus, -.hover-bg-near-white:hover { - background-color: #f4f4f4; -} -.hover-bg-white:focus, -.hover-bg-white:hover { - background-color: #fff; -} -.hover-bg-transparent:focus, -.hover-bg-transparent:hover { - background-color: transparent; -} -.hover-bg-black-90:focus, -.hover-bg-black-90:hover { - background-color: rgba(0, 0, 0, .9); -} -.hover-bg-black-80:focus, -.hover-bg-black-80:hover { - background-color: rgba(0, 0, 0, .8); -} -.hover-bg-black-70:focus, -.hover-bg-black-70:hover { - background-color: rgba(0, 0, 0, .7); -} -.hover-bg-black-60:focus, -.hover-bg-black-60:hover { - background-color: rgba(0, 0, 0, .6); -} -.hover-bg-black-50:focus, -.hover-bg-black-50:hover { - background-color: rgba(0, 0, 0, .5); -} -.hover-bg-black-40:focus, -.hover-bg-black-40:hover { - background-color: rgba(0, 0, 0, .4); -} -.hover-bg-black-30:focus, -.hover-bg-black-30:hover { - background-color: rgba(0, 0, 0, .3); -} -.hover-bg-black-20:focus, -.hover-bg-black-20:hover { - background-color: rgba(0, 0, 0, .2); -} -.hover-bg-black-10:focus, -.hover-bg-black-10:hover { - background-color: rgba(0, 0, 0, .1); -} -.hover-bg-white-90:focus, -.hover-bg-white-90:hover { - background-color: hsla(0, 0%, 100%, .9); -} -.hover-bg-white-80:focus, -.hover-bg-white-80:hover { - background-color: hsla(0, 0%, 100%, .8); -} -.hover-bg-white-70:focus, -.hover-bg-white-70:hover { - background-color: hsla(0, 0%, 100%, .7); -} -.hover-bg-white-60:focus, -.hover-bg-white-60:hover { - background-color: hsla(0, 0%, 100%, .6); -} -.hover-bg-white-50:focus, -.hover-bg-white-50:hover { - background-color: hsla(0, 0%, 100%, .5); -} -.hover-bg-white-40:focus, -.hover-bg-white-40:hover { - background-color: hsla(0, 0%, 100%, .4); -} -.hover-bg-white-30:focus, -.hover-bg-white-30:hover { - background-color: hsla(0, 0%, 100%, .3); -} -.hover-bg-white-20:focus, -.hover-bg-white-20:hover { - background-color: hsla(0, 0%, 100%, .2); -} -.hover-bg-white-10:focus, -.hover-bg-white-10:hover { - background-color: hsla(0, 0%, 100%, .1); -} -.hover-dark-red:focus, -.hover-dark-red:hover { - color: #e7040f; -} -.hover-red:focus, -.hover-red:hover { - color: #ff4136; -} -.hover-light-red:focus, -.hover-light-red:hover { - color: #ff725c; -} -.hover-orange:focus, -.hover-orange:hover { - color: #ff6300; -} -.hover-gold:focus, -.hover-gold:hover { - color: #ffb700; -} -.hover-yellow:focus, -.hover-yellow:hover { - color: gold; -} -.hover-light-yellow:focus, -.hover-light-yellow:hover { - color: #fbf1a9; -} -.hover-purple:focus, -.hover-purple:hover { - color: #5e2ca5; -} -.hover-light-purple:focus, -.hover-light-purple:hover { - color: #a463f2; -} -.hover-dark-pink:focus, -.hover-dark-pink:hover { - color: #d5008f; -} -.hover-hot-pink:focus, -.hover-hot-pink:hover { - color: #ff41b4; -} -.hover-pink:focus, -.hover-pink:hover { - color: #ff80cc; -} -.hover-light-pink:focus, -.hover-light-pink:hover { - color: #ffa3d7; -} -.hover-dark-green:focus, -.hover-dark-green:hover { - color: #137752; -} -.hover-green:focus, -.hover-green:hover { - color: #19a974; -} -.hover-light-green:focus, -.hover-light-green:hover { - color: #9eebcf; -} -.hover-navy:focus, -.hover-navy:hover { - color: #001b44; -} -.hover-dark-blue:focus, -.hover-dark-blue:hover { - color: #00449e; -} -.hover-blue:focus, -.hover-blue:hover { - color: #357edd; -} -.hover-light-blue:focus, -.hover-light-blue:hover { - color: #96ccff; -} -.hover-lightest-blue:focus, -.hover-lightest-blue:hover { - color: #cdecff; -} -.hover-washed-blue:focus, -.hover-washed-blue:hover { - color: #f6fffe; -} -.hover-washed-green:focus, -.hover-washed-green:hover { - color: #e8fdf5; -} -.hover-washed-yellow:focus, -.hover-washed-yellow:hover { - color: #fffceb; -} -.hover-washed-red:focus, -.hover-washed-red:hover { - color: #ffdfdf; -} -.hover-bg-dark-red:focus, -.hover-bg-dark-red:hover { - background-color: #e7040f; -} -.hover-bg-red:focus, -.hover-bg-red:hover { - background-color: #ff4136; -} -.hover-bg-light-red:focus, -.hover-bg-light-red:hover { - background-color: #ff725c; -} -.hover-bg-orange:focus, -.hover-bg-orange:hover { - background-color: #ff6300; -} -.hover-bg-gold:focus, -.hover-bg-gold:hover { - background-color: #ffb700; -} -.hover-bg-yellow:focus, -.hover-bg-yellow:hover { - background-color: gold; -} -.hover-bg-light-yellow:focus, -.hover-bg-light-yellow:hover { - background-color: #fbf1a9; -} -.hover-bg-purple:focus, -.hover-bg-purple:hover { - background-color: #5e2ca5; -} -.hover-bg-light-purple:focus, -.hover-bg-light-purple:hover { - background-color: #a463f2; -} -.hover-bg-dark-pink:focus, -.hover-bg-dark-pink:hover { - background-color: #d5008f; -} -.hover-bg-hot-pink:focus, -.hover-bg-hot-pink:hover { - background-color: #ff41b4; -} -.hover-bg-pink:focus, -.hover-bg-pink:hover { - background-color: #ff80cc; -} -.hover-bg-light-pink:focus, -.hover-bg-light-pink:hover { - background-color: #ffa3d7; -} -.hover-bg-dark-green:focus, -.hover-bg-dark-green:hover { - background-color: #137752; -} -.hover-bg-green:focus, -.hover-bg-green:hover { - background-color: #19a974; -} -.hover-bg-light-green:focus, -.hover-bg-light-green:hover { - background-color: #9eebcf; -} -.hover-bg-navy:focus, -.hover-bg-navy:hover { - background-color: #001b44; -} -.hover-bg-dark-blue:focus, -.hover-bg-dark-blue:hover { - background-color: #00449e; -} -.hover-bg-blue:focus, -.hover-bg-blue:hover { - background-color: #357edd; -} -.hover-bg-light-blue:focus, -.hover-bg-light-blue:hover { - background-color: #96ccff; -} -.hover-bg-lightest-blue:focus, -.hover-bg-lightest-blue:hover { - background-color: #cdecff; -} -.hover-bg-washed-blue:focus, -.hover-bg-washed-blue:hover { - background-color: #f6fffe; -} -.hover-bg-washed-green:focus, -.hover-bg-washed-green:hover { - background-color: #e8fdf5; -} -.hover-bg-washed-yellow:focus, -.hover-bg-washed-yellow:hover { - background-color: #fffceb; -} -.hover-bg-washed-red:focus, -.hover-bg-washed-red:hover { - background-color: #ffdfdf; -} -.hover-bg-inherit:focus, -.hover-bg-inherit:hover { - background-color: inherit; -} -.pa0 { - padding: 0; -} -.pa1 { - padding: .25rem; -} -.pa2 { - padding: .5rem; -} -.pa3 { - padding: 1rem; -} -.pa4 { - padding: 2rem; -} -.pa5 { - padding: 4rem; -} -.pa6 { - padding: 8rem; -} -.pa7 { - padding: 16rem; -} -.pl0 { - padding-left: 0; -} -.pl1 { - padding-left: .25rem; -} -.pl2 { - padding-left: .5rem; -} -.pl3 { - padding-left: 1rem; -} -.pl4 { - padding-left: 2rem; -} -.pl5 { - padding-left: 4rem; -} -.pl6 { - padding-left: 8rem; -} -.pl7 { - padding-left: 16rem; -} -.pr0 { - padding-right: 0; -} -.pr1 { - padding-right: .25rem; -} -.pr2 { - padding-right: .5rem; -} -.pr3 { - padding-right: 1rem; -} -.pr4 { - padding-right: 2rem; -} -.pr5 { - padding-right: 4rem; -} -.pr6 { - padding-right: 8rem; -} -.pr7 { - padding-right: 16rem; -} -.pb0 { - padding-bottom: 0; -} -.pb1 { - padding-bottom: .25rem; -} -.pb2 { - padding-bottom: .5rem; -} -.pb3 { - padding-bottom: 1rem; -} -.pb4 { - padding-bottom: 2rem; -} -.pb5 { - padding-bottom: 4rem; -} -.pb6 { - padding-bottom: 8rem; -} -.pb7 { - padding-bottom: 16rem; -} -.pt0 { - padding-top: 0; -} -.pt1 { - padding-top: .25rem; -} -.pt2 { - padding-top: .5rem; -} -.pt3 { - padding-top: 1rem; -} -.pt4 { - padding-top: 2rem; -} -.pt5 { - padding-top: 4rem; -} -.pt6 { - padding-top: 8rem; -} -.pt7 { - padding-top: 16rem; -} -.pv0 { - padding-top: 0; - padding-bottom: 0; -} -.pv1 { - padding-top: .25rem; - padding-bottom: .25rem; -} -.pv2 { - padding-top: .5rem; - padding-bottom: .5rem; -} -.pv3 { - padding-top: 1rem; - padding-bottom: 1rem; -} -.pv4 { - padding-top: 2rem; - padding-bottom: 2rem; -} -.pv5 { - padding-top: 4rem; - padding-bottom: 4rem; -} -.pv6 { - padding-top: 8rem; - padding-bottom: 8rem; -} -.pv7 { - padding-top: 16rem; - padding-bottom: 16rem; -} -.ph0 { - padding-left: 0; - padding-right: 0; -} -.ph1 { - padding-left: .25rem; - padding-right: .25rem; -} -.ph2 { - padding-left: .5rem; - padding-right: .5rem; -} -.ph3 { - padding-left: 1rem; - padding-right: 1rem; -} -.ph4 { - padding-left: 2rem; - padding-right: 2rem; -} -.ph5 { - padding-left: 4rem; - padding-right: 4rem; -} -.ph6 { - padding-left: 8rem; - padding-right: 8rem; -} -.ph7 { - padding-left: 16rem; - padding-right: 16rem; -} -.ma0 { - margin: 0; -} -.ma1 { - margin: .25rem; -} -.ma2 { - margin: .5rem; -} -.ma3 { - margin: 1rem; -} -.ma4 { - margin: 2rem; -} -.ma5 { - margin: 4rem; -} -.ma6 { - margin: 8rem; -} -.ma7 { - margin: 16rem; -} -.ml0 { - margin-left: 0; -} -.ml1 { - margin-left: .25rem; -} -.ml2 { - margin-left: .5rem; -} -.ml3 { - margin-left: 1rem; -} -.ml4 { - margin-left: 2rem; -} -.ml5 { - margin-left: 4rem; -} -.ml6 { - margin-left: 8rem; -} -.ml7 { - margin-left: 16rem; -} -.mr0 { - margin-right: 0; -} -.mr1 { - margin-right: .25rem; -} -.mr2 { - margin-right: .5rem; -} -.mr3 { - margin-right: 1rem; -} -.mr4 { - margin-right: 2rem; -} -.mr5 { - margin-right: 4rem; -} -.mr6 { - margin-right: 8rem; -} -.mr7 { - margin-right: 16rem; -} -.mb0 { - margin-bottom: 0; -} -.mb1 { - margin-bottom: .25rem; -} -.mb2 { - margin-bottom: .5rem; -} -.mb3 { - margin-bottom: 1rem; -} -.mb4 { - margin-bottom: 2rem; -} -.mb5 { - margin-bottom: 4rem; -} -.mb6 { - margin-bottom: 8rem; -} -.mb7 { - margin-bottom: 16rem; -} -.mt0 { - margin-top: 0; -} -.mt1 { - margin-top: .25rem; -} -.mt2 { - margin-top: .5rem; -} -.mt3 { - margin-top: 1rem; -} -.mt4 { - margin-top: 2rem; -} -.mt5 { - margin-top: 4rem; -} -.mt6 { - margin-top: 8rem; -} -.mt7 { - margin-top: 16rem; -} -.mv0 { - margin-top: 0; - margin-bottom: 0; -} -.mv1 { - margin-top: .25rem; - margin-bottom: .25rem; -} -.mv2 { - margin-top: .5rem; - margin-bottom: .5rem; -} -.mv3 { - margin-top: 1rem; - margin-bottom: 1rem; -} -.mv4 { - margin-top: 2rem; - margin-bottom: 2rem; -} -.mv5 { - margin-top: 4rem; - margin-bottom: 4rem; -} -.mv6 { - margin-top: 8rem; - margin-bottom: 8rem; -} -.mv7 { - margin-top: 16rem; - margin-bottom: 16rem; -} -.mh0 { - margin-left: 0; - margin-right: 0; -} -.mh1 { - margin-left: .25rem; - margin-right: .25rem; -} -.mh2 { - margin-left: .5rem; - margin-right: .5rem; -} -.mh3 { - margin-left: 1rem; - margin-right: 1rem; -} -.mh4 { - margin-left: 2rem; - margin-right: 2rem; -} -.mh5 { - margin-left: 4rem; - margin-right: 4rem; -} -.mh6 { - margin-left: 8rem; - margin-right: 8rem; -} -.mh7 { - margin-left: 16rem; - margin-right: 16rem; -} -.na1 { - margin: -.25rem; -} -.na2 { - margin: -.5rem; -} -.na3 { - margin: -1rem; -} -.na4 { - margin: -2rem; -} -.na5 { - margin: -4rem; -} -.na6 { - margin: -8rem; -} -.na7 { - margin: -16rem; -} -.nl1 { - margin-left: -.25rem; -} -.nl2 { - margin-left: -.5rem; -} -.nl3 { - margin-left: -1rem; -} -.nl4 { - margin-left: -2rem; -} -.nl5 { - margin-left: -4rem; -} -.nl6 { - margin-left: -8rem; -} -.nl7 { - margin-left: -16rem; -} -.nr1 { - margin-right: -.25rem; -} -.nr2 { - margin-right: -.5rem; -} -.nr3 { - margin-right: -1rem; -} -.nr4 { - margin-right: -2rem; -} -.nr5 { - margin-right: -4rem; -} -.nr6 { - margin-right: -8rem; -} -.nr7 { - margin-right: -16rem; -} -.nb1 { - margin-bottom: -.25rem; -} -.nb2 { - margin-bottom: -.5rem; -} -.nb3 { - margin-bottom: -1rem; -} -.nb4 { - margin-bottom: -2rem; -} -.nb5 { - margin-bottom: -4rem; -} -.nb6 { - margin-bottom: -8rem; -} -.nb7 { - margin-bottom: -16rem; -} -.nt1 { - margin-top: -.25rem; -} -.nt2 { - margin-top: -.5rem; -} -.nt3 { - margin-top: -1rem; -} -.nt4 { - margin-top: -2rem; -} -.nt5 { - margin-top: -4rem; -} -.nt6 { - margin-top: -8rem; -} -.nt7 { - margin-top: -16rem; -} -.collapse { - border-collapse: collapse; - border-spacing: 0; -} -.striped--light-silver:nth-child(odd) { - background-color: #aaa; -} -.striped--moon-gray:nth-child(odd) { - background-color: #ccc; -} -.striped--light-gray:nth-child(odd) { - background-color: #eee; -} -.striped--near-white:nth-child(odd) { - background-color: #f4f4f4; -} -.stripe-light:nth-child(odd) { - background-color: hsla(0, 0%, 100%, .1); -} -.stripe-dark:nth-child(odd) { - background-color: rgba(0, 0, 0, .1); -} -.strike { - text-decoration: line-through; -} -.underline { - text-decoration: underline; -} -.no-underline { - text-decoration: none; -} -.tl { - text-align: left; -} -.tr { - text-align: right; -} -.tc { - text-align: center; -} -.tj { - text-align: justify; -} -.ttc { - text-transform: capitalize; -} -.ttl { - text-transform: lowercase; -} -.ttu { - text-transform: uppercase; -} -.ttn { - text-transform: none; -} -.f-6, -.f-headline { - font-size: 6rem; -} -.f-5, -.f-subheadline { - font-size: 5rem; -} -.f1 { - font-size: 3rem; -} -.f2 { - font-size: 2.25rem; -} -.f3 { - font-size: 1.5rem; -} -.f4 { - font-size: 1.25rem; -} -.f5 { - font-size: 1rem; -} -.f6 { - font-size: .875rem; -} -.f7 { - font-size: .75rem; -} -.measure { - max-width: 30em; -} -.measure-wide { - max-width: 34em; -} -.measure-narrow { - max-width: 20em; -} -.indent { - text-indent: 1em; - margin-top: 0; - margin-bottom: 0; -} -.small-caps { - font-variant: small-caps; -} -.truncate { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.overflow-container { - overflow-y: scroll; -} -.center { - margin-left: auto; -} -.center, -.mr-auto { - margin-right: auto; -} -.ml-auto { - margin-left: auto; -} -.clip { - position: fixed !important; - _position: absolute !important; - clip: rect(1px 1px 1px 1px); - clip: rect(1px, 1px, 1px, 1px); -} -.ws-normal { - white-space: normal; -} -.nowrap { - white-space: nowrap; -} -.pre { - white-space: pre; -} -.v-base { - vertical-align: baseline; -} -.v-mid { - vertical-align: middle; -} -.v-top { - vertical-align: top; -} -.v-btm { - vertical-align: bottom; -} -.dim { - opacity: 1; -} -.dim, -.dim:focus, -.dim:hover { - transition: opacity .15s ease-in; -} -.dim:focus, -.dim:hover { - opacity: .5; -} -.dim:active { - opacity: .8; - transition: opacity .15s ease-out; -} -.glow, -.glow:focus, -.glow:hover { - transition: opacity .15s ease-in; -} -.glow:focus, -.glow:hover { - opacity: 1; -} -.hide-child .child { - opacity: 0; - transition: opacity .15s ease-in; -} -.hide-child:active .child, -.hide-child:focus .child, -.hide-child:hover .child { - opacity: 1; - transition: opacity .15s ease-in; -} -.underline-hover:focus, -.underline-hover:hover { - text-decoration: underline; -} -.grow { - -moz-osx-font-smoothing: grayscale; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-transform: translateZ(0); - transform: translateZ(0); - transition: -webkit-transform .25s ease-out; - transition: transform .25s ease-out; - transition: transform .25s ease-out, -webkit-transform .25s ease-out; -} -.grow:focus, -.grow:hover { - -webkit-transform: scale(1.05); - transform: scale(1.05); -} -.grow:active { - -webkit-transform: scale(.9); - transform: scale(.9); -} -.grow-large { - -moz-osx-font-smoothing: grayscale; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-transform: translateZ(0); - transform: translateZ(0); - transition: -webkit-transform .25s ease-in-out; - transition: transform .25s ease-in-out; - transition: transform .25s ease-in-out, -webkit-transform .25s ease-in-out; -} -.grow-large:focus, -.grow-large:hover { - -webkit-transform: scale(1.2); - transform: scale(1.2); -} -.grow-large:active { - -webkit-transform: scale(.95); - transform: scale(.95); -} -.pointer:hover, -.shadow-hover { - cursor: pointer; -} -.shadow-hover { - position: relative; - transition: all .5s cubic-bezier(.165, .84, .44, 1); -} -.shadow-hover:after { - content: ""; - box-shadow: 0 0 16px 2px rgba(0, 0, 0, .2); - border-radius: inherit; - opacity: 0; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: -1; - transition: opacity .5s cubic-bezier(.165, .84, .44, 1); -} -.shadow-hover:focus:after, -.shadow-hover:hover:after { - opacity: 1; -} -.bg-animate, -.bg-animate:focus, -.bg-animate:hover { - transition: background-color .15s ease-in-out; -} -.z-0 { - z-index: 0; -} -.z-1 { - z-index: 1; -} -.z-2 { - z-index: 2; -} -.z-3 { - z-index: 3; -} -.z-4 { - z-index: 4; -} -.z-5 { - z-index: 5; -} -.z-999 { - z-index: 999; -} -.z-9999 { - z-index: 9999; -} -.z-max { - z-index: 2147483647; -} -.z-inherit { - z-index: inherit; -} -.z-initial { - z-index: auto; -} -.z-unset { - z-index: unset; -} -.nested-copy-line-height ol, -.nested-copy-line-height p, -.nested-copy-line-height ul { - line-height: 1.5; -} -.nested-headline-line-height h1, -.nested-headline-line-height h2, -.nested-headline-line-height h3, -.nested-headline-line-height h4, -.nested-headline-line-height h5, -.nested-headline-line-height h6 { - line-height: 1.25; -} -.nested-list-reset ol, -.nested-list-reset ul { - padding-left: 0; - margin-left: 0; - list-style-type: none; -} -.nested-copy-indent p + p { - text-indent: 1em; - margin-top: 0; - margin-bottom: 0; -} -.nested-copy-separator p + p { - margin-top: 1.5em; -} -.nested-img img { - width: 100%; - max-width: 100%; - display: block; -} -.nested-links a { - color: #357edd; - transition: color .15s ease-in; -} -.nested-links a:focus, -.nested-links a:hover { - color: #96ccff; - transition: color .15s ease-in; -} -.debug * { - outline: 1px solid gold; -} -.debug-white * { - outline: 1px solid #fff; -} -.debug-black * { - outline: 1px solid #000; -} -.debug-grid { - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFElEQVR4AWPAC97/9x0eCsAEPgwAVLshdpENIxcAAAAASUVORK5CYII=) repeat 0 0; -} -.debug-grid-16 { - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR4AWOgCLz/b0epAa6UGuBOqQHOQHLUgFEDnAbcBZ4UGwDOkiCnkIhdgNgNxAYAiYlD+8sEuo8AAAAASUVORK5CYII=) repeat 0 0; -} -.debug-grid-8-solid { - background: #fff url(data:image/gif;base64,R0lGODdhCAAIAPEAAADw/wDx/////wAAACwAAAAACAAIAAACDZQvgaeb/lxbAIKA8y0AOw==) repeat 0 0; -} -.debug-grid-16-solid { - background: #fff url(data:image/gif;base64,R0lGODdhEAAQAPEAAADw/wDx/xXy/////ywAAAAAEAAQAAACIZyPKckYDQFsb6ZqD85jZ2+BkwiRFKehhqQCQgDHcgwEBQA7) repeat 0 0; -} -@media screen and (min-width:35em) { - .aspect-ratio-ns { - height: 0; - position: relative; - } - .aspect-ratio--16x9-ns { - padding-bottom: 56.25%; - } - .aspect-ratio--9x16-ns { - padding-bottom: 177.77%; - } - .aspect-ratio--4x3-ns { - padding-bottom: 75%; - } - .aspect-ratio--3x4-ns { - padding-bottom: 133.33%; - } - .aspect-ratio--6x4-ns { - padding-bottom: 66.6%; - } - .aspect-ratio--4x6-ns { - padding-bottom: 150%; - } - .aspect-ratio--8x5-ns { - padding-bottom: 62.5%; - } - .aspect-ratio--5x8-ns { - padding-bottom: 160%; - } - .aspect-ratio--7x5-ns { - padding-bottom: 71.42%; - } - .aspect-ratio--5x7-ns { - padding-bottom: 140%; - } - .aspect-ratio--1x1-ns { - padding-bottom: 100%; - } - .aspect-ratio--object-ns { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 100; - } - .cover-ns { - background-size: cover !important; - } - .contain-ns { - background-size: contain !important; - } - .bg-center-ns { - background-position: 50%; - } - .bg-center-ns, - .bg-top-ns { - background-repeat: no-repeat; - } - .bg-top-ns { - background-position: top; - } - .bg-right-ns { - background-position: 100%; - } - .bg-bottom-ns, - .bg-right-ns { - background-repeat: no-repeat; - } - .bg-bottom-ns { - background-position: bottom; - } - .bg-left-ns { - background-repeat: no-repeat; - background-position: 0; - } - .outline-ns { - outline: 1px solid; - } - .outline-transparent-ns { - outline: 1px solid transparent; - } - .outline-0-ns { - outline: 0; - } - .ba-ns { - border-style: solid; - border-width: 1px; - } - .bt-ns { - border-top-style: solid; - border-top-width: 1px; - } - .br-ns { - border-right-style: solid; - border-right-width: 1px; - } - .bb-ns { - border-bottom-style: solid; - border-bottom-width: 1px; - } - .bl-ns { - border-left-style: solid; - border-left-width: 1px; - } - .bn-ns { - border-style: none; - border-width: 0; - } - .br0-ns { - border-radius: 0; - } - .br1-ns { - border-radius: .125rem; - } - .br2-ns { - border-radius: .25rem; - } - .br3-ns { - border-radius: .5rem; - } - .br4-ns { - border-radius: 1rem; - } - .br-100-ns { - border-radius: 100%; - } - .br-pill-ns { - border-radius: 9999px; - } - .br--bottom-ns { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - .br--top-ns { - border-bottom-right-radius: 0; - } - .br--right-ns, - .br--top-ns { - border-bottom-left-radius: 0; - } - .br--right-ns { - border-top-left-radius: 0; - } - .br--left-ns { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - .br-inherit-ns { - border-radius: inherit; - } - .br-initial-ns { - border-radius: initial; - } - .br-unset-ns { - border-radius: unset; - } - .b--dotted-ns { - border-style: dotted; - } - .b--dashed-ns { - border-style: dashed; - } - .b--solid-ns { - border-style: solid; - } - .b--none-ns { - border-style: none; - } - .bw0-ns { - border-width: 0; - } - .bw1-ns { - border-width: .125rem; - } - .bw2-ns { - border-width: .25rem; - } - .bw3-ns { - border-width: .5rem; - } - .bw4-ns { - border-width: 1rem; - } - .bw5-ns { - border-width: 2rem; - } - .bt-0-ns { - border-top-width: 0; - } - .br-0-ns { - border-right-width: 0; - } - .bb-0-ns { - border-bottom-width: 0; - } - .bl-0-ns { - border-left-width: 0; - } - .shadow-1-ns { - box-shadow: 0 0 4px 2px rgba(0, 0, 0, .2); - } - .shadow-2-ns { - box-shadow: 0 0 8px 2px rgba(0, 0, 0, .2); - } - .shadow-3-ns { - box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, .2); - } - .shadow-4-ns { - box-shadow: 2px 2px 8px 0 rgba(0, 0, 0, .2); - } - .shadow-5-ns { - box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, .2); - } - .top-0-ns { - top: 0; - } - .left-0-ns { - left: 0; - } - .right-0-ns { - right: 0; - } - .bottom-0-ns { - bottom: 0; - } - .top-1-ns { - top: 1rem; - } - .left-1-ns { - left: 1rem; - } - .right-1-ns { - right: 1rem; - } - .bottom-1-ns { - bottom: 1rem; - } - .top-2-ns { - top: 2rem; - } - .left-2-ns { - left: 2rem; - } - .right-2-ns { - right: 2rem; - } - .bottom-2-ns { - bottom: 2rem; - } - .top--1-ns { - top: -1rem; - } - .right--1-ns { - right: -1rem; - } - .bottom--1-ns { - bottom: -1rem; - } - .left--1-ns { - left: -1rem; - } - .top--2-ns { - top: -2rem; - } - .right--2-ns { - right: -2rem; - } - .bottom--2-ns { - bottom: -2rem; - } - .left--2-ns { - left: -2rem; - } - .absolute--fill-ns { - top: 0; - right: 0; - bottom: 0; - left: 0; - } - .cl-ns { - clear: left; - } - .cr-ns { - clear: right; - } - .cb-ns { - clear: both; - } - .cn-ns { - clear: none; - } - .dn-ns { - display: none; - } - .di-ns { - display: inline; - } - .db-ns { - display: block; - } - .dib-ns { - display: inline-block; - } - .dit-ns { - display: inline-table; - } - .dt-ns { - display: table; - } - .dtc-ns { - display: table-cell; - } - .dt-row-ns { - display: table-row; - } - .dt-row-group-ns { - display: table-row-group; - } - .dt-column-ns { - display: table-column; - } - .dt-column-group-ns { - display: table-column-group; - } - .dt--fixed-ns { - table-layout: fixed; - width: 100%; - } - .flex-ns { - display: flex; - } - .inline-flex-ns { - display: inline-flex; - } - .flex-auto-ns { - flex: 1 1 auto; - min-width: 0; - min-height: 0; - } - .flex-none-ns { - flex: none; - } - .flex-column-ns { - flex-direction: column; - } - .flex-row-ns { - flex-direction: row; - } - .flex-wrap-ns { - flex-wrap: wrap; - } - .flex-nowrap-ns { - flex-wrap: nowrap; - } - .flex-wrap-reverse-ns { - flex-wrap: wrap-reverse; - } - .flex-column-reverse-ns { - flex-direction: column-reverse; - } - .flex-row-reverse-ns { - flex-direction: row-reverse; - } - .items-start-ns { - align-items: flex-start; - } - .items-end-ns { - align-items: flex-end; - } - .items-center-ns { - align-items: center; - } - .items-baseline-ns { - align-items: baseline; - } - .items-stretch-ns { - align-items: stretch; - } - .self-start-ns { - align-self: flex-start; - } - .self-end-ns { - align-self: flex-end; - } - .self-center-ns { - align-self: center; - } - .self-baseline-ns { - align-self: baseline; - } - .self-stretch-ns { - align-self: stretch; - } - .justify-start-ns { - justify-content: flex-start; - } - .justify-end-ns { - justify-content: flex-end; - } - .justify-center-ns { - justify-content: center; - } - .justify-between-ns { - justify-content: space-between; - } - .justify-around-ns { - justify-content: space-around; - } - .content-start-ns { - align-content: flex-start; - } - .content-end-ns { - align-content: flex-end; - } - .content-center-ns { - align-content: center; - } - .content-between-ns { - align-content: space-between; - } - .content-around-ns { - align-content: space-around; - } - .content-stretch-ns { - align-content: stretch; - } - .order-0-ns { - order: 0; - } - .order-1-ns { - order: 1; - } - .order-2-ns { - order: 2; - } - .order-3-ns { - order: 3; - } - .order-4-ns { - order: 4; - } - .order-5-ns { - order: 5; - } - .order-6-ns { - order: 6; - } - .order-7-ns { - order: 7; - } - .order-8-ns { - order: 8; - } - .order-last-ns { - order: 99999; - } - .flex-grow-0-ns { - flex-grow: 0; - } - .flex-grow-1-ns { - flex-grow: 1; - } - .flex-shrink-0-ns { - flex-shrink: 0; - } - .flex-shrink-1-ns { - flex-shrink: 1; - } - .fl-ns { - float: left; - } - .fl-ns, - .fr-ns { - _display: inline; - } - .fr-ns { - float: right; - } - .fn-ns { - float: none; - } - .i-ns { - font-style: italic; - } - .fs-normal-ns { - font-style: normal; - } - .normal-ns { - font-weight: 400; - } - .b-ns { - font-weight: 700; - } - .fw1-ns { - font-weight: 100; - } - .fw2-ns { - font-weight: 200; - } - .fw3-ns { - font-weight: 300; - } - .fw4-ns { - font-weight: 400; - } - .fw5-ns { - font-weight: 500; - } - .fw6-ns { - font-weight: 600; - } - .fw7-ns { - font-weight: 700; - } - .fw8-ns { - font-weight: 800; - } - .fw9-ns { - font-weight: 900; - } - .h1-ns { - height: 1rem; - } - .h2-ns { - height: 2rem; - } - .h3-ns { - height: 4rem; - } - .h4-ns { - height: 8rem; - } - .h5-ns { - height: 16rem; - } - .h-25-ns { - height: 25%; - } - .h-50-ns { - height: 50%; - } - .h-75-ns { - height: 75%; - } - .h-100-ns { - height: 100%; - } - .min-h-100-ns { - min-height: 100%; - } - .vh-25-ns { - height: 25vh; - } - .vh-50-ns { - height: 50vh; - } - .vh-75-ns { - height: 75vh; - } - .vh-100-ns { - height: 100vh; - } - .min-vh-100-ns { - min-height: 100vh; - } - .h-auto-ns { - height: auto; - } - .h-inherit-ns { - height: inherit; - } - .tracked-ns { - letter-spacing: .1em; - } - .tracked-tight-ns { - letter-spacing: -.05em; - } - .tracked-mega-ns { - letter-spacing: .25em; - } - .lh-solid-ns { - line-height: 1; - } - .lh-title-ns { - line-height: 1.25; - } - .lh-copy-ns { - line-height: 1.5; - } - .mw-100-ns { - max-width: 100%; - } - .mw1-ns { - max-width: 1rem; - } - .mw2-ns { - max-width: 2rem; - } - .mw3-ns { - max-width: 4rem; - } - .mw4-ns { - max-width: 8rem; - } - .mw5-ns { - max-width: 16rem; - } - .mw6-ns { - max-width: 32rem; - } - .mw7-ns { - max-width: 48rem; - } - .mw8-ns { - max-width: 64rem; - } - .mw9-ns { - max-width: 96rem; - } - .mw-none-ns { - max-width: none; - } - .w1-ns { - width: 1rem; - } - .w2-ns { - width: 2rem; - } - .w3-ns { - width: 4rem; - } - .w4-ns { - width: 8rem; - } - .w5-ns { - width: 16rem; - } - .w-10-ns { - width: 10%; - } - .w-20-ns { - width: 20%; - } - .w-25-ns { - width: 25%; - } - .w-30-ns { - width: 30%; - } - .w-33-ns { - width: 33%; - } - .w-34-ns { - width: 34%; - } - .w-40-ns { - width: 40%; - } - .w-50-ns { - width: 50%; - } - .w-60-ns { - width: 60%; - } - .w-70-ns { - width: 70%; - } - .w-75-ns { - width: 75%; - } - .w-80-ns { - width: 80%; - } - .w-90-ns { - width: 90%; - } - .w-100-ns { - width: 100%; - } - .w-third-ns { - width: 33.33333%; - } - .w-two-thirds-ns { - width: 66.66667%; - } - .w-auto-ns { - width: auto; - } - .overflow-visible-ns { - overflow: visible; - } - .overflow-hidden-ns { - overflow: hidden; - } - .overflow-scroll-ns { - overflow: scroll; - } - .overflow-auto-ns { - overflow: auto; - } - .overflow-x-visible-ns { - overflow-x: visible; - } - .overflow-x-hidden-ns { - overflow-x: hidden; - } - .overflow-x-scroll-ns { - overflow-x: scroll; - } - .overflow-x-auto-ns { - overflow-x: auto; - } - .overflow-y-visible-ns { - overflow-y: visible; - } - .overflow-y-hidden-ns { - overflow-y: hidden; - } - .overflow-y-scroll-ns { - overflow-y: scroll; - } - .overflow-y-auto-ns { - overflow-y: auto; - } - .static-ns { - position: static; - } - .relative-ns { - position: relative; - } - .absolute-ns { - position: absolute; - } - .fixed-ns { - position: fixed; - } - .rotate-45-ns { - -webkit-transform: rotate(45deg); - transform: rotate(45deg); - } - .rotate-90-ns { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - } - .rotate-135-ns { - -webkit-transform: rotate(135deg); - transform: rotate(135deg); - } - .rotate-180-ns { - -webkit-transform: rotate(180deg); - transform: rotate(180deg); - } - .rotate-225-ns { - -webkit-transform: rotate(225deg); - transform: rotate(225deg); - } - .rotate-270-ns { - -webkit-transform: rotate(270deg); - transform: rotate(270deg); - } - .rotate-315-ns { - -webkit-transform: rotate(315deg); - transform: rotate(315deg); - } - .pa0-ns { - padding: 0; - } - .pa1-ns { - padding: .25rem; - } - .pa2-ns { - padding: .5rem; - } - .pa3-ns { - padding: 1rem; - } - .pa4-ns { - padding: 2rem; - } - .pa5-ns { - padding: 4rem; - } - .pa6-ns { - padding: 8rem; - } - .pa7-ns { - padding: 16rem; - } - .pl0-ns { - padding-left: 0; - } - .pl1-ns { - padding-left: .25rem; - } - .pl2-ns { - padding-left: .5rem; - } - .pl3-ns { - padding-left: 1rem; - } - .pl4-ns { - padding-left: 2rem; - } - .pl5-ns { - padding-left: 4rem; - } - .pl6-ns { - padding-left: 8rem; - } - .pl7-ns { - padding-left: 16rem; - } - .pr0-ns { - padding-right: 0; - } - .pr1-ns { - padding-right: .25rem; - } - .pr2-ns { - padding-right: .5rem; - } - .pr3-ns { - padding-right: 1rem; - } - .pr4-ns { - padding-right: 2rem; - } - .pr5-ns { - padding-right: 4rem; - } - .pr6-ns { - padding-right: 8rem; - } - .pr7-ns { - padding-right: 16rem; - } - .pb0-ns { - padding-bottom: 0; - } - .pb1-ns { - padding-bottom: .25rem; - } - .pb2-ns { - padding-bottom: .5rem; - } - .pb3-ns { - padding-bottom: 1rem; - } - .pb4-ns { - padding-bottom: 2rem; - } - .pb5-ns { - padding-bottom: 4rem; - } - .pb6-ns { - padding-bottom: 8rem; - } - .pb7-ns { - padding-bottom: 16rem; - } - .pt0-ns { - padding-top: 0; - } - .pt1-ns { - padding-top: .25rem; - } - .pt2-ns { - padding-top: .5rem; - } - .pt3-ns { - padding-top: 1rem; - } - .pt4-ns { - padding-top: 2rem; - } - .pt5-ns { - padding-top: 4rem; - } - .pt6-ns { - padding-top: 8rem; - } - .pt7-ns { - padding-top: 16rem; - } - .pv0-ns { - padding-top: 0; - padding-bottom: 0; - } - .pv1-ns { - padding-top: .25rem; - padding-bottom: .25rem; - } - .pv2-ns { - padding-top: .5rem; - padding-bottom: .5rem; - } - .pv3-ns { - padding-top: 1rem; - padding-bottom: 1rem; - } - .pv4-ns { - padding-top: 2rem; - padding-bottom: 2rem; - } - .pv5-ns { - padding-top: 4rem; - padding-bottom: 4rem; - } - .pv6-ns { - padding-top: 8rem; - padding-bottom: 8rem; - } - .pv7-ns { - padding-top: 16rem; - padding-bottom: 16rem; - } - .ph0-ns { - padding-left: 0; - padding-right: 0; - } - .ph1-ns { - padding-left: .25rem; - padding-right: .25rem; - } - .ph2-ns { - padding-left: .5rem; - padding-right: .5rem; - } - .ph3-ns { - padding-left: 1rem; - padding-right: 1rem; - } - .ph4-ns { - padding-left: 2rem; - padding-right: 2rem; - } - .ph5-ns { - padding-left: 4rem; - padding-right: 4rem; - } - .ph6-ns { - padding-left: 8rem; - padding-right: 8rem; - } - .ph7-ns { - padding-left: 16rem; - padding-right: 16rem; - } - .ma0-ns { - margin: 0; - } - .ma1-ns { - margin: .25rem; - } - .ma2-ns { - margin: .5rem; - } - .ma3-ns { - margin: 1rem; - } - .ma4-ns { - margin: 2rem; - } - .ma5-ns { - margin: 4rem; - } - .ma6-ns { - margin: 8rem; - } - .ma7-ns { - margin: 16rem; - } - .ml0-ns { - margin-left: 0; - } - .ml1-ns { - margin-left: .25rem; - } - .ml2-ns { - margin-left: .5rem; - } - .ml3-ns { - margin-left: 1rem; - } - .ml4-ns { - margin-left: 2rem; - } - .ml5-ns { - margin-left: 4rem; - } - .ml6-ns { - margin-left: 8rem; - } - .ml7-ns { - margin-left: 16rem; - } - .mr0-ns { - margin-right: 0; - } - .mr1-ns { - margin-right: .25rem; - } - .mr2-ns { - margin-right: .5rem; - } - .mr3-ns { - margin-right: 1rem; - } - .mr4-ns { - margin-right: 2rem; - } - .mr5-ns { - margin-right: 4rem; - } - .mr6-ns { - margin-right: 8rem; - } - .mr7-ns { - margin-right: 16rem; - } - .mb0-ns { - margin-bottom: 0; - } - .mb1-ns { - margin-bottom: .25rem; - } - .mb2-ns { - margin-bottom: .5rem; - } - .mb3-ns { - margin-bottom: 1rem; - } - .mb4-ns { - margin-bottom: 2rem; - } - .mb5-ns { - margin-bottom: 4rem; - } - .mb6-ns { - margin-bottom: 8rem; - } - .mb7-ns { - margin-bottom: 16rem; - } - .mt0-ns { - margin-top: 0; - } - .mt1-ns { - margin-top: .25rem; - } - .mt2-ns { - margin-top: .5rem; - } - .mt3-ns { - margin-top: 1rem; - } - .mt4-ns { - margin-top: 2rem; - } - .mt5-ns { - margin-top: 4rem; - } - .mt6-ns { - margin-top: 8rem; - } - .mt7-ns { - margin-top: 16rem; - } - .mv0-ns { - margin-top: 0; - margin-bottom: 0; - } - .mv1-ns { - margin-top: .25rem; - margin-bottom: .25rem; - } - .mv2-ns { - margin-top: .5rem; - margin-bottom: .5rem; - } - .mv3-ns { - margin-top: 1rem; - margin-bottom: 1rem; - } - .mv4-ns { - margin-top: 2rem; - margin-bottom: 2rem; - } - .mv5-ns { - margin-top: 4rem; - margin-bottom: 4rem; - } - .mv6-ns { - margin-top: 8rem; - margin-bottom: 8rem; - } - .mv7-ns { - margin-top: 16rem; - margin-bottom: 16rem; - } - .mh0-ns { - margin-left: 0; - margin-right: 0; - } - .mh1-ns { - margin-left: .25rem; - margin-right: .25rem; - } - .mh2-ns { - margin-left: .5rem; - margin-right: .5rem; - } - .mh3-ns { - margin-left: 1rem; - margin-right: 1rem; - } - .mh4-ns { - margin-left: 2rem; - margin-right: 2rem; - } - .mh5-ns { - margin-left: 4rem; - margin-right: 4rem; - } - .mh6-ns { - margin-left: 8rem; - margin-right: 8rem; - } - .mh7-ns { - margin-left: 16rem; - margin-right: 16rem; - } - .na1-ns { - margin: -.25rem; - } - .na2-ns { - margin: -.5rem; - } - .na3-ns { - margin: -1rem; - } - .na4-ns { - margin: -2rem; - } - .na5-ns { - margin: -4rem; - } - .na6-ns { - margin: -8rem; - } - .na7-ns { - margin: -16rem; - } - .nl1-ns { - margin-left: -.25rem; - } - .nl2-ns { - margin-left: -.5rem; - } - .nl3-ns { - margin-left: -1rem; - } - .nl4-ns { - margin-left: -2rem; - } - .nl5-ns { - margin-left: -4rem; - } - .nl6-ns { - margin-left: -8rem; - } - .nl7-ns { - margin-left: -16rem; - } - .nr1-ns { - margin-right: -.25rem; - } - .nr2-ns { - margin-right: -.5rem; - } - .nr3-ns { - margin-right: -1rem; - } - .nr4-ns { - margin-right: -2rem; - } - .nr5-ns { - margin-right: -4rem; - } - .nr6-ns { - margin-right: -8rem; - } - .nr7-ns { - margin-right: -16rem; - } - .nb1-ns { - margin-bottom: -.25rem; - } - .nb2-ns { - margin-bottom: -.5rem; - } - .nb3-ns { - margin-bottom: -1rem; - } - .nb4-ns { - margin-bottom: -2rem; - } - .nb5-ns { - margin-bottom: -4rem; - } - .nb6-ns { - margin-bottom: -8rem; - } - .nb7-ns { - margin-bottom: -16rem; - } - .nt1-ns { - margin-top: -.25rem; - } - .nt2-ns { - margin-top: -.5rem; - } - .nt3-ns { - margin-top: -1rem; - } - .nt4-ns { - margin-top: -2rem; - } - .nt5-ns { - margin-top: -4rem; - } - .nt6-ns { - margin-top: -8rem; - } - .nt7-ns { - margin-top: -16rem; - } - .strike-ns { - text-decoration: line-through; - } - .underline-ns { - text-decoration: underline; - } - .no-underline-ns { - text-decoration: none; - } - .tl-ns { - text-align: left; - } - .tr-ns { - text-align: right; - } - .tc-ns { - text-align: center; - } - .tj-ns { - text-align: justify; - } - .ttc-ns { - text-transform: capitalize; - } - .ttl-ns { - text-transform: lowercase; - } - .ttu-ns { - text-transform: uppercase; - } - .ttn-ns { - text-transform: none; - } - .f-6-ns, - .f-headline-ns { - font-size: 6rem; - } - .f-5-ns, - .f-subheadline-ns { - font-size: 5rem; - } - .f1-ns { - font-size: 3rem; - } - .f2-ns { - font-size: 2.25rem; - } - .f3-ns { - font-size: 1.5rem; - } - .f4-ns { - font-size: 1.25rem; - } - .f5-ns { - font-size: 1rem; - } - .f6-ns { - font-size: .875rem; - } - .f7-ns { - font-size: .75rem; - } - .measure-ns { - max-width: 30em; - } - .measure-wide-ns { - max-width: 34em; - } - .measure-narrow-ns { - max-width: 20em; - } - .indent-ns { - text-indent: 1em; - margin-top: 0; - margin-bottom: 0; - } - .small-caps-ns { - font-variant: small-caps; - } - .truncate-ns { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .center-ns { - margin-left: auto; - } - .center-ns, - .mr-auto-ns { - margin-right: auto; - } - .ml-auto-ns { - margin-left: auto; - } - .clip-ns { - position: fixed !important; - _position: absolute !important; - clip: rect(1px 1px 1px 1px); - clip: rect(1px, 1px, 1px, 1px); - } - .ws-normal-ns { - white-space: normal; - } - .nowrap-ns { - white-space: nowrap; - } - .pre-ns { - white-space: pre; - } - .v-base-ns { - vertical-align: baseline; - } - .v-mid-ns { - vertical-align: middle; - } - .v-top-ns { - vertical-align: top; - } - .v-btm-ns { - vertical-align: bottom; - } -} -@media screen and (min-width:35em) and (max-width:60em) { - .aspect-ratio-m { - height: 0; - position: relative; - } - .aspect-ratio--16x9-m { - padding-bottom: 56.25%; - } - .aspect-ratio--9x16-m { - padding-bottom: 177.77%; - } - .aspect-ratio--4x3-m { - padding-bottom: 75%; - } - .aspect-ratio--3x4-m { - padding-bottom: 133.33%; - } - .aspect-ratio--6x4-m { - padding-bottom: 66.6%; - } - .aspect-ratio--4x6-m { - padding-bottom: 150%; - } - .aspect-ratio--8x5-m { - padding-bottom: 62.5%; - } - .aspect-ratio--5x8-m { - padding-bottom: 160%; - } - .aspect-ratio--7x5-m { - padding-bottom: 71.42%; - } - .aspect-ratio--5x7-m { - padding-bottom: 140%; - } - .aspect-ratio--1x1-m { - padding-bottom: 100%; - } - .aspect-ratio--object-m { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 100; - } - .cover-m { - background-size: cover !important; - } - .contain-m { - background-size: contain !important; - } - .bg-center-m { - background-position: 50%; - } - .bg-center-m, - .bg-top-m { - background-repeat: no-repeat; - } - .bg-top-m { - background-position: top; - } - .bg-right-m { - background-position: 100%; - } - .bg-bottom-m, - .bg-right-m { - background-repeat: no-repeat; - } - .bg-bottom-m { - background-position: bottom; - } - .bg-left-m { - background-repeat: no-repeat; - background-position: 0; - } - .outline-m { - outline: 1px solid; - } - .outline-transparent-m { - outline: 1px solid transparent; - } - .outline-0-m { - outline: 0; - } - .ba-m { - border-style: solid; - border-width: 1px; - } - .bt-m { - border-top-style: solid; - border-top-width: 1px; - } - .br-m { - border-right-style: solid; - border-right-width: 1px; - } - .bb-m { - border-bottom-style: solid; - border-bottom-width: 1px; - } - .bl-m { - border-left-style: solid; - border-left-width: 1px; - } - .bn-m { - border-style: none; - border-width: 0; - } - .br0-m { - border-radius: 0; - } - .br1-m { - border-radius: .125rem; - } - .br2-m { - border-radius: .25rem; - } - .br3-m { - border-radius: .5rem; - } - .br4-m { - border-radius: 1rem; - } - .br-100-m { - border-radius: 100%; - } - .br-pill-m { - border-radius: 9999px; - } - .br--bottom-m { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - .br--top-m { - border-bottom-right-radius: 0; - } - .br--right-m, - .br--top-m { - border-bottom-left-radius: 0; - } - .br--right-m { - border-top-left-radius: 0; - } - .br--left-m { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - .br-inherit-m { - border-radius: inherit; - } - .br-initial-m { - border-radius: initial; - } - .br-unset-m { - border-radius: unset; - } - .b--dotted-m { - border-style: dotted; - } - .b--dashed-m { - border-style: dashed; - } - .b--solid-m { - border-style: solid; - } - .b--none-m { - border-style: none; - } - .bw0-m { - border-width: 0; - } - .bw1-m { - border-width: .125rem; - } - .bw2-m { - border-width: .25rem; - } - .bw3-m { - border-width: .5rem; - } - .bw4-m { - border-width: 1rem; - } - .bw5-m { - border-width: 2rem; - } - .bt-0-m { - border-top-width: 0; - } - .br-0-m { - border-right-width: 0; - } - .bb-0-m { - border-bottom-width: 0; - } - .bl-0-m { - border-left-width: 0; - } - .shadow-1-m { - box-shadow: 0 0 4px 2px rgba(0, 0, 0, .2); - } - .shadow-2-m { - box-shadow: 0 0 8px 2px rgba(0, 0, 0, .2); - } - .shadow-3-m { - box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, .2); - } - .shadow-4-m { - box-shadow: 2px 2px 8px 0 rgba(0, 0, 0, .2); - } - .shadow-5-m { - box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, .2); - } - .top-0-m { - top: 0; - } - .left-0-m { - left: 0; - } - .right-0-m { - right: 0; - } - .bottom-0-m { - bottom: 0; - } - .top-1-m { - top: 1rem; - } - .left-1-m { - left: 1rem; - } - .right-1-m { - right: 1rem; - } - .bottom-1-m { - bottom: 1rem; - } - .top-2-m { - top: 2rem; - } - .left-2-m { - left: 2rem; - } - .right-2-m { - right: 2rem; - } - .bottom-2-m { - bottom: 2rem; - } - .top--1-m { - top: -1rem; - } - .right--1-m { - right: -1rem; - } - .bottom--1-m { - bottom: -1rem; - } - .left--1-m { - left: -1rem; - } - .top--2-m { - top: -2rem; - } - .right--2-m { - right: -2rem; - } - .bottom--2-m { - bottom: -2rem; - } - .left--2-m { - left: -2rem; - } - .absolute--fill-m { - top: 0; - right: 0; - bottom: 0; - left: 0; - } - .cl-m { - clear: left; - } - .cr-m { - clear: right; - } - .cb-m { - clear: both; - } - .cn-m { - clear: none; - } - .dn-m { - display: none; - } - .di-m { - display: inline; - } - .db-m { - display: block; - } - .dib-m { - display: inline-block; - } - .dit-m { - display: inline-table; - } - .dt-m { - display: table; - } - .dtc-m { - display: table-cell; - } - .dt-row-m { - display: table-row; - } - .dt-row-group-m { - display: table-row-group; - } - .dt-column-m { - display: table-column; - } - .dt-column-group-m { - display: table-column-group; - } - .dt--fixed-m { - table-layout: fixed; - width: 100%; - } - .flex-m { - display: flex; - } - .inline-flex-m { - display: inline-flex; - } - .flex-auto-m { - flex: 1 1 auto; - min-width: 0; - min-height: 0; - } - .flex-none-m { - flex: none; - } - .flex-column-m { - flex-direction: column; - } - .flex-row-m { - flex-direction: row; - } - .flex-wrap-m { - flex-wrap: wrap; - } - .flex-nowrap-m { - flex-wrap: nowrap; - } - .flex-wrap-reverse-m { - flex-wrap: wrap-reverse; - } - .flex-column-reverse-m { - flex-direction: column-reverse; - } - .flex-row-reverse-m { - flex-direction: row-reverse; - } - .items-start-m { - align-items: flex-start; - } - .items-end-m { - align-items: flex-end; - } - .items-center-m { - align-items: center; - } - .items-baseline-m { - align-items: baseline; - } - .items-stretch-m { - align-items: stretch; - } - .self-start-m { - align-self: flex-start; - } - .self-end-m { - align-self: flex-end; - } - .self-center-m { - align-self: center; - } - .self-baseline-m { - align-self: baseline; - } - .self-stretch-m { - align-self: stretch; - } - .justify-start-m { - justify-content: flex-start; - } - .justify-end-m { - justify-content: flex-end; - } - .justify-center-m { - justify-content: center; - } - .justify-between-m { - justify-content: space-between; - } - .justify-around-m { - justify-content: space-around; - } - .content-start-m { - align-content: flex-start; - } - .content-end-m { - align-content: flex-end; - } - .content-center-m { - align-content: center; - } - .content-between-m { - align-content: space-between; - } - .content-around-m { - align-content: space-around; - } - .content-stretch-m { - align-content: stretch; - } - .order-0-m { - order: 0; - } - .order-1-m { - order: 1; - } - .order-2-m { - order: 2; - } - .order-3-m { - order: 3; - } - .order-4-m { - order: 4; - } - .order-5-m { - order: 5; - } - .order-6-m { - order: 6; - } - .order-7-m { - order: 7; - } - .order-8-m { - order: 8; - } - .order-last-m { - order: 99999; - } - .flex-grow-0-m { - flex-grow: 0; - } - .flex-grow-1-m { - flex-grow: 1; - } - .flex-shrink-0-m { - flex-shrink: 0; - } - .flex-shrink-1-m { - flex-shrink: 1; - } - .fl-m { - float: left; - } - .fl-m, - .fr-m { - _display: inline; - } - .fr-m { - float: right; - } - .fn-m { - float: none; - } - .i-m { - font-style: italic; - } - .fs-normal-m { - font-style: normal; - } - .normal-m { - font-weight: 400; - } - .b-m { - font-weight: 700; - } - .fw1-m { - font-weight: 100; - } - .fw2-m { - font-weight: 200; - } - .fw3-m { - font-weight: 300; - } - .fw4-m { - font-weight: 400; - } - .fw5-m { - font-weight: 500; - } - .fw6-m { - font-weight: 600; - } - .fw7-m { - font-weight: 700; - } - .fw8-m { - font-weight: 800; - } - .fw9-m { - font-weight: 900; - } - .h1-m { - height: 1rem; - } - .h2-m { - height: 2rem; - } - .h3-m { - height: 4rem; - } - .h4-m { - height: 8rem; - } - .h5-m { - height: 16rem; - } - .h-25-m { - height: 25%; - } - .h-50-m { - height: 50%; - } - .h-75-m { - height: 75%; - } - .h-100-m { - height: 100%; - } - .min-h-100-m { - min-height: 100%; - } - .vh-25-m { - height: 25vh; - } - .vh-50-m { - height: 50vh; - } - .vh-75-m { - height: 75vh; - } - .vh-100-m { - height: 100vh; - } - .min-vh-100-m { - min-height: 100vh; - } - .h-auto-m { - height: auto; - } - .h-inherit-m { - height: inherit; - } - .tracked-m { - letter-spacing: .1em; - } - .tracked-tight-m { - letter-spacing: -.05em; - } - .tracked-mega-m { - letter-spacing: .25em; - } - .lh-solid-m { - line-height: 1; - } - .lh-title-m { - line-height: 1.25; - } - .lh-copy-m { - line-height: 1.5; - } - .mw-100-m { - max-width: 100%; - } - .mw1-m { - max-width: 1rem; - } - .mw2-m { - max-width: 2rem; - } - .mw3-m { - max-width: 4rem; - } - .mw4-m { - max-width: 8rem; - } - .mw5-m { - max-width: 16rem; - } - .mw6-m { - max-width: 32rem; - } - .mw7-m { - max-width: 48rem; - } - .mw8-m { - max-width: 64rem; - } - .mw9-m { - max-width: 96rem; - } - .mw-none-m { - max-width: none; - } - .w1-m { - width: 1rem; - } - .w2-m { - width: 2rem; - } - .w3-m { - width: 4rem; - } - .w4-m { - width: 8rem; - } - .w5-m { - width: 16rem; - } - .w-10-m { - width: 10%; - } - .w-20-m { - width: 20%; - } - .w-25-m { - width: 25%; - } - .w-30-m { - width: 30%; - } - .w-33-m { - width: 33%; - } - .w-34-m { - width: 34%; - } - .w-40-m { - width: 40%; - } - .w-50-m { - width: 50%; - } - .w-60-m { - width: 60%; - } - .w-70-m { - width: 70%; - } - .w-75-m { - width: 75%; - } - .w-80-m { - width: 80%; - } - .w-90-m { - width: 90%; - } - .w-100-m { - width: 100%; - } - .w-third-m { - width: 33.33333%; - } - .w-two-thirds-m { - width: 66.66667%; - } - .w-auto-m { - width: auto; - } - .overflow-visible-m { - overflow: visible; - } - .overflow-hidden-m { - overflow: hidden; - } - .overflow-scroll-m { - overflow: scroll; - } - .overflow-auto-m { - overflow: auto; - } - .overflow-x-visible-m { - overflow-x: visible; - } - .overflow-x-hidden-m { - overflow-x: hidden; - } - .overflow-x-scroll-m { - overflow-x: scroll; - } - .overflow-x-auto-m { - overflow-x: auto; - } - .overflow-y-visible-m { - overflow-y: visible; - } - .overflow-y-hidden-m { - overflow-y: hidden; - } - .overflow-y-scroll-m { - overflow-y: scroll; - } - .overflow-y-auto-m { - overflow-y: auto; - } - .static-m { - position: static; - } - .relative-m { - position: relative; - } - .absolute-m { - position: absolute; - } - .fixed-m { - position: fixed; - } - .rotate-45-m { - -webkit-transform: rotate(45deg); - transform: rotate(45deg); - } - .rotate-90-m { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - } - .rotate-135-m { - -webkit-transform: rotate(135deg); - transform: rotate(135deg); - } - .rotate-180-m { - -webkit-transform: rotate(180deg); - transform: rotate(180deg); - } - .rotate-225-m { - -webkit-transform: rotate(225deg); - transform: rotate(225deg); - } - .rotate-270-m { - -webkit-transform: rotate(270deg); - transform: rotate(270deg); - } - .rotate-315-m { - -webkit-transform: rotate(315deg); - transform: rotate(315deg); - } - .pa0-m { - padding: 0; - } - .pa1-m { - padding: .25rem; - } - .pa2-m { - padding: .5rem; - } - .pa3-m { - padding: 1rem; - } - .pa4-m { - padding: 2rem; - } - .pa5-m { - padding: 4rem; - } - .pa6-m { - padding: 8rem; - } - .pa7-m { - padding: 16rem; - } - .pl0-m { - padding-left: 0; - } - .pl1-m { - padding-left: .25rem; - } - .pl2-m { - padding-left: .5rem; - } - .pl3-m { - padding-left: 1rem; - } - .pl4-m { - padding-left: 2rem; - } - .pl5-m { - padding-left: 4rem; - } - .pl6-m { - padding-left: 8rem; - } - .pl7-m { - padding-left: 16rem; - } - .pr0-m { - padding-right: 0; - } - .pr1-m { - padding-right: .25rem; - } - .pr2-m { - padding-right: .5rem; - } - .pr3-m { - padding-right: 1rem; - } - .pr4-m { - padding-right: 2rem; - } - .pr5-m { - padding-right: 4rem; - } - .pr6-m { - padding-right: 8rem; - } - .pr7-m { - padding-right: 16rem; - } - .pb0-m { - padding-bottom: 0; - } - .pb1-m { - padding-bottom: .25rem; - } - .pb2-m { - padding-bottom: .5rem; - } - .pb3-m { - padding-bottom: 1rem; - } - .pb4-m { - padding-bottom: 2rem; - } - .pb5-m { - padding-bottom: 4rem; - } - .pb6-m { - padding-bottom: 8rem; - } - .pb7-m { - padding-bottom: 16rem; - } - .pt0-m { - padding-top: 0; - } - .pt1-m { - padding-top: .25rem; - } - .pt2-m { - padding-top: .5rem; - } - .pt3-m { - padding-top: 1rem; - } - .pt4-m { - padding-top: 2rem; - } - .pt5-m { - padding-top: 4rem; - } - .pt6-m { - padding-top: 8rem; - } - .pt7-m { - padding-top: 16rem; - } - .pv0-m { - padding-top: 0; - padding-bottom: 0; - } - .pv1-m { - padding-top: .25rem; - padding-bottom: .25rem; - } - .pv2-m { - padding-top: .5rem; - padding-bottom: .5rem; - } - .pv3-m { - padding-top: 1rem; - padding-bottom: 1rem; - } - .pv4-m { - padding-top: 2rem; - padding-bottom: 2rem; - } - .pv5-m { - padding-top: 4rem; - padding-bottom: 4rem; - } - .pv6-m { - padding-top: 8rem; - padding-bottom: 8rem; - } - .pv7-m { - padding-top: 16rem; - padding-bottom: 16rem; - } - .ph0-m { - padding-left: 0; - padding-right: 0; - } - .ph1-m { - padding-left: .25rem; - padding-right: .25rem; - } - .ph2-m { - padding-left: .5rem; - padding-right: .5rem; - } - .ph3-m { - padding-left: 1rem; - padding-right: 1rem; - } - .ph4-m { - padding-left: 2rem; - padding-right: 2rem; - } - .ph5-m { - padding-left: 4rem; - padding-right: 4rem; - } - .ph6-m { - padding-left: 8rem; - padding-right: 8rem; - } - .ph7-m { - padding-left: 16rem; - padding-right: 16rem; - } - .ma0-m { - margin: 0; - } - .ma1-m { - margin: .25rem; - } - .ma2-m { - margin: .5rem; - } - .ma3-m { - margin: 1rem; - } - .ma4-m { - margin: 2rem; - } - .ma5-m { - margin: 4rem; - } - .ma6-m { - margin: 8rem; - } - .ma7-m { - margin: 16rem; - } - .ml0-m { - margin-left: 0; - } - .ml1-m { - margin-left: .25rem; - } - .ml2-m { - margin-left: .5rem; - } - .ml3-m { - margin-left: 1rem; - } - .ml4-m { - margin-left: 2rem; - } - .ml5-m { - margin-left: 4rem; - } - .ml6-m { - margin-left: 8rem; - } - .ml7-m { - margin-left: 16rem; - } - .mr0-m { - margin-right: 0; - } - .mr1-m { - margin-right: .25rem; - } - .mr2-m { - margin-right: .5rem; - } - .mr3-m { - margin-right: 1rem; - } - .mr4-m { - margin-right: 2rem; - } - .mr5-m { - margin-right: 4rem; - } - .mr6-m { - margin-right: 8rem; - } - .mr7-m { - margin-right: 16rem; - } - .mb0-m { - margin-bottom: 0; - } - .mb1-m { - margin-bottom: .25rem; - } - .mb2-m { - margin-bottom: .5rem; - } - .mb3-m { - margin-bottom: 1rem; - } - .mb4-m { - margin-bottom: 2rem; - } - .mb5-m { - margin-bottom: 4rem; - } - .mb6-m { - margin-bottom: 8rem; - } - .mb7-m { - margin-bottom: 16rem; - } - .mt0-m { - margin-top: 0; - } - .mt1-m { - margin-top: .25rem; - } - .mt2-m { - margin-top: .5rem; - } - .mt3-m { - margin-top: 1rem; - } - .mt4-m { - margin-top: 2rem; - } - .mt5-m { - margin-top: 4rem; - } - .mt6-m { - margin-top: 8rem; - } - .mt7-m { - margin-top: 16rem; - } - .mv0-m { - margin-top: 0; - margin-bottom: 0; - } - .mv1-m { - margin-top: .25rem; - margin-bottom: .25rem; - } - .mv2-m { - margin-top: .5rem; - margin-bottom: .5rem; - } - .mv3-m { - margin-top: 1rem; - margin-bottom: 1rem; - } - .mv4-m { - margin-top: 2rem; - margin-bottom: 2rem; - } - .mv5-m { - margin-top: 4rem; - margin-bottom: 4rem; - } - .mv6-m { - margin-top: 8rem; - margin-bottom: 8rem; - } - .mv7-m { - margin-top: 16rem; - margin-bottom: 16rem; - } - .mh0-m { - margin-left: 0; - margin-right: 0; - } - .mh1-m { - margin-left: .25rem; - margin-right: .25rem; - } - .mh2-m { - margin-left: .5rem; - margin-right: .5rem; - } - .mh3-m { - margin-left: 1rem; - margin-right: 1rem; - } - .mh4-m { - margin-left: 2rem; - margin-right: 2rem; - } - .mh5-m { - margin-left: 4rem; - margin-right: 4rem; - } - .mh6-m { - margin-left: 8rem; - margin-right: 8rem; - } - .mh7-m { - margin-left: 16rem; - margin-right: 16rem; - } - .na1-m { - margin: -.25rem; - } - .na2-m { - margin: -.5rem; - } - .na3-m { - margin: -1rem; - } - .na4-m { - margin: -2rem; - } - .na5-m { - margin: -4rem; - } - .na6-m { - margin: -8rem; - } - .na7-m { - margin: -16rem; - } - .nl1-m { - margin-left: -.25rem; - } - .nl2-m { - margin-left: -.5rem; - } - .nl3-m { - margin-left: -1rem; - } - .nl4-m { - margin-left: -2rem; - } - .nl5-m { - margin-left: -4rem; - } - .nl6-m { - margin-left: -8rem; - } - .nl7-m { - margin-left: -16rem; - } - .nr1-m { - margin-right: -.25rem; - } - .nr2-m { - margin-right: -.5rem; - } - .nr3-m { - margin-right: -1rem; - } - .nr4-m { - margin-right: -2rem; - } - .nr5-m { - margin-right: -4rem; - } - .nr6-m { - margin-right: -8rem; - } - .nr7-m { - margin-right: -16rem; - } - .nb1-m { - margin-bottom: -.25rem; - } - .nb2-m { - margin-bottom: -.5rem; - } - .nb3-m { - margin-bottom: -1rem; - } - .nb4-m { - margin-bottom: -2rem; - } - .nb5-m { - margin-bottom: -4rem; - } - .nb6-m { - margin-bottom: -8rem; - } - .nb7-m { - margin-bottom: -16rem; - } - .nt1-m { - margin-top: -.25rem; - } - .nt2-m { - margin-top: -.5rem; - } - .nt3-m { - margin-top: -1rem; - } - .nt4-m { - margin-top: -2rem; - } - .nt5-m { - margin-top: -4rem; - } - .nt6-m { - margin-top: -8rem; - } - .nt7-m { - margin-top: -16rem; - } - .strike-m { - text-decoration: line-through; - } - .underline-m { - text-decoration: underline; - } - .no-underline-m { - text-decoration: none; - } - .tl-m { - text-align: left; - } - .tr-m { - text-align: right; - } - .tc-m { - text-align: center; - } - .tj-m { - text-align: justify; - } - .ttc-m { - text-transform: capitalize; - } - .ttl-m { - text-transform: lowercase; - } - .ttu-m { - text-transform: uppercase; - } - .ttn-m { - text-transform: none; - } - .f-6-m, - .f-headline-m { - font-size: 6rem; - } - .f-5-m, - .f-subheadline-m { - font-size: 5rem; - } - .f1-m { - font-size: 3rem; - } - .f2-m { - font-size: 2.25rem; - } - .f3-m { - font-size: 1.5rem; - } - .f4-m { - font-size: 1.25rem; - } - .f5-m { - font-size: 1rem; - } - .f6-m { - font-size: .875rem; - } - .f7-m { - font-size: .75rem; - } - .measure-m { - max-width: 30em; - } - .measure-wide-m { - max-width: 34em; - } - .measure-narrow-m { - max-width: 20em; - } - .indent-m { - text-indent: 1em; - margin-top: 0; - margin-bottom: 0; - } - .small-caps-m { - font-variant: small-caps; - } - .truncate-m { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .center-m { - margin-left: auto; - } - .center-m, - .mr-auto-m { - margin-right: auto; - } - .ml-auto-m { - margin-left: auto; - } - .clip-m { - position: fixed !important; - _position: absolute !important; - clip: rect(1px 1px 1px 1px); - clip: rect(1px, 1px, 1px, 1px); - } - .ws-normal-m { - white-space: normal; - } - .nowrap-m { - white-space: nowrap; - } - .pre-m { - white-space: pre; - } - .v-base-m { - vertical-align: baseline; - } - .v-mid-m { - vertical-align: middle; - } - .v-top-m { - vertical-align: top; - } - .v-btm-m { - vertical-align: bottom; - } -} -@media screen and (min-width:60em) { - .aspect-ratio-l { - height: 0; - position: relative; - } - .aspect-ratio--16x9-l { - padding-bottom: 56.25%; - } - .aspect-ratio--9x16-l { - padding-bottom: 177.77%; - } - .aspect-ratio--4x3-l { - padding-bottom: 75%; - } - .aspect-ratio--3x4-l { - padding-bottom: 133.33%; - } - .aspect-ratio--6x4-l { - padding-bottom: 66.6%; - } - .aspect-ratio--4x6-l { - padding-bottom: 150%; - } - .aspect-ratio--8x5-l { - padding-bottom: 62.5%; - } - .aspect-ratio--5x8-l { - padding-bottom: 160%; - } - .aspect-ratio--7x5-l { - padding-bottom: 71.42%; - } - .aspect-ratio--5x7-l { - padding-bottom: 140%; - } - .aspect-ratio--1x1-l { - padding-bottom: 100%; - } - .aspect-ratio--object-l { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 100; - } - .cover-l { - background-size: cover !important; - } - .contain-l { - background-size: contain !important; - } - .bg-center-l { - background-position: 50%; - } - .bg-center-l, - .bg-top-l { - background-repeat: no-repeat; - } - .bg-top-l { - background-position: top; - } - .bg-right-l { - background-position: 100%; - } - .bg-bottom-l, - .bg-right-l { - background-repeat: no-repeat; - } - .bg-bottom-l { - background-position: bottom; - } - .bg-left-l { - background-repeat: no-repeat; - background-position: 0; - } - .outline-l { - outline: 1px solid; - } - .outline-transparent-l { - outline: 1px solid transparent; - } - .outline-0-l { - outline: 0; - } - .ba-l { - border-style: solid; - border-width: 1px; - } - .bt-l { - border-top-style: solid; - border-top-width: 1px; - } - .br-l { - border-right-style: solid; - border-right-width: 1px; - } - .bb-l { - border-bottom-style: solid; - border-bottom-width: 1px; - } - .bl-l { - border-left-style: solid; - border-left-width: 1px; - } - .bn-l { - border-style: none; - border-width: 0; - } - .br0-l { - border-radius: 0; - } - .br1-l { - border-radius: .125rem; - } - .br2-l { - border-radius: .25rem; - } - .br3-l { - border-radius: .5rem; - } - .br4-l { - border-radius: 1rem; - } - .br-100-l { - border-radius: 100%; - } - .br-pill-l { - border-radius: 9999px; - } - .br--bottom-l { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - .br--top-l { - border-bottom-right-radius: 0; - } - .br--right-l, - .br--top-l { - border-bottom-left-radius: 0; - } - .br--right-l { - border-top-left-radius: 0; - } - .br--left-l { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - .br-inherit-l { - border-radius: inherit; - } - .br-initial-l { - border-radius: initial; - } - .br-unset-l { - border-radius: unset; - } - .b--dotted-l { - border-style: dotted; - } - .b--dashed-l { - border-style: dashed; - } - .b--solid-l { - border-style: solid; - } - .b--none-l { - border-style: none; - } - .bw0-l { - border-width: 0; - } - .bw1-l { - border-width: .125rem; - } - .bw2-l { - border-width: .25rem; - } - .bw3-l { - border-width: .5rem; - } - .bw4-l { - border-width: 1rem; - } - .bw5-l { - border-width: 2rem; - } - .bt-0-l { - border-top-width: 0; - } - .br-0-l { - border-right-width: 0; - } - .bb-0-l { - border-bottom-width: 0; - } - .bl-0-l { - border-left-width: 0; - } - .shadow-1-l { - box-shadow: 0 0 4px 2px rgba(0, 0, 0, .2); - } - .shadow-2-l { - box-shadow: 0 0 8px 2px rgba(0, 0, 0, .2); - } - .shadow-3-l { - box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, .2); - } - .shadow-4-l { - box-shadow: 2px 2px 8px 0 rgba(0, 0, 0, .2); - } - .shadow-5-l { - box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, .2); - } - .top-0-l { - top: 0; - } - .left-0-l { - left: 0; - } - .right-0-l { - right: 0; - } - .bottom-0-l { - bottom: 0; - } - .top-1-l { - top: 1rem; - } - .left-1-l { - left: 1rem; - } - .right-1-l { - right: 1rem; - } - .bottom-1-l { - bottom: 1rem; - } - .top-2-l { - top: 2rem; - } - .left-2-l { - left: 2rem; - } - .right-2-l { - right: 2rem; - } - .bottom-2-l { - bottom: 2rem; - } - .top--1-l { - top: -1rem; - } - .right--1-l { - right: -1rem; - } - .bottom--1-l { - bottom: -1rem; - } - .left--1-l { - left: -1rem; - } - .top--2-l { - top: -2rem; - } - .right--2-l { - right: -2rem; - } - .bottom--2-l { - bottom: -2rem; - } - .left--2-l { - left: -2rem; - } - .absolute--fill-l { - top: 0; - right: 0; - bottom: 0; - left: 0; - } - .cl-l { - clear: left; - } - .cr-l { - clear: right; - } - .cb-l { - clear: both; - } - .cn-l { - clear: none; - } - .dn-l { - display: none; - } - .di-l { - display: inline; - } - .db-l { - display: block; - } - .dib-l { - display: inline-block; - } - .dit-l { - display: inline-table; - } - .dt-l { - display: table; - } - .dtc-l { - display: table-cell; - } - .dt-row-l { - display: table-row; - } - .dt-row-group-l { - display: table-row-group; - } - .dt-column-l { - display: table-column; - } - .dt-column-group-l { - display: table-column-group; - } - .dt--fixed-l { - table-layout: fixed; - width: 100%; - } - .flex-l { - display: flex; - } - .inline-flex-l { - display: inline-flex; - } - .flex-auto-l { - flex: 1 1 auto; - min-width: 0; - min-height: 0; - } - .flex-none-l { - flex: none; - } - .flex-column-l { - flex-direction: column; - } - .flex-row-l { - flex-direction: row; - } - .flex-wrap-l { - flex-wrap: wrap; - } - .flex-nowrap-l { - flex-wrap: nowrap; - } - .flex-wrap-reverse-l { - flex-wrap: wrap-reverse; - } - .flex-column-reverse-l { - flex-direction: column-reverse; - } - .flex-row-reverse-l { - flex-direction: row-reverse; - } - .items-start-l { - align-items: flex-start; - } - .items-end-l { - align-items: flex-end; - } - .items-center-l { - align-items: center; - } - .items-baseline-l { - align-items: baseline; - } - .items-stretch-l { - align-items: stretch; - } - .self-start-l { - align-self: flex-start; - } - .self-end-l { - align-self: flex-end; - } - .self-center-l { - align-self: center; - } - .self-baseline-l { - align-self: baseline; - } - .self-stretch-l { - align-self: stretch; - } - .justify-start-l { - justify-content: flex-start; - } - .justify-end-l { - justify-content: flex-end; - } - .justify-center-l { - justify-content: center; - } - .justify-between-l { - justify-content: space-between; - } - .justify-around-l { - justify-content: space-around; - } - .content-start-l { - align-content: flex-start; - } - .content-end-l { - align-content: flex-end; - } - .content-center-l { - align-content: center; - } - .content-between-l { - align-content: space-between; - } - .content-around-l { - align-content: space-around; - } - .content-stretch-l { - align-content: stretch; - } - .order-0-l { - order: 0; - } - .order-1-l { - order: 1; - } - .order-2-l { - order: 2; - } - .order-3-l { - order: 3; - } - .order-4-l { - order: 4; - } - .order-5-l { - order: 5; - } - .order-6-l { - order: 6; - } - .order-7-l { - order: 7; - } - .order-8-l { - order: 8; - } - .order-last-l { - order: 99999; - } - .flex-grow-0-l { - flex-grow: 0; - } - .flex-grow-1-l { - flex-grow: 1; - } - .flex-shrink-0-l { - flex-shrink: 0; - } - .flex-shrink-1-l { - flex-shrink: 1; - } - .fl-l { - float: left; - } - .fl-l, - .fr-l { - _display: inline; - } - .fr-l { - float: right; - } - .fn-l { - float: none; - } - .i-l { - font-style: italic; - } - .fs-normal-l { - font-style: normal; - } - .normal-l { - font-weight: 400; - } - .b-l { - font-weight: 700; - } - .fw1-l { - font-weight: 100; - } - .fw2-l { - font-weight: 200; - } - .fw3-l { - font-weight: 300; - } - .fw4-l { - font-weight: 400; - } - .fw5-l { - font-weight: 500; - } - .fw6-l { - font-weight: 600; - } - .fw7-l { - font-weight: 700; - } - .fw8-l { - font-weight: 800; - } - .fw9-l { - font-weight: 900; - } - .h1-l { - height: 1rem; - } - .h2-l { - height: 2rem; - } - .h3-l { - height: 4rem; - } - .h4-l { - height: 8rem; - } - .h5-l { - height: 16rem; - } - .h-25-l { - height: 25%; - } - .h-50-l { - height: 50%; - } - .h-75-l { - height: 75%; - } - .h-100-l { - height: 100%; - } - .min-h-100-l { - min-height: 100%; - } - .vh-25-l { - height: 25vh; - } - .vh-50-l { - height: 50vh; - } - .vh-75-l { - height: 75vh; - } - .vh-100-l { - height: 100vh; - } - .min-vh-100-l { - min-height: 100vh; - } - .h-auto-l { - height: auto; - } - .h-inherit-l { - height: inherit; - } - .tracked-l { - letter-spacing: .1em; - } - .tracked-tight-l { - letter-spacing: -.05em; - } - .tracked-mega-l { - letter-spacing: .25em; - } - .lh-solid-l { - line-height: 1; - } - .lh-title-l { - line-height: 1.25; - } - .lh-copy-l { - line-height: 1.5; - } - .mw-100-l { - max-width: 100%; - } - .mw1-l { - max-width: 1rem; - } - .mw2-l { - max-width: 2rem; - } - .mw3-l { - max-width: 4rem; - } - .mw4-l { - max-width: 8rem; - } - .mw5-l { - max-width: 16rem; - } - .mw6-l { - max-width: 32rem; - } - .mw7-l { - max-width: 48rem; - } - .mw8-l { - max-width: 64rem; - } - .mw9-l { - max-width: 96rem; - } - .mw-none-l { - max-width: none; - } - .w1-l { - width: 1rem; - } - .w2-l { - width: 2rem; - } - .w3-l { - width: 4rem; - } - .w4-l { - width: 8rem; - } - .w5-l { - width: 16rem; - } - .w-10-l { - width: 10%; - } - .w-20-l { - width: 20%; - } - .w-25-l { - width: 25%; - } - .w-30-l { - width: 30%; - } - .w-33-l { - width: 33%; - } - .w-34-l { - width: 34%; - } - .w-40-l { - width: 40%; - } - .w-50-l { - width: 50%; - } - .w-60-l { - width: 60%; - } - .w-70-l { - width: 70%; - } - .w-75-l { - width: 75%; - } - .w-80-l { - width: 80%; - } - .w-90-l { - width: 90%; - } - .w-100-l { - width: 100%; - } - .w-third-l { - width: 33.33333%; - } - .w-two-thirds-l { - width: 66.66667%; - } - .w-auto-l { - width: auto; - } - .overflow-visible-l { - overflow: visible; - } - .overflow-hidden-l { - overflow: hidden; - } - .overflow-scroll-l { - overflow: scroll; - } - .overflow-auto-l { - overflow: auto; - } - .overflow-x-visible-l { - overflow-x: visible; - } - .overflow-x-hidden-l { - overflow-x: hidden; - } - .overflow-x-scroll-l { - overflow-x: scroll; - } - .overflow-x-auto-l { - overflow-x: auto; - } - .overflow-y-visible-l { - overflow-y: visible; - } - .overflow-y-hidden-l { - overflow-y: hidden; - } - .overflow-y-scroll-l { - overflow-y: scroll; - } - .overflow-y-auto-l { - overflow-y: auto; - } - .static-l { - position: static; - } - .relative-l { - position: relative; - } - .absolute-l { - position: absolute; - } - .fixed-l { - position: fixed; - } - .rotate-45-l { - -webkit-transform: rotate(45deg); - transform: rotate(45deg); - } - .rotate-90-l { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - } - .rotate-135-l { - -webkit-transform: rotate(135deg); - transform: rotate(135deg); - } - .rotate-180-l { - -webkit-transform: rotate(180deg); - transform: rotate(180deg); - } - .rotate-225-l { - -webkit-transform: rotate(225deg); - transform: rotate(225deg); - } - .rotate-270-l { - -webkit-transform: rotate(270deg); - transform: rotate(270deg); - } - .rotate-315-l { - -webkit-transform: rotate(315deg); - transform: rotate(315deg); - } - .pa0-l { - padding: 0; - } - .pa1-l { - padding: .25rem; - } - .pa2-l { - padding: .5rem; - } - .pa3-l { - padding: 1rem; - } - .pa4-l { - padding: 2rem; - } - .pa5-l { - padding: 4rem; - } - .pa6-l { - padding: 8rem; - } - .pa7-l { - padding: 16rem; - } - .pl0-l { - padding-left: 0; - } - .pl1-l { - padding-left: .25rem; - } - .pl2-l { - padding-left: .5rem; - } - .pl3-l { - padding-left: 1rem; - } - .pl4-l { - padding-left: 2rem; - } - .pl5-l { - padding-left: 4rem; - } - .pl6-l { - padding-left: 8rem; - } - .pl7-l { - padding-left: 16rem; - } - .pr0-l { - padding-right: 0; - } - .pr1-l { - padding-right: .25rem; - } - .pr2-l { - padding-right: .5rem; - } - .pr3-l { - padding-right: 1rem; - } - .pr4-l { - padding-right: 2rem; - } - .pr5-l { - padding-right: 4rem; - } - .pr6-l { - padding-right: 8rem; - } - .pr7-l { - padding-right: 16rem; - } - .pb0-l { - padding-bottom: 0; - } - .pb1-l { - padding-bottom: .25rem; - } - .pb2-l { - padding-bottom: .5rem; - } - .pb3-l { - padding-bottom: 1rem; - } - .pb4-l { - padding-bottom: 2rem; - } - .pb5-l { - padding-bottom: 4rem; - } - .pb6-l { - padding-bottom: 8rem; - } - .pb7-l { - padding-bottom: 16rem; - } - .pt0-l { - padding-top: 0; - } - .pt1-l { - padding-top: .25rem; - } - .pt2-l { - padding-top: .5rem; - } - .pt3-l { - padding-top: 1rem; - } - .pt4-l { - padding-top: 2rem; - } - .pt5-l { - padding-top: 4rem; - } - .pt6-l { - padding-top: 8rem; - } - .pt7-l { - padding-top: 16rem; - } - .pv0-l { - padding-top: 0; - padding-bottom: 0; - } - .pv1-l { - padding-top: .25rem; - padding-bottom: .25rem; - } - .pv2-l { - padding-top: .5rem; - padding-bottom: .5rem; - } - .pv3-l { - padding-top: 1rem; - padding-bottom: 1rem; - } - .pv4-l { - padding-top: 2rem; - padding-bottom: 2rem; - } - .pv5-l { - padding-top: 4rem; - padding-bottom: 4rem; - } - .pv6-l { - padding-top: 8rem; - padding-bottom: 8rem; - } - .pv7-l { - padding-top: 16rem; - padding-bottom: 16rem; - } - .ph0-l { - padding-left: 0; - padding-right: 0; - } - .ph1-l { - padding-left: .25rem; - padding-right: .25rem; - } - .ph2-l { - padding-left: .5rem; - padding-right: .5rem; - } - .ph3-l { - padding-left: 1rem; - padding-right: 1rem; - } - .ph4-l { - padding-left: 2rem; - padding-right: 2rem; - } - .ph5-l { - padding-left: 4rem; - padding-right: 4rem; - } - .ph6-l { - padding-left: 8rem; - padding-right: 8rem; - } - .ph7-l { - padding-left: 16rem; - padding-right: 16rem; - } - .ma0-l { - margin: 0; - } - .ma1-l { - margin: .25rem; - } - .ma2-l { - margin: .5rem; - } - .ma3-l { - margin: 1rem; - } - .ma4-l { - margin: 2rem; - } - .ma5-l { - margin: 4rem; - } - .ma6-l { - margin: 8rem; - } - .ma7-l { - margin: 16rem; - } - .ml0-l { - margin-left: 0; - } - .ml1-l { - margin-left: .25rem; - } - .ml2-l { - margin-left: .5rem; - } - .ml3-l { - margin-left: 1rem; - } - .ml4-l { - margin-left: 2rem; - } - .ml5-l { - margin-left: 4rem; - } - .ml6-l { - margin-left: 8rem; - } - .ml7-l { - margin-left: 16rem; - } - .mr0-l { - margin-right: 0; - } - .mr1-l { - margin-right: .25rem; - } - .mr2-l { - margin-right: .5rem; - } - .mr3-l { - margin-right: 1rem; - } - .mr4-l { - margin-right: 2rem; - } - .mr5-l { - margin-right: 4rem; - } - .mr6-l { - margin-right: 8rem; - } - .mr7-l { - margin-right: 16rem; - } - .mb0-l { - margin-bottom: 0; - } - .mb1-l { - margin-bottom: .25rem; - } - .mb2-l { - margin-bottom: .5rem; - } - .mb3-l { - margin-bottom: 1rem; - } - .mb4-l { - margin-bottom: 2rem; - } - .mb5-l { - margin-bottom: 4rem; - } - .mb6-l { - margin-bottom: 8rem; - } - .mb7-l { - margin-bottom: 16rem; - } - .mt0-l { - margin-top: 0; - } - .mt1-l { - margin-top: .25rem; - } - .mt2-l { - margin-top: .5rem; - } - .mt3-l { - margin-top: 1rem; - } - .mt4-l { - margin-top: 2rem; - } - .mt5-l { - margin-top: 4rem; - } - .mt6-l { - margin-top: 8rem; - } - .mt7-l { - margin-top: 16rem; - } - .mv0-l { - margin-top: 0; - margin-bottom: 0; - } - .mv1-l { - margin-top: .25rem; - margin-bottom: .25rem; - } - .mv2-l { - margin-top: .5rem; - margin-bottom: .5rem; - } - .mv3-l { - margin-top: 1rem; - margin-bottom: 1rem; - } - .mv4-l { - margin-top: 2rem; - margin-bottom: 2rem; - } - .mv5-l { - margin-top: 4rem; - margin-bottom: 4rem; - } - .mv6-l { - margin-top: 8rem; - margin-bottom: 8rem; - } - .mv7-l { - margin-top: 16rem; - margin-bottom: 16rem; - } - .mh0-l { - margin-left: 0; - margin-right: 0; - } - .mh1-l { - margin-left: .25rem; - margin-right: .25rem; - } - .mh2-l { - margin-left: .5rem; - margin-right: .5rem; - } - .mh3-l { - margin-left: 1rem; - margin-right: 1rem; - } - .mh4-l { - margin-left: 2rem; - margin-right: 2rem; - } - .mh5-l { - margin-left: 4rem; - margin-right: 4rem; - } - .mh6-l { - margin-left: 8rem; - margin-right: 8rem; - } - .mh7-l { - margin-left: 16rem; - margin-right: 16rem; - } - .na1-l { - margin: -.25rem; - } - .na2-l { - margin: -.5rem; - } - .na3-l { - margin: -1rem; - } - .na4-l { - margin: -2rem; - } - .na5-l { - margin: -4rem; - } - .na6-l { - margin: -8rem; - } - .na7-l { - margin: -16rem; - } - .nl1-l { - margin-left: -.25rem; - } - .nl2-l { - margin-left: -.5rem; - } - .nl3-l { - margin-left: -1rem; - } - .nl4-l { - margin-left: -2rem; - } - .nl5-l { - margin-left: -4rem; - } - .nl6-l { - margin-left: -8rem; - } - .nl7-l { - margin-left: -16rem; - } - .nr1-l { - margin-right: -.25rem; - } - .nr2-l { - margin-right: -.5rem; - } - .nr3-l { - margin-right: -1rem; - } - .nr4-l { - margin-right: -2rem; - } - .nr5-l { - margin-right: -4rem; - } - .nr6-l { - margin-right: -8rem; - } - .nr7-l { - margin-right: -16rem; - } - .nb1-l { - margin-bottom: -.25rem; - } - .nb2-l { - margin-bottom: -.5rem; - } - .nb3-l { - margin-bottom: -1rem; - } - .nb4-l { - margin-bottom: -2rem; - } - .nb5-l { - margin-bottom: -4rem; - } - .nb6-l { - margin-bottom: -8rem; - } - .nb7-l { - margin-bottom: -16rem; - } - .nt1-l { - margin-top: -.25rem; - } - .nt2-l { - margin-top: -.5rem; - } - .nt3-l { - margin-top: -1rem; - } - .nt4-l { - margin-top: -2rem; - } - .nt5-l { - margin-top: -4rem; - } - .nt6-l { - margin-top: -8rem; - } - .nt7-l { - margin-top: -16rem; - } - .strike-l { - text-decoration: line-through; - } - .underline-l { - text-decoration: underline; - } - .no-underline-l { - text-decoration: none; - } - .tl-l { - text-align: left; - } - .tr-l { - text-align: right; - } - .tc-l { - text-align: center; - } - .tj-l { - text-align: justify; - } - .ttc-l { - text-transform: capitalize; - } - .ttl-l { - text-transform: lowercase; - } - .ttu-l { - text-transform: uppercase; - } - .ttn-l { - text-transform: none; - } - .f-6-l, - .f-headline-l { - font-size: 6rem; - } - .f-5-l, - .f-subheadline-l { - font-size: 5rem; - } - .f1-l { - font-size: 3rem; - } - .f2-l { - font-size: 2.25rem; - } - .f3-l { - font-size: 1.5rem; - } - .f4-l { - font-size: 1.25rem; - } - .f5-l { - font-size: 1rem; - } - .f6-l { - font-size: .875rem; - } - .f7-l { - font-size: .75rem; - } - .measure-l { - max-width: 30em; - } - .measure-wide-l { - max-width: 34em; - } - .measure-narrow-l { - max-width: 20em; - } - .indent-l { - text-indent: 1em; - margin-top: 0; - margin-bottom: 0; - } - .small-caps-l { - font-variant: small-caps; - } - .truncate-l { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .center-l { - margin-left: auto; - } - .center-l, - .mr-auto-l { - margin-right: auto; - } - .ml-auto-l { - margin-left: auto; - } - .clip-l { - position: fixed !important; - _position: absolute !important; - clip: rect(1px 1px 1px 1px); - clip: rect(1px, 1px, 1px, 1px); - } - .ws-normal-l { - white-space: normal; - } - .nowrap-l { - white-space: nowrap; - } - .pre-l { - white-space: pre; - } - .v-base-l { - vertical-align: baseline; - } - .v-mid-l { - vertical-align: middle; - } - .v-top-l { - vertical-align: top; - } - .v-btm-l { - vertical-align: bottom; - } -} - -/* src/rawdata/css/hsf/tachyons-vars.css */ -:root { - --sans-serif: - -apple-system, - BlinkMacSystemFont, - "avenir next", - avenir, - helvetica, - "helvetica neue", - ubuntu, - roboto, - noto, - "segoe ui", - arial, - sans-serif; - --serif: georgia, serif; - --code: - consolas, - monaco, - monospace; - --font-size-headline: 6rem; - --font-size-subheadline: 5rem; - --font-size-1: 3rem; - --font-size-2: 2.25rem; - --font-size-3: 1.5rem; - --font-size-4: 1.25rem; - --font-size-5: 1rem; - --font-size-6: .875rem; - --font-size-7: .75rem; - --letter-spacing-tight: -.05em; - --letter-spacing-1: .1em; - --letter-spacing-2: .25em; - --line-height-solid: 1; - --line-height-title: 1.25; - --line-height-copy: 1.5; - --measure: 30em; - --measure-narrow: 20em; - --measure-wide: 34em; - --spacing-0: 0; - --spacing-1: .25rem; - --spacing-2: .5rem; - --spacing-3: 1rem; - --spacing-4: 2rem; - --spacing-5: 4rem; - --spacing-6: 8rem; - --spacing-7: 16rem; - --spacing-copy-separator: 1.5em; - --height-1: 1rem; - --height-2: 2rem; - --height-3: 4rem; - --height-4: 8rem; - --height-5: 16rem; - --height-6: 32rem; - --width-1: 1rem; - --width-2: 2rem; - --width-3: 4rem; - --width-4: 8rem; - --width-5: 16rem; - --width-6: 32rem; - --width-7: 48rem; - --width-8: 64rem; - --max-width-1: 1rem; - --max-width-2: 2rem; - --max-width-3: 4rem; - --max-width-4: 8rem; - --max-width-5: 16rem; - --max-width-6: 32rem; - --max-width-7: 48rem; - --max-width-8: 64rem; - --max-width-9: 96rem; - --border-radius-none: 0; - --border-radius-1: .125rem; - --border-radius-2: .25rem; - --border-radius-3: .5rem; - --border-radius-4: 1rem; - --border-radius-circle: 100%; - --border-radius-pill: 9999px; - --border-width-none: 0; - --border-width-1: .125rem; - --border-width-2: .25rem; - --border-width-3: .5rem; - --border-width-4: 1rem; - --border-width-5: 2rem; - --box-shadow-1: 0px 0px 4px 2px rgba(0, 0, 0, 0.2); - --box-shadow-2: 0px 0px 8px 2px rgba(0, 0, 0, 0.2); - --box-shadow-3: 2px 2px 4px 2px rgba(0, 0, 0, 0.2); - --box-shadow-4: 2px 2px 8px 0px rgba(0, 0, 0, 0.2); - --box-shadow-5: 4px 4px 8px 0px rgba(0, 0, 0, 0.2); - --black: #000; - --near-black: #111; - --dark-gray: #333; - --mid-gray: #555; - --gray: #777; - --silver: #999; - --light-silver: #aaa; - --moon-gray: #ccc; - --light-gray: #eee; - --near-white: #f4f4f4; - --white: #fff; - --transparent: transparent; - --black-90: rgba(0, 0, 0, .9); - --black-80: rgba(0, 0, 0, .8); - --black-70: rgba(0, 0, 0, .7); - --black-60: rgba(0, 0, 0, .6); - --black-50: rgba(0, 0, 0, .5); - --black-40: rgba(0, 0, 0, .4); - --black-30: rgba(0, 0, 0, .3); - --black-20: rgba(0, 0, 0, .2); - --black-10: rgba(0, 0, 0, .1); - --black-05: rgba(0, 0, 0, .05); - --black-025: rgba(0, 0, 0, .025); - --black-0125: rgba(0, 0, 0, .0125); - --white-90: rgba(255, 255, 255, .9); - --white-80: rgba(255, 255, 255, .8); - --white-70: rgba(255, 255, 255, .7); - --white-60: rgba(255, 255, 255, .6); - --white-50: rgba(255, 255, 255, .5); - --white-40: rgba(255, 255, 255, .4); - --white-30: rgba(255, 255, 255, .3); - --white-20: rgba(255, 255, 255, .2); - --white-10: rgba(255, 255, 255, .1); - --white-05: rgba(255, 255, 255, .05); - --white-025: rgba(255, 255, 255, .025); - --white-0125: rgba(255, 255, 255, .0125); - --dark-red: #e7040f; - --red: #ff4136; - --light-red: #ff725c; - --orange: #ff6300; - --gold: #ffb700; - --yellow: #ffd700; - --light-yellow: #fbf1a9; - --purple: #5e2ca5; - --light-purple: #a463f2; - --dark-pink: #d5008f; - --hot-pink: #ff41b4; - --pink: #ff80cc; - --light-pink: #ffa3d7; - --dark-green: #137752; - --green: #19a974; - --light-green: #9eebcf; - --navy: #001b44; - --dark-blue: #00449e; - --blue: #357edd; - --light-blue: #96ccff; - --lightest-blue: #cdecff; - --washed-blue: #f6fffe; - --washed-green: #e8fdf5; - --washed-yellow: #fffceb; - --washed-red: #ffdfdf; - --breakpoint-not-small: "screen and (min-width: 35em)"; - --breakpoint-medium: "screen and (min-width: 35em) and (max-width: 60em)"; - --breakpoint-large: "screen and (min-width: 60em)"; -} - -/* src/rawdata/css/hsf/vars.css */ -:root { - --c1: #fafafa; - --c2: #f3f3f3; - --c3: #dbdbdb; - --c4: #c1c1c1; - --c-transparent-background: #fffffff3; - --red: #c61d24; - --color: #000; - --link-color: #d12991; - --border-color: var(--c4); - --border-color-focused: #4e55ff; - --border-color-error: #ff3a3a; - --button-color: var(--c3); - --button-color-primary: #ee84ff; - --site-width: 48rem; -} -@media (prefers-color-scheme: dark) { - :root { - --color: #fff; - --link-color: #ff5dc2; - --c1: #1f1f1f; - --c2: #2f2f2f; - --c3: #494949; - --c4: #595959; - --c5: #cbcbcb; - --button-color-primary: #c900ea; - } -} - -/* src/rawdata/css/hsf/core.css */ -* { - box-sizing: border-box; - border-color: var(--border-color); -} -body { - background-color: var(--c2); - color: var(--color); - font-family: "Inter", sans-serif; - min-height: 100vh; - box-sizing: border-box; - line-height: 1.2; -} -a, -.link { - color: var(--link-color); - border-bottom-style: none; - text-decoration: none; -} -.link-normal { - --link-color: var(--color); -} -.bg-primary { - background: var(--color); -} -b, -strong { - font-weight: 600; -} -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: inherit; - margin: 0; - line-height: 1; - font-weight: 600; -} -br { - border-style: none; -} -hr { - margin: 0; - border: none; - border-bottom: 1px solid var(--border-color); -} -video { - max-width: 100%; -} -.flex-shrink-0 { - flex-shrink: 0; -} -.flex-grow-1 { - flex-grow: 1; -} -.flex-fair { - flex-basis: 1px; - flex-grow: 1; - flex-shrink: 1; -} -.aspect-ratio-16x9 { - aspect-ratio: 16 / 9; -} -.aspect-ratio-1x1 { - aspect-ratio: 1 / 1; -} -@media screen and (min-width: 35em) { - .flex-fair-ns { - flex-basis: 1px; - flex-grow: 1; - flex-shrink: 1; - } -} -@media screen and (min-width: 60em) { - .flex-fair-l { - flex-basis: 1px; - flex-grow: 1; - flex-shrink: 1; - } -} -.c-normal { - color: var(--color); -} -.f8 { - font-size: 0.65rem; -} -.w6 { - width: var(--width-6); -} -.w7 { - width: var(--width-7); -} -.w8 { - width: var(--width-8); -} -.maxh-3 { - max-height: var(--height-3); -} -.maxh-4 { - max-height: var(--height-4); -} -.maxh-5 { - max-height: var(--height-5); -} -.maxh-6 { - max-height: var(--height-6); -} -.maxh-100 { - max-height: 100%; -} -.maxh-50vh { - max-height: 50vh; -} -.maxh-60vh { - max-height: 60vh; -} -.maxh-70vh { - max-height: 70vh; -} -.maxh-80vh { - max-height: 80vh; -} -.minw-100 { - min-width: 100%; -} -.minh-1 { - min-height: var(--height-1); -} -.minh-2 { - min-height: var(--height-2); -} -.minh-3 { - min-height: var(--height-3); -} -.minh-4 { - min-height: var(--height-4); -} -.minh-5 { - min-height: var(--height-5); -} -.minh-6 { - min-height: var(--height-6); -} -.g0 { - gap: var(--spacing-0); -} -.g1 { - gap: var(--spacing-1); -} -.g2 { - gap: var(--spacing-2); -} -.g3 { - gap: var(--spacing-3); -} -.g4 { - gap: var(--spacing-4); -} -.g5 { - gap: var(--spacing-5); -} -.bg1 { - background-color: var(--c1); -} -.bg2 { - background-color: var(--c2); -} -.bg3 { - background-color: var(--c3); -} -.bg4 { - background-color: var(--c4); -} -.bg5 { - background-color: var(--c5); -} -@media screen and (min-width: 35em) { - .bg1-ns { - background-color: var(--c1); - } - .bg2-ns { - background-color: var(--c2); - } - .bg3-ns { - background-color: var(--c3); - } - .bg4-ns { - background-color: var(--c4); - } - .bg5-ns { - background-color: var(--c5); - } - .g0-ns { - gap: var(--spacing-0); - } - .g1-ns { - gap: var(--spacing-1); - } - .g2-ns { - gap: var(--spacing-2); - } - .g3-ns { - gap: var(--spacing-3); - } - .g4-ns { - gap: var(--spacing-4); - } - .g5-ns { - gap: var(--spacing-5); - } - .w6-ns { - width: var(--width-6); - } - .w7-ns { - width: var(--width-7); - } - .w8-ns { - width: var(--width-8); - } -} -@media screen and (min-width: 35em) and (max-width: 60em) { - .bg1-m { - background-color: var(--c1); - } - .bg2-m { - background-color: var(--c2); - } - .bg3-m { - background-color: var(--c3); - } - .bg4-m { - background-color: var(--c4); - } - .bg5-m { - background-color: var(--c5); - } - .g0-m { - gap: var(--spacing-0); - } - .g1-m { - gap: var(--spacing-1); - } - .g2-m { - gap: var(--spacing-2); - } - .g3-m { - gap: var(--spacing-3); - } - .g4-m { - gap: var(--spacing-4); - } - .g5-m { - gap: var(--spacing-5); - } - .w6-m { - width: var(--width-6); - } - .w7-m { - width: var(--width-7); - } - .w8-m { - width: var(--width-8); - } -} -@media screen and (min-width: 60em) { - .bg1-l { - background-color: var(--c1); - } - .bg2-l { - background-color: var(--c2); - } - .bg3-l { - background-color: var(--c3); - } - .bg4-l { - background-color: var(--c4); - } - .bg5-l { - background-color: var(--c5); - } - .g0-l { - gap: var(--spacing-0); - } - .g1-l { - gap: var(--spacing-1); - } - .g2-l { - gap: var(--spacing-2); - } - .g3-l { - gap: var(--spacing-3); - } - .g4-l { - gap: var(--spacing-4); - } - .g5-l { - gap: var(--spacing-5); - } - .w6-l { - width: var(--width-6); - } - .w7-l { - width: var(--width-7); - } - .w8-l { - width: var(--width-8); - } -} -.mw-site { - max-width: var(--site-width); -} -.grid { - display: grid; -} -.grid-2 { - grid: auto-flow / 1fr 1fr; -} - -/* src/rawdata/css/hsf/icons.css */ -.svg-mask { - display: inline-block; - -webkit-mask-image: var(--icon-url); - mask-image: var(--icon-url); - -webkit-mask-position: center; - mask-position: center; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-size: contain; - mask-size: contain; -} -.icon-mail { - --icon-url: url(/public/icons/mail.svg); -} -.icon-twitch { - --icon-url: url(/public/icons/twitch.svg); -} -.icon-vimeo { - --icon-url: url(/public/icons/vimeo.svg); -} -.icon-discord { - --icon-url: url(/public/icons/discord.svg); -} - -/* src/rawdata/css/hsf/btn.css */ -.btn { - display: inline-block; - background-color: var(--color); - color: var(--c1); - padding: var(--spacing-2) var(--spacing-3); -} - -/* src/rawdata/css/hsf/post.css */ -.post-content { - line-height: 1.4; -} -.post-content *:first-child { - margin-top: 0; -} -.post-content *:last-child { - margin-bottom: 0; -} -.post-content h1 { - font-size: 1.5rem; -} -.post-content h2 { - font-size: 1.25rem; -} -.post-content h3 { - font-size: 1rem; -} -.post-content h4 { - font-size: 0.8rem; -} -.post-content h5 { - font-size: 0.65rem; -} -.post-content h1, -.post-content h2, -.post-content h3, -.post-content h4, -.post-content h5 { - line-height: 1.25; - margin-top: 0.5em; - margin-bottom: 0.5em; -} -.post-content li:not(:last-child) { - margin-bottom: 0.6em; -} -.post-content li p { - margin-top: 0.6em; - margin-bottom: 0.6em; -} -.post-content img { - max-width: 100%; -} -.post-content hr { - margin-left: var(--spacing-3); - margin-right: var(--spacing-3); -} -.post-content div.code { - border-color: var(--dimmer-color); - max-width: 100%; - max-height: 20em; - max-height: 80vh; - overflow: auto; - -moz-tab-size: 4; - tab-size: 4; - border-width: 1px; - border-style: solid; -} -.post-content div + br, -.post-content blockquote + br, -.post-content ul + br, -.post-content ol + br { - display: none; -} -.post-content table { - margin-top: var(--spacing-2); - margin-bottom: var(--spacing-2); -} -.post-content th, -.post-content td { - padding: var(--spacing-1) var(--spacing-2); - border: 1px solid var(--border-color); -} -.post-content code { - background-color: var(--c1); - padding: .2em 0; - white-space: nowrap; -} -.post-content code::before, -.post-content code::after { - content: "\a0"; - letter-spacing: -0.2em; - display: inline-block; -} -.post-content pre > code, -.post-content pre.hmn-code { - background-color: var(--c1); - padding: 0.7em; - overflow-x: auto; - max-height: 80vh; -} -.post-content blockquote { - margin-left: var(--spacing-2); - padding-left: var(--spacing-2); - margin-right: 0; - border-left-style: solid; - border-left-width: 1px; -} -.post-content blockquote .quotewho { - line-height: 2em; -} -.post-content blockquote .quotewho::after { - content: " said:"; -} -.post-content figure { - margin: var(--spacing-2) 0; - padding: var(--spacing-2) var(--spacing-2) 0; - background-color: var(--dim-background); - border-radius: var(--border-radius-3); - display: flex; - flex-direction: column; -} -@media screen and (min-width: 35em) { - .post-content figure { - margin-top: var(--spacing-3); - margin-bottom: var(--spacing-3); - padding: var(--spacing-3) var(--spacing-3) 0; - } -} -.post-content figure figcaption { - margin: var(--spacing-1) 0; -} -@media screen and (min-width: 35em) { - .post-content figure figcaption { - margin: var(--spacing-2) 0; - } -} -.post-content table { - border-collapse: collapse; - table-layout: fixed; -} -.post-content td { - vertical-align: baseline; -} -.post-content .spoiler { - border-color: var(--c1); - border-width: 1px; - border-style: dashed; - color: transparent; -} -.post-content .spoiler::selection { - color: white; - background-color: black; -} - -/* src/rawdata/css/hsf/hsf.css */ -/*! TACHYONS v4.12.0 | http://tachyons.io */ -/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */ diff --git a/public/js/countdown.js b/public/js/countdown.js index ba9f2f66..9d956132 100644 --- a/public/js/countdown.js +++ b/public/js/countdown.js @@ -1,33 +1,40 @@ -document.addEventListener("DOMContentLoaded", () => { - for (const countdown of document.querySelectorAll(".countdown")) { - const deadline = countdown.getAttribute("data-deadline"); - const deadlineDate = new Date(parseInt(deadline, 10) * 1000); - - function updateCountdown() { - const remainingMs = deadlineDate.getTime() - new Date().getTime(); - const remainingMinutes = remainingMs / 1000 / 60; - const remainingHours = remainingMinutes / 60; - const remainingDays = remainingHours / 24; // no daylight savings transitions during the jam mmkay - - let str = "imminently"; - if (remainingMinutes < 60) { - str = `in ${Math.ceil(remainingMinutes)} ${ - remainingMinutes === 1 ? "minute" : "minutes" - }`; - } else if (remainingHours < 24) { - str = `in ${Math.ceil(remainingHours)} ${ - remainingHours === 1 ? "hour" : "hours" - }`; - } else { - str = `in ${Math.ceil(remainingDays)} ${ - remainingDays === 1 ? "day" : "days" - }`; - } - - countdown.innerText = str; - } - - updateCountdown(); - setInterval(updateCountdown, 1000 * 60); - } -}); +// src/rawdata/js/lib/utils.ts +function assert(cond, msg, soft = false) { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } +} +function must(val, msg) { + assert(val, msg); + return val; +} + +// src/rawdata/js/countdown.ts +document.addEventListener("DOMContentLoaded", () => { + for (const countdown of document.querySelectorAll(".countdown")) { + let updateCountdown2 = function() { + const remainingMs = deadlineDate.getTime() - (/* @__PURE__ */ new Date()).getTime(); + const remainingMinutes = remainingMs / 1e3 / 60; + const remainingHours = remainingMinutes / 60; + const remainingDays = remainingHours / 24; + let str = "imminently"; + if (remainingMinutes < 60) { + str = `in ${Math.ceil(remainingMinutes)} ${remainingMinutes === 1 ? "minute" : "minutes"}`; + } else if (remainingHours < 24) { + str = `in ${Math.ceil(remainingHours)} ${remainingHours === 1 ? "hour" : "hours"}`; + } else { + str = `in ${Math.ceil(remainingDays)} ${remainingDays === 1 ? "day" : "days"}`; + } + countdown.innerText = str; + }; + var updateCountdown = updateCountdown2; + const deadline = must(countdown.getAttribute("data-deadline")); + const deadlineDate = new Date(parseInt(deadline, 10) * 1e3); + updateCountdown2(); + setInterval(updateCountdown2, 1e3 * 60); + } +}); diff --git a/public/js/editor.js b/public/js/editor.js new file mode 100644 index 00000000..766333cb --- /dev/null +++ b/public/js/editor.js @@ -0,0 +1,463 @@ +// src/rawdata/js/lib/markdown_previews.ts +var previewWorker = new Worker("/assets/markdown_worker.js"); +function autosaveContent({ + inputEl, + storageKey +}) { + const storagePrefix = "saved-content"; + const aWeekAgo = (/* @__PURE__ */ new Date()).getTime() - 7 * 24 * 60 * 60 * 1e3; + for (const key in window.localStorage) { + if (!window.localStorage.hasOwnProperty(key)) { + continue; + } + if (key.startsWith(storagePrefix)) { + try { + const { when } = JSON.parse(window.localStorage.getItem(key)); + if (when <= aWeekAgo) { + window.localStorage.removeItem(key); + } + } catch (e) { + console.error(e); + } + } + } + const storageKeyFull = `${storagePrefix}/${storageKey}`; + const storedContents = window.localStorage.getItem(storageKeyFull); + if (storedContents && !inputEl.value) { + try { + const { contents } = JSON.parse(storedContents); + inputEl.value = contents; + } catch (e) { + console.error(e); + } + } + function updateContentCache() { + window.localStorage.setItem(storageKeyFull, JSON.stringify({ + when: (/* @__PURE__ */ new Date()).getTime(), + contents: inputEl.value + })); + } + inputEl.addEventListener("input", () => updateContentCache()); + return { + clear() { + window.localStorage.removeItem(storageKeyFull); + } + }; +} +var markdownIds = []; +function initLiveMarkdown({ + inputEl, + previewEl, + parserName +}) { + if (!parserName) { + parserName = "parseMarkdown"; + } + if (markdownIds.includes(inputEl.id)) { + console.warn(`Multiple elements with ID "${inputEl.id}" are being used for Markdown. Results will be very confusing!`); + } + markdownIds.push(inputEl.id); + previewWorker.onmessage = ({ data }) => { + const { elementID, html } = data; + if (elementID === inputEl.id) { + previewEl.innerHTML = html; + MathJax.typeset?.(); + } + }; + function doMarkdown() { + previewWorker.postMessage({ + elementID: inputEl.id, + markdown: inputEl.value, + parserName + }); + } + doMarkdown(); + inputEl.addEventListener("input", () => doMarkdown()); + return doMarkdown; +} + +// src/rawdata/js/lib/base64.ts +function uint6ToB64(nUint6) { + return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 ? nUint6 + 71 : nUint6 < 62 ? nUint6 - 4 : nUint6 === 62 ? 43 : nUint6 === 63 ? 47 : 65; +} +function base64EncArr(aBytes) { + var nMod3 = 2, sB64Enc = ""; + for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + if (nIdx > 0 && nIdx * 4 / 3 % 76 === 0) { + sB64Enc += "\r\n"; + } + nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); + nUint24 = 0; + } + } + return sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? "" : nMod3 === 1 ? "=" : "=="); +} +function strToUTF8Arr(sDOMStr) { + var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; + for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) { + nChr = sDOMStr.charCodeAt(nMapIdx); + nArrLen += nChr < 128 ? 1 : nChr < 2048 ? 2 : nChr < 65536 ? 3 : nChr < 2097152 ? 4 : nChr < 67108864 ? 5 : 6; + } + aBytes = new Uint8Array(nArrLen); + for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) { + nChr = sDOMStr.charCodeAt(nChrIdx); + if (nChr < 128) { + aBytes[nIdx++] = nChr; + } else if (nChr < 2048) { + aBytes[nIdx++] = 192 + (nChr >>> 6); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 65536) { + aBytes[nIdx++] = 224 + (nChr >>> 12); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 2097152) { + aBytes[nIdx++] = 240 + (nChr >>> 18); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 67108864) { + aBytes[nIdx++] = 248 + (nChr >>> 24); + aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else { + aBytes[nIdx++] = 252 + (nChr >>> 30); + aBytes[nIdx++] = 128 + (nChr >>> 24 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } + } + return aBytes; +} + +// src/rawdata/js/lib/utils.ts +function assert(cond, msg, soft = false) { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } +} +function must(val, msg) { + assert(val, msg); + return val; +} + +// src/rawdata/js/lib/markdown_upload.ts +function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown, maxFileSize, uploadUrl) { + const submitTexts = Array.from(eSubmits).map((e) => e.value); + const uploadProgress = must(eUploadBar.querySelector(".progress")); + const uploadProgressText = must(eUploadBar.querySelector(".progress_text")); + const uploadProgressBar = must(eUploadBar.querySelector(".progress_bar")); + const uploadProgressBarFill = must(eUploadBar.querySelector(".progress_bar > div")); + let fileCounter = 0; + let enterCounter = 0; + let uploadQueue = []; + let currentUpload = null; + let currentXhr = null; + let currentBatchSize = 0; + let currentBatchDone = 0; + eFileInput.addEventListener("change", () => { + if (eFileInput.files.length > 0) { + importUserFiles(eFileInput.files); + } + }); + eText.addEventListener("dragover", (ev) => { + let effect = "none"; + for (let i = 0; i < ev.dataTransfer.items.length; ++i) { + if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { + effect = "copy"; + break; + } + } + ev.dataTransfer.dropEffect = effect; + ev.preventDefault(); + }); + eText.addEventListener("dragenter", function(ev) { + enterCounter++; + let droppable = false; + for (let i = 0; i < ev.dataTransfer.items.length; ++i) { + if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { + droppable = true; + break; + } + } + if (droppable) { + eText.classList.add("drop"); + } + }); + eText.addEventListener("dragleave", function(ev) { + enterCounter--; + if (enterCounter == 0) { + eText.classList.remove("drop"); + } + }); + function makeUploadString(uploadNumber, filename) { + return `Uploading file #${uploadNumber}: \`${filename}\`...`; + } + eText.addEventListener("drop", (ev) => { + enterCounter = 0; + eText.classList.remove("drop"); + if (ev.dataTransfer && ev.dataTransfer.files) { + importUserFiles(ev.dataTransfer.files); + } + ev.preventDefault(); + }); + eText.addEventListener("paste", (ev) => { + const files = ev.clipboardData?.files; + if (files?.length ?? 0 > 0) { + importUserFiles(files); + ev.preventDefault(); + } + }); + function importUserFiles(files) { + let items = []; + for (let i = 0; i < files.length; ++i) { + let f = files[i]; + if (f.size < maxFileSize) { + items.push({ file: f, error: null }); + } else { + items.push({ file: null, error: `\`${f.name}\` is too big! Max size is ${maxFileSize} but the file is ${f.size}.` }); + } + } + let cursorStart = eText.selectionStart; + let cursorEnd = eText.selectionEnd; + let toInsert = ""; + let linesToCursor = eText.value.substr(0, cursorStart).split("\n"); + let cursorLine = linesToCursor[linesToCursor.length - 1].trim(); + if (cursorLine.length > 0) { + toInsert = "\n\n"; + } + for (const item of items) { + if (item.file) { + fileCounter++; + toInsert += makeUploadString(fileCounter, item.file.name) + "\n\n"; + queueUpload(fileCounter, item.file); + } else { + toInsert += `${item.error} + +`; + } + } + eText.value = eText.value.substring(0, cursorStart) + toInsert + eText.value.substring(cursorEnd, eText.value.length); + eText.selectionStart = cursorStart + toInsert.length; + eText.selectionEnd = eText.selectionStart; + doMarkdown(); + uploadNext(); + } + function replaceUploadString(upload, newString) { + let cursorStart = eText.selectionStart; + let cursorEnd = eText.selectionEnd; + let uploadString = makeUploadString(upload.uploadNumber, upload.file.name); + let insertIndex = eText.value.indexOf(uploadString); + if (insertIndex === -1) { + insertIndex = eText.value.length; + const newLines = newString.startsWith("\n\n") ? "" : "\n\n"; + eText.value = eText.value + newLines + newString; + } else { + eText.value = eText.value.replace(uploadString, newString); + } + const intersects = cursorStart < insertIndex + uploadString.length && insertIndex < cursorEnd; + const fullyInside = insertIndex <= cursorStart && cursorEnd <= insertIndex + uploadString.length; + if (fullyInside && cursorStart === cursorEnd || intersects && !fullyInside) { + eText.selectionStart = eText.selectionEnd = insertIndex + newString.length; + } else { + const difference = newString.length - uploadString.length; + eText.selectionStart = cursorStart >= insertIndex + uploadString.length ? cursorStart + difference : cursorStart; + eText.selectionEnd = cursorEnd >= insertIndex + uploadString.length ? cursorEnd + difference : cursorEnd; + } + doMarkdown(); + } + function replaceUploadStringError(upload) { + replaceUploadString(upload, `There was a problem uploading your file \`${upload.file.name}\`.`); + } + function queueUpload(uploadNumber, file) { + uploadQueue.push({ + uploadNumber, + file + }); + currentBatchSize++; + uploadProgressText.textContent = `Uploading files ${currentBatchDone + 1}/${currentBatchSize}`; + } + function uploadDone(ev) { + assert(currentXhr); + assert(currentUpload); + try { + if (currentXhr.status == 200 && currentXhr.response) { + if (currentXhr.response.url) { + let url = currentXhr.response.url; + let newString = `[${currentUpload.file.name}](${url})`; + if (currentXhr.response.mime.startsWith("image")) { + newString = "!" + newString; + } + replaceUploadString(currentUpload, newString); + } else if (currentXhr.response.error) { + replaceUploadString(currentUpload, `Upload failed for \`${currentUpload.file.name}\`: ${currentXhr.response.error}.`); + } else { + replaceUploadStringError(currentUpload); + } + } else { + replaceUploadStringError(currentUpload); + } + } catch (err) { + console.error(err); + replaceUploadStringError(currentUpload); + } + currentUpload = null; + currentXhr = null; + currentBatchDone++; + uploadNext(); + } + function updateUploadProgress(ev) { + if (ev.lengthComputable) { + let progress = ev.loaded / ev.total; + uploadProgressBarFill.style.width = Math.floor(progress * 100) + "%"; + } + } + function uploadNext() { + if (currentUpload == null) { + const next = uploadQueue.shift(); + if (next) { + uploadProgressText.textContent = `Uploading files ${currentBatchDone + 1}/${currentBatchSize}`; + eUploadBar.classList.add("uploading"); + uploadProgressBarFill.style.width = "0%"; + for (const e of eSubmits) { + e.disabled = true; + e.value = "Uploading files..."; + } + try { + let utf8Filename = strToUTF8Arr(next.file.name); + let base64Filename = base64EncArr(utf8Filename); + currentXhr = new XMLHttpRequest(); + currentXhr.upload.addEventListener("progress", updateUploadProgress); + currentXhr.open("POST", uploadUrl, true); + currentXhr.setRequestHeader("Hmn-Upload-Filename", base64Filename); + currentXhr.responseType = "json"; + currentXhr.addEventListener("loadend", uploadDone); + currentXhr.send(next.file); + currentUpload = next; + } catch (err) { + replaceUploadStringError(next); + console.error(err); + uploadNext(); + } + } else { + for (const [i, e] of Array.from(eSubmits).entries()) { + e.disabled = false; + e.value = submitTexts[i]; + } + eUploadBar.classList.remove("uploading"); + currentBatchSize = 0; + currentBatchDone = 0; + } + } + } +} + +// src/rawdata/js/editor.ts +var form = must(document.querySelector("#form")); +var titleField = document.querySelector("#title"); +var textField = must(document.querySelector("#editor")); +var preview = must(document.querySelector("#preview")); +function init({ maxFileSize, uploadUrl, markdownParser }) { + const clearFuncs = []; + if (titleField) { + const { clear: clearTitle } = autosaveContent({ + inputEl: titleField, + storageKey: `post-title/${window.location.host}${window.location.pathname}` + }); + clearFuncs.push(clearTitle); + } + const { clear: clearContent } = autosaveContent({ + inputEl: textField, + storageKey: `post-content/${window.location.host}${window.location.pathname}` + }); + clearFuncs.push(clearContent); + form.addEventListener("submit", (e) => { + for (const clear of clearFuncs) { + clear(); + } + }); + const doMarkdown = initLiveMarkdown({ + inputEl: textField, + previewEl: preview, + parserName: markdownParser + }); + setupMarkdownUpload( + document.querySelectorAll("#form input[type=submit]"), + must(document.querySelector("#file_input")), + must(document.querySelector(".upload_bar")), + textField, + doMarkdown, + maxFileSize, + uploadUrl + ); + textField.addEventListener("keydown", function(ev) { + if (ev.ctrlKey) { + const key = ev.key.toLowerCase(); + const start = textField.selectionStart; + const end = textField.selectionEnd; + const selected = textField.value.substring(start, end); + if (key === "k") { + ev.preventDefault(); + if (selected.length) { + textField.value = textField.value.substring(0, start) + "[" + selected + "]()" + textField.value.substring(end); + textField.selectionStart = textField.selectionEnd = end + 3; + } else { + textField.value = textField.value.substring(0, start) + "[](url)" + textField.value.substring(end); + textField.selectionStart = textField.selectionEnd = start + 1; + } + textField.focus(); + doMarkdown(); + } else if (key === "b") { + ev.preventDefault(); + if (selected.length) { + textField.value = textField.value.substring(0, start) + "**" + selected + "**" + textField.value.substring(end); + textField.selectionStart = textField.selectionEnd = end + 2; + } else { + textField.value = textField.value.substring(0, start) + "****" + textField.value.substring(end); + textField.selectionStart = textField.selectionEnd = start + 2; + } + textField.focus(); + doMarkdown(); + } else if (key === "i") { + ev.preventDefault(); + if (selected.length) { + textField.value = textField.value.substring(0, start) + "*" + selected + "*" + textField.value.substring(end); + textField.selectionStart = textField.selectionEnd = end + 1; + } else { + textField.value = textField.value.substring(0, start) + "**" + textField.value.substring(end); + textField.selectionStart = textField.selectionEnd = start + 1; + } + textField.focus(); + doMarkdown(); + } else if (key === "s") { + ev.preventDefault(); + } + } + }); + textField.addEventListener("paste", (ev) => { + const clipboard = must(ev.clipboardData); + const pastedText = clipboard.getData("text"); + const start = textField.selectionStart; + const end = textField.selectionEnd; + if (pastedText && (pastedText.startsWith("https://") || pastedText.startsWith("http://")) && start !== end) { + ev.preventDefault(); + textField.value = textField.value.substring(0, start) + "[" + textField.value.substring(start, end) + "](" + pastedText + ")" + textField.value.substring(end); + textField.selectionStart = textField.selectionEnd = end + 4 + pastedText.length; + textField.focus(); + doMarkdown(); + } + }); +} +export { + init +}; diff --git a/public/js/hilbert.js b/public/js/hilbert.js index 607e8275..e8731e8d 100644 --- a/public/js/hilbert.js +++ b/public/js/hilbert.js @@ -1,44 +1,40 @@ -function hilbertPath(iterations, step = 10) { - let l = "A"; - for (let i = 0; i < iterations; i++) { - l = l.replaceAll(/[AB]/g, c => c === "A" ? "+BF-AFA-FB+" : "-AF+BFB+FA-"); - } - - const dirs = [`h${step}`, `v${step}`, `h-${step}`, `v-${step}`]; - let dir = 0; - let p = `M${step} ${step}`; - for (const char of l) { - if (char === "F") { - p += dirs[dir]; - } else if (char === "+") { - dir += 1; - } else if (char === "-") { - dir -= 1; - } - dir = (dir + 4) % 4; - } - - return p; -} - -function hilbertWidth(iterations, step = 10) { - return step * (2**iterations - 1 + 2); -} - -function createHilbertCurves() { - const containers = document.querySelectorAll(".hilbert"); - for (const container of containers) { - const filler = document.createElement("div"); - filler.classList.add("hilbert-filler"); - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); - const p = hilbertPath(7, 10), width = hilbertWidth(7, 10); - path.setAttribute("d", p); - svg.setAttribute("viewBox", `0 0 ${width} ${width}`); - svg.appendChild(path); - filler.appendChild(svg); - container.appendChild(filler); - } -} - -document.addEventListener("DOMContentLoaded", () => createHilbertCurves()); +// src/rawdata/js/hilbert.ts +function hilbertPath(iterations, step = 10) { + let l = "A"; + for (let i = 0; i < iterations; i++) { + l = l.replaceAll(/[AB]/g, (c) => c === "A" ? "+BF-AFA-FB+" : "-AF+BFB+FA-"); + } + const dirs = [`h${step}`, `v${step}`, `h-${step}`, `v-${step}`]; + let dir = 0; + let p = `M${step} ${step}`; + for (const char of l) { + if (char === "F") { + p += dirs[dir]; + } else if (char === "+") { + dir += 1; + } else if (char === "-") { + dir -= 1; + } + dir = (dir + 4) % 4; + } + return p; +} +function hilbertWidth(iterations, step = 10) { + return step * (2 ** iterations - 1 + 2); +} +function createHilbertCurves() { + const containers = document.querySelectorAll(".hilbert"); + for (const container of containers) { + const filler = document.createElement("div"); + filler.classList.add("hilbert-filler"); + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + const p = hilbertPath(7, 10), width = hilbertWidth(7, 10); + path.setAttribute("d", p); + svg.setAttribute("viewBox", `0 0 ${width} ${width}`); + svg.appendChild(path); + filler.appendChild(svg); + container.appendChild(filler); + } +} +document.addEventListener("DOMContentLoaded", () => createHilbertCurves()); diff --git a/public/js/landing.js b/public/js/landing.js new file mode 100644 index 00000000..7e69179e --- /dev/null +++ b/public/js/landing.js @@ -0,0 +1,89 @@ +// src/rawdata/js/lib/tabs.ts +function initTabs(container, { + initialTab, + onSelect = () => { + } +} = {}) { + const buttons = Array.from(container.querySelectorAll("[data-tab-button]")); + const tabs = Array.from(container.querySelectorAll("[data-tab]")); + const firstTab = tabs[0].getAttribute("data-tab"); + function selectTab(name, { sendEvent = true } = {}) { + if (!container.querySelector(`[data-tab="${name}"]`)) { + console.warn("no tab found with name", name); + return selectTab(firstTab, { sendEvent }); + } + for (const tab of tabs) { + tab.hidden = tab.getAttribute("data-tab") !== name; + } + for (const button of buttons) { + button.classList.toggle("tab-button-active", button.getAttribute("data-tab-button") === name); + } + if (sendEvent) { + onSelect(name); + } + } + selectTab(initialTab || firstTab, { sendEvent: false }); + for (const button of buttons) { + button.addEventListener("click", () => { + selectTab(button.getAttribute("data-tab-button")); + }); + } + return { + selectTab + }; +} +function initHashTabs(container, { + initialTab +} = {}) { + const res = initTabs(container, { + initialTab: initialTab ?? document.location.hash.substring(1), + onSelect(name) { + document.location.hash = `#${name}`; + } + }); + const { selectTab } = res; + window.addEventListener("hashchange", (e) => { + const tab = new URL(e.newURL).hash.substring(1); + if (tab) { + selectTab(tab, { sendEvent: false }); + } + }); + return res; +} + +// src/rawdata/js/lib/utils.ts +function assert(cond, msg, soft = false) { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } +} +function must(val, msg) { + assert(val, msg); + return val; +} + +// src/rawdata/js/landing.ts +var noFollowing = document.querySelectorAll("[data-tab='following'] .timeline-item").length === 0; +initHashTabs(must(document.querySelector("#landing-tabs")), { + initialTab: document.location.hash.substring(1) || (noFollowing ? "featured" : void 0) +}); +var latestNews = must(document.querySelector("#latest_news")); +var latestNewsPostID = must(latestNews.getAttribute("data-id")); +var latestNewsClosedKey = "latest_news_closed"; +document.querySelector("#close-latest-news-button").addEventListener("click", (e) => { + e.preventDefault(); + localStorage.setItem(latestNewsClosedKey, latestNewsPostID); + hideLatestNewsIfClosedOrRead(); +}); +function hideLatestNewsIfClosedOrRead() { + const isUnread = latestNews.hasAttribute("data-unread"); + const closedID = localStorage.getItem(latestNewsClosedKey); + if (!isUnread || closedID === latestNewsPostID) { + latestNews.hidden = true; + } +} +hideLatestNewsIfClosedOrRead(); diff --git a/public/js/perfmon.js b/public/js/perfmon.js new file mode 100644 index 00000000..9393c6a2 --- /dev/null +++ b/public/js/perfmon.js @@ -0,0 +1,189 @@ +// src/rawdata/js/lib/utils.ts +function assert(cond, msg, soft = false) { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } +} + +// src/rawdata/js/lib/templates.ts +var templateElementCache = {}; +var templatePathCache = {}; +function getTemplateEl(id) { + if (!templateElementCache[id]) { + const el = document.getElementById(id); + assert(el, `no element with id ${id}`); + assert(el instanceof HTMLTemplateElement); + templateElementCache[id] = el; + } + return templateElementCache[id]; +} +function getTemplatePaths(id, rootNode) { + if (!templatePathCache[id]) { + let descend2 = function(path, el) { + for (var i = 0; i < el.children.length; ++i) { + var child = el.children[i]; + var childPath = path.concat([i]); + var tmplName = child.getAttribute("data-tmpl"); + if (tmplName) { + paths.push([tmplName, childPath]); + } + if (child.children.length > 0) { + descend2(childPath, child); + } + } + }; + var descend = descend2; + var paths = []; + paths.push(["root", []]); + descend2([], rootNode); + templatePathCache[id] = paths; + } + return templatePathCache[id]; +} +function collectElements(paths, rootElement) { + var result = {}; + for (var i = 0; i < paths.length; ++i) { + var path = paths[i]; + var current = rootElement; + for (var j = 0; j < path[1].length; ++j) { + current = current.children[path[1][j]]; + } + result[path[0]] = current; + } + return result; +} +function makeTemplateCloner(id) { + return function() { + var templateEl = getTemplateEl(id); + if (templateEl === null) { + throw new Error(`Couldn't find template with ID '${id}'`); + } + var root = templateEl.content.cloneNode(true); + var paths = getTemplatePaths(id, root); + var result = collectElements(paths, root); + return result; + }; +} + +// src/rawdata/js/perfmon.js +function init(perfRecordsJSON) { + const singleRowTemplate = makeTemplateCloner("single-record"); + const routeTemplate = makeTemplateCloner("route"); + const data = JSON.parse(perfRecordsJSON); + const recordsContainer = document.querySelector("#records"); + const routesContainer = document.querySelector("#routes"); + let routes = {}; + let recents = []; + for (let i = data.length - 1; i >= 0; --i) { + let record = data[i]; + if (recents.length < 100) { + recents.push(record); + } + if (!routes[record.Route]) { + routes[record.Route] = { + route: record.Route, + records: [] + }; + } + routes[record.Route].records.push(record); + } + function showRecords(records) { + recordsContainer.innerHTML = ""; + for (let i = 0; i < records.length; ++i) { + let getFlameRow2 = function(idx) { + while (flameRows.length <= idx) { + let flameRow = document.createElement("DIV"); + flameRow.classList.add("h1"); + flameRow.classList.add("relative"); + flameRows.push(flameRow); + flameGraph.appendChild(flameRow); + } + return flameRows[idx]; + }, placeFlameItems2 = function(parent, depth, maxDuration) { + let rowEl = getFlameRow2(depth); + for (let childIdx = 0; childIdx < parent.Children.length; ++childIdx) { + let child = parent.Children[childIdx]; + let item = document.createElement("DIV"); + let catCSS = child.Category.toLowerCase(); + item.classList.add("absolute", "h1", "overflow-hidden", "f7", "nowrap"); + item.style.color = `var(--${catCSS}-text)`; + item.style.backgroundColor = `var(--${catCSS}-bg, var(--c1))`; + item.textContent = `[${child.Category}] ${child.Description} | ${child.Duration / 1e3}ms`; + item.title = item.textContent; + item.style.width = child.Duration / maxDuration * 100 + "%"; + item.style.left = child.Offset / maxDuration * 100 + "%"; + rowEl.appendChild(item); + for (const checkpoint of child.Checkpoints) { + const el = document.createElement("div"); + el.title = `[${checkpoint.Category}] ${checkpoint.Description}`; + el.classList.add("absolute", "h1", "f7", "nowrap"); + el.style.backgroundColor = `#00000066`; + el.style.width = "2px"; + el.style.left = checkpoint.Offset / maxDuration * 100 + "%"; + rowEl.appendChild(el); + } + if (child.Children && child.Children.length > 0) { + placeFlameItems2(child, depth + 1, maxDuration); + } + } + }; + var getFlameRow = getFlameRow2, placeFlameItems = placeFlameItems2; + let record = records[i]; + let row = singleRowTemplate(); + row.route.textContent = record.Route; + row.duration.textContent = record.Duration / 1e3 + "ms"; + row.path.textContent = record.Path; + let flameGraph = row.flamegraph; + let flameRows = []; + if (record.Breakdown.Children) { + placeFlameItems2(record.Breakdown, 0, record.Breakdown.Duration); + } + recordsContainer.appendChild(row.row); + } + } + for (const key in routes) { + routes[key].records.sort(function(a, b) { + return b.Duration - a.Duration; + }); + } + let routesList = Object.values(routes); + routesList.sort(function(a, b) { + const medianA = a.records[Math.floor(a.records.length / 2)].Duration; + const medianB = b.records[Math.floor(b.records.length / 2)].Duration; + return medianB - medianA; + }); + let activeRoute = ""; + for (let i = 0; i < routesList.length; ++i) { + let r = routesList[i]; + let routeEl = routeTemplate(); + routeEl.name.textContent = r.route; + routeEl.hits.textContent = r.records.length; + routeEl.duration.textContent = r.records[Math.floor(r.records.length / 2)].Duration / 1e3 + "ms"; + routeEl.route.dataset.route = r.route; + routesContainer.appendChild(routeEl.route); + routesList[i].el = routeEl; + routeEl.route.addEventListener("click", function(ev) { + let el = ev.target.closest(".route"); + if (el.dataset.route == activeRoute) { + el.classList.remove("bg4"); + activeRoute = ""; + showRecords(recents); + } else { + for (let i2 = 0; i2 < routesList.length; ++i2) { + routesList[i2].el.route.classList.remove("bg4"); + } + activeRoute = el.dataset.route; + el.classList.add("bg4"); + showRecords(routes[activeRoute].records); + } + }); + } + showRecords(recents); +} +export { + init +}; diff --git a/public/js/project_edit.js b/public/js/project_edit.js new file mode 100644 index 00000000..04112c15 --- /dev/null +++ b/public/js/project_edit.js @@ -0,0 +1,1073 @@ +// src/rawdata/js/lib/utils.ts +function assert(cond, msg, soft = false) { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } +} +function must(val, msg) { + assert(val, msg); + return val; +} + +// src/rawdata/js/lib/templates.ts +var templateElementCache = {}; +var templatePathCache = {}; +function getTemplateEl(id) { + if (!templateElementCache[id]) { + const el = document.getElementById(id); + assert(el, `no element with id ${id}`); + assert(el instanceof HTMLTemplateElement); + templateElementCache[id] = el; + } + return templateElementCache[id]; +} +function getTemplatePaths(id, rootNode) { + if (!templatePathCache[id]) { + let descend2 = function(path, el) { + for (var i = 0; i < el.children.length; ++i) { + var child = el.children[i]; + var childPath = path.concat([i]); + var tmplName = child.getAttribute("data-tmpl"); + if (tmplName) { + paths.push([tmplName, childPath]); + } + if (child.children.length > 0) { + descend2(childPath, child); + } + } + }; + var descend = descend2; + var paths = []; + paths.push(["root", []]); + descend2([], rootNode); + templatePathCache[id] = paths; + } + return templatePathCache[id]; +} +function collectElements(paths, rootElement) { + var result = {}; + for (var i = 0; i < paths.length; ++i) { + var path = paths[i]; + var current = rootElement; + for (var j = 0; j < path[1].length; ++j) { + current = current.children[path[1][j]]; + } + result[path[0]] = current; + } + return result; +} +function makeTemplateCloner(id) { + return function() { + var templateEl = getTemplateEl(id); + if (templateEl === null) { + throw new Error(`Couldn't find template with ID '${id}'`); + } + var root = templateEl.content.cloneNode(true); + var paths = getTemplatePaths(id, root); + var result = collectElements(paths, root); + return result; + }; +} + +// src/rawdata/js/lib/image_selector.ts +var imageSelectorTemplate = makeTemplateCloner("image-selector"); +var ImageSelector = class { + // The current URL of the selector. (Will be an object URL if the image has + // not been submitted yet.) + url; + // The template content to be inserted into the DOM wherever you like. + root; + // + maxFileSize; + imageInput; + removeImageInput; + previewImage; + previewContainer; + resetLink; + removeLink; + filenameText; + errorEl; + defaultUrl; + originalUrl; + originalFilename; + onUpdate; + constructor(formName, maxFileSize, { + defaultUrl = "", + originalUrl = "", + originalFilename = "", + onUpdate = () => { + } + } = {}) { + const tmpl = imageSelectorTemplate(); + this.url = ""; + this.root = tmpl.root; + this.maxFileSize = maxFileSize; + this.imageInput = tmpl.inputImage; + this.removeImageInput = tmpl.inputRemove; + this.previewImage = tmpl.preview; + this.previewContainer = tmpl.previewContainer; + this.resetLink = tmpl.linkReset; + this.removeLink = tmpl.linkRemove; + this.filenameText = tmpl.filenameText; + this.errorEl = tmpl.errorMessage; + this.defaultUrl = defaultUrl; + this.originalUrl = originalUrl; + this.originalFilename = originalFilename; + this.onUpdate = onUpdate; + this.imageInput.name = formName; + this.imageInput.value = ""; + this.removeImageInput.name = `remove_${formName}`; + this.removeImageInput.value = ""; + this.setImageUrl( + this.originalUrl, + /*initial=*/ + true + ); + this.updatePreview(); + this.imageInput.addEventListener("change", (ev) => { + if (this.imageInput.files.length > 0) { + this.handleNewImageFile(this.imageInput.files[0]); + } + }); + this.resetLink.addEventListener("click", (ev) => { + this.resetImage(); + }); + if (this.removeLink) { + this.removeLink.addEventListener("click", (ev) => { + this.removeImage(); + }); + } + } + openImageInput() { + this.imageInput.click(); + } + setImageUrl(url, initial = false) { + this.url = url; + if (url) { + this.previewImage.style.display = "block"; + this.previewImage.src = url; + } else { + this.previewImage.style.display = "none"; + this.previewImage.removeAttribute("src"); + } + if (!initial) { + this.onUpdate(url); + } + } + setError(error) { + this.errorEl.textContent = error; + this.errorEl.hidden = !error; + this.imageInput.setCustomValidity(error); + this.imageInput.reportValidity(); + } + checkSizeLimit(size) { + if (size > this.maxFileSize) { + this.setError("File too big. Max file size is " + this.maxFileSize + " bytes."); + } else { + this.setError(""); + } + } + updatePreview(file = null) { + const showReset = this.originalUrl && this.originalUrl !== this.defaultUrl && this.originalUrl !== this.url; + const showRemove = this.url !== this.defaultUrl; + this.resetLink.hidden = !showReset; + this.removeLink.hidden = !showRemove; + if (this.url === this.originalUrl) { + this.filenameText.innerText = this.originalFilename; + } else { + this.filenameText.innerText = file ? file.name : ""; + } + this.previewContainer.hidden = !this.url; + } + handleNewImageFile(file) { + this.checkSizeLimit(file.size); + this.removeImageInput.value = ""; + this.setImageUrl(URL.createObjectURL(file)); + this.updatePreview(file); + } + removeImage() { + this.checkSizeLimit(0); + this.imageInput.value = ""; + this.removeImageInput.value = "true"; + this.setImageUrl(this.defaultUrl); + this.updatePreview(null); + } + resetImage() { + this.checkSizeLimit(0); + this.imageInput.value = ""; + this.removeImageInput.value = ""; + this.setImageUrl(this.originalUrl); + this.updatePreview(null); + } +}; + +// src/rawdata/js/lib/base64.ts +function uint6ToB64(nUint6) { + return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 ? nUint6 + 71 : nUint6 < 62 ? nUint6 - 4 : nUint6 === 62 ? 43 : nUint6 === 63 ? 47 : 65; +} +function base64EncArr(aBytes) { + var nMod3 = 2, sB64Enc = ""; + for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + if (nIdx > 0 && nIdx * 4 / 3 % 76 === 0) { + sB64Enc += "\r\n"; + } + nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); + nUint24 = 0; + } + } + return sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? "" : nMod3 === 1 ? "=" : "=="); +} +function strToUTF8Arr(sDOMStr) { + var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; + for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) { + nChr = sDOMStr.charCodeAt(nMapIdx); + nArrLen += nChr < 128 ? 1 : nChr < 2048 ? 2 : nChr < 65536 ? 3 : nChr < 2097152 ? 4 : nChr < 67108864 ? 5 : 6; + } + aBytes = new Uint8Array(nArrLen); + for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) { + nChr = sDOMStr.charCodeAt(nChrIdx); + if (nChr < 128) { + aBytes[nIdx++] = nChr; + } else if (nChr < 2048) { + aBytes[nIdx++] = 192 + (nChr >>> 6); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 65536) { + aBytes[nIdx++] = 224 + (nChr >>> 12); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 2097152) { + aBytes[nIdx++] = 240 + (nChr >>> 18); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 67108864) { + aBytes[nIdx++] = 248 + (nChr >>> 24); + aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else { + aBytes[nIdx++] = 252 + (nChr >>> 30); + aBytes[nIdx++] = 128 + (nChr >>> 24 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } + } + return aBytes; +} + +// src/rawdata/js/lib/markdown_upload.ts +function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown, maxFileSize, uploadUrl) { + const submitTexts = Array.from(eSubmits).map((e) => e.value); + const uploadProgress = must(eUploadBar.querySelector(".progress")); + const uploadProgressText = must(eUploadBar.querySelector(".progress_text")); + const uploadProgressBar = must(eUploadBar.querySelector(".progress_bar")); + const uploadProgressBarFill = must(eUploadBar.querySelector(".progress_bar > div")); + let fileCounter = 0; + let enterCounter = 0; + let uploadQueue = []; + let currentUpload = null; + let currentXhr = null; + let currentBatchSize = 0; + let currentBatchDone = 0; + eFileInput.addEventListener("change", () => { + if (eFileInput.files.length > 0) { + importUserFiles(eFileInput.files); + } + }); + eText.addEventListener("dragover", (ev) => { + let effect = "none"; + for (let i = 0; i < ev.dataTransfer.items.length; ++i) { + if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { + effect = "copy"; + break; + } + } + ev.dataTransfer.dropEffect = effect; + ev.preventDefault(); + }); + eText.addEventListener("dragenter", function(ev) { + enterCounter++; + let droppable = false; + for (let i = 0; i < ev.dataTransfer.items.length; ++i) { + if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { + droppable = true; + break; + } + } + if (droppable) { + eText.classList.add("drop"); + } + }); + eText.addEventListener("dragleave", function(ev) { + enterCounter--; + if (enterCounter == 0) { + eText.classList.remove("drop"); + } + }); + function makeUploadString(uploadNumber, filename) { + return `Uploading file #${uploadNumber}: \`${filename}\`...`; + } + eText.addEventListener("drop", (ev) => { + enterCounter = 0; + eText.classList.remove("drop"); + if (ev.dataTransfer && ev.dataTransfer.files) { + importUserFiles(ev.dataTransfer.files); + } + ev.preventDefault(); + }); + eText.addEventListener("paste", (ev) => { + const files = ev.clipboardData?.files; + if (files?.length ?? 0 > 0) { + importUserFiles(files); + ev.preventDefault(); + } + }); + function importUserFiles(files) { + let items = []; + for (let i = 0; i < files.length; ++i) { + let f = files[i]; + if (f.size < maxFileSize) { + items.push({ file: f, error: null }); + } else { + items.push({ file: null, error: `\`${f.name}\` is too big! Max size is ${maxFileSize} but the file is ${f.size}.` }); + } + } + let cursorStart = eText.selectionStart; + let cursorEnd = eText.selectionEnd; + let toInsert = ""; + let linesToCursor = eText.value.substr(0, cursorStart).split("\n"); + let cursorLine = linesToCursor[linesToCursor.length - 1].trim(); + if (cursorLine.length > 0) { + toInsert = "\n\n"; + } + for (const item of items) { + if (item.file) { + fileCounter++; + toInsert += makeUploadString(fileCounter, item.file.name) + "\n\n"; + queueUpload(fileCounter, item.file); + } else { + toInsert += `${item.error} + +`; + } + } + eText.value = eText.value.substring(0, cursorStart) + toInsert + eText.value.substring(cursorEnd, eText.value.length); + eText.selectionStart = cursorStart + toInsert.length; + eText.selectionEnd = eText.selectionStart; + doMarkdown(); + uploadNext(); + } + function replaceUploadString(upload, newString) { + let cursorStart = eText.selectionStart; + let cursorEnd = eText.selectionEnd; + let uploadString = makeUploadString(upload.uploadNumber, upload.file.name); + let insertIndex = eText.value.indexOf(uploadString); + if (insertIndex === -1) { + insertIndex = eText.value.length; + const newLines = newString.startsWith("\n\n") ? "" : "\n\n"; + eText.value = eText.value + newLines + newString; + } else { + eText.value = eText.value.replace(uploadString, newString); + } + const intersects = cursorStart < insertIndex + uploadString.length && insertIndex < cursorEnd; + const fullyInside = insertIndex <= cursorStart && cursorEnd <= insertIndex + uploadString.length; + if (fullyInside && cursorStart === cursorEnd || intersects && !fullyInside) { + eText.selectionStart = eText.selectionEnd = insertIndex + newString.length; + } else { + const difference = newString.length - uploadString.length; + eText.selectionStart = cursorStart >= insertIndex + uploadString.length ? cursorStart + difference : cursorStart; + eText.selectionEnd = cursorEnd >= insertIndex + uploadString.length ? cursorEnd + difference : cursorEnd; + } + doMarkdown(); + } + function replaceUploadStringError(upload) { + replaceUploadString(upload, `There was a problem uploading your file \`${upload.file.name}\`.`); + } + function queueUpload(uploadNumber, file) { + uploadQueue.push({ + uploadNumber, + file + }); + currentBatchSize++; + uploadProgressText.textContent = `Uploading files ${currentBatchDone + 1}/${currentBatchSize}`; + } + function uploadDone(ev) { + assert(currentXhr); + assert(currentUpload); + try { + if (currentXhr.status == 200 && currentXhr.response) { + if (currentXhr.response.url) { + let url = currentXhr.response.url; + let newString = `[${currentUpload.file.name}](${url})`; + if (currentXhr.response.mime.startsWith("image")) { + newString = "!" + newString; + } + replaceUploadString(currentUpload, newString); + } else if (currentXhr.response.error) { + replaceUploadString(currentUpload, `Upload failed for \`${currentUpload.file.name}\`: ${currentXhr.response.error}.`); + } else { + replaceUploadStringError(currentUpload); + } + } else { + replaceUploadStringError(currentUpload); + } + } catch (err) { + console.error(err); + replaceUploadStringError(currentUpload); + } + currentUpload = null; + currentXhr = null; + currentBatchDone++; + uploadNext(); + } + function updateUploadProgress(ev) { + if (ev.lengthComputable) { + let progress = ev.loaded / ev.total; + uploadProgressBarFill.style.width = Math.floor(progress * 100) + "%"; + } + } + function uploadNext() { + if (currentUpload == null) { + const next = uploadQueue.shift(); + if (next) { + uploadProgressText.textContent = `Uploading files ${currentBatchDone + 1}/${currentBatchSize}`; + eUploadBar.classList.add("uploading"); + uploadProgressBarFill.style.width = "0%"; + for (const e of eSubmits) { + e.disabled = true; + e.value = "Uploading files..."; + } + try { + let utf8Filename = strToUTF8Arr(next.file.name); + let base64Filename = base64EncArr(utf8Filename); + currentXhr = new XMLHttpRequest(); + currentXhr.upload.addEventListener("progress", updateUploadProgress); + currentXhr.open("POST", uploadUrl, true); + currentXhr.setRequestHeader("Hmn-Upload-Filename", base64Filename); + currentXhr.responseType = "json"; + currentXhr.addEventListener("loadend", uploadDone); + currentXhr.send(next.file); + currentUpload = next; + } catch (err) { + replaceUploadStringError(next); + console.error(err); + uploadNext(); + } + } else { + for (const [i, e] of Array.from(eSubmits).entries()) { + e.disabled = false; + e.value = submitTexts[i]; + } + eUploadBar.classList.remove("uploading"); + currentBatchSize = 0; + currentBatchDone = 0; + } + } + } +} + +// src/rawdata/js/lib/reorderable.js +function initReorderable(container, { + onReorder = (item) => { + } +} = {}) { + let dragItem = null; + let dragPointerId = null; + let dragItemStartY = 0; + let dragMouseStartY = 0; + const dummy = document.createElement("div"); + dummy.classList.add("reorderable-dummy"); + function startDrag(e) { + if (!e.isPrimary || e.button !== 0) { + return; + } + e.preventDefault(); + const item = e.target.closest(".reorderable-item"); + const top = item.offsetTop; + item.style.position = "absolute"; + item.style.top = `${top}px`; + item.classList.add("reorderable-dragging"); + dummy.style.height = `${item.offsetHeight}px`; + item.insertAdjacentElement("beforebegin", dummy); + document.body.classList.add("grabbing"); + dragItem = item; + dragPointerId = e.pointerId; + dragItemStartY = top; + dragMouseStartY = e.pageY; + container.setPointerCapture(e.pointerId); + container.addEventListener("pointermove", doDrag); + container.addEventListener("lostpointercapture", endDrag, { once: true }); + } + function doDrag(e) { + const delta = e.pageY - dragMouseStartY; + const top = dragItemStartY + delta; + const middle = top + dragItem.offsetHeight / 2; + const items = container.querySelectorAll(".reorderable-item"); + let closestItem = null; + let closestItemDist = Infinity; + let insertBefore = null; + for (const item of items) { + if (item === dragItem) { + continue; + } + const itemMiddle = item.offsetTop + item.offsetHeight / 2; + const dist = middle - itemMiddle; + if (Math.abs(dist) < closestItemDist) { + closestItem = item; + closestItemDist = Math.abs(dist); + insertBefore = dist < 0; + } + } + if (closestItem) { + let alreadyOrdered = true; + let n = closestItem; + while (true) { + if (insertBefore) { + n = n.previousSibling; + } else { + n = n.nextSibling; + } + if (!n) { + alreadyOrdered = false; + break; + } + if (n === dragItem) { + break; + } + if (n.classList?.contains("reorderable-item")) { + alreadyOrdered = false; + break; + } + } + if (!alreadyOrdered) { + closestItem.insertAdjacentElement(insertBefore ? "beforebegin" : "afterend", dummy); + dragItem.remove(); + dummy.insertAdjacentElement("beforebegin", dragItem); + onReorder(dragItem); + } + } + const maxTop = container.offsetHeight - dragItem.offsetHeight; + const newTop = Math.max(0, Math.min(maxTop, top)); + dragItem.style.top = `${newTop}px`; + } + function endDrag(e) { + container.removeEventListener("pointermove", doDrag); + dragItem.remove(); + dummy.insertAdjacentElement("beforebegin", dragItem); + dummy.remove(); + dragItem.style.position = null; + dragItem.style.top = null; + dragItem.classList.remove("reorderable-dragging"); + document.body.classList.remove("grabbing"); + onReorder(dragItem); + dragItem = null; + dragPointerId = null; + dragItemStartY = 0; + dragMouseStartY = 0; + } + return { + startDrag + }; +} + +// src/rawdata/js/lib/link_editor.ts +var linksContainer = must(document.querySelector("#links")); +var parentForm = must(linksContainer.closest("form")); +var addButton = must(document.querySelector("#link-editor-add-button")); +var primaryLinksTitle = must(linksContainer.querySelector(".primary-links")); +var secondaryLinksTitle = must(linksContainer.querySelector(".secondary-links")); +var linksJSONInput = must(document.querySelector("#links-json")); +var linkTemplate = makeTemplateCloner("link-editor-row"); +var emptySectionTemplate = makeTemplateCloner("link-editor-empty-section"); +var { startDrag: startLinkDrag } = initReorderable( + linksContainer, + { + onReorder(item) { + ensurePlaceholders(); + linksUpdated(); + } + } +); +function makeLink() { + const res = linkTemplate(); + res.nameInput.addEventListener("input", linkInput); + res.urlInput.addEventListener("input", linkInput); + res.grabHandle.addEventListener("pointerdown", startLinkDrag); + res.deleteButton.addEventListener("click", (e) => { + e.preventDefault(); + const link = must(e.target.closest(".link-editor-row")); + deleteLink(link); + }); + return res; +} +function ensurePlaceholders() { + for (const el of linksContainer.querySelectorAll(".link-placeholder")) { + el.remove(); + } + let numPrimary = 0, numSecondary = 0; + let primary = true; + for (const el of linksContainer.children) { + if (el === primaryLinksTitle) { + continue; + } + if (el === secondaryLinksTitle) { + primary = false; + continue; + } + if (el.classList.contains("reorderable-item")) { + if (primary) { + numPrimary += 1; + } else { + numSecondary += 1; + } + } + } + if (numPrimary === 0) { + primaryLinksTitle.insertAdjacentElement("afterend", emptySectionTemplate().rootElement); + } + if (numSecondary === 0) { + secondaryLinksTitle.insertAdjacentElement("afterend", emptySectionTemplate().rootElement); + } +} +function getLinkData() { + const links = []; + let primary = true; + for (const el of linksContainer.children) { + if (el === secondaryLinksTitle) { + primary = false; + continue; + } + if (el.classList.contains("link-editor-row")) { + const name = must(el.querySelector(".link-name")).value; + const url = must(el.querySelector(".link-url")).value; + if (!url) { + continue; + } + links.push({ name, url, primary }); + } + } + return links; +} +function updateLinksJSON() { + linksJSONInput.value = JSON.stringify(getLinkData()); +} +function linksUpdated() { + updateLinksJSON(); + window.dispatchEvent(new Event("linkedit")); +} +function addLink() { + secondaryLinksTitle.insertAdjacentElement("beforebegin", makeLink().rootElement); + ensurePlaceholders(); + linksUpdated(); +} +function deleteLink(row) { + row.remove(); + ensurePlaceholders(); + linksUpdated(); +} +function linkInput() { + linksUpdated(); +} +function initLinkEditor(initialLinks) { + for (const link of initialLinks) { + const l = makeLink(); + l.nameInput.value = link.name; + l.urlInput.value = link.url; + if (link.primary) { + secondaryLinksTitle.insertAdjacentElement("beforebegin", l.rootElement); + } else { + linksContainer.appendChild(l.rootElement); + } + } + ensurePlaceholders(); + addButton.addEventListener("click", (e) => { + e.preventDefault(); + addLink(); + }); + parentForm.addEventListener("submit", function() { + updateLinksJSON(); + }); +} + +// src/rawdata/js/lib/tabs.ts +function initTabs(container, { + initialTab, + onSelect = () => { + } +} = {}) { + const buttons = Array.from(container.querySelectorAll("[data-tab-button]")); + const tabs = Array.from(container.querySelectorAll("[data-tab]")); + const firstTab = tabs[0].getAttribute("data-tab"); + function selectTab(name, { sendEvent = true } = {}) { + if (!container.querySelector(`[data-tab="${name}"]`)) { + console.warn("no tab found with name", name); + return selectTab(firstTab, { sendEvent }); + } + for (const tab of tabs) { + tab.hidden = tab.getAttribute("data-tab") !== name; + } + for (const button of buttons) { + button.classList.toggle("tab-button-active", button.getAttribute("data-tab-button") === name); + } + if (sendEvent) { + onSelect(name); + } + } + selectTab(initialTab || firstTab, { sendEvent: false }); + for (const button of buttons) { + button.addEventListener("click", () => { + selectTab(button.getAttribute("data-tab-button")); + }); + } + return { + selectTab + }; +} +function initHashTabs(container, { + initialTab +} = {}) { + const res = initTabs(container, { + initialTab: initialTab ?? document.location.hash.substring(1), + onSelect(name) { + document.location.hash = `#${name}`; + } + }); + const { selectTab } = res; + window.addEventListener("hashchange", (e) => { + const tab = new URL(e.newURL).hash.substring(1); + if (tab) { + selectTab(tab, { sendEvent: false }); + } + }); + return res; +} + +// src/rawdata/js/lib/markdown_previews.ts +var previewWorker = new Worker("/assets/markdown_worker.js"); +function autosaveContent({ + inputEl, + storageKey +}) { + const storagePrefix = "saved-content"; + const aWeekAgo = (/* @__PURE__ */ new Date()).getTime() - 7 * 24 * 60 * 60 * 1e3; + for (const key in window.localStorage) { + if (!window.localStorage.hasOwnProperty(key)) { + continue; + } + if (key.startsWith(storagePrefix)) { + try { + const { when } = JSON.parse(window.localStorage.getItem(key)); + if (when <= aWeekAgo) { + window.localStorage.removeItem(key); + } + } catch (e) { + console.error(e); + } + } + } + const storageKeyFull = `${storagePrefix}/${storageKey}`; + const storedContents = window.localStorage.getItem(storageKeyFull); + if (storedContents && !inputEl.value) { + try { + const { contents } = JSON.parse(storedContents); + inputEl.value = contents; + } catch (e) { + console.error(e); + } + } + function updateContentCache() { + window.localStorage.setItem(storageKeyFull, JSON.stringify({ + when: (/* @__PURE__ */ new Date()).getTime(), + contents: inputEl.value + })); + } + inputEl.addEventListener("input", () => updateContentCache()); + return { + clear() { + window.localStorage.removeItem(storageKeyFull); + } + }; +} +var markdownIds = []; +function initLiveMarkdown({ + inputEl, + previewEl, + parserName +}) { + if (!parserName) { + parserName = "parseMarkdown"; + } + if (markdownIds.includes(inputEl.id)) { + console.warn(`Multiple elements with ID "${inputEl.id}" are being used for Markdown. Results will be very confusing!`); + } + markdownIds.push(inputEl.id); + previewWorker.onmessage = ({ data }) => { + const { elementID, html } = data; + if (elementID === inputEl.id) { + previewEl.innerHTML = html; + MathJax.typeset?.(); + } + }; + function doMarkdown() { + previewWorker.postMessage({ + elementID: inputEl.id, + markdown: inputEl.value, + parserName + }); + } + doMarkdown(); + inputEl.addEventListener("input", () => doMarkdown()); + return doMarkdown; +} + +// src/rawdata/js/project_edit.ts +function init({ + csrf, + projectName, + maxOwners, + logoMaxFileSize, + headerMaxFileSize, + textMaxFileSize, + editorUploadUrl, + initialLinks, + ownerCheckUrl, + logoUrl, + logoFilename, + headerImageUrl, + headerImageFilename +}) { + initHashTabs(document); + const projectForm = must(document.querySelector("#project_form")); + const tag = must(document.querySelector("#tag")); + const tagPreview = must(document.querySelector("#tag-preview")); + function updateTagPreview() { + tagPreview.innerText = tag.value || "[your tag]"; + } + updateTagPreview(); + tag.addEventListener("input", () => updateTagPreview()); + const description = must(document.querySelector("#full_description")); + const descPreview = must(document.querySelector("#desc_preview")); + const { clear: clearDescription } = autosaveContent({ + inputEl: description, + storageKey: `project-description/${projectName}` + }); + projectForm.addEventListener("submit", () => clearDescription()); + const doMarkdown = initLiveMarkdown({ inputEl: description, previewEl: descPreview }); + const OWNER_QUERY_STATE_IDLE = 0; + const OWNER_QUERY_STATE_QUERYING = 1; + let ownerQueryState = OWNER_QUERY_STATE_IDLE; + const addOwnerInput = must(document.querySelector("#owner_name")); + const addOwnerButton = must(document.querySelector("#owner_add")); + const ownersError = must(document.querySelector("#owners_error")); + const ownerList = must(document.querySelector("#owner_list")); + const ownerTemplate = makeTemplateCloner("owner_row"); + const ownerPreviewTemplate = makeTemplateCloner("owner_preview"); + const ownersPreviewContainer = must(document.querySelector("#owners_preview")); + addOwnerInput.addEventListener("keypress", function(ev) { + if (ev.which == 13) { + startAddOwner(); + ev.preventDefault(); + ev.stopPropagation(); + } + }); + addOwnerButton.addEventListener("click", function(ev) { + ev.preventDefault(); + startAddOwner(); + }); + function updateAddOwnerStyles() { + const numOwnerRows = document.querySelectorAll(".owner_row").length; + addOwnerInput.disabled = numOwnerRows >= maxOwners; + } + updateAddOwnerStyles(); + function startAddOwner() { + if (ownerQueryState == OWNER_QUERY_STATE_QUERYING) { + return; + } + let newOwner = addOwnerInput.value.trim().toLowerCase(); + if (newOwner.length == 0) { + return; + } + let ownerEls = ownerList.querySelectorAll(".owner_row input[name='owners']"); + for (let i = 0; i < ownerEls.length; ++i) { + let existingOwner = ownerEls[i].value.toLowerCase(); + if (newOwner == existingOwner) { + return; + } + } + ownersError.textContent = ""; + let xhr = new XMLHttpRequest(); + xhr.withCredentials = true; + xhr.open("POST", ownerCheckUrl); + xhr.responseType = "json"; + xhr.addEventListener("load", function(ev) { + let result = xhr.response; + if (result) { + if (result.found) { + addOwner(result.username, result.name, result.avatarUrl); + addOwnerInput.value = ""; + } else { + ownersError.textContent = "Username not found"; + } + } else { + ownersError.textContent = "There was an issue validating this username"; + } + setOwnerQueryState(OWNER_QUERY_STATE_IDLE); + if (document.activeElement == addOwnerButton) { + addOwnerInput.focus(); + } + }); + xhr.addEventListener("error", function(ev) { + ownersError.textContent = "There was an issue validating this username"; + setOwnerQueryState(OWNER_QUERY_STATE_IDLE); + }); + let data = new FormData(); + data.append(csrf.field, csrf.token); + data.append("username", newOwner); + xhr.send(data); + setOwnerQueryState(OWNER_QUERY_STATE_QUERYING); + } + function setOwnerQueryState(state) { + ownerQueryState = state; + const querying = ownerQueryState == OWNER_QUERY_STATE_QUERYING; + addOwnerInput.disabled = querying; + addOwnerButton.disabled = querying; + updateAddOwnerStyles(); + } + function addOwner(username, bestName, avatarUrl) { + let ownerEl = ownerTemplate(); + ownerEl.input.value = username; + ownerEl.name.textContent = bestName; + ownerEl.avatar.src = avatarUrl; + ownerList.appendChild(ownerEl.rootElement); + updateAddOwnerStyles(); + updateOwnersPreview(); + } + ownerList.addEventListener("click", (ev) => { + if (ev.target.closest(".remove_owner")) { + must(ev.target.closest(".owner_row")).remove(); + } + updateAddOwnerStyles(); + updateOwnersPreview(); + }); + function updateOwnersPreview() { + let ownerEls = ownerList.querySelectorAll(".owner_row"); + ownersPreviewContainer.innerHTML = ""; + for (let i = 0; i < ownerEls.length; ++i) { + let avatarUrl = must(ownerEls[i].querySelector("img")).src; + let name = must(ownerEls[i].querySelector("span")).textContent; + let previewEl = ownerPreviewTemplate(); + previewEl.avatar.src = avatarUrl; + previewEl.name.textContent = name; + ownersPreviewContainer.appendChild(previewEl.root); + } + } + updateOwnersPreview(); + const projectNameField = must(document.querySelector("#project_name")); + const descriptionField = must(document.querySelector("#description")); + const logoSelector = new ImageSelector( + "logo", + logoMaxFileSize, + { + originalUrl: logoUrl, + originalFilename: logoFilename, + onUpdate() { + updateCardPreview(); + } + } + ); + must(document.querySelector("#logo-upload-button")).addEventListener("click", (e) => { + e.preventDefault(); + logoSelector.openImageInput(); + }); + must(document.querySelector("#project-logo-placeholder")).replaceWith(logoSelector.root); + const headerSelector = new ImageSelector( + "header_image", + headerMaxFileSize, + { + originalUrl: headerImageUrl, + originalFilename: headerImageFilename, + onUpdate() { + updateCardPreview(); + } + } + ); + must(document.querySelector("#header-upload-button")).addEventListener("click", (e) => { + e.preventDefault(); + headerSelector.openImageInput(); + }); + must(document.querySelector("#header-image-placeholder")).replaceWith(headerSelector.root); + function updateCardPreview() { + const title = projectNameField.value || "Project Title"; + must(document.querySelector("#logo_preview img")).src = logoSelector.url; + must(document.querySelector("#logo_placeholder")).innerText = title[0].toUpperCase(); + must(document.querySelector("#logo_placeholder")).hidden = !!logoSelector.url; + must(document.querySelector("#header_img_preview")).style.backgroundImage = `url(${headerSelector.url})`; + must(document.querySelector("#flowsnake")).classList.toggle("dn", !!headerSelector.url); + must(document.querySelector("#name_preview")).innerText = title; + must(document.querySelector("#longdesc_title")).innerText = title; + must(document.querySelector("#blurb_preview")).innerText = descriptionField.value || "Project summary"; + } + updateCardPreview(); + projectNameField.addEventListener("input", updateCardPreview); + descriptionField.addEventListener("input", updateCardPreview); + setupMarkdownUpload( + document.querySelectorAll("#project_form input[type=submit]"), + must(document.querySelector("#file_input")), + must(document.querySelector(".upload_bar")), + description, + doMarkdown, + textMaxFileSize, + editorUploadUrl + ); + initLinkEditor(initialLinks); + const primaryLinkTemplate = makeTemplateCloner("primary_link"); + const secondaryLinkTemplate = makeTemplateCloner("secondary_link"); + function updateLinkPreviews() { + const linkData = getLinkData(); + const primaryPreview = must(document.querySelector("#primary_links_preview")); + const secondaryPreview = must(document.querySelector("#secondary_links_preview")); + primaryPreview.innerHTML = ""; + secondaryPreview.innerHTML = ""; + for (const link of linkData) { + if (link.primary) { + const l = primaryLinkTemplate(); + l.link.href = link.url; + l.name.innerText = link.name; + primaryPreview.appendChild(l.link); + } else { + let icon = "website"; + let title = ""; + if (parseKnownServicesForUrl) { + const guess = parseKnownServicesForUrl(link.url); + icon = guess.icon; + title = guess.service; + if (guess.username) { + title += ` (${guess.username})`; + } + } + const iconSVG = must(document.querySelector(`#link-icon-${icon}`)).innerHTML; + const l = secondaryLinkTemplate(); + l.link.href = link.url; + l.link.title = link.name || title; + l.link.innerHTML = iconSVG; + secondaryPreview.appendChild(l.link); + } + } + } + updateLinkPreviews(); + window.addEventListener("wasmready", () => updateLinkPreviews()); + window.addEventListener("linkedit", () => updateLinkPreviews()); +} +export { + init +}; diff --git a/public/js/showcase.js b/public/js/showcase.js index cb045f9a..88250092 100644 --- a/public/js/showcase.js +++ b/public/js/showcase.js @@ -1,249 +1,286 @@ -const TimelineMediaTypes = { - UNKNOWN: 0, - IMAGE: 1, - VIDEO: 2, - AUDIO: 3, - EMBED: 4, +// src/rawdata/js/lib/utils.ts +function assert(cond, msg, soft = false) { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } } -const showcaseItemTemplate = makeTemplateCloner("showcase_item"); -const modalTemplate = makeTemplateCloner("timeline_modal"); -const projectLinkTemplate = makeTemplateCloner("project_link"); +// src/rawdata/js/lib/templates.ts +var templateElementCache = {}; +var templatePathCache = {}; +function getTemplateEl(id) { + if (!templateElementCache[id]) { + const el = document.getElementById(id); + assert(el, `no element with id ${id}`); + assert(el instanceof HTMLTemplateElement); + templateElementCache[id] = el; + } + return templateElementCache[id]; +} +function getTemplatePaths(id, rootNode) { + if (!templatePathCache[id]) { + let descend2 = function(path, el) { + for (var i = 0; i < el.children.length; ++i) { + var child = el.children[i]; + var childPath = path.concat([i]); + var tmplName = child.getAttribute("data-tmpl"); + if (tmplName) { + paths.push([tmplName, childPath]); + } + if (child.children.length > 0) { + descend2(childPath, child); + } + } + }; + var descend = descend2; + var paths = []; + paths.push(["root", []]); + descend2([], rootNode); + templatePathCache[id] = paths; + } + return templatePathCache[id]; +} +function collectElements(paths, rootElement) { + var result = {}; + for (var i = 0; i < paths.length; ++i) { + var path = paths[i]; + var current = rootElement; + for (var j = 0; j < path[1].length; ++j) { + current = current.children[path[1][j]]; + } + result[path[0]] = current; + } + return result; +} +function makeTemplateCloner(id) { + return function() { + var templateEl = getTemplateEl(id); + if (templateEl === null) { + throw new Error(`Couldn't find template with ID '${id}'`); + } + var root = templateEl.content.cloneNode(true); + var paths = getTemplatePaths(id, root); + var result = collectElements(paths, root); + return result; + }; +} +function emptyElement(el) { + var newEl = el.cloneNode(false); + assert(el.parentElement); + el.parentElement.insertBefore(newEl, el); + el.parentElement.removeChild(el); + return newEl; +} +// src/rawdata/js/showcase.js +var TimelineMediaTypes = { + UNKNOWN: 0, + IMAGE: 1, + VIDEO: 2, + AUDIO: 3, + EMBED: 4 +}; +var showcaseItemTemplate = makeTemplateCloner("showcase_item"); +var modalTemplate = makeTemplateCloner("timeline_modal"); +var projectLinkTemplate = makeTemplateCloner("project_link"); function showcaseTimestamp(rawDate) { - const date = new Date(rawDate*1000); - return date.toLocaleDateString([], { 'dateStyle': 'long' }); + const date = new Date(rawDate * 1e3); + return date.toLocaleDateString([], { "dateStyle": "long" }); } - function doOnce(f) { - let did = false; - return () => { - if (!did) { - f(); - did = true; - } + let did = false; + return () => { + if (!did) { + f(); + did = true; } + }; } - -// TODO(redesign): Fix snippet editing, given that snippets are now presented very differently. function makeShowcaseItem(timelineItem) { - const timestamp = showcaseTimestamp(timelineItem.date); - - const itemEl = showcaseItemTemplate(); - itemEl.avatar.style.backgroundImage = `url('${timelineItem.owner_avatar}')`; - itemEl.username.textContent = timelineItem.owner_name; - itemEl.when.textContent = timestamp; - - let addThumbnailFunc = () => {}; - let createModalContentFunc = () => {}; - - switch (timelineItem.media_type) { + const timestamp = showcaseTimestamp(timelineItem.date); + const itemEl = showcaseItemTemplate(); + itemEl.avatar.style.backgroundImage = `url('${timelineItem.owner_avatar}')`; + itemEl.username.textContent = timelineItem.owner_name; + itemEl.when.textContent = timestamp; + let addThumbnailFunc = () => { + }; + let createModalContentFunc = () => { + }; + switch (timelineItem.media_type) { case TimelineMediaTypes.IMAGE: - addThumbnailFunc = () => { - itemEl.thumbnail.style.backgroundImage = `url('${timelineItem.thumbnail_url}')`; - }; - - createModalContentFunc = () => { - const modalImage = document.createElement('img'); - modalImage.src = timelineItem.asset_url; - modalImage.classList.add('mw-100', 'maxh-60vh'); - return modalImage; - }; - - break; + addThumbnailFunc = () => { + itemEl.thumbnail.style.backgroundImage = `url('${timelineItem.thumbnail_url}')`; + }; + createModalContentFunc = () => { + const modalImage = document.createElement("img"); + modalImage.src = timelineItem.asset_url; + modalImage.classList.add("mw-100", "maxh-60vh"); + return modalImage; + }; + break; case TimelineMediaTypes.VIDEO: - addThumbnailFunc = () => { - let thumbEl; - if (timelineItem.thumbnail_url) { - thumbEl = document.createElement('img'); - thumbEl.src = timelineItem.thumbnail_url; - } else { - thumbEl = document.createElement('video'); - thumbEl.src = timelineItem.asset_url; - thumbEl.controls = false; - thumbEl.preload = 'metadata'; - } - thumbEl.classList.add('h-100'); - itemEl.thumbnail.appendChild(thumbEl); - }; - - createModalContentFunc = () => { - const modalVideo = document.createElement('video'); - modalVideo.src = timelineItem.asset_url; - if (timelineItem.thumbnail_url) { - modalVideo.poster = timelineItem.thumbnail_url; - modalVideo.preload = 'none'; - } else { - modalVideo.preload = 'metadata'; - } - modalVideo.controls = true; - modalVideo.classList.add('mw-100', 'maxh-60vh'); - return modalVideo; - }; - - break; + addThumbnailFunc = () => { + let thumbEl; + if (timelineItem.thumbnail_url) { + thumbEl = document.createElement("img"); + thumbEl.src = timelineItem.thumbnail_url; + } else { + thumbEl = document.createElement("video"); + thumbEl.src = timelineItem.asset_url; + thumbEl.controls = false; + thumbEl.preload = "metadata"; + } + thumbEl.classList.add("h-100"); + itemEl.thumbnail.appendChild(thumbEl); + }; + createModalContentFunc = () => { + const modalVideo = document.createElement("video"); + modalVideo.src = timelineItem.asset_url; + if (timelineItem.thumbnail_url) { + modalVideo.poster = timelineItem.thumbnail_url; + modalVideo.preload = "none"; + } else { + modalVideo.preload = "metadata"; + } + modalVideo.controls = true; + modalVideo.classList.add("mw-100", "maxh-60vh"); + return modalVideo; + }; + break; case TimelineMediaTypes.AUDIO: - createModalContentFunc = () => { - const modalAudio = document.createElement('audio'); - modalAudio.src = timelineItem.asset_url; - modalAudio.controls = true; - modalAudio.preload = 'metadata'; - modalAudio.classList.add('w-70'); - return modalAudio; - }; - - break; - // TODO(ben): Other snippet types? - } - - let modalEl = null; - itemEl.container.addEventListener('click', function() { - if (!modalEl) { - modalEl = modalTemplate(); - modalEl.description.innerHTML = timelineItem.description; - modalEl.asset_container.appendChild(createModalContentFunc()); - - modalEl.avatar.src = timelineItem.owner_avatar; - modalEl.userLink.textContent = timelineItem.owner_name; - modalEl.userLink.href = timelineItem.owner_url; - modalEl.date.textContent = timestamp; - modalEl.date.setAttribute("href", timelineItem.snippet_url); - - if (timelineItem.projects.length === 0) { - modalEl.projects.remove(); - } else { - for (const proj of timelineItem.projects) { - const projectLink = projectLinkTemplate(); - projectLink.root.href = proj.url; - projectLink.logo.src = proj.logo; - projectLink.name.textContent = proj.name; - - modalEl.projects.appendChild(projectLink.root); - } - } - - if (timelineItem.discord_message_url != "") { - modalEl.discord_link.href = timelineItem.discord_message_url; - } else { - modalEl.discord_link.remove(); - } - - function close() { - modalEl.overlay.remove(); - } - modalEl.overlay.addEventListener('click', close); - modalEl.close.addEventListener('click', close); - modalEl.container.addEventListener('click', function(e) { - e.stopPropagation(); - }); + createModalContentFunc = () => { + const modalAudio = document.createElement("audio"); + modalAudio.src = timelineItem.asset_url; + modalAudio.controls = true; + modalAudio.preload = "metadata"; + modalAudio.classList.add("w-70"); + return modalAudio; + }; + break; + } + let modalEl = null; + itemEl.container.addEventListener("click", function() { + if (!modalEl) { + let close2 = function() { + modalEl.overlay.remove(); + }; + var close = close2; + modalEl = modalTemplate(); + modalEl.description.innerHTML = timelineItem.description; + modalEl.asset_container.appendChild(createModalContentFunc()); + modalEl.avatar.src = timelineItem.owner_avatar; + modalEl.userLink.textContent = timelineItem.owner_name; + modalEl.userLink.href = timelineItem.owner_url; + modalEl.date.textContent = timestamp; + modalEl.date.setAttribute("href", timelineItem.snippet_url); + if (timelineItem.projects.length === 0) { + modalEl.projects.remove(); + } else { + for (const proj of timelineItem.projects) { + const projectLink = projectLinkTemplate(); + projectLink.root.href = proj.url; + projectLink.logo.src = proj.logo; + projectLink.name.textContent = proj.name; + modalEl.projects.appendChild(projectLink.root); } - - document.body.appendChild(modalEl.overlay); - }); - - return [itemEl, doOnce(addThumbnailFunc)]; + } + if (timelineItem.discord_message_url != "") { + modalEl.discord_link.href = timelineItem.discord_message_url; + } else { + modalEl.discord_link.remove(); + } + modalEl.overlay.addEventListener("click", close2); + modalEl.close.addEventListener("click", close2); + modalEl.container.addEventListener("click", function(e) { + e.stopPropagation(); + }); + } + document.body.appendChild(modalEl.overlay); + }); + return [itemEl, doOnce(addThumbnailFunc)]; } - function initShowcaseContainer(container, items, rowHeight = 300, itemSpacing = 4) { - const addThumbnailFuncs = new Array(items.length); - - const itemElements = []; // array of arrays - for (let i = 0; i < items.length; i++) { - const item = items[i]; - - const [itemEl, addThumbnail] = makeShowcaseItem(item); - itemEl.container.setAttribute('data-index', i); - itemEl.container.setAttribute('data-date', item.date); - - addThumbnailFuncs[i] = addThumbnail; - - itemElements.push(itemEl.container); + const addThumbnailFuncs = new Array(items.length); + const itemElements = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const [itemEl, addThumbnail] = makeShowcaseItem(item); + itemEl.container.setAttribute("data-index", i); + itemEl.container.setAttribute("data-date", item.date); + addThumbnailFuncs[i] = addThumbnail; + itemElements.push(itemEl.container); + } + function layout() { + const width = container.getBoundingClientRect().width; + container = emptyElement(container); + function addRow(itemEls, rowWidth2, container2) { + const totalSpacing = itemSpacing * (itemEls.length - 1); + const scaleFactor = width / Math.max(rowWidth2, width); + const row = document.createElement("div"); + row.classList.add("flex"); + row.classList.toggle("justify-between", rowWidth2 >= width); + row.style.marginBottom = `${itemSpacing}px`; + for (const itemEl of itemEls) { + const index = parseInt(itemEl.getAttribute("data-index"), 10); + const item = items[index]; + const aspect = item.width / item.height; + const baseWidth = aspect * rowHeight * scaleFactor; + const actualWidth = baseWidth - totalSpacing / itemEls.length; + itemEl.style.width = `${actualWidth}px`; + itemEl.style.height = `${scaleFactor * rowHeight}px`; + itemEl.style.marginRight = `${itemSpacing}px`; + row.appendChild(itemEl); + } + container2.appendChild(row); } - - function layout() { - const width = container.getBoundingClientRect().width; - container = emptyElement(container); - - function addRow(itemEls, rowWidth, container) { - const totalSpacing = itemSpacing * (itemEls.length - 1); - const scaleFactor = (width / Math.max(rowWidth, width)); - - const row = document.createElement('div'); - row.classList.add('flex'); - row.classList.toggle('justify-between', rowWidth >= width); - row.style.marginBottom = `${itemSpacing}px`; - - for (const itemEl of itemEls) { - const index = parseInt(itemEl.getAttribute('data-index'), 10); - const item = items[index]; - - const aspect = item.width / item.height; - const baseWidth = (aspect * rowHeight) * scaleFactor; - const actualWidth = baseWidth - (totalSpacing / itemEls.length); - - itemEl.style.width = `${actualWidth}px`; - itemEl.style.height = `${scaleFactor * rowHeight}px`; - itemEl.style.marginRight = `${itemSpacing}px`; - - row.appendChild(itemEl); - } - - container.appendChild(row); - } - - let rowItemEls = []; - let rowWidth = 0; - - for (const itemEl of itemElements) { - const index = parseInt(itemEl.getAttribute('data-index'), 10); - const item = items[index]; - - const aspect = item.width / item.height; - rowWidth += aspect * rowHeight; - - rowItemEls.push(itemEl); - - if (rowWidth > width) { - addRow(rowItemEls, rowWidth, container); - - rowItemEls = []; - rowWidth = 0; - } - } - + let rowItemEls = []; + let rowWidth = 0; + for (const itemEl of itemElements) { + const index = parseInt(itemEl.getAttribute("data-index"), 10); + const item = items[index]; + const aspect = item.width / item.height; + rowWidth += aspect * rowHeight; + rowItemEls.push(itemEl); + if (rowWidth > width) { addRow(rowItemEls, rowWidth, container); + rowItemEls = []; + rowWidth = 0; + } } - - function tryLoadImages() { - const OFFSCREEN_THRESHOLD = 0; - - const rect = container.getBoundingClientRect(); - const offscreen = ( - rect.bottom < -OFFSCREEN_THRESHOLD - || rect.top > window.innerHeight + OFFSCREEN_THRESHOLD - ); - - if (!offscreen) { - const items = container.querySelectorAll('.showcase-item'); - for (const item of items) { - const i = parseInt(item.getAttribute('data-index'), 10); - addThumbnailFuncs[i](); - } - } + addRow(rowItemEls, rowWidth, container); + } + function tryLoadImages() { + const OFFSCREEN_THRESHOLD = 0; + const rect = container.getBoundingClientRect(); + const offscreen = rect.bottom < -OFFSCREEN_THRESHOLD || rect.top > window.innerHeight + OFFSCREEN_THRESHOLD; + if (!offscreen) { + const items2 = container.querySelectorAll(".showcase-item"); + for (const item of items2) { + const i = parseInt(item.getAttribute("data-index"), 10); + addThumbnailFuncs[i](); + } } - - window.addEventListener('DOMContentLoaded', () => { - layout(); - layout(); // scrollbars are fun!! - tryLoadImages(); - }) - - window.addEventListener('resize', () => { - layout(); - tryLoadImages(); - }); - - window.addEventListener('scroll', () => { - tryLoadImages(); - }); + } + window.addEventListener("DOMContentLoaded", () => { + layout(); + layout(); + tryLoadImages(); + }); + window.addEventListener("resize", () => { + layout(); + tryLoadImages(); + }); + window.addEventListener("scroll", () => { + tryLoadImages(); + }); } +export { + emptyElement, + initShowcaseContainer, + makeShowcaseItem, + makeTemplateCloner +}; diff --git a/public/js/snippetedit.js b/public/js/snippetedit.js index e740f639..85b19cf7 100644 --- a/public/js/snippetedit.js +++ b/public/js/snippetedit.js @@ -1,403 +1,473 @@ -const snippetEditTemplate = makeTemplateCloner("snippet-edit"); -const snippetEditProjectTemplate = makeTemplateCloner("snippet-edit-project"); - -function readableByteSize(numBytes) { - const scales = [ - " bytes", - "kb", - "mb", - "gb" - ]; - let scale = 0; - while (numBytes > 1024 && scale < scales.length - 1) { - numBytes /= 1024; - scale++; - } - return new Intl.NumberFormat([], { maximumFractionDigits: (scale > 0 ? 2 : 0) }).format(numBytes) + scales[scale]; +// src/rawdata/js/lib/utils.ts +function assert(cond, msg, soft = false) { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } +} +function must(val, msg) { + assert(val, msg); + return val; } -function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmentElement, projectIds, stickyProjectId, onDeleteRedirectUrl, snippetId, originalSnippetEl) { - let snippetEdit = snippetEditTemplate(); - let projectSelector = null; - let originalAttachment = null; - let originalText = text; - let attachmentChanged = false; - let hasAttachment = false; - snippetEdit.redirect.value = location.href; - if (ownerAvatar) { - snippetEdit.avatarImg.src = ownerAvatar; - snippetEdit.avatarLink.href = ownerUrl; - snippetEdit.avatarImg.hidden = false; - } else { - snippetEdit.avatarImg.hidden = true; - } - snippetEdit.username.textContent = ownerName; - snippetEdit.username.href = ownerUrl; - snippetEdit.date.textContent = new Intl.DateTimeFormat([], { month: "2-digit", day: "2-digit", year: "numeric" }).format(date); - snippetEdit.text.value = text; - if (attachmentElement) { - originalAttachment = attachmentElement.cloneNode(true); - clearAttachment(true); - } - if (snippetId !== undefined && snippetId !== null) { - snippetEdit.snippetId.value = snippetId; - } else { - snippetEdit.deleteButton.remove(); - } - - for (let i = 0; i < projectIds.length; ++i) { - let proj = null; - for (let j = 0; j < availableProjects.length; ++j) { - if (projectIds[i] == availableProjects[j].id) { - proj = availableProjects[j]; - break; - } - } - - if (proj) { - addProject(proj); - } - } - updateProjectSelector(); - - if (originalSnippetEl) { - snippetEdit.cancelLink.addEventListener("click", function () { - cancel(); - }); - } else { - snippetEdit.cancelLink.remove(); - } - - function cancel() { - if (originalSnippetEl) { - snippetEdit.root.parentElement.insertBefore(originalSnippetEl, snippetEdit.root); - } - snippetEdit.root.remove(); - } - - function addProject(proj) { - let projEl = snippetEditProjectTemplate(); - projEl.projectId.value = proj.id; - projEl.projectLogo.src = proj.logo; - projEl.projectName.textContent = proj.name; - if (proj.id == stickyProjectId) { - projEl.removeButton.remove(); - } else { - projEl.removeButton.addEventListener("click", function (ev) { - projEl.root.remove(); - updateProjectSelector(); - }); - } - snippetEdit.projectList.appendChild(projEl.root); - } - - function updateProjectSelector() { - if (projectSelector) { - projectSelector.remove(); - } - - let remainingProjects = []; - let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]"); - let assignedIds = []; - for (let i = 0; i < projInputs.length; ++i) { - let id = parseInt(projInputs[i].value, 10); - if (!isNaN(id)) { - assignedIds.push(id); - } - } - for (let i = 0; i < availableProjects.length; ++i) { - let found = false; - for (let j = 0; j < assignedIds.length; ++j) { - if (assignedIds[j] == availableProjects[i].id) { - found = true; - break; - } - } - if (!found) { - remainingProjects.push(availableProjects[i]); - } - } - - if (remainingProjects.length > 0) { - projectSelector = document.createElement("SELECT"); - let option = document.createElement("OPTION"); - option.textContent = "Add to project..."; - option.selected = true; - projectSelector.appendChild(option); - for (let i = 0; i < remainingProjects.length; ++i) { - option = document.createElement("OPTION"); - option.value = remainingProjects[i].id; - option.selected = false; - option.textContent = remainingProjects[i].name; - projectSelector.appendChild(option); - } - projectSelector.addEventListener("change", function (ev) { - if (projectSelector.selectedOptions.length > 0) { - let selected = projectSelector.selectedOptions[0]; - if (selected.value != "") { - let id = parseInt(selected.value, 10); - if (!isNaN(id)) { - for (let i = 0; i < availableProjects.length; ++i) { - if (availableProjects[i].id == id) { - addProject(availableProjects[i]); - break; - } - } - } - updateProjectSelector(); - } - } - }); - snippetEdit.projectList.appendChild(projectSelector); - } - - } - - function setFile(file) { - let dt = new DataTransfer(); - dt.items.add(file); - snippetEdit.file.files = dt.files; - - attachmentChanged = true; - snippetEdit.removeAttachment.value = "false"; - hasAttachment = true; - - let el = null; - if (file.type.startsWith("image/")) { - el = document.createElement("img"); - el.src = URL.createObjectURL(file); - } else if (file.type.startsWith("video/")) { - el = document.createElement("video"); - el.src = URL.createObjectURL(file); - el.controls = true; - } else if (file.type.startsWith("audio/")) { - el = document.createElement("audio"); - el.src = URL.createObjectURL(file); - } else { - el = document.createElement("div"); - el.classList.add("project-card", "br2", "pv1", "ph2"); - let anchor = document.createElement("a"); - anchor.href = URL.createObjectURL(file); - anchor.setAttribute("target", "_blank"); - anchor.textContent = file.name + " (" + readableByteSize(file.size) + ")"; - el.appendChild(anchor); - } - setPreview(el); - validate(); - } - - function clearAttachment(restoreOriginal) { - snippetEdit.file.value = ""; - let el = null; - attachmentChanged = false; - hasAttachment = false; - snippetEdit.removeAttachment.value = "false"; - if (originalAttachment) { - if (restoreOriginal) { - hasAttachment = true; - el = originalAttachment; - } else { - attachmentChanged = true; - snippetEdit.removeAttachment.value = "true"; - } - } - setPreview(el); - validate(); - } - - function setPreview(el) { - if (el) { - snippetEdit.uploadBox.style.display = "none"; - snippetEdit.previewBox.style.display = "block"; - snippetEdit.uploadResetBox.style.display = "none"; - snippetEdit.previewContent = emptyElement(snippetEdit.previewContent); - snippetEdit.previewContent.appendChild(el); - snippetEdit.resetLink.style.display = (!originalAttachment || el == originalAttachment) ? "none" : "inline-block"; - } else { - snippetEdit.uploadBox.style.display = "block"; - snippetEdit.previewBox.style.display = "none"; - if (originalAttachment) { - snippetEdit.uploadResetBox.style.display = "block"; - } - } - } - - function validate() { - let sizeGood = true; - if (snippetEdit.file.files.length > 0 && snippetEdit.file.files[0].size > maxFilesize) { - // NOTE(asaf): Writing this out in bytes to make the limit exactly clear to the user. - let readableSize = new Intl.NumberFormat([], { useGrouping: "always" }).format(maxFilesize); - snippetEdit.errors.textContent = "File is too big! Max filesize is " + readableSize + " bytes."; - sizeGood = false; - } else { - snippetEdit.errors.textContent = ""; - } - - let hasText = snippetEdit.text.value.trim().length > 0; - - if ((hasText || hasAttachment) && sizeGood) { - snippetEdit.saveButton.disabled = false; - } else { - snippetEdit.saveButton.disabled = true; - } - } - - snippetEdit.uploadLink.addEventListener("click", function () { - snippetEdit.file.click(); - }); - - snippetEdit.removeLink.addEventListener("click", function () { - clearAttachment(false); - }); - - snippetEdit.replaceLink.addEventListener("click", function () { - snippetEdit.file.click(); - }); - - snippetEdit.resetLink.addEventListener("click", function () { - clearAttachment(true); - }); - - snippetEdit.uploadResetLink.addEventListener("click", function () { - clearAttachment(true); - }); - - snippetEdit.file.addEventListener("change", function () { - if (snippetEdit.file.files.length > 0) { - setFile(snippetEdit.file.files[0]); - } - }); - - snippetEdit.root.addEventListener("dragover", function (ev) { - let effect = "none"; - for (let i = 0; i < ev.dataTransfer.items.length; ++i) { - if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { - effect = "copy"; - break; - } - } - ev.dataTransfer.dropEffect = effect; - ev.preventDefault(); - }); - - let enterCounter = 0; - - snippetEdit.root.addEventListener("dragenter", function (ev) { - enterCounter++; - let droppable = Array.from(ev.dataTransfer.items).some( - item => item.kind.toLowerCase() === "file" - ); - if (droppable) { - snippetEdit.root.classList.add("drop"); - } - }); - - snippetEdit.root.addEventListener("dragleave", function (ev) { - enterCounter--; - if (enterCounter == 0) { - snippetEdit.root.classList.remove("drop"); - } - }); - - snippetEdit.root.addEventListener("drop", function (ev) { - enterCounter = 0; - snippetEdit.root.classList.remove("drop"); - - if (ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files.length > 0) { - setFile(ev.dataTransfer.files[0]); - } - - ev.preventDefault(); - }); - - snippetEdit.text.addEventListener("paste", function (ev) { - const files = ev.clipboardData?.files ?? []; - if (files.length > 0) { - setFile(files[0]); - } - }); - - snippetEdit.text.addEventListener("input", function (ev) { - validate(); - }); - - snippetEdit.saveButton.addEventListener("click", function (ev) { - let projectsChanged = false; - let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]"); - let assignedIds = []; - for (let i = 0; i < projInputs.length; ++i) { - let id = parseInt(projInputs[i].value, 10); - if (!isNaN(id)) { - assignedIds.push(id); - } - } - if (projectIds.length != assignedIds.length) { - projectsChanged = true; - } else { - for (let i = 0; i < projectIds.length; ++i) { - let found = false; - for (let j = 0; j < assignedIds.length; ++j) { - if (projectIds[i] == assignedIds[j]) { - found = true; - } - } - if (!found) { - projectsChanged = true; - break; - } - } - } - - if (originalSnippetEl && (!attachmentChanged && originalText == snippetEdit.text.value.trim() && !projectsChanged)) { - // NOTE(asaf): We're in edit mode and nothing changed, so no need to submit to the server. - ev.preventDefault(); - cancel(); - } - }); - - snippetEdit.deleteButton.addEventListener("click", function (ev) { - if (!window.confirm("Are you sure you want to delete this snippet?")) { - ev.preventDefault(); - return; - } - - snippetEdit.redirect.value = onDeleteRedirectUrl; - snippetEdit.file.value = ""; - }); - - validate(); - - return snippetEdit; +// src/rawdata/js/lib/templates.ts +var templateElementCache = {}; +var templatePathCache = {}; +function getTemplateEl(id) { + if (!templateElementCache[id]) { + const el = document.getElementById(id); + assert(el, `no element with id ${id}`); + assert(el instanceof HTMLTemplateElement); + templateElementCache[id] = el; + } + return templateElementCache[id]; +} +function getTemplatePaths(id, rootNode) { + if (!templatePathCache[id]) { + let descend2 = function(path, el) { + for (var i = 0; i < el.children.length; ++i) { + var child = el.children[i]; + var childPath = path.concat([i]); + var tmplName = child.getAttribute("data-tmpl"); + if (tmplName) { + paths.push([tmplName, childPath]); + } + if (child.children.length > 0) { + descend2(childPath, child); + } + } + }; + var descend = descend2; + var paths = []; + paths.push(["root", []]); + descend2([], rootNode); + templatePathCache[id] = paths; + } + return templatePathCache[id]; +} +function collectElements(paths, rootElement) { + var result = {}; + for (var i = 0; i < paths.length; ++i) { + var path = paths[i]; + var current = rootElement; + for (var j = 0; j < path[1].length; ++j) { + current = current.children[path[1][j]]; + } + result[path[0]] = current; + } + return result; +} +function makeTemplateCloner(id) { + return function() { + var templateEl = getTemplateEl(id); + if (templateEl === null) { + throw new Error(`Couldn't find template with ID '${id}'`); + } + var root = templateEl.content.cloneNode(true); + var paths = getTemplatePaths(id, root); + var result = collectElements(paths, root); + return result; + }; +} +function emptyElement(el) { + var newEl = el.cloneNode(false); + assert(el.parentElement); + el.parentElement.insertBefore(newEl, el); + el.parentElement.removeChild(el); + return newEl; } -function editTimelineSnippet(timelineItemEl, stickyProjectId, onDeleteRedirectUrl) { - let ownerName = timelineItemEl.querySelector(".user")?.textContent; - let ownerUrl = timelineItemEl.querySelector(".user")?.href; - let ownerAvatar = timelineItemEl.querySelector(".avatar")?.src; - let creationDate = new Date(timelineItemEl.querySelector("time").dateTime); - let rawDesc = timelineItemEl.querySelector(".rawdesc").textContent; - let attachment = timelineItemEl.querySelector(".timeline-media")?.children?.[0]; - let projectIds = []; - let projectEls = timelineItemEl.querySelectorAll(".project-id-list > input"); - for (let i = 0; i < projectEls.length; ++i) { - let projid = parseInt(projectEls[i].value, 10); - if (projid) { - projectIds.push(projid); - } - } - let snippetEdit = makeSnippetEdit( - ownerName, - ownerAvatar, - ownerUrl, - creationDate, - rawDesc, - attachment, - projectIds, - stickyProjectId, - onDeleteRedirectUrl, - timelineItemEl.getAttribute("data-id"), - timelineItemEl +// src/rawdata/js/snippetedit.ts +var snippetEditTemplate = makeTemplateCloner("snippet-edit"); +var snippetEditProjectTemplate = makeTemplateCloner("snippet-edit-project"); +function readableByteSize(numBytes) { + const scales = [ + " bytes", + "kb", + "mb", + "gb" + ]; + let scale = 0; + while (numBytes > 1024 && scale < scales.length - 1) { + numBytes /= 1024; + scale++; + } + return new Intl.NumberFormat([], { maximumFractionDigits: scale > 0 ? 2 : 0 }).format(numBytes) + scales[scale]; +} +function makeSnippetEdit({ + maxFilesize, + availableProjects, + ownerName, + ownerAvatar, + ownerUrl, + date, + text, + attachmentElement, + projectIds, + stickyProjectId, + onDeleteRedirectUrl, + snippetId, + originalSnippetEl +}) { + const snippetEdit = snippetEditTemplate(); + let projectSelector = null; + let originalAttachment = null; + const originalText = text; + let attachmentChanged = false; + let hasAttachment = false; + snippetEdit.redirect.value = location.href; + if (ownerAvatar) { + assert(ownerUrl); + snippetEdit.avatarImg.src = ownerAvatar; + snippetEdit.avatarLink.href = ownerUrl; + snippetEdit.avatarImg.hidden = false; + } else { + snippetEdit.avatarImg.hidden = true; + } + snippetEdit.username.textContent = ownerName ?? ""; + snippetEdit.username.href = ownerUrl ?? ""; + snippetEdit.date.textContent = new Intl.DateTimeFormat([], { month: "2-digit", day: "2-digit", year: "numeric" }).format(date); + snippetEdit.text.value = text; + if (attachmentElement) { + originalAttachment = attachmentElement.cloneNode(true); + clearAttachment(true); + } + if (snippetId !== void 0 && snippetId !== null) { + snippetEdit.snippetId.value = snippetId; + } else { + snippetEdit.deleteButton.remove(); + } + for (let i = 0; i < projectIds.length; ++i) { + let proj = null; + for (let j = 0; j < availableProjects.length; ++j) { + if (projectIds[i] == availableProjects[j].id) { + proj = availableProjects[j]; + break; + } + } + if (proj) { + addProject(proj); + } + } + updateProjectSelector(); + if (originalSnippetEl) { + snippetEdit.cancelLink.addEventListener("click", function() { + cancel(); + }); + } else { + snippetEdit.cancelLink.remove(); + } + function cancel() { + if (originalSnippetEl) { + snippetEdit.root.parentElement.insertBefore(originalSnippetEl, snippetEdit.root); + } + snippetEdit.root.remove(); + } + function addProject(proj) { + let projEl = snippetEditProjectTemplate(); + projEl.projectId.value = `${proj.id}`; + projEl.projectLogo.src = proj.logo; + projEl.projectName.textContent = proj.name; + if (proj.id == stickyProjectId) { + projEl.removeButton.remove(); + } else { + projEl.removeButton.addEventListener("click", function(ev) { + projEl.root.remove(); + updateProjectSelector(); + }); + } + snippetEdit.projectList.appendChild(projEl.root); + } + function updateProjectSelector() { + if (projectSelector) { + projectSelector.remove(); + } + let remainingProjects = []; + let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]"); + let assignedIds = []; + for (let i = 0; i < projInputs.length; ++i) { + let id = parseInt(projInputs[i].value, 10); + if (!isNaN(id)) { + assignedIds.push(id); + } + } + for (let i = 0; i < availableProjects.length; ++i) { + let found = false; + for (let j = 0; j < assignedIds.length; ++j) { + if (assignedIds[j] == availableProjects[i].id) { + found = true; + break; + } + } + if (!found) { + remainingProjects.push(availableProjects[i]); + } + } + if (remainingProjects.length > 0) { + projectSelector = document.createElement("select"); + const option = document.createElement("option"); + option.textContent = "Add to project..."; + option.selected = true; + projectSelector.appendChild(option); + for (let i = 0; i < remainingProjects.length; ++i) { + const option2 = document.createElement("option"); + option2.value = `${remainingProjects[i].id}`; + option2.selected = false; + option2.textContent = remainingProjects[i].name; + projectSelector.appendChild(option2); + } + projectSelector.addEventListener("change", (ev) => { + assert(projectSelector); + if (projectSelector.selectedOptions.length > 0) { + let selected = projectSelector.selectedOptions[0]; + if (selected.value != "") { + let id = parseInt(selected.value, 10); + if (!isNaN(id)) { + for (let i = 0; i < availableProjects.length; ++i) { + if (availableProjects[i].id == id) { + addProject(availableProjects[i]); + break; + } + } + } + updateProjectSelector(); + } + } + }); + snippetEdit.projectList.appendChild(projectSelector); + } + } + function setFile(file) { + let dt = new DataTransfer(); + dt.items.add(file); + snippetEdit.file.files = dt.files; + attachmentChanged = true; + snippetEdit.removeAttachment.value = "false"; + hasAttachment = true; + let el = null; + if (file.type.startsWith("image/")) { + el = document.createElement("img"); + el.src = URL.createObjectURL(file); + } else if (file.type.startsWith("video/")) { + el = document.createElement("video"); + el.src = URL.createObjectURL(file); + el.controls = true; + } else if (file.type.startsWith("audio/")) { + el = document.createElement("audio"); + el.src = URL.createObjectURL(file); + } else { + el = document.createElement("div"); + el.classList.add("project-card", "br2", "pv1", "ph2"); + let anchor = document.createElement("a"); + anchor.href = URL.createObjectURL(file); + anchor.setAttribute("target", "_blank"); + anchor.textContent = file.name + " (" + readableByteSize(file.size) + ")"; + el.appendChild(anchor); + } + setPreview(el); + validate(); + } + function clearAttachment(restoreOriginal) { + snippetEdit.file.value = ""; + let el = null; + attachmentChanged = false; + hasAttachment = false; + snippetEdit.removeAttachment.value = "false"; + if (originalAttachment) { + if (restoreOriginal) { + hasAttachment = true; + el = originalAttachment; + } else { + attachmentChanged = true; + snippetEdit.removeAttachment.value = "true"; + } + } + setPreview(el); + validate(); + } + function setPreview(el) { + if (el) { + snippetEdit.uploadBox.style.display = "none"; + snippetEdit.previewBox.style.display = "block"; + snippetEdit.uploadResetBox.style.display = "none"; + snippetEdit.previewContent = emptyElement(snippetEdit.previewContent); + snippetEdit.previewContent.appendChild(el); + snippetEdit.resetLink.style.display = !originalAttachment || el == originalAttachment ? "none" : "inline-block"; + } else { + snippetEdit.uploadBox.style.display = "block"; + snippetEdit.previewBox.style.display = "none"; + if (originalAttachment) { + snippetEdit.uploadResetBox.style.display = "block"; + } + } + } + function validate() { + let sizeGood = true; + if (snippetEdit.file.files.length > 0 && snippetEdit.file.files[0].size > maxFilesize) { + let readableSize = new Intl.NumberFormat([], { useGrouping: true }).format(maxFilesize); + snippetEdit.errors.textContent = "File is too big! Max filesize is " + readableSize + " bytes."; + sizeGood = false; + } else { + snippetEdit.errors.textContent = ""; + } + let hasText = snippetEdit.text.value.trim().length > 0; + if ((hasText || hasAttachment) && sizeGood) { + snippetEdit.saveButton.disabled = false; + } else { + snippetEdit.saveButton.disabled = true; + } + } + snippetEdit.uploadLink.addEventListener("click", () => { + snippetEdit.file.click(); + }); + snippetEdit.removeLink.addEventListener("click", () => { + clearAttachment(false); + }); + snippetEdit.replaceLink.addEventListener("click", () => { + snippetEdit.file.click(); + }); + snippetEdit.resetLink.addEventListener("click", () => { + clearAttachment(true); + }); + snippetEdit.uploadResetLink.addEventListener("click", () => { + clearAttachment(true); + }); + snippetEdit.file.addEventListener("change", () => { + if (snippetEdit.file.files.length > 0) { + setFile(snippetEdit.file.files[0]); + } + }); + snippetEdit.root.addEventListener("dragover", (ev) => { + assert(ev.dataTransfer); + let effect = "none"; + for (let i = 0; i < ev.dataTransfer.items.length; ++i) { + if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { + effect = "copy"; + break; + } + } + ev.dataTransfer.dropEffect = effect; + ev.preventDefault(); + }); + let enterCounter = 0; + snippetEdit.root.addEventListener("dragenter", (ev) => { + assert(ev.dataTransfer); + enterCounter++; + const droppable = Array.from(ev.dataTransfer.items).some( + (item) => item.kind.toLowerCase() === "file" ); - timelineItemEl.parentElement.insertBefore(snippetEdit.root, timelineItemEl); - timelineItemEl.remove(); + if (droppable) { + snippetEdit.root.classList.add("drop"); + } + }); + snippetEdit.root.addEventListener("dragleave", (ev) => { + enterCounter--; + if (enterCounter == 0) { + snippetEdit.root.classList.remove("drop"); + } + }); + snippetEdit.root.addEventListener("drop", (ev) => { + enterCounter = 0; + snippetEdit.root.classList.remove("drop"); + if (ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files.length > 0) { + setFile(ev.dataTransfer.files[0]); + } + ev.preventDefault(); + }); + snippetEdit.text.addEventListener("paste", (ev) => { + assert(ev.clipboardData); + const files = ev.clipboardData.files ?? []; + if (files.length > 0) { + setFile(files[0]); + } + }); + snippetEdit.text.addEventListener("input", () => { + validate(); + }); + snippetEdit.saveButton.addEventListener("click", (ev) => { + let projectsChanged = false; + let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]"); + let assignedIds = []; + for (let i = 0; i < projInputs.length; ++i) { + let id = parseInt(projInputs[i].value, 10); + if (!isNaN(id)) { + assignedIds.push(id); + } + } + if (projectIds.length != assignedIds.length) { + projectsChanged = true; + } else { + for (let i = 0; i < projectIds.length; ++i) { + let found = false; + for (let j = 0; j < assignedIds.length; ++j) { + if (projectIds[i] == assignedIds[j]) { + found = true; + } + } + if (!found) { + projectsChanged = true; + break; + } + } + } + if (originalSnippetEl && (!attachmentChanged && originalText == snippetEdit.text.value.trim() && !projectsChanged)) { + ev.preventDefault(); + cancel(); + } + }); + snippetEdit.deleteButton.addEventListener("click", function(ev) { + if (!window.confirm("Are you sure you want to delete this snippet?")) { + ev.preventDefault(); + return; + } + snippetEdit.redirect.value = onDeleteRedirectUrl ?? ""; + snippetEdit.file.value = ""; + }); + validate(); + return snippetEdit; +} +function editTimelineSnippet(timelineItemEl, { + maxFilesize, + availableProjects, + stickyProjectId, + onDeleteRedirectUrl +}) { + const ownerName = timelineItemEl.querySelector(".user")?.textContent; + const ownerUrl = timelineItemEl.querySelector(".user")?.href; + const ownerAvatar = timelineItemEl.querySelector(".avatar")?.src; + const creationDate = new Date(must(timelineItemEl.querySelector("time")).dateTime); + const rawDesc = must(timelineItemEl.querySelector(".rawdesc")).textContent; + const attachment = timelineItemEl.querySelector(".timeline-media")?.children?.[0]; + const projectIds = []; + const projectEls = timelineItemEl.querySelectorAll(".project-id-list > input"); + for (let i = 0; i < projectEls.length; ++i) { + let projid = parseInt(projectEls[i].value, 10); + if (projid) { + projectIds.push(projid); + } + } + let snippetEdit = makeSnippetEdit({ + maxFilesize, + availableProjects, + ownerName, + ownerAvatar, + ownerUrl, + date: creationDate, + text: rawDesc, + attachmentElement: attachment, + projectIds, + stickyProjectId, + onDeleteRedirectUrl, + snippetId: must(timelineItemEl.getAttribute("data-id")), + originalSnippetEl: timelineItemEl + }); + timelineItemEl.parentElement.insertBefore(snippetEdit.root, timelineItemEl); + timelineItemEl.remove(); } +export { + editTimelineSnippet, + makeSnippetEdit +}; diff --git a/public/js/templates.js b/public/js/templates.js deleted file mode 100644 index 07d426a0..00000000 --- a/public/js/templates.js +++ /dev/null @@ -1,68 +0,0 @@ -var domLookupCache = {}; -var templatePathCache = {}; - -function getTemplateEl(id) { - if (!domLookupCache[id]) { - domLookupCache[id] = document.getElementById(id); - } - return domLookupCache[id]; -} - -function collectElements(paths, rootElement) { - var result = {}; - for (var i = 0; i < paths.length; ++i) { - var path = paths[i]; - var current = rootElement; - for (var j = 0; j < path[1].length; ++j) { - current = current.children[path[1][j]]; - } - result[path[0]] = current; - } - return result; -} - -function getTemplatePaths(id, rootElement) { - if (!templatePathCache[id]) { - var paths = []; - paths.push(["root", []]); - - function descend(path, el) { - for (var i = 0; i < el.children.length; ++i) { - var child = el.children[i]; - var childPath = path.concat([i]); - var tmplName = child.getAttribute("data-tmpl"); - if (tmplName) { - paths.push([tmplName, childPath]); - } - if (child.children.length > 0) { - descend(childPath, child); - } - } - } - - descend([], rootElement); - templatePathCache[id] = paths; - } - return templatePathCache[id]; -} - -function makeTemplateCloner(id) { - return function() { - var templateEl = getTemplateEl(id); - if (templateEl === null) { - throw new Error(`Couldn\'t find template with ID '${id}'`); - } - - var root = templateEl.content.cloneNode(true); - var paths = getTemplatePaths(id, root); - var result = collectElements(paths, root); - return result; - }; -} - -function emptyElement(el) { - var newEl = el.cloneNode(false); - el.parentElement.insertBefore(newEl, el); - el.parentElement.removeChild(el); - return newEl; -} diff --git a/public/js/user_settings.js b/public/js/user_settings.js new file mode 100644 index 00000000..301a9b75 --- /dev/null +++ b/public/js/user_settings.js @@ -0,0 +1,544 @@ +// src/rawdata/js/lib/utils.ts +function assert(cond, msg, soft = false) { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } +} +function must(val, msg) { + assert(val, msg); + return val; +} + +// src/rawdata/js/lib/templates.ts +var templateElementCache = {}; +var templatePathCache = {}; +function getTemplateEl(id) { + if (!templateElementCache[id]) { + const el = document.getElementById(id); + assert(el, `no element with id ${id}`); + assert(el instanceof HTMLTemplateElement); + templateElementCache[id] = el; + } + return templateElementCache[id]; +} +function getTemplatePaths(id, rootNode) { + if (!templatePathCache[id]) { + let descend2 = function(path, el) { + for (var i = 0; i < el.children.length; ++i) { + var child = el.children[i]; + var childPath = path.concat([i]); + var tmplName = child.getAttribute("data-tmpl"); + if (tmplName) { + paths.push([tmplName, childPath]); + } + if (child.children.length > 0) { + descend2(childPath, child); + } + } + }; + var descend = descend2; + var paths = []; + paths.push(["root", []]); + descend2([], rootNode); + templatePathCache[id] = paths; + } + return templatePathCache[id]; +} +function collectElements(paths, rootElement) { + var result = {}; + for (var i = 0; i < paths.length; ++i) { + var path = paths[i]; + var current = rootElement; + for (var j = 0; j < path[1].length; ++j) { + current = current.children[path[1][j]]; + } + result[path[0]] = current; + } + return result; +} +function makeTemplateCloner(id) { + return function() { + var templateEl = getTemplateEl(id); + if (templateEl === null) { + throw new Error(`Couldn't find template with ID '${id}'`); + } + var root = templateEl.content.cloneNode(true); + var paths = getTemplatePaths(id, root); + var result = collectElements(paths, root); + return result; + }; +} + +// src/rawdata/js/lib/image_selector.ts +var imageSelectorTemplate = makeTemplateCloner("image-selector"); +var ImageSelector = class { + // The current URL of the selector. (Will be an object URL if the image has + // not been submitted yet.) + url; + // The template content to be inserted into the DOM wherever you like. + root; + // + maxFileSize; + imageInput; + removeImageInput; + previewImage; + previewContainer; + resetLink; + removeLink; + filenameText; + errorEl; + defaultUrl; + originalUrl; + originalFilename; + onUpdate; + constructor(formName, maxFileSize, { + defaultUrl = "", + originalUrl = "", + originalFilename = "", + onUpdate = () => { + } + } = {}) { + const tmpl = imageSelectorTemplate(); + this.url = ""; + this.root = tmpl.root; + this.maxFileSize = maxFileSize; + this.imageInput = tmpl.inputImage; + this.removeImageInput = tmpl.inputRemove; + this.previewImage = tmpl.preview; + this.previewContainer = tmpl.previewContainer; + this.resetLink = tmpl.linkReset; + this.removeLink = tmpl.linkRemove; + this.filenameText = tmpl.filenameText; + this.errorEl = tmpl.errorMessage; + this.defaultUrl = defaultUrl; + this.originalUrl = originalUrl; + this.originalFilename = originalFilename; + this.onUpdate = onUpdate; + this.imageInput.name = formName; + this.imageInput.value = ""; + this.removeImageInput.name = `remove_${formName}`; + this.removeImageInput.value = ""; + this.setImageUrl( + this.originalUrl, + /*initial=*/ + true + ); + this.updatePreview(); + this.imageInput.addEventListener("change", (ev) => { + if (this.imageInput.files.length > 0) { + this.handleNewImageFile(this.imageInput.files[0]); + } + }); + this.resetLink.addEventListener("click", (ev) => { + this.resetImage(); + }); + if (this.removeLink) { + this.removeLink.addEventListener("click", (ev) => { + this.removeImage(); + }); + } + } + openImageInput() { + this.imageInput.click(); + } + setImageUrl(url, initial = false) { + this.url = url; + if (url) { + this.previewImage.style.display = "block"; + this.previewImage.src = url; + } else { + this.previewImage.style.display = "none"; + this.previewImage.removeAttribute("src"); + } + if (!initial) { + this.onUpdate(url); + } + } + setError(error) { + this.errorEl.textContent = error; + this.errorEl.hidden = !error; + this.imageInput.setCustomValidity(error); + this.imageInput.reportValidity(); + } + checkSizeLimit(size) { + if (size > this.maxFileSize) { + this.setError("File too big. Max file size is " + this.maxFileSize + " bytes."); + } else { + this.setError(""); + } + } + updatePreview(file = null) { + const showReset = this.originalUrl && this.originalUrl !== this.defaultUrl && this.originalUrl !== this.url; + const showRemove = this.url !== this.defaultUrl; + this.resetLink.hidden = !showReset; + this.removeLink.hidden = !showRemove; + if (this.url === this.originalUrl) { + this.filenameText.innerText = this.originalFilename; + } else { + this.filenameText.innerText = file ? file.name : ""; + } + this.previewContainer.hidden = !this.url; + } + handleNewImageFile(file) { + this.checkSizeLimit(file.size); + this.removeImageInput.value = ""; + this.setImageUrl(URL.createObjectURL(file)); + this.updatePreview(file); + } + removeImage() { + this.checkSizeLimit(0); + this.imageInput.value = ""; + this.removeImageInput.value = "true"; + this.setImageUrl(this.defaultUrl); + this.updatePreview(null); + } + resetImage() { + this.checkSizeLimit(0); + this.imageInput.value = ""; + this.removeImageInput.value = ""; + this.setImageUrl(this.originalUrl); + this.updatePreview(null); + } +}; + +// src/rawdata/js/lib/reorderable.js +function initReorderable(container, { + onReorder = (item) => { + } +} = {}) { + let dragItem = null; + let dragPointerId = null; + let dragItemStartY = 0; + let dragMouseStartY = 0; + const dummy = document.createElement("div"); + dummy.classList.add("reorderable-dummy"); + function startDrag(e) { + if (!e.isPrimary || e.button !== 0) { + return; + } + e.preventDefault(); + const item = e.target.closest(".reorderable-item"); + const top = item.offsetTop; + item.style.position = "absolute"; + item.style.top = `${top}px`; + item.classList.add("reorderable-dragging"); + dummy.style.height = `${item.offsetHeight}px`; + item.insertAdjacentElement("beforebegin", dummy); + document.body.classList.add("grabbing"); + dragItem = item; + dragPointerId = e.pointerId; + dragItemStartY = top; + dragMouseStartY = e.pageY; + container.setPointerCapture(e.pointerId); + container.addEventListener("pointermove", doDrag); + container.addEventListener("lostpointercapture", endDrag, { once: true }); + } + function doDrag(e) { + const delta = e.pageY - dragMouseStartY; + const top = dragItemStartY + delta; + const middle = top + dragItem.offsetHeight / 2; + const items = container.querySelectorAll(".reorderable-item"); + let closestItem = null; + let closestItemDist = Infinity; + let insertBefore = null; + for (const item of items) { + if (item === dragItem) { + continue; + } + const itemMiddle = item.offsetTop + item.offsetHeight / 2; + const dist = middle - itemMiddle; + if (Math.abs(dist) < closestItemDist) { + closestItem = item; + closestItemDist = Math.abs(dist); + insertBefore = dist < 0; + } + } + if (closestItem) { + let alreadyOrdered = true; + let n = closestItem; + while (true) { + if (insertBefore) { + n = n.previousSibling; + } else { + n = n.nextSibling; + } + if (!n) { + alreadyOrdered = false; + break; + } + if (n === dragItem) { + break; + } + if (n.classList?.contains("reorderable-item")) { + alreadyOrdered = false; + break; + } + } + if (!alreadyOrdered) { + closestItem.insertAdjacentElement(insertBefore ? "beforebegin" : "afterend", dummy); + dragItem.remove(); + dummy.insertAdjacentElement("beforebegin", dragItem); + onReorder(dragItem); + } + } + const maxTop = container.offsetHeight - dragItem.offsetHeight; + const newTop = Math.max(0, Math.min(maxTop, top)); + dragItem.style.top = `${newTop}px`; + } + function endDrag(e) { + container.removeEventListener("pointermove", doDrag); + dragItem.remove(); + dummy.insertAdjacentElement("beforebegin", dragItem); + dummy.remove(); + dragItem.style.position = null; + dragItem.style.top = null; + dragItem.classList.remove("reorderable-dragging"); + document.body.classList.remove("grabbing"); + onReorder(dragItem); + dragItem = null; + dragPointerId = null; + dragItemStartY = 0; + dragMouseStartY = 0; + } + return { + startDrag + }; +} + +// src/rawdata/js/lib/link_editor.ts +var linksContainer = must(document.querySelector("#links")); +var parentForm = must(linksContainer.closest("form")); +var addButton = must(document.querySelector("#link-editor-add-button")); +var primaryLinksTitle = must(linksContainer.querySelector(".primary-links")); +var secondaryLinksTitle = must(linksContainer.querySelector(".secondary-links")); +var linksJSONInput = must(document.querySelector("#links-json")); +var linkTemplate = makeTemplateCloner("link-editor-row"); +var emptySectionTemplate = makeTemplateCloner("link-editor-empty-section"); +var { startDrag: startLinkDrag } = initReorderable( + linksContainer, + { + onReorder(item) { + ensurePlaceholders(); + linksUpdated(); + } + } +); +function makeLink() { + const res = linkTemplate(); + res.nameInput.addEventListener("input", linkInput); + res.urlInput.addEventListener("input", linkInput); + res.grabHandle.addEventListener("pointerdown", startLinkDrag); + res.deleteButton.addEventListener("click", (e) => { + e.preventDefault(); + const link = must(e.target.closest(".link-editor-row")); + deleteLink(link); + }); + return res; +} +function ensurePlaceholders() { + for (const el of linksContainer.querySelectorAll(".link-placeholder")) { + el.remove(); + } + let numPrimary = 0, numSecondary = 0; + let primary = true; + for (const el of linksContainer.children) { + if (el === primaryLinksTitle) { + continue; + } + if (el === secondaryLinksTitle) { + primary = false; + continue; + } + if (el.classList.contains("reorderable-item")) { + if (primary) { + numPrimary += 1; + } else { + numSecondary += 1; + } + } + } + if (numPrimary === 0) { + primaryLinksTitle.insertAdjacentElement("afterend", emptySectionTemplate().rootElement); + } + if (numSecondary === 0) { + secondaryLinksTitle.insertAdjacentElement("afterend", emptySectionTemplate().rootElement); + } +} +function getLinkData() { + const links = []; + let primary = true; + for (const el of linksContainer.children) { + if (el === secondaryLinksTitle) { + primary = false; + continue; + } + if (el.classList.contains("link-editor-row")) { + const name = must(el.querySelector(".link-name")).value; + const url = must(el.querySelector(".link-url")).value; + if (!url) { + continue; + } + links.push({ name, url, primary }); + } + } + return links; +} +function updateLinksJSON() { + linksJSONInput.value = JSON.stringify(getLinkData()); +} +function linksUpdated() { + updateLinksJSON(); + window.dispatchEvent(new Event("linkedit")); +} +function addLink() { + secondaryLinksTitle.insertAdjacentElement("beforebegin", makeLink().rootElement); + ensurePlaceholders(); + linksUpdated(); +} +function deleteLink(row) { + row.remove(); + ensurePlaceholders(); + linksUpdated(); +} +function linkInput() { + linksUpdated(); +} +function initLinkEditor(initialLinks) { + for (const link of initialLinks) { + const l = makeLink(); + l.nameInput.value = link.name; + l.urlInput.value = link.url; + if (link.primary) { + secondaryLinksTitle.insertAdjacentElement("beforebegin", l.rootElement); + } else { + linksContainer.appendChild(l.rootElement); + } + } + ensurePlaceholders(); + addButton.addEventListener("click", (e) => { + e.preventDefault(); + addLink(); + }); + parentForm.addEventListener("submit", function() { + updateLinksJSON(); + }); +} + +// src/rawdata/js/lib/tabs.ts +function initTabs(container, { + initialTab, + onSelect = () => { + } +} = {}) { + const buttons = Array.from(container.querySelectorAll("[data-tab-button]")); + const tabs = Array.from(container.querySelectorAll("[data-tab]")); + const firstTab = tabs[0].getAttribute("data-tab"); + function selectTab(name, { sendEvent = true } = {}) { + if (!container.querySelector(`[data-tab="${name}"]`)) { + console.warn("no tab found with name", name); + return selectTab(firstTab, { sendEvent }); + } + for (const tab of tabs) { + tab.hidden = tab.getAttribute("data-tab") !== name; + } + for (const button of buttons) { + button.classList.toggle("tab-button-active", button.getAttribute("data-tab-button") === name); + } + if (sendEvent) { + onSelect(name); + } + } + selectTab(initialTab || firstTab, { sendEvent: false }); + for (const button of buttons) { + button.addEventListener("click", () => { + selectTab(button.getAttribute("data-tab-button")); + }); + } + return { + selectTab + }; +} +function initHashTabs(container, { + initialTab +} = {}) { + const res = initTabs(container, { + initialTab: initialTab ?? document.location.hash.substring(1), + onSelect(name) { + document.location.hash = `#${name}`; + } + }); + const { selectTab } = res; + window.addEventListener("hashchange", (e) => { + const tab = new URL(e.newURL).hash.substring(1); + if (tab) { + selectTab(tab, { sendEvent: false }); + } + }); + return res; +} + +// src/rawdata/js/user_settings.ts +function lengthReporter(inputEl, lengthEl) { + let updateLength = function() { + lengthEl.textContent = `${inputEl.value.length}/${inputEl.getAttribute("maxlength")}`; + }; + inputEl.addEventListener("input", updateLength); + updateLength(); +} +function init({ + avatarMaxFileSize, + avatarUrl, + avatarFilename, + linksJson +}) { + lengthReporter( + must(document.getElementById("realname")), + must(document.querySelector(".realname-length")) + ); + let avatarSelector = new ImageSelector( + "user_avatar", + avatarMaxFileSize, + { + originalUrl: avatarUrl, + originalFilename: avatarFilename + } + ); + must(document.querySelector("#upload-avatar-button")).addEventListener("click", (e) => { + e.preventDefault(); + avatarSelector.openImageInput(); + }); + must(document.querySelector("#user-avatar-placeholder")).replaceWith(avatarSelector.root); + initLinkEditor(JSON.parse(linksJson)); + lengthReporter( + must(document.getElementById("shortbio")), + must(document.querySelector(".shortbio-length")) + ); + lengthReporter( + must(document.getElementById("longbio")), + must(document.querySelector(".longbio-length")) + ); + if (document.getElementById("signature")) { + lengthReporter( + must(document.getElementById("signature")), + must(document.querySelector(".signature-length")) + ); + } + initHashTabs(document); + const discordUnlinkForm = must(document.querySelector("#discord-unlink-form")); + document.querySelector("#unlink-discord-button")?.addEventListener("click", (e) => { + e.preventDefault(); + discordUnlinkForm.submit(); + }); + const discordShowcaseBacklogForm = must(document.querySelector("#discord-showcase-backlog")); + document.querySelector("#discord-showcase-backlog-button")?.addEventListener("click", (e) => { + e.preventDefault(); + discordShowcaseBacklogForm.submit(); + }); +} +export { + init +}; diff --git a/public/style.css b/public/style.css index c5c6e954..144d9ef3 100644 --- a/public/style.css +++ b/public/style.css @@ -7255,16 +7255,14 @@ a, color: var(--link-color); border-bottom: none; text-decoration: none; -} -a.external::after, -.link.external::after { - font-family: "icons"; - content: " 1"; - vertical-align: middle; -} -a:hover, -.link:hover { - text-decoration: var(--link-decoration); + &.external::after { + font-family: "icons"; + content: " 1"; + vertical-align: middle; + } + &:hover { + text-decoration: var(--link-decoration); + } } .link-normal { --link-color: var(--color); @@ -7346,10 +7344,10 @@ video { } .c-inherit { color: inherit; -} -.c-inherit:hover, -.c-inherit:active { - color: inherit; + &:hover, + &:active { + color: inherit; + } } .c1 { color: var(--c1); @@ -7548,7 +7546,6 @@ video { touch-action: none; } .sticky { - position: -webkit-sticky; position: sticky; } .t1 { @@ -7581,23 +7578,26 @@ video { .b5 { bottom: var(--spacing-5); } -.svgicon svg { - fill: currentColor; - width: 1em; - height: 1em; - overflow: visible; -} -.svgicon:not(.svgicon-nofix) svg { - transform: translate(0px, 0.1em); +.svgicon { + svg { + fill: currentColor; + width: 1em; + height: 1em; + overflow: visible; + } + &:not(.svgicon-nofix) svg { + transform: translate(0px, 0.1em); + } } -.svgicon-lite svg { - fill: currentColor; - overflow: visible; +.svgicon-lite { + svg { + fill: currentColor; + overflow: visible; + } } .sr { border: 0; clip: rect(1px, 1px, 1px, 1px); - -webkit-clip-path: inset(50%); clip-path: inset(50%); height: 1px; margin: -1px; @@ -7614,15 +7614,16 @@ video { width: auto; background: var(--content-background); clip: initial; - -webkit-clip-path: initial; clip-path: initial; z-index: 99999; } -.breadcrumb:hover { - text-decoration: underline; -} -.breadcrumb.current { - text-overflow: clip ellipsis; +.breadcrumb { + &:hover { + text-decoration: underline; + } + &.current { + text-overflow: clip ellipsis; + } } .aspect-ratio-real--1x1 { aspect-ratio: 1 / 1; @@ -7859,77 +7860,69 @@ video { align-items: center; border-style: dashed; border-width: 0 0 1px; -} -@media screen and (min-width: 35em) { - .optionbar { + @media screen and (min-width: 35em) { flex-direction: row; text-align: left; padding-bottom: 0; } -} -.optionbar.bottom { - border-bottom-width: 0; - border-top-width: 1px; - padding-bottom: 0; - padding-top: var(--spacing-2); -} -@media screen and (min-width: 35em) { - .optionbar.bottom { - padding-top: 0; + &.bottom { + border-bottom-width: 0; + border-top-width: 1px; + padding-bottom: 0; + padding-top: var(--spacing-2); + @media screen and (min-width: 35em) { + padding-top: 0; + } } -} -.optionbar.center { - text-align: center; -} -.optionbar .options { - display: flex; - flex-direction: column; -} -@media screen and (min-width: 35em) { - .optionbar .options { - flex-direction: row; + &.center { + text-align: center; } -} -.optionbar .options .option { - padding: var(--spacing-1) var(--spacing-2); - display: inline-flex; - align-items: center; - justify-content: center; -} -@media screen and (min-width: 35em) { - .optionbar .options .option { - padding-top: var(--spacing-2); - padding-bottom: var(--spacing-2); + .options { + display: flex; + flex-direction: column; + @media screen and (min-width: 35em) { + flex-direction: row; + } + .option { + padding: var(--spacing-1) var(--spacing-2); + display: inline-flex; + align-items: center; + justify-content: center; + @media screen and (min-width: 35em) { + padding-top: var(--spacing-2); + padding-bottom: var(--spacing-2); + } + } + } + .group { + display: inline-block; + height: 100%; + margin: auto; } } -.optionbar .group { - display: inline-block; - height: 100%; - margin: auto; -} .hilbert { --angle: -8deg; --size: max(1920px, 120vw); display: flex; justify-content: stretch; align-items: stretch; -} -.hilbert .hilbert-filler { - flex-basis: 0; - flex-grow: 1; - position: relative; - overflow: hidden; -} -.hilbert svg { - position: absolute; - left: calc(50% - var(--size) / 2); - top: calc(50% - var(--size) / 2); - width: var(--size); - height: var(--size); - transform: rotate(var(--angle)); - fill: none; - stroke: currentColor; - stroke-width: 2px; + .hilbert-filler { + flex-basis: 0; + flex-grow: 1; + position: relative; + overflow: hidden; + } + svg { + position: absolute; + left: calc(50% - var(--size) / 2); + top: calc(50% - var(--size) / 2); + width: var(--size); + height: var(--size); + transform: rotate(var(--angle)); + fill: none; + stroke: currentColor; + stroke-width: 2px; + } } .hilbert-color { color: rgba(255, 255, 255, 0.35); @@ -7938,298 +7931,294 @@ video { } .b-table { border-collapse: collapse; -} -.b-table, -.b-table td, -.b-table th { - border: 1px solid var(--border-color); + &, + & td, + & th { + border: 1px solid var(--border-color); + } } /* src/rawdata/css/carousel.css */ -.carousel-container .carousel { - position: relative; -} -.carousel-container .carousel-item:not(.active) { - display: none; -} -.carousel-container .carousel-buttons { - display: flex; - justify-content: center; -} -.carousel-container .carousel-button { - border: 1px solid; - border-color: var(--dimmer-color); - cursor: pointer; - transition: all 100ms ease-in-out; -} -.carousel-container .carousel-button:hover { - background-color: var(--c3); -} -.carousel-container .carousel-button.active { - border-color: var(--theme-color); - width: var(--width-2); -} -.carousel-container .carousel-button.active:hover { - background-color: var(--theme-color-dimmest); +.carousel-container { + .carousel { + position: relative; + } + .carousel-item:not(.active) { + display: none; + } + .carousel-buttons { + display: flex; + justify-content: center; + } + .carousel-button { + border: 1px solid; + border-color: var(--dimmer-color); + cursor: pointer; + transition: all 100ms ease-in-out; + &:hover { + background-color: var(--c3); + } + &.active { + border-color: var(--theme-color); + width: var(--width-2); + &:hover { + background-color: var(--theme-color-dimmest); + } + } + } } /* src/rawdata/css/content.css */ .post-content { line-height: 1.4; -} -.post-content *:first-child { - margin-top: 0; -} -.post-content *:last-child { - margin-bottom: 0; -} -.post-content:not(.post-preview) h1 { - font-size: 2rem; -} -.post-content:not(.post-preview) h2 { - font-size: 1.5rem; -} -.post-content:not(.post-preview) h3 { - font-size: 1.25rem; -} -.post-content:not(.post-preview) h4 { - font-size: 1rem; -} -.post-content:not(.post-preview) h5 { - font-size: 0.8rem; -} -.post-content h1, -.post-content h2, -.post-content h3, -.post-content h4, -.post-content h5 { - line-height: 1.2; - margin-top: 1.5em; - margin-bottom: 0.5em; -} -.post-content li:not(:last-child) { - margin-bottom: 0.6em; -} -.post-content li p { - margin-top: 0.6em; - margin-bottom: 0.6em; -} -.post-content img { - max-width: 100%; -} -.post-content hr { - margin-left: var(--spacing-3); - margin-right: var(--spacing-3); -} -.post-content div.code { - border-color: var(--dimmer-color); - max-width: 100%; - max-height: 20em; - max-height: 80vh; - overflow: auto; - -moz-tab-size: 4; - tab-size: 4; - border-width: 1px; - border-style: solid; -} -.post-content div + br, -.post-content blockquote + br, -.post-content ul + br, -.post-content ol + br { - display: none; -} -.post-content table { - margin-top: var(--spacing-2); - margin-bottom: var(--spacing-2); -} -.post-content th, -.post-content td { - padding: var(--spacing-1) var(--spacing-2); - border: 1px solid var(--border-color); -} -.post-content code { - background-color: var(--c1); - padding: .2em 0; - white-space: nowrap; -} -.post-content code::before, -.post-content code::after { - content: "\a0"; - letter-spacing: -0.2em; - display: inline-block; -} -.post-content pre > code, -.post-content pre.hmn-code { - background-color: var(--c1); - padding: 0.7em; - overflow-x: auto; - max-height: 80vh; -} -.post-content blockquote { - margin-left: var(--spacing-2); - padding-left: var(--spacing-2); - margin-right: 0; - border-left-style: solid; - border-left-width: 1px; -} -.post-content blockquote .quotewho { - line-height: 2em; -} -.post-content blockquote .quotewho::after { - content: " said:"; -} -.post-content figure { - margin: var(--spacing-2) 0; - padding: var(--spacing-2) var(--spacing-2) 0; - background-color: var(--dim-background); - border-radius: var(--border-radius-3); - display: flex; - flex-direction: column; -} -@media screen and (min-width: 35em) { - .post-content figure { - margin-top: var(--spacing-3); - margin-bottom: var(--spacing-3); - padding: var(--spacing-3) var(--spacing-3) 0; + * { + &:first-child { + margin-top: 0; + } + &:last-child { + margin-bottom: 0; + } + } + &:not(.post-preview) { + h1 { + font-size: 2rem; + } + h2 { + font-size: 1.5rem; + } + h3 { + font-size: 1.25rem; + } + h4 { + font-size: 1rem; + } + h5 { + font-size: 0.8rem; + } + } + h1, + h2, + h3, + h4, + h5 { + line-height: 1.2; + margin-top: 1.5em; + margin-bottom: 0.5em; + } + li:not(:last-child) { + margin-bottom: 0.6em; + } + li p { + margin-top: 0.6em; + margin-bottom: 0.6em; + } + img { + max-width: 100%; } -} -.post-content figure figcaption { - margin: var(--spacing-1) 0; -} -@media screen and (min-width: 35em) { - .post-content figure figcaption { + hr { + margin-left: var(--spacing-3); + margin-right: var(--spacing-3); + } + div.code { + border-color: var(--dimmer-color); + max-width: 100%; + max-height: 20em; + max-height: 80vh; + overflow: auto; + -moz-tab-size: 4; + tab-size: 4; + border-width: 1px; + border-style: solid; + } + div + br, + blockquote + br, + ul + br, + ol + br { + display: none; + } + table { + margin-top: var(--spacing-2); + margin-bottom: var(--spacing-2); + } + th, + td { + padding: var(--spacing-1) var(--spacing-2); + border: 1px solid var(--border-color); + } + code { + background-color: var(--c1); + padding: .2em 0; + white-space: nowrap; + &::before, + &::after { + content: "\a0"; + letter-spacing: -0.2em; + display: inline-block; + } + } + pre > code, + pre.hmn-code { + background-color: var(--c1); + padding: 0.7em; + overflow-x: auto; + max-height: 80vh; + } + blockquote { + margin-left: var(--spacing-2); + padding-left: var(--spacing-2); + margin-right: 0; + border-left-style: solid; + border-left-width: 1px; + .quotewho { + line-height: 2em; + &::after { + content: " said:"; + } + } + } + figure { margin: var(--spacing-2) 0; + padding: var(--spacing-2) var(--spacing-2) 0; + background-color: var(--dim-background); + border-radius: var(--border-radius-3); + display: flex; + flex-direction: column; + @media screen and (min-width: 35em) { + margin-top: var(--spacing-3); + margin-bottom: var(--spacing-3); + padding: var(--spacing-3) var(--spacing-3) 0; + } + figcaption { + margin: var(--spacing-1) 0; + @media screen and (min-width: 35em) { + margin: var(--spacing-2) 0; + } + } + } + table { + border-collapse: collapse; + table-layout: fixed; + } + td { + vertical-align: baseline; + } + .spoiler { + border-color: var(--c1); + border-width: 1px; + border-style: dashed; + color: transparent; + &::selection { + color: white; + background-color: black; + } } -} -.post-content table { - border-collapse: collapse; - table-layout: fixed; -} -.post-content td { - vertical-align: baseline; -} -.post-content .spoiler { - border-color: var(--c1); - border-width: 1px; - border-style: dashed; - color: transparent; -} -.post-content .spoiler::selection { - color: white; - background-color: black; } /* src/rawdata/css/education.css */ -.edu-course.highlight { - border-left: 0.25rem solid black; - border-color: var(--link-color); - background-color: var(--theme-color-dimmest); -} -.edu-course .edu-article.coming-soon { - color: var(--dimmer-color); - font-style: italic; -} -.edu-course .edu-article::after { - --height: 10rem; - content: ""; - position: absolute; - width: 1rem; - height: var(--height); - border-width: 0 0 1px 1px; - border-style: solid; - left: -1.5rem; - top: calc(1rem - var(--height)); - border-bottom-left-radius: 0.5rem; +.edu-course { + &.highlight { + border-left: 0.25rem solid black; + border-color: var(--link-color); + background-color: var(--theme-color-dimmest); + } + .edu-article.coming-soon { + color: var(--dimmer-color); + font-style: italic; + } + .edu-article::after { + --height: 10rem; + content: ""; + position: absolute; + width: 1rem; + height: var(--height); + border-width: 0 0 1px 1px; + border-style: solid; + left: -1.5rem; + top: calc(1rem - var(--height)); + border-bottom-left-radius: 0.5rem; + } } -.edu-topic img { - width: 100%; - height: 14rem; - object-fit: cover; +.edu-topic { + img { + width: 100%; + height: 14rem; + object-fit: cover; + } } -.edu-article .note { - color: red; +.edu-article { + .note { + color: red; + } } /* src/rawdata/css/form.css */ -.hmn-form input:not([type=submit]), -.hmn-form textarea, -.hmn-form select { - color: var(--color); - background-color: var(--c3); - padding: 0.75rem; - outline: none; - border-radius: 0; -} -.hmn-form input:not([type=submit]):not(.no-border), -.hmn-form textarea:not(.no-border), -.hmn-form select:not(.no-border) { - border: 1px solid var(--border-color); -} -.hmn-form input:not([type=submit]):focus, -.hmn-form textarea:focus, -.hmn-form select:focus { - border-color: var(--border-color-focused); -} -.hmn-form input:not([type=submit]):focus ~ .also-focus, -.hmn-form textarea:focus ~ .also-focus, -.hmn-form select:focus ~ .also-focus { - border-color: var(--border-color-focused); -} -.error :is(.hmn-form input:not([type=submit])), -.hmn-form input:not([type=submit]).error, -.hmn-form input:not([type=submit]):user-invalid, -.error :is(.hmn-form textarea), -.hmn-form textarea.error, -.hmn-form textarea:user-invalid, -.error :is(.hmn-form select), -.hmn-form select.error, -.hmn-form select:user-invalid { - border-color: var(--border-color-error); -} -.hmn-form input:not([type=submit]):disabled, -.hmn-form textarea:disabled, -.hmn-form select:disabled { - background-color: var(--c5); - color: var(--c4); -} -.hmn-form input[type=checkbox], -.hmn-form input[type=radio], -.hmn-form input[type=range] { - accent-color: var(--button-color-primary); -} -.hmn-form select { - padding: 0 0.75rem; - height: 2.8rem; -} -.hmn-form textarea { - resize: vertical; -} -.hmn-form label { - font-weight: 600; -} -.hmn-form .input-group { - display: flex; - flex-direction: column; - gap: var(--spacing-1); - line-height: 1.4; -} -.hmn-form .error .error-msg { - color: var(--color-error); -} -.hmn-form .fieldset { - border: 1px solid var(--border-color); - background-color: var(--c2); - display: flex; - flex-direction: column; -} -.hmn-form legend { - display: flex; - padding: var(--spacing-3); - background-color: var(--c1); - font-weight: bold; -} -.hmn-form legend:not(:last-child) { - border-bottom: 1px solid var(--border-color); +.hmn-form { + input:not([type=submit]), + textarea, + select { + color: var(--color); + background-color: var(--c3); + padding: 0.75rem; + outline: none; + border-radius: 0; + &:not(.no-border) { + border: 1px solid var(--border-color); + } + &:focus { + border-color: var(--border-color-focused); + ~ .also-focus { + border-color: var(--border-color-focused); + } + } + .error &, + &.error, + &:user-invalid { + border-color: var(--border-color-error); + } + &:disabled { + background-color: var(--c5); + color: var(--c4); + } + } + input[type=checkbox], + input[type=radio], + input[type=range] { + accent-color: var(--button-color-primary); + } + select { + padding: 0 0.75rem; + height: 2.8rem; + } + textarea { + resize: vertical; + } + label { + font-weight: 600; + } + .input-group { + display: flex; + flex-direction: column; + gap: var(--spacing-1); + line-height: 1.4; + } + .error .error-msg { + color: var(--color-error); + } + .fieldset { + border: 1px solid var(--border-color); + background-color: var(--c2); + display: flex; + flex-direction: column; + } + legend { + display: flex; + padding: var(--spacing-3); + background-color: var(--c1); + font-weight: bold; + &:not(:last-child) { + border-bottom: 1px solid var(--border-color); + } + } } button, input[type=submit], @@ -8240,26 +8229,18 @@ input[type=submit], line-height: 1.5rem; border-style: none; border-radius: 0; -} -button:disabled, -button.disabled, -input[type=submit]:disabled, -input[type=submit].disabled, -.btn:disabled, -.btn.disabled { - cursor: default; - color: color-mix(var(--color), transparent 35%); -} -button.btn-primary, -input[type=submit].btn-primary, -.btn.btn-primary { - background-color: var(--button-color-primary); - border: 1px solid var(--button-border-color-primary); -} -button:not(.no-padding), -input[type=submit]:not(.no-padding), -.btn:not(.no-padding) { - padding: 0.5em 1.5em; + &:disabled, + &.disabled { + cursor: default; + color: color-mix(var(--color), transparent 35%); + } + &.btn-primary { + background-color: var(--button-color-primary); + border: 1px solid var(--button-border-color-primary); + } + &:not(.no-padding) { + padding: 0.5em 1.5em; + } } button.link-button, input[type=submit].link-button { @@ -8282,27 +8263,27 @@ input[type=submit].link-button { } .read { color: var(--forum-thread-read-color); -} -.read td { - color: var(--forum-thread-read-color); -} -.read a { - color: var(--forum-thread-read-link-color); -} -.read .title { - font-weight: 500; + td { + color: var(--forum-thread-read-color); + } + a { + color: var(--forum-thread-read-link-color); + } + .title { + font-weight: 500; + } } .goto { font-size: 200%; width: 30px; -} -.goto a { - display: block; - padding: 0px 10px; - box-sizing: border-box; - position: relative; - line-height: 100%; - background-color: transparent; + a { + display: block; + padding: 0px 10px; + box-sizing: border-box; + position: relative; + line-height: 100%; + background-color: transparent; + } } .badge { display: inline-block; @@ -8311,13 +8292,13 @@ input[type=submit].link-button { font-size: 0.6em; line-height: 1.8em; font-weight: bold; -} -.badge.staff { - background-color: #17b2c6; - color: white; -} -.badge.staff::before { - content: "Staff"; + &.staff { + background-color: #17b2c6; + color: white; + &::before { + content: "Staff"; + } + } } .postid a { margin-top: -4rem; @@ -8329,174 +8310,190 @@ input[type=submit].link-button { font-size: 14px; overflow: auto; line-height: 1.5em; + tbody { + width: 100%; + } + .linenos { + color: var(--theme-dim); + font-weight: 500; + padding: 5px; + background: rgba(0, 0, 0, 0.15); + } + .code { + padding-right: 20px; + padding-left: 10px; + max-width: 80em; + } } -.codeblocktable tbody { - width: 100%; -} -.codeblocktable .linenos { - color: var(--theme-dim); - font-weight: 500; - padding: 5px; - background: rgba(0, 0, 0, 0.15); -} -.codeblocktable .code { - padding-right: 20px; - padding-left: 10px; - max-width: 80em; -} -.post .action.button { - padding: 0px 10px; - margin-top: 0px; - margin-right: 4px; - background-color: transparent; - font-size: 130%; - border-radius: 0px; - border-width: 0px; - border-bottom-width: 2px; - transition: border-bottom-width 0.1s; -} -.post .action.button:hover { - border-bottom-width: 4px; -} -.blog .post .meta { - position: relative; -} -.blog .post.op .meta { - margin-bottom: 10px; -} -.blog .post .badges { - width: 40px; - font-size: 0.7em; - text-align: center; -} -.blog .sidebar .post > .author { - padding: 15px; - text-align: center; -} -.blog .sidebar .recent-posts { - text-align: center; - padding: 15px; -} -.blog .sidebar .recent-posts h2 { - display: inline; -} -.blog .sidebar .archive ul { - list-style-type: none; - text-align: left; -} -.blog .post-list .post:nth-child(even) { - background-color: transparent; +.post { + .action.button { + padding: 0px 10px; + margin-top: 0px; + margin-right: 4px; + background-color: transparent; + font-size: 130%; + border-radius: 0px; + border-width: 0px; + border-bottom-width: 2px; + transition: border-bottom-width 0.1s; + &:hover { + border-bottom-width: 4px; + } + } +} +.blog { + .post { + .meta { + position: relative; + } + &.op .meta { + margin-bottom: 10px; + } + .badges { + width: 40px; + font-size: 0.7em; + text-align: center; + } + } + .sidebar { + .post > .author { + padding: 15px; + text-align: center; + } + .recent-posts { + text-align: center; + padding: 15px; + h2 { + display: inline; + } + } + .archive ul { + list-style-type: none; + text-align: left; + } + } + .post-list { + .post { + &:nth-child(even) { + background-color: transparent; + } + } + } } .bbtable { width: 100%; border: 1px solid; -} -.bbtable td, -.bbtable th { - border: 1px solid; - padding: 4px; -} -.bbtable th { - background: rgba(0, 0, 0, 0.15); - font-weight: bold; - border-bottom-width: 2px; -} -.bbtable tbody tr:nth-child(even) { - background: rgba(0, 0, 0, 0.05); + td, + th { + border: 1px solid; + padding: 4px; + } + th { + background: rgba(0, 0, 0, 0.15); + font-weight: bold; + border-bottom-width: 2px; + } + tbody tr:nth-child(even) { + background: rgba(0, 0, 0, 0.05); + } } /* src/rawdata/css/header.css */ header.old { --logo-height: 3.75rem; -} -header.old .hmn-logo { - height: var(--logo-height); - width: 100%; - text-transform: uppercase; - font-family: "MohaveHMN", sans-serif; - font-size: 2rem; - display: flex; - align-items: center; - justify-content: center; - color: white !important; -} -@media screen and (min-width: 35em) { - header.old .hmn-logo.big { - width: 11.25rem; - } -} -@media screen and (min-width: 35em) { - header.old .hmn-logo.small { - width: var(--logo-height); - padding: 0.8rem; - text-align: justify; - text-align-last: justify; - text-justify: inter-character; - flex-direction: column; - font-size: 1rem; - line-height: 1em; - align-items: stretch; - } -} -header.old .items { - position: relative; -} -@media screen and (min-width: 35em) { - header.old .root-item { - position: relative; + .hmn-logo { height: var(--logo-height); + width: 100%; + text-transform: uppercase; + font-family: "MohaveHMN", sans-serif; + font-size: 2rem; + display: flex; + align-items: center; + justify-content: center; + color: white !important; + &.big { + @media screen and (min-width: 35em) { + width: 11.25rem; + } + } + &.small { + @media screen and (min-width: 35em) { + width: var(--logo-height); + padding: 0.8rem; + text-align: justify; + text-align-last: justify; + text-justify: inter-character; + flex-direction: column; + font-size: 1rem; + line-height: 1em; + align-items: stretch; + } + } + } + .items { + position: relative; } -} -header.old .root-item:not(:hover):not(.clicked) > .submenu { - display: none; -} -header.old .root-item.clicked .svgicon { - transform: rotate(180deg); -} -header.old .root-item > a { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - font-weight: bold; -} -header.old .root-item .svgicon { - font-size: 0.7em; -} -header.old:not(.clicked) .root-item:not(:hover) > .submenu, -header.old.clicked .root-item:not(.clicked) > .submenu { - display: none; -} -header.old .submenu { - display: flex; - flex-direction: column; - position: absolute; - left: 0; - right: 0; - z-index: 100; - min-width: 10rem; - border-top-style: solid; - border-bottom-style: solid; -} -@media screen and (min-width: 35em) { - header.old .submenu { - border-top-style: none; - border-left-style: solid; - border-right-style: solid; - left: initial; - right: initial; - } -} -header.old .submenu > a { - display: block; - white-space: nowrap; - z-index: 1; - font-weight: bold; - text-align: center; -} -@media screen and (min-width: 35em) { - header.old .submenu > a { - text-align: left; + .root-item { + @media screen and (min-width: 35em) { + & { + position: relative; + height: var(--logo-height); + } + } + &:not(:hover):not(.clicked) > .submenu { + display: none; + } + &.clicked .svgicon { + transform: rotate(180deg); + } + > a { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + font-weight: bold; + } + .svgicon { + font-size: 0.7em; + } + } + &:not(.clicked) .root-item:not(:hover), + &.clicked .root-item:not(.clicked) { + > .submenu { + display: none; + } + } + .submenu { + display: flex; + flex-direction: column; + position: absolute; + left: 0; + right: 0; + z-index: 100; + min-width: 10rem; + border-top-style: solid; + border-bottom-style: solid; + @media screen and (min-width: 35em) { + & { + border-top-style: none; + border-left-style: solid; + border-right-style: solid; + left: initial; + right: initial; + } + } + > a { + display: block; + white-space: nowrap; + z-index: 1; + font-weight: bold; + text-align: center; + @media screen and (min-width: 35em) { + & { + text-align: left; + } + } + } } } header { @@ -8506,104 +8503,100 @@ header { border-bottom-style: solid; border-bottom-width: var(--border-header); position: relative; -} -header .hmn-logo { - padding: 0.6rem 0.8rem; - display: flex; - align-items: center; -} -header .menu-chevron { - display: inline-block; - margin-left: var(--spacing-1); -} -header .avatar { - width: 1.8rem; - height: 1.8rem; -} -header .header-nav { - position: absolute; - top: calc(100% + var(--border-header)); - left: 0; - width: 100%; - background-color: var(--bg-header); - z-index: 101; -} -@media screen and (min-width: 35em) { - header .header-nav { + .hmn-logo { + padding: 0.6rem 0.8rem; display: flex; - flex-direction: column; - position: static; - width: auto; - flex-direction: row; align-items: center; - background-color: transparent; } -} -header .header-nav > a, -header .header-nav > .root-item > a { - padding: var(--spacing-3); - border-bottom: var(--border-header) solid var(--border-color); -} -header .header-nav > a:not(.db), -header .header-nav > .root-item > a:not(.db) { - display: block; -} -@media screen and (min-width: 35em) { - header .header-nav > a, - header .header-nav > .root-item > a { - border-bottom: none; + .menu-chevron { + display: inline-block; + margin-left: var(--spacing-1); } -} -header .root-item { - position: relative; -} -header .submenu { - display: flex; - flex-direction: column; -} -@media screen and (min-width: 35em) { - header .submenu { + .avatar { + width: 1.8rem; + height: 1.8rem; + } + .header-nav { position: absolute; - z-index: 100; - min-width: 8rem; + top: calc(100% + var(--border-header)); + left: 0; + width: 100%; background-color: var(--bg-header); - border-style: solid; - border-width: var(--border-header); - border-top-width: 0; - top: 100%; + z-index: 101; + @media screen and (min-width: 35em) { + display: flex; + flex-direction: column; + position: static; + width: auto; + flex-direction: row; + align-items: center; + background-color: transparent; + } + > a, + > .root-item > a { + &:not(.db) { + display: block; + } + padding: var(--spacing-3); + border-bottom: var(--border-header) solid var(--border-color); + @media screen and (min-width: 35em) { + border-bottom: none; + } + } + } + .root-item { + position: relative; } -} -header .submenu > a { - padding: var(--spacing-2) var(--spacing-3) var(--spacing-2) var(--spacing-4); - display: block; - white-space: nowrap; - z-index: 1; - border-bottom: var(--border-header) solid var(--border-color); -} -@media screen and (min-width: 35em) { - header .submenu > a { - padding: var(--spacing-2) var(--spacing-3); - border-bottom: none; + .submenu { + display: flex; + flex-direction: column; + @media screen and (min-width: 35em) { + position: absolute; + z-index: 100; + min-width: 8rem; + background-color: var(--bg-header); + border-style: solid; + border-width: var(--border-header); + border-top-width: 0; + top: 100%; + } + > a { + padding: var(--spacing-2) var(--spacing-3) var(--spacing-2) var(--spacing-4); + display: block; + white-space: nowrap; + z-index: 1; + border-bottom: var(--border-header) solid var(--border-color); + @media screen and (min-width: 35em) { + padding: var(--spacing-2) var(--spacing-3); + border-bottom: none; + } + } + } + .root-item { + &:not(:hover):not(.clicked) .submenu { + display: none; + } + &.clicked .svgicon { + transform: rotate(180deg); + } + } + &.mobile-open .header-nav { + display: block; + } + &:not(.clicked) .root-item:not(:hover), + &.clicked .root-item:not(.clicked) { + > .submenu { + display: none; + } } } -header .root-item:not(:hover):not(.clicked) .submenu { - display: none; -} -header .root-item.clicked .svgicon { - transform: rotate(180deg); -} -header.mobile-open .header-nav { - display: block; -} -header:not(.clicked) .root-item:not(:hover) > .submenu, -header.clicked .root-item:not(.clicked) > .submenu { - display: none; -} -.header-transparent header:not(.mobile-open) { - --bg-header: rgba(0, 0, 0, 0.4); - --border-header: 0px; - --border-color: transparent; - --color: #fff; +.header-transparent { + header:not(.mobile-open) { + --bg-header: rgba(0, 0, 0, 0.4); + --border-header: 0px; + --border-color: transparent; + --color: #fff; + } } /* src/rawdata/css/icons.css */ @@ -8710,56 +8703,58 @@ span.icon-rss::before { .pair { display: flex; align-items: flex-start; + .key { + font-weight: bold; + flex-shrink: 0; + } + .value { + text-align: right; + flex-grow: 1; + } } -.pair .key { - font-weight: bold; - flex-shrink: 0; -} -.pair .value { - text-align: right; - flex-grow: 1; -} -.project-carousel .carousel-item { - position: absolute; - top: 0; - left: 0; -} -.project-carousel .carousel-item br { - line-height: 0.6em; -} -.project-carousel .carousel-item-small { - position: absolute; - top: 0; - left: 0; -} -.project-carousel .carousel-item-small:not(.active) { - display: none; -} -.project-carousel .carousel-description { - max-height: 14rem; - overflow: hidden; -} -.project-carousel .carousel-fade { - position: absolute; - left: 0; - right: 0; - bottom: 0; - height: 30px; - background: linear-gradient(var(--dim-background-transparent), var(--dim-background)); +.project-carousel { + .carousel-item { + position: absolute; + top: 0; + left: 0; + br { + line-height: 0.6em; + } + } + .carousel-item-small { + position: absolute; + top: 0; + left: 0; + &:not(.active) { + display: none; + } + } + .carousel-description { + max-height: 14rem; + overflow: hidden; + } + .carousel-fade { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 30px; + background: linear-gradient(var(--dim-background-transparent), var(--dim-background)); + } } .project-card { background-color: var(--c3); color: var(--color); -} -.project-card .badges:empty { - display: none; -} -.project-card .badge { - color: white; - border-radius: 5px; -} -.project-card .blurb { - font-weight: normal; + .badges:empty { + display: none; + } + .badge { + color: white; + border-radius: 5px; + } + .blurb { + font-weight: normal; + } } .project-logo { width: 100%; @@ -8767,32 +8762,30 @@ span.icon-rss::before { height: 0; position: relative; padding-bottom: 100%; -} -.project-logo img { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - object-fit: cover; -} -.project-logo .project-logo-placeholder { - display: flex; - justify-content: center; - align-items: center; - font-size: 60cqw; - font-weight: bold; - background-color: #b1fbcb; - color: #306d60; - filter: hue-rotate(var(--hue, 0deg)); + img { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + .project-logo-placeholder { + display: flex; + justify-content: center; + align-items: center; + font-size: 60cqw; + font-weight: bold; + background-color: #b1fbcb; + color: #306d60; + filter: hue-rotate(var(--hue, 0deg)); + } } .project-card-logo { --size: 5rem; width: var(--size); flex-shrink: 0; -} -@media screen and (min-width: 35em) { - .project-card-logo { + @media screen and (min-width: 35em) { --size: 8rem; } } @@ -8801,9 +8794,7 @@ span.icon-rss::before { position: relative; background-size: cover; background-position: center; -} -@media screen and (min-width: 35em) { - .project-header-img { + @media screen and (min-width: 35em) { height: var(--height-5); } } @@ -8834,16 +8825,14 @@ span.icon-rss::before { align-items: center; --link-color: var(--color); font-weight: bold; -} -.project-links::after { - content: "\200b"; - padding: var(--spacing-2) 0; + &::after { + content: "\200b"; + padding: var(--spacing-2) 0; + } } .project-links-spacer { height: var(--height-4); -} -@media screen and (min-width: 35em) { - .project-links-spacer { + @media screen and (min-width: 35em) { height: auto; flex-grow: 1; } @@ -8858,9 +8847,7 @@ span.icon-rss::before { display: flex; gap: var(--spacing-3); overflow: hidden; -} -@media screen and (min-width: 35em) { - .project-homepage-card { + @media screen and (min-width: 35em) { margin-top: -4rem; } } @@ -8873,190 +8860,120 @@ span.icon-rss::before { } /* src/rawdata/css/syntax.css */ -pre .hll, -code .hll, -.codeblock .hll { - background-color: #ffffcc; -} -pre .c, -code .c, -.codeblock .c { - color: #008000; -} -pre .err, -code .err, -.codeblock .err { - border: 1px solid #FF0000; -} -pre .k, -code .k, -.codeblock .k { - color: #0000ff; -} -pre .ch, -code .ch, -.codeblock .ch { - color: #008000; -} -pre .cm, -code .cm, -.codeblock .cm { - color: #008000; -} -pre .cp, -code .cp, -.codeblock .cp { - color: #0000ff; -} -pre .cpf, -code .cpf, -.codeblock .cpf { - color: #008000; -} -pre .c1, -code .c1, -.codeblock .c1 { - color: #008000; -} -pre .cs, -code .cs, -.codeblock .cs { - color: #008000; -} -pre .ge, -code .ge, -.codeblock .ge { - font-style: italic; -} -pre .gh, -code .gh, -.codeblock .gh { - font-weight: bold; -} -pre .gp, -code .gp, -.codeblock .gp { - font-weight: bold; -} -pre .gs, -code .gs, -.codeblock .gs { - font-weight: bold; -} -pre .gu, -code .gu, -.codeblock .gu { - font-weight: bold; -} -pre .kc, -code .kc, -.codeblock .kc { - color: #0000ff; -} -pre .kd, -code .kd, -.codeblock .kd { - color: #0000ff; -} -pre .kn, -code .kn, -.codeblock .kn { - color: #0000ff; -} -pre .kp, -code .kp, -.codeblock .kp { - color: #0000ff; -} -pre .kr, -code .kr, -.codeblock .kr { - color: #0000ff; -} -pre .kt, -code .kt, -.codeblock .kt { - color: #2b91af; -} -pre .s, -code .s, -.codeblock .s { - color: #a31515; -} -pre .nc, -code .nc, -.codeblock .nc { - color: #2b91af; -} -pre .ow, -code .ow, -.codeblock .ow { - color: #0000ff; -} -pre .sa, -code .sa, -.codeblock .sa { - color: #a31515; -} -pre .sb, -code .sb, -.codeblock .sb { - color: #a31515; -} -pre .sc, -code .sc, -.codeblock .sc { - color: #a31515; -} -pre .dl, -code .dl, -.codeblock .dl { - color: #a31515; -} -pre .sd, -code .sd, -.codeblock .sd { - color: #a31515; -} -pre .s2, -code .s2, -.codeblock .s2 { - color: #a31515; -} -pre .se, -code .se, -.codeblock .se { - color: #a31515; -} -pre .sh, -code .sh, -.codeblock .sh { - color: #a31515; -} -pre .si, -code .si, -.codeblock .si { - color: #a31515; -} -pre .sx, -code .sx, -.codeblock .sx { - color: #a31515; -} -pre .sr, -code .sr, -.codeblock .sr { - color: #a31515; -} -pre .s1, -code .s1, -.codeblock .s1 { - color: #a31515; -} -pre .ss, -code .ss, -.codeblock .ss { - color: #a31515; +pre, +code, +.codeblock { + .hll { + background-color: #ffffcc; + } + .c { + color: #008000; + } + .err { + border: 1px solid #FF0000; + } + .k { + color: #0000ff; + } + .ch { + color: #008000; + } + .cm { + color: #008000; + } + .cp { + color: #0000ff; + } + .cpf { + color: #008000; + } + .c1 { + color: #008000; + } + .cs { + color: #008000; + } + .ge { + font-style: italic; + } + .gh { + font-weight: bold; + } + .gp { + font-weight: bold; + } + .gs { + font-weight: bold; + } + .gu { + font-weight: bold; + } + .kc { + color: #0000ff; + } + .kd { + color: #0000ff; + } + .kn { + color: #0000ff; + } + .kp { + color: #0000ff; + } + .kr { + color: #0000ff; + } + .kt { + color: #2b91af; + } + .s { + color: #a31515; + } + .nc { + color: #2b91af; + } + .ow { + color: #0000ff; + } + .sa { + color: #a31515; + } + .sb { + color: #a31515; + } + .sc { + color: #a31515; + } + .dl { + color: #a31515; + } + .sd { + color: #a31515; + } + .s2 { + color: #a31515; + } + .se { + color: #a31515; + } + .sh { + color: #a31515; + } + .si { + color: #a31515; + } + .sx { + color: #a31515; + } + .sr { + color: #a31515; + } + .s1 { + color: #a31515; + } + .ss { + color: #a31515; + } } /* src/rawdata/css/tabs.css */ @@ -9097,38 +9014,38 @@ img.avatar, .timeline-item { background-color: var(--c3); --fade-color: var(--c3); -} -.timeline-item .timeline-media { - background-color: var(--timeline-media-background); - max-height: 60vh; -} -.timeline-item .timeline-media.timeline-embed { - position: relative; - aspect-ratio: 16 / 9; -} -.timeline-item .timeline-media.timeline-embed > iframe { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 100; -} -.timeline-item .timeline-media > * { - display: block; - max-width: 100%; -} -.timeline-modal .container { - max-height: 100vh; - max-width: 100%; -} -@media screen and (min-width: 35em) { - .timeline-modal .container { - width: auto; - max-width: calc(100% - 2rem); - max-height: calc(100vh - 2rem); + .timeline-media { + background-color: var(--timeline-media-background); + max-height: 60vh; + &.timeline-embed { + position: relative; + aspect-ratio: 16 / 9; + > iframe { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100; + } + } + > * { + display: block; + max-width: 100%; + } + } +} +.timeline-modal { + .container { + max-height: 100vh; + max-width: 100%; + @media screen and (min-width: 35em) { + width: auto; + max-width: calc(100% - 2rem); + max-height: calc(100vh - 2rem); + } } } .timeline-unread { diff --git a/src/buildcss/cmd/cmd.go b/src/buildcss/cmd/cmd.go deleted file mode 100644 index cb382e28..00000000 --- a/src/buildcss/cmd/cmd.go +++ /dev/null @@ -1,39 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "git.handmade.network/hmn/hmn/src/buildcss" - "git.handmade.network/hmn/hmn/src/logging" - "git.handmade.network/hmn/hmn/src/website" - "github.com/spf13/cobra" -) - -func init() { - buildCommand := &cobra.Command{ - Use: "buildcss", - Short: "Build the website CSS", - Run: func(cmd *cobra.Command, args []string) { - ctx, err := buildcss.BuildContext() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - res := ctx.Rebuild() - outputFilenames := make([]string, 0) - for _, o := range res.OutputFiles { - outputFilenames = append(outputFilenames, o.Path) - } - logging.Info(). - Interface("Errors", res.Errors). - Interface("Warnings", res.Warnings). - Msg("Ran esbuild") - if len(outputFilenames) > 0 { - logging.Info().Interface("Files", outputFilenames).Msg("Wrote files") - } - }, - } - website.WebsiteCommand.AddCommand(buildCommand) -} diff --git a/src/buildcss/buildcss.go b/src/bundle/bundle.go similarity index 59% rename from src/buildcss/buildcss.go rename to src/bundle/bundle.go index 47bd9d6e..12ddd97f 100644 --- a/src/buildcss/buildcss.go +++ b/src/bundle/bundle.go @@ -1,4 +1,4 @@ -package buildcss +package bundle import ( "git.handmade.network/hmn/hmn/src/config" @@ -9,8 +9,8 @@ import ( var ActiveServerPort uint16 -func RunServer() *jobs.Job { - job := jobs.New("esbuild CSS server") +func RunEsBuildServer() *jobs.Job { + job := jobs.New("esbuild CSS/JS server") log := job.Logger if config.Config.Env != config.Dev { @@ -46,21 +46,27 @@ func RunServer() *jobs.Job { func BuildContext() (api.BuildContext, *api.ContextError) { return api.Context(api.BuildOptions{ - EntryPoints: []string{ - "src/rawdata/css/style.css", - "src/rawdata/css/force-light.css", - "src/rawdata/css/force-dark.css", - "src/rawdata/css/hsf/hsf.css", + EntryPointsAdvanced: []api.EntryPoint{ + {InputPath: "src/rawdata/css/style.css", OutputPath: "style"}, + {InputPath: "src/rawdata/css/force-light.css", OutputPath: "force-light"}, + {InputPath: "src/rawdata/css/force-dark.css", OutputPath: "force-dark"}, + {InputPath: "src/rawdata/js/*.js"}, + {InputPath: "src/rawdata/js/*.ts"}, }, - Outbase: "src/rawdata/css", + Outbase: "src/rawdata", Outdir: "public", External: []string{"/public/*"}, - Bundle: true, - Write: true, + + Format: api.FormatESModule, + Bundle: true, + Write: true, + Engines: []api.Engine{ - {Name: api.EngineChrome, Version: "109"}, - {Name: api.EngineFirefox, Version: "109"}, - {Name: api.EngineSafari, Version: "12"}, + // NOTE(ben): Currently all at the version that introduced CSS selector + // nesting. + {Name: api.EngineChrome, Version: "120"}, + {Name: api.EngineFirefox, Version: "117"}, + {Name: api.EngineSafari, Version: "17.2"}, }, }) } diff --git a/src/bundle/cmd/cmd.go b/src/bundle/cmd/cmd.go new file mode 100644 index 00000000..941334b3 --- /dev/null +++ b/src/bundle/cmd/cmd.go @@ -0,0 +1,112 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + "unicode" + + color "git.handmade.network/hmn/hmn/src/ansicolor" + "git.handmade.network/hmn/hmn/src/bundle" + "git.handmade.network/hmn/hmn/src/website" + "github.com/evanw/esbuild/pkg/api" + "github.com/spf13/cobra" +) + +func init() { + buildCommand := &cobra.Command{ + Use: "bundle", + Short: "Build the website CSS and JS. (Normally this happens automatically during development.)", + Run: func(cmd *cobra.Command, args []string) { + ctx, err := bundle.BuildContext() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + res := ctx.Rebuild() + for _, warning := range res.Warnings { + printEsBuildMessage(warning, false) + } + for _, err := range res.Errors { + printEsBuildMessage(err, true) + } + + if len(res.Errors) > 0 { + os.Exit(1) + } + }, + } + website.WebsiteCommand.AddCommand(buildCommand) +} + +func printEsBuildMessage(msg api.Message, isError bool) { + fgColor := color.Yellow + bgColor := color.BgYellow + upperName := "WARNING" + lowerName := "warning" + if isError { + fgColor = color.Red + bgColor = color.BgRed + upperName = "ERROR" + lowerName = "error" + } + + code := "" + if msg.ID != "" { + code = " (" + msg.ID + ")" + } + + fmt.Fprintf(os.Stderr, + bgColor+color.Bold+" %s%s "+color.Reset+" %s\n", + upperName, code, msg.Text, + ) + fmt.Fprintf(os.Stderr, + color.Faint+" at "+color.Reset+"%s:%d:%d:\n", + msg.Location.File, msg.Location.Line, msg.Location.Column, + ) + fmt.Fprint(os.Stderr, + color.Faint+" |"+color.Reset+"\n", + ) + + var firstNonWhitespaceChar, numLeadingTabs int + for i, c := range msg.Location.LineText { + if c == '\t' { + numLeadingTabs += 1 + } + if !unicode.IsSpace(c) { + firstNonWhitespaceChar = i + break + } + } + extraSpacesBecauseOfTabs := numLeadingTabs * (4 - 1) // assuming we render tabs as 4 spaces + lineNormalized := strings.Repeat(" ", firstNonWhitespaceChar+extraSpacesBecauseOfTabs) + msg.Location.LineText[firstNonWhitespaceChar:] + + fmt.Fprintf(os.Stderr, + color.Faint+" | "+color.Reset+"%s\n", + lineNormalized, + ) + fmt.Fprintf(os.Stderr, + color.Faint+" | "+color.Reset+"%s"+fgColor+"^ %s occurs here"+color.Reset+"\n", + strings.Repeat(" ", msg.Location.Column+extraSpacesBecauseOfTabs), lowerName, + ) + fmt.Fprint(os.Stderr, + color.Faint+" |"+color.Reset+"\n", + ) + + for _, note := range msg.Notes { + noteSpacing := "" + noteText := note.Text + if note.Location != nil { + noteSpacing = strings.Repeat(" ", note.Location.Column+extraSpacesBecauseOfTabs) + noteText = "^ " + noteText + } + fmt.Fprintf(os.Stderr, + color.Faint+" | "+color.Reset+"%s"+color.Blue+"%s"+color.Reset+"\n", + noteSpacing, noteText, + ) + fmt.Fprint(os.Stderr, + color.Faint+" |"+color.Reset+"\n", + ) + } +} diff --git a/src/rawdata/css/hsf/btn.css b/src/rawdata/css/hsf/btn.css deleted file mode 100644 index d1b878e7..00000000 --- a/src/rawdata/css/hsf/btn.css +++ /dev/null @@ -1,6 +0,0 @@ -.btn { - display: inline-block; - background-color: var(--color); - color: var(--c1); - padding: var(--spacing-2) var(--spacing-3); -} \ No newline at end of file diff --git a/src/rawdata/css/hsf/core.css b/src/rawdata/css/hsf/core.css deleted file mode 100644 index aa7d375f..00000000 --- a/src/rawdata/css/hsf/core.css +++ /dev/null @@ -1,412 +0,0 @@ -* { - box-sizing: border-box; - border-color: var(--border-color); -} - -body { - background-color: var(--c2); - color: var(--color); - font-family: "Inter", sans-serif; - min-height: 100vh; - box-sizing: border-box; - line-height: 1.2; -} - -a, -.link { - color: var(--link-color); - border-bottom-style: none; - text-decoration: none; -} - -.link-normal { - --link-color: var(--color); -} - -.bg-primary { - background: var(--color); -} - -b, -strong { - font-weight: 600; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: inherit; - margin: 0; - line-height: 1; - font-weight: 600; -} - -br { - /* why, IE... */ - border-style: none; -} - -hr { - margin: 0; - border: none; - border-bottom: 1px solid var(--border-color); -} - -video { - max-width: 100%; -} - -.flex-shrink-0 { - flex-shrink: 0; -} - -.flex-grow-1 { - flex-grow: 1; -} - -.flex-fair { - flex-basis: 1px; - flex-grow: 1; - flex-shrink: 1; -} - -.aspect-ratio-16x9 { - aspect-ratio: 16 / 9; -} - -.aspect-ratio-1x1 { - aspect-ratio: 1 / 1; -} - -@media screen and (min-width: 35em) { - .flex-fair-ns { - flex-basis: 1px; - flex-grow: 1; - flex-shrink: 1; - } -} - -@media screen and (min-width: 60em) { - .flex-fair-l { - flex-basis: 1px; - flex-grow: 1; - flex-shrink: 1; - } -} - -.c-normal { - color: var(--color); -} - -.f8 { - font-size: 0.65rem; -} - -.w6 { - width: var(--width-6); -} - -.w7 { - width: var(--width-7); -} - -.w8 { - width: var(--width-8); -} - -.maxh-3 { - max-height: var(--height-3); -} - -.maxh-4 { - max-height: var(--height-4); -} - -.maxh-5 { - max-height: var(--height-5); -} - -.maxh-6 { - max-height: var(--height-6); -} - -.maxh-100 { - max-height: 100%; -} - -.maxh-50vh { - max-height: 50vh; -} - -.maxh-60vh { - max-height: 60vh; -} - -.maxh-70vh { - max-height: 70vh; -} - -.maxh-80vh { - max-height: 80vh; -} - -.minw-100 { - min-width: 100%; -} - -.minh-1 { - min-height: var(--height-1); -} - -.minh-2 { - min-height: var(--height-2); -} - -.minh-3 { - min-height: var(--height-3); -} - -.minh-4 { - min-height: var(--height-4); -} - -.minh-5 { - min-height: var(--height-5); -} - -.minh-6 { - min-height: var(--height-6); -} - -.g0 { - gap: var(--spacing-0); -} - -.g1 { - gap: var(--spacing-1); -} - -.g2 { - gap: var(--spacing-2); -} - -.g3 { - gap: var(--spacing-3); -} - -.g4 { - gap: var(--spacing-4); -} - -.g5 { - gap: var(--spacing-5); -} - -.bg1 { - background-color: var(--c1); -} - -.bg2 { - background-color: var(--c2); -} - -.bg3 { - background-color: var(--c3); -} - -.bg4 { - background-color: var(--c4); -} - -.bg5 { - background-color: var(--c5); -} - -@media screen and (min-width: 35em) { - .bg1-ns { - background-color: var(--c1); - } - - .bg2-ns { - background-color: var(--c2); - } - - .bg3-ns { - background-color: var(--c3); - } - - .bg4-ns { - background-color: var(--c4); - } - - .bg5-ns { - background-color: var(--c5); - } - - .g0-ns { - gap: var(--spacing-0); - } - - .g1-ns { - gap: var(--spacing-1); - } - - .g2-ns { - gap: var(--spacing-2); - } - - .g3-ns { - gap: var(--spacing-3); - } - - .g4-ns { - gap: var(--spacing-4); - } - - .g5-ns { - gap: var(--spacing-5); - } - - .w6-ns { - width: var(--width-6); - } - - .w7-ns { - width: var(--width-7); - } - - .w8-ns { - width: var(--width-8); - } -} - -@media screen and (min-width: 35em) and (max-width: 60em) { - .bg1-m { - background-color: var(--c1); - } - - .bg2-m { - background-color: var(--c2); - } - - .bg3-m { - background-color: var(--c3); - } - - .bg4-m { - background-color: var(--c4); - } - - .bg5-m { - background-color: var(--c5); - } - - .g0-m { - gap: var(--spacing-0); - } - - .g1-m { - gap: var(--spacing-1); - } - - .g2-m { - gap: var(--spacing-2); - } - - .g3-m { - gap: var(--spacing-3); - } - - .g4-m { - gap: var(--spacing-4); - } - - .g5-m { - gap: var(--spacing-5); - } - - .w6-m { - width: var(--width-6); - } - - .w7-m { - width: var(--width-7); - } - - .w8-m { - width: var(--width-8); - } -} - -@media screen and (min-width: 60em) { - .bg1-l { - background-color: var(--c1); - } - - .bg2-l { - background-color: var(--c2); - } - - .bg3-l { - background-color: var(--c3); - } - - .bg4-l { - background-color: var(--c4); - } - - .bg5-l { - background-color: var(--c5); - } - - .g0-l { - gap: var(--spacing-0); - } - - .g1-l { - gap: var(--spacing-1); - } - - .g2-l { - gap: var(--spacing-2); - } - - .g3-l { - gap: var(--spacing-3); - } - - .g4-l { - gap: var(--spacing-4); - } - - .g5-l { - gap: var(--spacing-5); - } - - .w6-l { - width: var(--width-6); - } - - .w7-l { - width: var(--width-7); - } - - .w8-l { - width: var(--width-8); - } -} - -.mw-site { - max-width: var(--site-width); -} - -.grid { - display: grid; -} - -.grid-2 { - grid: auto-flow / 1fr 1fr; -} \ No newline at end of file diff --git a/src/rawdata/css/hsf/hsf.css b/src/rawdata/css/hsf/hsf.css deleted file mode 100644 index fdadf367..00000000 --- a/src/rawdata/css/hsf/hsf.css +++ /dev/null @@ -1,8 +0,0 @@ -@import "tachyons.min.css"; - -@import "vars.css"; -@import "core.css"; -@import "icons.css"; - -@import "btn.css"; -@import "post.css"; \ No newline at end of file diff --git a/src/rawdata/css/hsf/icons.css b/src/rawdata/css/hsf/icons.css deleted file mode 100644 index 0292f173..00000000 --- a/src/rawdata/css/hsf/icons.css +++ /dev/null @@ -1,23 +0,0 @@ -.svg-mask { - display: inline-block; - mask-image: var(--icon-url); - mask-position: center; - mask-repeat: no-repeat; - mask-size: contain; -} - -.icon-mail { - --icon-url: url('/public/icons/mail.svg'); -} - -.icon-twitch { - --icon-url: url('/public/icons/twitch.svg'); -} - -.icon-vimeo { - --icon-url: url('/public/icons/vimeo.svg'); -} - -.icon-discord { - --icon-url: url('/public/icons/discord.svg'); -} diff --git a/src/rawdata/css/hsf/post.css b/src/rawdata/css/hsf/post.css deleted file mode 100644 index fd0b021b..00000000 --- a/src/rawdata/css/hsf/post.css +++ /dev/null @@ -1,176 +0,0 @@ -.post-content { - line-height: 1.4; - - * { - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - } - - h1 { - font-size: 1.5rem; - } - - h2 { - font-size: 1.25rem; - } - - h3 { - font-size: 1rem; - } - - h4 { - font-size: 0.8rem; - } - - h5 { - font-size: 0.65rem; - } - - h1, - h2, - h3, - h4, - h5 { - line-height: 1.25; - margin-top: 0.5em; - margin-bottom: 0.5em; - } - - li:not(:last-child) { - margin-bottom: 0.6em; - } - - li p { - margin-top: 0.6em; - margin-bottom: 0.6em; - } - - img { - max-width: 100%; - } - - hr { - margin-left: var(--spacing-3); - margin-right: var(--spacing-3); - } - - div.code { - border-color: var(--dimmer-color); - - max-width: 100%; - max-height: 20em; - max-height: 80vh; - overflow: auto; - -moz-tab-size: 4; - tab-size: 4; - border-width: 1px; - border-style: solid; - } - - div+br, - blockquote+br, - ul+br, - ol+br { - display: none; - } - - table { - margin-top: var(--spacing-2); - margin-bottom: var(--spacing-2); - } - - th, - td { - padding: var(--spacing-1) var(--spacing-2); - border: 1px solid var(--border-color); - } - - code { - background-color: var(--c1); - - padding: .2em 0; - white-space: nowrap; - - &::before, - &::after { - content: "\00a0"; - letter-spacing: -0.2em; - display: inline-block; - } - } - - pre>code, - pre.hmn-code { - background-color: var(--c1); - - padding: 0.7em; - overflow-x: auto; - max-height: 80vh; - } - - blockquote { - margin-left: var(--spacing-2); - padding-left: var(--spacing-2); - margin-right: 0; - border-left-style: solid; - border-left-width: 1px; - - .quotewho { - line-height: 2em; - - &::after { - content: " said:"; - } - } - } - - figure { - margin: var(--spacing-2) 0; - padding: var(--spacing-2) var(--spacing-2) 0; - background-color: var(--dim-background); - border-radius: var(--border-radius-3); - display: flex; - flex-direction: column; - - @media screen and (min-width: 35em) { - margin-top: var(--spacing-3); - margin-bottom: var(--spacing-3); - padding: var(--spacing-3) var(--spacing-3) 0; - } - - figcaption { - margin: var(--spacing-1) 0; - - @media screen and (min-width: 35em) { - margin: var(--spacing-2) 0; - } - } - } - - table { - border-collapse: collapse; - table-layout: fixed; - } - - td { - vertical-align: baseline; - } - - .spoiler { - border-color: var(--c1); - - border-width: 1px; - border-style: dashed; - color: transparent; - - &::selection { - color: white; - background-color: black; - } - } -} \ No newline at end of file diff --git a/src/rawdata/css/hsf/tachyons-vars.css b/src/rawdata/css/hsf/tachyons-vars.css deleted file mode 100644 index 2b236b5b..00000000 --- a/src/rawdata/css/hsf/tachyons-vars.css +++ /dev/null @@ -1,140 +0,0 @@ -/* Variables copied from tachyons/variables.scss and adapted to CSS variables. */ - -:root { - --sans-serif: -apple-system, BlinkMacSystemFont, 'avenir next', avenir, helvetica, 'helvetica neue', ubuntu, roboto, noto, 'segoe ui', arial, sans-serif; - --serif: georgia, serif; - --code: consolas, monaco, monospace; - --font-size-headline: 6rem; - --font-size-subheadline: 5rem; - --font-size-1: 3rem; - --font-size-2: 2.25rem; - --font-size-3: 1.5rem; - --font-size-4: 1.25rem; - --font-size-5: 1rem; - --font-size-6: .875rem; - --font-size-7: .75rem; - --letter-spacing-tight: -.05em; - --letter-spacing-1: .1em; - --letter-spacing-2: .25em; - --line-height-solid: 1; - --line-height-title: 1.25; - --line-height-copy: 1.5; - --measure: 30em; - --measure-narrow: 20em; - --measure-wide: 34em; - --spacing-0: 0; - --spacing-1: .25rem; - --spacing-2: .5rem; - --spacing-3: 1rem; - --spacing-4: 2rem; - --spacing-5: 4rem; - --spacing-6: 8rem; - --spacing-7: 16rem; - --spacing-copy-separator: 1.5em; - --height-1: 1rem; - --height-2: 2rem; - --height-3: 4rem; - --height-4: 8rem; - --height-5: 16rem; - --height-6: 32rem; - --width-1: 1rem; - --width-2: 2rem; - --width-3: 4rem; - --width-4: 8rem; - --width-5: 16rem; - --width-6: 32rem; - --width-7: 48rem; - --width-8: 64rem; - --max-width-1: 1rem; - --max-width-2: 2rem; - --max-width-3: 4rem; - --max-width-4: 8rem; - --max-width-5: 16rem; - --max-width-6: 32rem; - --max-width-7: 48rem; - --max-width-8: 64rem; - --max-width-9: 96rem; - --border-radius-none: 0; - --border-radius-1: .125rem; - --border-radius-2: .25rem; - --border-radius-3: .5rem; - --border-radius-4: 1rem; - --border-radius-circle: 100%; - --border-radius-pill: 9999px; - --border-width-none: 0; - --border-width-1: .125rem; - --border-width-2: .25rem; - --border-width-3: .5rem; - --border-width-4: 1rem; - --border-width-5: 2rem; - --box-shadow-1: 0px 0px 4px 2px rgba(0, 0, 0, 0.2); - --box-shadow-2: 0px 0px 8px 2px rgba(0, 0, 0, 0.2); - --box-shadow-3: 2px 2px 4px 2px rgba(0, 0, 0, 0.2); - --box-shadow-4: 2px 2px 8px 0px rgba(0, 0, 0, 0.2); - --box-shadow-5: 4px 4px 8px 0px rgba(0, 0, 0, 0.2); - --black: #000; - --near-black: #111; - --dark-gray: #333; - --mid-gray: #555; - --gray: #777; - --silver: #999; - --light-silver: #aaa; - --moon-gray: #ccc; - --light-gray: #eee; - --near-white: #f4f4f4; - --white: #fff; - --transparent: transparent; - --black-90: rgba(0, 0, 0, .9); - --black-80: rgba(0, 0, 0, .8); - --black-70: rgba(0, 0, 0, .7); - --black-60: rgba(0, 0, 0, .6); - --black-50: rgba(0, 0, 0, .5); - --black-40: rgba(0, 0, 0, .4); - --black-30: rgba(0, 0, 0, .3); - --black-20: rgba(0, 0, 0, .2); - --black-10: rgba(0, 0, 0, .1); - --black-05: rgba(0, 0, 0, .05); - --black-025: rgba(0, 0, 0, .025); - --black-0125: rgba(0, 0, 0, .0125); - --white-90: rgba(255, 255, 255, .9); - --white-80: rgba(255, 255, 255, .8); - --white-70: rgba(255, 255, 255, .7); - --white-60: rgba(255, 255, 255, .6); - --white-50: rgba(255, 255, 255, .5); - --white-40: rgba(255, 255, 255, .4); - --white-30: rgba(255, 255, 255, .3); - --white-20: rgba(255, 255, 255, .2); - --white-10: rgba(255, 255, 255, .1); - --white-05: rgba(255, 255, 255, .05); - --white-025: rgba(255, 255, 255, .025); - --white-0125: rgba(255, 255, 255, .0125); - --dark-red: #e7040f; - --red: #ff4136; - --light-red: #ff725c; - --orange: #ff6300; - --gold: #ffb700; - --yellow: #ffd700; - --light-yellow: #fbf1a9; - --purple: #5e2ca5; - --light-purple: #a463f2; - --dark-pink: #d5008f; - --hot-pink: #ff41b4; - --pink: #ff80cc; - --light-pink: #ffa3d7; - --dark-green: #137752; - --green: #19a974; - --light-green: #9eebcf; - --navy: #001b44; - --dark-blue: #00449e; - --blue: #357edd; - --light-blue: #96ccff; - --lightest-blue: #cdecff; - --washed-blue: #f6fffe; - --washed-green: #e8fdf5; - --washed-yellow: #fffceb; - --washed-red: #ffdfdf; - - --breakpoint-not-small: 'screen and (min-width: 35em)'; - --breakpoint-medium: 'screen and (min-width: 35em) and (max-width: 60em)'; - --breakpoint-large: 'screen and (min-width: 60em)'; -} diff --git a/src/rawdata/css/hsf/tachyons.min.css b/src/rawdata/css/hsf/tachyons.min.css deleted file mode 100644 index 5538b7bb..00000000 --- a/src/rawdata/css/hsf/tachyons.min.css +++ /dev/null @@ -1,3 +0,0 @@ -/*! TACHYONS v4.12.0 | http://tachyons.io */ -/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none!important}.border-box,a,article,aside,blockquote,body,code,dd,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,html,input[type=email],input[type=number],input[type=password],input[type=tel],input[type=text],input[type=url],legend,li,main,nav,ol,p,pre,section,table,td,textarea,th,tr,ul{box-sizing:border-box}.aspect-ratio{height:0;position:relative}.aspect-ratio--16x9{padding-bottom:56.25%}.aspect-ratio--9x16{padding-bottom:177.77%}.aspect-ratio--4x3{padding-bottom:75%}.aspect-ratio--3x4{padding-bottom:133.33%}.aspect-ratio--6x4{padding-bottom:66.6%}.aspect-ratio--4x6{padding-bottom:150%}.aspect-ratio--8x5{padding-bottom:62.5%}.aspect-ratio--5x8{padding-bottom:160%}.aspect-ratio--7x5{padding-bottom:71.42%}.aspect-ratio--5x7{padding-bottom:140%}.aspect-ratio--1x1{padding-bottom:100%}.aspect-ratio--object{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}img{max-width:100%}.cover{background-size:cover!important}.contain{background-size:contain!important}.bg-center{background-position:50%}.bg-center,.bg-top{background-repeat:no-repeat}.bg-top{background-position:top}.bg-right{background-position:100%}.bg-bottom,.bg-right{background-repeat:no-repeat}.bg-bottom{background-position:bottom}.bg-left{background-repeat:no-repeat;background-position:0}.outline{outline:1px solid}.outline-transparent{outline:1px solid transparent}.outline-0{outline:0}.ba{border-style:solid;border-width:1px}.bt{border-top-style:solid;border-top-width:1px}.br{border-right-style:solid;border-right-width:1px}.bb{border-bottom-style:solid;border-bottom-width:1px}.bl{border-left-style:solid;border-left-width:1px}.bn{border-style:none;border-width:0}.b--black{border-color:#000}.b--near-black{border-color:#111}.b--dark-gray{border-color:#333}.b--mid-gray{border-color:#555}.b--gray{border-color:#777}.b--silver{border-color:#999}.b--light-silver{border-color:#aaa}.b--moon-gray{border-color:#ccc}.b--light-gray{border-color:#eee}.b--near-white{border-color:#f4f4f4}.b--white{border-color:#fff}.b--white-90{border-color:hsla(0,0%,100%,.9)}.b--white-80{border-color:hsla(0,0%,100%,.8)}.b--white-70{border-color:hsla(0,0%,100%,.7)}.b--white-60{border-color:hsla(0,0%,100%,.6)}.b--white-50{border-color:hsla(0,0%,100%,.5)}.b--white-40{border-color:hsla(0,0%,100%,.4)}.b--white-30{border-color:hsla(0,0%,100%,.3)}.b--white-20{border-color:hsla(0,0%,100%,.2)}.b--white-10{border-color:hsla(0,0%,100%,.1)}.b--white-05{border-color:hsla(0,0%,100%,.05)}.b--white-025{border-color:hsla(0,0%,100%,.025)}.b--white-0125{border-color:hsla(0,0%,100%,.0125)}.b--black-90{border-color:rgba(0,0,0,.9)}.b--black-80{border-color:rgba(0,0,0,.8)}.b--black-70{border-color:rgba(0,0,0,.7)}.b--black-60{border-color:rgba(0,0,0,.6)}.b--black-50{border-color:rgba(0,0,0,.5)}.b--black-40{border-color:rgba(0,0,0,.4)}.b--black-30{border-color:rgba(0,0,0,.3)}.b--black-20{border-color:rgba(0,0,0,.2)}.b--black-10{border-color:rgba(0,0,0,.1)}.b--black-05{border-color:rgba(0,0,0,.05)}.b--black-025{border-color:rgba(0,0,0,.025)}.b--black-0125{border-color:rgba(0,0,0,.0125)}.b--dark-red{border-color:#e7040f}.b--red{border-color:#ff4136}.b--light-red{border-color:#ff725c}.b--orange{border-color:#ff6300}.b--gold{border-color:#ffb700}.b--yellow{border-color:gold}.b--light-yellow{border-color:#fbf1a9}.b--purple{border-color:#5e2ca5}.b--light-purple{border-color:#a463f2}.b--dark-pink{border-color:#d5008f}.b--hot-pink{border-color:#ff41b4}.b--pink{border-color:#ff80cc}.b--light-pink{border-color:#ffa3d7}.b--dark-green{border-color:#137752}.b--green{border-color:#19a974}.b--light-green{border-color:#9eebcf}.b--navy{border-color:#001b44}.b--dark-blue{border-color:#00449e}.b--blue{border-color:#357edd}.b--light-blue{border-color:#96ccff}.b--lightest-blue{border-color:#cdecff}.b--washed-blue{border-color:#f6fffe}.b--washed-green{border-color:#e8fdf5}.b--washed-yellow{border-color:#fffceb}.b--washed-red{border-color:#ffdfdf}.b--transparent{border-color:transparent}.b--inherit{border-color:inherit}.b--initial{border-color:initial}.b--unset{border-color:unset}.br0{border-radius:0}.br1{border-radius:.125rem}.br2{border-radius:.25rem}.br3{border-radius:.5rem}.br4{border-radius:1rem}.br-100{border-radius:100%}.br-pill{border-radius:9999px}.br--bottom{border-top-left-radius:0;border-top-right-radius:0}.br--top{border-bottom-right-radius:0}.br--right,.br--top{border-bottom-left-radius:0}.br--right{border-top-left-radius:0}.br--left{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit{border-radius:inherit}.br-initial{border-radius:initial}.br-unset{border-radius:unset}.b--dotted{border-style:dotted}.b--dashed{border-style:dashed}.b--solid{border-style:solid}.b--none{border-style:none}.bw0{border-width:0}.bw1{border-width:.125rem}.bw2{border-width:.25rem}.bw3{border-width:.5rem}.bw4{border-width:1rem}.bw5{border-width:2rem}.bt-0{border-top-width:0}.br-0{border-right-width:0}.bb-0{border-bottom-width:0}.bl-0{border-left-width:0}.shadow-1{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.pre{overflow-x:auto;overflow-y:hidden;overflow:scroll}.top-0{top:0}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.top-1{top:1rem}.right-1{right:1rem}.bottom-1{bottom:1rem}.left-1{left:1rem}.top-2{top:2rem}.right-2{right:2rem}.bottom-2{bottom:2rem}.left-2{left:2rem}.top--1{top:-1rem}.right--1{right:-1rem}.bottom--1{bottom:-1rem}.left--1{left:-1rem}.top--2{top:-2rem}.right--2{right:-2rem}.bottom--2{bottom:-2rem}.left--2{left:-2rem}.absolute--fill{top:0;right:0;bottom:0;left:0}.cf:after,.cf:before{content:" ";display:table}.cf:after{clear:both}.cf{zoom:1}.cl{clear:left}.cr{clear:right}.cb{clear:both}.cn{clear:none}.dn{display:none}.di{display:inline}.db{display:block}.dib{display:inline-block}.dit{display:inline-table}.dt{display:table}.dtc{display:table-cell}.dt-row{display:table-row}.dt-row-group{display:table-row-group}.dt-column{display:table-column}.dt-column-group{display:table-column-group}.dt--fixed{table-layout:fixed;width:100%}.flex{display:flex}.inline-flex{display:inline-flex}.flex-auto{flex:1 1 auto;min-width:0;min-height:0}.flex-none{flex:none}.flex-column{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.flex-wrap-reverse{flex-wrap:wrap-reverse}.flex-column-reverse{flex-direction:column-reverse}.flex-row-reverse{flex-direction:row-reverse}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.self-start{align-self:flex-start}.self-end{align-self:flex-end}.self-center{align-self:center}.self-baseline{align-self:baseline}.self-stretch{align-self:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.content-start{align-content:flex-start}.content-end{align-content:flex-end}.content-center{align-content:center}.content-between{align-content:space-between}.content-around{align-content:space-around}.content-stretch{align-content:stretch}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-last{order:99999}.flex-grow-0{flex-grow:0}.flex-grow-1{flex-grow:1}.flex-shrink-0{flex-shrink:0}.flex-shrink-1{flex-shrink:1}.fl{float:left}.fl,.fr{_display:inline}.fr{float:right}.fn{float:none}.sans-serif{font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.serif{font-family:georgia,times,serif}.system-sans-serif{font-family:sans-serif}.system-serif{font-family:serif}.code,code{font-family:Consolas,monaco,monospace}.courier{font-family:Courier Next,courier,monospace}.helvetica{font-family:helvetica neue,helvetica,sans-serif}.avenir{font-family:avenir next,avenir,sans-serif}.athelas{font-family:athelas,georgia,serif}.georgia{font-family:georgia,serif}.times{font-family:times,serif}.bodoni{font-family:Bodoni MT,serif}.calisto{font-family:Calisto MT,serif}.garamond{font-family:garamond,serif}.baskerville{font-family:baskerville,serif}.i{font-style:italic}.fs-normal{font-style:normal}.normal{font-weight:400}.b{font-weight:700}.fw1{font-weight:100}.fw2{font-weight:200}.fw3{font-weight:300}.fw4{font-weight:400}.fw5{font-weight:500}.fw6{font-weight:600}.fw7{font-weight:700}.fw8{font-weight:800}.fw9{font-weight:900}.input-reset{-webkit-appearance:none;-moz-appearance:none}.button-reset::-moz-focus-inner,.input-reset::-moz-focus-inner{border:0;padding:0}.h1{height:1rem}.h2{height:2rem}.h3{height:4rem}.h4{height:8rem}.h5{height:16rem}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.min-h-100{min-height:100%}.vh-25{height:25vh}.vh-50{height:50vh}.vh-75{height:75vh}.vh-100{height:100vh}.min-vh-100{min-height:100vh}.h-auto{height:auto}.h-inherit{height:inherit}.tracked{letter-spacing:.1em}.tracked-tight{letter-spacing:-.05em}.tracked-mega{letter-spacing:.25em}.lh-solid{line-height:1}.lh-title{line-height:1.25}.lh-copy{line-height:1.5}.link{text-decoration:none}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{transition:color .15s ease-in}.link:focus{outline:1px dotted currentColor}.list{list-style-type:none}.mw-100{max-width:100%}.mw1{max-width:1rem}.mw2{max-width:2rem}.mw3{max-width:4rem}.mw4{max-width:8rem}.mw5{max-width:16rem}.mw6{max-width:32rem}.mw7{max-width:48rem}.mw8{max-width:64rem}.mw9{max-width:96rem}.mw-none{max-width:none}.w1{width:1rem}.w2{width:2rem}.w3{width:4rem}.w4{width:8rem}.w5{width:16rem}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-33{width:33%}.w-34{width:34%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.w-third{width:33.33333%}.w-two-thirds{width:66.66667%}.w-auto{width:auto}.overflow-visible{overflow:visible}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-auto{overflow:auto}.overflow-x-visible{overflow-x:visible}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-x-auto{overflow-x:auto}.overflow-y-visible{overflow-y:visible}.overflow-y-hidden{overflow-y:hidden}.overflow-y-scroll{overflow-y:scroll}.overflow-y-auto{overflow-y:auto}.static{position:static}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.o-100{opacity:1}.o-90{opacity:.9}.o-80{opacity:.8}.o-70{opacity:.7}.o-60{opacity:.6}.o-50{opacity:.5}.o-40{opacity:.4}.o-30{opacity:.3}.o-20{opacity:.2}.o-10{opacity:.1}.o-05{opacity:.05}.o-025{opacity:.025}.o-0{opacity:0}.rotate-45{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.black-90{color:rgba(0,0,0,.9)}.black-80{color:rgba(0,0,0,.8)}.black-70{color:rgba(0,0,0,.7)}.black-60{color:rgba(0,0,0,.6)}.black-50{color:rgba(0,0,0,.5)}.black-40{color:rgba(0,0,0,.4)}.black-30{color:rgba(0,0,0,.3)}.black-20{color:rgba(0,0,0,.2)}.black-10{color:rgba(0,0,0,.1)}.black-05{color:rgba(0,0,0,.05)}.white-90{color:hsla(0,0%,100%,.9)}.white-80{color:hsla(0,0%,100%,.8)}.white-70{color:hsla(0,0%,100%,.7)}.white-60{color:hsla(0,0%,100%,.6)}.white-50{color:hsla(0,0%,100%,.5)}.white-40{color:hsla(0,0%,100%,.4)}.white-30{color:hsla(0,0%,100%,.3)}.white-20{color:hsla(0,0%,100%,.2)}.white-10{color:hsla(0,0%,100%,.1)}.black{color:#000}.near-black{color:#111}.dark-gray{color:#333}.mid-gray{color:#555}.gray{color:#777}.silver{color:#999}.light-silver{color:#aaa}.moon-gray{color:#ccc}.light-gray{color:#eee}.near-white{color:#f4f4f4}.white{color:#fff}.dark-red{color:#e7040f}.red{color:#ff4136}.light-red{color:#ff725c}.orange{color:#ff6300}.gold{color:#ffb700}.yellow{color:gold}.light-yellow{color:#fbf1a9}.purple{color:#5e2ca5}.light-purple{color:#a463f2}.dark-pink{color:#d5008f}.hot-pink{color:#ff41b4}.pink{color:#ff80cc}.light-pink{color:#ffa3d7}.dark-green{color:#137752}.green{color:#19a974}.light-green{color:#9eebcf}.navy{color:#001b44}.dark-blue{color:#00449e}.blue{color:#357edd}.light-blue{color:#96ccff}.lightest-blue{color:#cdecff}.washed-blue{color:#f6fffe}.washed-green{color:#e8fdf5}.washed-yellow{color:#fffceb}.washed-red{color:#ffdfdf}.color-inherit{color:inherit}.bg-black-90{background-color:rgba(0,0,0,.9)}.bg-black-80{background-color:rgba(0,0,0,.8)}.bg-black-70{background-color:rgba(0,0,0,.7)}.bg-black-60{background-color:rgba(0,0,0,.6)}.bg-black-50{background-color:rgba(0,0,0,.5)}.bg-black-40{background-color:rgba(0,0,0,.4)}.bg-black-30{background-color:rgba(0,0,0,.3)}.bg-black-20{background-color:rgba(0,0,0,.2)}.bg-black-10{background-color:rgba(0,0,0,.1)}.bg-black-05{background-color:rgba(0,0,0,.05)}.bg-white-90{background-color:hsla(0,0%,100%,.9)}.bg-white-80{background-color:hsla(0,0%,100%,.8)}.bg-white-70{background-color:hsla(0,0%,100%,.7)}.bg-white-60{background-color:hsla(0,0%,100%,.6)}.bg-white-50{background-color:hsla(0,0%,100%,.5)}.bg-white-40{background-color:hsla(0,0%,100%,.4)}.bg-white-30{background-color:hsla(0,0%,100%,.3)}.bg-white-20{background-color:hsla(0,0%,100%,.2)}.bg-white-10{background-color:hsla(0,0%,100%,.1)}.bg-black{background-color:#000}.bg-near-black{background-color:#111}.bg-dark-gray{background-color:#333}.bg-mid-gray{background-color:#555}.bg-gray{background-color:#777}.bg-silver{background-color:#999}.bg-light-silver{background-color:#aaa}.bg-moon-gray{background-color:#ccc}.bg-light-gray{background-color:#eee}.bg-near-white{background-color:#f4f4f4}.bg-white{background-color:#fff}.bg-transparent{background-color:transparent}.bg-dark-red{background-color:#e7040f}.bg-red{background-color:#ff4136}.bg-light-red{background-color:#ff725c}.bg-orange{background-color:#ff6300}.bg-gold{background-color:#ffb700}.bg-yellow{background-color:gold}.bg-light-yellow{background-color:#fbf1a9}.bg-purple{background-color:#5e2ca5}.bg-light-purple{background-color:#a463f2}.bg-dark-pink{background-color:#d5008f}.bg-hot-pink{background-color:#ff41b4}.bg-pink{background-color:#ff80cc}.bg-light-pink{background-color:#ffa3d7}.bg-dark-green{background-color:#137752}.bg-green{background-color:#19a974}.bg-light-green{background-color:#9eebcf}.bg-navy{background-color:#001b44}.bg-dark-blue{background-color:#00449e}.bg-blue{background-color:#357edd}.bg-light-blue{background-color:#96ccff}.bg-lightest-blue{background-color:#cdecff}.bg-washed-blue{background-color:#f6fffe}.bg-washed-green{background-color:#e8fdf5}.bg-washed-yellow{background-color:#fffceb}.bg-washed-red{background-color:#ffdfdf}.bg-inherit{background-color:inherit}.hover-black:focus,.hover-black:hover{color:#000}.hover-near-black:focus,.hover-near-black:hover{color:#111}.hover-dark-gray:focus,.hover-dark-gray:hover{color:#333}.hover-mid-gray:focus,.hover-mid-gray:hover{color:#555}.hover-gray:focus,.hover-gray:hover{color:#777}.hover-silver:focus,.hover-silver:hover{color:#999}.hover-light-silver:focus,.hover-light-silver:hover{color:#aaa}.hover-moon-gray:focus,.hover-moon-gray:hover{color:#ccc}.hover-light-gray:focus,.hover-light-gray:hover{color:#eee}.hover-near-white:focus,.hover-near-white:hover{color:#f4f4f4}.hover-white:focus,.hover-white:hover{color:#fff}.hover-black-90:focus,.hover-black-90:hover{color:rgba(0,0,0,.9)}.hover-black-80:focus,.hover-black-80:hover{color:rgba(0,0,0,.8)}.hover-black-70:focus,.hover-black-70:hover{color:rgba(0,0,0,.7)}.hover-black-60:focus,.hover-black-60:hover{color:rgba(0,0,0,.6)}.hover-black-50:focus,.hover-black-50:hover{color:rgba(0,0,0,.5)}.hover-black-40:focus,.hover-black-40:hover{color:rgba(0,0,0,.4)}.hover-black-30:focus,.hover-black-30:hover{color:rgba(0,0,0,.3)}.hover-black-20:focus,.hover-black-20:hover{color:rgba(0,0,0,.2)}.hover-black-10:focus,.hover-black-10:hover{color:rgba(0,0,0,.1)}.hover-white-90:focus,.hover-white-90:hover{color:hsla(0,0%,100%,.9)}.hover-white-80:focus,.hover-white-80:hover{color:hsla(0,0%,100%,.8)}.hover-white-70:focus,.hover-white-70:hover{color:hsla(0,0%,100%,.7)}.hover-white-60:focus,.hover-white-60:hover{color:hsla(0,0%,100%,.6)}.hover-white-50:focus,.hover-white-50:hover{color:hsla(0,0%,100%,.5)}.hover-white-40:focus,.hover-white-40:hover{color:hsla(0,0%,100%,.4)}.hover-white-30:focus,.hover-white-30:hover{color:hsla(0,0%,100%,.3)}.hover-white-20:focus,.hover-white-20:hover{color:hsla(0,0%,100%,.2)}.hover-white-10:focus,.hover-white-10:hover{color:hsla(0,0%,100%,.1)}.hover-inherit:focus,.hover-inherit:hover{color:inherit}.hover-bg-black:focus,.hover-bg-black:hover{background-color:#000}.hover-bg-near-black:focus,.hover-bg-near-black:hover{background-color:#111}.hover-bg-dark-gray:focus,.hover-bg-dark-gray:hover{background-color:#333}.hover-bg-mid-gray:focus,.hover-bg-mid-gray:hover{background-color:#555}.hover-bg-gray:focus,.hover-bg-gray:hover{background-color:#777}.hover-bg-silver:focus,.hover-bg-silver:hover{background-color:#999}.hover-bg-light-silver:focus,.hover-bg-light-silver:hover{background-color:#aaa}.hover-bg-moon-gray:focus,.hover-bg-moon-gray:hover{background-color:#ccc}.hover-bg-light-gray:focus,.hover-bg-light-gray:hover{background-color:#eee}.hover-bg-near-white:focus,.hover-bg-near-white:hover{background-color:#f4f4f4}.hover-bg-white:focus,.hover-bg-white:hover{background-color:#fff}.hover-bg-transparent:focus,.hover-bg-transparent:hover{background-color:transparent}.hover-bg-black-90:focus,.hover-bg-black-90:hover{background-color:rgba(0,0,0,.9)}.hover-bg-black-80:focus,.hover-bg-black-80:hover{background-color:rgba(0,0,0,.8)}.hover-bg-black-70:focus,.hover-bg-black-70:hover{background-color:rgba(0,0,0,.7)}.hover-bg-black-60:focus,.hover-bg-black-60:hover{background-color:rgba(0,0,0,.6)}.hover-bg-black-50:focus,.hover-bg-black-50:hover{background-color:rgba(0,0,0,.5)}.hover-bg-black-40:focus,.hover-bg-black-40:hover{background-color:rgba(0,0,0,.4)}.hover-bg-black-30:focus,.hover-bg-black-30:hover{background-color:rgba(0,0,0,.3)}.hover-bg-black-20:focus,.hover-bg-black-20:hover{background-color:rgba(0,0,0,.2)}.hover-bg-black-10:focus,.hover-bg-black-10:hover{background-color:rgba(0,0,0,.1)}.hover-bg-white-90:focus,.hover-bg-white-90:hover{background-color:hsla(0,0%,100%,.9)}.hover-bg-white-80:focus,.hover-bg-white-80:hover{background-color:hsla(0,0%,100%,.8)}.hover-bg-white-70:focus,.hover-bg-white-70:hover{background-color:hsla(0,0%,100%,.7)}.hover-bg-white-60:focus,.hover-bg-white-60:hover{background-color:hsla(0,0%,100%,.6)}.hover-bg-white-50:focus,.hover-bg-white-50:hover{background-color:hsla(0,0%,100%,.5)}.hover-bg-white-40:focus,.hover-bg-white-40:hover{background-color:hsla(0,0%,100%,.4)}.hover-bg-white-30:focus,.hover-bg-white-30:hover{background-color:hsla(0,0%,100%,.3)}.hover-bg-white-20:focus,.hover-bg-white-20:hover{background-color:hsla(0,0%,100%,.2)}.hover-bg-white-10:focus,.hover-bg-white-10:hover{background-color:hsla(0,0%,100%,.1)}.hover-dark-red:focus,.hover-dark-red:hover{color:#e7040f}.hover-red:focus,.hover-red:hover{color:#ff4136}.hover-light-red:focus,.hover-light-red:hover{color:#ff725c}.hover-orange:focus,.hover-orange:hover{color:#ff6300}.hover-gold:focus,.hover-gold:hover{color:#ffb700}.hover-yellow:focus,.hover-yellow:hover{color:gold}.hover-light-yellow:focus,.hover-light-yellow:hover{color:#fbf1a9}.hover-purple:focus,.hover-purple:hover{color:#5e2ca5}.hover-light-purple:focus,.hover-light-purple:hover{color:#a463f2}.hover-dark-pink:focus,.hover-dark-pink:hover{color:#d5008f}.hover-hot-pink:focus,.hover-hot-pink:hover{color:#ff41b4}.hover-pink:focus,.hover-pink:hover{color:#ff80cc}.hover-light-pink:focus,.hover-light-pink:hover{color:#ffa3d7}.hover-dark-green:focus,.hover-dark-green:hover{color:#137752}.hover-green:focus,.hover-green:hover{color:#19a974}.hover-light-green:focus,.hover-light-green:hover{color:#9eebcf}.hover-navy:focus,.hover-navy:hover{color:#001b44}.hover-dark-blue:focus,.hover-dark-blue:hover{color:#00449e}.hover-blue:focus,.hover-blue:hover{color:#357edd}.hover-light-blue:focus,.hover-light-blue:hover{color:#96ccff}.hover-lightest-blue:focus,.hover-lightest-blue:hover{color:#cdecff}.hover-washed-blue:focus,.hover-washed-blue:hover{color:#f6fffe}.hover-washed-green:focus,.hover-washed-green:hover{color:#e8fdf5}.hover-washed-yellow:focus,.hover-washed-yellow:hover{color:#fffceb}.hover-washed-red:focus,.hover-washed-red:hover{color:#ffdfdf}.hover-bg-dark-red:focus,.hover-bg-dark-red:hover{background-color:#e7040f}.hover-bg-red:focus,.hover-bg-red:hover{background-color:#ff4136}.hover-bg-light-red:focus,.hover-bg-light-red:hover{background-color:#ff725c}.hover-bg-orange:focus,.hover-bg-orange:hover{background-color:#ff6300}.hover-bg-gold:focus,.hover-bg-gold:hover{background-color:#ffb700}.hover-bg-yellow:focus,.hover-bg-yellow:hover{background-color:gold}.hover-bg-light-yellow:focus,.hover-bg-light-yellow:hover{background-color:#fbf1a9}.hover-bg-purple:focus,.hover-bg-purple:hover{background-color:#5e2ca5}.hover-bg-light-purple:focus,.hover-bg-light-purple:hover{background-color:#a463f2}.hover-bg-dark-pink:focus,.hover-bg-dark-pink:hover{background-color:#d5008f}.hover-bg-hot-pink:focus,.hover-bg-hot-pink:hover{background-color:#ff41b4}.hover-bg-pink:focus,.hover-bg-pink:hover{background-color:#ff80cc}.hover-bg-light-pink:focus,.hover-bg-light-pink:hover{background-color:#ffa3d7}.hover-bg-dark-green:focus,.hover-bg-dark-green:hover{background-color:#137752}.hover-bg-green:focus,.hover-bg-green:hover{background-color:#19a974}.hover-bg-light-green:focus,.hover-bg-light-green:hover{background-color:#9eebcf}.hover-bg-navy:focus,.hover-bg-navy:hover{background-color:#001b44}.hover-bg-dark-blue:focus,.hover-bg-dark-blue:hover{background-color:#00449e}.hover-bg-blue:focus,.hover-bg-blue:hover{background-color:#357edd}.hover-bg-light-blue:focus,.hover-bg-light-blue:hover{background-color:#96ccff}.hover-bg-lightest-blue:focus,.hover-bg-lightest-blue:hover{background-color:#cdecff}.hover-bg-washed-blue:focus,.hover-bg-washed-blue:hover{background-color:#f6fffe}.hover-bg-washed-green:focus,.hover-bg-washed-green:hover{background-color:#e8fdf5}.hover-bg-washed-yellow:focus,.hover-bg-washed-yellow:hover{background-color:#fffceb}.hover-bg-washed-red:focus,.hover-bg-washed-red:hover{background-color:#ffdfdf}.hover-bg-inherit:focus,.hover-bg-inherit:hover{background-color:inherit}.pa0{padding:0}.pa1{padding:.25rem}.pa2{padding:.5rem}.pa3{padding:1rem}.pa4{padding:2rem}.pa5{padding:4rem}.pa6{padding:8rem}.pa7{padding:16rem}.pl0{padding-left:0}.pl1{padding-left:.25rem}.pl2{padding-left:.5rem}.pl3{padding-left:1rem}.pl4{padding-left:2rem}.pl5{padding-left:4rem}.pl6{padding-left:8rem}.pl7{padding-left:16rem}.pr0{padding-right:0}.pr1{padding-right:.25rem}.pr2{padding-right:.5rem}.pr3{padding-right:1rem}.pr4{padding-right:2rem}.pr5{padding-right:4rem}.pr6{padding-right:8rem}.pr7{padding-right:16rem}.pb0{padding-bottom:0}.pb1{padding-bottom:.25rem}.pb2{padding-bottom:.5rem}.pb3{padding-bottom:1rem}.pb4{padding-bottom:2rem}.pb5{padding-bottom:4rem}.pb6{padding-bottom:8rem}.pb7{padding-bottom:16rem}.pt0{padding-top:0}.pt1{padding-top:.25rem}.pt2{padding-top:.5rem}.pt3{padding-top:1rem}.pt4{padding-top:2rem}.pt5{padding-top:4rem}.pt6{padding-top:8rem}.pt7{padding-top:16rem}.pv0{padding-top:0;padding-bottom:0}.pv1{padding-top:.25rem;padding-bottom:.25rem}.pv2{padding-top:.5rem;padding-bottom:.5rem}.pv3{padding-top:1rem;padding-bottom:1rem}.pv4{padding-top:2rem;padding-bottom:2rem}.pv5{padding-top:4rem;padding-bottom:4rem}.pv6{padding-top:8rem;padding-bottom:8rem}.pv7{padding-top:16rem;padding-bottom:16rem}.ph0{padding-left:0;padding-right:0}.ph1{padding-left:.25rem;padding-right:.25rem}.ph2{padding-left:.5rem;padding-right:.5rem}.ph3{padding-left:1rem;padding-right:1rem}.ph4{padding-left:2rem;padding-right:2rem}.ph5{padding-left:4rem;padding-right:4rem}.ph6{padding-left:8rem;padding-right:8rem}.ph7{padding-left:16rem;padding-right:16rem}.ma0{margin:0}.ma1{margin:.25rem}.ma2{margin:.5rem}.ma3{margin:1rem}.ma4{margin:2rem}.ma5{margin:4rem}.ma6{margin:8rem}.ma7{margin:16rem}.ml0{margin-left:0}.ml1{margin-left:.25rem}.ml2{margin-left:.5rem}.ml3{margin-left:1rem}.ml4{margin-left:2rem}.ml5{margin-left:4rem}.ml6{margin-left:8rem}.ml7{margin-left:16rem}.mr0{margin-right:0}.mr1{margin-right:.25rem}.mr2{margin-right:.5rem}.mr3{margin-right:1rem}.mr4{margin-right:2rem}.mr5{margin-right:4rem}.mr6{margin-right:8rem}.mr7{margin-right:16rem}.mb0{margin-bottom:0}.mb1{margin-bottom:.25rem}.mb2{margin-bottom:.5rem}.mb3{margin-bottom:1rem}.mb4{margin-bottom:2rem}.mb5{margin-bottom:4rem}.mb6{margin-bottom:8rem}.mb7{margin-bottom:16rem}.mt0{margin-top:0}.mt1{margin-top:.25rem}.mt2{margin-top:.5rem}.mt3{margin-top:1rem}.mt4{margin-top:2rem}.mt5{margin-top:4rem}.mt6{margin-top:8rem}.mt7{margin-top:16rem}.mv0{margin-top:0;margin-bottom:0}.mv1{margin-top:.25rem;margin-bottom:.25rem}.mv2{margin-top:.5rem;margin-bottom:.5rem}.mv3{margin-top:1rem;margin-bottom:1rem}.mv4{margin-top:2rem;margin-bottom:2rem}.mv5{margin-top:4rem;margin-bottom:4rem}.mv6{margin-top:8rem;margin-bottom:8rem}.mv7{margin-top:16rem;margin-bottom:16rem}.mh0{margin-left:0;margin-right:0}.mh1{margin-left:.25rem;margin-right:.25rem}.mh2{margin-left:.5rem;margin-right:.5rem}.mh3{margin-left:1rem;margin-right:1rem}.mh4{margin-left:2rem;margin-right:2rem}.mh5{margin-left:4rem;margin-right:4rem}.mh6{margin-left:8rem;margin-right:8rem}.mh7{margin-left:16rem;margin-right:16rem}.na1{margin:-.25rem}.na2{margin:-.5rem}.na3{margin:-1rem}.na4{margin:-2rem}.na5{margin:-4rem}.na6{margin:-8rem}.na7{margin:-16rem}.nl1{margin-left:-.25rem}.nl2{margin-left:-.5rem}.nl3{margin-left:-1rem}.nl4{margin-left:-2rem}.nl5{margin-left:-4rem}.nl6{margin-left:-8rem}.nl7{margin-left:-16rem}.nr1{margin-right:-.25rem}.nr2{margin-right:-.5rem}.nr3{margin-right:-1rem}.nr4{margin-right:-2rem}.nr5{margin-right:-4rem}.nr6{margin-right:-8rem}.nr7{margin-right:-16rem}.nb1{margin-bottom:-.25rem}.nb2{margin-bottom:-.5rem}.nb3{margin-bottom:-1rem}.nb4{margin-bottom:-2rem}.nb5{margin-bottom:-4rem}.nb6{margin-bottom:-8rem}.nb7{margin-bottom:-16rem}.nt1{margin-top:-.25rem}.nt2{margin-top:-.5rem}.nt3{margin-top:-1rem}.nt4{margin-top:-2rem}.nt5{margin-top:-4rem}.nt6{margin-top:-8rem}.nt7{margin-top:-16rem}.collapse{border-collapse:collapse;border-spacing:0}.striped--light-silver:nth-child(odd){background-color:#aaa}.striped--moon-gray:nth-child(odd){background-color:#ccc}.striped--light-gray:nth-child(odd){background-color:#eee}.striped--near-white:nth-child(odd){background-color:#f4f4f4}.stripe-light:nth-child(odd){background-color:hsla(0,0%,100%,.1)}.stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.strike{text-decoration:line-through}.underline{text-decoration:underline}.no-underline{text-decoration:none}.tl{text-align:left}.tr{text-align:right}.tc{text-align:center}.tj{text-align:justify}.ttc{text-transform:capitalize}.ttl{text-transform:lowercase}.ttu{text-transform:uppercase}.ttn{text-transform:none}.f-6,.f-headline{font-size:6rem}.f-5,.f-subheadline{font-size:5rem}.f1{font-size:3rem}.f2{font-size:2.25rem}.f3{font-size:1.5rem}.f4{font-size:1.25rem}.f5{font-size:1rem}.f6{font-size:.875rem}.f7{font-size:.75rem}.measure{max-width:30em}.measure-wide{max-width:34em}.measure-narrow{max-width:20em}.indent{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps{font-variant:small-caps}.truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.overflow-container{overflow-y:scroll}.center{margin-left:auto}.center,.mr-auto{margin-right:auto}.ml-auto{margin-left:auto}.clip{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal{white-space:normal}.nowrap{white-space:nowrap}.pre{white-space:pre}.v-base{vertical-align:baseline}.v-mid{vertical-align:middle}.v-top{vertical-align:top}.v-btm{vertical-align:bottom}.dim{opacity:1}.dim,.dim:focus,.dim:hover{transition:opacity .15s ease-in}.dim:focus,.dim:hover{opacity:.5}.dim:active{opacity:.8;transition:opacity .15s ease-out}.glow,.glow:focus,.glow:hover{transition:opacity .15s ease-in}.glow:focus,.glow:hover{opacity:1}.hide-child .child{opacity:0;transition:opacity .15s ease-in}.hide-child:active .child,.hide-child:focus .child,.hide-child:hover .child{opacity:1;transition:opacity .15s ease-in}.underline-hover:focus,.underline-hover:hover{text-decoration:underline}.grow{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);transition:-webkit-transform .25s ease-out;transition:transform .25s ease-out;transition:transform .25s ease-out,-webkit-transform .25s ease-out}.grow:focus,.grow:hover{-webkit-transform:scale(1.05);transform:scale(1.05)}.grow:active{-webkit-transform:scale(.9);transform:scale(.9)}.grow-large{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);transition:-webkit-transform .25s ease-in-out;transition:transform .25s ease-in-out;transition:transform .25s ease-in-out,-webkit-transform .25s ease-in-out}.grow-large:focus,.grow-large:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.grow-large:active{-webkit-transform:scale(.95);transform:scale(.95)}.pointer:hover,.shadow-hover{cursor:pointer}.shadow-hover{position:relative;transition:all .5s cubic-bezier(.165,.84,.44,1)}.shadow-hover:after{content:"";box-shadow:0 0 16px 2px rgba(0,0,0,.2);border-radius:inherit;opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:-1;transition:opacity .5s cubic-bezier(.165,.84,.44,1)}.shadow-hover:focus:after,.shadow-hover:hover:after{opacity:1}.bg-animate,.bg-animate:focus,.bg-animate:hover{transition:background-color .15s ease-in-out}.z-0{z-index:0}.z-1{z-index:1}.z-2{z-index:2}.z-3{z-index:3}.z-4{z-index:4}.z-5{z-index:5}.z-999{z-index:999}.z-9999{z-index:9999}.z-max{z-index:2147483647}.z-inherit{z-index:inherit}.z-initial{z-index:auto}.z-unset{z-index:unset}.nested-copy-line-height ol,.nested-copy-line-height p,.nested-copy-line-height ul{line-height:1.5}.nested-headline-line-height h1,.nested-headline-line-height h2,.nested-headline-line-height h3,.nested-headline-line-height h4,.nested-headline-line-height h5,.nested-headline-line-height h6{line-height:1.25}.nested-list-reset ol,.nested-list-reset ul{padding-left:0;margin-left:0;list-style-type:none}.nested-copy-indent p+p{text-indent:1em;margin-top:0;margin-bottom:0}.nested-copy-separator p+p{margin-top:1.5em}.nested-img img{width:100%;max-width:100%;display:block}.nested-links a{color:#357edd;transition:color .15s ease-in}.nested-links a:focus,.nested-links a:hover{color:#96ccff;transition:color .15s ease-in}.debug *{outline:1px solid gold}.debug-white *{outline:1px solid #fff}.debug-black *{outline:1px solid #000}.debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFElEQVR4AWPAC97/9x0eCsAEPgwAVLshdpENIxcAAAAASUVORK5CYII=) repeat 0 0}.debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR4AWOgCLz/b0epAa6UGuBOqQHOQHLUgFEDnAbcBZ4UGwDOkiCnkIhdgNgNxAYAiYlD+8sEuo8AAAAASUVORK5CYII=) repeat 0 0}.debug-grid-8-solid{background:#fff url(data:image/gif;base64,R0lGODdhCAAIAPEAAADw/wDx/////wAAACwAAAAACAAIAAACDZQvgaeb/lxbAIKA8y0AOw==) repeat 0 0}.debug-grid-16-solid{background:#fff url(data:image/gif;base64,R0lGODdhEAAQAPEAAADw/wDx/xXy/////ywAAAAAEAAQAAACIZyPKckYDQFsb6ZqD85jZ2+BkwiRFKehhqQCQgDHcgwEBQA7) repeat 0 0}@media screen and (min-width:35em){.aspect-ratio-ns{height:0;position:relative}.aspect-ratio--16x9-ns{padding-bottom:56.25%}.aspect-ratio--9x16-ns{padding-bottom:177.77%}.aspect-ratio--4x3-ns{padding-bottom:75%}.aspect-ratio--3x4-ns{padding-bottom:133.33%}.aspect-ratio--6x4-ns{padding-bottom:66.6%}.aspect-ratio--4x6-ns{padding-bottom:150%}.aspect-ratio--8x5-ns{padding-bottom:62.5%}.aspect-ratio--5x8-ns{padding-bottom:160%}.aspect-ratio--7x5-ns{padding-bottom:71.42%}.aspect-ratio--5x7-ns{padding-bottom:140%}.aspect-ratio--1x1-ns{padding-bottom:100%}.aspect-ratio--object-ns{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-ns{background-size:cover!important}.contain-ns{background-size:contain!important}.bg-center-ns{background-position:50%}.bg-center-ns,.bg-top-ns{background-repeat:no-repeat}.bg-top-ns{background-position:top}.bg-right-ns{background-position:100%}.bg-bottom-ns,.bg-right-ns{background-repeat:no-repeat}.bg-bottom-ns{background-position:bottom}.bg-left-ns{background-repeat:no-repeat;background-position:0}.outline-ns{outline:1px solid}.outline-transparent-ns{outline:1px solid transparent}.outline-0-ns{outline:0}.ba-ns{border-style:solid;border-width:1px}.bt-ns{border-top-style:solid;border-top-width:1px}.br-ns{border-right-style:solid;border-right-width:1px}.bb-ns{border-bottom-style:solid;border-bottom-width:1px}.bl-ns{border-left-style:solid;border-left-width:1px}.bn-ns{border-style:none;border-width:0}.br0-ns{border-radius:0}.br1-ns{border-radius:.125rem}.br2-ns{border-radius:.25rem}.br3-ns{border-radius:.5rem}.br4-ns{border-radius:1rem}.br-100-ns{border-radius:100%}.br-pill-ns{border-radius:9999px}.br--bottom-ns{border-top-left-radius:0;border-top-right-radius:0}.br--top-ns{border-bottom-right-radius:0}.br--right-ns,.br--top-ns{border-bottom-left-radius:0}.br--right-ns{border-top-left-radius:0}.br--left-ns{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-ns{border-radius:inherit}.br-initial-ns{border-radius:initial}.br-unset-ns{border-radius:unset}.b--dotted-ns{border-style:dotted}.b--dashed-ns{border-style:dashed}.b--solid-ns{border-style:solid}.b--none-ns{border-style:none}.bw0-ns{border-width:0}.bw1-ns{border-width:.125rem}.bw2-ns{border-width:.25rem}.bw3-ns{border-width:.5rem}.bw4-ns{border-width:1rem}.bw5-ns{border-width:2rem}.bt-0-ns{border-top-width:0}.br-0-ns{border-right-width:0}.bb-0-ns{border-bottom-width:0}.bl-0-ns{border-left-width:0}.shadow-1-ns{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-ns{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-ns{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-ns{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-ns{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-ns{top:0}.left-0-ns{left:0}.right-0-ns{right:0}.bottom-0-ns{bottom:0}.top-1-ns{top:1rem}.left-1-ns{left:1rem}.right-1-ns{right:1rem}.bottom-1-ns{bottom:1rem}.top-2-ns{top:2rem}.left-2-ns{left:2rem}.right-2-ns{right:2rem}.bottom-2-ns{bottom:2rem}.top--1-ns{top:-1rem}.right--1-ns{right:-1rem}.bottom--1-ns{bottom:-1rem}.left--1-ns{left:-1rem}.top--2-ns{top:-2rem}.right--2-ns{right:-2rem}.bottom--2-ns{bottom:-2rem}.left--2-ns{left:-2rem}.absolute--fill-ns{top:0;right:0;bottom:0;left:0}.cl-ns{clear:left}.cr-ns{clear:right}.cb-ns{clear:both}.cn-ns{clear:none}.dn-ns{display:none}.di-ns{display:inline}.db-ns{display:block}.dib-ns{display:inline-block}.dit-ns{display:inline-table}.dt-ns{display:table}.dtc-ns{display:table-cell}.dt-row-ns{display:table-row}.dt-row-group-ns{display:table-row-group}.dt-column-ns{display:table-column}.dt-column-group-ns{display:table-column-group}.dt--fixed-ns{table-layout:fixed;width:100%}.flex-ns{display:flex}.inline-flex-ns{display:inline-flex}.flex-auto-ns{flex:1 1 auto;min-width:0;min-height:0}.flex-none-ns{flex:none}.flex-column-ns{flex-direction:column}.flex-row-ns{flex-direction:row}.flex-wrap-ns{flex-wrap:wrap}.flex-nowrap-ns{flex-wrap:nowrap}.flex-wrap-reverse-ns{flex-wrap:wrap-reverse}.flex-column-reverse-ns{flex-direction:column-reverse}.flex-row-reverse-ns{flex-direction:row-reverse}.items-start-ns{align-items:flex-start}.items-end-ns{align-items:flex-end}.items-center-ns{align-items:center}.items-baseline-ns{align-items:baseline}.items-stretch-ns{align-items:stretch}.self-start-ns{align-self:flex-start}.self-end-ns{align-self:flex-end}.self-center-ns{align-self:center}.self-baseline-ns{align-self:baseline}.self-stretch-ns{align-self:stretch}.justify-start-ns{justify-content:flex-start}.justify-end-ns{justify-content:flex-end}.justify-center-ns{justify-content:center}.justify-between-ns{justify-content:space-between}.justify-around-ns{justify-content:space-around}.content-start-ns{align-content:flex-start}.content-end-ns{align-content:flex-end}.content-center-ns{align-content:center}.content-between-ns{align-content:space-between}.content-around-ns{align-content:space-around}.content-stretch-ns{align-content:stretch}.order-0-ns{order:0}.order-1-ns{order:1}.order-2-ns{order:2}.order-3-ns{order:3}.order-4-ns{order:4}.order-5-ns{order:5}.order-6-ns{order:6}.order-7-ns{order:7}.order-8-ns{order:8}.order-last-ns{order:99999}.flex-grow-0-ns{flex-grow:0}.flex-grow-1-ns{flex-grow:1}.flex-shrink-0-ns{flex-shrink:0}.flex-shrink-1-ns{flex-shrink:1}.fl-ns{float:left}.fl-ns,.fr-ns{_display:inline}.fr-ns{float:right}.fn-ns{float:none}.i-ns{font-style:italic}.fs-normal-ns{font-style:normal}.normal-ns{font-weight:400}.b-ns{font-weight:700}.fw1-ns{font-weight:100}.fw2-ns{font-weight:200}.fw3-ns{font-weight:300}.fw4-ns{font-weight:400}.fw5-ns{font-weight:500}.fw6-ns{font-weight:600}.fw7-ns{font-weight:700}.fw8-ns{font-weight:800}.fw9-ns{font-weight:900}.h1-ns{height:1rem}.h2-ns{height:2rem}.h3-ns{height:4rem}.h4-ns{height:8rem}.h5-ns{height:16rem}.h-25-ns{height:25%}.h-50-ns{height:50%}.h-75-ns{height:75%}.h-100-ns{height:100%}.min-h-100-ns{min-height:100%}.vh-25-ns{height:25vh}.vh-50-ns{height:50vh}.vh-75-ns{height:75vh}.vh-100-ns{height:100vh}.min-vh-100-ns{min-height:100vh}.h-auto-ns{height:auto}.h-inherit-ns{height:inherit}.tracked-ns{letter-spacing:.1em}.tracked-tight-ns{letter-spacing:-.05em}.tracked-mega-ns{letter-spacing:.25em}.lh-solid-ns{line-height:1}.lh-title-ns{line-height:1.25}.lh-copy-ns{line-height:1.5}.mw-100-ns{max-width:100%}.mw1-ns{max-width:1rem}.mw2-ns{max-width:2rem}.mw3-ns{max-width:4rem}.mw4-ns{max-width:8rem}.mw5-ns{max-width:16rem}.mw6-ns{max-width:32rem}.mw7-ns{max-width:48rem}.mw8-ns{max-width:64rem}.mw9-ns{max-width:96rem}.mw-none-ns{max-width:none}.w1-ns{width:1rem}.w2-ns{width:2rem}.w3-ns{width:4rem}.w4-ns{width:8rem}.w5-ns{width:16rem}.w-10-ns{width:10%}.w-20-ns{width:20%}.w-25-ns{width:25%}.w-30-ns{width:30%}.w-33-ns{width:33%}.w-34-ns{width:34%}.w-40-ns{width:40%}.w-50-ns{width:50%}.w-60-ns{width:60%}.w-70-ns{width:70%}.w-75-ns{width:75%}.w-80-ns{width:80%}.w-90-ns{width:90%}.w-100-ns{width:100%}.w-third-ns{width:33.33333%}.w-two-thirds-ns{width:66.66667%}.w-auto-ns{width:auto}.overflow-visible-ns{overflow:visible}.overflow-hidden-ns{overflow:hidden}.overflow-scroll-ns{overflow:scroll}.overflow-auto-ns{overflow:auto}.overflow-x-visible-ns{overflow-x:visible}.overflow-x-hidden-ns{overflow-x:hidden}.overflow-x-scroll-ns{overflow-x:scroll}.overflow-x-auto-ns{overflow-x:auto}.overflow-y-visible-ns{overflow-y:visible}.overflow-y-hidden-ns{overflow-y:hidden}.overflow-y-scroll-ns{overflow-y:scroll}.overflow-y-auto-ns{overflow-y:auto}.static-ns{position:static}.relative-ns{position:relative}.absolute-ns{position:absolute}.fixed-ns{position:fixed}.rotate-45-ns{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-ns{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-ns{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-ns{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-ns{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-ns{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-ns{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-ns{padding:0}.pa1-ns{padding:.25rem}.pa2-ns{padding:.5rem}.pa3-ns{padding:1rem}.pa4-ns{padding:2rem}.pa5-ns{padding:4rem}.pa6-ns{padding:8rem}.pa7-ns{padding:16rem}.pl0-ns{padding-left:0}.pl1-ns{padding-left:.25rem}.pl2-ns{padding-left:.5rem}.pl3-ns{padding-left:1rem}.pl4-ns{padding-left:2rem}.pl5-ns{padding-left:4rem}.pl6-ns{padding-left:8rem}.pl7-ns{padding-left:16rem}.pr0-ns{padding-right:0}.pr1-ns{padding-right:.25rem}.pr2-ns{padding-right:.5rem}.pr3-ns{padding-right:1rem}.pr4-ns{padding-right:2rem}.pr5-ns{padding-right:4rem}.pr6-ns{padding-right:8rem}.pr7-ns{padding-right:16rem}.pb0-ns{padding-bottom:0}.pb1-ns{padding-bottom:.25rem}.pb2-ns{padding-bottom:.5rem}.pb3-ns{padding-bottom:1rem}.pb4-ns{padding-bottom:2rem}.pb5-ns{padding-bottom:4rem}.pb6-ns{padding-bottom:8rem}.pb7-ns{padding-bottom:16rem}.pt0-ns{padding-top:0}.pt1-ns{padding-top:.25rem}.pt2-ns{padding-top:.5rem}.pt3-ns{padding-top:1rem}.pt4-ns{padding-top:2rem}.pt5-ns{padding-top:4rem}.pt6-ns{padding-top:8rem}.pt7-ns{padding-top:16rem}.pv0-ns{padding-top:0;padding-bottom:0}.pv1-ns{padding-top:.25rem;padding-bottom:.25rem}.pv2-ns{padding-top:.5rem;padding-bottom:.5rem}.pv3-ns{padding-top:1rem;padding-bottom:1rem}.pv4-ns{padding-top:2rem;padding-bottom:2rem}.pv5-ns{padding-top:4rem;padding-bottom:4rem}.pv6-ns{padding-top:8rem;padding-bottom:8rem}.pv7-ns{padding-top:16rem;padding-bottom:16rem}.ph0-ns{padding-left:0;padding-right:0}.ph1-ns{padding-left:.25rem;padding-right:.25rem}.ph2-ns{padding-left:.5rem;padding-right:.5rem}.ph3-ns{padding-left:1rem;padding-right:1rem}.ph4-ns{padding-left:2rem;padding-right:2rem}.ph5-ns{padding-left:4rem;padding-right:4rem}.ph6-ns{padding-left:8rem;padding-right:8rem}.ph7-ns{padding-left:16rem;padding-right:16rem}.ma0-ns{margin:0}.ma1-ns{margin:.25rem}.ma2-ns{margin:.5rem}.ma3-ns{margin:1rem}.ma4-ns{margin:2rem}.ma5-ns{margin:4rem}.ma6-ns{margin:8rem}.ma7-ns{margin:16rem}.ml0-ns{margin-left:0}.ml1-ns{margin-left:.25rem}.ml2-ns{margin-left:.5rem}.ml3-ns{margin-left:1rem}.ml4-ns{margin-left:2rem}.ml5-ns{margin-left:4rem}.ml6-ns{margin-left:8rem}.ml7-ns{margin-left:16rem}.mr0-ns{margin-right:0}.mr1-ns{margin-right:.25rem}.mr2-ns{margin-right:.5rem}.mr3-ns{margin-right:1rem}.mr4-ns{margin-right:2rem}.mr5-ns{margin-right:4rem}.mr6-ns{margin-right:8rem}.mr7-ns{margin-right:16rem}.mb0-ns{margin-bottom:0}.mb1-ns{margin-bottom:.25rem}.mb2-ns{margin-bottom:.5rem}.mb3-ns{margin-bottom:1rem}.mb4-ns{margin-bottom:2rem}.mb5-ns{margin-bottom:4rem}.mb6-ns{margin-bottom:8rem}.mb7-ns{margin-bottom:16rem}.mt0-ns{margin-top:0}.mt1-ns{margin-top:.25rem}.mt2-ns{margin-top:.5rem}.mt3-ns{margin-top:1rem}.mt4-ns{margin-top:2rem}.mt5-ns{margin-top:4rem}.mt6-ns{margin-top:8rem}.mt7-ns{margin-top:16rem}.mv0-ns{margin-top:0;margin-bottom:0}.mv1-ns{margin-top:.25rem;margin-bottom:.25rem}.mv2-ns{margin-top:.5rem;margin-bottom:.5rem}.mv3-ns{margin-top:1rem;margin-bottom:1rem}.mv4-ns{margin-top:2rem;margin-bottom:2rem}.mv5-ns{margin-top:4rem;margin-bottom:4rem}.mv6-ns{margin-top:8rem;margin-bottom:8rem}.mv7-ns{margin-top:16rem;margin-bottom:16rem}.mh0-ns{margin-left:0;margin-right:0}.mh1-ns{margin-left:.25rem;margin-right:.25rem}.mh2-ns{margin-left:.5rem;margin-right:.5rem}.mh3-ns{margin-left:1rem;margin-right:1rem}.mh4-ns{margin-left:2rem;margin-right:2rem}.mh5-ns{margin-left:4rem;margin-right:4rem}.mh6-ns{margin-left:8rem;margin-right:8rem}.mh7-ns{margin-left:16rem;margin-right:16rem}.na1-ns{margin:-.25rem}.na2-ns{margin:-.5rem}.na3-ns{margin:-1rem}.na4-ns{margin:-2rem}.na5-ns{margin:-4rem}.na6-ns{margin:-8rem}.na7-ns{margin:-16rem}.nl1-ns{margin-left:-.25rem}.nl2-ns{margin-left:-.5rem}.nl3-ns{margin-left:-1rem}.nl4-ns{margin-left:-2rem}.nl5-ns{margin-left:-4rem}.nl6-ns{margin-left:-8rem}.nl7-ns{margin-left:-16rem}.nr1-ns{margin-right:-.25rem}.nr2-ns{margin-right:-.5rem}.nr3-ns{margin-right:-1rem}.nr4-ns{margin-right:-2rem}.nr5-ns{margin-right:-4rem}.nr6-ns{margin-right:-8rem}.nr7-ns{margin-right:-16rem}.nb1-ns{margin-bottom:-.25rem}.nb2-ns{margin-bottom:-.5rem}.nb3-ns{margin-bottom:-1rem}.nb4-ns{margin-bottom:-2rem}.nb5-ns{margin-bottom:-4rem}.nb6-ns{margin-bottom:-8rem}.nb7-ns{margin-bottom:-16rem}.nt1-ns{margin-top:-.25rem}.nt2-ns{margin-top:-.5rem}.nt3-ns{margin-top:-1rem}.nt4-ns{margin-top:-2rem}.nt5-ns{margin-top:-4rem}.nt6-ns{margin-top:-8rem}.nt7-ns{margin-top:-16rem}.strike-ns{text-decoration:line-through}.underline-ns{text-decoration:underline}.no-underline-ns{text-decoration:none}.tl-ns{text-align:left}.tr-ns{text-align:right}.tc-ns{text-align:center}.tj-ns{text-align:justify}.ttc-ns{text-transform:capitalize}.ttl-ns{text-transform:lowercase}.ttu-ns{text-transform:uppercase}.ttn-ns{text-transform:none}.f-6-ns,.f-headline-ns{font-size:6rem}.f-5-ns,.f-subheadline-ns{font-size:5rem}.f1-ns{font-size:3rem}.f2-ns{font-size:2.25rem}.f3-ns{font-size:1.5rem}.f4-ns{font-size:1.25rem}.f5-ns{font-size:1rem}.f6-ns{font-size:.875rem}.f7-ns{font-size:.75rem}.measure-ns{max-width:30em}.measure-wide-ns{max-width:34em}.measure-narrow-ns{max-width:20em}.indent-ns{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-ns{font-variant:small-caps}.truncate-ns{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-ns{margin-left:auto}.center-ns,.mr-auto-ns{margin-right:auto}.ml-auto-ns{margin-left:auto}.clip-ns{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-ns{white-space:normal}.nowrap-ns{white-space:nowrap}.pre-ns{white-space:pre}.v-base-ns{vertical-align:baseline}.v-mid-ns{vertical-align:middle}.v-top-ns{vertical-align:top}.v-btm-ns{vertical-align:bottom}}@media screen and (min-width:35em) and (max-width:60em){.aspect-ratio-m{height:0;position:relative}.aspect-ratio--16x9-m{padding-bottom:56.25%}.aspect-ratio--9x16-m{padding-bottom:177.77%}.aspect-ratio--4x3-m{padding-bottom:75%}.aspect-ratio--3x4-m{padding-bottom:133.33%}.aspect-ratio--6x4-m{padding-bottom:66.6%}.aspect-ratio--4x6-m{padding-bottom:150%}.aspect-ratio--8x5-m{padding-bottom:62.5%}.aspect-ratio--5x8-m{padding-bottom:160%}.aspect-ratio--7x5-m{padding-bottom:71.42%}.aspect-ratio--5x7-m{padding-bottom:140%}.aspect-ratio--1x1-m{padding-bottom:100%}.aspect-ratio--object-m{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-m{background-size:cover!important}.contain-m{background-size:contain!important}.bg-center-m{background-position:50%}.bg-center-m,.bg-top-m{background-repeat:no-repeat}.bg-top-m{background-position:top}.bg-right-m{background-position:100%}.bg-bottom-m,.bg-right-m{background-repeat:no-repeat}.bg-bottom-m{background-position:bottom}.bg-left-m{background-repeat:no-repeat;background-position:0}.outline-m{outline:1px solid}.outline-transparent-m{outline:1px solid transparent}.outline-0-m{outline:0}.ba-m{border-style:solid;border-width:1px}.bt-m{border-top-style:solid;border-top-width:1px}.br-m{border-right-style:solid;border-right-width:1px}.bb-m{border-bottom-style:solid;border-bottom-width:1px}.bl-m{border-left-style:solid;border-left-width:1px}.bn-m{border-style:none;border-width:0}.br0-m{border-radius:0}.br1-m{border-radius:.125rem}.br2-m{border-radius:.25rem}.br3-m{border-radius:.5rem}.br4-m{border-radius:1rem}.br-100-m{border-radius:100%}.br-pill-m{border-radius:9999px}.br--bottom-m{border-top-left-radius:0;border-top-right-radius:0}.br--top-m{border-bottom-right-radius:0}.br--right-m,.br--top-m{border-bottom-left-radius:0}.br--right-m{border-top-left-radius:0}.br--left-m{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-m{border-radius:inherit}.br-initial-m{border-radius:initial}.br-unset-m{border-radius:unset}.b--dotted-m{border-style:dotted}.b--dashed-m{border-style:dashed}.b--solid-m{border-style:solid}.b--none-m{border-style:none}.bw0-m{border-width:0}.bw1-m{border-width:.125rem}.bw2-m{border-width:.25rem}.bw3-m{border-width:.5rem}.bw4-m{border-width:1rem}.bw5-m{border-width:2rem}.bt-0-m{border-top-width:0}.br-0-m{border-right-width:0}.bb-0-m{border-bottom-width:0}.bl-0-m{border-left-width:0}.shadow-1-m{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-m{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-m{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-m{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-m{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-m{top:0}.left-0-m{left:0}.right-0-m{right:0}.bottom-0-m{bottom:0}.top-1-m{top:1rem}.left-1-m{left:1rem}.right-1-m{right:1rem}.bottom-1-m{bottom:1rem}.top-2-m{top:2rem}.left-2-m{left:2rem}.right-2-m{right:2rem}.bottom-2-m{bottom:2rem}.top--1-m{top:-1rem}.right--1-m{right:-1rem}.bottom--1-m{bottom:-1rem}.left--1-m{left:-1rem}.top--2-m{top:-2rem}.right--2-m{right:-2rem}.bottom--2-m{bottom:-2rem}.left--2-m{left:-2rem}.absolute--fill-m{top:0;right:0;bottom:0;left:0}.cl-m{clear:left}.cr-m{clear:right}.cb-m{clear:both}.cn-m{clear:none}.dn-m{display:none}.di-m{display:inline}.db-m{display:block}.dib-m{display:inline-block}.dit-m{display:inline-table}.dt-m{display:table}.dtc-m{display:table-cell}.dt-row-m{display:table-row}.dt-row-group-m{display:table-row-group}.dt-column-m{display:table-column}.dt-column-group-m{display:table-column-group}.dt--fixed-m{table-layout:fixed;width:100%}.flex-m{display:flex}.inline-flex-m{display:inline-flex}.flex-auto-m{flex:1 1 auto;min-width:0;min-height:0}.flex-none-m{flex:none}.flex-column-m{flex-direction:column}.flex-row-m{flex-direction:row}.flex-wrap-m{flex-wrap:wrap}.flex-nowrap-m{flex-wrap:nowrap}.flex-wrap-reverse-m{flex-wrap:wrap-reverse}.flex-column-reverse-m{flex-direction:column-reverse}.flex-row-reverse-m{flex-direction:row-reverse}.items-start-m{align-items:flex-start}.items-end-m{align-items:flex-end}.items-center-m{align-items:center}.items-baseline-m{align-items:baseline}.items-stretch-m{align-items:stretch}.self-start-m{align-self:flex-start}.self-end-m{align-self:flex-end}.self-center-m{align-self:center}.self-baseline-m{align-self:baseline}.self-stretch-m{align-self:stretch}.justify-start-m{justify-content:flex-start}.justify-end-m{justify-content:flex-end}.justify-center-m{justify-content:center}.justify-between-m{justify-content:space-between}.justify-around-m{justify-content:space-around}.content-start-m{align-content:flex-start}.content-end-m{align-content:flex-end}.content-center-m{align-content:center}.content-between-m{align-content:space-between}.content-around-m{align-content:space-around}.content-stretch-m{align-content:stretch}.order-0-m{order:0}.order-1-m{order:1}.order-2-m{order:2}.order-3-m{order:3}.order-4-m{order:4}.order-5-m{order:5}.order-6-m{order:6}.order-7-m{order:7}.order-8-m{order:8}.order-last-m{order:99999}.flex-grow-0-m{flex-grow:0}.flex-grow-1-m{flex-grow:1}.flex-shrink-0-m{flex-shrink:0}.flex-shrink-1-m{flex-shrink:1}.fl-m{float:left}.fl-m,.fr-m{_display:inline}.fr-m{float:right}.fn-m{float:none}.i-m{font-style:italic}.fs-normal-m{font-style:normal}.normal-m{font-weight:400}.b-m{font-weight:700}.fw1-m{font-weight:100}.fw2-m{font-weight:200}.fw3-m{font-weight:300}.fw4-m{font-weight:400}.fw5-m{font-weight:500}.fw6-m{font-weight:600}.fw7-m{font-weight:700}.fw8-m{font-weight:800}.fw9-m{font-weight:900}.h1-m{height:1rem}.h2-m{height:2rem}.h3-m{height:4rem}.h4-m{height:8rem}.h5-m{height:16rem}.h-25-m{height:25%}.h-50-m{height:50%}.h-75-m{height:75%}.h-100-m{height:100%}.min-h-100-m{min-height:100%}.vh-25-m{height:25vh}.vh-50-m{height:50vh}.vh-75-m{height:75vh}.vh-100-m{height:100vh}.min-vh-100-m{min-height:100vh}.h-auto-m{height:auto}.h-inherit-m{height:inherit}.tracked-m{letter-spacing:.1em}.tracked-tight-m{letter-spacing:-.05em}.tracked-mega-m{letter-spacing:.25em}.lh-solid-m{line-height:1}.lh-title-m{line-height:1.25}.lh-copy-m{line-height:1.5}.mw-100-m{max-width:100%}.mw1-m{max-width:1rem}.mw2-m{max-width:2rem}.mw3-m{max-width:4rem}.mw4-m{max-width:8rem}.mw5-m{max-width:16rem}.mw6-m{max-width:32rem}.mw7-m{max-width:48rem}.mw8-m{max-width:64rem}.mw9-m{max-width:96rem}.mw-none-m{max-width:none}.w1-m{width:1rem}.w2-m{width:2rem}.w3-m{width:4rem}.w4-m{width:8rem}.w5-m{width:16rem}.w-10-m{width:10%}.w-20-m{width:20%}.w-25-m{width:25%}.w-30-m{width:30%}.w-33-m{width:33%}.w-34-m{width:34%}.w-40-m{width:40%}.w-50-m{width:50%}.w-60-m{width:60%}.w-70-m{width:70%}.w-75-m{width:75%}.w-80-m{width:80%}.w-90-m{width:90%}.w-100-m{width:100%}.w-third-m{width:33.33333%}.w-two-thirds-m{width:66.66667%}.w-auto-m{width:auto}.overflow-visible-m{overflow:visible}.overflow-hidden-m{overflow:hidden}.overflow-scroll-m{overflow:scroll}.overflow-auto-m{overflow:auto}.overflow-x-visible-m{overflow-x:visible}.overflow-x-hidden-m{overflow-x:hidden}.overflow-x-scroll-m{overflow-x:scroll}.overflow-x-auto-m{overflow-x:auto}.overflow-y-visible-m{overflow-y:visible}.overflow-y-hidden-m{overflow-y:hidden}.overflow-y-scroll-m{overflow-y:scroll}.overflow-y-auto-m{overflow-y:auto}.static-m{position:static}.relative-m{position:relative}.absolute-m{position:absolute}.fixed-m{position:fixed}.rotate-45-m{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-m{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-m{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-m{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-m{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-m{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-m{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-m{padding:0}.pa1-m{padding:.25rem}.pa2-m{padding:.5rem}.pa3-m{padding:1rem}.pa4-m{padding:2rem}.pa5-m{padding:4rem}.pa6-m{padding:8rem}.pa7-m{padding:16rem}.pl0-m{padding-left:0}.pl1-m{padding-left:.25rem}.pl2-m{padding-left:.5rem}.pl3-m{padding-left:1rem}.pl4-m{padding-left:2rem}.pl5-m{padding-left:4rem}.pl6-m{padding-left:8rem}.pl7-m{padding-left:16rem}.pr0-m{padding-right:0}.pr1-m{padding-right:.25rem}.pr2-m{padding-right:.5rem}.pr3-m{padding-right:1rem}.pr4-m{padding-right:2rem}.pr5-m{padding-right:4rem}.pr6-m{padding-right:8rem}.pr7-m{padding-right:16rem}.pb0-m{padding-bottom:0}.pb1-m{padding-bottom:.25rem}.pb2-m{padding-bottom:.5rem}.pb3-m{padding-bottom:1rem}.pb4-m{padding-bottom:2rem}.pb5-m{padding-bottom:4rem}.pb6-m{padding-bottom:8rem}.pb7-m{padding-bottom:16rem}.pt0-m{padding-top:0}.pt1-m{padding-top:.25rem}.pt2-m{padding-top:.5rem}.pt3-m{padding-top:1rem}.pt4-m{padding-top:2rem}.pt5-m{padding-top:4rem}.pt6-m{padding-top:8rem}.pt7-m{padding-top:16rem}.pv0-m{padding-top:0;padding-bottom:0}.pv1-m{padding-top:.25rem;padding-bottom:.25rem}.pv2-m{padding-top:.5rem;padding-bottom:.5rem}.pv3-m{padding-top:1rem;padding-bottom:1rem}.pv4-m{padding-top:2rem;padding-bottom:2rem}.pv5-m{padding-top:4rem;padding-bottom:4rem}.pv6-m{padding-top:8rem;padding-bottom:8rem}.pv7-m{padding-top:16rem;padding-bottom:16rem}.ph0-m{padding-left:0;padding-right:0}.ph1-m{padding-left:.25rem;padding-right:.25rem}.ph2-m{padding-left:.5rem;padding-right:.5rem}.ph3-m{padding-left:1rem;padding-right:1rem}.ph4-m{padding-left:2rem;padding-right:2rem}.ph5-m{padding-left:4rem;padding-right:4rem}.ph6-m{padding-left:8rem;padding-right:8rem}.ph7-m{padding-left:16rem;padding-right:16rem}.ma0-m{margin:0}.ma1-m{margin:.25rem}.ma2-m{margin:.5rem}.ma3-m{margin:1rem}.ma4-m{margin:2rem}.ma5-m{margin:4rem}.ma6-m{margin:8rem}.ma7-m{margin:16rem}.ml0-m{margin-left:0}.ml1-m{margin-left:.25rem}.ml2-m{margin-left:.5rem}.ml3-m{margin-left:1rem}.ml4-m{margin-left:2rem}.ml5-m{margin-left:4rem}.ml6-m{margin-left:8rem}.ml7-m{margin-left:16rem}.mr0-m{margin-right:0}.mr1-m{margin-right:.25rem}.mr2-m{margin-right:.5rem}.mr3-m{margin-right:1rem}.mr4-m{margin-right:2rem}.mr5-m{margin-right:4rem}.mr6-m{margin-right:8rem}.mr7-m{margin-right:16rem}.mb0-m{margin-bottom:0}.mb1-m{margin-bottom:.25rem}.mb2-m{margin-bottom:.5rem}.mb3-m{margin-bottom:1rem}.mb4-m{margin-bottom:2rem}.mb5-m{margin-bottom:4rem}.mb6-m{margin-bottom:8rem}.mb7-m{margin-bottom:16rem}.mt0-m{margin-top:0}.mt1-m{margin-top:.25rem}.mt2-m{margin-top:.5rem}.mt3-m{margin-top:1rem}.mt4-m{margin-top:2rem}.mt5-m{margin-top:4rem}.mt6-m{margin-top:8rem}.mt7-m{margin-top:16rem}.mv0-m{margin-top:0;margin-bottom:0}.mv1-m{margin-top:.25rem;margin-bottom:.25rem}.mv2-m{margin-top:.5rem;margin-bottom:.5rem}.mv3-m{margin-top:1rem;margin-bottom:1rem}.mv4-m{margin-top:2rem;margin-bottom:2rem}.mv5-m{margin-top:4rem;margin-bottom:4rem}.mv6-m{margin-top:8rem;margin-bottom:8rem}.mv7-m{margin-top:16rem;margin-bottom:16rem}.mh0-m{margin-left:0;margin-right:0}.mh1-m{margin-left:.25rem;margin-right:.25rem}.mh2-m{margin-left:.5rem;margin-right:.5rem}.mh3-m{margin-left:1rem;margin-right:1rem}.mh4-m{margin-left:2rem;margin-right:2rem}.mh5-m{margin-left:4rem;margin-right:4rem}.mh6-m{margin-left:8rem;margin-right:8rem}.mh7-m{margin-left:16rem;margin-right:16rem}.na1-m{margin:-.25rem}.na2-m{margin:-.5rem}.na3-m{margin:-1rem}.na4-m{margin:-2rem}.na5-m{margin:-4rem}.na6-m{margin:-8rem}.na7-m{margin:-16rem}.nl1-m{margin-left:-.25rem}.nl2-m{margin-left:-.5rem}.nl3-m{margin-left:-1rem}.nl4-m{margin-left:-2rem}.nl5-m{margin-left:-4rem}.nl6-m{margin-left:-8rem}.nl7-m{margin-left:-16rem}.nr1-m{margin-right:-.25rem}.nr2-m{margin-right:-.5rem}.nr3-m{margin-right:-1rem}.nr4-m{margin-right:-2rem}.nr5-m{margin-right:-4rem}.nr6-m{margin-right:-8rem}.nr7-m{margin-right:-16rem}.nb1-m{margin-bottom:-.25rem}.nb2-m{margin-bottom:-.5rem}.nb3-m{margin-bottom:-1rem}.nb4-m{margin-bottom:-2rem}.nb5-m{margin-bottom:-4rem}.nb6-m{margin-bottom:-8rem}.nb7-m{margin-bottom:-16rem}.nt1-m{margin-top:-.25rem}.nt2-m{margin-top:-.5rem}.nt3-m{margin-top:-1rem}.nt4-m{margin-top:-2rem}.nt5-m{margin-top:-4rem}.nt6-m{margin-top:-8rem}.nt7-m{margin-top:-16rem}.strike-m{text-decoration:line-through}.underline-m{text-decoration:underline}.no-underline-m{text-decoration:none}.tl-m{text-align:left}.tr-m{text-align:right}.tc-m{text-align:center}.tj-m{text-align:justify}.ttc-m{text-transform:capitalize}.ttl-m{text-transform:lowercase}.ttu-m{text-transform:uppercase}.ttn-m{text-transform:none}.f-6-m,.f-headline-m{font-size:6rem}.f-5-m,.f-subheadline-m{font-size:5rem}.f1-m{font-size:3rem}.f2-m{font-size:2.25rem}.f3-m{font-size:1.5rem}.f4-m{font-size:1.25rem}.f5-m{font-size:1rem}.f6-m{font-size:.875rem}.f7-m{font-size:.75rem}.measure-m{max-width:30em}.measure-wide-m{max-width:34em}.measure-narrow-m{max-width:20em}.indent-m{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-m{font-variant:small-caps}.truncate-m{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-m{margin-left:auto}.center-m,.mr-auto-m{margin-right:auto}.ml-auto-m{margin-left:auto}.clip-m{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-m{white-space:normal}.nowrap-m{white-space:nowrap}.pre-m{white-space:pre}.v-base-m{vertical-align:baseline}.v-mid-m{vertical-align:middle}.v-top-m{vertical-align:top}.v-btm-m{vertical-align:bottom}}@media screen and (min-width:60em){.aspect-ratio-l{height:0;position:relative}.aspect-ratio--16x9-l{padding-bottom:56.25%}.aspect-ratio--9x16-l{padding-bottom:177.77%}.aspect-ratio--4x3-l{padding-bottom:75%}.aspect-ratio--3x4-l{padding-bottom:133.33%}.aspect-ratio--6x4-l{padding-bottom:66.6%}.aspect-ratio--4x6-l{padding-bottom:150%}.aspect-ratio--8x5-l{padding-bottom:62.5%}.aspect-ratio--5x8-l{padding-bottom:160%}.aspect-ratio--7x5-l{padding-bottom:71.42%}.aspect-ratio--5x7-l{padding-bottom:140%}.aspect-ratio--1x1-l{padding-bottom:100%}.aspect-ratio--object-l{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-l{background-size:cover!important}.contain-l{background-size:contain!important}.bg-center-l{background-position:50%}.bg-center-l,.bg-top-l{background-repeat:no-repeat}.bg-top-l{background-position:top}.bg-right-l{background-position:100%}.bg-bottom-l,.bg-right-l{background-repeat:no-repeat}.bg-bottom-l{background-position:bottom}.bg-left-l{background-repeat:no-repeat;background-position:0}.outline-l{outline:1px solid}.outline-transparent-l{outline:1px solid transparent}.outline-0-l{outline:0}.ba-l{border-style:solid;border-width:1px}.bt-l{border-top-style:solid;border-top-width:1px}.br-l{border-right-style:solid;border-right-width:1px}.bb-l{border-bottom-style:solid;border-bottom-width:1px}.bl-l{border-left-style:solid;border-left-width:1px}.bn-l{border-style:none;border-width:0}.br0-l{border-radius:0}.br1-l{border-radius:.125rem}.br2-l{border-radius:.25rem}.br3-l{border-radius:.5rem}.br4-l{border-radius:1rem}.br-100-l{border-radius:100%}.br-pill-l{border-radius:9999px}.br--bottom-l{border-top-left-radius:0;border-top-right-radius:0}.br--top-l{border-bottom-right-radius:0}.br--right-l,.br--top-l{border-bottom-left-radius:0}.br--right-l{border-top-left-radius:0}.br--left-l{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-l{border-radius:inherit}.br-initial-l{border-radius:initial}.br-unset-l{border-radius:unset}.b--dotted-l{border-style:dotted}.b--dashed-l{border-style:dashed}.b--solid-l{border-style:solid}.b--none-l{border-style:none}.bw0-l{border-width:0}.bw1-l{border-width:.125rem}.bw2-l{border-width:.25rem}.bw3-l{border-width:.5rem}.bw4-l{border-width:1rem}.bw5-l{border-width:2rem}.bt-0-l{border-top-width:0}.br-0-l{border-right-width:0}.bb-0-l{border-bottom-width:0}.bl-0-l{border-left-width:0}.shadow-1-l{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-l{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-l{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-l{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-l{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-l{top:0}.left-0-l{left:0}.right-0-l{right:0}.bottom-0-l{bottom:0}.top-1-l{top:1rem}.left-1-l{left:1rem}.right-1-l{right:1rem}.bottom-1-l{bottom:1rem}.top-2-l{top:2rem}.left-2-l{left:2rem}.right-2-l{right:2rem}.bottom-2-l{bottom:2rem}.top--1-l{top:-1rem}.right--1-l{right:-1rem}.bottom--1-l{bottom:-1rem}.left--1-l{left:-1rem}.top--2-l{top:-2rem}.right--2-l{right:-2rem}.bottom--2-l{bottom:-2rem}.left--2-l{left:-2rem}.absolute--fill-l{top:0;right:0;bottom:0;left:0}.cl-l{clear:left}.cr-l{clear:right}.cb-l{clear:both}.cn-l{clear:none}.dn-l{display:none}.di-l{display:inline}.db-l{display:block}.dib-l{display:inline-block}.dit-l{display:inline-table}.dt-l{display:table}.dtc-l{display:table-cell}.dt-row-l{display:table-row}.dt-row-group-l{display:table-row-group}.dt-column-l{display:table-column}.dt-column-group-l{display:table-column-group}.dt--fixed-l{table-layout:fixed;width:100%}.flex-l{display:flex}.inline-flex-l{display:inline-flex}.flex-auto-l{flex:1 1 auto;min-width:0;min-height:0}.flex-none-l{flex:none}.flex-column-l{flex-direction:column}.flex-row-l{flex-direction:row}.flex-wrap-l{flex-wrap:wrap}.flex-nowrap-l{flex-wrap:nowrap}.flex-wrap-reverse-l{flex-wrap:wrap-reverse}.flex-column-reverse-l{flex-direction:column-reverse}.flex-row-reverse-l{flex-direction:row-reverse}.items-start-l{align-items:flex-start}.items-end-l{align-items:flex-end}.items-center-l{align-items:center}.items-baseline-l{align-items:baseline}.items-stretch-l{align-items:stretch}.self-start-l{align-self:flex-start}.self-end-l{align-self:flex-end}.self-center-l{align-self:center}.self-baseline-l{align-self:baseline}.self-stretch-l{align-self:stretch}.justify-start-l{justify-content:flex-start}.justify-end-l{justify-content:flex-end}.justify-center-l{justify-content:center}.justify-between-l{justify-content:space-between}.justify-around-l{justify-content:space-around}.content-start-l{align-content:flex-start}.content-end-l{align-content:flex-end}.content-center-l{align-content:center}.content-between-l{align-content:space-between}.content-around-l{align-content:space-around}.content-stretch-l{align-content:stretch}.order-0-l{order:0}.order-1-l{order:1}.order-2-l{order:2}.order-3-l{order:3}.order-4-l{order:4}.order-5-l{order:5}.order-6-l{order:6}.order-7-l{order:7}.order-8-l{order:8}.order-last-l{order:99999}.flex-grow-0-l{flex-grow:0}.flex-grow-1-l{flex-grow:1}.flex-shrink-0-l{flex-shrink:0}.flex-shrink-1-l{flex-shrink:1}.fl-l{float:left}.fl-l,.fr-l{_display:inline}.fr-l{float:right}.fn-l{float:none}.i-l{font-style:italic}.fs-normal-l{font-style:normal}.normal-l{font-weight:400}.b-l{font-weight:700}.fw1-l{font-weight:100}.fw2-l{font-weight:200}.fw3-l{font-weight:300}.fw4-l{font-weight:400}.fw5-l{font-weight:500}.fw6-l{font-weight:600}.fw7-l{font-weight:700}.fw8-l{font-weight:800}.fw9-l{font-weight:900}.h1-l{height:1rem}.h2-l{height:2rem}.h3-l{height:4rem}.h4-l{height:8rem}.h5-l{height:16rem}.h-25-l{height:25%}.h-50-l{height:50%}.h-75-l{height:75%}.h-100-l{height:100%}.min-h-100-l{min-height:100%}.vh-25-l{height:25vh}.vh-50-l{height:50vh}.vh-75-l{height:75vh}.vh-100-l{height:100vh}.min-vh-100-l{min-height:100vh}.h-auto-l{height:auto}.h-inherit-l{height:inherit}.tracked-l{letter-spacing:.1em}.tracked-tight-l{letter-spacing:-.05em}.tracked-mega-l{letter-spacing:.25em}.lh-solid-l{line-height:1}.lh-title-l{line-height:1.25}.lh-copy-l{line-height:1.5}.mw-100-l{max-width:100%}.mw1-l{max-width:1rem}.mw2-l{max-width:2rem}.mw3-l{max-width:4rem}.mw4-l{max-width:8rem}.mw5-l{max-width:16rem}.mw6-l{max-width:32rem}.mw7-l{max-width:48rem}.mw8-l{max-width:64rem}.mw9-l{max-width:96rem}.mw-none-l{max-width:none}.w1-l{width:1rem}.w2-l{width:2rem}.w3-l{width:4rem}.w4-l{width:8rem}.w5-l{width:16rem}.w-10-l{width:10%}.w-20-l{width:20%}.w-25-l{width:25%}.w-30-l{width:30%}.w-33-l{width:33%}.w-34-l{width:34%}.w-40-l{width:40%}.w-50-l{width:50%}.w-60-l{width:60%}.w-70-l{width:70%}.w-75-l{width:75%}.w-80-l{width:80%}.w-90-l{width:90%}.w-100-l{width:100%}.w-third-l{width:33.33333%}.w-two-thirds-l{width:66.66667%}.w-auto-l{width:auto}.overflow-visible-l{overflow:visible}.overflow-hidden-l{overflow:hidden}.overflow-scroll-l{overflow:scroll}.overflow-auto-l{overflow:auto}.overflow-x-visible-l{overflow-x:visible}.overflow-x-hidden-l{overflow-x:hidden}.overflow-x-scroll-l{overflow-x:scroll}.overflow-x-auto-l{overflow-x:auto}.overflow-y-visible-l{overflow-y:visible}.overflow-y-hidden-l{overflow-y:hidden}.overflow-y-scroll-l{overflow-y:scroll}.overflow-y-auto-l{overflow-y:auto}.static-l{position:static}.relative-l{position:relative}.absolute-l{position:absolute}.fixed-l{position:fixed}.rotate-45-l{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-l{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-l{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-l{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-l{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-l{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-l{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-l{padding:0}.pa1-l{padding:.25rem}.pa2-l{padding:.5rem}.pa3-l{padding:1rem}.pa4-l{padding:2rem}.pa5-l{padding:4rem}.pa6-l{padding:8rem}.pa7-l{padding:16rem}.pl0-l{padding-left:0}.pl1-l{padding-left:.25rem}.pl2-l{padding-left:.5rem}.pl3-l{padding-left:1rem}.pl4-l{padding-left:2rem}.pl5-l{padding-left:4rem}.pl6-l{padding-left:8rem}.pl7-l{padding-left:16rem}.pr0-l{padding-right:0}.pr1-l{padding-right:.25rem}.pr2-l{padding-right:.5rem}.pr3-l{padding-right:1rem}.pr4-l{padding-right:2rem}.pr5-l{padding-right:4rem}.pr6-l{padding-right:8rem}.pr7-l{padding-right:16rem}.pb0-l{padding-bottom:0}.pb1-l{padding-bottom:.25rem}.pb2-l{padding-bottom:.5rem}.pb3-l{padding-bottom:1rem}.pb4-l{padding-bottom:2rem}.pb5-l{padding-bottom:4rem}.pb6-l{padding-bottom:8rem}.pb7-l{padding-bottom:16rem}.pt0-l{padding-top:0}.pt1-l{padding-top:.25rem}.pt2-l{padding-top:.5rem}.pt3-l{padding-top:1rem}.pt4-l{padding-top:2rem}.pt5-l{padding-top:4rem}.pt6-l{padding-top:8rem}.pt7-l{padding-top:16rem}.pv0-l{padding-top:0;padding-bottom:0}.pv1-l{padding-top:.25rem;padding-bottom:.25rem}.pv2-l{padding-top:.5rem;padding-bottom:.5rem}.pv3-l{padding-top:1rem;padding-bottom:1rem}.pv4-l{padding-top:2rem;padding-bottom:2rem}.pv5-l{padding-top:4rem;padding-bottom:4rem}.pv6-l{padding-top:8rem;padding-bottom:8rem}.pv7-l{padding-top:16rem;padding-bottom:16rem}.ph0-l{padding-left:0;padding-right:0}.ph1-l{padding-left:.25rem;padding-right:.25rem}.ph2-l{padding-left:.5rem;padding-right:.5rem}.ph3-l{padding-left:1rem;padding-right:1rem}.ph4-l{padding-left:2rem;padding-right:2rem}.ph5-l{padding-left:4rem;padding-right:4rem}.ph6-l{padding-left:8rem;padding-right:8rem}.ph7-l{padding-left:16rem;padding-right:16rem}.ma0-l{margin:0}.ma1-l{margin:.25rem}.ma2-l{margin:.5rem}.ma3-l{margin:1rem}.ma4-l{margin:2rem}.ma5-l{margin:4rem}.ma6-l{margin:8rem}.ma7-l{margin:16rem}.ml0-l{margin-left:0}.ml1-l{margin-left:.25rem}.ml2-l{margin-left:.5rem}.ml3-l{margin-left:1rem}.ml4-l{margin-left:2rem}.ml5-l{margin-left:4rem}.ml6-l{margin-left:8rem}.ml7-l{margin-left:16rem}.mr0-l{margin-right:0}.mr1-l{margin-right:.25rem}.mr2-l{margin-right:.5rem}.mr3-l{margin-right:1rem}.mr4-l{margin-right:2rem}.mr5-l{margin-right:4rem}.mr6-l{margin-right:8rem}.mr7-l{margin-right:16rem}.mb0-l{margin-bottom:0}.mb1-l{margin-bottom:.25rem}.mb2-l{margin-bottom:.5rem}.mb3-l{margin-bottom:1rem}.mb4-l{margin-bottom:2rem}.mb5-l{margin-bottom:4rem}.mb6-l{margin-bottom:8rem}.mb7-l{margin-bottom:16rem}.mt0-l{margin-top:0}.mt1-l{margin-top:.25rem}.mt2-l{margin-top:.5rem}.mt3-l{margin-top:1rem}.mt4-l{margin-top:2rem}.mt5-l{margin-top:4rem}.mt6-l{margin-top:8rem}.mt7-l{margin-top:16rem}.mv0-l{margin-top:0;margin-bottom:0}.mv1-l{margin-top:.25rem;margin-bottom:.25rem}.mv2-l{margin-top:.5rem;margin-bottom:.5rem}.mv3-l{margin-top:1rem;margin-bottom:1rem}.mv4-l{margin-top:2rem;margin-bottom:2rem}.mv5-l{margin-top:4rem;margin-bottom:4rem}.mv6-l{margin-top:8rem;margin-bottom:8rem}.mv7-l{margin-top:16rem;margin-bottom:16rem}.mh0-l{margin-left:0;margin-right:0}.mh1-l{margin-left:.25rem;margin-right:.25rem}.mh2-l{margin-left:.5rem;margin-right:.5rem}.mh3-l{margin-left:1rem;margin-right:1rem}.mh4-l{margin-left:2rem;margin-right:2rem}.mh5-l{margin-left:4rem;margin-right:4rem}.mh6-l{margin-left:8rem;margin-right:8rem}.mh7-l{margin-left:16rem;margin-right:16rem}.na1-l{margin:-.25rem}.na2-l{margin:-.5rem}.na3-l{margin:-1rem}.na4-l{margin:-2rem}.na5-l{margin:-4rem}.na6-l{margin:-8rem}.na7-l{margin:-16rem}.nl1-l{margin-left:-.25rem}.nl2-l{margin-left:-.5rem}.nl3-l{margin-left:-1rem}.nl4-l{margin-left:-2rem}.nl5-l{margin-left:-4rem}.nl6-l{margin-left:-8rem}.nl7-l{margin-left:-16rem}.nr1-l{margin-right:-.25rem}.nr2-l{margin-right:-.5rem}.nr3-l{margin-right:-1rem}.nr4-l{margin-right:-2rem}.nr5-l{margin-right:-4rem}.nr6-l{margin-right:-8rem}.nr7-l{margin-right:-16rem}.nb1-l{margin-bottom:-.25rem}.nb2-l{margin-bottom:-.5rem}.nb3-l{margin-bottom:-1rem}.nb4-l{margin-bottom:-2rem}.nb5-l{margin-bottom:-4rem}.nb6-l{margin-bottom:-8rem}.nb7-l{margin-bottom:-16rem}.nt1-l{margin-top:-.25rem}.nt2-l{margin-top:-.5rem}.nt3-l{margin-top:-1rem}.nt4-l{margin-top:-2rem}.nt5-l{margin-top:-4rem}.nt6-l{margin-top:-8rem}.nt7-l{margin-top:-16rem}.strike-l{text-decoration:line-through}.underline-l{text-decoration:underline}.no-underline-l{text-decoration:none}.tl-l{text-align:left}.tr-l{text-align:right}.tc-l{text-align:center}.tj-l{text-align:justify}.ttc-l{text-transform:capitalize}.ttl-l{text-transform:lowercase}.ttu-l{text-transform:uppercase}.ttn-l{text-transform:none}.f-6-l,.f-headline-l{font-size:6rem}.f-5-l,.f-subheadline-l{font-size:5rem}.f1-l{font-size:3rem}.f2-l{font-size:2.25rem}.f3-l{font-size:1.5rem}.f4-l{font-size:1.25rem}.f5-l{font-size:1rem}.f6-l{font-size:.875rem}.f7-l{font-size:.75rem}.measure-l{max-width:30em}.measure-wide-l{max-width:34em}.measure-narrow-l{max-width:20em}.indent-l{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-l{font-variant:small-caps}.truncate-l{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-l{margin-left:auto}.center-l,.mr-auto-l{margin-right:auto}.ml-auto-l{margin-left:auto}.clip-l{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-l{white-space:normal}.nowrap-l{white-space:nowrap}.pre-l{white-space:pre}.v-base-l{vertical-align:baseline}.v-mid-l{vertical-align:middle}.v-top-l{vertical-align:top}.v-btm-l{vertical-align:bottom}} - diff --git a/src/rawdata/css/hsf/vars.css b/src/rawdata/css/hsf/vars.css deleted file mode 100644 index f65ba870..00000000 --- a/src/rawdata/css/hsf/vars.css +++ /dev/null @@ -1,63 +0,0 @@ -/* -Media queries cannot have variables, so here are media queries you can copy-paste as necessary: - -$breakpoint-not-small: screen and (min-width: 35em) -$breakpoint-medium: screen and (min-width: 35em) and (max-width: 60em) -$breakpoint-large: screen and (min-width: 60em) -*/ - -@import "tachyons-vars.css"; - -/* - * COLOR GUIDE: - * - * Generally speaking, all text and backgrounds use the same palette of - * colors, which are various shades of gray. These are --c1 through --c5. - * - * --c1: Alternate page background color. Use when you need a different - * background color for variety. - * --c2: Primary page background color. - * --c3: Background color for content boxes. Works on both --c1 and --c2. - * --c4: Color of borders and lines. - * --c5: Color of disabled inputs. - */ - -:root { - /* TODO(redesign): Probably not the right color for Soft Gray. Check with Jes. */ - --c1: #fafafa; - --c2: #f3f3f3; - --c3: #dbdbdb; - --c4: #c1c1c1; - /* TODO(redesign): Need a --c5. */ - - --c-transparent-background: #fffffff3; - - --red: #c61d24; - - --color: #000; - --link-color: #d12991; - - --border-color: var(--c4); - --border-color-focused: #4e55ff; - --border-color-error: #ff3a3a; - - --button-color: var(--c3); - --button-color-primary: #ee84ff; - - --site-width: 48rem; -} - -@media (prefers-color-scheme: dark) { - :root { - --color: #fff; - --link-color: #ff5dc2; - - --c1: #1f1f1f; - --c2: #2f2f2f; - --c3: #494949; - --c4: #595959; - --c5: #cbcbcb; - - --button-color-primary: #c900ea; - } -} \ No newline at end of file diff --git a/src/rawdata/js/countdown.ts b/src/rawdata/js/countdown.ts new file mode 100644 index 00000000..b6e12ab0 --- /dev/null +++ b/src/rawdata/js/countdown.ts @@ -0,0 +1,32 @@ +import { must } from "./lib/utils"; + +document.addEventListener("DOMContentLoaded", () => { + for (const countdown of document.querySelectorAll(".countdown")) { + const deadline = must(countdown.getAttribute("data-deadline")); + const deadlineDate = new Date(parseInt(deadline, 10) * 1000); + + function updateCountdown() { + const remainingMs = deadlineDate.getTime() - new Date().getTime(); + const remainingMinutes = remainingMs / 1000 / 60; + const remainingHours = remainingMinutes / 60; + const remainingDays = remainingHours / 24; // no daylight savings transitions during the jam mmkay + + let str = "imminently"; + if (remainingMinutes < 60) { + str = `in ${Math.ceil(remainingMinutes)} ${remainingMinutes === 1 ? "minute" : "minutes" + }`; + } else if (remainingHours < 24) { + str = `in ${Math.ceil(remainingHours)} ${remainingHours === 1 ? "hour" : "hours" + }`; + } else { + str = `in ${Math.ceil(remainingDays)} ${remainingDays === 1 ? "day" : "days" + }`; + } + + (countdown as HTMLElement).innerText = str; + } + + updateCountdown(); + setInterval(updateCountdown, 1000 * 60); + } +}); diff --git a/src/rawdata/js/declared/globals.d.ts b/src/rawdata/js/declared/globals.d.ts new file mode 100644 index 00000000..162ea584 --- /dev/null +++ b/src/rawdata/js/declared/globals.d.ts @@ -0,0 +1,7 @@ +declare const MathJax: { + // NOTE(ben): These are declared as optional parameters because the MathJax + // global is defined immediately but not filled out until mathjax.js finishes + // loading (asynchronously). + typeset?: (elements?: HTMLElement[]) => void; + typesetPromise?: (elements?: HTMLElement[]) => Promise; +}; diff --git a/src/rawdata/js/editor.ts b/src/rawdata/js/editor.ts new file mode 100644 index 00000000..929d565b --- /dev/null +++ b/src/rawdata/js/editor.ts @@ -0,0 +1,158 @@ +import { autosaveContent, initLiveMarkdown } from "./lib/markdown_previews"; +import { setupMarkdownUpload } from "./lib/markdown_upload"; +import { HTMLFileInputElement } from "./lib/types"; +import { must } from "./lib/utils"; + +const form = must(document.querySelector('#form')); +const titleField = document.querySelector('#title'); // may be null in cases like replies +const textField = must(document.querySelector('#editor')); +const preview = must(document.querySelector('#preview')); + +export type EditorOptions = { + maxFileSize: number, + uploadUrl: string, + markdownParser?: string, +}; + +export function init({ maxFileSize, uploadUrl, markdownParser }: EditorOptions) { + // Save content on change, clear on submit + const clearFuncs: (() => void)[] = []; + if (titleField) { + const { clear: clearTitle } = autosaveContent({ + inputEl: titleField, + storageKey: `post-title/${window.location.host}${window.location.pathname}`, + }); + clearFuncs.push(clearTitle); + } + const { clear: clearContent } = autosaveContent({ + inputEl: textField, + storageKey: `post-content/${window.location.host}${window.location.pathname}`, + }); + clearFuncs.push(clearContent); + form.addEventListener('submit', e => { + for (const clear of clearFuncs) { + clear(); + } + }); + + // Do live Markdown previews + const doMarkdown = initLiveMarkdown({ + inputEl: textField, + previewEl: preview, + parserName: markdownParser, + }); + + /* + / Asset upload + */ + setupMarkdownUpload( + document.querySelectorAll("#form input[type=submit]"), + must(document.querySelector('#file_input')), + must(document.querySelector('.upload_bar')), + textField, + doMarkdown, + maxFileSize, + uploadUrl + ); + + textField.addEventListener("keydown", function (ev) { + if (ev.ctrlKey) { + const key = ev.key.toLowerCase(); + const start = textField.selectionStart; + const end = textField.selectionEnd; + const selected = textField.value.substring(start, end); + + if (key === 'k') { + ev.preventDefault(); + + if (selected.length) { + textField.value = + textField.value.substring(0, start) + '[' + + selected + ']()' + + textField.value.substring(end); + + textField.selectionStart = textField.selectionEnd = end + 3; + } + else { + textField.value = + textField.value.substring(0, start) + + "[](url)" + + textField.value.substring(end); + + textField.selectionStart = textField.selectionEnd = start + 1; + } + textField.focus(); + doMarkdown(); + } + else if (key === 'b') { + ev.preventDefault(); + + if (selected.length) { + textField.value = + textField.value.substring(0, start) + '**' + + selected + '**' + + textField.value.substring(end); + + textField.selectionStart = textField.selectionEnd = end + 2; + } + else { + textField.value = + textField.value.substring(0, start) + + "****" + + textField.value.substring(end); + + textField.selectionStart = textField.selectionEnd = start + 2; + } + textField.focus(); + doMarkdown(); + } + else if (key === 'i') { + ev.preventDefault(); + + if (selected.length) { + textField.value = + textField.value.substring(0, start) + '*' + + selected + '*' + + textField.value.substring(end); + + textField.selectionStart = textField.selectionEnd = end + 1; + } + else { + textField.value = + textField.value.substring(0, start) + + "**" + + textField.value.substring(end); + + textField.selectionStart = textField.selectionEnd = start + 1; + } + textField.focus(); + doMarkdown(); + } + else if (key === 's') { + ev.preventDefault(); + } + } + }); + + textField.addEventListener("paste", (ev) => { + const clipboard = must(ev.clipboardData); + const pastedText = clipboard.getData("text"); + const start = textField.selectionStart; + const end = textField.selectionEnd; + + if (pastedText && (pastedText.startsWith('https://') || pastedText.startsWith('http://')) && start !== end) { + ev.preventDefault(); + + textField.value = + textField.value.substring(0, start) + '[' + + textField.value.substring(start, end) + '](' + + pastedText + ')' + + textField.value.substring(end); + + textField.selectionStart = textField.selectionEnd = end + 4 + pastedText.length; + + textField.focus(); + doMarkdown(); + } + }); +} diff --git a/src/rawdata/js/hilbert.ts b/src/rawdata/js/hilbert.ts new file mode 100644 index 00000000..29cc244e --- /dev/null +++ b/src/rawdata/js/hilbert.ts @@ -0,0 +1,44 @@ +function hilbertPath(iterations: number, step = 10) { + let l = "A"; + for (let i = 0; i < iterations; i++) { + l = l.replaceAll(/[AB]/g, c => c === "A" ? "+BF-AFA-FB+" : "-AF+BFB+FA-"); + } + + const dirs = [`h${step}`, `v${step}`, `h-${step}`, `v-${step}`]; + let dir = 0; + let p = `M${step} ${step}`; + for (const char of l) { + if (char === "F") { + p += dirs[dir]; + } else if (char === "+") { + dir += 1; + } else if (char === "-") { + dir -= 1; + } + dir = (dir + 4) % 4; + } + + return p; +} + +function hilbertWidth(iterations: number, step = 10) { + return step * (2 ** iterations - 1 + 2); +} + +function createHilbertCurves() { + const containers = document.querySelectorAll(".hilbert"); + for (const container of containers) { + const filler = document.createElement("div"); + filler.classList.add("hilbert-filler"); + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + const p = hilbertPath(7, 10), width = hilbertWidth(7, 10); + path.setAttribute("d", p); + svg.setAttribute("viewBox", `0 0 ${width} ${width}`); + svg.appendChild(path); + filler.appendChild(svg); + container.appendChild(filler); + } +} + +document.addEventListener("DOMContentLoaded", () => createHilbertCurves()); diff --git a/src/rawdata/js/landing.ts b/src/rawdata/js/landing.ts new file mode 100644 index 00000000..8b4c52f2 --- /dev/null +++ b/src/rawdata/js/landing.ts @@ -0,0 +1,28 @@ +import { initHashTabs } from "./lib/tabs"; +import { must } from "./lib/utils"; + +const noFollowing = document.querySelectorAll("[data-tab='following'] .timeline-item").length === 0; +initHashTabs(must(document.querySelector("#landing-tabs")), { + initialTab: document.location.hash.substring(1) || (noFollowing ? "featured" : undefined), +}); + +// Latest news + +const latestNews = must(document.querySelector("#latest_news")); +const latestNewsPostID = must(latestNews.getAttribute("data-id")); +const latestNewsClosedKey = "latest_news_closed"; + +document.querySelector("#close-latest-news-button")!.addEventListener("click", e => { + e.preventDefault(); + localStorage.setItem(latestNewsClosedKey, latestNewsPostID); + hideLatestNewsIfClosedOrRead(); +}); + +function hideLatestNewsIfClosedOrRead() { + const isUnread = latestNews.hasAttribute("data-unread"); + const closedID = localStorage.getItem(latestNewsClosedKey); + if (!isUnread || closedID === latestNewsPostID) { + latestNews.hidden = true; + } +} +hideLatestNewsIfClosedOrRead(); diff --git a/public/js/base64.js b/src/rawdata/js/lib/base64.ts similarity index 74% rename from public/js/base64.js rename to src/rawdata/js/lib/base64.ts index e1f36262..d8a47859 100644 --- a/public/js/base64.js +++ b/src/rawdata/js/lib/base64.ts @@ -11,25 +11,22 @@ // var sMyOutput = UTF8ArrToStr(aMyUTF8Output); // alert(sMyOutput); -function b64ToUint6 (nChr) { - +export function b64ToUint6(nChr: number) { return nChr > 64 && nChr < 91 ? - nChr - 65 + nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 - : nChr > 47 && nChr < 58 ? - nChr + 4 - : nChr === 43 ? - 62 - : nChr === 47 ? - 63 - : - 0; - + : nChr > 47 && nChr < 58 ? + nChr + 4 + : nChr === 43 ? + 62 + : nChr === 47 ? + 63 + : + 0; } -function base64DecToArr (sBase64, nBlocksSize) { - +export function base64DecToArr(sBase64: string, nBlocksSize?: number) { var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); @@ -51,25 +48,22 @@ function base64DecToArr (sBase64, nBlocksSize) { /* Base64 string to array encoding */ -function uint6ToB64 (nUint6) { - +export function uint6ToB64(nUint6: number) { return nUint6 < 26 ? - nUint6 + 65 + nUint6 + 65 : nUint6 < 52 ? nUint6 + 71 - : nUint6 < 62 ? - nUint6 - 4 - : nUint6 === 62 ? - 43 - : nUint6 === 63 ? - 47 - : - 65; - + : nUint6 < 62 ? + nUint6 - 4 + : nUint6 === 62 ? + 43 + : nUint6 === 63 ? + 47 + : + 65; } -function base64EncArr (aBytes) { - +export function base64EncArr(aBytes: Uint8Array) { var nMod3 = 2, sB64Enc = ""; for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { @@ -83,13 +77,11 @@ function base64EncArr (aBytes) { } return sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? '' : nMod3 === 1 ? '=' : '=='); - } /* UTF-8 array to DOMString and vice versa */ -function UTF8ArrToStr (aBytes) { - +export function UTF8ArrToStr(aBytes: Uint8Array) { var sView = ""; for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) { @@ -98,25 +90,23 @@ function UTF8ArrToStr (aBytes) { nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */ /* (nPart - 252 << 30) may be not so safe in ECMAScript! So...: */ (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ - (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ - (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ - (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ - (nPart - 192 << 6) + aBytes[++nIdx] - 128 - : /* nPart < 127 ? */ /* one byte */ - nPart + : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ + (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ + (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ + (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ + (nPart - 192 << 6) + aBytes[++nIdx] - 128 + : /* nPart < 127 ? */ /* one byte */ + nPart ); } return sView; - } -function strToUTF8Arr (sDOMStr) { - +export function strToUTF8Arr(sDOMStr: string) { var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; /* mapping... */ @@ -169,5 +159,4 @@ function strToUTF8Arr (sDOMStr) { } return aBytes; - } diff --git a/public/js/carousel.js b/src/rawdata/js/lib/carousel.js similarity index 100% rename from public/js/carousel.js rename to src/rawdata/js/lib/carousel.js diff --git a/public/js/image_selector.js b/src/rawdata/js/lib/image_selector.ts similarity index 57% rename from public/js/image_selector.js rename to src/rawdata/js/lib/image_selector.ts index 06ff591f..b7a24b68 100644 --- a/public/js/image_selector.js +++ b/src/rawdata/js/lib/image_selector.ts @@ -1,35 +1,69 @@ +import { makeTemplateCloner } from "./templates"; + +export type ImageSelectorOptions = { + defaultUrl?: string, + originalUrl?: string, + originalFilename?: string, + + onUpdate?: ImageSelectorUpdateFunc, +}; +export type ImageSelectorUpdateFunc = (url: string) => void; + // NOTE(ben): See image_selector.html. -const imageSelectorTemplate = makeTemplateCloner("image-selector"); +type ImageSelectorTmpl = { + inputImage: HTMLInputElement, + inputRemove: HTMLInputElement, + errorMessage: HTMLElement, + previewContainer: HTMLElement, + preview: HTMLImageElement, + linkReset: HTMLAnchorElement, + linkRemove: HTMLAnchorElement, + filenameText: HTMLElement, +} +const imageSelectorTemplate = makeTemplateCloner("image-selector"); + +export class ImageSelector { + // The current URL of the selector. (Will be an object URL if the image has + // not been submitted yet.) + url: string; + + // The template content to be inserted into the DOM wherever you like. + root: DocumentFragment; + + // + + private maxFileSize: number; + private imageInput: HTMLInputElement; + private removeImageInput: HTMLInputElement; + private previewImage: HTMLImageElement; + private previewContainer: HTMLElement; + private resetLink: HTMLAnchorElement; + private removeLink: HTMLAnchorElement; + private filenameText: HTMLElement; + private errorEl: HTMLElement; + private defaultUrl: string; + private originalUrl: string; + private originalFilename: string; + private onUpdate: ImageSelectorUpdateFunc; -class ImageSelector { constructor( - formName, - maxFileSize, + formName: string, + maxFileSize: number, { defaultUrl = "", originalUrl = "", originalFilename = "", - onUpdate = url => {}, - } = {}, + onUpdate = () => { }, + }: ImageSelectorOptions = {}, ) { const tmpl = imageSelectorTemplate(); - - // ------------------------------------------------------------------------ - // NOTE(ben): "Public" properties - // The current URL of the selector. (Will be an object URL if the image has - // not been submitted yet.) - this.url = ""; - - // The template content to be inserted into the DOM wherever you like. + this.url = ""; this.root = tmpl.root; - // ------------------------------------------------------------------------ - // NOTE(ben): Set other "private" instance variables - this.maxFileSize = maxFileSize; - + this.imageInput = tmpl.inputImage; this.removeImageInput = tmpl.inputRemove; this.previewImage = tmpl.preview; @@ -55,27 +89,27 @@ class ImageSelector { this.setImageUrl(this.originalUrl, /*initial=*/true); this.updatePreview(); - this.imageInput.addEventListener("change", function (ev) { - if (this.imageInput.files.length > 0) { - this.handleNewImageFile(this.imageInput.files[0]); + this.imageInput.addEventListener("change", ev => { + if (this.imageInput.files!.length > 0) { + this.handleNewImageFile(this.imageInput.files![0]); } - }.bind(this)); + }); - this.resetLink.addEventListener("click", function (ev) { + this.resetLink.addEventListener("click", ev => { this.resetImage(); - }.bind(this)); + }); if (this.removeLink) { - this.removeLink.addEventListener("click", function (ev) { + this.removeLink.addEventListener("click", ev => { this.removeImage(); - }.bind(this)); + }); } } openImageInput() { this.imageInput.click(); } - setImageUrl(url, initial = false) { + setImageUrl(url: string, initial = false) { this.url = url; if (url) { this.previewImage.style.display = "block"; @@ -88,20 +122,20 @@ class ImageSelector { this.onUpdate(url); } } - setError(error) { + private setError(error: string) { this.errorEl.textContent = error; this.errorEl.hidden = !error; this.imageInput.setCustomValidity(error); this.imageInput.reportValidity(); } - checkSizeLimit(size) { + private checkSizeLimit(size: number) { if (size > this.maxFileSize) { this.setError("File too big. Max file size is " + this.maxFileSize + " bytes."); } else { this.setError(""); } } - updatePreview(file = null) { + private updatePreview(file: File | null = null) { const showReset = ( this.originalUrl && this.originalUrl !== this.defaultUrl @@ -120,7 +154,7 @@ class ImageSelector { this.previewContainer.hidden = !this.url; } - handleNewImageFile(file) { + private handleNewImageFile(file: File) { this.checkSizeLimit(file.size); this.removeImageInput.value = ""; this.setImageUrl(URL.createObjectURL(file)); diff --git a/src/rawdata/js/lib/link_editor.ts b/src/rawdata/js/lib/link_editor.ts new file mode 100644 index 00000000..671bd1cc --- /dev/null +++ b/src/rawdata/js/lib/link_editor.ts @@ -0,0 +1,154 @@ +import { initReorderable } from "./reorderable"; +import { makeTemplateCloner } from "./templates"; +import { must } from "./utils"; + +export type LinkData = Array<{ + name: string, + url: string, + primary: boolean, +}>; + +type LinkEditorRow = { + rootElement: HTMLElement, + nameInput: HTMLInputElement, + urlInput: HTMLInputElement, + grabHandle: HTMLElement, + deleteButton: HTMLAnchorElement, +}; + +type EmptySectionTemplate = { + rootElement: HTMLElement, +}; + +const linksContainer = must(document.querySelector("#links")); +const parentForm = must(linksContainer.closest("form")); +const addButton = must(document.querySelector("#link-editor-add-button")); +const primaryLinksTitle = must(linksContainer.querySelector(".primary-links")); +const secondaryLinksTitle = must(linksContainer.querySelector(".secondary-links")); +const linksJSONInput = must(document.querySelector("#links-json")) as HTMLInputElement; + +const linkTemplate = makeTemplateCloner("link-editor-row"); +const emptySectionTemplate = makeTemplateCloner("link-editor-empty-section"); + +const { startDrag: startLinkDrag } = initReorderable( + linksContainer, + { + onReorder(item) { + ensurePlaceholders(); + linksUpdated(); + }, + }, +); + +function makeLink(): LinkEditorRow { + const res = linkTemplate(); + res.nameInput.addEventListener("input", linkInput); + res.urlInput.addEventListener("input", linkInput); + res.grabHandle.addEventListener("pointerdown", startLinkDrag); + res.deleteButton.addEventListener("click", e => { + e.preventDefault(); + const link = must((e.target as typeof res.deleteButton).closest(".link-editor-row")); + deleteLink(link); + }); + return res; +} + +function ensurePlaceholders() { + for (const el of linksContainer.querySelectorAll(".link-placeholder")) { + el.remove(); + } + + let numPrimary = 0, numSecondary = 0; + let primary = true; + for (const el of linksContainer.children) { + if (el === primaryLinksTitle) { + continue; + } + if (el === secondaryLinksTitle) { + primary = false; + continue; + } + + if (el.classList.contains("reorderable-item")) { + if (primary) { + numPrimary += 1; + } else { + numSecondary += 1; + } + } + } + + if (numPrimary === 0) { + primaryLinksTitle.insertAdjacentElement("afterend", emptySectionTemplate().rootElement); + } + if (numSecondary === 0) { + secondaryLinksTitle.insertAdjacentElement("afterend", emptySectionTemplate().rootElement); + } +} + +export function getLinkData(): LinkData { + const links = []; + let primary = true; + for (const el of linksContainer.children) { + if (el === secondaryLinksTitle) { + primary = false; + continue; + } + + if (el.classList.contains("link-editor-row")) { + const name = (must(el.querySelector(".link-name")) as HTMLInputElement).value; + const url = (must(el.querySelector(".link-url")) as HTMLInputElement).value; + if (!url) { + continue; + } + links.push({ name, url, primary }); + } + } + return links; +} +function updateLinksJSON() { + linksJSONInput.value = JSON.stringify(getLinkData()); +} +function linksUpdated() { + updateLinksJSON(); + window.dispatchEvent(new Event("linkedit")); +} + +function addLink() { + secondaryLinksTitle.insertAdjacentElement("beforebegin", makeLink().rootElement); + + ensurePlaceholders(); + linksUpdated(); +} +function deleteLink(row: Element) { + row.remove(); + + ensurePlaceholders(); + linksUpdated(); +} +function linkInput() { + linksUpdated(); +} + +export function initLinkEditor(initialLinks: LinkData) { + for (const link of initialLinks) { + const l = makeLink(); + l.nameInput.value = link.name; + l.urlInput.value = link.url; + if (link.primary) { + secondaryLinksTitle.insertAdjacentElement("beforebegin", l.rootElement); + } else { + linksContainer.appendChild(l.rootElement); + } + } + ensurePlaceholders() + + addButton.addEventListener("click", e => { + e.preventDefault(); + addLink(); + }) + + parentForm.addEventListener("submit", function () { + updateLinksJSON(); // NOTE(ben): Just update JSON, don't fire another event + }); +} diff --git a/src/rawdata/js/lib/markdown_previews.ts b/src/rawdata/js/lib/markdown_previews.ts new file mode 100644 index 00000000..a0a30a03 --- /dev/null +++ b/src/rawdata/js/lib/markdown_previews.ts @@ -0,0 +1,121 @@ +const previewWorker = new Worker("/assets/markdown_worker.js"); + +export type AutosaveContentOptions = { + inputEl: HTMLInputElement | HTMLTextAreaElement, + /** Unique string identifying this field across the site. */ + storageKey: string, +}; + +export type AutosaveContentResult = { + /** + * Call this function when you submit the form or otherwise want to delete + * the work-in-progress user content. + */ + clear: () => void, +}; + +/** + * Automatically save and restore content from a text field on change. + */ +export function autosaveContent({ + inputEl, + storageKey, +}: AutosaveContentOptions): AutosaveContentResult { + const storagePrefix = 'saved-content'; + + // Delete old irrelevant local contents + const aWeekAgo = new Date().getTime() - (7 * 24 * 60 * 60 * 1000); + for (const key in window.localStorage) { + if (!window.localStorage.hasOwnProperty(key)) { + continue; + } + + if (key.startsWith(storagePrefix)) { + try { + const { when } = JSON.parse(window.localStorage.getItem(key)!); + if (when <= aWeekAgo) { + window.localStorage.removeItem(key); + } + } catch (e) { + console.error(e); + } + } + } + + // Load any stored content from localStorage + const storageKeyFull = `${storagePrefix}/${storageKey}`; + const storedContents = window.localStorage.getItem(storageKeyFull); + if (storedContents && !inputEl.value) { + try { + const { contents } = JSON.parse(storedContents); + inputEl.value = contents; + } catch (e) { + console.error(e); + } + } + + function updateContentCache() { + window.localStorage.setItem(storageKeyFull, JSON.stringify({ + when: new Date().getTime(), + contents: inputEl.value, + })); + } + + inputEl.addEventListener('input', () => updateContentCache()); + + return { + clear() { + window.localStorage.removeItem(storageKeyFull); + }, + } +} + +const markdownIds: string[] = []; + +export type LiveMarkdownOptions = { + inputEl: HTMLInputElement | HTMLTextAreaElement, + previewEl: HTMLElement, + parserName?: string, +}; + +/** + * Initialize live Markdown rendering. Returns a function that renders the + * latest Markdown when called. + */ +export function initLiveMarkdown({ + inputEl, + previewEl, + parserName, +}: LiveMarkdownOptions): () => void { + // NOTE(ben): Doing this here instead of in the binding above so that we can + // treat an empty string as also needing the default. + if (!parserName) { + parserName = "parseMarkdown"; + } + + if (markdownIds.includes(inputEl.id)) { + console.warn(`Multiple elements with ID "${inputEl.id}" are being used for Markdown. Results will be very confusing!`); + } + markdownIds.push(inputEl.id); + + previewWorker.onmessage = ({ data }) => { + const { elementID, html } = data; + if (elementID === inputEl.id) { + previewEl.innerHTML = html; + MathJax.typeset?.(); + } + }; + + function doMarkdown() { + previewWorker.postMessage({ + elementID: inputEl.id, + markdown: inputEl.value, + parserName, + }); + } + + doMarkdown(); + inputEl.addEventListener('input', () => doMarkdown()); + + return doMarkdown; +} \ No newline at end of file diff --git a/public/js/markdown_upload.js b/src/rawdata/js/lib/markdown_upload.ts similarity index 70% rename from public/js/markdown_upload.js rename to src/rawdata/js/lib/markdown_upload.ts index 5dea938d..6f1a21a7 100644 --- a/public/js/markdown_upload.js +++ b/src/rawdata/js/lib/markdown_upload.ts @@ -1,4 +1,11 @@ -// Requires base64.js +import { base64EncArr, strToUTF8Arr } from "./base64"; +import { HTMLFileInputElement } from "./types"; +import { assert, must } from "./utils"; + +type Upload = { + uploadNumber: number, + file: File, +}; /** * Sets up file / image uploading for Markdown content. @@ -24,43 +31,51 @@ * @param maxFileSize The max allowed file size in bytes. * @param uploadUrl The URL to POST assets to (unique per project to avoid CORS issues). */ -function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown, maxFileSize, uploadUrl) { +export function setupMarkdownUpload( + eSubmits: Iterable, + eFileInput: HTMLFileInputElement, + eUploadBar: HTMLElement, + eText: HTMLTextAreaElement, + doMarkdown: () => void, + maxFileSize: number, + uploadUrl: string, +) { const submitTexts = Array.from(eSubmits).map(e => e.value); - const uploadProgress = eUploadBar.querySelector('.progress'); - const uploadProgressText = eUploadBar.querySelector('.progress_text'); - const uploadProgressBar = eUploadBar.querySelector('.progress_bar'); - const uploadProgressBarFill = eUploadBar.querySelector('.progress_bar > div'); + const uploadProgress = must(eUploadBar.querySelector('.progress')) as HTMLElement; + const uploadProgressText = must(eUploadBar.querySelector('.progress_text')) as HTMLElement; + const uploadProgressBar = must(eUploadBar.querySelector('.progress_bar')) as HTMLElement; + const uploadProgressBarFill = must(eUploadBar.querySelector('.progress_bar > div')) as HTMLElement; let fileCounter = 0; let enterCounter = 0; - let uploadQueue = []; - let currentUpload = null; - let currentXhr = null; + let uploadQueue: Upload[] = []; + let currentUpload: Upload | null = null; + let currentXhr: XMLHttpRequest | null = null; let currentBatchSize = 0; let currentBatchDone = 0; - eFileInput.addEventListener("change", function(ev) { + eFileInput.addEventListener("change", () => { if (eFileInput.files.length > 0) { importUserFiles(eFileInput.files); } }); - eText.addEventListener("dragover", function(ev) { - let effect = "none"; - for (let i = 0; i < ev.dataTransfer.items.length; ++i) { - if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { + eText.addEventListener("dragover", ev => { + let effect: DataTransfer["dropEffect"] = "none"; + for (let i = 0; i < ev.dataTransfer!.items.length; ++i) { + if (ev.dataTransfer!.items[i].kind.toLowerCase() == "file") { effect = "copy"; break; } } - ev.dataTransfer.dropEffect = effect; + ev.dataTransfer!.dropEffect = effect; ev.preventDefault(); }); - eText.addEventListener("dragenter", function(ev) { + eText.addEventListener("dragenter", function (ev) { enterCounter++; let droppable = false; - for (let i = 0; i < ev.dataTransfer.items.length; ++i) { - if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { + for (let i = 0; i < ev.dataTransfer!.items.length; ++i) { + if (ev.dataTransfer!.items[i].kind.toLowerCase() == "file") { droppable = true; break; } @@ -70,18 +85,18 @@ function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown } }); - eText.addEventListener("dragleave", function(ev) { + eText.addEventListener("dragleave", function (ev) { enterCounter--; if (enterCounter == 0) { eText.classList.remove("drop"); } }); - function makeUploadString(uploadNumber, filename) { + function makeUploadString(uploadNumber: number, filename: string) { return `Uploading file #${uploadNumber}: \`${filename}\`...`; } - eText.addEventListener("drop", function(ev) { + eText.addEventListener("drop", ev => { enterCounter = 0; eText.classList.remove("drop"); @@ -92,16 +107,19 @@ function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown ev.preventDefault(); }); - eText.addEventListener("paste", function(ev) { - const files = ev.clipboardData?.files ?? []; - if (files.length > 0) { - importUserFiles(files) - ev.preventDefault(); + eText.addEventListener("paste", ev => { + const files = ev.clipboardData?.files; + if (files?.length ?? 0 > 0) { + importUserFiles(files!) + ev.preventDefault(); } }); - function importUserFiles(files) { - let items = []; + function importUserFiles(files: FileList) { + let items: ( + { file: File, error: null } + | { file: null, error: string } + )[] = []; for (let i = 0; i < files.length; ++i) { let f = files[i]; if (f.size < maxFileSize) { @@ -116,33 +134,33 @@ function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown let toInsert = ""; let linesToCursor = eText.value.substr(0, cursorStart).split("\n"); - let cursorLine = linesToCursor[linesToCursor.length-1].trim(); + let cursorLine = linesToCursor[linesToCursor.length - 1].trim(); if (cursorLine.length > 0) { toInsert = "\n\n"; } - for (let i = 0; i < items.length; ++i) { - if (items[i].file) { + for (const item of items) { + if (item.file) { fileCounter++; - toInsert += makeUploadString(fileCounter, items[i].file.name) + "\n\n"; - queueUpload(fileCounter, items[i].file); + toInsert += makeUploadString(fileCounter, item.file.name) + "\n\n"; + queueUpload(fileCounter, item.file); } else { - toInsert += `${items[i].error}\n\n`; + toInsert += `${item.error}\n\n`; } } eText.value = eText.value.substring(0, cursorStart) + toInsert + eText.value.substring(cursorEnd, eText.value.length); - eText.selectionStart = cursorStart + toInsert.length; - eText.selectionEnd = eText.selectionStart; + eText.selectionStart = cursorStart + toInsert.length; + eText.selectionEnd = eText.selectionStart; doMarkdown(); uploadNext(); } - function replaceUploadString(upload, newString) { + function replaceUploadString(upload: Upload, newString: string) { let cursorStart = eText.selectionStart; let cursorEnd = eText.selectionEnd; let uploadString = makeUploadString(upload.uploadNumber, upload.file.name); let insertIndex = eText.value.indexOf(uploadString) - + // The user deleted part of the upload string during the upload. // Paste the newString at the end. if (insertIndex === -1) { @@ -156,7 +174,7 @@ function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown const intersects = cursorStart < insertIndex + uploadString.length && insertIndex < cursorEnd; const fullyInside = insertIndex <= cursorStart && cursorEnd <= insertIndex + uploadString.length; - if ( (fullyInside && cursorStart === cursorEnd) || (intersects && !fullyInside) ) { + if ((fullyInside && cursorStart === cursorEnd) || (intersects && !fullyInside)) { // The user's cursor is inside the placeholder string, or some but not all of the placeholder is selected // the cursor should be moved to the end of the replaced string eText.selectionStart = eText.selectionEnd = insertIndex + newString.length; @@ -176,21 +194,23 @@ function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown doMarkdown(); } - function replaceUploadStringError(upload) { + function replaceUploadStringError(upload: Upload) { replaceUploadString(upload, `There was a problem uploading your file \`${upload.file.name}\`.`); } - function queueUpload(uploadNumber, file) { + function queueUpload(uploadNumber: number, file: File) { uploadQueue.push({ uploadNumber: uploadNumber, file: file }); currentBatchSize++; - uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`; + uploadProgressText.textContent = `Uploading files ${currentBatchDone + 1}/${currentBatchSize}`; } - function uploadDone(ev) { + function uploadDone(ev: ProgressEvent) { + assert(currentXhr); + assert(currentUpload); try { if (currentXhr.status == 200 && currentXhr.response) { if (currentXhr.response.url) { @@ -219,7 +239,7 @@ function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown uploadNext(); } - function updateUploadProgress(ev) { + function updateUploadProgress(ev: ProgressEvent) { if (ev.lengthComputable) { let progress = ev.loaded / ev.total; uploadProgressBarFill.style.width = Math.floor(progress * 100) + "%"; @@ -228,9 +248,9 @@ function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown function uploadNext() { if (currentUpload == null) { - next = uploadQueue.shift(); + const next = uploadQueue.shift(); if (next) { - uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`; + uploadProgressText.textContent = `Uploading files ${currentBatchDone + 1}/${currentBatchSize}`; eUploadBar.classList.add("uploading"); uploadProgressBarFill.style.width = "0%"; for (const e of eSubmits) { @@ -256,7 +276,7 @@ function setupMarkdownUpload(eSubmits, eFileInput, eUploadBar, eText, doMarkdown uploadNext(); } } else { - for (const [i, e] of eSubmits.entries()) { + for (const [i, e] of Array.from(eSubmits).entries()) { e.disabled = false; e.value = submitTexts[i]; } diff --git a/public/js/reorderable.js b/src/rawdata/js/lib/reorderable.js similarity index 93% rename from public/js/reorderable.js rename to src/rawdata/js/lib/reorderable.js index 5157dece..e24940a0 100644 --- a/public/js/reorderable.js +++ b/src/rawdata/js/lib/reorderable.js @@ -18,8 +18,8 @@ * finished; however, there will also be an item with the `reorderable-dummy` * class that you may need to be aware of. */ -function initReorderable(container, { - onReorder = item => {}, +export function initReorderable(container, { + onReorder = item => { }, } = {}) { let dragItem = null; let dragPointerId = null; @@ -59,7 +59,7 @@ function initReorderable(container, { function doDrag(e) { const delta = e.pageY - dragMouseStartY; const top = dragItemStartY + delta; - const middle = top + dragItem.offsetHeight/2; + const middle = top + dragItem.offsetHeight / 2; // NOTE(ben): Find the closest item to insert before or after. const items = container.querySelectorAll(".reorderable-item"); @@ -71,7 +71,7 @@ function initReorderable(container, { continue; } - const itemMiddle = item.offsetTop + item.offsetHeight/2; + const itemMiddle = item.offsetTop + item.offsetHeight / 2; const dist = middle - itemMiddle; if (Math.abs(dist) < closestItemDist) { closestItem = item; diff --git a/public/js/tabs.js b/src/rawdata/js/lib/tabs.ts similarity index 70% rename from public/js/tabs.js rename to src/rawdata/js/lib/tabs.ts index e834489e..a48a8bd1 100644 --- a/public/js/tabs.js +++ b/src/rawdata/js/lib/tabs.ts @@ -1,3 +1,18 @@ +import { must } from "./utils"; + +export type TabsOptions = { + initialTab?: string, + onSelect?: (name: string) => void, +}; + +export type TabsFunctions = { + selectTab: (name: string, opts?: SelectTabOptions) => void, +}; + +export type SelectTabOptions = { + sendEvent?: boolean, +}; + /** * A utility for a tabbed section on the page. To use it: * @@ -24,19 +39,19 @@ * A function that selects a tab by name without clicking a button. By * setting `sendEvent` to false you can suppress the call to `onSelect`. */ -function initTabs(container, { - initialTab = null, - onSelect = name => {}, -} = {}) { - const buttons = Array.from(container.querySelectorAll("[data-tab-button]")); - const tabs = Array.from(container.querySelectorAll("[data-tab]")); +export function initTabs(container: ParentNode, { + initialTab, + onSelect = () => { }, +}: TabsOptions = {}): TabsFunctions { + const buttons = Array.from(container.querySelectorAll("[data-tab-button]")) as HTMLElement[]; + const tabs = Array.from(container.querySelectorAll("[data-tab]")) as HTMLElement[]; - const firstTab = tabs[0].getAttribute("data-tab"); + const firstTab = tabs[0].getAttribute("data-tab")!; - function selectTab(name, { sendEvent = true } = {}) { + function selectTab(name: string, { sendEvent = true } = {}) { if (!container.querySelector(`[data-tab="${name}"]`)) { console.warn("no tab found with name", name); - return selectTab(firstTab, initial); + return selectTab(firstTab, { sendEvent }); } for (const tab of tabs) { @@ -54,7 +69,7 @@ function initTabs(container, { for (const button of buttons) { button.addEventListener("click", () => { - selectTab(button.getAttribute("data-tab-button")); + selectTab(button.getAttribute("data-tab-button")!); }); } @@ -63,12 +78,16 @@ function initTabs(container, { }; } +export type HashTabsOptions = { + initialTab?: string, +}; + /** - * A wrapper around the above that automatically uses the URL #hash. + * A wrapper around `initTabs` that automatically uses the URL #hash. */ -function initHashTabs(container, { - initialTab = null, -} = {}) { +export function initHashTabs(container: ParentNode, { + initialTab, +}: HashTabsOptions = {}) { const res = initTabs(container, { initialTab: initialTab ?? document.location.hash.substring(1), onSelect(name) { @@ -82,6 +101,6 @@ function initHashTabs(container, { selectTab(tab, { sendEvent: false }); } }); - + return res; } \ No newline at end of file diff --git a/src/rawdata/js/lib/templates.ts b/src/rawdata/js/lib/templates.ts new file mode 100644 index 00000000..fbeade20 --- /dev/null +++ b/src/rawdata/js/lib/templates.ts @@ -0,0 +1,81 @@ +import { assert } from "./utils"; + +export type TemplatePath = [name: string, path: number[]]; +export type ClonedTemplate = { [name: string]: Node }; + +var templateElementCache: { [id: string]: HTMLTemplateElement } = {}; +var templatePathCache: { [id: string]: TemplatePath[] } = {}; + +function getTemplateEl(id: string): HTMLTemplateElement { + if (!templateElementCache[id]) { + const el = document.getElementById(id); + assert(el, `no element with id ${id}`); + assert(el instanceof HTMLTemplateElement); + templateElementCache[id] = el as HTMLTemplateElement; + } + return templateElementCache[id]; +} + +function getTemplatePaths(id: string, rootNode: ParentNode) { + if (!templatePathCache[id]) { + var paths: TemplatePath[] = []; + paths.push(["root", []]); + + function descend(path: number[], el: ParentNode) { + for (var i = 0; i < el.children.length; ++i) { + var child = el.children[i]; + var childPath: number[] = path.concat([i]); + var tmplName = child.getAttribute("data-tmpl"); + if (tmplName) { + paths.push([tmplName, childPath]); + } + if (child.children.length > 0) { + descend(childPath, child); + } + } + } + + descend([], rootNode); + templatePathCache[id] = paths; + } + return templatePathCache[id]; +} + +function collectElements(paths: TemplatePath[], rootElement: ParentNode) { + var result: { [name: string]: Node } = {}; + for (var i = 0; i < paths.length; ++i) { + var path = paths[i]; + var current = rootElement; + for (var j = 0; j < path[1].length; ++j) { + current = current.children[path[1][j]]; + } + result[path[0]] = current; + } + return result; +} + +// NOTE(ben): provide `root: DocumentFragment` if T does not contain it; +// otherwise, T's `root` take precedence. +export type TemplateElements = T & Omit<{ root: DocumentFragment }, keyof T>; + +export function makeTemplateCloner(id: string): () => TemplateElements { + return function () { + var templateEl = getTemplateEl(id); + if (templateEl === null) { + throw new Error(`Couldn\'t find template with ID '${id}'`); + } + + var root = templateEl.content.cloneNode(true) as DocumentFragment; + var paths = getTemplatePaths(id, root); + var result = collectElements(paths, root); + return result as TemplateElements; + }; +} + +export function emptyElement(el: T) { + var newEl = el.cloneNode(false); + assert(el.parentElement); + el.parentElement.insertBefore(newEl, el); + el.parentElement.removeChild(el); + return newEl as T; +} diff --git a/src/rawdata/js/lib/types.ts b/src/rawdata/js/lib/types.ts new file mode 100644 index 00000000..c099df4a --- /dev/null +++ b/src/rawdata/js/lib/types.ts @@ -0,0 +1,6 @@ +export type CSRFToken = { + field: string, + token: string, +}; + +export type HTMLFileInputElement = HTMLInputElement & { files: FileList }; diff --git a/src/rawdata/js/lib/utils.ts b/src/rawdata/js/lib/utils.ts new file mode 100644 index 00000000..01517a52 --- /dev/null +++ b/src/rawdata/js/lib/utils.ts @@ -0,0 +1,20 @@ +export type Falsy = null | undefined | false | 0 | -0 | 0n | ""; + +export function assert( + cond: T | Falsy, + msg?: string, + soft = false, +): asserts cond is T { + if (!cond) { + if (soft) { + console.error(msg ?? "Assertion failed"); + } else { + throw new Error(msg ?? "Assertion failed"); + } + } +} + +export function must(val: T | Falsy, msg?: string): T { + assert(val, msg); + return val; +} diff --git a/src/rawdata/js/perfmon.js b/src/rawdata/js/perfmon.js new file mode 100644 index 00000000..e3bd554d --- /dev/null +++ b/src/rawdata/js/perfmon.js @@ -0,0 +1,135 @@ +import { makeTemplateCloner } from "./lib/templates"; + +export function init(perfRecordsJSON) { + const singleRowTemplate = makeTemplateCloner("single-record"); + const routeTemplate = makeTemplateCloner("route"); + const data = JSON.parse(perfRecordsJSON); + const recordsContainer = document.querySelector("#records"); + const routesContainer = document.querySelector("#routes"); + + let routes = {} + let recents = []; + + for (let i = data.length - 1; i >= 0; --i) { + let record = data[i]; + + if (recents.length < 100) { + recents.push(record); + } + + if (!routes[record.Route]) { + routes[record.Route] = { + route: record.Route, + records: [], + }; + } + + routes[record.Route].records.push(record); + } + + function showRecords(records) { + recordsContainer.innerHTML = ""; + for (let i = 0; i < records.length; ++i) { + let record = records[i]; + let row = singleRowTemplate(); + row.route.textContent = record.Route; + row.duration.textContent = record.Duration / 1000 + "ms"; + row.path.textContent = record.Path; + let flameGraph = row.flamegraph; + + let flameRows = []; + function getFlameRow(idx) { + while (flameRows.length <= idx) { + let flameRow = document.createElement("DIV"); + flameRow.classList.add("h1"); + flameRow.classList.add("relative"); + flameRows.push(flameRow); + flameGraph.appendChild(flameRow); + } + + return flameRows[idx]; + } + + function placeFlameItems(parent, depth, maxDuration) { + let rowEl = getFlameRow(depth); + for (let childIdx = 0; childIdx < parent.Children.length; ++childIdx) { + let child = parent.Children[childIdx]; + let item = document.createElement("DIV"); + let catCSS = child.Category.toLowerCase(); + item.classList.add("absolute", "h1", "overflow-hidden", "f7", "nowrap"); + item.style.color = `var(--${catCSS}-text)`; + item.style.backgroundColor = `var(--${catCSS}-bg, var(--c1))`; + item.textContent = `[${child.Category}] ${child.Description} | ${child.Duration / 1000}ms`; + item.title = item.textContent; + item.style.width = ((child.Duration / maxDuration) * 100) + "%"; + item.style.left = (((child.Offset) / maxDuration) * 100) + "%"; + rowEl.appendChild(item); + + for (const checkpoint of child.Checkpoints) { + const el = document.createElement("div"); + el.title = `[${checkpoint.Category}] ${checkpoint.Description}`; + el.classList.add("absolute", "h1", "f7", "nowrap"); + el.style.backgroundColor = `#00000066`; + el.style.width = "2px"; + el.style.left = (((checkpoint.Offset) / maxDuration) * 100) + "%"; + rowEl.appendChild(el); + } + + if (child.Children && child.Children.length > 0) { + placeFlameItems(child, depth + 1, maxDuration); + } + } + } + + if (record.Breakdown.Children) { + placeFlameItems(record.Breakdown, 0, record.Breakdown.Duration); + } + + recordsContainer.appendChild(row.row); + } + } + + for (const key in routes) { + routes[key].records.sort(function (a, b) { + return b.Duration - a.Duration; + }); + } + + let routesList = Object.values(routes); + routesList.sort(function (a, b) { + const medianA = a.records[Math.floor(a.records.length / 2)].Duration; + const medianB = b.records[Math.floor(b.records.length / 2)].Duration; + return medianB - medianA; + }); + + let activeRoute = ""; + + for (let i = 0; i < routesList.length; ++i) { + let r = routesList[i]; + let routeEl = routeTemplate(); + routeEl.name.textContent = r.route; + routeEl.hits.textContent = r.records.length; + routeEl.duration.textContent = (r.records[Math.floor(r.records.length / 2)].Duration / 1000) + "ms"; + routeEl.route.dataset.route = r.route; + routesContainer.appendChild(routeEl.route); + routesList[i].el = routeEl; + + routeEl.route.addEventListener("click", function (ev) { + let el = ev.target.closest(".route"); + if (el.dataset.route == activeRoute) { + el.classList.remove("bg4"); + activeRoute = ""; + showRecords(recents); + } else { + for (let i = 0; i < routesList.length; ++i) { + routesList[i].el.route.classList.remove("bg4"); + } + activeRoute = el.dataset.route; + el.classList.add("bg4"); + showRecords(routes[activeRoute].records); + } + }); + } + + showRecords(recents); +} diff --git a/src/rawdata/js/project_edit.ts b/src/rawdata/js/project_edit.ts new file mode 100644 index 00000000..afa2fcb6 --- /dev/null +++ b/src/rawdata/js/project_edit.ts @@ -0,0 +1,335 @@ +import { ImageSelector } from "./lib/image_selector"; +import { setupMarkdownUpload } from "./lib/markdown_upload"; +import { getLinkData, initLinkEditor, LinkData } from "./lib/link_editor"; +import { initHashTabs } from "./lib/tabs"; +import { makeTemplateCloner } from "./lib/templates"; +import { CSRFToken, HTMLFileInputElement } from "./lib/types"; +import { must } from "./lib/utils"; +import { autosaveContent, initLiveMarkdown } from "./lib/markdown_previews"; + +// NOTE(ben): Set on the window by our Go code. +declare const parseKnownServicesForUrl: + | ((url: string) => { service: string, icon: string, username: string }) + | null; + +export type ProjectEditConfig = { + csrf: CSRFToken, + projectName: string, + maxOwners: number, + logoMaxFileSize: number, + headerMaxFileSize: number, + textMaxFileSize: number, + editorUploadUrl: string, + initialLinks: LinkData, + ownerCheckUrl: string, + logoUrl: string, + logoFilename: string, + headerImageUrl: string, + headerImageFilename: string, +}; + +export function init({ + csrf, + projectName, + maxOwners, + logoMaxFileSize, + headerMaxFileSize, + textMaxFileSize, + editorUploadUrl, + initialLinks, + ownerCheckUrl, + logoUrl, + logoFilename, + headerImageUrl, + headerImageFilename, +}: ProjectEditConfig) { + initHashTabs(document); + + const projectForm = must(document.querySelector("#project_form")); + + ////////// + // Tags // + ////////// + + const tag = must(document.querySelector('#tag')); + const tagPreview = must(document.querySelector('#tag-preview')); + function updateTagPreview() { + tagPreview.innerText = tag.value || "[your tag]"; + } + updateTagPreview(); + tag.addEventListener('input', () => updateTagPreview()); + + //////////////////////////// + // Description management // + //////////////////////////// + + const description = must(document.querySelector('#full_description')); + const descPreview = must(document.querySelector('#desc_preview')); + const { clear: clearDescription } = autosaveContent({ + inputEl: description, + storageKey: `project-description/${projectName}`, + }); + projectForm.addEventListener('submit', () => clearDescription()); + + // TODO(ben): Probably the live markdown stuff should be module-ified too. + const doMarkdown = initLiveMarkdown({ inputEl: description, previewEl: descPreview }); + + ////////////////////// + // Owner management // + ////////////////////// + + const OWNER_QUERY_STATE_IDLE = 0; + const OWNER_QUERY_STATE_QUERYING = 1; + type OwnerQueryState = typeof OWNER_QUERY_STATE_IDLE | typeof OWNER_QUERY_STATE_QUERYING; + + let ownerQueryState: OwnerQueryState = OWNER_QUERY_STATE_IDLE; + const addOwnerInput = must(document.querySelector("#owner_name")); + const addOwnerButton = must(document.querySelector("#owner_add")); + const ownersError = must(document.querySelector("#owners_error")); + const ownerList = must(document.querySelector("#owner_list")); + const ownerTemplate = makeTemplateCloner<{ + rootElement: HTMLElement, + input: HTMLInputElement, + avatar: HTMLImageElement, + name: HTMLElement, + }>("owner_row"); + const ownerPreviewTemplate = makeTemplateCloner<{ + avatar: HTMLImageElement, + name: HTMLElement, + }>("owner_preview"); + const ownersPreviewContainer = must(document.querySelector("#owners_preview")); + + addOwnerInput.addEventListener("keypress", function (ev) { + if (ev.which == 13) { + startAddOwner(); + ev.preventDefault(); + ev.stopPropagation(); + } + }); + + addOwnerButton.addEventListener("click", function (ev) { + ev.preventDefault(); + startAddOwner(); + }); + + function updateAddOwnerStyles() { + const numOwnerRows = document.querySelectorAll('.owner_row').length; + addOwnerInput.disabled = numOwnerRows >= maxOwners; + } + updateAddOwnerStyles(); + + function startAddOwner() { + if (ownerQueryState == OWNER_QUERY_STATE_QUERYING) { + return; + } + let newOwner = addOwnerInput.value.trim().toLowerCase(); + if (newOwner.length == 0) { + return; + } + let ownerEls = ownerList.querySelectorAll(".owner_row input[name='owners']"); + for (let i = 0; i < ownerEls.length; ++i) { + let existingOwner = ownerEls[i].value.toLowerCase(); + if (newOwner == existingOwner) { + return; + } + } + + ownersError.textContent = ""; + let xhr = new XMLHttpRequest(); + xhr.withCredentials = true; + xhr.open("POST", ownerCheckUrl); + xhr.responseType = "json"; + xhr.addEventListener("load", function (ev) { + let result = xhr.response; + if (result) { + if (result.found) { + addOwner(result.username, result.name, result.avatarUrl); + addOwnerInput.value = ""; + } else { + ownersError.textContent = "Username not found"; + } + } else { + ownersError.textContent = "There was an issue validating this username"; + } + setOwnerQueryState(OWNER_QUERY_STATE_IDLE); + if (document.activeElement == addOwnerButton) { + addOwnerInput.focus(); + } + }); + xhr.addEventListener("error", function (ev) { + ownersError.textContent = "There was an issue validating this username"; + setOwnerQueryState(OWNER_QUERY_STATE_IDLE); + }); + let data = new FormData(); + data.append(csrf.field, csrf.token); + data.append("username", newOwner); + xhr.send(data); + setOwnerQueryState(OWNER_QUERY_STATE_QUERYING); + } + + function setOwnerQueryState(state: OwnerQueryState) { + ownerQueryState = state; + const querying = (ownerQueryState == OWNER_QUERY_STATE_QUERYING); + addOwnerInput.disabled = querying; + addOwnerButton.disabled = querying; + updateAddOwnerStyles(); + } + + function addOwner(username: string, bestName: string, avatarUrl: string) { + let ownerEl = ownerTemplate(); + ownerEl.input.value = username; + ownerEl.name.textContent = bestName; + ownerEl.avatar.src = avatarUrl; + ownerList.appendChild(ownerEl.rootElement); + updateAddOwnerStyles(); + updateOwnersPreview(); + } + + ownerList.addEventListener("click", ev => { + if ((ev.target as Element).closest(".remove_owner")) { + must((ev.target as Element).closest(".owner_row")).remove(); + } + updateAddOwnerStyles(); + updateOwnersPreview(); + }); + + function updateOwnersPreview() { + let ownerEls = ownerList.querySelectorAll(".owner_row"); + ownersPreviewContainer.innerHTML = ""; + for (let i = 0; i < ownerEls.length; ++i) { + let avatarUrl = must(ownerEls[i].querySelector("img")).src; + let name = must(ownerEls[i].querySelector("span")).textContent; + let previewEl = ownerPreviewTemplate(); + previewEl.avatar.src = avatarUrl; + previewEl.name.textContent = name; + ownersPreviewContainer.appendChild(previewEl.root); + } + + } + + updateOwnersPreview(); + + ////////////////////////////// + // Logo / header management // + ////////////////////////////// + + const projectNameField = must(document.querySelector("#project_name")); + const descriptionField = must(document.querySelector("#description")); + + const logoSelector = new ImageSelector( + "logo", + logoMaxFileSize, + { + originalUrl: logoUrl, + originalFilename: logoFilename, + onUpdate() { + updateCardPreview(); + }, + }, + ); + must(document.querySelector("#logo-upload-button")).addEventListener("click", e => { + e.preventDefault(); + logoSelector.openImageInput(); + }); + must(document.querySelector("#project-logo-placeholder")).replaceWith(logoSelector.root); + + const headerSelector = new ImageSelector( + "header_image", + headerMaxFileSize, + { + originalUrl: headerImageUrl, + originalFilename: headerImageFilename, + onUpdate() { + updateCardPreview(); + }, + }, + ); + must(document.querySelector("#header-upload-button")).addEventListener("click", e => { + e.preventDefault(); + headerSelector.openImageInput(); + }); + must(document.querySelector("#header-image-placeholder")).replaceWith(headerSelector.root); + + function updateCardPreview() { + const title = projectNameField.value || "Project Title"; + + must(document.querySelector("#logo_preview img")).src = logoSelector.url; + must(document.querySelector("#logo_placeholder")).innerText = title[0].toUpperCase(); + must(document.querySelector("#logo_placeholder")).hidden = !!logoSelector.url; + must(document.querySelector("#header_img_preview")).style.backgroundImage = `url(${headerSelector.url})`; + must(document.querySelector("#flowsnake")).classList.toggle("dn", !!headerSelector.url); + must(document.querySelector("#name_preview")).innerText = title; + must(document.querySelector("#longdesc_title")).innerText = title; + must(document.querySelector("#blurb_preview")).innerText = descriptionField.value || "Project summary"; + } + updateCardPreview(); + projectNameField.addEventListener("input", updateCardPreview); + descriptionField.addEventListener("input", updateCardPreview); + + ////////////////// + // Asset upload // + ////////////////// + setupMarkdownUpload( + document.querySelectorAll("#project_form input[type=submit]"), + must(document.querySelector('#file_input')), + must(document.querySelector('.upload_bar')), + description, + doMarkdown, + textMaxFileSize, + editorUploadUrl, + ); + + ///////////////////// + // Link management // + ///////////////////// + + initLinkEditor(initialLinks); + + const primaryLinkTemplate = makeTemplateCloner<{ + link: HTMLAnchorElement, + name: HTMLElement, + }>("primary_link"); + const secondaryLinkTemplate = makeTemplateCloner<{ + link: HTMLAnchorElement, + }>("secondary_link"); + + function updateLinkPreviews() { + const linkData = getLinkData(); + + const primaryPreview = must(document.querySelector("#primary_links_preview")); + const secondaryPreview = must(document.querySelector("#secondary_links_preview")); + + primaryPreview.innerHTML = ""; + secondaryPreview.innerHTML = ""; + + for (const link of linkData) { + if (link.primary) { + const l = primaryLinkTemplate(); + l.link.href = link.url; + l.name.innerText = link.name; + primaryPreview.appendChild(l.link); + } else { + let icon = "website"; + let title = ""; + if (parseKnownServicesForUrl) { + const guess = parseKnownServicesForUrl(link.url); + icon = guess.icon; + title = guess.service; + if (guess.username) { + title += ` (${guess.username})`; + } + } + const iconSVG = must(document.querySelector(`#link-icon-${icon}`)).innerHTML; + + const l = secondaryLinkTemplate(); + l.link.href = link.url; + l.link.title = link.name || title; + l.link.innerHTML = iconSVG; + secondaryPreview.appendChild(l.link); + } + } + } + updateLinkPreviews(); + window.addEventListener("wasmready", () => updateLinkPreviews()); + window.addEventListener("linkedit", () => updateLinkPreviews()); +} diff --git a/src/rawdata/js/showcase.js b/src/rawdata/js/showcase.js new file mode 100644 index 00000000..08e7a59c --- /dev/null +++ b/src/rawdata/js/showcase.js @@ -0,0 +1,256 @@ +// NOTE(ben): Because this file is only used by old jam pages, we do a weird +// thing where all the exports from this module will be splatted onto `window` +// and therefore globally accessible like the old script used to do it. + +import { makeTemplateCloner, emptyElement } from "./lib/templates"; +export * from "./lib/templates"; + +const TimelineMediaTypes = { + UNKNOWN: 0, + IMAGE: 1, + VIDEO: 2, + AUDIO: 3, + EMBED: 4, +} + +const showcaseItemTemplate = makeTemplateCloner("showcase_item"); +const modalTemplate = makeTemplateCloner("timeline_modal"); +const projectLinkTemplate = makeTemplateCloner("project_link"); + +function showcaseTimestamp(rawDate) { + const date = new Date(rawDate * 1000); + return date.toLocaleDateString([], { 'dateStyle': 'long' }); +} + +function doOnce(f) { + let did = false; + return () => { + if (!did) { + f(); + did = true; + } + } +} + +// TODO(redesign): Fix snippet editing, given that snippets are now presented very differently. +export function makeShowcaseItem(timelineItem) { + const timestamp = showcaseTimestamp(timelineItem.date); + + const itemEl = showcaseItemTemplate(); + itemEl.avatar.style.backgroundImage = `url('${timelineItem.owner_avatar}')`; + itemEl.username.textContent = timelineItem.owner_name; + itemEl.when.textContent = timestamp; + + let addThumbnailFunc = () => { }; + let createModalContentFunc = () => { }; + + switch (timelineItem.media_type) { + case TimelineMediaTypes.IMAGE: + addThumbnailFunc = () => { + itemEl.thumbnail.style.backgroundImage = `url('${timelineItem.thumbnail_url}')`; + }; + + createModalContentFunc = () => { + const modalImage = document.createElement('img'); + modalImage.src = timelineItem.asset_url; + modalImage.classList.add('mw-100', 'maxh-60vh'); + return modalImage; + }; + + break; + case TimelineMediaTypes.VIDEO: + addThumbnailFunc = () => { + let thumbEl; + if (timelineItem.thumbnail_url) { + thumbEl = document.createElement('img'); + thumbEl.src = timelineItem.thumbnail_url; + } else { + thumbEl = document.createElement('video'); + thumbEl.src = timelineItem.asset_url; + thumbEl.controls = false; + thumbEl.preload = 'metadata'; + } + thumbEl.classList.add('h-100'); + itemEl.thumbnail.appendChild(thumbEl); + }; + + createModalContentFunc = () => { + const modalVideo = document.createElement('video'); + modalVideo.src = timelineItem.asset_url; + if (timelineItem.thumbnail_url) { + modalVideo.poster = timelineItem.thumbnail_url; + modalVideo.preload = 'none'; + } else { + modalVideo.preload = 'metadata'; + } + modalVideo.controls = true; + modalVideo.classList.add('mw-100', 'maxh-60vh'); + return modalVideo; + }; + + break; + case TimelineMediaTypes.AUDIO: + createModalContentFunc = () => { + const modalAudio = document.createElement('audio'); + modalAudio.src = timelineItem.asset_url; + modalAudio.controls = true; + modalAudio.preload = 'metadata'; + modalAudio.classList.add('w-70'); + return modalAudio; + }; + + break; + // TODO(ben): Other snippet types? + } + + let modalEl = null; + itemEl.container.addEventListener('click', function () { + if (!modalEl) { + modalEl = modalTemplate(); + modalEl.description.innerHTML = timelineItem.description; + modalEl.asset_container.appendChild(createModalContentFunc()); + + modalEl.avatar.src = timelineItem.owner_avatar; + modalEl.userLink.textContent = timelineItem.owner_name; + modalEl.userLink.href = timelineItem.owner_url; + modalEl.date.textContent = timestamp; + modalEl.date.setAttribute("href", timelineItem.snippet_url); + + if (timelineItem.projects.length === 0) { + modalEl.projects.remove(); + } else { + for (const proj of timelineItem.projects) { + const projectLink = projectLinkTemplate(); + projectLink.root.href = proj.url; + projectLink.logo.src = proj.logo; + projectLink.name.textContent = proj.name; + + modalEl.projects.appendChild(projectLink.root); + } + } + + if (timelineItem.discord_message_url != "") { + modalEl.discord_link.href = timelineItem.discord_message_url; + } else { + modalEl.discord_link.remove(); + } + + function close() { + modalEl.overlay.remove(); + } + modalEl.overlay.addEventListener('click', close); + modalEl.close.addEventListener('click', close); + modalEl.container.addEventListener('click', function (e) { + e.stopPropagation(); + }); + } + + document.body.appendChild(modalEl.overlay); + }); + + return [itemEl, doOnce(addThumbnailFunc)]; +} + +export function initShowcaseContainer(container, items, rowHeight = 300, itemSpacing = 4) { + const addThumbnailFuncs = new Array(items.length); + + const itemElements = []; // array of arrays + for (let i = 0; i < items.length; i++) { + const item = items[i]; + + const [itemEl, addThumbnail] = makeShowcaseItem(item); + itemEl.container.setAttribute('data-index', i); + itemEl.container.setAttribute('data-date', item.date); + + addThumbnailFuncs[i] = addThumbnail; + + itemElements.push(itemEl.container); + } + + function layout() { + const width = container.getBoundingClientRect().width; + container = emptyElement(container); + + function addRow(itemEls, rowWidth, container) { + const totalSpacing = itemSpacing * (itemEls.length - 1); + const scaleFactor = (width / Math.max(rowWidth, width)); + + const row = document.createElement('div'); + row.classList.add('flex'); + row.classList.toggle('justify-between', rowWidth >= width); + row.style.marginBottom = `${itemSpacing}px`; + + for (const itemEl of itemEls) { + const index = parseInt(itemEl.getAttribute('data-index'), 10); + const item = items[index]; + + const aspect = item.width / item.height; + const baseWidth = (aspect * rowHeight) * scaleFactor; + const actualWidth = baseWidth - (totalSpacing / itemEls.length); + + itemEl.style.width = `${actualWidth}px`; + itemEl.style.height = `${scaleFactor * rowHeight}px`; + itemEl.style.marginRight = `${itemSpacing}px`; + + row.appendChild(itemEl); + } + + container.appendChild(row); + } + + let rowItemEls = []; + let rowWidth = 0; + + for (const itemEl of itemElements) { + const index = parseInt(itemEl.getAttribute('data-index'), 10); + const item = items[index]; + + const aspect = item.width / item.height; + rowWidth += aspect * rowHeight; + + rowItemEls.push(itemEl); + + if (rowWidth > width) { + addRow(rowItemEls, rowWidth, container); + + rowItemEls = []; + rowWidth = 0; + } + } + + addRow(rowItemEls, rowWidth, container); + } + + function tryLoadImages() { + const OFFSCREEN_THRESHOLD = 0; + + const rect = container.getBoundingClientRect(); + const offscreen = ( + rect.bottom < -OFFSCREEN_THRESHOLD + || rect.top > window.innerHeight + OFFSCREEN_THRESHOLD + ); + + if (!offscreen) { + const items = container.querySelectorAll('.showcase-item'); + for (const item of items) { + const i = parseInt(item.getAttribute('data-index'), 10); + addThumbnailFuncs[i](); + } + } + } + + window.addEventListener('DOMContentLoaded', () => { + layout(); + layout(); // scrollbars are fun!! + tryLoadImages(); + }) + + window.addEventListener('resize', () => { + layout(); + tryLoadImages(); + }); + + window.addEventListener('scroll', () => { + tryLoadImages(); + }); +} diff --git a/src/rawdata/js/snippetedit.ts b/src/rawdata/js/snippetedit.ts new file mode 100644 index 00000000..c808952e --- /dev/null +++ b/src/rawdata/js/snippetedit.ts @@ -0,0 +1,494 @@ +import { emptyElement, makeTemplateCloner } from "./lib/templates"; +import { HTMLFileInputElement } from "./lib/types"; +import { assert, must } from "./lib/utils"; + +const snippetEditTemplate = makeTemplateCloner<{ + root: HTMLFormElement, + redirect: HTMLInputElement, + snippetId: HTMLInputElement, + removeAttachment: HTMLInputElement, + file: HTMLFileInputElement, + avatarLink: HTMLAnchorElement, + avatarImg: HTMLImageElement, + username: HTMLAnchorElement, + date: HTMLElement, + cancelLink: HTMLAnchorElement, + text: HTMLTextAreaElement, + uploadBox: HTMLElement, + uploadLink: HTMLAnchorElement, + uploadResetBox: HTMLElement, + uploadResetLink: HTMLAnchorElement, + previewBox: HTMLElement, + previewContent: HTMLElement, + removeLink: HTMLAnchorElement, + resetLink: HTMLAnchorElement, + replaceLink: HTMLAnchorElement, + errors: HTMLElement, + projectList: HTMLElement, + deleteButton: HTMLInputElement, + saveButton: HTMLInputElement, +}>("snippet-edit"); +const snippetEditProjectTemplate = makeTemplateCloner<{ + root: HTMLElement, + projectId: HTMLInputElement, + projectLogo: HTMLImageElement, + projectName: HTMLElement, + removeButton: HTMLAnchorElement, +}>("snippet-edit-project"); + +function readableByteSize(numBytes: number) { + const scales = [ + " bytes", + "kb", + "mb", + "gb" + ]; + let scale = 0; + while (numBytes > 1024 && scale < scales.length - 1) { + numBytes /= 1024; + scale++; + } + return new Intl.NumberFormat([], { maximumFractionDigits: (scale > 0 ? 2 : 0) }).format(numBytes) + scales[scale]; +} + +type AvailableProject = { + id: number, + name: string, + logo: string, +}; + +type SnippetEditOptions = { + maxFilesize: number, + availableProjects: AvailableProject[], + ownerName: string | undefined, + ownerAvatar: string | undefined, + ownerUrl: string | undefined, + date: Date, + text: string, + attachmentElement: Element | undefined, + projectIds: number[], + stickyProjectId: number | undefined, + onDeleteRedirectUrl: string | undefined, + snippetId: string | undefined, + originalSnippetEl: HTMLElement | undefined, +}; + +export function makeSnippetEdit({ + maxFilesize, + availableProjects, + ownerName, + ownerAvatar, + ownerUrl, + date, + text, + attachmentElement, + projectIds, + stickyProjectId, + onDeleteRedirectUrl, + snippetId, + originalSnippetEl, +}: SnippetEditOptions) { + const snippetEdit = snippetEditTemplate(); + let projectSelector: HTMLSelectElement | null = null; + let originalAttachment: Element | null = null; + const originalText = text; + let attachmentChanged = false; + let hasAttachment = false; + snippetEdit.redirect.value = location.href; + if (ownerAvatar) { + assert(ownerUrl); + snippetEdit.avatarImg.src = ownerAvatar; + snippetEdit.avatarLink.href = ownerUrl; + snippetEdit.avatarImg.hidden = false; + } else { + snippetEdit.avatarImg.hidden = true; + } + snippetEdit.username.textContent = ownerName ?? ""; + snippetEdit.username.href = ownerUrl ?? ""; + snippetEdit.date.textContent = new Intl.DateTimeFormat([], { month: "2-digit", day: "2-digit", year: "numeric" }).format(date); + snippetEdit.text.value = text; + if (attachmentElement) { + originalAttachment = attachmentElement.cloneNode(true) as Element; + clearAttachment(true); + } + if (snippetId !== undefined && snippetId !== null) { + snippetEdit.snippetId.value = snippetId; + } else { + snippetEdit.deleteButton.remove(); + } + + for (let i = 0; i < projectIds.length; ++i) { + let proj = null; + for (let j = 0; j < availableProjects.length; ++j) { + if (projectIds[i] == availableProjects[j].id) { + proj = availableProjects[j]; + break; + } + } + + if (proj) { + addProject(proj); + } + } + updateProjectSelector(); + + if (originalSnippetEl) { + snippetEdit.cancelLink.addEventListener("click", function () { + cancel(); + }); + } else { + snippetEdit.cancelLink.remove(); + } + + function cancel() { + if (originalSnippetEl) { + snippetEdit.root.parentElement!.insertBefore(originalSnippetEl, snippetEdit.root); + } + snippetEdit.root.remove(); + } + + function addProject(proj: AvailableProject) { + let projEl = snippetEditProjectTemplate(); + projEl.projectId.value = `${proj.id}`; + projEl.projectLogo.src = proj.logo; + projEl.projectName.textContent = proj.name; + if (proj.id == stickyProjectId) { + projEl.removeButton.remove(); + } else { + projEl.removeButton.addEventListener("click", function (ev) { + projEl.root.remove(); + updateProjectSelector(); + }); + } + snippetEdit.projectList.appendChild(projEl.root); + } + + function updateProjectSelector() { + if (projectSelector) { + projectSelector.remove(); + } + + let remainingProjects = []; + let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]"); + let assignedIds = []; + for (let i = 0; i < projInputs.length; ++i) { + let id = parseInt(projInputs[i].value, 10); + if (!isNaN(id)) { + assignedIds.push(id); + } + } + for (let i = 0; i < availableProjects.length; ++i) { + let found = false; + for (let j = 0; j < assignedIds.length; ++j) { + if (assignedIds[j] == availableProjects[i].id) { + found = true; + break; + } + } + if (!found) { + remainingProjects.push(availableProjects[i]); + } + } + + if (remainingProjects.length > 0) { + projectSelector = document.createElement("select"); + const option = document.createElement("option"); + option.textContent = "Add to project..."; + option.selected = true; + projectSelector.appendChild(option); + for (let i = 0; i < remainingProjects.length; ++i) { + const option = document.createElement("option"); + option.value = `${remainingProjects[i].id}`; + option.selected = false; + option.textContent = remainingProjects[i].name; + projectSelector.appendChild(option); + } + projectSelector.addEventListener("change", ev => { + assert(projectSelector); + if (projectSelector.selectedOptions.length > 0) { + let selected = projectSelector.selectedOptions[0]; + if (selected.value != "") { + let id = parseInt(selected.value, 10); + if (!isNaN(id)) { + for (let i = 0; i < availableProjects.length; ++i) { + if (availableProjects[i].id == id) { + addProject(availableProjects[i]); + break; + } + } + } + updateProjectSelector(); + } + } + }); + snippetEdit.projectList.appendChild(projectSelector); + } + + } + + function setFile(file: File) { + let dt = new DataTransfer(); + dt.items.add(file); + snippetEdit.file.files = dt.files; + + attachmentChanged = true; + snippetEdit.removeAttachment.value = "false"; + hasAttachment = true; + + let el = null; + if (file.type.startsWith("image/")) { + el = document.createElement("img"); + el.src = URL.createObjectURL(file); + } else if (file.type.startsWith("video/")) { + el = document.createElement("video"); + el.src = URL.createObjectURL(file); + el.controls = true; + } else if (file.type.startsWith("audio/")) { + el = document.createElement("audio"); + el.src = URL.createObjectURL(file); + } else { + el = document.createElement("div"); + el.classList.add("project-card", "br2", "pv1", "ph2"); + let anchor = document.createElement("a"); + anchor.href = URL.createObjectURL(file); + anchor.setAttribute("target", "_blank"); + anchor.textContent = file.name + " (" + readableByteSize(file.size) + ")"; + el.appendChild(anchor); + } + setPreview(el); + validate(); + } + + function clearAttachment(restoreOriginal: boolean) { + snippetEdit.file.value = ""; + let el = null; + attachmentChanged = false; + hasAttachment = false; + snippetEdit.removeAttachment.value = "false"; + if (originalAttachment) { + if (restoreOriginal) { + hasAttachment = true; + el = originalAttachment; + } else { + attachmentChanged = true; + snippetEdit.removeAttachment.value = "true"; + } + } + setPreview(el); + validate(); + } + + function setPreview(el: Element | null) { + if (el) { + snippetEdit.uploadBox.style.display = "none"; + snippetEdit.previewBox.style.display = "block"; + snippetEdit.uploadResetBox.style.display = "none"; + snippetEdit.previewContent = emptyElement(snippetEdit.previewContent); + snippetEdit.previewContent.appendChild(el); + snippetEdit.resetLink.style.display = (!originalAttachment || el == originalAttachment) ? "none" : "inline-block"; + } else { + snippetEdit.uploadBox.style.display = "block"; + snippetEdit.previewBox.style.display = "none"; + if (originalAttachment) { + snippetEdit.uploadResetBox.style.display = "block"; + } + } + } + + function validate() { + let sizeGood = true; + if (snippetEdit.file.files.length > 0 && snippetEdit.file.files[0].size > maxFilesize) { + // NOTE(asaf): Writing this out in bytes to make the limit exactly clear to the user. + let readableSize = new Intl.NumberFormat([], { useGrouping: true }).format(maxFilesize); + snippetEdit.errors.textContent = "File is too big! Max filesize is " + readableSize + " bytes."; + sizeGood = false; + } else { + snippetEdit.errors.textContent = ""; + } + + let hasText = snippetEdit.text.value.trim().length > 0; + + if ((hasText || hasAttachment) && sizeGood) { + snippetEdit.saveButton.disabled = false; + } else { + snippetEdit.saveButton.disabled = true; + } + } + + snippetEdit.uploadLink.addEventListener("click", () => { + snippetEdit.file.click(); + }); + + snippetEdit.removeLink.addEventListener("click", () => { + clearAttachment(false); + }); + + snippetEdit.replaceLink.addEventListener("click", () => { + snippetEdit.file.click(); + }); + + snippetEdit.resetLink.addEventListener("click", () => { + clearAttachment(true); + }); + + snippetEdit.uploadResetLink.addEventListener("click", () => { + clearAttachment(true); + }); + + snippetEdit.file.addEventListener("change", () => { + if (snippetEdit.file.files.length > 0) { + setFile(snippetEdit.file.files[0]); + } + }); + + snippetEdit.root.addEventListener("dragover", ev => { + assert(ev.dataTransfer); + let effect: DataTransfer["dropEffect"] = "none"; + for (let i = 0; i < ev.dataTransfer.items.length; ++i) { + if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { + effect = "copy"; + break; + } + } + ev.dataTransfer.dropEffect = effect; + ev.preventDefault(); + }); + + let enterCounter = 0; + + snippetEdit.root.addEventListener("dragenter", ev => { + assert(ev.dataTransfer); + enterCounter++; + const droppable = Array.from(ev.dataTransfer.items).some( + item => item.kind.toLowerCase() === "file" + ); + if (droppable) { + snippetEdit.root.classList.add("drop"); + } + }); + + snippetEdit.root.addEventListener("dragleave", ev => { + enterCounter--; + if (enterCounter == 0) { + snippetEdit.root.classList.remove("drop"); + } + }); + + snippetEdit.root.addEventListener("drop", ev => { + enterCounter = 0; + snippetEdit.root.classList.remove("drop"); + + if (ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files.length > 0) { + setFile(ev.dataTransfer.files[0]); + } + + ev.preventDefault(); + }); + + snippetEdit.text.addEventListener("paste", ev => { + assert(ev.clipboardData); + const files = ev.clipboardData.files ?? []; + if (files.length > 0) { + setFile(files[0]); + } + }); + + snippetEdit.text.addEventListener("input", () => { + validate(); + }); + + snippetEdit.saveButton.addEventListener("click", ev => { + let projectsChanged = false; + let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]"); + let assignedIds = []; + for (let i = 0; i < projInputs.length; ++i) { + let id = parseInt(projInputs[i].value, 10); + if (!isNaN(id)) { + assignedIds.push(id); + } + } + if (projectIds.length != assignedIds.length) { + projectsChanged = true; + } else { + for (let i = 0; i < projectIds.length; ++i) { + let found = false; + for (let j = 0; j < assignedIds.length; ++j) { + if (projectIds[i] == assignedIds[j]) { + found = true; + } + } + if (!found) { + projectsChanged = true; + break; + } + } + } + + if (originalSnippetEl && (!attachmentChanged && originalText == snippetEdit.text.value.trim() && !projectsChanged)) { + // NOTE(asaf): We're in edit mode and nothing changed, so no need to submit to the server. + ev.preventDefault(); + cancel(); + } + }); + + snippetEdit.deleteButton.addEventListener("click", function (ev) { + if (!window.confirm("Are you sure you want to delete this snippet?")) { + ev.preventDefault(); + return; + } + + snippetEdit.redirect.value = onDeleteRedirectUrl ?? ""; + snippetEdit.file.value = ""; + }); + + validate(); + + return snippetEdit; +} + +export type EditTimelineSnippetOptions = { + maxFilesize: number, + availableProjects: AvailableProject[], + stickyProjectId?: number, + onDeleteRedirectUrl?: string, +}; + +export function editTimelineSnippet(timelineItemEl: HTMLElement, { + maxFilesize, + availableProjects, + stickyProjectId, + onDeleteRedirectUrl, +}: EditTimelineSnippetOptions) { + // TODO(ben): must/types + const ownerName = timelineItemEl.querySelector(".user")?.textContent; + const ownerUrl = timelineItemEl.querySelector(".user")?.href; + const ownerAvatar = timelineItemEl.querySelector(".avatar")?.src; + const creationDate = new Date(must(timelineItemEl.querySelector("time")).dateTime); + const rawDesc = must(timelineItemEl.querySelector(".rawdesc")).textContent; + const attachment = timelineItemEl.querySelector(".timeline-media")?.children?.[0]; + const projectIds: number[] = []; + const projectEls = timelineItemEl.querySelectorAll(".project-id-list > input"); + for (let i = 0; i < projectEls.length; ++i) { + let projid = parseInt(projectEls[i].value, 10); + if (projid) { + projectIds.push(projid); + } + } + let snippetEdit = makeSnippetEdit({ + maxFilesize, + availableProjects, + ownerName, + ownerAvatar, + ownerUrl, + date: creationDate, + text: rawDesc, + attachmentElement: attachment, + projectIds, + stickyProjectId, + onDeleteRedirectUrl, + snippetId: must(timelineItemEl.getAttribute("data-id")), + originalSnippetEl: timelineItemEl, + }); + timelineItemEl.parentElement!.insertBefore(snippetEdit.root, timelineItemEl); + timelineItemEl.remove(); +} diff --git a/src/rawdata/js/tsconfig.json b/src/rawdata/js/tsconfig.json new file mode 100644 index 00000000..1df48adb --- /dev/null +++ b/src/rawdata/js/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2022", + "lib": [ + "ES2022", + "DOM" + ], + "module": "es2022", + "moduleResolution": "bundler", // allow imports and exports without file extensions, which matches esbuild + "moduleDetection": "force", // always treat our TS files as modules + "allowJs": true, + "checkJs": false, + "noEmit": true, + "isolatedModules": true, // treat files independently the way esbuild will + "skipLibCheck": true, + "strict": true, + }, +} \ No newline at end of file diff --git a/src/rawdata/js/user_settings.ts b/src/rawdata/js/user_settings.ts new file mode 100644 index 00000000..201e16c9 --- /dev/null +++ b/src/rawdata/js/user_settings.ts @@ -0,0 +1,79 @@ +import { ImageSelector } from "./lib/image_selector"; +import { initLinkEditor } from "./lib/link_editor"; +import { initHashTabs } from "./lib/tabs"; +import { must } from "./lib/utils"; + +function lengthReporter(inputEl: HTMLInputElement, lengthEl: HTMLElement) { + let updateLength = function () { + lengthEl.textContent = `${inputEl.value.length}/${inputEl.getAttribute("maxlength")}`; + } + inputEl.addEventListener("input", updateLength); + updateLength(); +} + +export type UserSettingsOptions = { + avatarMaxFileSize: number, + avatarUrl: string | undefined, + avatarFilename: string | undefined, + linksJson: string, +}; + +export function init({ + avatarMaxFileSize, + avatarUrl, + avatarFilename, + linksJson, +}: UserSettingsOptions) { + lengthReporter( + must(document.getElementById("realname")) as HTMLInputElement, + must(document.querySelector(".realname-length")), + ); + + let avatarSelector = new ImageSelector( + "user_avatar", + avatarMaxFileSize, + { + originalUrl: avatarUrl, + originalFilename: avatarFilename, + }, + ); + + must(document.querySelector("#upload-avatar-button")).addEventListener("click", e => { + e.preventDefault(); + avatarSelector.openImageInput(); + }); + must(document.querySelector("#user-avatar-placeholder")).replaceWith(avatarSelector.root); + + initLinkEditor(JSON.parse(linksJson)); + + lengthReporter( + must(document.getElementById("shortbio")) as HTMLInputElement, + must(document.querySelector(".shortbio-length")), + ); + + lengthReporter( + must(document.getElementById("longbio")) as HTMLInputElement, + must(document.querySelector(".longbio-length")), + ); + + if (document.getElementById("signature")) { + lengthReporter( + must(document.getElementById("signature")) as HTMLInputElement, + must(document.querySelector(".signature-length")), + ); + } + + initHashTabs(document); + + const discordUnlinkForm = must(document.querySelector('#discord-unlink-form')); + document.querySelector("#unlink-discord-button")?.addEventListener("click", e => { + e.preventDefault(); + discordUnlinkForm.submit(); + }); + + const discordShowcaseBacklogForm = must(document.querySelector("#discord-showcase-backlog")); + document.querySelector("#discord-showcase-backlog-button")?.addEventListener("click", e => { + e.preventDefault(); + discordShowcaseBacklogForm.submit(); + }); +} diff --git a/src/templates/src/blog_index.html b/src/templates/src/blog_index.html index 0c52f63a..18a9f391 100644 --- a/src/templates/src/blog_index.html +++ b/src/templates/src/blog_index.html @@ -1,7 +1,7 @@ {{ template "base-2024.html" . }} {{ define "extrahead" }} - + {{ end }} {{ define "content" }} diff --git a/src/templates/src/blog_post.html b/src/templates/src/blog_post.html index 98bf1301..f8d64bb1 100644 --- a/src/templates/src/blog_post.html +++ b/src/templates/src/blog_post.html @@ -1,7 +1,7 @@ {{ template "base-2024.html" . }} {{ define "extrahead" }} - + {{ end }} {{ define "content" }} diff --git a/src/templates/src/editor.html b/src/templates/src/editor.html index b48ec889..0249c2fe 100644 --- a/src/templates/src/editor.html +++ b/src/templates/src/editor.html @@ -1,10 +1,17 @@ {{ template "base-2024.html" . }} {{ define "extrahead" }} - {{ template "markdown_previews.html" .TextEditor }} + {{ template "markdown_previews.html" }} - - + - {{ end }} {{ define "content" }} @@ -108,137 +107,8 @@

Recent hits

- {{ end }} diff --git a/src/templates/src/project_edit.html b/src/templates/src/project_edit.html index 3c987533..d3cf7797 100644 --- a/src/templates/src/project_edit.html +++ b/src/templates/src/project_edit.html @@ -1,26 +1,53 @@ {{ template "base-2024.html" . }} {{ define "extrahead" }} - {{ template "markdown_previews.html" .TextEditor }} + {{ template "markdown_previews.html" }} {{ template "image_selector.html" }} - - - - - - - + + - {{ end }} {{ define "content" }} @@ -243,7 +242,14 @@

Recent Activity

{{ if .OwnProfile }} document.querySelector(".create_snippet_link")?.addEventListener("click", function() { - let snippetEdit = makeSnippetEdit(userName, userAvatar, userUrl, new Date(), "", null, [], null, null, null); + let snippetEdit = makeSnippetEditLegacy({ + ownerName: userName, + ownerAvatar: userAvatar, + ownerUrl: userUrl, + date: new Date(), + text: "", + projectIds: [], + }); document.querySelector(".timeline").insertBefore(snippetEdit.root, document.querySelector(".timeline").children[0]); document.querySelector(".create_snippet_link")?.remove(); }); @@ -256,7 +262,7 @@

Recent Activity

parent = parent.parentElement; } if (parent && parent.classList.contains("timeline-item")) { - editTimelineSnippet(parent, null); + editTimelineSnippetLegacy(parent); } } }); diff --git a/src/templates/src/user_settings.html b/src/templates/src/user_settings.html index bddc6ec2..931dd9a0 100644 --- a/src/templates/src/user_settings.html +++ b/src/templates/src/user_settings.html @@ -2,11 +2,15 @@ {{ define "extrahead" }} {{ template "image_selector.html" }} - - - - - + {{ end }} {{ define "content" }} @@ -22,61 +26,29 @@
{{ csrftoken .Session }} - - {{ if .DiscordUser }} - - {{ csrftoken .Session }} - + {{ csrftoken .Session }}
- {{ csrftoken .Session }} - + {{ csrftoken .Session }}
{{ end }} diff --git a/src/templates/src/values.html b/src/templates/src/values.html index f285aeec..0c484f69 100644 --- a/src/templates/src/values.html +++ b/src/templates/src/values.html @@ -1,7 +1,7 @@ {{ template "base-2024.html" . }} {{ define "extrahead" }} - + {{ end }} {{ define "content" }} diff --git a/src/website/base_data.go b/src/website/base_data.go index 6036e775..f08092d2 100644 --- a/src/website/base_data.go +++ b/src/website/base_data.go @@ -3,7 +3,7 @@ package website import ( "time" - "git.handmade.network/hmn/hmn/src/buildcss" + "git.handmade.network/hmn/hmn/src/bundle" "git.handmade.network/hmn/hmn/src/config" "git.handmade.network/hmn/hmn/src/hmndata" "git.handmade.network/hmn/hmn/src/hmnurl" @@ -125,7 +125,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc }, } - if buildcss.ActiveServerPort != 0 { + if bundle.ActiveServerPort != 0 { baseData.EsBuildSSEUrl = hmnurl.BuildEsBuild() } diff --git a/src/website/routes.go b/src/website/routes.go index ae2f57bc..a5a147a6 100644 --- a/src/website/routes.go +++ b/src/website/routes.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "git.handmade.network/hmn/hmn/src/buildcss" + "git.handmade.network/hmn/hmn/src/bundle" "git.handmade.network/hmn/hmn/src/db" "git.handmade.network/hmn/hmn/src/email" "git.handmade.network/hmn/hmn/src/hmndata" @@ -48,14 +48,14 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt apiRoutes := routes.WithMiddleware(panicCatcherMiddleware(true)) routes.GET(hmnurl.RegexEsBuild, func(c *RequestContext) ResponseData { - if buildcss.ActiveServerPort != 0 { + if bundle.ActiveServerPort != 0 { var err error defer utils.RecoverPanicAsError(&err) var res ResponseData proxy := httputil.ReverseProxy{ Director: func(r *http.Request) { r.URL.Scheme = "http" - r.URL.Host = fmt.Sprintf("localhost:%d", buildcss.ActiveServerPort) + r.URL.Host = fmt.Sprintf("localhost:%d", bundle.ActiveServerPort) r.Host = "localhost" }, } @@ -69,12 +69,12 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt routes.GET(hmnurl.RegexPublic, func(c *RequestContext) ResponseData { var res ResponseData - if buildcss.ActiveServerPort != 0 { - if strings.HasSuffix(c.Req.URL.Path, ".css") { + if bundle.ActiveServerPort != 0 { + if strings.HasSuffix(c.Req.URL.Path, ".css") || strings.HasSuffix(c.Req.URL.Path, ".js") { proxy := httputil.ReverseProxy{ Director: func(r *http.Request) { r.URL.Scheme = "http" - r.URL.Host = fmt.Sprintf("localhost:%d", buildcss.ActiveServerPort) + r.URL.Host = fmt.Sprintf("localhost:%d", bundle.ActiveServerPort) r.Host = "localhost" }, ModifyResponse: func(res *http.Response) error { diff --git a/src/website/website.go b/src/website/website.go index af740a56..563b1138 100644 --- a/src/website/website.go +++ b/src/website/website.go @@ -13,7 +13,7 @@ import ( "git.handmade.network/hmn/hmn/src/assets" "git.handmade.network/hmn/hmn/src/auth" - "git.handmade.network/hmn/hmn/src/buildcss" + "git.handmade.network/hmn/hmn/src/bundle" "git.handmade.network/hmn/hmn/src/calendar" "git.handmade.network/hmn/hmn/src/config" "git.handmade.network/hmn/hmn/src/db" @@ -53,7 +53,7 @@ var WebsiteCommand = &cobra.Command{ hmns3.StartServer(), assets.BackgroundPreviewGeneration(conn), calendar.MonitorCalendars(), - buildcss.RunServer(), + bundle.RunEsBuildServer(), email.MonitorBounces(conn), NagUsersToCreateJamProjectsJob(conn), }