├── .gitignore
├── backend
├── qqwryFormat
│ ├── start.sh
│ ├── isp.js
│ ├── areafix.js
│ ├── preformat.js
│ ├── server.js
│ ├── special.js
│ ├── countryfix.js
│ ├── owner.js
│ └── index.js
├── cityCN.db
├── country.db
├── ipipfree.ipdb
├── country.php
├── redis.php
├── qqwryUpdate.sh
├── cityCN.php
├── ipinfo.php
├── ipip.php
├── getInfo.php
├── qqwry.php
├── specialIP.php
└── queryInfo.php
├── assets
├── error
│ ├── icons.woff
│ └── style.css
├── img
│ └── favicon.png
├── fonts
│ └── text
│ │ └── nexa
│ │ ├── Nexa Bold.otf
│ │ ├── NexaBold.ttf
│ │ ├── NexaBold.woff
│ │ ├── NexaLight.ttf
│ │ ├── Nexa Light.otf
│ │ └── NexaLight.woff
├── js
│ ├── main.min.js
│ ├── main.js
│ └── jquery.qrcode.min.js
├── css
│ ├── main.min.css
│ └── main.css
└── mapbox
│ └── mapbox-gl.min.css
├── conf
├── docker
│ ├── init.sh
│ ├── nginx.conf
│ ├── iconv.sh
│ └── ip.conf
└── nginx
│ ├── docker.conf
│ └── ip.conf
├── Dockerfile
├── LICENSE
├── README.md
├── error.html
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | backend/qqwry.dat
--------------------------------------------------------------------------------
/backend/qqwryFormat/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | node server&
--------------------------------------------------------------------------------
/backend/cityCN.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/backend/cityCN.db
--------------------------------------------------------------------------------
/backend/country.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/backend/country.db
--------------------------------------------------------------------------------
/backend/ipipfree.ipdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/backend/ipipfree.ipdb
--------------------------------------------------------------------------------
/assets/error/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/assets/error/icons.woff
--------------------------------------------------------------------------------
/assets/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/assets/img/favicon.png
--------------------------------------------------------------------------------
/assets/fonts/text/nexa/Nexa Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/assets/fonts/text/nexa/Nexa Bold.otf
--------------------------------------------------------------------------------
/assets/fonts/text/nexa/NexaBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/assets/fonts/text/nexa/NexaBold.ttf
--------------------------------------------------------------------------------
/assets/fonts/text/nexa/NexaBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/assets/fonts/text/nexa/NexaBold.woff
--------------------------------------------------------------------------------
/assets/fonts/text/nexa/NexaLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/assets/fonts/text/nexa/NexaLight.ttf
--------------------------------------------------------------------------------
/assets/fonts/text/nexa/Nexa Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/assets/fonts/text/nexa/Nexa Light.otf
--------------------------------------------------------------------------------
/assets/fonts/text/nexa/NexaLight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnomd343/echoIP/HEAD/assets/fonts/text/nexa/NexaLight.woff
--------------------------------------------------------------------------------
/backend/qqwryFormat/isp.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '中国': ['教育网', '电信', '联通', '移动', '铁通', '广电网', '鹏博士', '长城'],
3 | '中国-台湾': ['中华电信', '亚太电信', '远传电信'],
4 | }
--------------------------------------------------------------------------------
/backend/qqwryFormat/areafix.js:
--------------------------------------------------------------------------------
1 | const list = {
2 | '天津市河东区 /河北区联通': '天津市联通',
3 | '北京市 广东盈世计算机科技有限公司联通节点': '北京市联通',
4 | '江苏省南京市 驿站网吧(鼓楼区山西路虎啸花园后门)': '江苏省南京市',
5 | }
6 |
7 | module.exports.init = () => {
8 | return list
9 | }
--------------------------------------------------------------------------------
/conf/docker/init.sh:
--------------------------------------------------------------------------------
1 | /usr/bin/redis-server /etc/redis.conf
2 | /usr/sbin/php-fpm7
3 | /usr/sbin/nginx
4 | sh /var/www/echoIP/backend/qqwryUpdate.sh
5 | /usr/sbin/crond
6 | echo "echoIP is already running..."
7 | node /var/www/echoIP/backend/qqwryFormat/server.js
--------------------------------------------------------------------------------
/conf/docker/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes auto;
3 | pcre_jit on;
4 | include /etc/nginx/modules/*.conf;
5 |
6 | events {
7 | worker_connections 1024;
8 | }
9 |
10 | http {
11 | include /etc/nginx/mime.types;
12 | include /etc/nginx/echoip.conf;
13 | }
--------------------------------------------------------------------------------
/backend/qqwryFormat/preformat.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | module.exports.init = () => {
4 | const map = new Map()
5 | try {
6 | const file = fs.readFileSync(__dirname + '/preformat.db', 'utf8')
7 | const data = file.trim().split('\n').map(x => x.split('|'))
8 | for (const item of data) {
9 | map.set([item[0], item[1]].join('|'), {
10 | country_name: item[2],
11 | region_name: item[3],
12 | city_name: item[4],
13 | owner_domain: item[5],
14 | isp_domain: item[6]
15 | })
16 | }
17 | } catch (ignore) {}
18 |
19 | return map
20 | }
21 |
--------------------------------------------------------------------------------
/backend/qqwryFormat/server.js:
--------------------------------------------------------------------------------
1 | var http = require('http');
2 | var url = require('url');
3 | const format = require('./');
4 |
5 | http.createServer(function(req, res){
6 | var params = url.parse(req.url, true).query;
7 | let info = format(params.dataA, params.dataB);
8 | res.write("{");
9 | res.write("\"dataA\": \"" + info['country'] + "\",");
10 | res.write("\"dataB\": \"" + info['area'] + "\",");
11 | res.write("\"country\": \"" + info['country_name'] + "\",");
12 | res.write("\"region\": \"" + info['region_name'] + "\",");
13 | res.write("\"city\": \"" + info['city_name'] + "\",");
14 | res.write("\"domain\": \"" + info['owner_domain'] + "\",");
15 | res.write("\"isp\": \"" + info['isp_domain'] + "\"");
16 | res.write("}");
17 | res.end();
18 | }).listen(1602);
19 |
--------------------------------------------------------------------------------
/backend/country.php:
--------------------------------------------------------------------------------
1 | open('country.db'); // 国家地区缩写及代号数据库
15 | }
16 | }
17 |
18 | function getCountry($code) { // 根据两位国家代码获取英文与中文全称
19 | if ($code == null) {
20 | return null;
21 | }
22 | $db = new countryDB;
23 | $raw = $db->query('SELECT * FROM main WHERE alpha_2=\'' . $code . '\';')->fetchArray(SQLITE3_ASSOC);
24 | $data['code'] = $code;
25 | if ($raw) {
26 | $data['en'] = $raw['name_en'];
27 | $data['cn'] = $raw['name_cn'];
28 | } else {
29 | $data['en'] = null;
30 | $data['cn'] = null;
31 | }
32 | return $data;
33 | }
34 |
35 | ?>
--------------------------------------------------------------------------------
/conf/docker/iconv.sh:
--------------------------------------------------------------------------------
1 | apk add --update wget php7 php7-dev build-base autoconf re2c libtool
2 | mkdir -p /tmp
3 |
4 | wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.16.tar.gz -O /tmp/libiconv.tar.gz
5 | tar xf /tmp/libiconv.tar.gz -C /tmp
6 | cd /tmp/libiconv-1.16/
7 | sed -i 's/_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");/#if HAVE_RAW_DECL_GETS\n_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");\n#endif/g' srclib/stdio.in.h
8 | ./configure --prefix=/usr/local
9 | make && make install
10 |
11 | php_version=`php -r "phpinfo();" | grep "PHP Version" | head -1`
12 | php_version=${php_version#*=> }
13 | wget http://php.net/distributions/php-$php_version.tar.gz -O /tmp/php.tar.gz
14 | tar xf /tmp/php.tar.gz -C /tmp
15 | cd /tmp/php-$php_version/ext/iconv
16 | phpize
17 | ./configure --with-iconv=/usr/local
18 | make && make install
19 |
20 | mkdir /tmp/iconv
21 | cp /usr/local/lib/libiconv.so /tmp/iconv
22 | cp /usr/lib/php7/modules/iconv.so /tmp/iconv
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine as iconv
2 | COPY ./conf/docker/iconv.sh /
3 | RUN sh /iconv.sh
4 |
5 | FROM alpine
6 | LABEL maintainer="dnomd343"
7 | COPY . /var/www/echoIP
8 | COPY --from=iconv /tmp/iconv/ /usr/local/lib/
9 | RUN apk --update add --no-cache nginx curl nodejs redis php7 php7-fpm php7-gmp php7-json php7-iconv php7-redis php7-sqlite3 php7-openssl php7-mbstring && \
10 | rm /usr/lib/php7/modules/iconv.so && ln -s /usr/local/lib/iconv.so /usr/lib/php7/modules/ && \
11 | mv /usr/local/lib/libiconv.so /usr/local/lib/libiconv.so.2 && \
12 | mkdir -p /run/nginx && touch /run/nginx/nginx.pid && \
13 | echo "daemonize yes" >> /etc/redis.conf && \
14 | cp /var/www/echoIP/conf/docker/init.sh / && \
15 | cp /var/www/echoIP/conf/docker/ip.conf /etc/nginx/echoip.conf && \
16 | cp -f /var/www/echoIP/conf/docker/nginx.conf /etc/nginx/nginx.conf && \
17 | cp /var/www/echoIP/conf/docker/init.sh / && \
18 | sed -i '$i\0\t0\t*\t*\t*\t/var/www/echoIP/backend/qqwryUpdate.sh' /var/spool/cron/crontabs/root
19 | EXPOSE 1601
20 | CMD ["sh","init.sh"]
21 |
--------------------------------------------------------------------------------
/conf/nginx/docker.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | listen [::]:80;
4 | server_name ip.343.re; # 改为自己的域名
5 | location / {
6 | if ($http_user_agent !~* (curl|wget)) {
7 | return 301 https://$server_name$request_uri;
8 | }
9 | proxy_set_header Host $http_host;
10 | proxy_set_header X-Real-IP $remote_addr;
11 | proxy_pass http://127.0.0.1:1601;
12 | }
13 | }
14 |
15 | server {
16 | listen 443 ssl http2;
17 | listen [::]:443 ssl http2;
18 | server_name ip.343.re; # 改为自己的域名
19 | ssl_certificate /etc/ssl/certs/343.re/fullchain.pem; # 改为自己的证书
20 | ssl_certificate_key /etc/ssl/certs/343.re/privkey.pem;
21 |
22 | gzip on;
23 | gzip_buffers 32 4K;
24 | gzip_comp_level 6;
25 | gzip_min_length 100;
26 | gzip_types application/javascript text/css text/xml;
27 | gzip_disable "MSIE [1-6]\.";
28 | gzip_vary on;
29 |
30 | location / {
31 | proxy_set_header Host $http_host;
32 | proxy_set_header X-Real-IP $remote_addr;
33 | proxy_pass http://127.0.0.1:1601;
34 | }
35 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Dnomd343
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/backend/redis.php:
--------------------------------------------------------------------------------
1 | true,
5 | 'host' => '127.0.0.1',
6 | 'port' => 6379,
7 | 'passwd' => '',
8 | 'prefix' => 'echoip-',
9 | 'cache_time' => 21600 // 缓存6小时
10 | );
11 |
12 | function getRedisData($ip) { // 查询Redis,不存在返回NULL
13 | $redis = new Redis();
14 | global $redisSetting;
15 | $redis->connect($redisSetting['host'], $redisSetting['port']);
16 | if ($redisSetting['passwd'] != '') {
17 | $redis->auth($redisSetting['passwd']);
18 | }
19 | $redisKey = $redisSetting['prefix'] . $ip;
20 | $redisValue = $redis->exists($redisKey) ? $redis->get($redisKey) : NULL;
21 | return $redisValue;
22 | }
23 |
24 | function setRedisData($ip, $data) { // 写入信息到Redis
25 | $redis = new Redis();
26 | global $redisSetting;
27 | $redis->connect($redisSetting['host'], $redisSetting['port']);
28 | if ($redisSetting['passwd'] != '') {
29 | $redis->auth($redisSetting['passwd']);
30 | }
31 | $redisKey = $redisSetting['prefix'] . $ip;
32 | $redis->set($redisKey, $data); // 写入数据库
33 | $redis->pexpire($redisKey, $redisSetting['cache_time'] * 1000); // 设置过期时间
34 | }
35 |
36 | ?>
37 |
--------------------------------------------------------------------------------
/backend/qqwryUpdate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ua="Mozilla/3.0 (compatible; Indy Library)"
4 |
5 | cd `dirname $0`
6 | mkdir -p temp
7 | cd temp
8 |
9 | if [ -n "$SOCKS5_CN" ]; then
10 | socks5=" --socks5 $SOCKS5_CN"
11 | else
12 | socks5=""
13 | fi
14 |
15 | curl http://update.cz88.net/ip/copywrite.rar -o copywrite.rar$socks5 --user-agent '$ua'
16 | curl http://update.cz88.net/ip/qqwry.rar -o qqwry.rar$socks5 --user-agent '$ua'
17 |
18 | cat > unlock.php <
36 | EOF
37 |
38 | php unlock.php
39 |
40 | file_size=`du qqwry.dat | awk '{print $1}'`
41 | if [ $file_size = "0" ]; then
42 | echo "qqwry.dat update fail."
43 | cd .. && rm -rf temp/
44 | exit
45 | fi
46 |
47 | cd ..
48 | cp -f temp/qqwry.dat qqwry.dat
49 | rm -rf temp/
50 | echo "qqwry.dat update complete."
51 |
--------------------------------------------------------------------------------
/backend/qqwryFormat/special.js:
--------------------------------------------------------------------------------
1 | const rules = [
2 | [
3 | ['country_name', '美国', 'region_name', '俄克拉荷马州', 'city_name', '俄克拉荷马城'],
4 | ['region_name', '奥克拉荷马州', 'city_name', '奥克拉荷马城'],
5 | ],
6 | [
7 | ['country_name', '美国', 'region_name', '乔治亚州'],
8 | ['region_name', '佐治亚州'],
9 | ],
10 | [
11 | ['country_name', '美国', 'region_name', '得克萨斯州'],
12 | ['region_name', '德克萨斯州'],
13 | ],
14 | [
15 | ['country_name', '美国', 'region_name', '俄克拉荷马州'],
16 | ['region_name', '奥克拉荷马州'],
17 | ],
18 | [
19 | ['country_name', '美国', 'region_name', '罗德岛州'],
20 | ['region_name', '罗得岛州'],
21 | ],
22 | [
23 | ['country_name', '俄罗斯', 'region_name', '伊尔库州'],
24 | ['region_name', '伊尔库茨克州'],
25 | ],
26 | [
27 | ['country_name', '韩国', 'region_name', '首尔'],
28 | ['region_name', '首尔特别市'],
29 | ],
30 | ]
31 |
32 | module.exports = (result) => {
33 | for (let [rule, change] of rules) {
34 | let flag = true
35 | for (let i = 0; i < rule.length; i += 2) {
36 | if (result[rule[i]] !== rule[i + 1]) {
37 | flag = false
38 | break
39 | }
40 | }
41 | if (flag) {
42 | for (let i = 0; i < change.length; i += 2) {
43 | result[change[i]] = change[i + 1]
44 | }
45 | return
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/conf/docker/ip.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 1601;
3 | set $my_host 127.0.0.1:1601;
4 | set_real_ip_from 0.0.0.0/0;
5 | real_ip_header X-Real-IP;
6 |
7 | root /var/www/echoIP;
8 | error_page 403 404 = /error.html;
9 |
10 | location ^~ /assets {}
11 | location = /index.html {}
12 | location = /error.html {}
13 |
14 | location = /error {
15 | index error.html;
16 | }
17 |
18 | location = /ua {
19 | if ($http_user_agent ~* (curl|wget)) {
20 | return 200 $http_user_agent\n;
21 | }
22 | default_type application/json;
23 | return 200 $http_user_agent;
24 | }
25 |
26 | location = / {
27 | proxy_set_header X-Real-IP $remote_addr;
28 | if ($http_user_agent ~* (curl|wget)) {
29 | proxy_pass http://$my_host/query?justip=true&cli=true;
30 | }
31 | index index.html;
32 | }
33 |
34 | location / {
35 | set $query_param $query_string;
36 | if ($http_user_agent ~* (curl|wget)) {
37 | set $query_param $query_param&cli=true;
38 | }
39 | include fastcgi_params;
40 | fastcgi_pass 127.0.0.1:9000;
41 | fastcgi_param QUERY_STRING $query_param;
42 | fastcgi_param SCRIPT_FILENAME /var/www/echoIP/backend/queryInfo.php;
43 | }
44 | }
--------------------------------------------------------------------------------
/backend/cityCN.php:
--------------------------------------------------------------------------------
1 | open('cityCN.db'); // 中国省市经纬度数据库
16 | }
17 | }
18 |
19 | function getLoc($region, $city) { // 根据省份/城市信息查询经纬度
20 | $db = new cityDB;
21 | $data['region'] = $region;
22 | $data['city'] = $city;
23 | $query_str='SELECT * FROM main WHERE level1="'.$region.'" AND level2="'.$city.'";';
24 | $raw = $db->query($query_str)->fetchArray(SQLITE3_ASSOC);
25 | if (!$raw) { // 查无数据
26 | $query_str='SELECT * FROM main WHERE level1="'.$region.'" AND level2="-";'; // 尝试仅查询省份数据
27 | $raw = $db->query($query_str)->fetchArray(SQLITE3_ASSOC);
28 | if (!$raw) { // 省份错误,返回北京经纬度
29 | $data['region'] = '北京';
30 | $data['city'] = '北京';
31 | $data['lat'] = '39.91';
32 | $data['lon'] = '116.73';
33 | return $data;
34 | }
35 | if ($city == '') {
36 | $query_str='SELECT * FROM main WHERE level1="'.$region.'" LIMIT 1,1;'; // 获取省会记录
37 | $raw = $db->query($query_str)->fetchArray(SQLITE3_ASSOC);
38 | $data['city'] = $raw['level2'];
39 | }
40 | }
41 | $data['lat'] = $raw['lat'];
42 | $data['lon'] = $raw['lon'];
43 | return $data;
44 | }
45 |
46 | ?>
--------------------------------------------------------------------------------
/backend/ipinfo.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
echoIP
5 | 显示客户端IP、查询IP详细信息
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | + 获取IP地址的AS、地址、ISP、网段等详细信息,支持IPv4与IPv6地址。
15 |
16 | + 项目部署在服务器上,客户端向服务器查询自身IP地址,或任意IP地址的详细信息。
17 |
18 | + 兼容CDN封装在http头部的原始IP信息,部署时无需做额外修改,直接启用CDN加速即可。
19 |
20 | + 数据来自多个上游服务整合处理,包括在线API与离线数据库,同时支持命令行与网页端查询方式。
21 |
22 | ## 使用方法
23 |
24 | ### 命令行模式
25 |
26 | ```
27 | # 查询客户端IP
28 | shell> curl ip.343.re
29 |
30 | # 查询客户端UA
31 | shell> curl ip.343.re/ua
32 |
33 | # 查询客户端IP的详细信息
34 | shell> curl ip.343.re/info
35 |
36 | # 查询指定IP地址详细信息
37 | shell> curl ip.343.re/8.8.8.8
38 | ```
39 |
40 | 
41 |
42 | 更多使用方法见[命令列表](https://github.com/dnomd343/echoIP/wiki/cmd-list)
43 |
44 | ### 网页访问模式
45 |
46 | 你可以直接在 [ip.343.re](https://ip.343.re/) 或 [ip.dnomd343.top](https://ip.dnomd343.top/) 上进行查询,或者将项目部署到自己的服务器上。
47 |
48 | 
49 |
50 | + 双击IP字段,可获取当前数据库版本。
51 |
52 | + 点击AS编号,将跳转到该自治系统的详细信息页面。
53 |
54 | + 点击经纬度信息,将打开谷歌地球并显示该点的三维图像。
55 |
56 | + 双击显示框空白处,将会弹出一个二维码,扫描可以直达当前页面。
57 |
58 | ## 部署教程
59 |
60 | > 如果想在自己域名下建立echoIP服务,可按如下方式部署
61 |
62 | [容器部署方式(推荐)](https://github.com/dnomd343/echoIP/wiki/setup-docker)
63 |
64 | [常规部署方式](https://github.com/dnomd343/echoIP/wiki/setup)
65 |
66 | [CDN注意事项](https://github.com/dnomd343/echoIP/wiki/cdn)
67 |
68 | [Proxy Protocol支持](https://github.com/dnomd343/echoIP/wiki/proxy-protocol)
69 |
70 | ## 开发资料
71 |
72 | [容器构建](https://github.com/dnomd343/echoIP/wiki/docker-build)
73 |
74 | [开发接口](https://github.com/dnomd343/echoIP/wiki/interface)
75 |
76 | [上游服务](https://github.com/dnomd343/echoIP/wiki/upstream)
77 |
78 | ## 许可证
79 |
80 | MIT ©2021 [@dnomd343](https://github.com/dnomd343) [@ShevonKuan](https://github.com/ShevonKuan)
--------------------------------------------------------------------------------
/backend/qqwryFormat/countryfix.js:
--------------------------------------------------------------------------------
1 | const list = {
2 | 'IANA': '保留地址',
3 | '欧盟': '欧洲地区',
4 | '亚洲': '亚洲地区',
5 | '欧洲': '欧洲地区',
6 | '澳洲': '澳大利亚',
7 | '东北三省': '中国',
8 | '长江大学东校区': '中国/湖北荆州',
9 | '沈阳市东陵区': '中国/辽宁沈阳',
10 | '沈阳市皇姑区': '中国/辽宁沈阳',
11 | '合肥工业大学': '中国/安徽合肥',
12 | '宁波大学': '中国/浙江宁波',
13 | '长江大学': '中国/湖北荆州',
14 | '联合国': '美国',
15 | '欧美地区': '美洲地区',
16 | '运营商级NAT': '共享地址',
17 | '电信': '中国/电信',
18 | '印尼': '印度尼西亚',
19 | '韩国首尔': '韩国/首尔',
20 | '本地': '本地链路',
21 | '广州市清远市清城区': '中国/广东省广州市清远市清城区',
22 | '加勒比海地区': '库拉索',
23 | '孟加拉国': '孟加拉',
24 | '成都信息工程学院': '中国/四川成都',
25 | '华东理工大学': '中国/上海',
26 | '南开大学': '中国/天津',
27 | 'CoreLink骨干网': '骨干网/CoreLink',
28 | '长春工业大学': '中国/吉林长春',
29 | '西安石油大学(本部)计算机中心二楼(第五微机室)': '中国/陕西西安',
30 | '西安石油大学': '中国/陕西西安',
31 | '北方工业大学': '中国/北京',
32 | '首都经贸大学东区7楼大机房': '中国/北京',
33 | '首都经贸大学': '中国/北京',
34 | '荷兰省': '荷兰/北荷兰省',
35 | '南京大学鼓楼校区': '中国/江苏南京',
36 | '南京大学': '中国/江苏南京',
37 | '集美大学航海学院': '中国/福建厦门',
38 | '华中农业大学': '中国/湖北武汉',
39 | '首都师范大学': '中国/北京',
40 | '成都理工大学': '中国/四川成都',
41 | '哈尔滨工程大学': '中国/黑龙江哈尔滨',
42 | '佳木斯大学': '中国/黑龙江佳木斯',
43 | '太原科技大学': '中国/山西太原',
44 | '中北大学': '中国/山西太原',
45 | '青岛大学': '中国/山东青岛',
46 | '华东师范大学': '中国/上海',
47 | '南京理工大学': '中国/江苏南京',
48 | '中南大学岳麓山校区': '中国/湖南长沙',
49 | '中南大学': '中国/湖南长沙',
50 | '华中农业大学学生宿舍': '中国/湖北武汉',
51 | '中南财经政法大学': '中国/湖北武汉',
52 | '武汉科技大学': '中国/湖北武汉',
53 | '西安科技大学': '中国/陕西西安',
54 | '东北农业大学': '中国/黑龙江哈尔滨',
55 | '对外经济贸易大学学生公寓': '中国/广东广州',
56 | '东华大学': '中国/上海',
57 | '首都科技大学': '中国/北京',
58 | '长沙理工大学': '中国/四川长沙',
59 | '南昌理工学院': '中国/江西南昌',
60 | '华南理工大学北区北十一': '中国/广东广州',
61 | '华南理工大学北区B2': '中国/广东广州',
62 | '华中科技大学韵苑公寓15栋': '中国/湖北武汉',
63 | '华中科技大学韵苑公寓': '中国/湖北武汉',
64 | '武汉大学医学部': '中国/湖北武汉',
65 | '武汉大学信息学部': '中国/湖北武汉',
66 | '武汉大学工学部': '中国/湖北武汉',
67 | '大连理工大学': '中国/辽宁大连',
68 | '西安建筑科技大学': '中国/陕西西安',
69 | '西安思源学院': '中国/陕西西安',
70 | '对外经济贸易大学': '中国/广东广州',
71 | 'IANA机构': '保留地址',
72 | 'IANA保留地址': '保留地址',
73 | '新加披': '新加坡',
74 | '苏格兰': '英国/苏格兰',
75 | '美国加利福尼亚州费里蒙CodecCloud数据中心': '美国/加利福尼亚州费利蒙CodecCloud数据中心',
76 | '美国弗吉尼亚州阿什本Amazon数据中心': '美国/弗吉尼亚州阿什本Amazon数据中心',
77 | '美国新泽西州皮斯卡特维Choopa数据中心': '美国/新泽西州皮斯卡特维Choopa数据中心',
78 | '天津市河北区': '中国/天津市',
79 | }
80 |
81 | module.exports.init = () => {
82 | for (const [raw, regions] of Object.entries(list)) {
83 | list[raw] = regions.split('/')
84 | }
85 | return list
86 | }
--------------------------------------------------------------------------------
/conf/nginx/ip.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | listen [::]:80;
4 | server_name ip.343.re; # 改为自己的域名
5 | location / {
6 | if ($http_user_agent !~* (curl|wget)) {
7 | return 301 https://$server_name$request_uri;
8 | }
9 | proxy_set_header Host $http_host;
10 | proxy_set_header X-Real-IP $remote_addr;
11 | proxy_pass http://127.0.0.1:1601;
12 | }
13 | }
14 |
15 | server {
16 | listen 443 ssl http2;
17 | listen [::]:443 ssl http2;
18 | server_name ip.343.re; # 改为自己的域名
19 | ssl_certificate /etc/ssl/certs/343.re/fullchain.pem; # 改为自己的证书
20 | ssl_certificate_key /etc/ssl/certs/343.re/privkey.pem;
21 |
22 | gzip on;
23 | gzip_buffers 32 4K;
24 | gzip_comp_level 6;
25 | gzip_min_length 100;
26 | gzip_types application/javascript text/css text/xml;
27 | gzip_disable "MSIE [1-6]\.";
28 | gzip_vary on;
29 |
30 | location / {
31 | proxy_set_header Host $http_host;
32 | proxy_set_header X-Real-IP $remote_addr;
33 | proxy_pass http://127.0.0.1:1601;
34 | }
35 | }
36 |
37 | server {
38 | listen 127.0.0.1:1601;
39 | set $my_host 127.0.0.1:1601;
40 | set_real_ip_from 0.0.0.0/0;
41 | real_ip_header X-Real-IP;
42 |
43 | root /var/www/echoIP;
44 | error_page 403 404 = /error.html;
45 |
46 | location ^~ /assets {}
47 | location = /index.html {}
48 | location = /error.html {}
49 |
50 | location = /error {
51 | index error.html;
52 | }
53 |
54 | location = /ua {
55 | if ($http_user_agent ~* (curl|wget)) {
56 | return 200 $http_user_agent\n;
57 | }
58 | default_type application/json;
59 | return 200 $http_user_agent;
60 | }
61 |
62 | location = / {
63 | proxy_set_header X-Real-IP $remote_addr;
64 | if ($http_user_agent ~* (curl|wget)) {
65 | proxy_pass http://$my_host/query?justip=true&cli=true;
66 | }
67 | index index.html;
68 | }
69 |
70 | location / {
71 | set $query_param $query_string;
72 | if ($http_user_agent ~* (curl|wget)) {
73 | set $query_param $query_param&cli=true;
74 | }
75 | include fastcgi_params;
76 | fastcgi_pass 127.0.0.1:9000; # 服务器php-fpm接口
77 | fastcgi_param QUERY_STRING $query_param;
78 | fastcgi_param SCRIPT_FILENAME /var/www/echoIP/backend/queryInfo.php;
79 | }
80 | }
--------------------------------------------------------------------------------
/backend/ipip.php:
--------------------------------------------------------------------------------
1 | fp = fopen($fileName, 'rb');
25 | $metaSize = unpack('N', fread($this->fp, 4))[1]; // 获取元数据长度
26 | $this->meta = json_decode(fread($this->fp, $metaSize), 1); // 读取元数据信息
27 | $this->nodeCount = $this->meta['node_count'];
28 | $this->nodeOffset = 4 + $metaSize;
29 | }
30 |
31 | public function __destruct() { // 析构函数
32 | if ($this->fp) {
33 | fclose($this->fp);
34 | }
35 | }
36 |
37 | public function getVersion() { // 获取版本信息
38 | return date("Ymd", $this->meta['build']);
39 | }
40 |
41 | public function getDistrict($ip) { // 获取地区信息
42 | $node = $this->getNode($ip);
43 | if ($node <= 0) {
44 | return NULL;
45 | }
46 | $data = explode("\t", $this->getData($node));
47 | foreach ($data as &$field) {
48 | $field = trim($field);
49 | }
50 | if ($data[1] === '中国' && $data[2] === '中国') {
51 | $data[1] = $data[2] = '';
52 | }
53 | return $data;
54 | }
55 |
56 | private function getNode($ip) { // 获取节点编号
57 | $node = 0;
58 | $binary = inet_pton($ip);
59 | for ($i = 0; $i < 96 && $node < $this->nodeCount; $i++) {
60 | if ($i >= 80) {
61 | $node = $this->readNode($node, 1);
62 | } else {
63 | $node = $this->readNode($node, 0);
64 | }
65 | if ($node > $this->nodeCount) {
66 | return 0;
67 | }
68 | }
69 | for ($i = 0; $i < 32; $i++) {
70 | if ($node >= $this->nodeCount) {
71 | break;
72 | }
73 | $node = $this->readNode($node, 1 & ((0xFF & ord($binary[$i >> 3])) >> 7 - ($i % 8)));
74 | }
75 | if ($node <= $this->nodeCount) {
76 | return NULL;
77 | }
78 | return $node;
79 | }
80 |
81 | private function readNode($node, $index) {
82 | return unpack('N', $this->read($node * 8 + $index * 4, 4))[1];
83 | }
84 |
85 | private function getData($node) { // 根据节点编号获取信息
86 | $offset = $node + $this->nodeCount * 7;
87 | $bytes = $this->read($offset, 2);
88 | $size = unpack('N', str_pad($bytes, 4, "\x00", STR_PAD_LEFT))[1];
89 | $offset += 2;
90 | return $this->read($offset, $size);
91 | }
92 |
93 | private function read($offset, $length) { // 从指定位置读取指定长度
94 | if ($length <= 0) {
95 | return NULL;
96 | }
97 | if (fseek($this->fp, $offset + $this->nodeOffset) === 0) {
98 | return fread($this->fp, $length);
99 | }
100 | }
101 | }
102 |
103 | ?>
104 |
--------------------------------------------------------------------------------
/backend/qqwryFormat/owner.js:
--------------------------------------------------------------------------------
1 | const list = [
2 | // 通用
3 | ['cloudflare.com', 'cloudflare', '*'],
4 | ['microsoft.com', ['microsoft', '微软'], '*'],
5 | ['akamai.com', 'akamai', '*'],
6 | ['amazon.com', 'amazon', '*'],
7 | ['amazon.com', 'cloudfront', '*'],
8 | ['digitalocean.com', 'digitalocean', '*'],
9 | ['choopa.com', 'choopa', '*'],
10 | ['ntt.com', ['ntt网络', 'ntt通信'], '*'],
11 | ['he.net', 'hurricane electric', '*'],
12 | ['level3.com', ['level3', 'level 3'], '*'],
13 | ['zenlayer.com', 'zenlayer', '*'],
14 | ['facebook.com', 'facebook', '*'],
15 | ['cogentco.com', 'cogent通信', '*'],
16 | ['godaddy.com', 'godaddy', '*'],
17 | ['starhub.com', 'starhub', '*'],
18 | ['ovh.com', 'ovh', '*'],
19 | ['fiber.google.com', 'google fiber', '*'],
20 | ['cloud.google.com', 'google云计算', '*'],
21 | ['sita.aero', '国际航空电讯集团公司(sita)', '*'],
22 | ['aliyun.com', '阿里云', '*'],
23 | ['cloud.tencent.com', '腾讯云', '*'],
24 | ['huawei.com', '华为', '*'],
25 | ['cloudinnovation.org', 'cloudinnovation', '*'],
26 | ['att.com', ['ATT用户', 'AT&T'], '*'],
27 | ['edgecast.com', 'EdgeCast', '*'],
28 | ['cdnetworks.com', 'CDNetworks', '*'],
29 | ['hp.com', '惠普HP', '*'],
30 | ['apple.com', 'apple', '*'],
31 | ['fastly.com', 'Fastly', '*'],
32 | // 混合
33 | ['rixcloud.com', 'rixcloud', ['中国-香港', '美国', '日本', '英国', '俄罗斯', '巴西', '荷兰', '新加坡']],
34 | ['linode.com', 'linode', ['德国', '日本', '美国', '英国', '新加坡', '德国', '加拿大']],
35 | ['yandex.ru', 'yandex', ['俄罗斯', '荷兰', '美国', '乌克兰']],
36 | ['apnic.net', ['APNIC', '亚太互联网络信息中心'], ['澳大利亚', '马来西亚', '德国', '日本', '美国']],
37 | // 中国
38 | ['qingcloud.com', ['青云数据中心', '青云电信节点'], '中国'],
39 | ['ksyun.com', '金山云', '中国'],
40 | ['netease.com', '网易', ['中国', '中国-香港']],
41 | ['shuim.net', 'shuiM Data Exchange Center', '中国'],
42 | // 中国-台湾
43 | ['cht.com.tw', '中华电信', '中国-台湾'],
44 | ['so-net.net.tw', 'So-net', '中国-台湾'],
45 | ['tinp.net.tw', '台基科', '中国-台湾'],
46 | // 中国-香港
47 | ['pccw.com', '电讯盈科', ['中国-香港', '美国']],
48 | // 美国
49 | ['macstadium.com', 'macstadium', '美国'],
50 | ['riven.ee', 'rivencloud', ['美国', '中国-香港', '法国', '德国']],
51 | ['github.com', 'github', ['美国', '荷兰']],
52 | ['it7.net', 'it7', ['美国', '俄罗斯']],
53 | ['defense.gov', '国防部', '美国'],
54 | ['dod.com', 'DoD网络信息中心', '美国'],
55 | ['ibm.com', 'IBM公司', '美国'],
56 | ['comcast.com', 'Comcast通信公司', '美国'],
57 | ['rackspace.com', 'Rackspace Hosting公司', '美国'],
58 | // 新西兰
59 | ['vocus.co.nz', 'vocus', '新西兰'],
60 | // 越南
61 | ['hanelcom.vn', 'hanelcom', '越南'],
62 | ['vnpt.vn', 'VNPT', '越南'],
63 | // 韩国
64 | ['kt.com', 'kt电信', '韩国'],
65 | // 日本
66 | ['idcf.jp', 'idcf', '日本'],
67 | ['jcom.co.jp', 'j:com电信', '日本'],
68 | ['megaegg.jp', 'Energia通讯', '日本'],
69 | // 英国
70 | ['gov.uk', '社会保险安全部', '英国'],
71 | // 加拿大
72 | ['bell.ca', 'Bell', '加拿大'],
73 | ]
74 |
75 | module.exports.init = () => {
76 | const result = {}
77 | for (const item of list) {
78 | let name = item[0]
79 | let keyword = item[1]
80 | if (!Array.isArray(keyword)) keyword = [keyword]
81 | let country = item[2]
82 | if (!Array.isArray(country)) country = [country]
83 | for (let t_keyword of keyword) {
84 | for (let t_country of country) {
85 | if (result[t_country] == undefined) result[t_country] = {}
86 | result[t_country][t_keyword.toLowerCase()] = name.toLowerCase()
87 | }
88 | }
89 | }
90 | return result
91 | }
--------------------------------------------------------------------------------
/backend/qqwryFormat/index.js:
--------------------------------------------------------------------------------
1 | const iso3166 = require('./iso3166').init()
2 | const china_keyword = Object.keys(iso3166['中国'])
3 | const countryfix_list = require('./countryfix').init()
4 | const areafix_list = require('./areafix').init()
5 | const isp_list = require('./isp')
6 | const owner_list = require('./owner').init()
7 | const specialfix = require('./special')
8 | const preformat = require('./preformat').init()
9 |
10 | /**
11 | * 进行市级粒度匹配
12 | */
13 | const city_match = (result, country, area, citys) => {
14 | if (citys.length) {
15 | for (const city of citys) {
16 | if (area.includes(city)) {
17 | result.city_name = city
18 | return result
19 | }
20 | }
21 | } else {
22 | const regions = iso3166[country]
23 | for (let [region, value] of Object.entries(regions)) {
24 | for (let city of value) {
25 | if (area.includes(city)) {
26 | result.region_name = region
27 | result.city_name = city
28 | return result
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
35 |
36 | module.exports = (country, area) => {
37 |
38 | const result = {
39 | country,
40 | area,
41 | country_name: '',
42 | region_name: '',
43 | city_name: '',
44 | owner_domain: '',
45 | isp_domain: ''
46 | }
47 |
48 | /**
49 | * 数据库预匹配
50 | */
51 | if (preformat.has(`${country}|${area}`)) {
52 | const info = preformat.get(`${country}|${area}`)
53 | result.country_name = info.country_name
54 | result.region_name = info.region_name
55 | result.city_name = info.city_name
56 | result.owner_domain = info.owner_domain
57 | result.isp_domain = info.isp_domain
58 | return result
59 | }
60 |
61 | /**
62 | * 去除数据中存在的空格字符,避免干扰判断
63 | *
64 | * { country: '马来西亚', area: ' CZ88.NET' } ->
65 | * { country: '马来西亚', area: 'CZ88.NET' } ->
66 | */
67 | country = country.trim()
68 | area = area.trim()
69 |
70 | /**
71 | * 根据关键词修正中国地区标注
72 | *
73 | * { country: '内蒙古通辽市开鲁县', area: '联通' } ->
74 | * { country: '中国', area: '内蒙古通辽市开鲁县 联通' }
75 | */
76 | if (!iso3166[country]) {
77 | for (const keyword of china_keyword) {
78 | if (country.includes(keyword)) {
79 | area = country + ' ' + area
80 | country = '中国'
81 | break
82 | }
83 | }
84 | }
85 |
86 | /**
87 | * 修正国家字段命名不规范的数据
88 | *
89 | * { country: '华中科技大学韵苑公寓15栋', area: '623' } ->
90 | * { country: '中国', area: '湖北省武汉市 华中科技大学韵苑公寓15栋 623' }
91 | */
92 | if (countryfix_list[country]) {
93 | let info = countryfix_list[country]
94 | country = info[0]
95 | if (info.length === 2) area = info[1] + ' ' + area
96 | }
97 |
98 | /**
99 | * 处理高校教育网教育网
100 | *
101 | * { country: '中国', area: '湖北省宜昌市 三峡大学电子阅览室' } ->
102 | * { country: '中国', area: '湖北省宜昌市 三峡大学电子阅览室(教育网)' } ->
103 | */
104 | if (country === '中国' && area.includes('大学') && !area.includes('网吧')) {
105 | area += '(教育网)'
106 | }
107 |
108 | /**
109 | * 修正区域字段存在混淆的数据
110 | *
111 | * { country: '江苏省宿迁市沭阳县', area: '上海南路(农机校对面)凌云网吧' } ->
112 | * { country: '江苏省宿迁市沭阳县', area: '凌云网吧' }
113 | */
114 | if (areafix_list[area]) {
115 | area = areafix_list[area]
116 | }
117 |
118 | /**
119 | * 进行国家粒度匹配
120 | */
121 | if (iso3166[country]) {
122 | result.country_name = country
123 | result.region_name = country
124 | }
125 |
126 | /**
127 | * 进行省级粒度匹配
128 | */
129 | let citys = []
130 | if (iso3166[country]) {
131 | const regions = iso3166[country]
132 | for (const [region, value] of Object.entries(regions)) {
133 | if (region !== country && area.includes(region)) {
134 | result.region_name = region
135 | citys = value
136 | break
137 | }
138 | }
139 | }
140 |
141 | /**
142 | * 进行市级粒度匹配,深度匹配
143 | */
144 | if (iso3166[country]) {
145 | city_match(result, country, area, citys)
146 | }
147 |
148 | /**
149 | * 进行特殊修正
150 | */
151 | specialfix(result)
152 |
153 | /**
154 | * 进行运营商匹配
155 | */
156 | const isps = isp_list[`${result.country_name}-${result.region_name}`]
157 | || isp_list[result.country_name]
158 | || []
159 | for (const isp of isps) {
160 | if (area.includes(isp)) {
161 | result.isp_domain = isp
162 | break
163 | }
164 | }
165 |
166 | /**
167 | * 进行服务持有方匹配
168 | */
169 | let owners = owner_list[`${result.country_name}-${result.region_name}`]
170 | || owner_list[result.country_name]
171 | || []
172 | owners = {...owners, ...owner_list['*']}
173 | for (const [owner, value] of Object.entries(owners)) {
174 | if (area.toLowerCase().includes(owner)) {
175 | result.owner_domain = value
176 | break
177 | }
178 | }
179 |
180 | /**
181 | * 数据兜底
182 | */
183 | if (result.country_name === '') {
184 | result.country_name = country
185 | result.region_name = area
186 | }
187 |
188 | return result
189 | }
190 |
--------------------------------------------------------------------------------
/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Dnomd343 - IP
10 |
11 |
12 |
13 |
14 |
22 |
23 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
44 |
45 |
46 |
Page not found
47 |
The page you are looking for is not here.
48 |
49 |
50 |
51 |
52 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/backend/getInfo.php:
--------------------------------------------------------------------------------
1 | getDistrict($ip); // 获取IPIP.net数据
29 | $data = IPinfo::getInfo($ip); // 获取ipinfo.io数据
30 | $country = getCountry($data['country']); // 解析国家2位编码
31 | $qqwry = new QQWry('qqwry.dat');
32 | $detail = $qqwry->getDetail($ip); // 获取纯真IP数据
33 | $info['ip'] = $data['ip'];
34 | $info['as'] = $data['as'];
35 | $info['city'] = $data['city'];
36 | $info['region'] = $data['region'];
37 | if (isset($data['country'])) {
38 | $info['country'] = $data['country'];
39 | if (isset($country['en'])) {
40 | $info['country'] .= ' - ' . $country['en'];
41 | $info['country'] .= "(" . $country['cn'] . ")";
42 | }
43 | }
44 | $info['timezone'] = $data['timezone'];
45 | $info['loc'] = $data['loc'];
46 | $info['isp'] = $data['isp'];
47 | if ($detail['country'] == '中国') {
48 | $info['country'] = 'CN - China(中国)';
49 | $info['timezone'] = 'Asia/Shanghai';
50 | if ($detail['region'] == '台湾') { // 修正台湾数据带 "市" 或 "县" 的情况
51 | if (mb_substr($detail['city'], -1) == '市' || mb_substr($detail['city'], -1) == '县') {
52 | $detail['city'] = mb_substr($detail['city'], 0, mb_strlen($detail['city']) - 1);
53 | }
54 | }
55 | if ($detail['region'] == '' && $detail['city'] == '') { // 纯真库解析不出数据
56 | if ($addr[1] != '' || $addr[2] != '') { // IPIP数据不同时为空
57 | $detail['region'] = $addr[1];
58 | $detail['city'] = $addr[2];
59 | }
60 | } else if ($detail['region'] == '' || $detail['city'] == '') { // 纯真库存在空数据
61 | if ($addr[1] != '' && $addr[2] != '') { // IPIP数据完整
62 | $detail['region'] = $addr[1]; // 修正纯真数据
63 | $detail['city'] = $addr[2];
64 | }
65 | }
66 | if ($detail['region'] != '' || $detail['city'] != '') { // 修正后数据不同时为空
67 | $cityLoc = getLoc($detail['region'], $detail['city']); // 获取城市经纬度
68 | if ($cityLoc['region'] != '香港' && $cityLoc['region'] != '澳门' && $cityLoc['region'] != '台湾') { // 跳过港澳台数据
69 | $info['region'] = $cityLoc['region'];
70 | $info['city'] = $cityLoc['city'];
71 | $info['loc'] = $cityLoc['lat'] . ',' . $cityLoc['lon'];
72 | }
73 | }
74 | if ($detail['isp'] == '教育网') { // 载入纯真库分析出的ISP数据
75 | $info['isp'] = 'China Education and Research Network';
76 | } else if ($detail['isp'] == '电信') {
77 | $info['isp'] = 'China Telecom';
78 | } else if ($detail['isp'] == '联通') {
79 | $info['isp'] = 'China Unicom Limited';
80 | } else if ($detail['isp'] == '移动') {
81 | $info['isp'] = 'China Mobile Communications Corporation';
82 | } else if ($detail['isp'] == '铁通') {
83 | $info['isp'] = 'China Tietong Telecom';
84 | } else if ($detail['isp'] == '广电网') {
85 | $info['isp'] = 'Shaanxi Broadcast & TV Network Intermediary';
86 | } else if ($detail['isp'] == '鹏博士') {
87 | $info['isp'] = 'Chengdu Dr.Peng Technology';
88 | } else if ($detail['isp'] == '长城') {
89 | $info['isp'] = 'Great Wall Broadband Network Service';
90 | } else if ($detail['isp'] == '中华电信') {
91 | $info['isp'] = 'ChungHwa Telecom';
92 | } else if ($detail['isp'] == '亚太电信') {
93 | $info['isp'] = 'Asia Pacific Telecom';
94 | } else if ($detail['isp'] == '远传电信') {
95 | $info['isp'] = 'Far EasTone Telecommunications';
96 | }
97 | }
98 | if (filter_var($ip, \FILTER_VALIDATE_IP,\FILTER_FLAG_IPV4)) { // 录入纯真库数据
99 | $info['scope'] = tryCIDR($detail['beginIP'], $detail['endIP']);
100 | $info['detail'] = $detail['dataA'] . $detail['dataB'];
101 | } else {
102 | $info['scope'] = $info['ip'];
103 | $info['detail'] = $info['as'] . ' ' . $info['isp'];
104 | }
105 | }
106 | if (trim($info['detail']) == '') {
107 | $info['detail'] = null;
108 | }
109 | return $info;
110 | }
111 |
112 | function tryCIDR($beginIP, $endIP) { // 给定IP范围,尝试计算CIDR
113 | $tmp = ip2long($endIP) - ip2long($beginIP) + 1;
114 | if (pow(2, intval(log($tmp, 2))) == $tmp) { // 判断是否为2的整数次方
115 | return $beginIP . '/' . (32 - log($tmp, 2));
116 | } else {
117 | return $beginIP . ' - ' . $endIP;
118 | }
119 | }
120 |
121 | function getVersion() { // 获取自身及数据库版本号
122 | global $myVersion;
123 | $version['echoip'] = $myVersion;
124 | $qqwry = new QQWry('qqwry.dat');
125 | $IPIP = new IPDB('ipipfree.ipdb');
126 | $version['qqwry'] = $qqwry->getVersion();
127 | $version['ipip'] = $IPIP->getVersion();
128 | return $version;
129 | }
130 |
131 | ?>
132 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Dnomd343 - IP
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
127 |
128 |
129 |
204 |
205 |
206 |
207 |
--------------------------------------------------------------------------------
/backend/qqwry.php:
--------------------------------------------------------------------------------
1 | fp = fopen($fileName, 'rb');
31 | $this->firstRecord = $this->read4byte();
32 | $this->lastRecord = $this->read4byte();
33 | $this->recordNum = ($this->lastRecord - $this->firstRecord) / 7; // 每条索引长度为7字节
34 | }
35 |
36 | public function __destruct() { // 析构函数
37 | if ($this->fp) {
38 | fclose($this->fp);
39 | }
40 | }
41 |
42 | private function read4byte() { // 读取4字节并转为long
43 | return unpack('Vlong', fread($this->fp, 4))['long'];
44 | }
45 |
46 | private function read3byte() { // 读取3字节并转为long
47 | return unpack('Vlong', fread($this->fp, 3) . chr(0))['long'];
48 | }
49 |
50 | private function readString() { // 读取字符串
51 | $str = '';
52 | $char = fread($this->fp, 1);
53 | while (ord($char) != 0) { // 读到二进制0结束
54 | $str .= $char;
55 | $char = fread($this->fp, 1);
56 | }
57 | return $str;
58 | }
59 |
60 | private function zipIP($ip) { // IP地址转为数字
61 | $ip_arr = explode('.', $ip);
62 | $tmp = (16777216 * intval($ip_arr[0])) + (65536 * intval($ip_arr[1])) + (256 * intval($ip_arr[2])) + intval($ip_arr[3]);
63 | return pack('N', intval($tmp)); // 32位无符号大端序长整型
64 | }
65 |
66 | private function unzipIP($ip) { // 数字转为IP地址
67 | return long2ip($ip);
68 | }
69 |
70 | public function getVersion() { // 获取当前数据库的版本
71 | fseek($this->fp, $this->lastRecord + 4);
72 | $tmp = $this->getRecord($this->read3byte())['B'];
73 | return substr($tmp, 0, 4) . substr($tmp, 7, 2) . substr($tmp, 12, 2);
74 | }
75 |
76 | public function getDetail($ip) { // 获取IP地址区段及所在位置
77 | if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { // 判断是否为IPv4地址
78 | return null;
79 | }
80 |
81 | fseek($this->fp, $this->searchRecord($ip)); // 跳转到对应IP记录的位置
82 | $detail['beginIP'] = $this->unzipIP($this->read4byte()); // 目标IP所在网段的起始IP
83 | $offset = $this->read3byte(); // 索引后3字节为对应记录的偏移量
84 | fseek($this->fp, $offset);
85 | $detail['endIP'] = $this->unzipIP($this->read4byte()); // 目标IP所在网段的结束IP
86 |
87 | $tmp = $this->getRecord($offset); // 获取记录的dataA与dataB
88 | $detail['dataA'] = $tmp['A'];
89 | $detail['dataB'] = $tmp['B'];
90 |
91 | if ($detail['beginIP'] == '255.255.255.0') { // 去除附加信息
92 | $detail['dataA'] = 'IANA';
93 | $detail['dataB'] = '保留地址';
94 | }
95 | if ($detail['dataA'] == ' CZ88.NET' || $detail['dataA'] == '纯真网络') {
96 | $detail['dataA'] = '';
97 | }
98 | if ($detail['dataB'] == ' CZ88.NET') {
99 | $detail['dataB'] = '';
100 | }
101 | $rawData = $this->formatData($detail['dataA'], $detail['dataB']);
102 | if ($rawData['dataA'] != '' && $rawData['dataB'] != '') {
103 | $detail['dataA'] = $rawData['dataA'];
104 | $detail['dataB'] = $rawData['dataB'];
105 | }
106 | $detail['country'] = $rawData['country'];
107 | $detail['region'] = $rawData['region'];
108 | $detail['city'] = $rawData['city'];
109 | $detail['domain'] = $rawData['domain'];
110 | $detail['isp'] = $rawData['isp'];
111 | return $detail;
112 | }
113 |
114 | private function searchRecord($ip) { // 根据IP地址获取索引的绝对偏移量
115 | $ip = $this->zipIP($ip); // 转为数字以比较大小
116 | $down = 0;
117 | $up = $this->recordNum;
118 | while ($down <= $up) { // 二分法查找
119 | $mid = floor(($down + $up) / 2); // 计算二分点
120 | fseek($this->fp, $this->firstRecord + $mid * 7);
121 | $beginip = strrev(fread($this->fp, 4)); // 获取二分区域的下边界
122 | if ($ip < $beginip) { // 目标IP在二分区域以下
123 | $up = $mid - 1; // 缩小搜索的上边界
124 | } else {
125 | fseek($this->fp, $this->read3byte());
126 | $endip = strrev(fread($this->fp, 4)); // 获取二分区域的上边界
127 | if ($ip > $endip) { // 目标IP在二分区域以上
128 | $down = $mid + 1; // 缩小搜索的下边界
129 | } else { // 目标IP在二分区域内
130 | return $this->firstRecord + $mid * 7; // 返回索引的偏移量
131 | }
132 | }
133 | }
134 | return $this->lastRecord; // 无法找到对应索引,返回最后一条记录的偏移量
135 | }
136 |
137 | private function getRecord($offset) { // 读取IP记录的数据
138 | fseek($this->fp, $offset + 4);
139 | $flag = ord(fread($this->fp, 1));
140 | if ($flag == 1) { // dataA与dataB均重定向
141 | $offset = $this->read3byte(); // 重定向偏移
142 | fseek($this->fp, $offset);
143 | if (ord(fread($this->fp, 1)) == 2) { // dataA再次重定向
144 | fseek($this->fp, $this->read3byte());
145 | $data['A'] = $this->readString();
146 | fseek($this->fp, $offset + 4);
147 | $data['B'] = $this->getDataB();
148 | } else { // dataA无重定向
149 | fseek($this->fp, -1, SEEK_CUR); // 文件指针回退1字节
150 | $data['A'] = $this->readString();
151 | $data['B'] = $this->getDataB();
152 | }
153 | } else if ($flag == 2) { // dataA重定向
154 | fseek($this->fp, $this->read3byte());
155 | $data['A'] = $this->readString();
156 | fseek($this->fp, $offset + 8); // IP占4字节, 重定向标志占1字节, dataA指针占3字节
157 | $data['B'] = $this->getDataB();
158 | } else { // dataA无重定向
159 | fseek($this->fp, -1, SEEK_CUR); // 文件指针回退1字节
160 | $data['A'] = $this->readString();
161 | $data['B'] = $this->getDataB();
162 | }
163 | $data['A'] = iconv("GBK", "UTF-8", $data['A']); // GBK -> UTF-8
164 | $data['B'] = iconv("GBK", "UTF-8", $data['B']);
165 | return $data;
166 | }
167 |
168 | private function getDataB() { // 从fp指定偏移获取dataB
169 | $flag = ord(fread($this->fp, 1));
170 | if ($flag == 0) { // dataB无信息
171 | return '';
172 | } else if ($flag == 1 || $flag == 2) { // dataB重定向
173 | fseek($this->fp, $this->read3byte());
174 | return $this->readString();
175 | } else { // dataB无重定向
176 | fseek($this->fp, -1, SEEK_CUR); // 文件指针回退1字节
177 | return $this->readString();
178 | }
179 | }
180 |
181 | private function formatData($dataA, $dataB) { // 从数据中提取国家、地区、城市、运营商等信息
182 | $str_json = file_get_contents('http://127.0.0.1:' . $this->formatPort . '/?dataA=' . urlencode($dataA) . '&dataB=' . urlencode($dataB));
183 | return json_decode($str_json, true); // 格式化为JSON
184 | }
185 | }
186 |
187 | ?>
188 |
--------------------------------------------------------------------------------
/assets/js/main.min.js:
--------------------------------------------------------------------------------
1 | var rangeSize=300,shareSize=61,shareX,shareY;function isPortrait(){return $(document).width()<$(document).height()||$(document).width()<800}function lockMap(){isPortrait()&&($(".positioncontrol").css("left","0"),$(".positioncontrol").css("right","0"))}function unlockMap(){if(isPortrait()){var margin=$("#output").css("margin-left");"0px"!==margin&&($(".positioncontrol").css("left",margin),$(".positioncontrol").css("right",margin))}}function checkRange(){var distanceX=Math.abs(event.pageX-shareX),distanceY=Math.abs(event.pageY-shareY);(distanceX>=rangeSize||distanceY>=rangeSize)&&hideQRCode()}function hideQRCode(){$("#qrcode").is(":hidden")||$("#share").hide(200)}function showQRCode(){var ip=$("#ip").text();if("ok"==checkIP(ip)){var pageUri="https://"+window.location.host+"/?ip="+ip;$("#qrcode").attr("href",pageUri),$("#qrcode").empty(),$("#qrcode").qrcode({width:100,height:100,text:pageUri});var shareLeft=event.pageX-shareSize,shareTop=event.pageY-shareSize;shareLeft+2*shareSize>$(document).width()&&(shareLeft=$(document).width()-2*shareSize),shareTop+2*shareSize>$(document).height()&&(shareTop=$(document).height()-2*shareSize),shareLeft<0&&(shareLeft=0),shareTop<0&&(shareTop=0),shareX=shareLeft+shareSize,shareY=shareTop+shareSize,$("#qrcode").is(":hidden")?($("#share").css("left",shareLeft),$("#share").css("top",shareTop),$("#share").show(200)):$("#share").hide(100,(function(){$("#share").css("left",shareLeft),$("#share").css("top",shareTop),$("#share").show(150)}))}}function getInfo(){$.get("/info/"+$("input").val(),(function(data){if(console.log(data),"F"!=data.status){if($("input").val()||$("input").val(data.ip),$("button").text("Search"),$("#ip").text(data.ip),data.city=null==data.city?"Unknow":data.city,data.region=null==data.region?"Unknow":data.region,data.country=null==data.country?"Unknow":data.country,data.timezone=null==data.timezone?"Unknow":data.timezone,data.isp=null==data.isp?"Unknow":data.isp,data.scope=null==data.scope?"Unknow":data.scope,data.detail=null==data.detail||" "==data.detail?"Unknow":data.detail,$("#city").text(data.city),$("#region").text(data.region),$("#country").text(data.country),$("#timezone").text(data.timezone),$("#isp").text(data.isp),$("#scope").text(data.scope),$("#detail").text(data.detail),null==data.as)$("#as").text("Unknow");else{$("#as").text(data.as);var asUri="https://bgpview.io/asn/"+data.as.substr(2);$("#as").html(''+data.as+"")}if(null==data.loc)$("#loc").text("Unknow"),clear();else{var earthUri="https://earth.google.com/web/@"+data.loc+",9.963a,7999.357d,35y,-34.3h,45t,0r/data=KAI";$("#loc").html(''+data.loc+""),draw(parseFloat(data.loc.split(",")[0]),parseFloat(data.loc.split(",")[1]))}$("#table").show(500),setTimeout((function(){unlockMap()}),3e3)}else errorIP()}))}function getVersion(){$.get("/version",(function(data){console.log(data),data.qqwry=data.qqwry.slice(0,4)+"-"+data.qqwry.slice(4,6)+"-"+data.qqwry.slice(6,8),data.ipip=data.ipip.slice(0,4)+"-"+data.ipip.slice(4,6)+"-"+data.ipip.slice(6,8);var data_ver="";data_ver+="echoIP: "+data.echoip+"\n",data_ver+="纯真数据库: "+data.qqwry+"\n",data_ver+="IPIP.net数据库: "+data.ipip,alert(data_ver)}))}function trim(str){return str.replace(/(^\s*)|(\s*$)/g,"")}function errorIP(){$("button").text("Illegal IP"),$("button").css({"border-color":"#ff406f","background-color":"#ff406f"}),$("input").focus()}function checkIP(ipStr){if(null===ipStr)return"error";var regIPv4=/^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/,regIPv6=/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:)|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}(:[0-9A-Fa-f]{1,4}){1,2})|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){1,3})|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){1,4})|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){1,5})|([0-9A-Fa-f]{1,4}:(:[0-9A-Fa-f]{1,4}){1,6})|(:(:[0-9A-Fa-f]{1,4}){1,7})|(([0-9A-Fa-f]{1,4}:){6}(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(([0-9A-Fa-f]{1,4}:){5}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|([0-9A-Fa-f]{1,4}:(:[0-9A-Fa-f]{1,4}){0,4}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(:(:[0-9A-Fa-f]{1,4}){0,5}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}))$/,V4=ipStr.match(regIPv4),V6=ipStr.match(regIPv6);return null===V4&&null===V6?"error":"ok"}function getQuery(name){var reg=new RegExp("(^|&)"+name+"=([^&]*)(&|$)"),result=window.location.search.substr(1).match(reg);return null!=result?unescape(result[2]):null}function clear(){var map;new mapboxgl.Map({container:"map",style:"mapbox://styles/mapbox/streets-v11",center:[0,0],zoom:1}).on("load",(function(){console.log("reset map")}))}function draw(x,y){var size=100,map=new mapboxgl.Map({container:"map",style:"mapbox://styles/mapbox/streets-v11",center:[y,x],zoom:3}),pulsingDot={width:100,height:100,data:new Uint8Array(4e4),onAdd:function(){var canvas=document.createElement("canvas");canvas.width=this.width,canvas.height=this.height,this.context=canvas.getContext("2d")},render:function(){var duration=1e3,t=performance.now()%1e3/1e3,radius=15,outerRadius=35*t+15,context=this.context;return context.clearRect(0,0,this.width,this.height),context.beginPath(),context.arc(this.width/2,this.height/2,outerRadius,0,2*Math.PI),context.fillStyle="rgba(255, 200, 200,"+(1-t)+")",context.fill(),context.beginPath(),context.arc(this.width/2,this.height/2,15,0,2*Math.PI),context.fillStyle="rgba(255, 100, 100, 1)",context.strokeStyle="white",context.lineWidth=2+4*(1-t),context.fill(),context.stroke(),this.data=context.getImageData(0,0,this.width,this.height).data,map.triggerRepaint(),!0}};map.on("load",(function(){map.addImage("pulsing-dot",pulsingDot,{pixelRatio:2}),map.addLayer({id:"points",type:"symbol",source:{type:"geojson",data:{type:"FeatureCollection",features:[{type:"Feature",geometry:{type:"Point",coordinates:[y,x]}}]}},layout:{"icon-image":"pulsing-dot"}})}))}mapboxgl.accessToken="pk.eyJ1Ijoic2hldm9ua3VhbiIsImEiOiJja20yMjlnNDYybGg2Mm5zNW40eTNnNnUwIn0.6xj6sgjWvdQgT_7OQUy_Jg",$(document).ready((function(){lockMap(),null===getQuery("ip")?getInfo():($("input").val(getQuery("ip")),getInfo()),$("#table").hide(),$("#share").hide(),$("button").click((function(){$(this).css({"border-color":"","background-color":""}),$(this).text("Searching..."),lockMap(),$("#table").hide(500),$("input").val(trim($("input").val())),""==$("input").val()||"ok"==checkIP($("input").val())?getInfo():errorIP()})),$(".table tr:first td:first").dblclick((function(event){event.stopPropagation(),getVersion()})),$("input").dblclick((function(event){event.stopPropagation()})),$("button").dblclick((function(event){event.stopPropagation()})),$("table").dblclick((function(event){event.stopPropagation()})),$("#output").dblclick((function(){showQRCode()})),$("#output").click((function(){hideQRCode()})),$("#map").click((function(){hideQRCode()})),$("body").mousemove((function(){checkRange()})),$("body").mouseleave((function(){hideQRCode()})),$(window).resize((function(){isPortrait()?($(".positioncontrol").css("left","0"),$(".positioncontrol").css("right","0"),$(".positioncontrol").css("transform","translate(0, 0)")):($(".positioncontrol").css("left","calc(80vw - 180px)"),$(".positioncontrol").css("right",""),$(".positioncontrol").css("transform","translate(-20%, 0)"))}))})),$(document).keydown((function(event){13==event.keyCode&&$("button").focus()}));
--------------------------------------------------------------------------------
/assets/error/style.css:
--------------------------------------------------------------------------------
1 | html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}*{box-sizing:border-box}:after,:before{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:clarendon-text-pro,Georgia,"Times New Roman",Times,serif;font-size:16px;line-height:1.5;color:#454e57;background-color:#fff}a{text-decoration:none}.h2,h2{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h2 .small,.h2 small,h2 .small,h2 small{font-weight:400;line-height:1;color:#86929e}.h2,h2{margin-top:24px;margin-bottom:12px}.h2 .small,.h2 small,h2 .small,h2 small{font-size:65%}.h2,h2{font-size:34px}p{margin:0 0 12px}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}body{transition:.75s;transition-property:opacity,transform}#loading{height:100%;overflow:hidden}#loading body{opacity:0}#loading .homepage{-ms-transform:translateY(-50px);transform:translateY(-50px)}.not-found{background:#d5dde1}.not-found .row{margin-top:25%}.not-found h2{font-weight:700}.not-found .ghost{width:105px;float:right;margin:0 30px}.not-found .ghost .body{display:block;height:120px;padding-top:35px;background:#fff;position:relative;border-radius:70px 70px 0 0;text-align:center;animation:ghost-float 3s alternate infinite ease-in-out}.not-found .ghost .body i{color:#454e57;font-size:40px;display:inline-block;animation:ghost-blink 4s infinite ease;position:relative;transition:all .1s ease}.not-found .ghost .body i.left{margin-left:-25px}.not-found .ghost .body i.right{margin-right:-25px}.not-found .ghost .body i.bottom{margin-top:15px}.not-found .ghost .body i.top{margin-top:-25px}.not-found .ghost .body i.top.left,.not-found .ghost .body i.top.right{margin-top:-15px}.not-found .ghost .body:after{content:" ";display:block;width:100%;height:10px;position:absolute;bottom:-9px;background-position:center top;background-repeat:repeat-x;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0nMTcnIGhlaWdodD0nMTcnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgIHZpZXdCb3g9JzAgMCAxMDAgMTAwJz4KCTxsaW5lYXJHcmFkaWVudCBpZD0nZzEnIHgxPScwJScgeTE9JzAlJyB4Mj0nMTAwJScgeTI9JzAlJz4KCQk8c3RvcCBvZmZzZXQ9JzAlJyBzdHlsZT0nc3RvcC1jb2xvcjojOTk5OTk5O3N0b3Atb3BhY2l0eToxJyAvPgoJCTxzdG9wIG9mZnNldD0nMTAwJScgc3R5bGU9J3N0b3AtY29sb3I6I2ZiZGE1OTtzdG9wLW9wYWNpdHk6MCcgLz4KCTwvbGluZWFyR3JhZGllbnQ+Cgk8bGluZWFyR3JhZGllbnQgaWQ9J2cyJyB4MT0nMCUnIHkxPScwJScgeDI9JzAlJyB5Mj0nMTAwJSc+CgkJPHN0b3Agb2Zmc2V0PScwJScgc3R5bGU9J3N0b3AtY29sb3I6Izk5OTk5OTtzdG9wLW9wYWNpdHk6MScgLz4KCQk8c3RvcCBvZmZzZXQ9JzEwMCUnIHN0eWxlPSdzdG9wLWNvbG9yOiNmYmRhNTk7c3RvcC1vcGFjaXR5OjAnIC8+Cgk8L2xpbmVhckdyYWRpZW50PgoJPHJhZGlhbEdyYWRpZW50IGlkPSdnMycgY3g9JzAlJyBjeT0nMCUnIHI9JzEwMCUnPgoJCTxzdG9wIG9mZnNldD0nMCUnIHN0eWxlPSdzdG9wLWNvbG9yOiM5OTk5OTk7c3RvcC1vcGFjaXR5OjEnIC8+CgkJPHN0b3Agb2Zmc2V0PScxMDAlJyBzdHlsZT0nc3RvcC1jb2xvcjojZmJkYTU5O3N0b3Atb3BhY2l0eTowJyAvPgoJPC9yYWRpYWxHcmFkaWVudD4KCTxnIHRyYW5zZm9ybT0ncm90YXRlKDQ1KSB0cmFuc2xhdGUoMjEuMiAtNDkuNSknPgoJCTxyZWN0IHg9Jy01MCUnIHk9Jy01MCUnIHdpZHRoPScxMDAlJyBoZWlnaHQ9JzEwMCUnIGZpbGw9JyNmZmZmZmYnIC8+CgkJPHJlY3QgeD0nNTAlJyB5PSctNTAlJyB3aWR0aD0nMCUnIGhlaWdodD0nMTAwJScgZmlsbD0ndXJsKCNnMSknIC8+CgkJPHJlY3QgeD0nLTUwJScgeT0nNTAlJyB3aWR0aD0nMTAwJScgaGVpZ2h0PScwJScgZmlsbD0ndXJsKCNnMiknIC8+CgkJPHJlY3QgeD0nNTAlJyB5PSc1MCUnIHdpZHRoPScwJScgaGVpZ2h0PScwJScgZmlsbD0ndXJsKCNnMyknIC8+Cgk8L2c+Cjwvc3ZnPg==)}.not-found .ghost:after{content:" ";display:block;height:15px;border-radius:50%;margin-top:20px;background:rgba(0,0,0,.1);animation:shadow-fade 3s alternate infinite ease-in-out}.not-found p{color:#64707d}@media (max-width:768px){.not-found{text-align:center}.not-found .ghost{float:none;margin:0 auto}}@keyframes ghost-float{from{transform:translateY(0)}to{transform:translateY(-15px)}}@keyframes shadow-fade{from{opacity:1}to{opacity:.3}}@keyframes ghost-blink{0%{transform:scale(1,0)}5%{transform:scale(1,1)}100%{transform:scale(1,1)}}@media (max-width:767px){footer{text-align:center}footer .social{float:none;margin-bottom:50px}footer .social li{margin:0 10px}}@font-face{font-family:icons;src:url(./icons.woff) format("woff");font-weight:400;font-style:normal}.icon{display:inline-block;vertical-align:middle;line-height:1;font-weight:400;font-style:normal;text-decoration:inherit;text-transform:none;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon:before{font-family:icons}.icon-flickr:before{content:"\f107"}
--------------------------------------------------------------------------------
/backend/specialIP.php:
--------------------------------------------------------------------------------
1 | '0.0.0.0/8', // 0.0.0.0 - 0.255.255.255
8 | 'desc' => 'Self Identification'
9 | ],
10 | [
11 | 'scope' => '10.0.0.0/8', // 10.0.0.0 - 10.255.255.255
12 | 'desc' => 'Private Use Networks'
13 | ],
14 | [
15 | 'scope' => '100.64.0.0/10', // 100.64.0.0 - 100.127.255.255
16 | 'desc' => 'Shared Address Space'
17 | ],
18 | [
19 | 'scope' => '127.0.0.0/8', // 127.0.0.0 - 127.255.255.255
20 | 'desc' => 'Loopback'
21 | ],
22 | [
23 | 'scope' => '169.254.0.0/16', // 169.254.0.0 - 169.254.255.255
24 | 'desc' => 'Link Local'
25 | ],
26 | [
27 | 'scope' => '172.16.0.0/12', // 172.16.0.0 - 172.31.255.255
28 | 'desc' => 'Private Use Networks'
29 | ],
30 | [
31 | 'scope' => '192.0.0.0/29', // 192.0.0.0 - 192.0.0.7
32 | 'desc' => 'IPv4 Service Continuity Prefix'
33 | ],
34 | [
35 | 'scope' => '192.0.0.8/32', // 192.0.0.8
36 | 'desc' => 'IPv4 dummy address'
37 | ],
38 | [
39 | 'scope' => '192.0.0.9/32', // 192.0.0.9
40 | 'desc' => 'Port Control Protocol Anycast'
41 | ],
42 | [
43 | 'scope' => '192.0.0.10/32', // 192.0.0.10
44 | 'desc' => 'Traversal Using Relays around NAT Anycast'
45 | ],
46 | [
47 | 'scope' => '192.0.0.170/32', // 192.0.0.170
48 | 'desc' => 'NAT64/DNS64 Discovery'
49 | ],
50 | [
51 | 'scope' => '192.0.0.171/32', // 192.0.0.171
52 | 'desc' => 'NAT64/DNS64 Discovery'
53 | ],
54 | [
55 | 'scope' => '192.0.0.0/24', // 192.0.0.0 - 192.0.0.255
56 | 'desc' => 'IETF Protocol Assignments'
57 | ],
58 | [
59 | 'scope' => '192.0.2.0/24', // 192.0.2.0 - 192.0.2.255
60 | 'desc' => 'TEST-NET-1'
61 | ],
62 | // [
63 | // 'scope' => '192.31.196.0/24', // 192.31.196.0 - 192.31.196.255
64 | // 'desc' => 'AS112-v4'
65 | // ],
66 | [
67 | 'scope' => '192.52.193.0/24', // 192.52.193.0 - 192.52.193.255
68 | 'desc' => 'AMT'
69 | ],
70 | [
71 | 'scope' => '192.88.99.0/24', // 192.88.99.0 - 192.88.99.255
72 | 'desc' => 'Deprecated (6to4 Relay Anycast)'
73 | ],
74 | [
75 | 'scope' => '192.168.0.0/16', // 192.168.0.0 - 192.168.255.255
76 | 'desc' => 'Private Use Networks'
77 | ],
78 | // [
79 | // 'scope' => '192.175.48.0/24', // 192.175.48.0 - 192.175.48.255
80 | // 'desc' => 'Direct Delegation AS112 Service'
81 | // ],
82 | [
83 | 'scope' => '198.18.0.0/15', // 198.18.0.0 - 198.19.255.255
84 | 'desc' => 'Benchmarking'
85 | ],
86 | [
87 | 'scope' => '198.51.100.0/24', // 198.51.100.0 - 198.51.100.255
88 | 'desc' => 'TEST-NET-2'
89 | ],
90 | [
91 | 'scope' => '203.0.113.0/24', // 203.0.113.0 - 203.0.113.255
92 | 'desc' => 'TEST-NET-3'
93 | ],
94 | [
95 | 'scope' => '224.0.0.0/4', // 224.0.0.0 - 239.255.255.255
96 | 'desc' => 'IPv4 Class D for Multicasting'
97 | ],
98 | [
99 | 'scope' => '255.255.255.255/32', // 255.255.255.255
100 | 'desc' => 'Limited Broadcast'
101 | ],
102 | [
103 | 'scope' => '240.0.0.0/4', // 240.0.0.0 - 255.255.255.255
104 | 'desc' => 'IPv4 Class E Reserved'
105 | ]
106 | );
107 |
108 | // data from: https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
109 |
110 | $specialIPv6 = array(
111 | [
112 | 'scope' => '::1/128',
113 | 'range' => '::1 - ::1',
114 | 'descEn' => 'Loopback Address',
115 | 'descCn' => '环回地址'
116 | ],
117 | [
118 | 'scope' => '::/128',
119 | 'range' => ':: - ::',
120 | 'descEn' => 'Unspecified Address',
121 | 'descCn' => '未指定地址'
122 | ],
123 | [
124 | 'scope' => '::ffff:0:0/96',
125 | 'range' => '::ffff:0:0 - ::ffff:ffff:ffff',
126 | 'descEn' => 'IPv4-mapped Address',
127 | 'descCn' => 'IPv4映射地址'
128 | ],
129 | [
130 | 'scope' => '64:ff9b::/96',
131 | 'range' => '64:ff9b:: - 64:ff9b::ffff:ffff',
132 | 'descEn' => 'IPv4-IPv6 Translat',
133 | 'descCn' => 'IPv4转IPv6地址'
134 | ],
135 | [
136 | 'scope' => '64:ff9b:1::/48',
137 | 'range' => '64:ff9b:1:: - 64:ff9b:1:ffff:ffff:ffff:ffff:ffff',
138 | 'descEn' => 'IPv4-IPv6 Translat',
139 | 'descCn' => 'IPv4转IPv6地址'
140 | ],
141 | [
142 | 'scope' => '100::/64',
143 | 'range' => '100:: - 100::ffff:ffff:ffff:ffff',
144 | 'descEn' => 'Discard-Only Address Block',
145 | 'descCn' => '仅丢弃块地址'
146 | ],
147 | [
148 | 'scope' => '2001:1::1/128',
149 | 'range' => '2001:1::1 - 2001:1::1',
150 | 'descEn' => 'Port Control Protocol Anycast',
151 | 'descCn' => '端口控制协议任播地址'
152 | ],
153 | [
154 | 'scope' => '2001:1::2/128',
155 | 'range' => '2001:1::2 - 2001:1::2',
156 | 'descEn' => 'Traversal Using Relays around NAT Anycast',
157 | 'descCn' => '中继遍历NAT任播地址'
158 | ],
159 | [
160 | 'scope' => '2001:2::/48',
161 | 'range' => '2001:2:: - 2001:2::ffff:ffff:ffff:ffff:ffff',
162 | 'descEn' => 'Benchmarking',
163 | 'descCn' => '基准测试地址'
164 | ],
165 | [
166 | 'scope' => '2001:3::/32',
167 | 'range' => '2001:3:: - 2001:3:ffff:ffff:ffff:ffff:ffff:ffff',
168 | 'descEn' => 'AMT',
169 | 'descCn' => 'AMT地址'
170 | ],
171 | // [
172 | // 'scope' => '2001:4:112::/48',
173 | // 'range' => '2001:4:112:: - 2001:4:112:ffff:ffff:ffff:ffff:ffff',
174 | // 'descEn' => 'AS112-v6',
175 | // 'descCn' => ''
176 | // ],
177 | [
178 | 'scope' => '2001:10::/28',
179 | 'range' => '2001:10:: - 2001:1f:ffff:ffff:ffff:ffff:ffff:ffff',
180 | 'descEn' => 'Deprecated (previously ORCHID)',
181 | 'descCn' => 'ORCHID地址(已弃用)'
182 | ],
183 | [
184 | 'scope' => '2001:20::/28',
185 | 'range' => '2001:20:: - 2001:2f:ffff:ffff:ffff:ffff:ffff:ffff',
186 | 'descEn' => 'ORCHIDv2',
187 | 'descCn' => 'ORCHIDv2地址'
188 | ],
189 | [
190 | 'scope' => '2001::/23',
191 | 'range' => '2001:: - 2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff',
192 | 'descEn' => 'IETF Protocol Assignments',
193 | 'descCn' => 'IETF协议分配地址'
194 | ],
195 | [
196 | 'scope' => '2001:db8::/32',
197 | 'range' => '2001:db8:: - 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff',
198 | 'descEn' => 'Documentation',
199 | 'descCn' => '文档地址'
200 | ],
201 | [
202 | 'scope' => '2001::/32',
203 | 'range' => '2001:: - 2001::ffff:ffff:ffff:ffff:ffff:ffff',
204 | 'descEn' => 'TEREDO',
205 | 'descCn' => 'TEREDO地址'
206 | ],
207 | [
208 | 'scope' => '2002::/16',
209 | 'range' => '2002:: - 2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
210 | 'descEn' => 'IPv6 to IPv4',
211 | 'descCn' => 'IPv6转IPv4地址'
212 | ],
213 | // [
214 | // 'scope' => '2620:4f:8000::/48',
215 | // 'range' => '2620:4f:8000:: - 2620:4f:8000:ffff:ffff:ffff:ffff:ffff',
216 | // 'descEn' => 'Direct Delegation AS112 Service',
217 | // 'descCn' => ''
218 | // ],
219 | [
220 | 'scope' => 'fc00::/7',
221 | 'range' => 'fc00:: - fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
222 | 'descEn' => 'Unique-Local',
223 | 'descCn' => '唯一本地地址'
224 | ],
225 | [
226 | 'scope' => 'fe80::/10',
227 | 'range' => 'fe80:: - febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
228 | 'descEn' => 'Link-Local Unicast',
229 | 'descCn' => '链路本地地址'
230 | ],
231 | );
232 |
233 | function cidrToRange($cidr) { // CIDR转IP段
234 | $cidr = explode('/', $cidr);
235 | $range['start'] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))));
236 | $range['end'] = long2ip((ip2long($range['start'])) + pow(2, (32 - (int)$cidr[1])) - 1);
237 | return $range;
238 | }
239 |
240 | function ip2long6($ipv6) { // 压缩IPv6地址为long
241 | $ip_n = inet_pton($ipv6);
242 | $bits = 15;
243 | while ($bits >= 0) {
244 | $bin = sprintf("%08b", (ord($ip_n[$bits])));
245 | $ipv6long = $bin.$ipv6long;
246 | $bits--;
247 | }
248 | return gmp_strval(gmp_init($ipv6long, 2), 10);
249 | }
250 |
251 | function long2ip6($ipv6long) { // 解压long为IPv6地址
252 | $bin = gmp_strval(gmp_init($ipv6long, 10), 2);
253 | if (strlen($bin) < 128) {
254 | $pad = 128 - strlen($bin);
255 | for ($i = 1; $i <= $pad; $i++) {
256 | $bin = '0' . $bin;
257 | }
258 | }
259 | $bits = 0;
260 | while ($bits <= 7) {
261 | $bin_part = substr($bin, ($bits * 16), 16);
262 | $ipv6 .= dechex(bindec($bin_part)) . ':';
263 | $bits++;
264 | }
265 | return inet_ntop(inet_pton(substr($ipv6, 0, -1)));
266 | }
267 |
268 | function checkSpecialIPv4($ip) { // 检查特殊IPv4地址
269 | global $specialIPv4;
270 | $ipv4 = ip2long($ip);
271 | foreach ($specialIPv4 as $special) {
272 | $range = cidrToRange($special['scope']);
273 | if ($ipv4 >= ip2long($range['start']) && $ipv4 <= ip2long($range['end'])) {
274 | $detail = (new QQWry)->getDetail($ip);
275 | return array(
276 | 'scope' => $special['scope'],
277 | 'descEn' => $special['desc'],
278 | 'descCn' => $detail['dataA'] . $detail['dataB']
279 | );
280 | }
281 | }
282 | return null; // 非特殊地址
283 | }
284 |
285 | function checkSpecialIPv6($ip) { // 检查特殊IPv6地址
286 | global $specialIPv6;
287 | $ipv6 = ip2long6($ip);
288 | foreach ($specialIPv6 as $special) {
289 | $range = explode(' - ', $special['range']);
290 | if ($ipv6 >= ip2long6($range[0]) && $ipv6 <= ip2long6($range[1])) {
291 | unset($special['range']);
292 | return $special;
293 | }
294 | }
295 | return null; // 非特殊地址
296 | }
297 |
298 | function checkSpecial($ip) { // 检查特殊IP地址并返回说明
299 | if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { // IPv4
300 | return checkSpecialIPv4($ip);
301 | } else {
302 | return checkSpecialIPv6($ip);
303 | }
304 | }
305 |
306 | ?>
--------------------------------------------------------------------------------
/backend/queryInfo.php:
--------------------------------------------------------------------------------
1 | getModuleCount(); $y++) {
28 | for ($x = 0; $x < $qr->getModuleCount(); $x++) {
29 | $qrString .= ($qr->isDark($y, $x) ? $block : ' ');
30 | }
31 | $qrString .= PHP_EOL;
32 | }
33 | return $qrString;
34 | }
35 |
36 | function getQrCodeUtf($str) { // 用特殊Unicode编码绘制二维码
37 | $qr = QRCode::getMinimumQRCode($str, QR_ERROR_CORRECT_LEVEL_L);
38 | $length = $qr->getModuleCount();
39 | for ($y = 0; $y < $length; $y++) {
40 | for ($x = 0; $x < $length; $x++) {
41 | $table[$y][$x] = $qr->isDark($y, $x);
42 | }
43 | if ($length % 2) {
44 | $table[$y][$length] = false; // 宽度扩充为偶数
45 | }
46 | }
47 | if ($length % 2) { // 若二维码边长为奇数
48 | for ($i = 0; $i <= $length; $i++) {
49 | $table[$length][$i] = false; // 高度扩充为偶数
50 | }
51 | $length++;
52 | }
53 | for ($y = 0; $y < $length; $y += 2) { // 每次输出两行
54 | for ($x = 0; $x < $length; $x++) {
55 | if ($table[$y][$x] && $table[$y + 1][$x]) { // 分四种情况输出上下两格
56 | echo '█';
57 | } else if ($table[$y][$x] && !$table[$y + 1][$x]) {
58 | echo '▀';
59 | } else if (!$table[$y][$x] && $table[$y + 1][$x]) {
60 | echo '▄';
61 | } else {
62 | echo ' ';
63 | }
64 | }
65 | echo PHP_EOL;
66 | }
67 | }
68 |
69 | function preRount() { // 解析请求路径
70 | global $request;
71 | $requestUri = $_SERVER['DOCUMENT_URI']; // 获取不带参数的请求路径
72 | if ($_GET['cli'] == 'true') { // 识别nginx附带的cli参数
73 | $request['cli'] = true;
74 | }
75 | if ($requestUri == '/' || $requestUri == '/ip') { // URI -> / or /ip
76 | $request['justip'] = true;
77 | return;
78 | } else if ($requestUri == '/help') { // URI -> /help
79 | $request['help'] = true;
80 | return;
81 | } else if ($requestUri == '/version') { // URI -> /version
82 | $request['version'] = true;
83 | return;
84 | } else if ($requestUri == '/qr') { // URI -> /qr
85 | $request['qr'] = true;
86 | return;
87 | } else if ($requestUri == '/qr/') { // URI -> /qr/
88 | $request['qr'] = true;
89 | $request['qr_fill'] = '██';
90 | return;
91 | } else if ($requestUri == '/info' || $requestUri == '/info/') { // URI -> /info or /info/
92 | $request['ip'] = getClientIP();
93 | return;
94 | } else if ($requestUri == '/info/gbk') { // URI -> /info/gbk
95 | $request['ip'] = getClientIP();
96 | $request['gbk'] = true;
97 | return;
98 | } else if ($requestUri == '/query') { // URI -> /query?xxx=xxx
99 | if ($_GET['error'] == 'true') { $request['error'] = true; }
100 | if ($_GET['version'] == 'true') { $request['version'] = true; }
101 | if ($_GET['help'] == 'true') { $request['help'] = true; }
102 | if ($_GET['gbk'] == 'true') { $request['gbk'] = true; }
103 | if ($_GET['qr'] == 'true') { $request['qr'] = true; }
104 | if ($_GET['justip'] == 'true') { $request['justip'] = true; }
105 | if (isset($_GET['ip'])) { $request['ip'] = $_GET['ip']; }
106 | return;
107 | }
108 | preg_match('#^/qr/([^/]{2})$#', $requestUri, $match); // URI -> /qr/{qr_fill}
109 | if (count($match) > 0) {
110 | $request['qr'] = true;
111 | $request['qr_fill'] = $match[1];
112 | return;
113 | }
114 | preg_match('#^/([^/]+?)$#', $requestUri, $match); // URI -> /{ip}
115 | if (count($match) > 0) {
116 | if ($request['cli']) { // 命令行模式
117 | $request['ip'] = $match[1];
118 | } else {
119 | $request['error'] = true;
120 | }
121 | return;
122 | }
123 | preg_match('#^/([^/]+?)/gbk$#', $requestUri, $match); // URI -> /{ip}/gbk
124 | if (count($match) > 0) {
125 | $request['ip'] = $match[1];
126 | $request['gbk'] = true;
127 | return;
128 | }
129 | preg_match('#^/info/([^/]+?)$#', $requestUri, $match); // URI -> /info/{ip}
130 | if (count($match) > 0) {
131 | $request['ip'] = $match[1];
132 | return;
133 | }
134 | preg_match('#^/info/([^/]+?)/gbk$#', $requestUri, $match); // URI -> /info/{ip}/gbk
135 | if (count($match) > 0) {
136 | $request['ip'] = $match[1];
137 | $request['gbk'] = true;
138 | return;
139 | }
140 | $request['error'] = true; // 未匹配到请求路径
141 | }
142 |
143 | function getInfo($ip) { // 获取并格式化IP数据
144 | global $request;
145 | global $redisSetting;
146 | if ($redisSetting['enable']) { // 启用Redis缓存
147 | $info = getRedisData($ip); // 查询缓存数据
148 | if ($info == NULL) { // 缓存未命中
149 | $info = getIPInfo($ip); // 发起查询
150 | setRedisData($ip, json_encode($info)); // 缓存数据
151 | } else { // 缓存命中
152 | $info = json_decode($info, true); // 使用缓存数据
153 | }
154 | } else { // 未启用Redis缓存
155 | $info = getIPInfo($ip);
156 | }
157 | if ($request['cli']) { // 使用命令行模式
158 | $cli = "IP: " . $info['ip'] . PHP_EOL;
159 | if ($info['as'] != NULL) { $cli .= "AS: " . $info['as'] . PHP_EOL; }
160 | if ($info['city'] != NULL) { $cli .= "City: " . $info['city'] . PHP_EOL; }
161 | if ($info['region'] != NULL) { $cli .= "Region: " . $info['region'] . PHP_EOL; }
162 | if ($info['country'] != NULL) { $cli .= "Country: " . $info['country'] . PHP_EOL; }
163 | if ($info['timezone'] != NULL) { $cli .= "Timezone: " . $info['timezone'] . PHP_EOL; }
164 | if ($info['loc'] != NULL) { $cli .= "Location: " . $info['loc'] . PHP_EOL; }
165 | if ($info['isp'] != NULL) { $cli .= "ISP: " . $info['isp'] . PHP_EOL; }
166 | if ($info['scope'] != NULL) { $cli .= "Scope: " . $info['scope'] . PHP_EOL; }
167 | if ($info['detail'] != NULL) { $cli .= "Detail: " . $info['detail'] . PHP_EOL; }
168 | return $cli;
169 | }
170 | $info['status'] = 'T';
171 | header('Content-Type: application/json; charset=utf-8'); // 以JSON格式发送
172 | return json_encode($info);
173 | }
174 |
175 | function routeParam() {
176 | // error -> 请求出错
177 | // version -> 获取版本数据
178 | // help -> 显示帮助信息
179 | // cli -> 来自命令行下的请求
180 | // gbk -> 返回数据使用GBK编码
181 | // qr -> 生成二维码
182 | // qr_fill -> 二维码填充符号
183 | // justip -> 仅查询IP地址
184 | // ip -> 请求指定IP的数据
185 |
186 | global $request;
187 | global $webUri;
188 | global $helpContent;
189 | if ($request['error']) { // 请求出错
190 | if ($request['cli']) { // 命令行模式
191 | echo 'Illegal Request' . PHP_EOL;
192 | } else {
193 | errorPage();
194 | }
195 | exit; // 退出
196 | }
197 |
198 | if ($request['help']) { // 显示帮助信息
199 | if ($request['cli']) {
200 | echo $helpContent;
201 | } else {
202 | errorPage(); // 网页模式不输出
203 | }
204 | exit;
205 | }
206 |
207 | if ($request['version']) { // 请求版本信息
208 | $version = getVersion();
209 | if ($request['cli']) { // 命令行模式
210 | echo "echoip -> " . $version['echoip'] . PHP_EOL;
211 | echo "qqwry.dat -> " . formatDate($version['qqwry']) . PHP_EOL;
212 | echo "ipip.net -> " . formatDate($version['ipip']) . PHP_EOL;
213 | } else {
214 | header('Content-Type: application/json; charset=utf-8');
215 | echo json_encode($version); // 返回JSON数据
216 | }
217 | exit; // 退出
218 | }
219 |
220 | if ($request['qr']) { // 生成二维码
221 | if ($request['cli']) {
222 | echo $webUri . '?ip=' . getClientIP() . PHP_EOL;
223 | if (isset($request['qr_fill'])) { // 使用字符填充生成二维码
224 | echo getQrCode($webUri . '?ip=' . getClientIP(), $request['qr_fill']);
225 | } else { // 使用特殊Unicode字符生成二维码
226 | echo getQrCodeUtf($webUri . '?ip=' . getClientIP());
227 | }
228 | } else {
229 | errorPage(); // 网页模式不输出
230 | }
231 | exit;
232 | }
233 |
234 | if ($request['justip']) { // 仅查询IP地址
235 | if ($request['cli']) { // 命令行模式
236 | echo getClientIP() . PHP_EOL;
237 | } else {
238 | header('Content-Type: application/json; charset=utf-8');
239 | echo '{"ip":"' . getClientIP() . '"}'; // 返回JSON数据
240 | }
241 | exit;
242 | }
243 |
244 | $ip = isset($request['ip']) ? $request['ip'] : getClientIP(); // 若存在请求信息则查询该IP
245 | if (!filter_var($ip, \FILTER_VALIDATE_IP)) { // 输入IP不合法
246 | if ($request['cli']) { // 命令行模式
247 | echo "Illegal Request" . PHP_EOL;
248 | } else {
249 | $reply = array(
250 | 'status' => 'F',
251 | 'message' => 'Illegal Request'
252 | );
253 | header('Content-Type: application/json; charset=utf-8');
254 | echo json_encode($reply);
255 | }
256 | exit;
257 | }
258 | $info = getInfo($ip); // 查询目标IP
259 | if ($request['gbk']) {
260 | $info = iconv('UTF-8', 'gbk', $info); // 输出为GBK编码
261 | }
262 | echo $info;
263 | }
264 |
265 | function main() {
266 | preRount(); // 解析请求路径
267 | routeParam(); // 处理请求参数
268 | }
269 |
270 | $myVersion = 'v1.4';
271 |
272 | $request = array(
273 | 'error' => false,
274 | 'version' => false,
275 | 'help' => false,
276 | 'cli' => false,
277 | 'gbk' => false,
278 | 'qr' => false,
279 | 'justip' => false
280 | );
281 |
282 | $webSite = 'ip.343.re'; // 默认域名
283 | if (isset($_SERVER['HTTP_HOST'])) {
284 | preg_match('#^127.0.0.1#', $_SERVER['HTTP_HOST'], $match); // 排除127.0.0.1下的host
285 | if (count($match) == 0) {
286 | $webSite = $_SERVER['HTTP_HOST'];
287 | }
288 | }
289 | $webUri = 'http://' . $webSite . '/';
290 |
291 | $helpContent = PHP_EOL . 'echoIP - ' . $myVersion . ' (https://github.com/dnomd343/echoIP)' . PHP_EOL . '
292 | Format: http(s)://' . $webSite . '{Request_URI}
293 |
294 | / or /ip -> Show client IP.
295 |
296 | /info or /info/ -> Show detail of client IP.
297 | /{ip} or /info/{ip} -> Show detail of {ip}.
298 |
299 | /info/gbk -> Show detail of client IP (use GBK encoding).
300 | /{ip}/gbk or /info/{ip}/gbk -> Show detail of {ip} (use GBK encoding).
301 |
302 | /qr -> Show QR code of client IP (use special unicode characters).
303 | /qr/ -> Show QR code of client IP (use full characters).
304 | /qr/{xx} -> Show QR code of client IP (Use two custom characters).
305 |
306 | /help -> Show help message.
307 | /ua -> Show http user-agent of client.
308 | /version -> Show version of echoIP and IP database.
309 |
310 | /query?xxx=xxx&xxx=xxx
311 | |-> error=true: Show error request.
312 | |-> version=true: Show version of echoIP and IP database.
313 | |-> help=true: Show help message.
314 | |-> gbk=true: Use GBK encoding.
315 | |-> qr=true: Show QR code of client IP.
316 | |-> justip=true: Only query the client IP.
317 | |-> ip={ip}: Query of specified IP.
318 |
319 | ';
320 |
321 | main();
322 |
323 | ?>
--------------------------------------------------------------------------------
/assets/js/main.js:
--------------------------------------------------------------------------------
1 | var rangeSize = 300;
2 | var shareSize = 61;
3 | var shareX, shareY;
4 | mapboxgl.accessToken = 'pk.eyJ1Ijoic2hldm9ua3VhbiIsImEiOiJja20yMjlnNDYybGg2Mm5zNW40eTNnNnUwIn0.6xj6sgjWvdQgT_7OQUy_Jg';
5 |
6 | $(document).ready(function() {
7 | lockMap();
8 | if (getQuery("ip") === null) {
9 | getInfo();
10 | } else {
11 | $("input").val(getQuery("ip"));
12 | getInfo();
13 | }
14 | $("#table").hide();
15 | $("#share").hide();
16 | $("button").click(function() {
17 | $(this).css({
18 | 'border-color': '',
19 | 'background-color': ''
20 | });
21 | $(this).text("Searching...");
22 | lockMap();
23 | $("#table").hide(500);
24 | $("input").val(trim($("input").val()));
25 | if ($("input").val() == '' || checkIP($("input").val()) == "ok") {
26 | getInfo();
27 | } else {
28 | errorIP();
29 | }
30 | });
31 | $(".table tr:first td:first").dblclick(function(event) {
32 | event.stopPropagation();
33 | getVersion();
34 | });
35 | $("input").dblclick(function(event) {
36 | event.stopPropagation();
37 | });
38 | $("button").dblclick(function(event) {
39 | event.stopPropagation();
40 | });
41 | $("table").dblclick(function(event) {
42 | event.stopPropagation();
43 | });
44 | $('#output').dblclick(function() {
45 | showQRCode();
46 | });
47 | $('#output').click(function() {
48 | hideQRCode();
49 | });
50 | $('#map').click(function() {
51 | hideQRCode();
52 | });
53 | $('body').mousemove(function() {
54 | checkRange();
55 | });
56 | $('body').mouseleave(function() {
57 | hideQRCode();
58 | });
59 | $(window).resize(function() {
60 | if (isPortrait()) {
61 | $(".positioncontrol").css('left', '0');
62 | $(".positioncontrol").css('right', '0');
63 | $(".positioncontrol").css('transform', 'translate(0, 0)');
64 | } else {
65 | $(".positioncontrol").css('left', 'calc(80vw - 180px)');
66 | $(".positioncontrol").css('right', '');
67 | $(".positioncontrol").css('transform', 'translate(-20%, 0)');
68 | }
69 | });
70 | });
71 |
72 | $(document).keydown(function(event) {
73 | if (event.keyCode == 13) {
74 | $("button").focus();
75 | }
76 | });
77 |
78 | function isPortrait() {
79 | if ($(document).width() < $(document).height()) {
80 | return true;
81 | }
82 | if ($(document).width() < 800) {
83 | return true;
84 | }
85 | return false;
86 | }
87 |
88 | function lockMap() {
89 | if (!isPortrait()) {
90 | return;
91 | }
92 | $(".positioncontrol").css('left', '0');
93 | $(".positioncontrol").css('right', '0');
94 | }
95 |
96 | function unlockMap() {
97 | if (!isPortrait()) {
98 | return;
99 | }
100 | var margin = $('#output').css('margin-left');
101 | if (margin !== '0px') {
102 | $('.positioncontrol').css('left', margin);
103 | $('.positioncontrol').css('right', margin);
104 | }
105 | }
106 |
107 | function checkRange() {
108 | var distanceX = Math.abs(event.pageX - shareX);
109 | var distanceY = Math.abs(event.pageY - shareY);
110 | if ((distanceX >= rangeSize) || (distanceY >= rangeSize)) {
111 | hideQRCode();
112 | }
113 | }
114 |
115 | function hideQRCode() {
116 | if (!$("#qrcode").is(':hidden')) {
117 | $("#share").hide(200);
118 | }
119 | }
120 |
121 | function showQRCode() {
122 | var ip = $("#ip").text();
123 | if (checkIP(ip) == 'ok') {
124 | var pageUri = "https://" + window.location.host + "/?ip=" + ip;
125 | $("#qrcode").attr("href", pageUri);
126 | $("#qrcode").empty();
127 | $('#qrcode').qrcode({
128 | width: 100,
129 | height: 100,
130 | text: pageUri
131 | });
132 | var shareLeft = event.pageX - shareSize;
133 | var shareTop = event.pageY - shareSize;
134 | if (shareLeft + shareSize * 2 > $(document).width()) {
135 | shareLeft = $(document).width() - shareSize * 2;
136 | }
137 | if (shareTop + shareSize * 2 > $(document).height()) {
138 | shareTop = $(document).height() - shareSize * 2;
139 | }
140 | if (shareLeft < 0) {
141 | shareLeft = 0;
142 | }
143 | if (shareTop < 0) {
144 | shareTop = 0;
145 | }
146 | shareX = shareLeft + shareSize;
147 | shareY = shareTop + shareSize;
148 | if ($("#qrcode").is(':hidden')) {
149 | $("#share").css('left', shareLeft);
150 | $("#share").css('top', shareTop);
151 | $("#share").show(200);
152 | } else {
153 | $("#share").hide(100, function() {
154 | $("#share").css('left', shareLeft);
155 | $("#share").css('top', shareTop);
156 | $("#share").show(150);
157 | });
158 | }
159 | }
160 | }
161 |
162 | function getInfo() {
163 | $.get("/info/" + $("input").val(), function(data) {
164 | console.log(data);
165 | if (data.status == "F") {
166 | errorIP();
167 | return;
168 | }
169 | if (!$("input").val()) {
170 | $("input").val(data.ip);
171 | }
172 | $("button").text("Search");
173 | $("#ip").text(data.ip);
174 | data.city = (data.city == null) ? "Unknow" : data.city;
175 | data.region = (data.region == null) ? "Unknow" : data.region;
176 | data.country = (data.country == null) ? "Unknow" : data.country;
177 | data.timezone = (data.timezone == null) ? "Unknow" : data.timezone;
178 | data.isp = (data.isp == null) ? "Unknow" : data.isp;
179 | data.scope = (data.scope == null) ? "Unknow" : data.scope;
180 | data.detail = (data.detail == null || data.detail == ' ') ? "Unknow" : data.detail;
181 | $("#city").text(data.city);
182 | $("#region").text(data.region);
183 | $("#country").text(data.country);
184 | $("#timezone").text(data.timezone);
185 | $("#isp").text(data.isp);
186 | $("#scope").text(data.scope);
187 | $("#detail").text(data.detail);
188 | if (data.as == null) {
189 | $("#as").text("Unknow");
190 | } else {
191 | $("#as").text(data.as);
192 | var asUri = "https://bgpview.io/asn/" + data.as.substr(2);
193 | $("#as").html('' + data.as + '');
194 | }
195 | if (data.loc == null) {
196 | $("#loc").text("Unknow");
197 | clear();
198 | } else {
199 | var earthUri = "https://earth.google.com/web/@" + data.loc + ",9.963a,7999.357d,35y,-34.3h,45t,0r/data=KAI";
200 | $("#loc").html('' + data.loc + '');
201 | draw(parseFloat(data.loc.split(',')[0]), parseFloat(data.loc.split(',')[1]));
202 | }
203 | $("#table").show(500);
204 | setTimeout(function() {
205 | unlockMap();
206 | }, 3000);
207 | });
208 | }
209 |
210 | function getVersion() {
211 | $.get("/version", function(data) {
212 | console.log(data);
213 | data.qqwry = data.qqwry.slice(0, 4) + "-" + data.qqwry.slice(4, 6) + "-" + data.qqwry.slice(6, 8);
214 | data.ipip = data.ipip.slice(0, 4) + "-" + data.ipip.slice(4, 6) + "-" + data.ipip.slice(6, 8);
215 | var data_ver = "";
216 | data_ver += "echoIP: " + data.echoip + "\n";
217 | data_ver += "纯真数据库: " + data.qqwry + "\n";
218 | data_ver += "IPIP.net数据库: " + data.ipip;
219 | alert(data_ver);
220 | });
221 | }
222 |
223 | function trim(str) {
224 | return str.replace(/(^\s*)|(\s*$)/g, "");
225 | }
226 |
227 | function errorIP() {
228 | $("button").text("Illegal IP");
229 | $("button").css({
230 | 'border-color': '#ff406f',
231 | 'background-color': '#ff406f'
232 | });
233 | $("input").focus();
234 | }
235 |
236 | function checkIP(ipStr) {
237 | if (ipStr === null) {
238 | return "error";
239 | }
240 | var regIPv4=/^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
241 | var regIPv6=/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:)|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}(:[0-9A-Fa-f]{1,4}){1,2})|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){1,3})|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){1,4})|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){1,5})|([0-9A-Fa-f]{1,4}:(:[0-9A-Fa-f]{1,4}){1,6})|(:(:[0-9A-Fa-f]{1,4}){1,7})|(([0-9A-Fa-f]{1,4}:){6}(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(([0-9A-Fa-f]{1,4}:){5}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|([0-9A-Fa-f]{1,4}:(:[0-9A-Fa-f]{1,4}){0,4}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})|(:(:[0-9A-Fa-f]{1,4}){0,5}:(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}))$/;
242 | var V4 = ipStr.match(regIPv4);
243 | var V6 = ipStr.match(regIPv6);
244 | if (V4 === null && V6 === null) {
245 | return "error";
246 | } else {
247 | return "ok";
248 | }
249 | }
250 |
251 | function getQuery(name) {
252 | var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
253 | var result = window.location.search.substr(1).match(reg);
254 | if (result != null) {
255 | return unescape(result[2]);
256 | } else {
257 | return null;
258 | }
259 | }
260 |
261 | function clear() {
262 | var map = new mapboxgl.Map({
263 | container: 'map',
264 | style: 'mapbox://styles/mapbox/streets-v11',
265 | center: [0, 0],
266 | zoom: 1
267 | });
268 | map.on('load', function() {
269 | console.log("reset map");
270 | });
271 | };
272 |
273 | function draw(x, y) {
274 | var size = 100;
275 | var map = new mapboxgl.Map({
276 | container: 'map',
277 | style: 'mapbox://styles/mapbox/streets-v11',
278 | center: [y, x],
279 | zoom: 3
280 | });
281 | var pulsingDot = {
282 | width: size,
283 | height: size,
284 | data: new Uint8Array(size * size * 4),
285 |
286 | onAdd: function() {
287 | var canvas = document.createElement('canvas');
288 | canvas.width = this.width;
289 | canvas.height = this.height;
290 | this.context = canvas.getContext('2d');
291 | },
292 |
293 | render: function() {
294 | var duration = 1000;
295 | var t = (performance.now() % duration) / duration;
296 | var radius = size / 2 * 0.3;
297 | var outerRadius = size / 2 * 0.7 * t + radius;
298 | var context = this.context;
299 |
300 | context.clearRect(0, 0, this.width, this.height);
301 | context.beginPath();
302 | context.arc(this.width / 2, this.height / 2, outerRadius, 0, Math.PI * 2);
303 | context.fillStyle = 'rgba(255, 200, 200,' + (1 - t) + ')';
304 | context.fill();
305 |
306 | context.beginPath();
307 | context.arc(this.width / 2, this.height / 2, radius, 0, Math.PI * 2);
308 | context.fillStyle = 'rgba(255, 100, 100, 1)';
309 | context.strokeStyle = 'white';
310 | context.lineWidth = 2 + 4 * (1 - t);
311 | context.fill();
312 | context.stroke();
313 |
314 | this.data = context.getImageData(0, 0, this.width, this.height).data;
315 | map.triggerRepaint();
316 | return true;
317 | }
318 | };
319 |
320 | map.on('load', function() {
321 | map.addImage('pulsing-dot', pulsingDot, { pixelRatio: 2 });
322 | map.addLayer({
323 | "id": "points",
324 | "type": "symbol",
325 | "source": {
326 | "type": "geojson",
327 | "data": {
328 | "type": "FeatureCollection",
329 | "features": [{
330 | "type": "Feature",
331 | "geometry": {
332 | "type": "Point",
333 | "coordinates": [y, x]
334 | }
335 | }]
336 | }
337 | },
338 | "layout": {
339 | "icon-image": "pulsing-dot"
340 | }
341 | });
342 | });
343 | };
344 |
--------------------------------------------------------------------------------
/assets/js/jquery.qrcode.min.js:
--------------------------------------------------------------------------------
1 | (function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;da||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]=
5 | 0<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c=
7 | j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount-
8 | b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0g;g++)if(null==this.modules[b][i-g]){var n=!1;f>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a,
9 | c),b=new t,e=0;e8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d=
10 | 0,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+
14 | a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;dc)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+
15 | a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256),
17 | LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d
18 | this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b>>7-a%8&1)},put:function(a,c){for(var d=0;d>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1,
26 | correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e").css("height",b+"px").appendTo(c);for(i=0;i").css("width",
28 | d+"px").css("background-color",a.isDark(e,i)?h.foreground:h.background).appendTo(f)}}a=c;jQuery(a).appendTo(this)})}})(jQuery);
29 |
--------------------------------------------------------------------------------
/assets/css/main.min.css:
--------------------------------------------------------------------------------
1 | .mapboxgl-ctrl button:not(:disabled):hover{background-color:rgba(0,0,0,.05)}.mapboxgl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}/*! bulma.io v0.9.1 | MIT License | github.com/jgthms/bulma */.button{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.button,.input{-moz-appearance:none;-webkit-appearance:none;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:none;box-shadow:none;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;font-size:1rem;height:2.5em;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.5em - 1px);padding-left:calc(.75em - 1px);padding-right:calc(.75em - 1px);padding-top:calc(.5em - 1px);position:relative;vertical-align:top}.button:active,.button:focus,.input:active,.input:focus{outline:0}body,h3,html,p{margin:0;padding:0}h3{font-size:100%;font-weight:400}button,input{margin:0}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}table{border-collapse:collapse;border-spacing:0}td{padding:0}td:not([align]){text-align:inherit}html{background-color:#fff;font-size:14px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}body,button,input{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}table td{vertical-align:top}table td:not([align]){text-align:inherit}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-bottom:calc(.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(.5em - 1px);text-align:center;white-space:nowrap}.button:hover{border-color:#b5b5b5;color:#363636}.button:focus{border-color:#3273dc;color:#363636}.button.is-focused:not(:active),.button:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(50,115,220,.25);box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button:active{border-color:#4a4a4a;color:#363636}.button.is-white.is-focused:not(:active),.button.is-white:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(255,255,255,.25);box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-black.is-focused:not(:active),.button.is-black:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(10,10,10,.25);box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-light.is-focused:not(:active),.button.is-light:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(245,245,245,.25);box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-dark.is-focused:not(:active),.button.is-dark:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(54,54,54,.25);box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-primary.is-focused:not(:active),.button.is-primary:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(0,209,178,.25);box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.button.is-link.is-focused:not(:active),.button.is-link:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(50,115,220,.25);box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-info.is-focused:not(:active),.button.is-info:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(50,152,220,.25);box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.button.is-success.is-focused:not(:active),.button.is-success:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(72,199,116,.25);box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.button.is-warning.is-focused:not(:active),.button.is-warning:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(255,221,87,.25);box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.button.is-danger.is-focused:not(:active),.button.is-danger:focus:not(:active){-webkit-box-shadow:0 0 0 .125em rgba(241,70,104,.25);box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-fullwidth{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%}.container{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;margin:0 auto;position:relative;width:auto}@media screen and (min-width:1024px){.container{max-width:960px}}@media screen and (min-width:1216px){.container:not(.is-max-desktop){max-width:1152px}}.table{background-color:#fff;color:#363636}.table td{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table tbody{background-color:rgba(0,0,0,0)}.table tbody tr:last-child td{border-bottom-width:0}.input{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder{color:rgba(54,54,54,.3)}.input::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.input:-moz-placeholder{color:rgba(54,54,54,.3)}.input:-ms-input-placeholder{color:rgba(54,54,54,.3)}.input:hover{border-color:#b5b5b5}.input:active,.input:focus{border-color:#3273dc;-webkit-box-shadow:0 0 0 .125em rgba(50,115,220,.25);box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.input{-webkit-box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}.is-medium.input{font-size:1.25rem}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-link:not(:hover)::after{border-color:#3273dc}.select.is-info:not(:hover)::after{border-color:#3298dc}.select.is-success:not(:hover)::after{border-color:#48c774}.select.is-warning:not(:hover)::after{border-color:#ffdd57}.select.is-danger:not(:hover)::after{border-color:#f14668}.control{-webkit-box-sizing:border-box;box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}@media screen and (min-width:1024px){.navbar-link.is-active:not(:focus):not(:hover),a.navbar-item.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}}.column{display:block;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:1;flex-shrink:1;padding:.75rem}@media all and (orientation :landscape),print{.column.is-6{-webkit-box-flex:0;-ms-flex:none;flex:none;width:50%}.column.is-offset-6{margin-left:50%}.column.is-12{-webkit-box-flex:0;-ms-flex:none;flex:none;width:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-multiline{-ms-flex-wrap:wrap;flex-wrap:wrap}.columns.is-vcentered{-webkit-box-align:center;-ms-flex-align:center;align-items:center}@media all and (orientation :landscape),print{.columns:not(.is-desktop){display:-webkit-box;display:-ms-flexbox;display:flex}}.has-text-centered{text-align:center!important}.hero{-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.hero.is-fullheight .hero-body{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex}.hero.is-fullheight .hero-body>.container{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:1;flex-shrink:1}.hero.is-fullheight{min-height:100vh}.hero-body{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:0;flex-shrink:0;padding:3rem 1.5rem}p{font-family:Roboto,sans-serif}::-moz-selection{background:#8c8cf9;color:#fff}::selection{background:#8c8cf9;color:#fff}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";overflow:hidden!important}div,div:active,div:focus{outline:0!important}.button:active,.button:focus{-webkit-box-shadow:none!important;box-shadow:none!important}@font-face{font-family:'Nexa Light';src:url(../fonts/text/nexa/NexaLight.woff) format("woff"),url(../fonts/text/nexa/NexaLight.ttf) format("truetype")}@font-face{font-family:'Nexa Bold';font-weight:700;src:url(../fonts/text/nexa/NexaBold.woff) format("woff"),url(../fonts/text/nexa/NexaBold.ttf) format("truetype")}::-webkit-input-placeholder{color:#cecece}::-moz-placeholder{color:#cecece}:-ms-input-placeholder{color:#cecece}:-moz-placeholder{color:#cecece}body::-webkit-scrollbar{width:10px}body::-webkit-scrollbar-thumb{border-radius:5px;background:rgba(0,0,0,.2)}.hero.is-app-grey{background-color:#f5f6fa}.hero-body{position:relative;background-size:cover!important;background-repeat:no-repeat!important;z-index:6}.hero-body .is-subheader-caption{padding-bottom:60px}.button{-webkit-transition:all .5s;transition:all .5s}.button.button-cta{padding:22px 40px!important}.button.no-lh{line-height:0!important}.button.raised:hover{-webkit-box-shadow:0 14px 26px -12px rgba(0,0,0,.42),0 4px 23px 0 rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)!important;box-shadow:0 14px 26px -12px rgba(0,0,0,.42),0 4px 23px 0 rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)!important;opacity:.8}.button.is-bold{font-weight:500}.button:focus{border-color:#ccc}.button.is-fullwidth{display:block!important;width:100%!important;text-align:center!important}.button.primary-btn{outline:0;border-color:#8c8cf9;background-color:#8c8cf9;color:#fff;-webkit-transition:all .5s;transition:all .5s}.button.primary-btn:hover{color:#fff}.button.primary-btn.raised:hover{-webkit-box-shadow:0 14px 26px -12px rgba(140,140,249,.42),0 4px 23px 0 rgba(0,0,0,.12),0 8px 10px -5px rgba(140,140,249,.2)!important;box-shadow:0 14px 26px -12px rgba(140,140,249,.42),0 4px 23px 0 rgba(0,0,0,.12),0 8px 10px -5px rgba(140,140,249,.2)!important;opacity:.9 .5}.button.primary-btn:focus{border-color:#8c8cf9}.flex-card{position:relative;background-color:#fff;border:1px solid #fcfcfc;border-radius:.1875rem;display:inline-block;position:relative;overflow:hidden;width:100%;margin-bottom:20px;-webkit-transition:all .5s;transition:all .5s}.input{-webkit-box-shadow:none!important;box-shadow:none!important}.input{color:#878787;font-family:Roboto,sans-serif;font-size:.95rem;height:36px;-webkit-transition:all .3s;transition:all .3s;border-color:#dee2e6}.input.is-medium{height:44px}.input:hover{border-color:#d3d3d3}.input:active,.input:focus{border-color:#d6d6d6}.input:focus{-webkit-box-shadow:-1px 3px 15px 0 rgba(0,0,0,.06)!important;box-shadow:-1px 3px 15px 0 rgba(0,0,0,.06)!important}.field>label{font-family:Roboto,sans-serif;padding-bottom:4px;color:#5d6c84}.table{width:100%}.switch[type=checkbox]:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(237,237,237,.25);box-shadow:0 0 0 .125em rgba(237,237,237,.25)}.switch[type=checkbox]:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(135,135,135,.25);box-shadow:0 0 0 .125em rgba(135,135,135,.25)}.switch[type=checkbox].is-small:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(237,237,237,.25);box-shadow:0 0 0 .125em rgba(237,237,237,.25)}.switch[type=checkbox].is-small:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(135,135,135,.25);box-shadow:0 0 0 .125em rgba(135,135,135,.25)}.switch[type=checkbox].is-medium:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(237,237,237,.25);box-shadow:0 0 0 .125em rgba(237,237,237,.25)}.switch[type=checkbox].is-medium:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(135,135,135,.25);box-shadow:0 0 0 .125em rgba(135,135,135,.25)}.switch[type=checkbox].is-large:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(237,237,237,.25);box-shadow:0 0 0 .125em rgba(237,237,237,.25)}.switch[type=checkbox].is-large:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(135,135,135,.25);box-shadow:0 0 0 .125em rgba(135,135,135,.25)}.switch[type=checkbox].is-primary:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(140,140,249,.25);box-shadow:0 0 0 .125em rgba(140,140,249,.25)}.switch[type=checkbox].is-secondary:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(127,0,255,.25);box-shadow:0 0 0 .125em rgba(127,0,255,.25)}.switch[type=checkbox].is-accent:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(173,92,255,.25);box-shadow:0 0 0 .125em rgba(173,92,255,.25)}.switch[type=checkbox].is-info:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(3,155,229,.25);box-shadow:0 0 0 .125em rgba(3,155,229,.25)}.switch[type=checkbox].is-success:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(6,214,160,.25);box-shadow:0 0 0 .125em rgba(6,214,160,.25)}.switch[type=checkbox].is-warning:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(237,165,20,.25);box-shadow:0 0 0 .125em rgba(237,165,20,.25)}.switch[type=checkbox].is-danger:checked:focus:not(:active)+label::before{-webkit-box-shadow:0 0 0 .125em rgba(249,43,96,.25);box-shadow:0 0 0 .125em rgba(249,43,96,.25)}.hero{position:relative}.carousel-wrap .testimonials-solo-carousel .hero-form .testimonial-item .field .control .input:not(:focus),.hero-form .carousel-wrap .testimonials-solo-carousel .testimonial-item .field .control .input:not(:focus),.hero-form .flex-card .field .control .input:not(:focus),.hero-form .network-grid .network-card .field .control .input:not(:focus),.hero-form .profile-card .field .control .input:not(:focus),.hero-form .project-card .field .control .input:not(:focus),.network-grid .hero-form .network-card .field .control .input:not(:focus){border-color:#dee2e6}.mapbox-hero{position:relative}.contact-page-form{display:block;padding:40px;border:1px solid #eaecef;-webkit-box-shadow:-1px 3px 15px 0 rgba(0,0,0,.06);box-shadow:-1px 3px 15px 0 rgba(0,0,0,.06);border-radius:6px;width:100%;max-width:380px;margin:0 auto;text-align:left}.contact-page-form>h3{font-family:"Nexa Bold",sans-serif;font-size:1.4rem;color:#444f60}.contact-page-form>p{color:#8e9baf;margin-bottom:2rem}.contact-page-form .column{padding:.5rem}.contact-page-form .field>label{font-size:.95rem}.contact-page-form .field .control .input{font-size:1.1rem}@media (max-width:768px){#main-hero{max-height:100vh;background-position-x:55%!important}}@font-face{font-family:slick;src:url(/assets/fonts/slick.eot);src:url(/assets/fonts/slick.eot?#iefix) format("embedded-opentype"),url(/assets/fonts/slick.woff) format("woff"),url(/assets/fonts/slick.ttf) format("truetype"),url(/assets/fonts/slick.svg#slick) format("svg");font-weight:400;font-style:normal}
--------------------------------------------------------------------------------
/assets/css/main.css:
--------------------------------------------------------------------------------
1 | .mapboxgl-ctrl button:not(:disabled):hover {
2 | background-color: rgba(0, 0, 0, .05)
3 | }
4 |
5 | .mapboxgl-ctrl-group button:focus:focus-visible {
6 | box-shadow: 0 0 2px 2px #0096ff
7 | }
8 |
9 | .mapboxgl-ctrl-group button:focus:not(:focus-visible) {
10 | box-shadow: none
11 | }
12 |
13 |
14 | /*! bulma.io v0.9.1 | MIT License | github.com/jgthms/bulma */
15 |
16 | .button {
17 | -webkit-touch-callout: none;
18 | -webkit-user-select: none;
19 | -moz-user-select: none;
20 | -ms-user-select: none;
21 | user-select: none
22 | }
23 |
24 | .button,
25 | .input {
26 | -moz-appearance: none;
27 | -webkit-appearance: none;
28 | -webkit-box-align: center;
29 | -ms-flex-align: center;
30 | align-items: center;
31 | border: 1px solid transparent;
32 | border-radius: 4px;
33 | -webkit-box-shadow: none;
34 | box-shadow: none;
35 | display: -webkit-inline-box;
36 | display: -ms-inline-flexbox;
37 | display: inline-flex;
38 | font-size: 1rem;
39 | height: 2.5em;
40 | -webkit-box-pack: start;
41 | -ms-flex-pack: start;
42 | justify-content: flex-start;
43 | line-height: 1.5;
44 | padding-bottom: calc(.5em - 1px);
45 | padding-left: calc(.75em - 1px);
46 | padding-right: calc(.75em - 1px);
47 | padding-top: calc(.5em - 1px);
48 | position: relative;
49 | vertical-align: top
50 | }
51 |
52 | .button:active,
53 | .button:focus,
54 | .input:active,
55 | .input:focus {
56 | outline: 0
57 | }
58 |
59 | body,
60 | h3,
61 | html,
62 | p {
63 | margin: 0;
64 | padding: 0
65 | }
66 |
67 | h3 {
68 | font-size: 100%;
69 | font-weight: 400
70 | }
71 |
72 | button,
73 | input {
74 | margin: 0
75 | }
76 |
77 | html {
78 | -webkit-box-sizing: border-box;
79 | box-sizing: border-box
80 | }
81 |
82 | *,
83 | ::after,
84 | ::before {
85 | -webkit-box-sizing: inherit;
86 | box-sizing: inherit
87 | }
88 |
89 | table {
90 | border-collapse: collapse;
91 | border-spacing: 0
92 | }
93 |
94 | td {
95 | padding: 0
96 | }
97 |
98 | td:not([align]) {
99 | text-align: inherit
100 | }
101 |
102 | html {
103 | background-color: #fff;
104 | font-size: 14px;
105 | -moz-osx-font-smoothing: grayscale;
106 | -webkit-font-smoothing: antialiased;
107 | min-width: 300px;
108 | overflow-x: hidden;
109 | overflow-y: scroll;
110 | text-rendering: optimizeLegibility;
111 | -webkit-text-size-adjust: 100%;
112 | -moz-text-size-adjust: 100%;
113 | -ms-text-size-adjust: 100%;
114 | text-size-adjust: 100%
115 | }
116 |
117 | body,
118 | button,
119 | input {
120 | font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif
121 | }
122 |
123 | body {
124 | color: #4a4a4a;
125 | font-size: 1em;
126 | font-weight: 400;
127 | line-height: 1.5
128 | }
129 |
130 | table td {
131 | vertical-align: top
132 | }
133 |
134 | table td:not([align]) {
135 | text-align: inherit
136 | }
137 |
138 | .button {
139 | background-color: #fff;
140 | border-color: #dbdbdb;
141 | border-width: 1px;
142 | color: #363636;
143 | cursor: pointer;
144 | -webkit-box-pack: center;
145 | -ms-flex-pack: center;
146 | justify-content: center;
147 | padding-bottom: calc(.5em - 1px);
148 | padding-left: 1em;
149 | padding-right: 1em;
150 | padding-top: calc(.5em - 1px);
151 | text-align: center;
152 | white-space: nowrap
153 | }
154 |
155 | .button:hover {
156 | border-color: #b5b5b5;
157 | color: #363636
158 | }
159 |
160 | .button:focus {
161 | border-color: #3273dc;
162 | color: #363636
163 | }
164 |
165 | .button.is-focused:not(:active),
166 | .button:focus:not(:active) {
167 | -webkit-box-shadow: 0 0 0 .125em rgba(50, 115, 220, .25);
168 | box-shadow: 0 0 0 .125em rgba(50, 115, 220, .25)
169 | }
170 |
171 | .button:active {
172 | border-color: #4a4a4a;
173 | color: #363636
174 | }
175 |
176 | .button.is-white.is-focused:not(:active),
177 | .button.is-white:focus:not(:active) {
178 | -webkit-box-shadow: 0 0 0 .125em rgba(255, 255, 255, .25);
179 | box-shadow: 0 0 0 .125em rgba(255, 255, 255, .25)
180 | }
181 |
182 | .button.is-black.is-focused:not(:active),
183 | .button.is-black:focus:not(:active) {
184 | -webkit-box-shadow: 0 0 0 .125em rgba(10, 10, 10, .25);
185 | box-shadow: 0 0 0 .125em rgba(10, 10, 10, .25)
186 | }
187 |
188 | .button.is-light.is-focused:not(:active),
189 | .button.is-light:focus:not(:active) {
190 | -webkit-box-shadow: 0 0 0 .125em rgba(245, 245, 245, .25);
191 | box-shadow: 0 0 0 .125em rgba(245, 245, 245, .25)
192 | }
193 |
194 | .button.is-dark.is-focused:not(:active),
195 | .button.is-dark:focus:not(:active) {
196 | -webkit-box-shadow: 0 0 0 .125em rgba(54, 54, 54, .25);
197 | box-shadow: 0 0 0 .125em rgba(54, 54, 54, .25)
198 | }
199 |
200 | .button.is-primary.is-focused:not(:active),
201 | .button.is-primary:focus:not(:active) {
202 | -webkit-box-shadow: 0 0 0 .125em rgba(0, 209, 178, .25);
203 | box-shadow: 0 0 0 .125em rgba(0, 209, 178, .25)
204 | }
205 |
206 | .button.is-link.is-focused:not(:active),
207 | .button.is-link:focus:not(:active) {
208 | -webkit-box-shadow: 0 0 0 .125em rgba(50, 115, 220, .25);
209 | box-shadow: 0 0 0 .125em rgba(50, 115, 220, .25)
210 | }
211 |
212 | .button.is-info.is-focused:not(:active),
213 | .button.is-info:focus:not(:active) {
214 | -webkit-box-shadow: 0 0 0 .125em rgba(50, 152, 220, .25);
215 | box-shadow: 0 0 0 .125em rgba(50, 152, 220, .25)
216 | }
217 |
218 | .button.is-success.is-focused:not(:active),
219 | .button.is-success:focus:not(:active) {
220 | -webkit-box-shadow: 0 0 0 .125em rgba(72, 199, 116, .25);
221 | box-shadow: 0 0 0 .125em rgba(72, 199, 116, .25)
222 | }
223 |
224 | .button.is-warning.is-focused:not(:active),
225 | .button.is-warning:focus:not(:active) {
226 | -webkit-box-shadow: 0 0 0 .125em rgba(255, 221, 87, .25);
227 | box-shadow: 0 0 0 .125em rgba(255, 221, 87, .25)
228 | }
229 |
230 | .button.is-danger.is-focused:not(:active),
231 | .button.is-danger:focus:not(:active) {
232 | -webkit-box-shadow: 0 0 0 .125em rgba(241, 70, 104, .25);
233 | box-shadow: 0 0 0 .125em rgba(241, 70, 104, .25)
234 | }
235 |
236 | .button.is-fullwidth {
237 | display: -webkit-box;
238 | display: -ms-flexbox;
239 | display: flex;
240 | width: 100%
241 | }
242 |
243 | .container {
244 | -webkit-box-flex: 1;
245 | -ms-flex-positive: 1;
246 | flex-grow: 1;
247 | margin: 0 auto;
248 | position: relative;
249 | width: auto
250 | }
251 |
252 | @media screen and (min-width:1024px) {
253 | .container {
254 | max-width: 960px
255 | }
256 | }
257 |
258 | @media screen and (min-width:1216px) {
259 | .container:not(.is-max-desktop) {
260 | max-width: 1152px
261 | }
262 | }
263 |
264 | .table {
265 | background-color: #fff;
266 | color: #363636
267 | }
268 |
269 | .table td {
270 | border: 1px solid #dbdbdb;
271 | border-width: 0 0 1px;
272 | padding: .5em .75em;
273 | vertical-align: top
274 | }
275 |
276 | .table tbody {
277 | background-color: rgba(0, 0, 0, 0)
278 | }
279 |
280 | .table tbody tr:last-child td {
281 | border-bottom-width: 0
282 | }
283 |
284 | .input {
285 | background-color: #fff;
286 | border-color: #dbdbdb;
287 | border-radius: 4px;
288 | color: #363636
289 | }
290 |
291 | .input::-moz-placeholder {
292 | color: rgba(54, 54, 54, .3)
293 | }
294 |
295 | .input::-webkit-input-placeholder {
296 | color: rgba(54, 54, 54, .3)
297 | }
298 |
299 | .input:-moz-placeholder {
300 | color: rgba(54, 54, 54, .3)
301 | }
302 |
303 | .input:-ms-input-placeholder {
304 | color: rgba(54, 54, 54, .3)
305 | }
306 |
307 | .input:hover {
308 | border-color: #b5b5b5
309 | }
310 |
311 | .input:active,
312 | .input:focus {
313 | border-color: #3273dc;
314 | -webkit-box-shadow: 0 0 0 .125em rgba(50, 115, 220, .25);
315 | box-shadow: 0 0 0 .125em rgba(50, 115, 220, .25)
316 | }
317 |
318 | .input {
319 | -webkit-box-shadow: inset 0 .0625em .125em rgba(10, 10, 10, .05);
320 | box-shadow: inset 0 .0625em .125em rgba(10, 10, 10, .05);
321 | max-width: 100%;
322 | width: 100%
323 | }
324 |
325 | .is-medium.input {
326 | font-size: 1.25rem
327 | }
328 |
329 | .select.is-white:not(:hover)::after {
330 | border-color: #fff
331 | }
332 |
333 | .select.is-black:not(:hover)::after {
334 | border-color: #0a0a0a
335 | }
336 |
337 | .select.is-light:not(:hover)::after {
338 | border-color: #f5f5f5
339 | }
340 |
341 | .select.is-dark:not(:hover)::after {
342 | border-color: #363636
343 | }
344 |
345 | .select.is-primary:not(:hover)::after {
346 | border-color: #00d1b2
347 | }
348 |
349 | .select.is-link:not(:hover)::after {
350 | border-color: #3273dc
351 | }
352 |
353 | .select.is-info:not(:hover)::after {
354 | border-color: #3298dc
355 | }
356 |
357 | .select.is-success:not(:hover)::after {
358 | border-color: #48c774
359 | }
360 |
361 | .select.is-warning:not(:hover)::after {
362 | border-color: #ffdd57
363 | }
364 |
365 | .select.is-danger:not(:hover)::after {
366 | border-color: #f14668
367 | }
368 |
369 | .control {
370 | -webkit-box-sizing: border-box;
371 | box-sizing: border-box;
372 | clear: both;
373 | font-size: 1rem;
374 | position: relative;
375 | text-align: inherit
376 | }
377 |
378 | @media screen and (min-width:1024px) {
379 | .navbar-link.is-active:not(:focus):not(:hover),
380 | a.navbar-item.is-active:not(:focus):not(:hover) {
381 | background-color: rgba(0, 0, 0, 0)
382 | }
383 | }
384 |
385 | .column {
386 | display: block;
387 | -ms-flex-preferred-size: 0;
388 | flex-basis: 0;
389 | -webkit-box-flex: 1;
390 | -ms-flex-positive: 1;
391 | flex-grow: 1;
392 | -ms-flex-negative: 1;
393 | flex-shrink: 1;
394 | padding: .75rem
395 | }
396 |
397 | /* @media screen and (min-width:769px), */
398 | @media all and (orientation : landscape),
399 | print {
400 | .column.is-6 {
401 | -webkit-box-flex: 0;
402 | -ms-flex: none;
403 | flex: none;
404 | width: 50%
405 | }
406 | .column.is-offset-6 {
407 | margin-left: 50%
408 | }
409 | .column.is-12 {
410 | -webkit-box-flex: 0;
411 | -ms-flex: none;
412 | flex: none;
413 | width: 100%
414 | }
415 | }
416 |
417 | .columns {
418 | margin-left: -.75rem;
419 | margin-right: -.75rem;
420 | margin-top: -.75rem
421 | }
422 |
423 | .columns:last-child {
424 | margin-bottom: -.75rem
425 | }
426 |
427 | .columns:not(:last-child) {
428 | margin-bottom: calc(1.5rem - .75rem)
429 | }
430 |
431 | .columns.is-multiline {
432 | -ms-flex-wrap: wrap;
433 | flex-wrap: wrap
434 | }
435 |
436 | .columns.is-vcentered {
437 | -webkit-box-align: center;
438 | -ms-flex-align: center;
439 | align-items: center
440 | }
441 |
442 | /* @media screen and (min-width:769px), */
443 | @media all and (orientation : landscape),
444 | print {
445 | .columns:not(.is-desktop) {
446 | display: -webkit-box;
447 | display: -ms-flexbox;
448 | display: flex
449 | }
450 | }
451 |
452 | .has-text-centered {
453 | text-align: center!important
454 | }
455 |
456 | .hero {
457 | -webkit-box-align: stretch;
458 | -ms-flex-align: stretch;
459 | align-items: stretch;
460 | display: -webkit-box;
461 | display: -ms-flexbox;
462 | display: flex;
463 | -webkit-box-orient: vertical;
464 | -webkit-box-direction: normal;
465 | -ms-flex-direction: column;
466 | flex-direction: column;
467 | -webkit-box-pack: justify;
468 | -ms-flex-pack: justify;
469 | justify-content: space-between
470 | }
471 |
472 | .hero.is-fullheight .hero-body {
473 | -webkit-box-align: center;
474 | -ms-flex-align: center;
475 | align-items: center;
476 | display: -webkit-box;
477 | display: -ms-flexbox;
478 | display: flex
479 | }
480 |
481 | .hero.is-fullheight .hero-body>.container {
482 | -webkit-box-flex: 1;
483 | -ms-flex-positive: 1;
484 | flex-grow: 1;
485 | -ms-flex-negative: 1;
486 | flex-shrink: 1
487 | }
488 |
489 | .hero.is-fullheight {
490 | min-height: 100vh
491 | }
492 |
493 | .hero-body {
494 | -webkit-box-flex: 1;
495 | -ms-flex-positive: 1;
496 | flex-grow: 1;
497 | -ms-flex-negative: 0;
498 | flex-shrink: 0;
499 | padding: 3rem 1.5rem
500 | }
501 |
502 | p {
503 | font-family: Roboto, sans-serif
504 | }
505 |
506 | ::-moz-selection {
507 | background: #8c8cf9;
508 | color: #fff
509 | }
510 |
511 | ::selection {
512 | background: #8c8cf9;
513 | color: #fff
514 | }
515 |
516 | body {
517 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
518 | overflow: hidden!important
519 | }
520 |
521 | div,
522 | div:active,
523 | div:focus {
524 | outline: 0!important
525 | }
526 |
527 | .button:active,
528 | .button:focus {
529 | -webkit-box-shadow: none!important;
530 | box-shadow: none!important
531 | }
532 |
533 | @font-face {
534 | font-family: 'Nexa Light';
535 | src: url(../fonts/text/nexa/NexaLight.woff) format("woff"), url(../fonts/text/nexa/NexaLight.ttf) format("truetype")
536 | }
537 |
538 | @font-face {
539 | font-family: 'Nexa Bold';
540 | font-weight: 700;
541 | src: url(../fonts/text/nexa/NexaBold.woff) format("woff"), url(../fonts/text/nexa/NexaBold.ttf) format("truetype")
542 | }
543 |
544 | ::-webkit-input-placeholder {
545 | color: #cecece
546 | }
547 |
548 | ::-moz-placeholder {
549 | color: #cecece
550 | }
551 |
552 | :-ms-input-placeholder {
553 | color: #cecece
554 | }
555 |
556 | :-moz-placeholder {
557 | color: #cecece
558 | }
559 |
560 | body::-webkit-scrollbar {
561 | width: 10px
562 | }
563 |
564 | body::-webkit-scrollbar-thumb {
565 | border-radius: 5px;
566 | background: rgba(0, 0, 0, .2)
567 | }
568 |
569 | .hero.is-app-grey {
570 | background-color: #f5f6fa
571 | }
572 |
573 | .hero-body {
574 | position: relative;
575 | background-size: cover!important;
576 | background-repeat: no-repeat!important;
577 | z-index: 6
578 | }
579 |
580 | .hero-body .is-subheader-caption {
581 | padding-bottom: 60px
582 | }
583 |
584 | .button {
585 | -webkit-transition: all .5s;
586 | transition: all .5s
587 | }
588 |
589 | .button.button-cta {
590 | padding: 22px 40px!important
591 | }
592 |
593 | .button.no-lh {
594 | line-height: 0!important
595 | }
596 |
597 | .button.raised:hover {
598 | -webkit-box-shadow: 0 14px 26px -12px rgba(0, 0, 0, .42), 0 4px 23px 0 rgba(0, 0, 0, .12), 0 8px 10px -5px rgba(0, 0, 0, .2)!important;
599 | box-shadow: 0 14px 26px -12px rgba(0, 0, 0, .42), 0 4px 23px 0 rgba(0, 0, 0, .12), 0 8px 10px -5px rgba(0, 0, 0, .2)!important;
600 | opacity: .8
601 | }
602 |
603 | .button.is-bold {
604 | font-weight: 500
605 | }
606 |
607 | .button:focus {
608 | border-color: #ccc
609 | }
610 |
611 | .button.is-fullwidth {
612 | display: block!important;
613 | width: 100%!important;
614 | text-align: center!important
615 | }
616 |
617 | .button.primary-btn {
618 | outline: 0;
619 | border-color: #8c8cf9;
620 | background-color: #8c8cf9;
621 | color: #fff;
622 | -webkit-transition: all .5s;
623 | transition: all .5s
624 | }
625 |
626 | .button.primary-btn:hover {
627 | color: #fff
628 | }
629 |
630 | .button.primary-btn.raised:hover {
631 | -webkit-box-shadow: 0 14px 26px -12px rgba(140, 140, 249, .42), 0 4px 23px 0 rgba(0, 0, 0, .12), 0 8px 10px -5px rgba(140, 140, 249, .2)!important;
632 | box-shadow: 0 14px 26px -12px rgba(140, 140, 249, .42), 0 4px 23px 0 rgba(0, 0, 0, .12), 0 8px 10px -5px rgba(140, 140, 249, .2)!important;
633 | opacity: .9 .5
634 | }
635 |
636 | .button.primary-btn:focus {
637 | border-color: #8c8cf9
638 | }
639 |
640 | .flex-card {
641 | position: relative;
642 | background-color: #fff;
643 | border: 1px solid #fcfcfc;
644 | border-radius: .1875rem;
645 | display: inline-block;
646 | position: relative;
647 | overflow: hidden;
648 | width: 100%;
649 | margin-bottom: 20px;
650 | -webkit-transition: all .5s;
651 | transition: all .5s
652 | }
653 |
654 | .input {
655 | -webkit-box-shadow: none!important;
656 | box-shadow: none!important
657 | }
658 |
659 | .input {
660 | color: #878787;
661 | font-family: Roboto, sans-serif;
662 | font-size: .95rem;
663 | height: 36px;
664 | -webkit-transition: all .3s;
665 | transition: all .3s;
666 | border-color: #dee2e6
667 | }
668 |
669 | .input.is-medium {
670 | height: 44px
671 | }
672 |
673 | .input:hover {
674 | border-color: #d3d3d3
675 | }
676 |
677 | .input:active,
678 | .input:focus {
679 | border-color: #d6d6d6
680 | }
681 |
682 | .input:focus {
683 | -webkit-box-shadow: -1px 3px 15px 0 rgba(0, 0, 0, .06)!important;
684 | box-shadow: -1px 3px 15px 0 rgba(0, 0, 0, .06)!important
685 | }
686 |
687 | .field>label {
688 | font-family: Roboto, sans-serif;
689 | padding-bottom: 4px;
690 | color: #5d6c84
691 | }
692 |
693 | .table {
694 | width: 100%
695 | }
696 |
697 | .switch[type=checkbox]:focus:not(:active)+label::before {
698 | -webkit-box-shadow: 0 0 0 .125em rgba(237, 237, 237, .25);
699 | box-shadow: 0 0 0 .125em rgba(237, 237, 237, .25)
700 | }
701 |
702 | .switch[type=checkbox]:checked:focus:not(:active)+label::before {
703 | -webkit-box-shadow: 0 0 0 .125em rgba(135, 135, 135, .25);
704 | box-shadow: 0 0 0 .125em rgba(135, 135, 135, .25)
705 | }
706 |
707 | .switch[type=checkbox].is-small:focus:not(:active)+label::before {
708 | -webkit-box-shadow: 0 0 0 .125em rgba(237, 237, 237, .25);
709 | box-shadow: 0 0 0 .125em rgba(237, 237, 237, .25)
710 | }
711 |
712 | .switch[type=checkbox].is-small:checked:focus:not(:active)+label::before {
713 | -webkit-box-shadow: 0 0 0 .125em rgba(135, 135, 135, .25);
714 | box-shadow: 0 0 0 .125em rgba(135, 135, 135, .25)
715 | }
716 |
717 | .switch[type=checkbox].is-medium:focus:not(:active)+label::before {
718 | -webkit-box-shadow: 0 0 0 .125em rgba(237, 237, 237, .25);
719 | box-shadow: 0 0 0 .125em rgba(237, 237, 237, .25)
720 | }
721 |
722 | .switch[type=checkbox].is-medium:checked:focus:not(:active)+label::before {
723 | -webkit-box-shadow: 0 0 0 .125em rgba(135, 135, 135, .25);
724 | box-shadow: 0 0 0 .125em rgba(135, 135, 135, .25)
725 | }
726 |
727 | .switch[type=checkbox].is-large:focus:not(:active)+label::before {
728 | -webkit-box-shadow: 0 0 0 .125em rgba(237, 237, 237, .25);
729 | box-shadow: 0 0 0 .125em rgba(237, 237, 237, .25)
730 | }
731 |
732 | .switch[type=checkbox].is-large:checked:focus:not(:active)+label::before {
733 | -webkit-box-shadow: 0 0 0 .125em rgba(135, 135, 135, .25);
734 | box-shadow: 0 0 0 .125em rgba(135, 135, 135, .25)
735 | }
736 |
737 | .switch[type=checkbox].is-primary:checked:focus:not(:active)+label::before {
738 | -webkit-box-shadow: 0 0 0 .125em rgba(140, 140, 249, .25);
739 | box-shadow: 0 0 0 .125em rgba(140, 140, 249, .25)
740 | }
741 |
742 | .switch[type=checkbox].is-secondary:checked:focus:not(:active)+label::before {
743 | -webkit-box-shadow: 0 0 0 .125em rgba(127, 0, 255, .25);
744 | box-shadow: 0 0 0 .125em rgba(127, 0, 255, .25)
745 | }
746 |
747 | .switch[type=checkbox].is-accent:checked:focus:not(:active)+label::before {
748 | -webkit-box-shadow: 0 0 0 .125em rgba(173, 92, 255, .25);
749 | box-shadow: 0 0 0 .125em rgba(173, 92, 255, .25)
750 | }
751 |
752 | .switch[type=checkbox].is-info:checked:focus:not(:active)+label::before {
753 | -webkit-box-shadow: 0 0 0 .125em rgba(3, 155, 229, .25);
754 | box-shadow: 0 0 0 .125em rgba(3, 155, 229, .25)
755 | }
756 |
757 | .switch[type=checkbox].is-success:checked:focus:not(:active)+label::before {
758 | -webkit-box-shadow: 0 0 0 .125em rgba(6, 214, 160, .25);
759 | box-shadow: 0 0 0 .125em rgba(6, 214, 160, .25)
760 | }
761 |
762 | .switch[type=checkbox].is-warning:checked:focus:not(:active)+label::before {
763 | -webkit-box-shadow: 0 0 0 .125em rgba(237, 165, 20, .25);
764 | box-shadow: 0 0 0 .125em rgba(237, 165, 20, .25)
765 | }
766 |
767 | .switch[type=checkbox].is-danger:checked:focus:not(:active)+label::before {
768 | -webkit-box-shadow: 0 0 0 .125em rgba(249, 43, 96, .25);
769 | box-shadow: 0 0 0 .125em rgba(249, 43, 96, .25)
770 | }
771 |
772 | .hero {
773 | position: relative
774 | }
775 |
776 | .carousel-wrap .testimonials-solo-carousel .hero-form .testimonial-item .field .control .input:not(:focus),
777 | .hero-form .carousel-wrap .testimonials-solo-carousel .testimonial-item .field .control .input:not(:focus),
778 | .hero-form .flex-card .field .control .input:not(:focus),
779 | .hero-form .network-grid .network-card .field .control .input:not(:focus),
780 | .hero-form .profile-card .field .control .input:not(:focus),
781 | .hero-form .project-card .field .control .input:not(:focus),
782 | .network-grid .hero-form .network-card .field .control .input:not(:focus) {
783 | border-color: #dee2e6
784 | }
785 |
786 | .mapbox-hero {
787 | position: relative
788 | }
789 |
790 | .contact-page-form {
791 | display: block;
792 | padding: 40px;
793 | border: 1px solid #eaecef;
794 | -webkit-box-shadow: -1px 3px 15px 0 rgba(0, 0, 0, .06);
795 | box-shadow: -1px 3px 15px 0 rgba(0, 0, 0, .06);
796 | border-radius: 6px;
797 | width: 100%;
798 | max-width: 380px;
799 | margin: 0 auto;
800 | text-align: left
801 | }
802 |
803 | .contact-page-form>h3 {
804 | font-family: "Nexa Bold", sans-serif;
805 | font-size: 1.4rem;
806 | color: #444f60
807 | }
808 |
809 | .contact-page-form>p {
810 | color: #8e9baf;
811 | margin-bottom: 2rem
812 | }
813 |
814 | .contact-page-form .column {
815 | padding: .5rem
816 | }
817 |
818 | .contact-page-form .field>label {
819 | font-size: .95rem
820 | }
821 |
822 | .contact-page-form .field .control .input {
823 | font-size: 1.1rem
824 | }
825 |
826 | @media (max-width:768px) {
827 | #main-hero {
828 | max-height: 100vh;
829 | background-position-x: 55%!important
830 | }
831 | }
832 |
833 | /* @media only screen and (min-width:768px) and (max-width:1024px) and (orientation:portrait) {
834 | .columns {
835 | padding: 0 10%
836 | }
837 | } */
838 |
839 | @font-face {
840 | font-family: slick;
841 | src: url(/assets/fonts/slick.eot);
842 | src: url(/assets/fonts/slick.eot?#iefix) format("embedded-opentype"), url(/assets/fonts/slick.woff) format("woff"), url(/assets/fonts/slick.ttf) format("truetype"), url(/assets/fonts/slick.svg#slick) format("svg");
843 | font-weight: 400;
844 | font-style: normal
845 | }
--------------------------------------------------------------------------------
/assets/mapbox/mapbox-gl.min.css:
--------------------------------------------------------------------------------
1 | .mapboxgl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-highlight-color:transparent}.mapboxgl-canvas{position:absolute;left:0;top:0}.mapboxgl-map:-webkit-full-screen{width:100%;height:100%}.mapboxgl-canary{background-color:salmon}.mapboxgl-canvas-container.mapboxgl-interactive,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass{cursor:grab;-webkit-user-select:none;user-select:none}.mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer{cursor:pointer}.mapboxgl-canvas-container.mapboxgl-interactive:active,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active{cursor:grabbing}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas{touch-action:pan-x pan-y}.mapboxgl-canvas-container.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:pinch-zoom}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:none}.mapboxgl-ctrl-bottom-left,.mapboxgl-ctrl-bottom-right,.mapboxgl-ctrl-top-left,.mapboxgl-ctrl-top-right{position:absolute;pointer-events:none;z-index:2}.mapboxgl-ctrl-top-left{top:0;left:0}.mapboxgl-ctrl-top-right{top:0;right:0}.mapboxgl-ctrl-bottom-left{bottom:0;left:0}.mapboxgl-ctrl-bottom-right{right:0;bottom:0}.mapboxgl-ctrl{clear:both;pointer-events:auto;transform:translate(0)}.mapboxgl-ctrl-top-left .mapboxgl-ctrl{margin:10px 0 0 10px;float:left}.mapboxgl-ctrl-top-right .mapboxgl-ctrl{margin:10px 10px 0 0;float:right}.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl{margin:0 0 10px 10px;float:left}.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl{margin:0 10px 10px 0;float:right}.mapboxgl-ctrl-group{border-radius:4px;background:#fff}.mapboxgl-ctrl-group:not(:empty){box-shadow:0 0 0 2px rgba(0,0,0,.1)}@media (-ms-high-contrast:active){.mapboxgl-ctrl-group:not(:empty){box-shadow:0 0 0 2px ButtonText}}.mapboxgl-ctrl-group button{width:29px;height:29px;display:block;padding:0;outline:0;border:0;box-sizing:border-box;background-color:transparent;cursor:pointer}.mapboxgl-ctrl-group button+button{border-top:1px solid #ddd}.mapboxgl-ctrl button .mapboxgl-ctrl-icon{display:block;width:100%;height:100%;background-repeat:no-repeat;background-position:50%}@media (-ms-high-contrast:active){.mapboxgl-ctrl-icon{background-color:transparent}.mapboxgl-ctrl-group button+button{border-top:1px solid ButtonText}}.mapboxgl-ctrl-attrib-button:focus,.mapboxgl-ctrl-group button:focus{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl button:disabled{cursor:not-allowed}.mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon{opacity:.25}.mapboxgl-ctrl button:not(:disabled):hover{background-color:rgba(0,0,0,.05)}.mapboxgl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}.mapboxgl-ctrl-group button:focus:first-child{border-radius:4px 4px 0 0}.mapboxgl-ctrl-group button:focus:last-child{border-radius:0 0 4px 4px}.mapboxgl-ctrl-group button:focus:only-child{border-radius:inherit}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23999'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23aaa'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-waiting .mapboxgl-ctrl-icon{animation:mapboxgl-spin 2s linear infinite}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23999'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23666'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}}@keyframes mapboxgl-spin{0%{transform:rotate(0)}to{transform:rotate(1turn)}}a.mapboxgl-ctrl-logo{width:88px;height:23px;margin:0 0 -4px -4px;display:block;background-repeat:no-repeat;cursor:pointer;overflow:hidden;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg opacity='.3' stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg opacity='.9' fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}a.mapboxgl-ctrl-logo.mapboxgl-compact{width:23px}@media (-ms-high-contrast:active){a.mapboxgl-ctrl-logo{background-color:transparent;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){a.mapboxgl-ctrl-logo{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23fff' stroke-width='3' fill='%23fff'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/svg%3E")}}.mapboxgl-ctrl.mapboxgl-ctrl-attrib{padding:0 5px;background-color:hsla(0,0%,100%,.5);margin:0}@media screen{.mapboxgl-ctrl-attrib.mapboxgl-compact{min-height:20px;padding:2px 24px 2px 0;margin:10px;position:relative;background-color:#fff;border-radius:12px}.mapboxgl-ctrl-attrib.mapboxgl-compact-show{padding:2px 28px 2px 8px;visibility:visible}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact-show,.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact-show{padding:2px 8px 2px 28px;border-radius:12px}.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-inner{display:none}.mapboxgl-ctrl-attrib-button{display:none;cursor:pointer;position:absolute;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E");background-color:hsla(0,0%,100%,.5);width:24px;height:24px;box-sizing:border-box;border-radius:12px;outline:0;top:0;right:0;border:0}.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl-attrib-button,.mapboxgl-ctrl-top-left .mapboxgl-ctrl-attrib-button{left:0}.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-button,.mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-inner{display:block}.mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-button{background-color:rgba(0,0,0,.05)}.mapboxgl-ctrl-bottom-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;right:0}.mapboxgl-ctrl-top-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;right:0}.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;left:0}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;left:0}}@media screen and (-ms-high-contrast:active){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' fill='%23fff'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}@media screen and (-ms-high-contrast:black-on-white){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}.mapboxgl-ctrl-attrib a{color:rgba(0,0,0,.75);text-decoration:none}.mapboxgl-ctrl-attrib a:hover{color:inherit;text-decoration:underline}.mapboxgl-ctrl-attrib .mapbox-improve-map{font-weight:700;margin-left:2px}.mapboxgl-attrib-empty{display:none}.mapboxgl-ctrl-scale{background-color:hsla(0,0%,100%,.75);font-size:10px;border:2px solid #333;border-top:#333;padding:0 5px;color:#333;box-sizing:border-box}.mapboxgl-popup{position:absolute;top:0;left:0;display:flex;will-change:transform;pointer-events:none}.mapboxgl-popup-anchor-top,.mapboxgl-popup-anchor-top-left,.mapboxgl-popup-anchor-top-right{flex-direction:column}.mapboxgl-popup-anchor-bottom,.mapboxgl-popup-anchor-bottom-left,.mapboxgl-popup-anchor-bottom-right{flex-direction:column-reverse}.mapboxgl-popup-anchor-left{flex-direction:row}.mapboxgl-popup-anchor-right{flex-direction:row-reverse}.mapboxgl-popup-tip{width:0;height:0;border:10px solid transparent;z-index:1}.mapboxgl-popup-anchor-top .mapboxgl-popup-tip{align-self:center;border-top:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip{align-self:flex-start;border-top:none;border-left:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip{align-self:flex-end;border-top:none;border-right:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip{align-self:center;border-bottom:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip{align-self:flex-start;border-bottom:none;border-left:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip{align-self:flex-end;border-bottom:none;border-right:none;border-top-color:#fff}.mapboxgl-popup-anchor-left .mapboxgl-popup-tip{align-self:center;border-left:none;border-right-color:#fff}.mapboxgl-popup-anchor-right .mapboxgl-popup-tip{align-self:center;border-right:none;border-left-color:#fff}.mapboxgl-popup-close-button{position:absolute;right:0;top:0;border:0;border-radius:0 3px 0 0;cursor:pointer;background-color:transparent}.mapboxgl-popup-close-button:hover{background-color:rgba(0,0,0,.05)}.mapboxgl-popup-content{position:relative;background:#fff;border-radius:3px;box-shadow:0 1px 2px rgba(0,0,0,.1);padding:10px 10px 15px;pointer-events:auto}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content{border-top-left-radius:0}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content{border-top-right-radius:0}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content{border-bottom-left-radius:0}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content{border-bottom-right-radius:0}.mapboxgl-popup-track-pointer{display:none}.mapboxgl-popup-track-pointer *{pointer-events:none;user-select:none}.mapboxgl-map:hover .mapboxgl-popup-track-pointer{display:flex}.mapboxgl-map:active .mapboxgl-popup-track-pointer{display:none}.mapboxgl-marker{position:absolute;top:0;left:0;will-change:transform;opacity:1;transition:opacity .2s}.mapboxgl-marker-occluded{opacity:.2}.mapboxgl-user-location-dot,.mapboxgl-user-location-dot:before{background-color:#1da1f2;width:15px;height:15px;border-radius:50%}.mapboxgl-user-location-dot:before{content:"";position:absolute;animation:mapboxgl-user-location-dot-pulse 2s infinite}.mapboxgl-user-location-dot:after{border-radius:50%;border:2px solid #fff;content:"";height:19px;left:-2px;position:absolute;top:-2px;width:19px;box-sizing:border-box;box-shadow:0 0 3px rgba(0,0,0,.35)}@keyframes mapboxgl-user-location-dot-pulse{0%{transform:scale(1);opacity:1}70%{transform:scale(3);opacity:0}to{transform:scale(1);opacity:0}}.mapboxgl-user-location-dot-stale{background-color:#aaa}.mapboxgl-user-location-dot-stale:after{display:none}.mapboxgl-user-location-accuracy-circle{background-color:rgba(29,161,242,.2);width:1px;height:1px;border-radius:100%}.mapboxgl-crosshair,.mapboxgl-crosshair .mapboxgl-interactive,.mapboxgl-crosshair .mapboxgl-interactive:active{cursor:crosshair}.mapboxgl-boxzoom{position:absolute;top:0;left:0;width:0;height:0;background:#fff;border:2px dotted #202020;opacity:.5}@media print{.mapbox-improve-map{display:none}}
--------------------------------------------------------------------------------