├── .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 | echoIP 4 |

echoIP

5 |

显示客户端IP、查询IP详细信息

6 |

7 |

8 | license 9 | version 10 | total-lines 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 | ![echoIP-cli](https://pic.dnomd343.top/images/X4F.png) 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 | ![echoIP-web](https://pic.dnomd343.top/images/FR5.png) 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 |
38 |
39 | 40 | 41 | 42 |
43 |
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 |
130 |
131 |
132 |
133 |

134 | echoIP 135 |

136 |
137 |
138 |
139 |
140 | 141 |
142 |
143 |
144 |
145 |
146 |
147 | 148 |
149 |
150 |
151 |
152 |
153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 |
IP
AS
City
Region
Country
Timezone
Location
ISP
Scope
Detail
195 |
196 |
197 |
198 |
199 | 200 |
201 |
202 |
203 |
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}} --------------------------------------------------------------------------------