commit 0c7237da457dd944ee3e49d87aa18e7194f3ec8b Author: 李志强 <357099073@qq.com> Date: Mon Jul 14 14:48:36 2025 +0800 first commit diff --git a/.env b/.env new file mode 100644 index 0000000..86e5f26 --- /dev/null +++ b/.env @@ -0,0 +1,17 @@ +APP_DEBUG = false + +[APP] +DEFAULT_TIMEZONE = Asia/Shanghai + +[DATABASE] +TYPE = mysql +HOSTNAME = 127.0.0.1 +DATABASE = ruankao +USERNAME = ruankao +PASSWORD = 123456 +HOSTPORT = 3306 +CHARSET = utf8 +DEBUG = true + +[LANG] +default_lang = zh-cn \ No newline at end of file diff --git a/.example.env b/.example.env new file mode 100644 index 0000000..c27f74c --- /dev/null +++ b/.example.env @@ -0,0 +1 @@ +APP_DEBUG = true [APP] DEFAULT_TIMEZONE = Asia/Shanghai [DATABASE] TYPE = mysql HOSTNAME = 127.0.0.1 DATABASE = test USERNAME = username PASSWORD = password HOSTPORT = 3306 CHARSET = utf8 DEBUG = true [LANG] default_lang = zh-cn \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..625fcdf --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/.idea +/.vscode +/vendor +runtime +*.log +config/database.php diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36f7b6f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +sudo: false + +language: php + +branches: + only: + - stable + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: + - composer install --no-dev --no-interaction --ignore-platform-reqs + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . + - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" + - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . + +script: + - php think unit + +deploy: + provider: releases + api_key: + secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= + file: + - ThinkPHP_Core.zip + - ThinkPHP_Full.zip + skip_cleanup: true + on: + tags: true diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..15dc9c3 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,41 @@ +云泽网商业软件许可证协议 +版本 1.0 - 2025年 + +版权所有 (c) 2025 云泽网。保留所有权利。 + +重要提示:在使用本软件前,请仔细阅读以下条款和条件。下载、安装或使用本软件即表示您同意受本协议的约束。 + +1. 授权范围 +1.1 本软件仅供评估和测试目的使用,不得用于任何商业用途。 +1.2 任何商业用途(包括但不限于生产环境使用、商业服务提供、商业产品集成等)必须获得云泽网的书面授权许可。 + +2. 限制条款 +2.1 禁止移除、修改或隐藏任何版权声明、商标标识或专有权利通知。 +2.2 禁止将本软件用于生产环境或任何可能产生商业收益的场景。 +2.3 禁止对本软件进行转售、分发、出租、出借或再许可。 +2.4 禁止对本软件进行反向工程、反编译或反汇编。 + +3. 商业授权 +3.1 商业用户需通过官方渠道购买商业授权许可证。 +3.2 商业授权将提供: + - 合法的商业使用权利 + - 正式的技术支持服务 + - 软件更新和维护 +3.3 授权购买请联系:357099073@qq.com +3.4 官方网站:https://www.yunzer.cn + +4. 免责声明 +4.1 本软件按"原样"提供,不作任何明示或暗示的保证。 +4.2 在任何情况下,云泽网不对因使用或不能使用本软件所发生的特殊、附带、间接或结果性损害承担任何责任。 + +5. 违约与终止 +5.1 如违反本协议任何条款,您的使用权利将自动终止。 +5.2 云泽网保留对任何未经授权商业使用行为追究法律责任的权利。 + +6. 其他条款 +6.1 本协议受中华人民共和国法律管辖。 +6.2 云泽网保留随时修改本协议条款的权利,修改后的协议将在官方网站公布后生效。 + +如需商业使用授权,请通过以下方式联系我们: +电子邮件:357099073@qq.com +官方网站:https://www.yunzer.cn \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dee8da3 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# phpAdmin 后台管理 (admin-thinkphp-Layui2) + +- [官网:http://www.yunzer.cn](http://www.yunzer.cn) +- [演示:admin-thinkphp-layui](admin-thinkphp-layui) + +## 一、介绍 + +- phpAdmin 后台管理 + +- [admin-thinkphp-layui] + - 后端框架:Thinkphp6.1 + - Layui2.8 + +## 二、安装教程 + +- [根据 Thinkphp6 安装](https://www.kancloud.cn/manual/thinkphp6_0/1037479) +- 伪静态 +``` +location ~* (runtime|application)/{ + return 403; +} +location / { + if (!-e $request_filename){ + rewrite ^(.*)$ /index.php?s=$1 last; break; + } +} +``` + +## 三、使用说明 + +- 1、域名指向 public 目录下 +- 2、后台管理系统访问网址:http://www.xxx.com/index.php/bews/index/index + +## 四、鸣谢 + +- [PHP 中文网](https://www.php.cn) +- [Thinkphp](http://www.thinkphp.cn) +- [Layui](https://layui.dev) + +## 五、技术支持 + +- QQ:357099073 diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/app/AppApi.php b/app/AppApi.php new file mode 100644 index 0000000..ffdbe81 --- /dev/null +++ b/app/AppApi.php @@ -0,0 +1,55 @@ + '系统错误,请稍后重试', + #############系统后台使用############## + '90000001' => '管理员账户不能为空', + '90000002' => '真实姓名不能为空', + '90000003' => '手机号不能为空', + '90000004' => '角色不能为空', + '90000005' => '密码不能为空', + '90000006' => '管理员账户要用邮箱', + '90000007' => '管理员账户已存在', + '90000008' => '角色名称不能为空', + '90000009' => '导航名称不能为空', + '90000010' => '导航下还有数据', + '90000011' => '请选择类型', + '90000012' => '内部代码不能为空', + '90000013' => '链接地址不能为空', + '90000015' => '按钮名称不能为空', + '90000029' => '管理员账户不存在', + '90000030' => '管理员已被禁用', + '90000031' => '密码不正确', + + '91000001' => '添加失败', + '91000002' => '修改失败', + '91000003' => '删除失败', + '91000004' => '未改变数据', + '91000005' => '未查询到数据', + '91000006' => '失败', + ); + return $array[$code]; + } +} \ No newline at end of file diff --git a/app/AppService.php b/app/AppService.php new file mode 100644 index 0000000..7d58313 --- /dev/null +++ b/app/AppService.php @@ -0,0 +1,39 @@ +where('status', '<>', 3); + + // 分类筛选 + if (!empty($category)) { + // 先获取分类ID + $cateInfo = ArticlesCategory::where('name', $category) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + + if ($cateInfo) { + $query = $query->where('cate', $cateInfo['id']); + } + } + + // 标题搜索 + if (!empty($title)) { + $query = $query->where('title', 'like', '%'.$title.'%'); + } + + // 作者搜索 + if (!empty($author)) { + $query = $query->where('author', 'like', '%'.$author.'%'); + } + + // 获取总记录数 + $count = $query->count(); + + // 获取分页数据 + $lists = $query->order('id DESC') + ->page($page, $limit) + ->select() + ->each(function ($item) { + // 获取分类信息 + $cateInfo = ArticlesCategory::where('id', $item['cate']) + ->field('name, image') + ->find(); + + // 设置分类名称 + $item['cate'] = $cateInfo ? $cateInfo['name'] : ''; + + // 如果文章没有图片,使用分类的图片 + if (empty($item['image']) && $cateInfo && !empty($cateInfo['image'])) { + $item['image'] = $cateInfo['image']; + } + + // 格式化时间 + $item['create_time'] = is_numeric($item['create_time']) ? date('Y-m-d H:i:s', $item['create_time']) : $item['create_time']; + $item['publishdate'] = is_numeric($item['publishdate']) ? date('Y-m-d H:i:s', $item['publishdate']) : ''; + + return $item; + }); + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'count' => $count, + 'data' => $lists + ]); + } else { + // 获取所有分类并构建父子结构 + $allCategories = ArticlesCategory::where('delete_time', null) + ->where('status', 1) + ->order('sort asc, id asc') + ->select() + ->toArray(); + + $categories = []; + foreach ($allCategories as $category) { + if ($category['cid'] == 0) { + $category['children'] = []; + foreach ($allCategories as $subCategory) { + if ($subCategory['cid'] == $category['id']) { + $category['children'][] = $subCategory; + } + } + $categories[] = $category; + } + } + + View::assign([ + 'categories' => $categories + ]); + return View::fetch(); + } + } + + // 添加文章 + public function add() + { + if (Request::isPost()) { + $data = [ + 'title' => input('post.title'), + 'cate' => input('post.cate'), + 'image' => input('post.image'), + 'content' => input('post.content'), + 'author' => input('post.author'), + 'desc' => input('post.desc'), + 'status' => input('post.status', 2), + 'publishdate' => time(), + 'create_time' => time() + ]; + + $insert = Articles::insert($data); + if (empty($insert)) { + Log::record('添加文章', 0, '添加文章失败', '文章管理'); + return json(['code' => 1, 'msg' => '添加失败', 'data' => []]); + } + Log::record('添加文章', 1, '', '文章管理'); + return json(['code' => 0, 'msg' => '添加成功', 'data' => []]); + } else { + $lists = Articles::order('id DESC') + ->select() + ->each(function ($item, $key) { + $item['create_time'] = time(); + return $item; + }); + View::assign([ + 'lists' => $lists + ]); + return View::fetch(); + } + } + + // 编辑文章 + public function edit() + { + if (Request::isPost()) { + $id = input('get.id'); + $data = [ + 'title' => input('post.title'), + 'cate' => input('post.cate'), + 'image' => input('post.image'), + 'content' => input('post.content'), + 'author' => input('post.author'), + 'desc' => input('post.desc'), + 'status' => input('post.status', 2), + 'update_time' => time() + ]; + + $update = Articles::where('id', $id)->update($data); + if ($update === false) { + Log::record('编辑文章', 0, '编辑文章失败', '文章管理'); + return json(['code' => 1, 'msg' => '更新失败', 'data' => []]); + } + Log::record('编辑文章', 1, '', '文章管理'); + return json(['code' => 0, 'msg' => '更新成功', 'data' => []]); + } else { + $id = input('get.id'); + $info = Articles::where('id', $id)->find(); + if ($info === null) { + return json(['code' => 1, 'msg' => '文章不存在', 'data' => []]); + } + + $cates = ArticlesCategory::where('delete_time', null) + ->where('status', 1) + ->order('sort asc, id asc') + ->select() + ->toArray(); + + $info['content'] = !empty($info['content']) ? htmlspecialchars_decode(str_replace(["\r\n", "\r", "\n"], '', addslashes($info['content']))) : ''; + + $currentCate = ArticlesCategory::where('id', $info['cate']) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + $info['cate_name'] = $currentCate ? $currentCate['name'] : ''; + + View::assign([ + 'info' => $info, + 'cates' => $cates + ]); + return View::fetch(); + } + } + + // 删除文章 + public function delete() + { + $id = input('post.id'); + $data = [ + 'delete_time' => time(), + ]; + $delete = Articles::where('id', $id)->update($data); + if ($delete === false) { + Log::record('删除文章', 0, '删除文章失败', '文章管理'); + return json(['code' => 1, 'msg' => '删除失败', 'data' => []]); + } + Log::record('删除文章', 1, '', '文章管理'); + return json(['code' => 0, 'msg' => '删除成功', 'data' => []]); + } + + // 文章分类 + public function articlecate() + { + if (Request::isPost()) { + $lists = ArticlesCategory::where('delete_time', null) + ->where('status', 1) + ->order('sort asc, id asc') + ->select() + ->toArray(); + + // 构建树形结构 + $tree = []; + foreach ($lists as $item) { + if ($item['cid'] == 0) { + $node = [ + 'id' => $item['id'], + 'title' => $item['name'], + 'children' => [] + ]; + + // 查找子分类 + foreach ($lists as $subItem) { + if ($subItem['cid'] == $item['id']) { + $node['children'][] = [ + 'id' => $subItem['id'], + 'title' => $subItem['name'], + 'children' => [] + ]; + } + } + + $tree[] = $node; + } + } + + return json(['code' => 0, 'msg' => '获取成功', 'data' => $tree]); + } + + // 非 POST 请求返回视图 + return View::fetch(); + } + + //获取分类结构 + public function getcate() + { + // 获取所有分类 + $lists = ArticlesCategory::where('delete_time', null) + ->where('status', 1) + ->order('sort asc, id asc') + ->select() + ->toArray(); + + // 构建父子结构 + $tree = []; + foreach ($lists as $item) { + if ($item['cid'] == 0) { + // 顶级分类 + $tree[] = $item; + } else { + // 子分类 + foreach ($tree as &$parent) { + if ($parent['id'] == $item['cid']) { + if (!isset($parent['children'])) { + $parent['children'] = []; + } + $parent['children'][] = $item; + break; + } + } + } + } + + return json(['code' => 0, 'msg' => '获取成功', 'data' => $tree]); + } + + // 添加文章分类 + public function cateadd() + { + if (Request::isPost()) { + $data = [ + 'name' => input('post.name'), + 'image' => input('post.image'), + 'cid' => input('post.cid'), + 'sort' => input('post.sort', 0), + 'status' => input('post.status', 1), + 'create_time' => time() + ]; + + $insert = ArticlesCategory::insert($data); + if (empty($insert)) { + Log::record('添加文章分类', 0, '添加文章分类失败', '文章分类'); + return json(['code' => 1, 'msg' => '添加失败', 'data' => []]); + } + Log::record('添加文章分类', 1, '', '文章分类'); + return json(['code' => 0, 'msg' => '添加成功', 'data' => []]); + } else { + // 获取所有可选的父级分类 + $parentCategories = ArticlesCategory::where('delete_time', null) + ->where('status', 1) + ->where('cid', 0) + ->field('id, name') + ->select() + ->toArray(); + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'parentOptions' => $parentCategories + ] + ]); + } + } + + //编辑文章分类 + public function cateedit() + { + if (Request::isPost()) { + $data = [ + 'id' => input('post.id'), + 'name' => input('post.name'), + 'image' => input('post.image'), + 'cid' => input('post.cid'), + 'sort' => input('post.sort', 0), + 'status' => input('post.status', 1), + 'update_time' => time() + ]; + + $update = ArticlesCategory::where('id', $data['id']) + ->update($data); + + if ($update === false) { + Log::record('编辑文章分类', 0, '更新文章分类失败', '文章分类'); + return json(['code' => 1, 'msg' => '更新失败', 'data' => []]); + } + Log::record('编辑文章分类', 1, '', '文章分类'); + return json(['code' => 0, 'msg' => '更新成功', 'data' => []]); + } else { + $id = input('get.id'); + $info = ArticlesCategory::where('id', $id)->find(); + + // 获取所有可选的父级分类 + $parentCategories = ArticlesCategory::where('delete_time', null) + ->where('status', 1) + ->where('id', '<>', $id) // 排除自己 + ->where(function ($query) use ($id) { + // 排除自己的所有子分类 + $query->where('cid', '<>', $id); + }) + ->field('id, name, cid') + ->select() + ->toArray(); + + // 构建父级分类选项 + $parentOptions = []; + foreach ($parentCategories as $category) { + if ($category['cid'] == 0) { + $parentOptions[] = [ + 'id' => $category['id'], + 'name' => $category['name'] + ]; + } + } + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'info' => $info, + 'parentOptions' => $parentOptions + ] + ]); + } + } + + //删除文章分类 + public function catedel() + { + $id = input('post.id'); + + // 检查是否有子分类 + $hasChildren = ArticlesCategory::where('cid', $id) + ->where('delete_time', null) + ->find(); + + if ($hasChildren) { + Log::record('删除文章分类', 0, '该分类下有子分类,无法删除', '文章分类'); + return json(['code' => 1, 'msg' => '该分类下有子分类,无法删除', 'data' => []]); + } + + $delete = ArticlesCategory::where('id', $id) + ->update(['delete_time' => time()]); + + if ($delete === false) { + Log::record('删除文章分类', 0, '删除文章分类失败', '文章分类'); + return json(['code' => 1, 'msg' => '删除失败', 'data' => []]); + } + Log::record('删除文章分类', 1, '', '文章分类'); + return json(['code' => 0, 'msg' => '删除成功', 'data' => []]); + } + + //统计文章数量 + public function counts() { + try { + // 获取文章总数 + $total = Articles::where('delete_time', null) + ->where('status', '<>', 3) + ->count(); + + // 获取今日新增文章数 + $today = strtotime(date('Y-m-d')); + $todayNew = Articles::where('delete_time', null) + ->where('status', '<>', 3) + ->where('create_time', '>=', $today) + ->count(); + + // 获取最近7天的文章数据 + $dates = []; + $counts = []; + $totalCounts = []; // 存储每天的总文章数 + $totalSoFar = 0; // 用于累计总文章数 + + for ($i = 6; $i >= 0; $i--) { + $date = date('Y-m-d', strtotime("-$i days")); + $start = strtotime($date); + $end = $start + 86400; + + // 获取当天新增文章数 + $count = Articles::where('delete_time', null) + ->where('status', '<>', 3) + ->where('create_time', '>=', $start) + ->where('create_time', '<', $end) + ->count(); + + // 获取截至当天的总文章数 + $totalCount = Articles::where('delete_time', null) + ->where('status', '<>', 3) + ->where('create_time', '<', $end) + ->count(); + + $dates[] = $date; + $counts[] = $count; + $totalCounts[] = $totalCount; + } + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'total' => $total, + 'todayNew' => $todayNew, + 'dates' => $dates, + 'counts' => $counts, + 'totalCounts' => $totalCounts + ] + ]); + } catch (\Exception $e) { + return json([ + 'code' => 1, + 'msg' => '获取失败:' . $e->getMessage() + ]); + } + } +} \ No newline at end of file diff --git a/app/admin/controller/Base.php b/app/admin/controller/Base.php new file mode 100644 index 0000000..b912c9a --- /dev/null +++ b/app/admin/controller/Base.php @@ -0,0 +1,181 @@ +config = $YzAdminConfig->getAll(); + # 获取账户,账户判断 + $this->adminId = Cookie::get('admin_id'); + if (empty($this->adminId)) { + header('Location:' . $this->config['admin_route'] . 'Login/index'); + exit; + } + $this->aUser = Db::table('yz_admin_user')->where('uid', $this->adminId)->find(); + + if (empty($this->aUser)) { + Cookie::delete('admin_id'); + $this->error('管理员账户不存在'); + } + if ($this->aUser['status'] != 1) { + Cookie::delete('admin_id'); + $this->error('管理员已被禁用'); + } + # 获取用户组权限 + $group = Db::table('yz_admin_user_group')->where(['group_id' => $this->aUser['group_id']])->find(); + if (empty($group)) { + $this->error('对不起,您没有权限'); + } + # 获取当前链接,查询是否有权限 + $controller = request()->controller(); + $action = request()->action(); + $key = $controller . '/' . $action; + View::assign([ + 'aUser' => $this->aUser, + 'config' => $this->config + ]); + } + /** + * 返回json对象 + */ + protected function returnCode($code, $data = [], $count = 10) + { + header('Content-type:application/json'); + if ($code == 0) { + $arr = array( + 'code' => $code, + 'msg' => '操作成功', + 'count' => $count, + 'data' => $data + ); + } else if ($code >= 1 && $code <= 100) { + $arr = array( + 'code' => $code, + 'msg' => $data + ); + } else { + $appapi = new AppApi(); + $arr = array( + 'code' => $code, + 'msg' => $appapi::errorTip($code) + ); + } + echo json_encode($arr); + if ($code != 0) { + exit; + } + } + + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @return void + */ + protected function success($msg = '') + { + $result = [ + 'code' => 1, + 'msg' => $msg + ]; + + $type = $this->getResponseType(); + if ($type == 'html') { + $response = view(Config::get('app.dispatch_success_tmpl'), $result); + } else if ($type == 'json') { + $response = json($result); + } + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @return void + */ + protected function error($msg = '') + { + $result = [ + 'code' => 0, + 'msg' => $msg + ]; + $response = view(Config::get('app.dispatch_error_tmpl'), $result); + throw new HttpResponseException($response); + } + + /** + * 获取当前的response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + return Request::isJson() || Request::isAjax() ? 'json' : 'html'; + } + + public function initialize(App $app) + { + $this->app = $app; + $this->request = $this->app->request; + + // 检查是否是直接访问具体页面 + $controller = $this->request->controller(); + $action = $this->request->action(); + + // 如果不是访问index控制器,且不是通过iframe加载,且不是ajax请求 + if ( + $controller != 'Index' && + !$this->request->isAjax() && + !$this->request->header('X-Requested-With') && + !$this->request->param('iframe') + ) { // 添加iframe参数检查 + + // 重定向到index页面,并带上当前页面参数 + $currentUrl = $controller . '/' . $action; + redirect(url('index/index', ['page' => $currentUrl]))->send(); + exit; + } + } +} \ No newline at end of file diff --git a/app/admin/controller/BaseController.php b/app/admin/controller/BaseController.php new file mode 100644 index 0000000..52a4f16 --- /dev/null +++ b/app/admin/controller/BaseController.php @@ -0,0 +1,149 @@ +app = $app; + $this->request = $this->app->request; + + // 控制器初始化 + $this->initialize($app); + } + + /** + * 初始化 + * @access public + * @param App $app 应用对象 + */ + public function initialize(App $app) + { + // 注册控制器映射 + $this->registerControllerMap(); + } + + /** + * 注册控制器映射 + */ + protected function registerControllerMap() + { + // 获取当前控制器类名 + $className = get_class($this); + // 获取不带命名空间的类名 + $shortName = substr($className, strrpos($className, '\\') + 1); + // 移除Controller后缀 + $mapName = str_replace('Controller', '', $shortName); + + // 调试信息 + trace("Controller Mapping: {$mapName} => {$className}", 'debug'); + + // 注册控制器映射 + $this->app->route->setControllerMap($mapName, $className); + } + + /** + * 获取控制器名称(移除Controller后缀) + * @return string + */ + public function getControllerName() + { + $className = get_class($this); + $className = substr($className, strrpos($className, '\\') + 1); + return str_replace('Controller', '', $className); + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array|string|true + * @throws ValidateException + */ + protected function validate(array $data, $validate, array $message = [], bool $batch = false) + { + if (is_array($validate)) { + $v = new Validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + $v = new $class(); + if (!empty($scene)) { + $v->scene($scene); + } + } + + $v->message($message); + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + return $v->failException(true)->check($data); + } + +} \ No newline at end of file diff --git a/app/admin/controller/IndexController.php b/app/admin/controller/IndexController.php new file mode 100644 index 0000000..0c14dd8 --- /dev/null +++ b/app/admin/controller/IndexController.php @@ -0,0 +1,518 @@ +$this->aUser['group_id']]; + $role = AdminUserGroup::where($where)->find(); + if($role){ + $role['rights'] = (isset($role['rights']) && $role['rights']) ? json_decode($role['rights'],true) : []; + } + if($role['rights']){ + $where = [ + ['smid','in',implode(',',$role['rights']) ], + ['status','=',1] + ]; + // 获取所有菜单 + $menus = AdminSysMenu::order('type,sort desc')->where($where)->select()->toArray(); + + // 构建树形结构菜单 + $menuTree = []; + $menuMap = []; + + // 先将所有菜单项映射到一个关联数组中 + foreach($menus as $item){ + $item['children'] = []; + $menuMap[$item['smid']] = $item; + } + + // 构建树形结构 + foreach($menus as $item){ + if($item['parent_id'] == 0){ + // 顶级菜单 + $menuTree[$item['smid']] = &$menuMap[$item['smid']]; + }else{ + // 子菜单,添加到父菜单的children数组中 + if(isset($menuMap[$item['parent_id']])){ + $menuMap[$item['parent_id']]['children'][] = &$menuMap[$item['smid']]; + } + } + } + + $menu = $menuTree; + } + + View::assign([ + 'role' => $role, + 'menu' => $menu + ]); + return View::fetch(); + } + # 欢迎页面 + public function welcome(){ + try { + // 获取最近7天的日期 + $dates = []; + for ($i = 6; $i >= 0; $i--) { + $dates[] = date('Y-m-d', strtotime("-$i day")); + } + + // 初始化数据数组 + $visitData = []; + $userData = []; + $resourceData = []; + $articleData = []; + + // 直接查询每天的数据 + foreach ($dates as $date) { + $dayStats = Db::name('daily_stats') + ->where('date', $date) + ->find(); + + // 访问数据 + $visitData[] = [ + 'date' => $date, + 'visits' => $dayStats ? intval($dayStats['daily_visits']) : 0, + 'uv' => $dayStats ? intval($dayStats['unique_visitors']) : 0 + ]; + + // 用户数据 + $userData[] = [ + 'date' => $date, + 'total' => $dayStats ? intval($dayStats['total_users']) : 0, + 'new' => $dayStats ? intval($dayStats['new_users']) : 0 + ]; + + // 资源数据 + $resourceData[] = [ + 'date' => $date, + 'total' => $dayStats ? intval($dayStats['total_resources']) : 0, + 'new' => $dayStats ? intval($dayStats['daily_resources']) : 0, + 'downloads' => $dayStats ? intval($dayStats['resource_downloads']) : 0 + ]; + + // 文章数据 + $articleData[] = [ + 'date' => $date, + 'total' => $dayStats ? intval($dayStats['total_articles']) : 0, + 'new' => $dayStats ? intval($dayStats['daily_articles']) : 0, + 'views' => $dayStats ? intval($dayStats['article_views']) : 0 + ]; + } + + // 获取今日统计数据 + $today = date('Y-m-d'); + $todayStats = Db::name('daily_stats') + ->where('date', $today) + ->find(); + + // 获取最近的操作日志 + $recentActivities = Db::name('logs_operation') + ->field('operation_time, module, operation') + ->order('operation_time DESC') + ->limit(5) + ->select() + ->each(function($item) { + $item['content'] = date('Y年m月d日 H:i:s', strtotime($item['operation_time'])) . ' 在 ' . + ($item['module'] ?: '未知模块') . ' ' . + ($item['operation'] ?: '未知操作'); + $item['icon'] = $this->getActivityIcon($item['module'] ?: '其他'); + return $item; + }); + + // 处理图表数据 + $chartData = [ + 'visitTrend' => [ + 'dates' => array_map(function($item) { return date('m-d', strtotime($item['date'])); }, $visitData), + 'visits' => array_column($visitData, 'visits'), + 'uvs' => array_column($visitData, 'uv') + ], + 'userGrowth' => [ + 'dates' => array_map(function($item) { return date('m-d', strtotime($item['date'])); }, $userData), + 'newUsers' => array_column($userData, 'new'), + 'totalUsers' => array_column($userData, 'total') + ], + 'resourceStats' => [ + 'dates' => array_map(function($item) { return date('m-d', strtotime($item['date'])); }, $resourceData), + 'newResources' => array_column($resourceData, 'new'), + 'totalResources' => array_column($resourceData, 'total'), + 'downloads' => array_column($resourceData, 'downloads') + ], + 'articleStats' => [ + 'dates' => array_map(function($item) { return date('m-d', strtotime($item['date'])); }, $articleData), + 'newArticles' => array_column($articleData, 'new'), + 'totalArticles' => array_column($articleData, 'total'), + 'views' => array_column($articleData, 'views') + ] + ]; + + // 传递给视图 + View::assign([ + 'todayStats' => $todayStats ?: [ + 'total_users' => 0, + 'new_users' => 0, + 'total_visits' => 0, + 'daily_visits' => 0, + 'unique_visitors' => 0, + 'total_articles' => 0, + 'daily_articles' => 0, + 'article_views' => 0, + 'total_resources' => 0, + 'daily_resources' => 0, + 'resource_downloads' => 0 + ], + 'recentActivities' => $recentActivities, + 'chartData' => $chartData + ]); + + return View::fetch(); + } catch (\Exception $e) { + // 记录错误日志 + \think\facade\Log::error('获取统计数据失败:' . $e->getMessage()); + // 返回空数据 + View::assign([ + 'todayStats' => [ + 'total_users' => 0, + 'new_users' => 0, + 'total_visits' => 0, + 'daily_visits' => 0, + 'unique_visitors' => 0, + 'total_articles' => 0, + 'daily_articles' => 0, + 'article_views' => 0, + 'total_resources' => 0, + 'daily_resources' => 0, + 'resource_downloads' => 0 + ], + 'recentActivities' => [], + 'chartData' => [ + 'visitTrend' => ['dates' => [], 'visits' => [], 'uvs' => []], + 'userGrowth' => ['dates' => [], 'newUsers' => [], 'totalUsers' => []], + 'resourceStats' => ['dates' => [], 'newResources' => [], 'totalResources' => [], 'downloads' => []], + 'articleStats' => ['dates' => [], 'newArticles' => [], 'totalArticles' => [], 'views' => []] + ] + ]); + return View::fetch(); + } + } + + /** + * 根据操作类型获取对应的图标 + */ + private function getActivityIcon($type) + { + $icons = [ + '用户管理' => '👥', + '文章管理' => '📝', + '资源管理' => '📦', + '系统设置' => '⚙️', + '登录' => '🔑', + '退出' => '🚪', + '其他' => '📌' + ]; + + return $icons[$type] ?? '📌'; + } + + /** + * 格式化访问趋势数据 + */ + private function formatVisitTrendData($data) + { + $dates = []; + $visits = []; + $uvs = []; + + foreach ($data as $item) { + $dates[] = date('m-d', strtotime($item['date'])); + $visits[] = $item['daily_visits']; + $uvs[] = $item['unique_visitors']; + } + + return [ + 'dates' => $dates, + 'visits' => $visits, + 'uvs' => $uvs + ]; + } + + /** + * 格式化用户增长数据 + */ + private function formatUserGrowthData($data) + { + $dates = []; + $newUsers = []; + $totalUsers = []; + + foreach ($data as $item) { + $dates[] = date('m-d', strtotime($item['date'])); + $newUsers[] = $item['new_users']; + $totalUsers[] = $item['total_users']; + } + + return [ + 'dates' => $dates, + 'newUsers' => $newUsers, + 'totalUsers' => $totalUsers + ]; + } + + /** + * 格式化资源统计数据 + */ + private function formatResourceStatsData($data) + { + $dates = []; + $resources = []; + $downloads = []; + + foreach ($data as $item) { + $dates[] = date('m-d', strtotime($item['date'])); + $resources[] = $item['daily_resources']; + $downloads[] = $item['resource_downloads']; + } + + return [ + 'dates' => $dates, + 'resources' => $resources, + 'downloads' => $downloads + ]; + } + + /** + * 格式化文章统计数据 + */ + private function formatArticleStatsData($data) + { + $dates = []; + $articles = []; + $views = []; + + foreach ($data as $item) { + $dates[] = date('m-d', strtotime($item['date'])); + $articles[] = $item['daily_articles']; + $views[] = $item['article_views']; + } + + return [ + 'dates' => $dates, + 'articles' => $articles, + 'views' => $views + ]; + } + + /** + * 保存附件信息到数据库 + * @param string $name 文件名 + * @param int $type 附件类型 + * @param int $size 文件大小 + * @param string $src 文件路径 + * @return int 附件ID + */ + private function saveAttachment($name, $type, $size, $src) { + $data = [ + 'name' => $name, + 'type' => $type, + 'size' => $size, + 'src' => $src, + 'create_time' => time(), + 'update_time' => time() + ]; + return Attachments::insertGetId($data); + } + + # 图片上传 + public function upload_img(){ + // 获取上传的文件 + $file = request()->file(); + $files = request()->file('file'); + + // 检查是否有文件上传 + if(empty($file)){ + return json(['code'=>1, 'msg'=>'没有文件上传'])->send(); + } + + try { + // 验证上传的文件 + validate([ + 'image'=>'filesize:51200|fileExt:jpg,png,gif,jpeg' + ])->check($file); + + // 存储文件到public磁盘的uploads目录 + $info = Filesystem::disk('public')->putFile('uploads', $files); + + // 处理文件路径,统一使用正斜杠 + $info = str_replace("\\", "/", $info); + $img = '/storage/'.$info; + + // 保存附件信息 + $fileName = $files->getOriginalName(); + $fileSize = $files->getSize(); + $attachmentId = $this->saveAttachment($fileName, 1, $fileSize, $img); // 1: 图片 + + // 返回成功信息 + return json([ + 'code' => 0, + 'data' => $img, + 'url' => $this->config['admin_domain'].$img, + 'attachment_id' => $attachmentId + ])->send(); + + } catch (\think\exception\ValidateException $e) { + // 捕获验证异常并返回错误信息 + return json(['code'=>1, 'msg'=>$e->getMessage()])->send(); + } catch (\Exception $e) { + // 捕获其他异常 + return json(['code'=>1, 'msg'=>'上传失败:'.$e->getMessage()])->send(); + } + } + # 清除缓存 + public function clear(){ + $a = delete_dir_file(Env::get('runtime_path').'cache/'); + $b = delete_dir_file(Env::get('runtime_path').'temp/'); + if ($a || $b) { + $this->returnCode(0, '清除缓存成功'); + } else { + $this->returnCode(1, '清除缓存失败'); + } + } + # 文件上传 + public function upload_file(){ + $file = request()->file(); + $files = request()->file('file'); + if(empty($file)){ + return json(['code'=>1, 'msg'=>'没有文件上传'])->send(); + } + try { + // 只验证文件扩展名,不验证大小 + validate([ + 'file'=>'fileExt:doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,rar,7z' + ])->check($file); + + $info = Filesystem::disk('public')->putFile('uploads/files', $files); + + // 处理文件路径 + $info = str_replace("\\", "/", $info); + $filePath = '/storage/'.$info; + + // 保存附件信息 + $fileName = $files->getOriginalName(); + $fileSize = $files->getSize(); + $attachmentId = $this->saveAttachment($fileName, 2, $fileSize, $filePath); // 2: 文件 + + return json([ + 'code' => 0, + 'data' => [ + 'src' => $filePath, + 'attachment_id' => $attachmentId + ] + ])->send(); + } catch (\think\exception\ValidateException $e) { + return json(['code'=>1, 'msg'=>$e->getMessage()])->send(); + } catch (\Exception $e) { + return json(['code'=>1, 'msg'=>'上传失败:'.$e->getMessage()])->send(); + } + } + + # 视频上传 + public function upload_video(){ + $file = request()->file(); + $files = request()->file('file'); + if(empty($file)){ + return json(['code'=>1, 'msg'=>'没有文件上传'])->send(); + } + try { + validate(['video'=>'filesize:102400|fileExt:mp4,avi,mov,wmv,flv'])->check($file); + $info = Filesystem::disk('public')->putFile('uploads/videos', $files); + + // 处理文件路径 + $info = str_replace("\\", "/", $info); + $videoPath = '/storage/'.$info; + + // 保存附件信息 + $fileName = $files->getOriginalName(); + $fileSize = $files->getSize(); + $attachmentId = $this->saveAttachment($fileName, 3, $fileSize, $videoPath); // 3: 视频 + + return json([ + 'code' => 0, + 'data' => [ + 'src' => $videoPath, + 'attachment_id' => $attachmentId + ] + ])->send(); + } catch (\think\exception\ValidateException $e) { + return json(['code'=>1, 'msg'=>$e->getMessage()])->send(); + } + } + + # 音频上传 + public function upload_audio(){ + $file = request()->file(); + $files = request()->file('file'); + if(empty($file)){ + return json(['code'=>1, 'msg'=>'没有文件上传'])->send(); + } + try { + validate(['audio'=>'filesize:51200|fileExt:mp3,wav,ogg,m4a'])->check($file); + $info = Filesystem::disk('public')->putFile('uploads/audios', $files); + + // 处理文件路径 + $info = str_replace("\\", "/", $info); + $audioPath = '/storage/'.$info; + + // 保存附件信息 + $fileName = $files->getOriginalName(); + $fileSize = $files->getSize(); + $attachmentId = $this->saveAttachment($fileName, 4, $fileSize, $audioPath); // 4: 音频 + + return json([ + 'code' => 0, + 'data' => [ + 'src' => $audioPath, + 'attachment_id' => $attachmentId + ] + ])->send(); + } catch (\think\exception\ValidateException $e) { + return json(['code'=>1, 'msg'=>$e->getMessage()])->send(); + } + } + +} \ No newline at end of file diff --git a/app/admin/controller/LogController.php b/app/admin/controller/LogController.php new file mode 100644 index 0000000..349cdbc --- /dev/null +++ b/app/admin/controller/LogController.php @@ -0,0 +1,177 @@ +=', $startTime); + } + if ($endTime) { + $query = LogsLogin::where('login_time', '<=', $endTime); + } + + $count = LogsLogin::count(); + $list = LogsLogin::order('id desc') + ->page($page, $limit) + ->select(); + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'count' => $count, + 'data' => $list + ]); + } + + return View::fetch(); + } + + /** + * 操作日志列表 + */ + public function operation() + { + $params = input(); + $page = $params['page'] ?? 1; + $limit = $params['limit'] ?? 10; + + // 构建搜索条件 + $searchFields = [ + 'username' => ['field' => 'username', 'type' => 'like'], + 'module' => ['field' => 'module', 'type' => 'like'], + 'operation' => ['field' => 'operation', 'type' => 'like'], + 'status' => ['field' => 'status', 'type' => '='], + 'start_time' => ['field' => 'operation_time', 'type' => '>='], + 'end_time' => ['field' => 'operation_time', 'type' => '<='] + ]; + + $query = LogsOperation::where('1=1'); + + foreach ($searchFields as $param => $config) { + if (!empty($params[$param])) { + $value = $params[$param]; + if ($config['type'] === 'like') { + $value = "%{$value}%"; + } + $query = $query->where($config['field'], $config['type'], $value); + } + } + + $count = LogsOperation::where('1=1')->count(); + $list = LogsOperation::where('1=1') + ->order('id desc') + ->page($page, $limit) + ->select(); + + if (Request::isAjax()) { + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'count' => $count, + 'data' => $list + ]); + } + + return View::fetch(); + } + + /** + * 记录操作日志 + * @param string $operation 操作名称 + * @param int $status 状态 1成功 0失败 + * @param string $error_message 错误信息 + * @param string $module 模块名称 + */ + public static function record($operation, $status = 1, $error_message = '', $module = '') + { + $data = [ + 'username' => Cookie::get('admin_name') ?: '未知用户', + 'module' => $module ?: '系统管理', + 'operation' => $operation, + 'request_method' => Request::method(), + 'request_url' => Request::url(true), + 'request_params' => json_encode(Request::param(), JSON_UNESCAPED_UNICODE), + 'ip_address' => Request::ip(), + 'status' => $status, + 'error_message' => $error_message, + 'operation_time' => date('Y-m-d H:i:s'), + 'execution_time' => 0 + ]; + + LogsOperation::insert($data); + } + + /** + * 获取操作日志详情 + */ + public function getOperationDetail() + { + $id = input('id/d', 0); + if (!$id) { + return json(['code' => 1, 'msg' => '参数错误']); + } + + $info = LogsOperation::where('id', $id) + ->find(); + + if (!$info) { + return json(['code' => 1, 'msg' => '日志不存在']); + } + + // 格式化请求参数 + if (!empty($info['request_params'])) { + $info['request_params'] = json_decode($info['request_params'], true); + } + + return json(['code' => 0, 'msg' => '获取成功', 'data' => $info]); + } +} \ No newline at end of file diff --git a/app/admin/controller/LoginController.php b/app/admin/controller/LoginController.php new file mode 100644 index 0000000..775df20 --- /dev/null +++ b/app/admin/controller/LoginController.php @@ -0,0 +1,195 @@ +app = $app; + $this->config = new YzAdminConfig(); + } + + // 登录页面 + public function index() + { + # 获取配置 + $config = $this->config->getAll(); + View::assign([ + 'config' => $config + ]); + return View::fetch(); + } + + // 记录登录日志 + public function recordLoginLog($username, $status, $reason = '') + { + $data = [ + 'username' => $username, + 'ip_address' => Request::ip(), + 'location' => $this->getLocation(Request::ip()), + 'device_type' => $this->getDeviceType(), + 'user_agent' => Request::header('user-agent'), + 'login_status' => $status, + 'failure_reason' => $reason, + 'login_time' => date('Y-m-d H:i:s') + ]; + LogsLogin::create($data); + } + + // 获取IP地址位置 + public function getLocation($ip) + { + // 这里可以接入IP地址库或第三方API + return '未知'; + } + + // 获取设备类型 + public function getDeviceType() + { + $agent = Request::header('user-agent'); + if (preg_match('/(iPhone|iPod|Android|ios|iPad|Mobile)/i', $agent)) { + return '移动端'; + } + return 'PC端'; + } + + // 登录 + public function login() + { + if (Request::isPost()) { + $account = trim(input('post.account')); + if (empty($account)) { + $this->recordLoginLog($account, 0, '账号不能为空'); + return json(['code' => 1, 'msg' => '账号不能为空']); + } + $pattern = "/^([0-9A-Za-z-_.]+)@([0-9a-z]+.[a-z]{2,3}(.[a-z]{2})?)$/i"; + if (!preg_match($pattern, $account)) { + $this->recordLoginLog($account, 0, '邮箱格式不正确'); + return json(['code' => 1, 'msg' => '邮箱格式不正确']); + } + $password = trim(input('post.password')); + if (empty($password)) { + $this->recordLoginLog($account, 0, '密码不能为空'); + return json(['code' => 1, 'msg' => '密码不能为空']); + } + $code = trim(input('post.code')); + if ($code == '') { + $this->recordLoginLog($account, 0, '验证码不能为空'); + return json(['code' => 1, 'msg' => '验证码不能为空']); + } + if (!captcha_check($code)) { + $this->recordLoginLog($account, 0, '验证码错误'); + return json(['code' => 1, 'msg' => '验证码错误']); + } + $aUser = AdminUser::where('account', $account)->find(); + if (empty($aUser)) { + $this->recordLoginLog($account, 0, '账号不存在'); + return json(['code' => 1, 'msg' => '账号不存在']); + } + if ($aUser['status'] != 1) { + $this->recordLoginLog($account, 0, '账号已被禁用'); + return json(['code' => 1, 'msg' => '账号已被禁用']); + } + if ($aUser['password'] != md5($password)) { + $this->recordLoginLog($account, 0, '密码错误'); + return json(['code' => 1, 'msg' => '密码错误']); + } + $remember = input('post.remember'); + if (!empty($remember)) { + Cookie::set('admin_id', $aUser['uid'], 60 * 60 * 24 * 7); + Cookie::set('admin_name', $aUser['name'], 60 * 60 * 24 * 7); + } else { + Cookie::set('admin_id', $aUser['uid']); + Cookie::set('admin_name', $aUser['name']); + } + AdminUser::where('uid', $aUser['uid'])->update( + ['login_count' => $aUser['login_count'] + 1, 'update_time' => time()] + ); + // 记录登录成功日志 + $this->recordLoginLog($account, 1); + return json(['code' => 0, 'msg' => '登录成功', 'data' => []]); + } + } + + // 退出 + public function logout() + { + Cookie::delete('admin_id'); + Cookie::delete('admin_name'); + return json(['code' => 0, 'msg' => '退出成功', 'data' => []]); + } + + // 密码重置页面 + public function resetpwdindex() + { + return View::fetch('resetpwd'); + } + + //管理员密码重置 + public function resetpwd() + { + $account = trim(input('post.account')); + if (empty($account)) { + return json(['code' => 1, 'msg' => '账号不能为空']); + } + + $user = AdminUser::where('account', $account)->find(); + + if (!$user) { + return json(['code' => 1, 'msg' => '未找到该用户名']); + } + + // 使用md5进行密码加密处理 + $password = md5('123456'); + + try { + $res = AdminUser::where('account', $account) + ->update(['password' => $password]); + + if ($res === false) { + return json(['code' => 1, 'msg' => '数据库更新失败']); + } + + if ($res === 0) { + return json(['code' => 1, 'msg' => '密码未发生变化']); + } + + return json(['code' => 0, 'msg' => '密码重置成功', 'data' => []]); + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '系统错误:' . $e->getMessage()]); + } + } + +} \ No newline at end of file diff --git a/app/admin/controller/ResourcesController.php b/app/admin/controller/ResourcesController.php new file mode 100644 index 0000000..1c32770 --- /dev/null +++ b/app/admin/controller/ResourcesController.php @@ -0,0 +1,554 @@ + input('post.category'), + 'name' => input('post.name'), + 'uploader' => input('post.uploader') + ]; + $page = (int) input('post.page', 1); + $limit = (int) input('post.limit', 10); + + $query = Resource::where('delete_time', null) + ->where('status', 1); + + // 分类筛选 + if (!empty($params['category'])) { + $cateInfo = ResourceCategory::where('name', $params['category']) + ->where('delete_time', null) + ->where('status', 1) + ->field('id') + ->find(); + + if ($cateInfo) { + $query = $query->where('cate', (int) $cateInfo['id']); + } + } + + // 名称搜索 + if (!empty($params['name'])) { + $query = $query->where('name', 'like', '%' . $params['name'] . '%'); + } + + // 上传者搜索 + if (!empty($params['uploader'])) { + $query = $query->where('uploader', 'like', '%' . $params['uploader'] . '%'); + } + + $count = $query->count(); + + $lists = $query->order('id DESC') + ->page($page, $limit) + ->select() + ->each(function ($item) { + // 获取分类信息 + $cateInfo = ResourceCategory::where('id', (int) $item['cate']) + ->field('name, icon') + ->find(); + if ($cateInfo) { + $item['cate'] = $cateInfo['name']; + if (empty($item['icon']) && !empty($cateInfo['icon'])) { + $item['icon'] = $cateInfo['icon']; + } + } + $item['create_time'] = date('Y-m-d H:i:s', (int) $item['create_time']); + return $item; + }); + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'count' => $count, + 'data' => $lists + ]); + } else { + $allCategories = ResourceCategory::where('delete_time', null) + ->where('status', 1) + ->field('id, name, cid, icon') + ->order('sort asc, id asc') + ->select() + ->toArray(); + + $categories = $this->buildParentChild($allCategories); + + View::assign([ + 'categories' => $categories + ]); + return View::fetch(); + } + } + + // 添加资源 + public function add() + { + if (Request::isPost()) { + $data = [ + 'title' => input('post.title'), + 'cate' => input('post.cate'), + 'desc' => input('post.desc'), + 'icon' => input('post.icon'), + 'images' => input('post.images'), + 'url' => input('post.url'), + 'fileurl' => input('post.fileurl'), + 'code' => input('post.code'), + 'zipcode' => input('post.zipcode'), + 'uploader' => input('post.uploader'), + 'content' => input('post.content'), + 'number' => input('post.number'), + 'status' => input('post.status', 1), + 'create_time' => time() + ]; + + $insert = Resource::insert($data); + if (empty($insert)) { + Log::record('添加资源', 0, '添加资源失败', '资源管理'); + $this->error('添加失败'); + } + Log::record('添加资源', 1, '', '资源管理'); + return json(['code' => 0, 'msg' => '添加成功', 'data' => []]); + } else { + // 获取所有分类 + $allCategories = ResourceCategory::where('delete_time', null) + ->where('status', 1) + ->field('id, name, cid, icon, number') + ->order('sort asc, id asc') + ->select() + ->toArray(); + + $categories = $this->buildParentChild($allCategories); + + View::assign([ + 'categories' => $categories + ]); + + return View::fetch(); + } catch (\Exception $e) { + Log::record('添加资源页面加载', 0, $e->getMessage(), '资源管理'); + $this->error('页面加载失败:' . $e->getMessage()); + } + } + + // 编辑资源 + public function edit() + { + if (Request::isPost()) { + $data = input('post.'); + $id = input('id/d', 0); + + if (!$id) { + Log::record('编辑资源', 0, '参数错误', '资源管理'); + return json(['code' => 1, 'msg' => '参数错误']); + } + + $updateData = [ + 'title' => $data['title'], + 'cate' => $data['cate'], + 'desc' => $data['desc'], + 'uploader' => $data['uploader'], + 'icon' => $data['icon'], + 'images' => $data['images'], + 'fileurl' => $data['fileurl'], + 'url' => $data['url'], + 'code' => $data['code'], + 'zipcode' => $data['zipcode'], + 'sort' => $data['sort'], + 'number' => $data['number'], + 'content' => $data['content'], + 'update_time' => time() + ]; + + $result = Resource::where('id', $id) + ->update($updateData); + + if ($result !== false) { + Log::record('编辑资源', 1, '', '资源管理'); + return json(['code' => 0, 'msg' => '编辑成功']); + } else { + Log::record('编辑资源', 0, '编辑资源失败', '资源管理'); + return json(['code' => 1, 'msg' => '编辑失败']); + } + } + + $id = input('id/d', 0); + if (!$id) { + Log::record('编辑资源', 0, '参数错误', '资源管理'); + $this->error('参数错误'); + } + + $resource = Resource::where('id', $id) + ->where('delete_time', null) + ->find(); + + if (!$resource) { + Log::record('编辑资源', 0, '资源不存在', '资源管理'); + $this->error('资源不存在'); + } + + // 处理图片路径 + if (!empty($resource['images'])) { + $domain = request()->domain(); + $images = explode(',', $resource['images']); + // $images = array_map(function ($image) use ($domain) { + // return $domain . $image; + // }, $images); + $resource['images'] = implode(',', $images); + } + + View::assign('resource', $resource); + return View::fetch(); + } + + // 删除资源 + public function delete() + { + $id = input('post.id'); + $delete = Resource::where('id', $id) + ->update(['delete_time' => time()]); + if ($delete === false) { + Log::record('删除资源', 0, '删除资源失败', '资源管理'); + return json(['code' => 1, 'msg' => '删除失败', 'data' => []]); + } + Log::record('删除资源', 1, '', '资源管理'); + return json(['code' => 0, 'msg' => '删除成功', 'data' => []]); + } + + // 资源分类 + public function cate() + { + if (Request::isPost()) { + $lists = ResourceCategory::where('delete_time', null) + ->where('status', 1) + ->order('sort asc, id asc') + ->select() + ->toArray(); + $tree = $this->buildTree($lists); + return json(['code' => 0, 'msg' => '获取成功', 'data' => $tree]); + } + return View::fetch(); + } + + //获取分类结构 + public function getcate() + { + $lists = ResourceCategory::where('delete_time', null) + ->where('status', 1) + ->order('sort asc, id asc') + ->select() + ->toArray(); + + // 获取每个分类下的资源总数 + foreach ($lists as &$item) { + if ($item['cid'] == 0) { + // 父级分类 - 统计所有子分类的资源总数 + $childIds = ResourceCategory::where('cid', $item['id']) + ->where('delete_time', null) + ->where('status', 1) + ->column('id'); + + $item['total'] = Resource::where('cate', 'in', array_merge([$item['id']], $childIds)) + ->where('delete_time', null) + ->where('status', '<>', 3) + ->count(); + } else { + // 子分类 - 只统计当前分类的资源 + $item['total'] = Resource::where('cate', $item['id']) + ->where('delete_time', null) + ->where('status', '<>', 3) + ->count(); + } + } + + $tree = $this->buildParentChild($lists); + return json(['code' => 0, 'msg' => '获取成功', 'data' => $tree]); + } + + // 添加资源分类 + public function cateadd() + { + if (Request::isPost()) { + $data = [ + 'name' => input('post.name'), + 'icon' => input('post.icon'), + 'cid' => input('post.cid'), + 'number' => input('post.number'), + 'sort' => input('post.sort', 0), + 'status' => input('post.status', 1), + 'create_time' => time() + ]; + + $insert = ResourceCategory::insert($data); + if (empty($insert)) { + Log::record('添加资源分类', 0, '添加资源分类失败', '资源分类'); + return json(['code' => 1, 'msg' => '添加失败', 'data' => []]); + } + Log::record('添加资源分类', 1, '', '资源分类'); + return json(['code' => 0, 'msg' => '添加成功', 'data' => []]); + } else { + $parentCategories = ResourceCategory::where('delete_time', null) + ->where('status', 1) + ->where('cid', 0) + ->field('id, name') + ->select() + ->toArray(); + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'parentOptions' => $parentCategories + ] + ]); + } + } + + //编辑资源分类 + public function cateedit() + { + if (Request::isPost()) { + $data = [ + 'id' => input('post.id'), + 'name' => input('post.name'), + 'icon' => input('post.icon'), + 'cid' => input('post.cid'), + 'number' => input('post.number'), + 'sort' => input('post.sort', 0), + 'status' => input('post.status', 1), + 'update_time' => time() + ]; + + $update = ResourceCategory::where('id', $data['id']) + ->update($data); + if ($update === false) { + Log::record('编辑资源分类', 0, '更新资源分类失败', '资源分类'); + return json(['code' => 1, 'msg' => '更新失败', 'data' => []]); + } + Log::record('编辑资源分类', 1, '', '资源分类'); + return json(['code' => 0, 'msg' => '更新成功', 'data' => []]); + } else { + $id = input('get.id'); + $info = ResourceCategory::where('id', $id)->find(); + $parentCategories = ResourceCategory::where('delete_time', null) + ->where('status', 1) + ->where('cid', 0) + ->where('id', '<>', $id) + ->field('id, name') + ->select() + ->toArray(); + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'info' => $info, + 'parentOptions' => $parentCategories + ] + ]); + } + } + + //删除资源分类 + public function catedel() + { + $id = input('post.id'); + + // 检查是否有子分类 + $hasChildren = ResourceCategory::where('cid', $id) + ->where('delete_time', null) + ->find(); + if ($hasChildren) { + Log::record('删除资源分类', 0, '该分类下有子分类,无法删除', '资源分类'); + return json(['code' => 1, 'msg' => '该分类下有子分类,无法删除', 'data' => []]); + } + + $delete = ResourceCategory::where('id', $id) + ->update(['delete_time' => time()]); + if ($delete === false) { + Log::record('删除资源分类', 0, '删除资源分类失败', '资源分类'); + return json(['code' => 1, 'msg' => '删除失败', 'data' => []]); + } + Log::record('删除资源分类', 1, '', '资源分类'); + return json(['code' => 0, 'msg' => '删除成功', 'data' => []]); + } + + /** + * 获取资源详情 + */ + public function get() + { + $id = input('id/d', 0); + if (!$id) { + Log::record('获取资源详情', 0, '参数错误', '资源管理'); + return json(['code' => 1, 'msg' => '参数错误']); + } + + $resource = Resource::where('id', $id) + ->where('delete_time', null) + ->find(); + + if (!$resource) { + Log::record('获取资源详情', 0, '资源不存在', '资源管理'); + return json(['code' => 1, 'msg' => '资源不存在']); + } + + // 获取分类信息 + $cateInfo = ResourceCategory::where('id', $resource['cate']) + ->field('name') + ->find(); + + if ($cateInfo) { + $resource['cate_name'] = $cateInfo['name']; + } + + Log::record('获取资源详情', 1, '', '资源管理'); + return json(['code' => 0, 'msg' => '获取成功', 'data' => $resource]); + } + + //统计资源数量 + public function counts() + { + try { + // 获取资源总数 + $total = Resource::where('delete_time', null) + ->where('status', '<>', 3) + ->count(); + + // 获取今日新增资源数 + $today = strtotime(date('Y-m-d')); + $todayNew = Resource::where('delete_time', null) + ->where('status', '<>', 3) + ->where('create_time', '>=', $today) + ->count(); + + // 获取最近7天的资源数据 + $dates = []; + $counts = []; + $totalCounts = []; // 存储每天的总资源数 + + for ($i = 6; $i >= 0; $i--) { + $date = date('Y-m-d', strtotime("-$i days")); + $start = strtotime($date); + $end = $start + 86400; + + // 获取当天新增资源数 + $count = Resource::where('delete_time', null) + ->where('status', '<>', 3) + ->where('create_time', '>=', $start) + ->where('create_time', '<', $end) + ->count(); + + // 获取截至当天的总资源数 + $totalCount = Resource::where('delete_time', null) + ->where('status', '<>', 3) + ->where('create_time', '<', $end) + ->count(); + + $dates[] = $date; + $counts[] = $count; + $totalCounts[] = $totalCount; + } + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'total' => $total, + 'todayNew' => $todayNew, + 'dates' => $dates, + 'counts' => $counts, + 'totalCounts' => $totalCounts + ] + ]); + } catch (\Exception $e) { + return json([ + 'code' => 1, + 'msg' => '获取失败:' . $e->getMessage() + ]); + } + } + + // 构建树形结构 + private function buildTree($lists) + { + $tree = []; + foreach ($lists as $item) { + if ($item['cid'] == 0) { + $node = [ + 'id' => $item['id'], + 'title' => $item['name'], + 'children' => [] + ]; + + // 查找子分类 + foreach ($lists as $subItem) { + if ($subItem['cid'] == $item['id']) { + $node['children'][] = [ + 'id' => $subItem['id'], + 'title' => $subItem['name'], + 'children' => [] + ]; + } + } + + $tree[] = $node; + } + } + return $tree; + } + + // 构建父子结构 + private function buildParentChild($lists) + { + $tree = []; + foreach ($lists as $item) { + if ($item['cid'] == 0) { + // 顶级分类 + $tree[] = $item; + } else { + // 子分类 + foreach ($tree as &$parent) { + if ($parent['id'] == $item['cid']) { + if (!isset($parent['children'])) { + $parent['children'] = []; + } + $parent['children'][] = $item; + break; + } + } + } + } + return $tree; + } +} \ No newline at end of file diff --git a/app/admin/controller/UsersController.php b/app/admin/controller/UsersController.php new file mode 100644 index 0000000..8c0bf1c --- /dev/null +++ b/app/admin/controller/UsersController.php @@ -0,0 +1,98 @@ +where('status', 1) + ->count(); + + // 获取今日新增用户数 + $today = strtotime(date('Y-m-d')); + $todayNew = Users::where('delete_time', 0) + ->where('status', 1) + ->where('create_time', '>=', $today) + ->count(); + + // 获取最近7天的用户数据 + $dates = []; + $counts = []; + $totalCounts = []; // 存储每天的总用户数 + + for ($i = 6; $i >= 0; $i--) { + $date = date('Y-m-d', strtotime("-$i days")); + $start = strtotime($date); + $end = $start + 86400; + + // 获取当天新增用户数 + $count = Users::where('delete_time', 0) + ->where('status', 1) + ->where('create_time', '>=', $start) + ->where('create_time', '<', $end) + ->count(); + + // 获取截至当天的总用户数 + $totalCount = Users::where('delete_time', 0) + ->where('status', 1) + ->where('create_time', '<', $end) + ->count(); + + $dates[] = $date; + $counts[] = $count; + $totalCounts[] = $totalCount; + } + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'total' => $total, + 'todayNew' => $todayNew, + 'dates' => $dates, + 'counts' => $counts, + 'totalCounts' => $totalCounts + ] + ]); + } catch (\Exception $e) { + return json([ + 'code' => 1, + 'msg' => '获取失败:' . $e->getMessage() + ]); + } + } +} \ No newline at end of file diff --git a/app/admin/controller/YunzerController.php b/app/admin/controller/YunzerController.php new file mode 100644 index 0000000..166d665 --- /dev/null +++ b/app/admin/controller/YunzerController.php @@ -0,0 +1,556 @@ +order('sort DESC,smid DESC')->select(); + View::assign([ + 'lists' => $lists + ]); + return View::fetch(); + } + # 菜单添加 + public function menuadd() + { + $req = request(); + if ($req->isPost()) { + $data['label'] = trim(input('post.label')); + if (empty($data['label'])) { + Log::record('添加菜单', 0, '请输入菜单名称', '菜单管理'); + $this->returnCode(1, '请输入菜单名称'); + } + $data['icon_class'] = trim(input('post.icon_class')); + $data['sort'] = (int) trim(input('post.sort')); + $data['status'] = (int) trim(input('post.status')); + $data['type'] = (int) trim(input('post.type', 0)); + if ($data['type'] == 1) { + $data['src'] = trim(input('post.src1')); + if (empty($data['src'])) { + Log::record('添加菜单', 0, '请输入内部跳转地址', '菜单管理'); + $this->returnCode(1, '请输入内部跳转地址'); + } + } else if ($data['type'] == 2) { + $data['src'] = trim(input('post.src2')); + if (empty($data['src'])) { + Log::record('添加菜单', 0, '请输入超链接地址', '菜单管理'); + $this->returnCode(1, '请输入超链接地址'); + } + } else { + $data['src'] = ''; + } + // 保存菜单 + $res = AdminSysMenu::insert($data); + if (!$res) { + Log::record('添加菜单', 0, '添加菜单失败', '菜单管理'); + $this->returnCode(1, '添加菜单失败'); + } + + // 获取新插入的菜单ID + $newMenuId = AdminSysMenu::getLastInsID(); + + // 获取所有管理员用户组 + $adminGroups = AdminUserGroup::select(); + foreach ($adminGroups as $group) { + // 获取现有权限 + $rights = []; + if (!empty($group['rights'])) { + $rights = json_decode($group['rights'], true); + } + + // 添加新菜单ID到权限列表 + if (!in_array($newMenuId, $rights)) { + $rights[] = $newMenuId; + + // 更新用户组权限 + AdminUserGroup::where('group_id', $group['group_id']) + ->update(['rights' => json_encode($rights)]); + } + } + + Log::record('添加菜单', 1, '', '菜单管理'); + $this->returnCode(0); + } else { + $iconfont = ZIconfont::where('status', 1)->select(); + View::assign([ + 'iconfont' => $iconfont + ]); + return View::fetch(); + } + } + # 菜单修改 + public function menuedit() + { + $req = request(); + if ($req->isPost()) { + $smid = (int) input('post.smid'); + $data['label'] = trim(input('post.label')); + if (!$data['label']) { + Log::record('编辑菜单', 0, '请输入菜单名称', '菜单管理'); + $this->returnCode(1, '请输入菜单名称'); + } + $data['icon_class'] = trim(input('post.icon_class')); + $data['sort'] = (int) trim(input('post.sort')); + $data['status'] = (int) trim(input('post.status')); + $data['type'] = (int) trim(input('post.type', 0)); + if ($data['type'] == 1) { + $data['src'] = trim(input('post.src1')); + if (empty($data['src'])) { + Log::record('编辑菜单', 0, '请输入内部跳转地址', '菜单管理'); + $this->returnCode(1, '请输入内部跳转地址'); + } + } else if ($data['type'] == 2) { + $data['src'] = trim(input('post.src2')); + if (empty($data['src'])) { + Log::record('编辑菜单', 0, '请输入超链接地址', '菜单管理'); + $this->returnCode(1, '请输入超链接地址'); + } + } else { + $data['src'] = ''; + } + // 保存用户 + $res = AdminSysMenu::where('smid', $smid)->update($data); + if (!$res) { + Log::record('编辑菜单', 0, '修改菜单失败', '菜单管理'); + $this->returnCode(1, '修改菜单失败'); + } + Log::record('编辑菜单', 1, '', '菜单管理'); + $this->returnCode(0); + } else { + $smid = (int) input('get.smid'); + $lists = AdminSysMenu::where('smid', $smid)->find(); + $iconfont = ZIconfont::where('status', 1)->select(); + View::assign([ + 'lists' => $lists, + 'iconfont' => $iconfont + ]); + return View::fetch(); + } + } + # 菜单删除 + public function menudel() + { + $smid = (int) input('post.smid'); + $count = AdminSysMenu::where('parent_id', $smid)->count(); + if ($count > 0) { + Log::record('删除菜单', 0, '该菜单下还有子菜单,不能删除', '菜单管理'); + $this->returnCode(1, '该菜单下还有子菜单,不能删除'); + } + $res = AdminSysMenu::where('smid', $smid)->delete(); + if (empty($res)) { + Log::record('删除菜单', 0, '删除菜单失败', '菜单管理'); + $this->returnCode(1, '删除菜单失败'); + } + Log::record('删除菜单', 1, '', '菜单管理'); + $this->returnCode(0); + } + # 按钮管理 + public function buttoninfo() + { + $smid = (int) input('get.smid'); + $lists = AdminSysMenu::where('parent_id', $smid)->order('sort DESC')->select()->toArray(); + if (!empty($lists)) { + foreach ($lists as &$v) { + switch ($v['type']) { + case 0: + $v['type_name'] = '顶级菜单'; + break; + case 1: + $v['type_name'] = '内部跳转'; + break; + case 2: + $v['type_name'] = '超链接'; + break; + default: + $v['type_name'] = '未规划类型'; + break; + } + } + } + View::assign([ + 'lists' => $lists, + 'smid' => $smid + ]); + return View::fetch(); + } + # 按钮添加 + public function buttonadd() + { + $req = request(); + if ($req->isPost()) { + $smid = (int) input('post.smid'); + $data['label'] = trim(input('post.label')); + if (!$data['label']) { + Log::record('添加按钮', 0, '请输入按钮名称', '按钮管理'); + $this->returnCode(1, '请输入按钮名称'); + } + $data['icon_class'] = trim(input('post.icon_class')); + $data['sort'] = (int) trim(input('post.sort')); + $data['status'] = (int) trim(input('post.status')); + $data['type'] = (int) trim(input('post.type')); + if (empty($data['type'])) { + Log::record('添加按钮', 0, '请选择按钮类型', '按钮管理'); + $this->returnCode(1, '请选择按钮类型'); + } + if ($data['type'] == 1) { + $data['src'] = trim(input('post.src1')); + if (empty($data['src'])) { + Log::record('添加按钮', 0, '请输入内部跳转地址', '按钮管理'); + $this->returnCode(1, '请输入内部跳转地址'); + } + } else if ($data['type'] == 2) { + $data['src'] = trim(input('post.src2')); + if (empty($data['src'])) { + Log::record('添加按钮', 0, '请输入超链接地址', '按钮管理'); + $this->returnCode(1, '请输入超链接地址'); + } + } + $data['parent_id'] = $smid; + $res = AdminSysMenu::insert($data); + if (!$res) { + Log::record('添加按钮', 0, '添加按钮失败', '按钮管理'); + $this->returnCode(1, '添加按钮失败'); + } + Log::record('添加按钮', 1, '', '按钮管理'); + $this->returnCode(0); + } else { + $smid = (int) input('get.smid'); + $iconfont = ZIconfont::where('status', 1)->select(); + View::assign([ + 'smid' => $smid, + 'iconfont' => $iconfont + ]); + return View::fetch(); + } + } + # 按钮修改 + public function buttonedit() + { + $req = request(); + if ($req->isPost()) { + $smid = (int) input('post.smid'); + $data['label'] = trim(input('post.label')); + if (!$data['label']) { + Log::record('编辑按钮', 0, '请输入按钮名称', '按钮管理'); + $this->returnCode(1, '请输入按钮名称'); + } + $data['icon_class'] = trim(input('post.icon_class')); + $data['sort'] = (int) trim(input('post.sort')); + $data['status'] = (int) trim(input('post.status')); + $data['type'] = (int) trim(input('post.type')); + if (empty($data['type'])) { + Log::record('编辑按钮', 0, '请选择按钮类型', '按钮管理'); + $this->returnCode(1, '请选择按钮类型'); + } + if ($data['type'] == 1) { + $data['src'] = trim(input('post.src1')); + if (empty($data['src'])) { + Log::record('编辑按钮', 0, '请输入内部跳转地址', '按钮管理'); + $this->returnCode(1, '请输入内部跳转地址'); + } + } else if ($data['type'] == 2) { + $data['src'] = trim(input('post.src2')); + if (empty($data['src'])) { + Log::record('编辑按钮', 0, '请输入超链接地址', '按钮管理'); + $this->returnCode(1, '请输入超链接地址'); + } + } + $res = AdminSysMenu::where('smid', $smid)->update($data); + if (!$res) { + Log::record('编辑按钮', 0, '修改按钮失败', '按钮管理'); + $this->returnCode(1, '修改按钮失败'); + } + Log::record('编辑按钮', 1, '', '按钮管理'); + $this->returnCode(0); + } else { + $smid = (int) input('get.smid'); + $lists = AdminSysMenu::where('smid', $smid)->find(); + $iconfont = ZIconfont::where('status', 1)->select(); + View::assign([ + 'lists' => $lists, + 'iconfont' => $iconfont + ]); + return View::fetch(); + } + } + # 按钮删除 + public function buttondel() + { + $smid = (int) input('post.smid'); + $res = AdminSysMenu::where('smid', $smid)->delete(); + if (empty($res)) { + Log::record('删除按钮', 0, '删除按钮失败', '按钮管理'); + $this->returnCode(1, '删除按钮失败'); + } + Log::record('删除按钮', 1, '', '按钮管理'); + $this->returnCode(0); + } + # 配置列表 + public function configlist() + { + $req = request(); + if ($req->isPost()) { + $page = (int) input('post.page', 1); + $limit = (int) input('post.limit', $this->config['admin_page']); + $count = AdminConfig::count(); + $lists = AdminConfig::page($page, $limit)->order('config_sort DESC,config_id DESC')->select(); + $this->returnCode(0, $lists, $count); + } else { + return View::fetch(); + } + } + # 配置添加 + public function configadd() + { + $req = request(); + if ($req->isPost()) { + $data['config_name'] = trim(input('post.config_name')); + if (empty($data['config_name'])) { + Log::record('添加配置', 0, '请输入关键词', '系统配置'); + $this->returnCode(1, '请输入关键词'); + } + $data['config_info'] = trim(input('post.config_info')); + if (empty($data['config_info'])) { + Log::record('添加配置', 0, '请输入作用', '系统配置'); + $this->returnCode(1, '请输入作用'); + } + $data['config_type'] = trim(input('post.config_type')); + $data['config_desc'] = trim(input('post.config_desc')); + $data['config_status'] = trim(input('post.config_status')); + $data['config_sort'] = trim(input('post.config_sort')); + $res = AdminConfig::insert($data); + if (empty($res)) { + Log::record('添加配置', 0, '添加配置失败', '系统配置'); + $this->returnCode(1, '添加配置失败'); + } + Log::record('添加配置', 1, '', '系统配置'); + $this->returnCode(0); + } else { + return View::fetch(); + } + } + # 配置修改 + public function configedit() + { + $req = request(); + if ($req->isPost()) { + $config_id = (int) input('post.config_id'); + if (empty($config_id)) { + Log::record('编辑配置', 0, '请选择一条数据', '系统配置'); + $this->returnCode(1, '请选择一条数据'); + } + $data['config_name'] = trim(input('post.config_name')); + if (empty($data['config_name'])) { + Log::record('编辑配置', 0, '请输入关键词', '系统配置'); + $this->returnCode(1, '请输入关键词'); + } + $data['config_info'] = trim(input('post.config_info')); + if (empty($data['config_info'])) { + Log::record('编辑配置', 0, '请输入作用', '系统配置'); + $this->returnCode(1, '请输入作用'); + } + $data['config_type'] = trim(input('post.config_type')); + $data['config_desc'] = trim(input('post.config_desc')); + $data['config_status'] = trim(input('post.config_status')); + $data['config_sort'] = trim(input('post.config_sort')); + $res = AdminConfig::where('config_id', $config_id)->update($data); + if (empty($res)) { + Log::record('编辑配置', 0, '修改配置失败', '系统配置'); + $this->returnCode(1, '修改配置失败'); + } + Log::record('编辑配置', 1, '', '系统配置'); + $this->returnCode(0); + } else { + $config_id = (int) input('get.config_id'); + $find = AdminConfig::where('config_id', $config_id)->find(); + View::assign([ + 'find' => $find + ]); + return View::fetch(); + } + } + # 配置删除 + public function configdel() + { + $config_id = (int) input('post.config_id'); + if (empty($config_id)) { + Log::record('删除配置', 0, '请选择一条数据', '系统配置'); + $this->returnCode(1, '请选择一条数据'); + } + $res = AdminConfig::where('config_id', $config_id)->delete(); + if (empty($res)) { + Log::record('删除配置', 0, '删除配置失败', '系统配置'); + $this->returnCode(1, '删除配置失败'); + } + Log::record('删除配置', 1, '', '系统配置'); + $this->returnCode(0); + } + # 配置值 + public function configvalue() + { + $req = request(); + if ($req->isPost()) { + $post = input('post.'); + if (empty($post)) { + Log::record('更新配置值', 0, '数据不能为空', '系统配置'); + $this->returnCode(1, '数据不能为空'); + } + + // 获取所有配置项,检查 config_type 为 4 的项 + $allConfigs = AdminConfig::order('config_sort DESC,config_id')->select(); + foreach ($allConfigs as $config) { + if ($config['config_type'] == 4) { + $checkboxName = $config['config_name'] . '_checkbox'; + $configName = $config['config_name']; + if (!isset($post[$checkboxName])) { + // 复选框未选中,手动添加对应的值为 0 + $post[$configName] = 0; + } else { + // 复选框选中,确保值为 1 + $post[$configName] = 1; + } + // 移除 checkbox 字段 + unset($post[$checkboxName]); + } + } + + $oConfig = new YzAdminConfig(); + $updateAll = $oConfig->updateAll($post); + if (empty($updateAll)) { + Log::record('更新配置值', 0, '更新配置值失败', '系统配置'); + $this->returnCode(1, '更新配置值失败'); + } + Log::record('更新配置值', 1, '', '系统配置'); + $this->returnCode(0); + } else { + $lists = AdminConfig::order('config_sort DESC,config_id')->select(); + View::assign([ + 'lists' => $lists + ]); + return View::fetch(); + } + } + + + // 邮件配置 + public function mailconfig() + { + if (Request::isPost()) { + $data = [ + 'smtp_host' => input('post.smtp_host'), + 'smtp_port' => input('post.smtp_port'), + 'smtp_email' => input('post.smtp_email'), + 'smtp_password' => input('post.smtp_password'), + 'smtp_name' => input('post.smtp_name') + ]; + + // 验证必填字段 + if (empty($data['smtp_host']) || empty($data['smtp_port']) || empty($data['smtp_email']) || empty($data['smtp_password'])) { + // Log::record('修改邮件配置', 0, '必填字段不能为空', '邮件配置'); + return json(['code' => 1, 'msg' => '必填字段不能为空']); + } + + $res = MailConfig::where('id', 1)->update($data); + if ($res === false) { + // Log::record('修改邮件配置', 0, '更新邮件配置失败', '邮件配置'); + return json(['code' => 1, 'msg' => '更新邮件配置失败']); + } + // Log::record('修改邮件配置', 1, '', '邮件配置'); + return json(['code' => 0, 'msg' => '更新成功']); + } + + $config = MailConfig::where('id', 1)->find(); + View::assign([ + 'config' => $config + ]); + return View::fetch(); + } + + // 获取邮件配置 + public function getMailConfig() + { + $config = MailConfig::where('id', 1)->find(); + if ($config) { + return json(['code' => 0, 'msg' => '获取成功', 'data' => $config]); + } + return json(['code' => 1, 'msg' => '获取配置失败']); + } + + // 测试邮件配置 + public function testMailConfig() + { + if (!Request::isPost()) { + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + $email = input('post.email'); + $config = input('post.config'); + + if (empty($email) || empty($config)) { + return json(['code' => 1, 'msg' => '参数错误']); + } + + try { + // 配置邮件服务器 + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->Host = $config['smtp_host']; + $mail->Port = $config['smtp_port']; + $mail->SMTPAuth = true; + $mail->Username = $config['smtp_email']; + $mail->Password = $config['smtp_password']; + $mail->SMTPSecure = 'ssl'; + $mail->CharSet = 'UTF-8'; + + // 设置发件人 + $mail->setFrom($config['smtp_email'], $config['smtp_name']); + $mail->addAddress($email); + + // 设置邮件内容 + $mail->isHTML(true); + $mail->Subject = '邮件配置测试'; + $mail->Body = '这是一封测试邮件,如果您收到这封邮件,说明邮件配置正确。'; + + $mail->send(); + return json(['code' => 0, 'msg' => '发送成功']); + } catch (\Exception $e) { + //Log::record('测试邮件配置', 0, $e->getMessage(), '邮件配置'); + return json(['code' => 1, 'msg' => '发送失败:' . $e->getMessage()]); + } + } +} \ No newline at end of file diff --git a/app/admin/controller/YunzeradminController.php b/app/admin/controller/YunzeradminController.php new file mode 100644 index 0000000..b35ea19 --- /dev/null +++ b/app/admin/controller/YunzeradminController.php @@ -0,0 +1,1209 @@ + $group + ]); + return View::fetch(); + } + + // 角色添加 + public function groupadd() + { + if (Request::isPost()) { + $data['group_name'] = trim(input('post.group_name')); + if (!$data['group_name']) { + Log::record('添加角色', 0, '角色名称不能为空', '角色管理'); + return json(['code' => 1, 'msg' => '角色名称不能为空']); + } + $data['status'] = intval(trim(input('post.status'))); + $data['create_time'] = time(); + $menus = input('post.menu/a'); + if ($menus) { + $data['rights'] = json_encode(array_keys($menus)); + } + $res = AdminUserGroup::insert($data); + if (!$res) { + Log::record('添加角色', 0, '添加角色失败', '角色管理'); + return json(['code' => 1, 'msg' => '添加角色失败']); + } + Log::record('添加角色', 1, '', '角色管理'); + return json(['code' => 0, 'msg' => '添加成功']); + } else { + $menus = AdminSysMenu::order('type,sort desc')->where('status', '=', 1)->select(); + $menu = []; + + // 先处理所有父菜单 + foreach ($menus as $menus_v) { + if ($menus_v['parent_id'] == 0) { + $menu[$menus_v['smid']] = $menus_v; + $menu[$menus_v['smid']]['children'] = []; // 初始化 children 数组 + } + } + + // 再处理子菜单 + foreach ($menus as $menus_v) { + if ($menus_v['parent_id'] != 0 && isset($menu[$menus_v['parent_id']])) { + $menu[$menus_v['parent_id']]['children'][] = $menus_v; + } + } + + View::assign([ + 'menus' => $menu + ]); + return View::fetch(); + } + } + + // 角色编辑 + public function groupedit() + { + if (Request::isPost()) { + $group_id = (int) trim(input('post.group_id')); + $data['group_name'] = trim(input('post.group_name')); + if (!$data['group_name']) { + Log::record('编辑角色', 0, '角色名称不能为空', '角色管理'); + return json(['code' => 1, 'msg' => '角色名称不能为空']); + } + $data['status'] = (int) trim(input('post.status')); + $menus = input('post.menu/a'); + if ($menus) { + $data['rights'] = json_encode(array_keys($menus)); + } else { + $data['rights'] = ''; + } + $res = AdminUserGroup::where('group_id', $group_id)->update($data); + if (!$res) { + Log::record('编辑角色', 0, '更新角色失败', '角色管理'); + return json(['code' => 1, 'msg' => '更新角色失败']); + } + Log::record('编辑角色', 1, '', '角色管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } else { + $group_id = (int) input('get.group_id'); + $group = AdminUserGroup::where('group_id', $group_id)->find(); + if ($group && $group['rights']) { + $group['rights'] = json_decode($group['rights']); + } + + // 使用模型中的 getMenuTree 方法获取菜单树 + $menu = AdminSysMenu::getMenuTree(); + + View::assign([ + 'group' => $group, + 'menus' => $menu + ]); + return View::fetch(); + } + } + + // 角色删除 + public function groupdel() + { + $group_id = (int) input('post.group_id'); + $res = AdminUserGroup::where('group_id', $group_id)->delete(); + if (empty($res)) { + Log::record('删除角色', 0, '删除角色失败', '角色管理'); + return json(['code' => 1, 'msg' => '删除角色失败']); + } + Log::record('删除角色', 1, '', '角色管理'); + return json(['code' => 0, 'msg' => '删除成功']); + } + + // 管理员列表 + public function userinfo() + { + $lists = AdminUser::select(); + $group = []; + $groups = AdminUserGroup::select(); + foreach ($groups as $key => $value) { + $group[$value['group_id']] = $value; + } + View::assign([ + 'lists' => $lists, + 'group' => $group + ]); + return View::fetch(); + } + + // 管理员添加 + public function useradd() + { + if (Request::isPost()) { + $data['account'] = trim(input('post.account')); + if (empty($data['account'])) { + Log::record('添加管理员', 0, '账号不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '账号不能为空']); + } + $pattern = "/^([0-9A-Za-z-_.]+)@([0-9a-z]+.[a-z]{2,3}(.[a-z]{2})?)$/i"; + if (!preg_match($pattern, $data['account'])) { + Log::record('添加管理员', 0, '邮箱格式不正确', '管理员管理'); + return json(['code' => 1, 'msg' => '邮箱格式不正确']); + } + $item = AdminUser::where('account', $data['account'])->find(); + if ($item) { + Log::record('添加管理员', 0, '该账号已存在', '管理员管理'); + return json(['code' => 1, 'msg' => '该账号已存在']); + } + $data['name'] = trim(input('post.name')); + $data['phone'] = trim(input('post.phone')); + $data['qq'] = (int) trim(input('post.qq')); + $data['group_id'] = (int) input('post.group_id'); + $data['sex'] = (int) (input('post.sex')); + $data['status'] = (int) (input('post.status')); + $password = trim(input('post.password')); + if (empty($data['name'])) { + Log::record('添加管理员', 0, '姓名不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '姓名不能为空']); + } + if (empty($data['phone'])) { + Log::record('添加管理员', 0, '手机号不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '手机号不能为空']); + } + if (empty($data['group_id'])) { + Log::record('添加管理员', 0, '请选择角色', '管理员管理'); + return json(['code' => 1, 'msg' => '请选择角色']); + } + if (empty($password)) { + Log::record('添加管理员', 0, '密码不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '密码不能为空']); + } else { + $data['password'] = md5($password); + } + $data['create_time'] = time(); + $data['update_time'] = time(); + $res = AdminUser::insert($data); + if (!$res) { + Log::record('添加管理员', 0, '添加管理员失败', '管理员管理'); + return json(['code' => 1, 'msg' => '添加管理员失败']); + } + Log::record('添加管理员', 1, '', '管理员管理'); + return json(['code' => 0, 'msg' => '添加成功']); + } else { + $group = []; + $groups = AdminUserGroup::select(); + foreach ($groups as $key => $value) { + $group[$value['group_id']] = $value; + } + View::assign([ + 'group' => $group + ]); + return View::fetch(); + } + } + + // 管理员编辑 + public function useredit() + { + if (Request::isPost()) { + $uid = (int) trim(input('post.uid')); + $data['name'] = trim(input('post.name')); + $data['phone'] = trim(input('post.phone')); + $data['qq'] = (int) trim(input('post.qq')); + $data['group_id'] = (int) input('post.group_id'); + $data['sex'] = (int) (input('post.sex')); + $data['status'] = (int) (input('post.status')); + if (empty($data['name'])) { + Log::record('编辑管理员', 0, '姓名不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '姓名不能为空']); + } + if (empty($data['phone'])) { + Log::record('编辑管理员', 0, '手机号不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '手机号不能为空']); + } + if (empty($data['group_id'])) { + Log::record('编辑管理员', 0, '请选择角色', '管理员管理'); + return json(['code' => 1, 'msg' => '请选择角色']); + } + $res = AdminUser::where('uid', $uid)->update($data); + if (!$res) { + Log::record('编辑管理员', 0, '更新管理员信息失败', '管理员管理'); + return json(['code' => 1, 'msg' => '更新管理员信息失败']); + } + Log::record('编辑管理员', 1, '', '管理员管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } else { + $uid = (int) input('get.uid'); + // 加载管理员 + $lists = AdminUser::where('uid', $uid)->find(); + // 加载角色 + $group = []; + $groups = AdminUserGroup::select(); + foreach ($groups as $key => $value) { + $group[$value['group_id']] = $value; + } + View::assign([ + 'lists' => $lists, + 'group' => $group + ]); + return View::fetch(); + } + } + + // 管理员删除 + public function userdel() + { + $uid = (int) input('post.uid'); + $res = AdminUser::where('uid', $uid)->delete(); + if (empty($res)) { + Log::record('删除管理员', 0, '删除管理员失败', '管理员管理'); + return json(['code' => 1, 'msg' => '删除管理员失败']); + } + Log::record('删除管理员', 1, '', '管理员管理'); + return json(['code' => 0, 'msg' => '删除成功']); + } + + // 管理员信息 + public function admininfo() + { + if (Request::isPost()) { + $find = AdminUser::where('uid', $this->adminId)->find(); + if (empty($find)) { + Log::record('修改个人信息', 0, '当前账户不存在', '个人信息'); + return json(['code' => 1, 'msg' => '当前账户不存在']); + } + $data['name'] = trim(input('post.name')); + $data['phone'] = trim(input('post.phone')); + $data['qq'] = (int) trim(input('post.qq')); + $data['sex'] = (int) (input('post.sex')); + if (empty($data['name'])) { + Log::record('修改个人信息', 0, '姓名不能为空', '个人信息'); + return json(['code' => 1, 'msg' => '姓名不能为空']); + } + if (empty($data['phone'])) { + Log::record('修改个人信息', 0, '手机号不能为空', '个人信息'); + return json(['code' => 1, 'msg' => '手机号不能为空']); + } + + // 处理密码修改 + $old_pw = trim(input('post.old_pw')); + $new_pw = trim(input('post.new_pw')); + if (!empty($old_pw) && !empty($new_pw)) { + if (md5($old_pw) != $find['password']) { + Log::record('修改个人信息', 0, '原密码错误', '个人信息'); + return json(['code' => 1, 'msg' => '原密码错误']); + } + $data['password'] = md5($new_pw); + } + + $res = AdminUser::where('uid', $this->adminId)->update($data); + if (!$res) { + Log::record('修改个人信息', 0, '更新管理员信息失败', '个人信息'); + return json(['code' => 1, 'msg' => '更新管理员信息失败']); + } + Log::record('修改个人信息', 1, '', '个人信息'); + return json(['code' => 0, 'msg' => '更新成功']); + } else { + return View::fetch(); + } + } + + // 前端会员列表 + public function frontuser() + { + $lists = Users::select(); + $group = []; + $groups = UsersGroup::select(); + foreach ($groups as $key => $value) { + $group[$value['group_id']] = $value['group_name']; + } + // 替换 group_id 为 group_name + foreach ($lists as &$user) { + $gid = $user['group_id'] ?? 0; + $user['group_id'] = isset($group[$gid]) ? $group[$gid] : ''; + } + unset($user); + if (request()->isAjax()) { + return json([ + 'code' => 0, + 'msg' => '', + 'count' => count($lists), + 'data' => $lists + ]); + } + View::assign([ + 'lists' => $lists, + 'group' => $group + ]); + return View::fetch(); + } + + // 前端会员添加 + public function frontuseradd() + { + if (Request::isPost()) { + $data['account'] = trim(input('post.account')); + if (empty($data['account'])) { + Log::record('添加管理员', 0, '账号不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '账号不能为空']); + } + $pattern = "/^([0-9A-Za-z-_.]+)@([0-9a-z]+.[a-z]{2,3}(.[a-z]{2})?)$/i"; + if (!preg_match($pattern, $data['account'])) { + Log::record('添加管理员', 0, '邮箱格式不正确', '管理员管理'); + return json(['code' => 1, 'msg' => '邮箱格式不正确']); + } + $item = Users::where('account', $data['account'])->find(); + if ($item) { + Log::record('添加管理员', 0, '该账号已存在', '管理员管理'); + return json(['code' => 1, 'msg' => '该账号已存在']); + } + $data['name'] = trim(input('post.name')); + $data['phone'] = trim(input('post.phone')); + $data['qq'] = (int) trim(input('post.qq')); + $data['group_id'] = (int) input('post.group_id'); + $data['sex'] = (int) (input('post.sex')); + $data['status'] = (int) (input('post.status')); + $password = trim(input('post.password')); + if (empty($data['name'])) { + Log::record('添加管理员', 0, '姓名不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '姓名不能为空']); + } + if (empty($data['phone'])) { + Log::record('添加管理员', 0, '手机号不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '手机号不能为空']); + } + if (empty($data['group_id'])) { + Log::record('添加管理员', 0, '请选择角色', '管理员管理'); + return json(['code' => 1, 'msg' => '请选择角色']); + } + if (empty($password)) { + Log::record('添加管理员', 0, '密码不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '密码不能为空']); + } else { + $data['password'] = md5($password); + } + $data['create_time'] = time(); + $data['update_time'] = time(); + $res = Users::insert($data); + if (!$res) { + Log::record('添加管理员', 0, '添加管理员失败', '管理员管理'); + return json(['code' => 1, 'msg' => '添加管理员失败']); + } + Log::record('添加管理员', 1, '', '管理员管理'); + return json(['code' => 0, 'msg' => '添加成功']); + } else { + $group = []; + $groups = UsersGroup::select(); + foreach ($groups as $key => $value) { + $group[$value['group_id']] = $value; + } + View::assign([ + 'group' => $group + ]); + return View::fetch(); + } + } + + // 前端会员编辑 + public function frontuseredit() + { + if (Request::isPost()) { + $uid = (int) trim(input('post.uid')); + $data['name'] = trim(input('post.name')); + $data['phone'] = trim(input('post.phone')); + $data['qq'] = (int) trim(input('post.qq')); + $data['group_id'] = (int) input('post.group_id'); + $data['sex'] = (int) (input('post.sex')); + $data['status'] = (int) (input('post.status')); + if (empty($data['name'])) { + Log::record('编辑管理员', 0, '姓名不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '姓名不能为空']); + } + if (empty($data['phone'])) { + Log::record('编辑管理员', 0, '手机号不能为空', '管理员管理'); + return json(['code' => 1, 'msg' => '手机号不能为空']); + } + if (empty($data['group_id'])) { + Log::record('编辑管理员', 0, '请选择角色', '管理员管理'); + return json(['code' => 1, 'msg' => '请选择角色']); + } + $res = Users::where('uid', $uid)->update($data); + if (!$res) { + Log::record('编辑管理员', 0, '更新管理员信息失败', '管理员管理'); + return json(['code' => 1, 'msg' => '更新管理员信息失败']); + } + Log::record('编辑管理员', 1, '', '管理员管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } else { + $uid = (int) input('get.uid'); + // 加载前端会员 + $lists = Users::where('uid', $uid)->find(); + // 加载角色 + $group = []; + $groups = UsersGroup::select(); + foreach ($groups as $key => $value) { + $group[$value['group_id']] = $value; + } + View::assign([ + 'lists' => $lists, + 'group' => $group + ]); + return View::fetch(); + } + } + + // 前端会员删除 + public function frontuserdel() + { + $uid = (int) input('post.uid'); + $res = Users::where('uid', $uid)->delete(); + if (empty($res)) { + Log::record('删除管理员', 0, '删除管理员失败', '管理员管理'); + return json(['code' => 1, 'msg' => '删除管理员失败']); + } + Log::record('删除管理员', 1, '', '管理员管理'); + return json(['code' => 0, 'msg' => '删除成功']); + } + + // 前端会员信息 + public function frontuserdetail() + { + if (Request::isPost()) { + $find = Users::where('uid', $this->adminId)->find(); + if (empty($find)) { + Log::record('修改个人信息', 0, '当前账户不存在', '个人信息'); + return json(['code' => 1, 'msg' => '当前账户不存在']); + } + $data['name'] = trim(input('post.name')); + $data['phone'] = trim(input('post.phone')); + $data['qq'] = (int) trim(input('post.qq')); + $data['sex'] = (int) (input('post.sex')); + if (empty($data['name'])) { + Log::record('修改个人信息', 0, '姓名不能为空', '个人信息'); + return json(['code' => 1, 'msg' => '姓名不能为空']); + } + if (empty($data['phone'])) { + Log::record('修改个人信息', 0, '手机号不能为空', '个人信息'); + return json(['code' => 1, 'msg' => '手机号不能为空']); + } + + // 处理密码修改 + $old_pw = trim(input('post.old_pw')); + $new_pw = trim(input('post.new_pw')); + if (!empty($old_pw) && !empty($new_pw)) { + if (md5($old_pw) != $find['password']) { + Log::record('修改个人信息', 0, '原密码错误', '个人信息'); + return json(['code' => 1, 'msg' => '原密码错误']); + } + $data['password'] = md5($new_pw); + } + + $res = Users::where('uid', $this->adminId)->update($data); + if (!$res) { + Log::record('修改个人信息', 0, '更新管理员信息失败', '个人信息'); + return json(['code' => 1, 'msg' => '更新管理员信息失败']); + } + Log::record('修改个人信息', 1, '', '个人信息'); + return json(['code' => 0, 'msg' => '更新成功']); + } else { + return View::fetch(); + } + } + + //banner管理 + public function banner() + { + return View::fetch(); + } + + // banner列表 + public function bannerlist() + { + if (Request::isGet()) { + $page = intval(input('post.page', 1)); + $limit = intval(input('post.limit', 10)); + + $query = Banner::where('delete_time', null) + ->field('id, title, image, url, sort, create_time, update_time'); + + // 获取总记录数 + $count = $query->count(); + + // 获取分页数据 + $lists = $query->order(['sort DESC', 'id DESC']) + ->page($page, $limit) + ->select() + ->toArray(); + + // 处理数据 + foreach ($lists as &$item) { + $item['create_time'] = is_numeric($item['create_time']) ? date('Y-m-d H:i:s', $item['create_time']) : $item['create_time']; + $item['update_time'] = is_numeric($item['update_time']) ? date('Y-m-d H:i:s', $item['update_time']) : $item['update_time']; + } + + return json([ + 'code' => 0, + 'msg' => '', + 'count' => $count, + 'data' => $lists + ]); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 添加banner + public function banneradd() + { + if (Request::isPost()) { + $data = [ + 'title' => input('post.title'), + 'image' => input('post.image'), + 'url' => input('post.url'), + 'sort' => input('post.sort', 0), + 'status' => 1, + 'create_time' => time() + ]; + + $res = Banner::insert($data); + if (!$res) { + Log::record('添加Banner', 0, '添加Banner失败', 'Banner管理'); + return json(['code' => 1, 'msg' => '添加Banner失败']); + } + Log::record('添加Banner', 1, '', 'Banner管理'); + return json(['code' => 0, 'msg' => '添加成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 编辑banner + public function banneredit() + { + if (Request::isPost()) { + $id = input('post.id'); + if (empty($id)) { + Log::record('编辑Banner', 0, 'ID不能为空', 'Banner管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $data = [ + 'title' => input('post.title'), + 'image' => input('post.image'), + 'url' => input('post.url'), + 'sort' => input('post.sort', 0), + 'update_time' => time() + ]; + + $res = Banner::where('id', $id)->update($data); + if ($res === false) { + Log::record('编辑Banner', 0, '更新Banner失败', 'Banner管理'); + return json(['code' => 1, 'msg' => '更新Banner失败']); + } + Log::record('编辑Banner', 1, '', 'Banner管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 删除banner + public function bannerdel() + { + if (Request::isPost()) { + $id = input('post.id'); + if (empty($id)) { + Log::record('删除Banner', 0, 'ID不能为空', 'Banner管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $res = Banner::where('id', $id)->update(['delete_time' => time()]); + if (!$res) { + Log::record('删除Banner', 0, '删除Banner失败', 'Banner管理'); + return json(['code' => 1, 'msg' => '删除Banner失败']); + } + Log::record('删除Banner', 1, '', 'Banner管理'); + return json(['code' => 0, 'msg' => '删除成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 修改banner状态 + public function bannerstatus() + { + if (Request::isPost()) { + $id = input('post.id'); + $status = input('post.status'); + + if (empty($id)) { + Log::record('修改Banner状态', 0, 'ID不能为空', 'Banner管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $res = Banner::where('id', $id)->update(['status' => $status]); + if ($res === false) { + Log::record('修改Banner状态', 0, '更新状态失败', 'Banner管理'); + return json(['code' => 1, 'msg' => '更新状态失败']); + } + Log::record('修改Banner状态', 1, '', 'Banner管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + //内容推送 + public function contentpush() + { + return View::fetch(); + } + + // 内容推送列表 + public function contentpushlist() + { + if (Request::isGet()) { + // 获取分页参数 + $page = (int) input('get.page', 1); + $limit = (int) input('get.limit', 10); + + // 1. 获取 Articles 表(delete_time为空,status=2) + $articles = Articles::where('delete_time', null) + ->where('status', 2) + ->field('id, title, push, create_time') + ->select() + ->toArray(); + foreach ($articles as &$a) { + $a['type'] = 'article'; + $a['create_time'] = is_numeric($a['create_time']) ? date('Y-m-d H:i:s', $a['create_time']) : $a['create_time']; + } + unset($a); + + // 2. 获取 Resource 表(delete_time为空,status=1) + $resources = Resource::where('delete_time', null) + ->where('status', 1) + ->field('id, title, push, create_time') + ->select() + ->toArray(); + foreach ($resources as &$r) { + $r['type'] = 'resource'; + $r['create_time'] = is_numeric($r['create_time']) ? date('Y-m-d H:i:s', $r['create_time']) : $r['create_time']; + } + unset($r); + + // 3. 合并 + $lists = array_merge($articles, $resources); + + // 4. 优先显示push为0的数据,再按create_time倒序排序 + usort($lists, function ($a, $b) { + // push为0的排在前面 + if ($a['push'] != $b['push']) { + return $a['push'] - $b['push']; + } + // push相同则按create_time倒序 + return strtotime($b['create_time']) <=> strtotime($a['create_time']); + }); + + // 5. 分页 + $total = count($lists); + $offset = ($page - 1) * $limit; + $data = array_slice($lists, $offset, $limit); + + return json([ + 'code' => 0, + 'msg' => '', + 'count' => $total, + 'data' => $data + ]); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 添加内容推送 + public function contentpushadd() + { + if (Request::isPost()) { + $data = [ + 'title' => input('post.title'), + 'content' => input('post.content'), + 'image' => input('post.image'), + 'url' => input('post.url'), + 'type' => input('post.type', 1), + 'status' => input('post.status', 1), + 'sort' => input('post.sort', 0), + 'create_time' => time() + ]; + + $res = ContentPush::insert($data); + if (!$res) { + Log::record('添加内容推送', 0, '添加内容推送失败', '内容推送管理'); + return json(['code' => 1, 'msg' => '添加内容推送失败']); + } + Log::record('添加内容推送', 1, '', '内容推送管理'); + return json(['code' => 0, 'msg' => '添加成功']); + } else { + return View::fetch(); + } + } + + // 编辑内容推送 + public function contentpushedit() + { + if (Request::isPost()) { + $id = input('post.id'); + if (empty($id)) { + Log::record('编辑内容推送', 0, 'ID不能为空', '内容推送管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $data = [ + 'title' => input('post.title'), + 'content' => input('post.content'), + 'image' => input('post.image'), + 'url' => input('post.url'), + 'type' => input('post.type', 1), + 'status' => input('post.status', 1), + 'sort' => input('post.sort', 0), + 'update_time' => time() + ]; + + $res = ContentPush::where('id', $id)->update($data); + if ($res === false) { + Log::record('编辑内容推送', 0, '更新内容推送失败', '内容推送管理'); + return json(['code' => 1, 'msg' => '更新内容推送失败']); + } + Log::record('编辑内容推送', 1, '', '内容推送管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 删除内容推送 + public function contentpushdel() + { + if (Request::isPost()) { + $id = input('post.id'); + if (empty($id)) { + Log::record('删除内容推送', 0, 'ID不能为空', '内容推送管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $res = ContentPush::where('id', $id)->update(['delete_time' => time()]); + if (!$res) { + Log::record('删除内容推送', 0, '删除内容推送失败', '内容推送管理'); + return json(['code' => 1, 'msg' => '删除内容推送失败']); + } + Log::record('删除内容推送', 1, '', '内容推送管理'); + return json(['code' => 0, 'msg' => '删除成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 修改内容推送状态 + public function contentpushstatus() + { + if (Request::isPost()) { + $id = input('post.id'); + $status = input('post.status'); + + if (empty($id)) { + Log::record('修改内容推送状态', 0, 'ID不能为空', '内容推送管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $res = ContentPush::where('id', $id)->update(['status' => $status]); + if ($res === false) { + Log::record('修改内容推送状态', 0, '更新状态失败', '内容推送管理'); + return json(['code' => 1, 'msg' => '更新状态失败']); + } + Log::record('修改内容推送状态', 1, '', '内容推送管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + //推送配置列表(渲染列表) + public function contentpushsetting() + { + if (Request::isAjax() || Request::isPost()) { + $page = intval(input('get.page', 1)); + $limit = intval(input('get.limit', 10)); + + $query = ContentPushSetting::where('delete_time', null) + ->field('id, title, value, platformType, status, sort, create_time'); + + $count = $query->count(); + + $lists = $query->order(['sort' => 'DESC', 'id' => 'DESC']) + ->page($page, $limit) + ->select() + ->toArray(); + + foreach ($lists as &$item) { + $item['create_time'] = is_numeric($item['create_time']) ? date('Y-m-d H:i:s', $item['create_time']) : $item['create_time']; + } + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'count' => $count, + 'data' => $lists + ]); + } else { + return View::fetch(); + } + } + + //选择列出推送内容列表 + public function selectpushcontent() + { + $pushcate = strtolower(trim(input('get.pushcate', ''))); + $where = ['push' => 0]; + + $modelMap = [ + 'article' => Articles::class, + 'resource' => Resource::class, + ]; + + if (!isset($modelMap[$pushcate])) { + return json(['code' => 1, 'msg' => '参数错误: pushcate']); + } + + $model = $modelMap[$pushcate]; + + $query = $model::where($where)->field('title,id'); + $count = $query->count(); + $list = $query->order('id', 'desc')->select()->toArray(); + + // 判断是接口请求还是页面请求 + if (request()->isAjax() || request()->isJson()) { + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'count' => $count, + 'data' => $list + ]); + } else { + // 这里传递变量到模板 + return View::fetch('', [ + 'data' => $list + ]); + } + } + + //推送配置添加和编辑通用方法 + public function contentpushsettingadd() + { + if (Request::isPost()) { + $params = input('post.'); + $id = isset($params['id']) ? intval($params['id']) : 0; + + if ($id > 0) { + // 编辑 + $res = ContentPushSetting::update($params, ['id' => $id]); + if ($res === false) { + Log::record('编辑推送配置', 0, '编辑推送配置失败', '推送配置管理'); + return json(['code' => 1, 'msg' => '编辑推送配置失败']); + } + Log::record('编辑推送配置', 1, '', '推送配置管理'); + return json(['code' => 0, 'msg' => '编辑成功']); + } else { + // 添加 + $res = ContentPushSetting::create($params); + if (!$res) { + Log::record('添加推送配置', 0, '添加推送配置失败', '推送配置管理'); + return json(['code' => 1, 'msg' => '添加推送配置失败']); + } + Log::record('添加推送配置', 1, '', '推送配置管理'); + return json(['code' => 0, 'msg' => '添加成功']); + } + } else { + $id = input('get.id', 0); + $info = []; + if ($id) { + $info = ContentPushSetting::where('id', $id)->find(); + if ($info) { + $info = $info->toArray(); + } + } + return View::fetch('', ['info' => $info]); + } + } + + //推送配置软删除 + public function contentpushsettingdel() + { + if (Request::isPost()) { + $id = intval(input('post.id', 0)); + if (!$id) { + return json(['code' => 1, 'msg' => '参数错误']); + } + $setting = ContentPushSetting::where('id', $id)->find(); + if (!$setting) { + return json(['code' => 1, 'msg' => '配置不存在']); + } + $res = ContentPushSetting::where('id', $id)->update(['delete_time' => date('Y-m-d H:i:s')]); + if ($res === false) { + Log::record('删除推送配置', 0, '删除失败', '推送配置管理'); + return json(['code' => 1, 'msg' => '删除失败']); + } + Log::record('删除推送配置', 1, '', '推送配置管理'); + return json(['code' => 0, 'msg' => '删除成功']); + } + return json(['code' => 1, 'msg' => '请求方式错误']); + } + + //素材中心 + public function materialcenter() + { + return View::fetch(); + } + // 内容推送列表 + public function materialcenterlist() + { + if (Request::isGet()) { + $page = intval(input('post.page', 1)); + $limit = intval(input('post.limit', 10)); + + $query = ContentPush::where('delete_time', null) + ->field('id, title, type, status, sort, create_time, update_time'); + + // 获取总记录数 + $count = $query->count(); + + // 获取分页数据 + $lists = $query->order(['sort DESC', 'id DESC']) + ->page($page, $limit) + ->select() + ->toArray(); + + // 处理数据 + foreach ($lists as &$item) { + $item['create_time'] = is_numeric($item['create_time']) ? date('Y-m-d H:i:s', $item['create_time']) : $item['create_time']; + $item['update_time'] = is_numeric($item['update_time']) ? date('Y-m-d H:i:s', $item['update_time']) : $item['update_time']; + } + + return json([ + 'code' => 0, + 'msg' => '', + 'count' => $count, + 'data' => $lists + ]); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + // 添加内容推送 + public function materialcenteradd() + { + if (Request::isPost()) { + $data = [ + 'title' => input('post.title'), + 'content' => input('post.content'), + 'image' => input('post.image'), + 'url' => input('post.url'), + 'type' => input('post.type', 1), + 'status' => input('post.status', 1), + 'sort' => input('post.sort', 0), + 'create_time' => time() + ]; + + $res = ContentPush::insert($data); + if (!$res) { + Log::record('添加内容推送', 0, '添加内容推送失败', '内容推送管理'); + return json(['code' => 1, 'msg' => '添加内容推送失败']); + } + Log::record('添加内容推送', 1, '', '内容推送管理'); + return json(['code' => 0, 'msg' => '添加成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 编辑内容推送 + public function materialcenteredit() + { + if (Request::isPost()) { + $id = input('post.id'); + if (empty($id)) { + Log::record('编辑内容推送', 0, 'ID不能为空', '内容推送管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $data = [ + 'title' => input('post.title'), + 'content' => input('post.content'), + 'image' => input('post.image'), + 'url' => input('post.url'), + 'type' => input('post.type', 1), + 'status' => input('post.status', 1), + 'sort' => input('post.sort', 0), + 'update_time' => time() + ]; + + $res = ContentPush::where('id', $id)->update($data); + if ($res === false) { + Log::record('编辑内容推送', 0, '更新内容推送失败', '内容推送管理'); + return json(['code' => 1, 'msg' => '更新内容推送失败']); + } + Log::record('编辑内容推送', 1, '', '内容推送管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 删除内容推送 + public function materialcenterdel() + { + if (Request::isPost()) { + $id = input('post.id'); + if (empty($id)) { + Log::record('删除内容推送', 0, 'ID不能为空', '内容推送管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $res = ContentPush::where('id', $id)->update(['delete_time' => time()]); + if (!$res) { + Log::record('删除内容推送', 0, '删除内容推送失败', '内容推送管理'); + return json(['code' => 1, 'msg' => '删除内容推送失败']); + } + Log::record('删除内容推送', 1, '', '内容推送管理'); + return json(['code' => 0, 'msg' => '删除成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + // 修改内容推送状态 + public function materialcenterstatus() + { + if (Request::isPost()) { + $id = input('post.id'); + $status = input('post.status'); + + if (empty($id)) { + Log::record('修改内容推送状态', 0, 'ID不能为空', '内容推送管理'); + return json(['code' => 1, 'msg' => 'ID不能为空']); + } + + $res = ContentPush::where('id', $id)->update(['status' => $status]); + if ($res === false) { + Log::record('修改内容推送状态', 0, '更新状态失败', '内容推送管理'); + return json(['code' => 1, 'msg' => '更新状态失败']); + } + Log::record('修改内容推送状态', 1, '', '内容推送管理'); + return json(['code' => 0, 'msg' => '更新成功']); + } + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + + public function gopush() + { + $platformType = input('post.platformType'); + $urls = input('post.urls/a', []); + + if (empty($platformType)) { + return json(['code' => 1, 'msg' => '平台参数缺失']); + } + if (empty($urls)) { + return json(['code' => 1, 'msg' => '推送内容url为空']); + } + + // 查找推送平台配置 + $setting = ContentPushSetting::where('platformType', $platformType)->find(); + if (!$setting) { + return json(['code' => 1, 'msg' => '未找到该平台的推送配置']); + } + if ($setting['status'] == 0) { + return json(['code' => 1, 'msg' => '该平台推送已禁用']); + } + $api = $setting['value']; + if (empty($api)) { + return json(['code' => 1, 'msg' => '推送API地址未配置']); + } + + // 推送 + $ch = curl_init(); + $options = array( + CURLOPT_URL => $api, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => implode("\n", $urls), // 多行文本,每行一个url + CURLOPT_HTTPHEADER => array('Content-Type: text/plain'), + ); + curl_setopt_array($ch, $options); + $result = curl_exec($ch); + $error = curl_error($ch); + curl_close($ch); + + if ($error) { + return json(['code' => 1, 'msg' => $error, 'result' => $result]); + } + + // 解析推送返回结果 + $resultArr = json_decode($result, true); + + // 判断返回是否有error字段 + if (is_array($resultArr) && isset($resultArr['error'])) { + return json([ + 'code' => 1, + 'msg' => '推送失败: ' . (isset($resultArr['message']) ? $resultArr['message'] : '未知错误'), + 'result' => $resultArr + ]); + } + + // 判断是否有success字段,只有有success字段才认为推送成功 + if (is_array($resultArr) && isset($resultArr['success'])) { + // 推送成功后,更新对应数据的push字段为1 + // 解析urls,分别更新Articles或Resource表 + foreach ($urls as $url) { + // 匹配文章 + if (preg_match('/\/index\/articles\/detail\?id=(\d+)/', $url, $matches)) { + $id = intval($matches[1]); + if ($id > 0) { + Articles::where('id', $id)->update(['push' => 1]); + } + } + // 匹配资源 + elseif (preg_match('/\/index\/resources\/detail\?id=(\d+)/', $url, $matches)) { + $id = intval($matches[1]); + if ($id > 0) { + Resource::where('id', $id)->update(['push' => 1]); + } + } + } + return json(['code' => 0, 'msg' => '推送成功', 'result' => $resultArr]); + } else { + // 没有success字段,视为推送失败,把失败的result反馈给前端 + return json([ + 'code' => 1, + 'msg' => '推送失败', + 'result' => $resultArr !== null ? $resultArr : $result + ]); + } + } + + +} \ No newline at end of file diff --git a/app/admin/controller/Yunzertest.php b/app/admin/controller/Yunzertest.php new file mode 100644 index 0000000..ebc5b7a --- /dev/null +++ b/app/admin/controller/Yunzertest.php @@ -0,0 +1,184 @@ +where('status','=',1)->select(); + View::assign([ + 'lists' => $lists + ]); + return View::fetch(); + } + public function test_list(){ + if(Request::isPost()){ + $count = Db::table('yz_z_test')->count(); + $page = (int)input('post.page',1); + $limit = (int)input('post.limit',10); + $lists = Db::table('yz_z_test')->order('test_id DESC')->page($page,$limit)->select()->each(function($item, $key){ + if($item['test_reference'] == 1){ + $item['test_reference'] = '开启'; + }else{ + $item['test_reference'] = '关闭'; + } + $item['test_time'] = date('Y-m-d H:i:s',$item['test_time']); + return $item; + }); + $this->returnCode(0,$lists,$count); + }else{ + return View::fetch(); + } + } + public function test_add(){ + if(Request::isPost()){ + $data['test_input'] = input('post.test_input'); + $data['test_reference'] = input('post.test_reference'); + $data['test_time'] = input('post.test_time'); + if(!empty($data['test_time'])){ + $data['test_time'] = strtotime($data['test_time']); + } + $data['test_data'] = input('post.test_data'); + $data['test_datatime'] = input('post.test_datatime'); + $data['test_img'] = input('post.test_img'); + $data['test_rich_baidu'] = input('post.test_rich_baidu'); + $data['test_url'] = input('post.test_url'); + + $insert = Db::table('yz_z_test')->insert($data); + if(empty($insert)){ + $this->returnCode('91000001'); + } + $this->returnCode(0); + }else{ + return View::fetch(); + } + } + public function test_edit(){ + if(Request::isPost()){ + $test_id = input('post.test_id'); + $data['test_input'] = input('post.test_input'); + $data['test_reference'] = input('post.test_reference'); + $data['test_time'] = input('post.test_time'); + if(!empty($data['test_time'])){ + $data['test_time'] = strtotime($data['test_time']); + } + $data['test_data'] = input('post.test_data'); + $data['test_datatime'] = input('post.test_datatime'); + $data['test_img'] = input('post.test_img'); + $data['test_rich'] = input('post.test_rich'); + $data['test_rich_baidu'] = input('post.test_rich_baidu'); + $data['test_url'] = input('post.test_url'); + + $update = Db::table('yz_z_test')->where('test_id',$test_id)->update($data); + if(empty($update)){ + $this->returnCode('91000002'); + } + $this->returnCode(0); + }else{ + $test_id = input('get.test_id'); + $find = Db::table('yz_z_test')->where('test_id',$test_id)->find(); + if(!empty($find)){ + $find['test_time'] = date('Y-m-d H:i:s',$find['test_time']); + } + View::assign([ + 'find' => $find + ]); + return View::fetch(); + } + } + public function test_del(){ + $test_id = (int)input('post.test_id'); + $res = Db::table('yz_z_test')->where('test_id',$test_id)->delete(); + if(empty($res)){ + $this->returnCode('91000003'); + } + $this->returnCode(0); + } + public function test_static_list(){ + $lists = Db::table('yz_z_test') + ->order('test_id DESC') + ->paginate(); + View::assign([ + 'lists' => $lists + ]); + return View::fetch(); + } + public function test_static_add(){ + if(Request::isPost()){ + $data['test_input'] = input('post.test_input'); + $data['test_reference'] = input('post.test_reference'); + $data['test_time'] = input('post.test_time'); + if(!empty($data['test_time'])){ + $data['test_time'] = strtotime($data['test_time']); + } + $data['test_data'] = input('post.test_data'); + $data['test_datatime'] = input('post.test_datatime'); + $data['test_img'] = input('post.test_img'); + $data['test_rich_baidu'] = input('post.test_rich_baidu'); + $data['test_url'] = input('post.test_url'); + + $insert = Db::table('yz_z_test')->insert($data); + if(empty($insert)){ + $this->returnCode('91000001'); + } + $this->returnCode(0); + }else{ + return View::fetch(); + } + } + public function test_static_edit(){ + if(Request::isPost()){ + $test_id = input('post.test_id'); + $data['test_input'] = input('post.test_input'); + $data['test_reference'] = input('post.test_reference'); + $data['test_time'] = input('post.test_time'); + if(!empty($data['test_time'])){ + $data['test_time'] = strtotime($data['test_time']); + } + $data['test_data'] = input('post.test_data'); + $data['test_datatime'] = input('post.test_datatime'); + $data['test_img'] = input('post.test_img'); + $data['test_rich'] = input('post.test_rich'); + $data['test_rich_baidu'] = input('post.test_rich_baidu'); + $data['test_url'] = input('post.test_url'); + + $update = Db::table('yz_z_test')->where('test_id',$test_id)->update($data); + if(empty($update)){ + $this->returnCode('91000002'); + } + $this->returnCode(0); + }else{ + $test_id = input('get.test_id'); + $find = Db::table('yz_z_test')->where('test_id',$test_id)->find(); + if(!empty($find)){ + $find['test_time'] = date('Y-m-d H:i:s',$find['test_time']); + if(!empty($find['test_img'])){ + $find['test_img_s'] = explode(';',$find['test_img']); + } + } + View::assign([ + 'find' => $find + ]); + return View::fetch(); + } + } +} \ No newline at end of file diff --git a/app/admin/model/AdminConfig.php b/app/admin/model/AdminConfig.php new file mode 100644 index 0000000..bd6216b --- /dev/null +++ b/app/admin/model/AdminConfig.php @@ -0,0 +1,25 @@ +order('type', 'asc') + ->order('sort', 'desc') + ->select() + ->toArray(); + + $menuTree = []; + + // 先处理所有父菜单 + foreach ($menus as $menu) { + if ($menu['parent_id'] == 0) { + $menuTree[$menu['smid']] = $menu; + $menuTree[$menu['smid']]['children'] = []; + } + } + + // 再处理子菜单 + foreach ($menus as $menu) { + if ($menu['parent_id'] != 0 && isset($menuTree[$menu['parent_id']])) { + $menuTree[$menu['parent_id']]['children'][] = $menu; + } + } + + return array_values($menuTree); + } +} diff --git a/app/admin/model/AdminUser.php b/app/admin/model/AdminUser.php new file mode 100644 index 0000000..dc1d6aa --- /dev/null +++ b/app/admin/model/AdminUser.php @@ -0,0 +1,25 @@ + logs($data,$fileName); + } + } + $info = ['data'=>$data]; + $content = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; + if(empty($fileName)){ + $filePath = $path . "/" . date("Ymd",time()).'.info.log'; + }else{ + $filePath = $path . "/" . $fileName . '.info.log'; + } + $time = "[".date("Y-m-d H:i:s",time())."]"; + $re = file_put_contents($filePath, $time." ".$content , FILE_APPEND); + if(!$re){ + $this -> logs($data,$fileName); + }else{ + return true; + } + } +} \ No newline at end of file diff --git a/app/admin/model/ContentPush/ContentPush.php b/app/admin/model/ContentPush/ContentPush.php new file mode 100644 index 0000000..652375d --- /dev/null +++ b/app/admin/model/ContentPush/ContentPush.php @@ -0,0 +1,34 @@ +order('config_sort DESC')->select()->toArray(); + if(empty($aList)){ + return []; + }else{ + $return = []; + foreach($aList as $k=>$v){ + $return[$v['config_name']] = $v['config_value']; + } + } + return $return; + } + + /** + * 多条数据更新 + */ + public function updateAll($data){ + $lists = static::order('config_sort DESC,config_id')->select()->toArray(); + if(empty($lists)){ + return false; + }else{ + foreach($lists as &$lists_v){ + $lists_v['config_value'] = $data[$lists_v['config_name']]; + } + $save = static::saveAll($lists); + if(empty($save)){ + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/app/admin/model/ZIconfont.php b/app/admin/model/ZIconfont.php new file mode 100644 index 0000000..419ae18 --- /dev/null +++ b/app/admin/model/ZIconfont.php @@ -0,0 +1,25 @@ + +
+
+ 添加文章 +
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + + +
+ +
+ +
+
+ +
+
+
+
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+ + + +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/app/admin/view/articles/articlecate.php b/app/admin/view/articles/articlecate.php new file mode 100644 index 0000000..7139c16 --- /dev/null +++ b/app/admin/view/articles/articlecate.php @@ -0,0 +1,525 @@ +{include file="public/header" /} +
+ +
+
+ + 文章分类管理 +
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ 分类列表 + 支持两级分类结构 +
+
+
+
+
+
+ + +
+
+
+ 分类信息 +
+
+ +
+ +

请选择左侧分类或点击新增按钮

+
+ + + +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/app/admin/view/articles/articlelist.php b/app/admin/view/articles/articlelist.php new file mode 100644 index 0000000..4477daa --- /dev/null +++ b/app/admin/view/articles/articlelist.php @@ -0,0 +1,204 @@ +{include file="public/header" /} +
+ +
+
+ + 文章列表 +
+
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+
+ + +
+
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/app/admin/view/articles/cateadd.php b/app/admin/view/articles/cateadd.php new file mode 100644 index 0000000..8b19156 --- /dev/null +++ b/app/admin/view/articles/cateadd.php @@ -0,0 +1,77 @@ +{include file="public/header" /} +
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/app/admin/view/articles/cateedit.php b/app/admin/view/articles/cateedit.php new file mode 100644 index 0000000..5cb7186 --- /dev/null +++ b/app/admin/view/articles/cateedit.php @@ -0,0 +1,154 @@ +{include file="public/header" /} +
+
+ +
+ +
+ +
+
+ +
+
+
+
+
+ +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/app/admin/view/articles/edit.php b/app/admin/view/articles/edit.php new file mode 100644 index 0000000..a99019f --- /dev/null +++ b/app/admin/view/articles/edit.php @@ -0,0 +1,313 @@ +{include file="public/header" /} +
+
+
+ 编辑文章 +
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + + +
+ +
+ +
+
+ +
+
+
+
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/app/admin/view/index/index.php b/app/admin/view/index/index.php new file mode 100644 index 0000000..9fd99b0 --- /dev/null +++ b/app/admin/view/index/index.php @@ -0,0 +1,424 @@ + + + + + + {$config['web_title']} + + + + + + + + + + + +
+
+
+ +
+ + +
+
+ +
+
+ + +
+ POWER BY 云泽网 +
+
+
+ +
+
+ +
+ +
+ +
+
    +
    +
    +
    +
    + +
    +
    +
    + +
    + + + + + + \ No newline at end of file diff --git a/app/admin/view/index/welcome.php b/app/admin/view/index/welcome.php new file mode 100644 index 0000000..04c91e0 --- /dev/null +++ b/app/admin/view/index/welcome.php @@ -0,0 +1,725 @@ +{include file="public/header" /} +
    +
    +

    欢迎使用{$config['web_title']}

    +

    今天是 ,祝您工作愉快

    +
    + +
    +
    +
    用户总数
    +
    {$todayStats.total_users|number_format}
    +
    👥
    +
    +
    +
    今日访问
    +
    {$todayStats.daily_visits|number_format}
    +
    📊
    +
    +
    +
    文章总数
    +
    {$todayStats.total_articles|number_format}
    +
    📝
    +
    + +
    + +
    +

    快捷操作

    + +
    + +
    +

    最近动态

    +
    + {volist name="recentActivities" id="activity"} +
    +
    {$activity.icon|default='📌'}
    +
    +
    {$activity.content}
    +
    +
    + {/volist} +
    +
    +
    +
    +

    访问趋势

    +
    +
    +
    +

    用户增长

    +
    +
    + +
    +

    文章统计

    +
    +
    +
    +
    + + + + + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/log/login.php b/app/admin/view/log/login.php new file mode 100644 index 0000000..109882a --- /dev/null +++ b/app/admin/view/log/login.php @@ -0,0 +1,117 @@ +{include file="public/header" /} +
    +
    + 登录日志 +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/app/admin/view/log/operation.php b/app/admin/view/log/operation.php new file mode 100644 index 0000000..2075484 --- /dev/null +++ b/app/admin/view/log/operation.php @@ -0,0 +1,173 @@ +{include file="public/header" /} +
    +
    + 操作日志 +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/app/admin/view/login/index.php b/app/admin/view/login/index.php new file mode 100644 index 0000000..7400829 --- /dev/null +++ b/app/admin/view/login/index.php @@ -0,0 +1,130 @@ + + + + + 后台管理系统 + + + + + + + + + +
    +
    + + +
    +
    + + + + \ No newline at end of file diff --git a/app/admin/view/login/resetpwd.php b/app/admin/view/login/resetpwd.php new file mode 100644 index 0000000..0f2815d --- /dev/null +++ b/app/admin/view/login/resetpwd.php @@ -0,0 +1,79 @@ + + + + + + 重置密码 + + + + + + + + + +
    +
    + + +
    +
    + + + + + + \ No newline at end of file diff --git a/app/admin/view/public/header.php b/app/admin/view/public/header.php new file mode 100644 index 0000000..814978e --- /dev/null +++ b/app/admin/view/public/header.php @@ -0,0 +1,94 @@ + + + + {$config['web_title']} + + + + + + + + + + + \ No newline at end of file diff --git a/app/admin/view/public/tail.php b/app/admin/view/public/tail.php new file mode 100644 index 0000000..8738b98 --- /dev/null +++ b/app/admin/view/public/tail.php @@ -0,0 +1,30 @@ + + + \ No newline at end of file diff --git a/app/admin/view/resources/add.php b/app/admin/view/resources/add.php new file mode 100644 index 0000000..3748958 --- /dev/null +++ b/app/admin/view/resources/add.php @@ -0,0 +1,571 @@ +{include file="public/header" /} +
    +
    +
    + 添加资源 +
    +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    建议尺寸:250px * 140px
    +
    +
    + +
    + +
    +
    + +
    + 预览图: +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    + +
    + +
    点击上传,或将文件拖拽到此处
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/app/admin/view/resources/cate.php b/app/admin/view/resources/cate.php new file mode 100644 index 0000000..ff9c0d4 --- /dev/null +++ b/app/admin/view/resources/cate.php @@ -0,0 +1,533 @@ +{include file="public/header" /} +
    + +
    +
    + + 资源分类管理 +
    +
    +
    + + +
    +
    +
    + + +
    +
    + +
    +
    +
    + 分类列表 + 支持两级分类结构 +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + 分类信息 +
    +
    + +
    + +

    请选择左侧分类或点击新增按钮

    +
    + + + +
    +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/app/admin/view/resources/edit.php b/app/admin/view/resources/edit.php new file mode 100644 index 0000000..1975e38 --- /dev/null +++ b/app/admin/view/resources/edit.php @@ -0,0 +1,872 @@ +{include file="public/header" /} +
    +
    +
    + 编辑资源 +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    建议尺寸:250px * 140px
    +
    +
    + +
    + +
    +
    + +
    + {if condition="isset($resource.images) && !empty($resource.images)"} +
    + + + + + + + + + {php} + // 处理图片字符串,正确拆分逗号分隔的路径 + $imageArray = []; + if (isset($resource['images']) && !empty($resource['images'])) { + $imageArray = explode(',', $resource['images']); + $imageArray = array_map('trim', $imageArray); // 去除每个路径的前后空格 + $imageArray = array_filter($imageArray); // 过滤空值 + } + {/php} + {volist name="imageArray" id="image"} + + + + + {/volist} + +
    缩略图操作
    +
    + 缩略图 +
    +
    + +
    +
    + {/if} +
    + + +
    +
    +
    + +
    + +
    + +
    + +
    点击上传,或将文件拖拽到此处
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    +
    + + + + + + + + + + + + \ No newline at end of file diff --git a/app/admin/view/resources/lists.php b/app/admin/view/resources/lists.php new file mode 100644 index 0000000..c838b41 --- /dev/null +++ b/app/admin/view/resources/lists.php @@ -0,0 +1,207 @@ +{include file="public/header" /} +
    + +
    +
    + + 资源列表 +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + + +
    +
    +
    + + +
    +
    +
    + +
    +
    + + + + + + + + \ No newline at end of file diff --git a/app/admin/view/yunzer/buttonadd.php b/app/admin/view/yunzer/buttonadd.php new file mode 100644 index 0000000..85d6c25 --- /dev/null +++ b/app/admin/view/yunzer/buttonadd.php @@ -0,0 +1,94 @@ +{include file="public/header" /} +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/buttonedit.php b/app/admin/view/yunzer/buttonedit.php new file mode 100644 index 0000000..b2f49ba --- /dev/null +++ b/app/admin/view/yunzer/buttonedit.php @@ -0,0 +1,98 @@ +{include file="public/header" /} +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/buttoninfo.php b/app/admin/view/yunzer/buttoninfo.php new file mode 100644 index 0000000..9d36b1e --- /dev/null +++ b/app/admin/view/yunzer/buttoninfo.php @@ -0,0 +1,155 @@ +{include file="public/header" /} +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    菜单结构
    + +
    +
    +
    + + + + + + + + + + + + + {volist name="lists" id='vo'} + + + + + + + + + {/volist} + +
    排序类型按钮名图标状态操作
    {$vo['sort']} + {if($vo['type']==1)} + 功能模块 {$vo['src']} + {elseif($vo['type']==2)/} + 超链接 {$vo['src']} + {/if} + {$vo['label']} {$vo['icon_class']} + {if($vo['status']==1)} + 开启 + {else/} + 禁用 + {/if} + +
    + + +
    +
    +
    +
    +
    +
    + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/configadd.php b/app/admin/view/yunzer/configadd.php new file mode 100644 index 0000000..fb1ffb1 --- /dev/null +++ b/app/admin/view/yunzer/configadd.php @@ -0,0 +1,47 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/configedit.php b/app/admin/view/yunzer/configedit.php new file mode 100644 index 0000000..a14d7e3 --- /dev/null +++ b/app/admin/view/yunzer/configedit.php @@ -0,0 +1,47 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/configlist.php b/app/admin/view/yunzer/configlist.php new file mode 100644 index 0000000..91577f3 --- /dev/null +++ b/app/admin/view/yunzer/configlist.php @@ -0,0 +1,307 @@ +{include file="public/header" /} + + +
    +
    + 站点管理 + +
    + + +
    +
    + + + +
    +
    +
    +
    + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/configvalue.php b/app/admin/view/yunzer/configvalue.php new file mode 100644 index 0000000..90968c8 --- /dev/null +++ b/app/admin/view/yunzer/configvalue.php @@ -0,0 +1,222 @@ +{include file="public/header" /} + + +
    +
    + 站点配置 + +
    + +
    + {foreach($lists as $lists_v)} + {if($lists_v['config_type'] == 1)} +
    + +
    + +
    +
    {$lists_v['config_desc']}
    +
    + {elseif($lists_v['config_type'] == 2)/} +
    + +
    + +
    +
    {$lists_v['config_desc']}
    +
    +
    + +
    +
    + {if !empty($lists_v['config_value'])} +
    + + +
    + {/if} +
    +
    +
    + + {elseif($lists_v['config_type'] == 3)/} +
    + +
    + +
    +
    + {elseif($lists_v['config_type'] == 4)/} +
    + +
    + + +
    +
    {$lists_v['config_desc']}
    +
    + {/if} + {/foreach} +
    + +
    + +
    +
    + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/mailconfig.php b/app/admin/view/yunzer/mailconfig.php new file mode 100644 index 0000000..dbaf1c3 --- /dev/null +++ b/app/admin/view/yunzer/mailconfig.php @@ -0,0 +1,105 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + + +
    +
    +
    + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/menuadd.php b/app/admin/view/yunzer/menuadd.php new file mode 100644 index 0000000..9fe8528 --- /dev/null +++ b/app/admin/view/yunzer/menuadd.php @@ -0,0 +1,95 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + + +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/menuedit.php b/app/admin/view/yunzer/menuedit.php new file mode 100644 index 0000000..cd130ac --- /dev/null +++ b/app/admin/view/yunzer/menuedit.php @@ -0,0 +1,185 @@ +{include file="public/header" /} +
    + +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +
    + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzer/menuinfo.php b/app/admin/view/yunzer/menuinfo.php new file mode 100644 index 0000000..d6e8974 --- /dev/null +++ b/app/admin/view/yunzer/menuinfo.php @@ -0,0 +1,120 @@ +{include file="public/header" /} +
    + +
    +
    + + 菜单管理 +
    +
    +
    + + +
    +
    +
    + +
    + + + + + + + + + + + + + {volist name="lists" id='vo'} + + + + + + + + + {/volist} + +
    类型菜单名图标排序状态操作
    + {if($vo['type']==1)} + 功能模块:{$vo['src']} + {elseif($vo['type']==2)/} + 超链接:{$vo['src']} + {else/} + 顶级 + {/if} + {$vo['label']} {$vo['icon_class']}{$vo['sort']}{$vo['status']==1?'开启':'禁用'} + {if($vo['type'] == 0)} + + {/if} + + +
    + +
    + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/admininfo.php b/app/admin/view/yunzeradmin/admininfo.php new file mode 100644 index 0000000..e5dba55 --- /dev/null +++ b/app/admin/view/yunzeradmin/admininfo.php @@ -0,0 +1,109 @@ +{include file="public/header" /} +
    +
    + +
    +
    +
    + +
    +
    {$aUser['account']}
    +
    +
    +
    + +
    +
    账户用于登录系统的账号,不能修改
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    {$aUser['login_count']}次
    +
    +
    +
    + +
    +
    {:date('Y-m-d H:i:s',$aUser['create_time'])}
    +
    +
    +
    + +
    +
    {:date('Y-m-d H:i:s',$aUser['update_time'])}
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/banner.php b/app/admin/view/yunzeradmin/banner.php new file mode 100644 index 0000000..95e456b --- /dev/null +++ b/app/admin/view/yunzeradmin/banner.php @@ -0,0 +1,249 @@ +{include file="public/header" /} + +
    + +
    +
    + + Banner管理 +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + + + +
    + + + + + + + \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/contentpush.php b/app/admin/view/yunzeradmin/contentpush.php new file mode 100644 index 0000000..58d8279 --- /dev/null +++ b/app/admin/view/yunzeradmin/contentpush.php @@ -0,0 +1,231 @@ +{include file="public/header" /} +
    + +
    +
    + + 内容推送列表 +
    +
    +
    + + + + +
    +
    +
    + +
    +
    + + + + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/contentpushadd.php b/app/admin/view/yunzeradmin/contentpushadd.php new file mode 100644 index 0000000..4a7dc0e --- /dev/null +++ b/app/admin/view/yunzeradmin/contentpushadd.php @@ -0,0 +1,174 @@ +{include file="public/header" /} +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + +
    +
    + + + + + + + + + +
    +
    + + +
    +
    +
    +
    + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/contentpushedit.php b/app/admin/view/yunzeradmin/contentpushedit.php new file mode 100644 index 0000000..37689f4 --- /dev/null +++ b/app/admin/view/yunzeradmin/contentpushedit.php @@ -0,0 +1,108 @@ +{include file="public/header" /} +
    +
    + + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + + +{include file="public/tail" /} diff --git a/app/admin/view/yunzeradmin/contentpushsetting.php b/app/admin/view/yunzeradmin/contentpushsetting.php new file mode 100644 index 0000000..0c0eef0 --- /dev/null +++ b/app/admin/view/yunzeradmin/contentpushsetting.php @@ -0,0 +1,109 @@ +{include file="public/header" /} +
    +
    +
    + + 推送配置列表 +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + +{include file="public/tail" /} diff --git a/app/admin/view/yunzeradmin/contentpushsettingadd.php b/app/admin/view/yunzeradmin/contentpushsettingadd.php new file mode 100644 index 0000000..cba0435 --- /dev/null +++ b/app/admin/view/yunzeradmin/contentpushsettingadd.php @@ -0,0 +1,75 @@ +{include file="public/header" /} +
    +
    + {if isset($info.id)} + + {/if} +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/frontuser.php b/app/admin/view/yunzeradmin/frontuser.php new file mode 100644 index 0000000..02dc67d --- /dev/null +++ b/app/admin/view/yunzeradmin/frontuser.php @@ -0,0 +1,181 @@ +{include file="public/header" /} +
    + +
    +
    + + 用户列表 +
    +
    +
    + + +
    +
    +
    + +
    +
    + + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/frontuseradd.php b/app/admin/view/yunzeradmin/frontuseradd.php new file mode 100644 index 0000000..c8b051d --- /dev/null +++ b/app/admin/view/yunzeradmin/frontuseradd.php @@ -0,0 +1,84 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/frontuseredit.php b/app/admin/view/yunzeradmin/frontuseredit.php new file mode 100644 index 0000000..3bd7683 --- /dev/null +++ b/app/admin/view/yunzeradmin/frontuseredit.php @@ -0,0 +1,76 @@ +{include file="public/header" /} +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/groupadd.php b/app/admin/view/yunzeradmin/groupadd.php new file mode 100644 index 0000000..36dbd7c --- /dev/null +++ b/app/admin/view/yunzeradmin/groupadd.php @@ -0,0 +1,53 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + + {volist name="menus" id="vo"} +
    +
    + +
    + {volist name="vo.children" id="cvo"} + + {/volist} +
    + {/volist} +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/groupedit.php b/app/admin/view/yunzeradmin/groupedit.php new file mode 100644 index 0000000..8decfef --- /dev/null +++ b/app/admin/view/yunzeradmin/groupedit.php @@ -0,0 +1,54 @@ +{include file="public/header" /} +
    + +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + + {volist name="menus" id="vo"} +
    +
    + +
    + {volist name="vo.children" id="cvo"} + + {/volist} +
    + {/volist} +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/groupinfo.php b/app/admin/view/yunzeradmin/groupinfo.php new file mode 100644 index 0000000..093d496 --- /dev/null +++ b/app/admin/view/yunzeradmin/groupinfo.php @@ -0,0 +1,100 @@ +{include file="public/header" /} +
    + +
    +
    + + 角色列表 +
    +
    +
    + + +
    +
    +
    + + + + + + + + + + + + {volist name="group" id='vo'} + + + + + + + {/volist} + +
    角色名状态添加时间操作
    {$vo.group_name}{$vo.status==1?'开启':'禁用'}{:date('Y-m-d H:i:s',$vo.create_time)} + + +
    + +
    + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/materialcenter.php b/app/admin/view/yunzeradmin/materialcenter.php new file mode 100644 index 0000000..991d5cd --- /dev/null +++ b/app/admin/view/yunzeradmin/materialcenter.php @@ -0,0 +1,363 @@ +{include file="public/header" /} +
    +
    +
    图片
    +
    视频
    +
    文件
    +
    +
    + +
    +
    + + + +
    +
    +
    + + +
    + + +
    +
    +
    + + 示例图片 +
    示例图片.jpg
    +
    + + + +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    + + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/materialcenteradd.php b/app/admin/view/yunzeradmin/materialcenteradd.php new file mode 100644 index 0000000..e69de29 diff --git a/app/admin/view/yunzeradmin/materialcenteredit.php b/app/admin/view/yunzeradmin/materialcenteredit.php new file mode 100644 index 0000000..e69de29 diff --git a/app/admin/view/yunzeradmin/selectpushcontent.php b/app/admin/view/yunzeradmin/selectpushcontent.php new file mode 100644 index 0000000..af55c76 --- /dev/null +++ b/app/admin/view/yunzeradmin/selectpushcontent.php @@ -0,0 +1,29 @@ +{include file="public/header" /} +
    + + + + + + + + + + + + + +
    标题
    + + + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/useradd.php b/app/admin/view/yunzeradmin/useradd.php new file mode 100644 index 0000000..c8b051d --- /dev/null +++ b/app/admin/view/yunzeradmin/useradd.php @@ -0,0 +1,84 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/useredit.php b/app/admin/view/yunzeradmin/useredit.php new file mode 100644 index 0000000..a5159bb --- /dev/null +++ b/app/admin/view/yunzeradmin/useredit.php @@ -0,0 +1,76 @@ +{include file="public/header" /} +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzeradmin/userinfo.php b/app/admin/view/yunzeradmin/userinfo.php new file mode 100644 index 0000000..f978d62 --- /dev/null +++ b/app/admin/view/yunzeradmin/userinfo.php @@ -0,0 +1,116 @@ +{include file="public/header" /} +
    + +
    +
    + + 用户列表 +
    +
    +
    + + +
    +
    +
    + + + + + + + + + + + + + + + + + + {volist name="lists" id='vo'} + + + + + + + + + + + + + {/volist} + +
    真实姓名账户手机QQ角色性别状态登陆次数登陆时间操作
    {$vo.name}{$vo.account}{$vo.phone}{$vo.qq}{:isset($group[$vo.group_id])?$group[$vo.group_id]['group_name']:''} + {if $vo['sex']==1} + + {elseif $vo['sex'] == 2 /} + + {else /} + 未知 + {/if} + {$vo.status==1?'开启':'禁用'}{$vo.login_count}{:date('Y-m-d H:i:s',$vo.update_time)} + + +
    +
    + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzertest/icon_list.php b/app/admin/view/yunzertest/icon_list.php new file mode 100644 index 0000000..fde0872 --- /dev/null +++ b/app/admin/view/yunzertest/icon_list.php @@ -0,0 +1,82 @@ +{include file="public/header" /} + +
    + 图标 +
    +
    +
    +	
    +	
    +	
    +	
    +	
    +	
    +
    + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzertest/test_add.php b/app/admin/view/yunzertest/test_add.php new file mode 100644 index 0000000..692cb42 --- /dev/null +++ b/app/admin/view/yunzertest/test_add.php @@ -0,0 +1,136 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    支持png,jpg,gif
    +
    +
    + +
    +
    +
    + +
    +
    + +
    + +
    + + + +
    +
    + +
    + +
    +
    +
    +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzertest/test_edit.php b/app/admin/view/yunzertest/test_edit.php new file mode 100644 index 0000000..36e79b1 --- /dev/null +++ b/app/admin/view/yunzertest/test_edit.php @@ -0,0 +1,144 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    支持png,jpg,gif
    +
    +
    + +
    +
    + {if(!empty($find['test_img']))} +
    + + + +
    + {/if} +
    +
    + +
    +
    + +
    + +
    + + + +
    +
    + +
    + +
    +
    +
    +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzertest/test_list.php b/app/admin/view/yunzertest/test_list.php new file mode 100644 index 0000000..bd79f92 --- /dev/null +++ b/app/admin/view/yunzertest/test_list.php @@ -0,0 +1,193 @@ +{include file="public/header" /} +
    + 演示列表-方法渲染 +
    +
    +
    + +
    + +
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzertest/test_static_add.php b/app/admin/view/yunzertest/test_static_add.php new file mode 100644 index 0000000..061377f --- /dev/null +++ b/app/admin/view/yunzertest/test_static_add.php @@ -0,0 +1,140 @@ +{include file="public/header" /} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    支持png,jpg,gif
    +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzertest/test_static_edit.php b/app/admin/view/yunzertest/test_static_edit.php new file mode 100644 index 0000000..403b6cf --- /dev/null +++ b/app/admin/view/yunzertest/test_static_edit.php @@ -0,0 +1,149 @@ +{include file="public/header" /} +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    支持png,jpg,gif
    +
    +
    + +
    +
    + {if(!empty($find['test_img']))} +
    + + + +
    + {/if} +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + + + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/admin/view/yunzertest/test_static_list.php b/app/admin/view/yunzertest/test_static_list.php new file mode 100644 index 0000000..12d240a --- /dev/null +++ b/app/admin/view/yunzertest/test_static_list.php @@ -0,0 +1,104 @@ +{include file="public/header" /} +
    + 演示列表-静态表格 + +
    +
    + + + + + + + + + + + + + + + + + + {volist name="lists" id='vo'} + + + + + + + + + + + + + + {/volist} + +
    ID文本富文本百度文本图片参照时间戳日期日期时间网址链接操作
    {$vo['test_id']}{$vo['test_input']}{$vo['test_rich']}{$vo['test_rich_baidu']} + + + {if($vo['test_reference'] == 1)} + 开启 + {else/} + 关闭 + {/if} + {:date('Y-m-d H:i:s',$vo.test_time)}{$vo['test_data']}{$vo['test_datatime']}{$vo['test_url']} + + +
    +
    {$lists|raw}
    + +{include file="public/tail" /} \ No newline at end of file diff --git a/app/common.php b/app/common.php new file mode 100644 index 0000000..431314f --- /dev/null +++ b/app/common.php @@ -0,0 +1,77 @@ + [ + ], + + 'listen' => [ + 'AppInit' => [], + 'HttpRun' => [], + 'HttpEnd' => [], + 'LogLevel' => [], + 'LogWrite' => [], + ], + + 'subscribe' => [ + ], +]; diff --git a/app/index/common.php b/app/index/common.php new file mode 100644 index 0000000..ba292c2 --- /dev/null +++ b/app/index/common.php @@ -0,0 +1,19 @@ +where('delete_time', null) + ->where('status', 1) + ->select() + ->toArray(); + + // 获取顶级分类信息 + $category = null; + if ($cateid > 0) { + $category = ArticlesCategory::where('id', $cateid) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + } + + // 获取所有子分类 + $subCategories = []; + if ($cateid > 0) { + $subCategories = ArticlesCategory::where('cid', $cateid) + ->where('delete_time', null) + ->where('status', 1) + ->select() + ->toArray(); + } + + // 获取所有子分类ID + $subCategoryIds = array_column($subCategories, 'id'); + if ($cateid > 0) { + $subCategoryIds[] = $cateid; + } + + // 构建文章查询条件 + $where = [ + ['delete_time', '=', null], + ['status', '=', 2] + ]; + + if (!empty($subCategoryIds)) { + $where[] = ['cate', 'in', $subCategoryIds]; + } + + // 查询文章 + $articles = Articles::where($where) + ->order('id DESC') + ->page($page, $limit) + ->select() + ->toArray(); + + // 按子分类分组文章 + $groupedArticles = []; + foreach ($subCategories as $subCategory) { + $groupedArticles[$subCategory['id']] = [ + 'id' => $subCategory['id'], + 'name' => $subCategory['name'], + 'desc' => $subCategory['desc'], + 'image' => $subCategory['image'], + 'list' => [] + ]; + } + + // 将文章分配到对应的子分类 + foreach ($articles as $article) { + if (isset($groupedArticles[$article['cate']])) { + // 如果文章图片为空,使用分类图片 + if (empty($article['image'])) { + $article['image'] = $groupedArticles[$article['cate']]['image']; + } + $groupedArticles[$article['cate']]['list'][] = $article; + } + } + + // 获取总数 + $total = Articles::where($where)->count(); + + // 准备返回数据 + $data = [ + 'cate' => [ + 'id' => $cateid, + 'name' => $category ? $category['name'] : '', + 'desc' => $category ? $category['desc'] : '', + 'image' => $category ? $category['image'] : '', + 'subCategories' => array_values($groupedArticles), + 'total' => $total, + 'page' => $page, + 'limit' => $limit + ] + ]; + + // 根据请求方式返回不同的输出 + if (request()->isPost()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => $data + ]); + } else { + // 为视图准备数据 + $viewData = [ + 'categories' => $categories, + 'cate' => $data['cate'] + ]; + return view('index', $viewData); + } + } + + // 文章列表页 + public function list() + { + // 获取分类ID + $cateId = Request::param('cate/d', 0); + + // 构建查询条件 + $where = [ + ['a.delete_time', '=', null], + ['a.status', '=', 2] + ]; + + if ($cateId > 0) { + $where[] = ['a.cate', '=', $cateId]; + } + + // 获取文章列表 + $articles = Articles::alias('a') + ->join('articles_category c', 'a.cate = c.id') + ->where($where) + ->field([ + 'a.*', + 'IF(a.image IS NULL OR a.image = "", c.image, a.image) as image' + ]) + ->order('a.id DESC') + ->paginate([ + 'list_rows' => 10, + 'query' => Request::instance()->param() + ]); + + // 获取分类信息 + $category = null; + if ($cateId > 0) { + $category = ArticlesCategory::where('id', $cateId) + ->where('delete_time', null) + ->where('status', 3) + ->find(); + } + + // 获取所有分类 + $categories = ArticlesCategory::where('delete_time', null) + ->where('status', 3) + ->select() + ->toArray(); + + // 根据请求方式返回不同的输出 + if (request()->isPost()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'articles' => $articles->items(), + 'category' => $category, + 'categories' => $categories, + 'total' => $articles->total(), + 'current_page' => $articles->currentPage(), + 'per_page' => $articles->listRows() + ] + ]); + } else { + // 将变量传递给视图 + View::assign([ + 'articles' => $articles, + 'category' => $category, + 'categories' => $categories + ]); + return view('list'); + } + } + + // 文章详情页 + public function detail() + { + $id = Request::param('id/d', 0); + $article = Articles::where('id', $id)->find(); + + if (!$article) { + return json(['code' => 0, 'msg' => '文章不存在或已被删除']); + } + + // 获取分类名称 + $cateName = ArticlesCategory::where('id', $article['cate']) + ->value('name'); + + // 获取作者信息 + $authorInfo = Users::where('name', $article['author'])->find(); + if ($authorInfo) { + // 统计作者的文章数 + $articleCount = Articles::where('author', $article['author'])->count(); + // 统计作者的资源数 + $resourceCount = Resources::where('uploader', $article['author'])->count(); + + $authorData = [ + 'avatar' => $authorInfo['avatar'] ?: '/static/images/avatar.png', + 'name' => $authorInfo['name'], + 'resource_count' => $resourceCount, + 'article_count' => $articleCount + ]; + } else { + $authorData = [ + 'avatar' => '/static/images/avatar.png', + 'name' => $article['author'], + 'resource_count' => 0, + 'article_count' => 0 + ]; + } + + // 获取上一篇和下一篇文章 + $prevArticle = Articles::where('id', '<', $id) + ->where('delete_time', null) + ->where('status', '<>', 3) + ->where('cate', $article['cate']) + ->field(['id', 'title']) + ->order('id DESC') + ->find(); + + $nextArticle = Articles::where('id', '>', $id) + ->where('delete_time', null) + ->where('status', '<>', 3) + ->where('cate', $article['cate']) + ->field(['id', 'title']) + ->order('id ASC') + ->find(); + + // 获取相关文章(同分类的其他文章) + $relatedArticles = Articles::alias('a') + ->join('articles_category c', 'a.cate = c.id') + ->where('a.cate', $article['cate']) + ->where('a.id', '<>', $id) + ->where('a.delete_time', null) + ->where('a.status', '=', 2) + ->field([ + 'a.id', + 'a.title', + 'IF(a.image IS NULL OR a.image = "", c.image, a.image) as image' + ]) + ->order('a.id DESC') + ->limit(3) + ->select() + ->toArray(); + + // 如果是 POST 请求,返回 JSON 数据 + if (Request::isPost()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'authorInfo' => $authorData, + 'article' => $article, + 'cateName' => $cateName, + 'prevArticle' => $prevArticle, + 'nextArticle' => $nextArticle, + 'relatedArticles' => $relatedArticles + ] + ]); + } + + // GET 请求返回视图 + View::assign([ + 'authorInfo' => $authorData, + 'article' => $article, + 'cateName' => $cateName, + 'prevArticle' => $prevArticle, + 'nextArticle' => $nextArticle, + 'relatedArticles' => $relatedArticles + ]); + + return view('detail'); + } + + // 文章点赞 + public function like() + { + if (!Request::isAjax()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $id = Request::param('id/d', 0); + + // 检查文章是否存在 + $article = Articles::where('id', $id) + ->where('delete_time', null) + ->where('status', 2) + ->find(); + + if (!$article) { + return json(['code' => 0, 'msg' => '文章不存在或已被删除']); + } + + // 更新点赞数 + $result = Articles::where('id', $id) + ->where('delete_time', null) + ->inc('likes', 1) + ->update(); + + if ($result) { + // 返回更新后的点赞数 + $newLikes = $article['likes'] + 1; + return json([ + 'code' => 1, + 'msg' => '点赞成功', + 'data' => [ + 'likes' => $newLikes + ] + ]); + } else { + return json(['code' => 0, 'msg' => '点赞失败']); + } + } + + // 提交评论 + public function comment() + { + if (!Request::isAjax() || !Request::isPost()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $articleId = Request::param('article_id/d', 0); + $content = Request::param('content/s', ''); + $parentId = Request::param('parent_id/d', 0); + + if (empty($content)) { + return json(['code' => 0, 'msg' => '评论内容不能为空']); + } + + // 检查文章是否存在 + $article = Articles::where('id', $articleId) + ->where('delete_time', null) + ->where('status', 3) + ->find(); + + if (!$article) { + return json(['code' => 0, 'msg' => '文章不存在或已被删除']); + } + + // 添加评论 + // $data = [ + // 'article_id' => $articleId, + // 'content' => $content, + // 'parent_id' => $parentId, + // 'user_id' => $this->getUserId(), + // 'user_name' => $this->getUserName(), + // 'status' => 1, + // 'create_time' => time() + // ]; + + // $result = Db::table('yz_article_comment')->insert($data); + + // if ($result) { + // return json(['code' => 1, 'msg' => '评论成功']); + // } else { + // return json(['code' => 0, 'msg' => '评论失败']); + // } + } + + // 获取当前用户ID(示例方法,实际应根据您的用户系统实现) + private function getUserId() + { + // 这里应该返回当前登录用户的ID + return 1; // 示例返回值 + } + + // 获取当前用户名(示例方法,实际应根据您的用户系统实现) + private function getUserName() + { + // 这里应该返回当前登录用户的用户名 + return '游客'; // 示例返回值 + } + + // 获取访问统计 + public function viewStats() + { + $id = Request::param('id/d', 0); + + + // 获取总访问量 + $totalViews = Articles::where('id', $id) + ->value('views'); + + return json([ + 'code' => 1, + 'data' => [ + 'total' => $totalViews + ] + ]); + } + + /** + * 更新文章访问次数 + */ + public function updateViews() + { + if (!Request::isPost()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $id = Request::post('id'); + if (!$id) { + return json(['code' => 0, 'msg' => '参数错误']); + } + + try { + // 更新访问次数 + $article = Articles::where('id', $id)->find(); + if (!$article) { + return json(['code' => 0, 'msg' => '文章不存在']); + } + + // 更新访问次数 + Articles::where('id', $id)->inc('views')->update(); + + // 获取更新后的访问次数 + $newViews = Articles::where('id', $id)->value('views'); + + return json(['code' => 1, 'msg' => '更新成功', 'data' => ['views' => $newViews]]); + } catch (\Exception $e) { + return json(['code' => 0, 'msg' => '更新失败:' . $e->getMessage()]); + } + } + + //获取作者信息 + public function getAuthorInfo() + { + if (!Request::isPost()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $authorName = Request::post('name'); + if (empty($authorName)) { + return json(['code' => 0, 'msg' => '作者名称不能为空']); + } + + try { + // 获取作者基本信息 + $author = Db::name('users') + ->where('name', $authorName) + ->field('id, name, avatar') + ->find(); + + if (!$author) { + return json(['code' => 0, 'msg' => '作者不存在']); + } + + // 获取作者发布的资源数量 + $resourceCount = Db::name('resources') + ->where('user_id', $author['id']) + ->where('delete_time', null) + ->where('status', 2) // 假设2是已发布状态 + ->count(); + + // 获取作者发布的文章数量 + $articleCount = Db::name('articles') + ->where('author', $authorName) + ->where('delete_time', null) + ->where('status', 2) // 假设2是已发布状态 + ->count(); + + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'avatar' => $author['avatar'], + 'name' => $author['name'], + 'resource_count' => $resourceCount, + 'article_count' => $articleCount + ] + ]); + + } catch (\Exception $e) { + return json(['code' => 0, 'msg' => '获取作者信息失败:' . $e->getMessage()]); + } + } +} diff --git a/app/index/controller/Base.php b/app/index/controller/Base.php new file mode 100644 index 0000000..e4514c8 --- /dev/null +++ b/app/index/controller/Base.php @@ -0,0 +1,144 @@ +config = Db::table('yz_admin_config')->select()->toArray(); + // 将配置数据转换为键值对形式 + $configData = []; + foreach ($this->config as $item) { + // 使用正确的字段名 config_name 和 config_value + if (isset($item['config_name']) && isset($item['config_value'])) { + $configData[$item['config_name']] = $item['config_value']; + } + } + $this->config = $configData; + + # 获取用户信息 + $this->userId = Cookie::get('user_id'); + if (!empty($this->userId)) { + $this->user = User::where('uid', $this->userId)->find(); + } + + View::assign([ + 'user' => $this->user, + 'config' => $this->config + ]); + } + + /** + * 返回json对象 + */ + protected function returnCode($code, $data = [], $count = 10) + { + header('Content-type:application/json'); + if ($code == 0) { + $arr = array( + 'code' => $code, + 'msg' => '成功', + 'count' => $count, + 'data' => $data + ); + } else if ($code >= 1 && $code <= 100) { + $arr = array( + 'code' => $code, + 'msg' => $data + ); + } else { + $appapi = new AppApi(); + $arr = array( + 'code' => $code, + 'msg' => $appapi::errorTip($code) + ); + } + echo json_encode($arr); + if ($code != 0) { + exit; + } + } + + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @return void + */ + protected function success($msg = '') + { + $result = [ + 'code' => 1, + 'msg' => $msg + ]; + + $type = $this->getResponseType(); + if ($type == 'html') { + $response = view(Config::get('app.dispatch_success_tmpl'), $result); + } else if ($type == 'json') { + $response = json($result); + } + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @return void + */ + protected function error($msg = '') + { + $result = [ + 'code' => 0, + 'msg' => $msg + ]; + $response = view(Config::get('app.dispatch_error_tmpl'), $result); + throw new HttpResponseException($response); + } + + /** + * 获取当前的response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + return Request::isJson() || Request::isAjax() ? 'json' : 'html'; + } +} diff --git a/app/index/controller/BaseController.php b/app/index/controller/BaseController.php new file mode 100644 index 0000000..cfbc858 --- /dev/null +++ b/app/index/controller/BaseController.php @@ -0,0 +1,264 @@ +app = $app; + $this->request = $this->app->request; + $this->visitStats = new VisitStatsService(); + + // 控制器初始化 + $this->initialize(); + } + + /** + * 初始化 + */ + protected function initialize() + { + // 记录访问 + $this->visitStats->recordVisit($this->getControllerName()); + + // 获取配置 + $configList = AdminConfig::where('config_status', 1) + ->order('config_sort DESC') + ->select() + ->toArray(); + + // 将配置数据转换为键值对形式 + $config = []; + foreach ($configList as $item) { + $config[$item['config_name']] = $item['config_value']; + } + + // 判断用户是否登录 + $userInfo = []; + if (session('user_id')) { + // 从数据库获取最新用户信息 + $user = Users::where('uid', session('user_id'))->find(); + if ($user) { + $userInfo = [ + 'id' => $user->uid, + 'name' => $user->name, + 'account' => $user->account, + 'avatar' => $user->avatar ?? '/static/images/avatar.png', + 'is_login' => true, + 'last_login_time' => $user->last_login_time + ]; + } else { + // 用户不存在,清除session + session('user_id', null); + session('user_name', null); + $userInfo = ['is_login' => false]; + } + } else { + $userInfo = ['is_login' => false]; + } + + // 设置通用变量 + View::assign([ + 'config' => $config, + 'userInfo' => $userInfo + ]); + } + + /** + * 获取控制器名称(移除Controller后缀) + * @return string + */ + public function getControllerName() + { + $className = get_class($this); + $className = substr($className, strrpos($className, '\\') + 1); + return str_replace('Controller', '', $className); + } + + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @return string + */ + protected function fetch($template = '', $vars = []) + { + return View::fetch($template, $vars); + } + + /** + * 操作成功跳转 + * @param string $msg 提示信息 + * @param string $url 跳转地址 + * @param mixed $data 返回数据 + * @param integer $wait 跳转等待时间 + * @return \think\response\Json|string + */ + protected function success($msg = '', $url = null, $data = '', $wait = 3) + { + if (Request::isAjax()) { + return json([ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url + ]); + } + + return View::fetch('common/success', [ + 'msg' => $msg, + 'url' => $url, + 'data' => $data, + 'wait' => $wait + ]); + } + + /** + * 操作失败跳转 + * @param string $msg 提示信息 + * @param string $url 跳转地址 + * @param mixed $data 返回数据 + * @param integer $wait 跳转等待时间 + * @return \think\response\Json|string + */ + protected function error($msg = '', $url = null, $data = '', $wait = 3) + { + if (Request::isAjax()) { + return json([ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url + ]); + } + + return View::fetch('common/error', [ + 'msg' => $msg, + 'url' => $url, + 'data' => $data, + 'wait' => $wait + ]); + } + + + protected function sendEmail($to, $content, $title) + { + // 获取邮件配置 + $mailConfig = MailConfig::where('id', 1)->find(); + if (!$mailConfig) { + return '邮件配置不存在'; + } + + //实例化PHPMailer核心类 + $mail = new PHPMailer(); + + //是否启用smtp的debug进行调试 开发环境建议开启 生产环境注释掉即可 默认关闭debug调试模式 + $mail->SMTPDebug = 0; + + //使用smtp鉴权方式发送邮件 + $mail->isSMTP(); + + //smtp需要鉴权 这个必须是true + $mail->SMTPAuth = true; + + //链接qq域名邮箱的服务器地址 + $mail->Host = $mailConfig['smtp_host']; + + //设置使用ssl加密方式登录鉴权 + $mail->SMTPSecure = 'ssl'; + + //设置ssl连接smtp服务器的远程服务器端口号 + $mail->Port = $mailConfig['smtp_port']; + + //设置发件人的主机域 + $mail->Hostname = $mailConfig['smtp_email']; + + //设置发送的邮件的编码 + $mail->CharSet = 'UTF-8'; + + //设置发件人姓名(昵称) + $mail->FromName = $mailConfig['smtp_name']; + + //smtp登录的账号 + $mail->Username = $mailConfig['smtp_email']; + + //smtp登录的密码 + $mail->Password = $mailConfig['smtp_password']; + + //设置发件人邮箱地址 + $mail->setFrom($mailConfig['smtp_email'], $mailConfig['smtp_name']); + + //邮件正文是否为html编码 + $mail->isHTML(true); + + //设置收件人邮箱地址 + $mail->addAddress($to); + + //添加该邮件的主题 + $mail->Subject = $title; + + //添加邮件正文 + $mail->Body = $content; + + try { + $status = $mail->send(); + if ($status) { + return '发送成功'; + } else { + return '发送失败'; + } + } catch (\Exception $e) { + return '发送失败:' . $e->getMessage(); + } + } +} \ No newline at end of file diff --git a/app/index/controller/GameController.php b/app/index/controller/GameController.php new file mode 100644 index 0000000..ff1fecd --- /dev/null +++ b/app/index/controller/GameController.php @@ -0,0 +1,391 @@ +where('delete_time', null) + ->where('status', 1) + ->select() + ->toArray(); + + // 获取顶级分类信息 + $category = null; + if ($cateid > 0) { + $category = ResourcesCategory::where('id', $cateid) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + } + + // 获取所有子分类 + $subCategories = []; + if ($cateid > 0) { + $subCategories = ResourcesCategory::where('cid', $cateid) + ->where('delete_time', null) + ->where('status', 1) + ->select() + ->toArray(); + } + + // 获取所有子分类ID + $subCategoryIds = array_column($subCategories, 'id'); + if ($cateid > 0) { + $subCategoryIds[] = $cateid; + } + + // 构建游戏查询条件 + $where = [ + ['delete_time', '=', null], + ['status', '=', 1] + ]; + + if (!empty($subCategoryIds)) { + $where[] = ['cate', 'in', $subCategoryIds]; + } + + // 查询游戏 + $games = Resources::where($where) + ->order('id DESC') + ->page($page, $limit) + ->select() + ->toArray(); + + // 按子分类分组游戏 + $groupedGames = []; + foreach ($subCategories as $subCategory) { + $groupedGames[$subCategory['id']] = [ + 'id' => $subCategory['id'], + 'name' => $subCategory['name'], + 'list' => [] + ]; + } + + // 将游戏分配到对应的子分类 + foreach ($games as $game) { + if (isset($groupedGames[$game['cate']])) { + $groupedGames[$game['cate']]['list'][] = $game; + } + } + + // 获取总数 + $total = Resources::where($where)->count(); + + // 准备返回数据 + $data = [ + 'cate' => [ + 'id' => $cateid, + 'name' => $category ? $category['name'] : '', + 'desc' => $category ? $category['desc'] : '', + 'image' => $category ? $category['image'] : '', + 'subCategories' => array_values($groupedGames), + 'total' => $total, + 'page' => $page, + 'limit' => $limit + ] + ]; + + // 根据请求方式返回不同的输出 + if (request()->isPost()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => $data + ]); + } else { + // 为视图准备数据 + $viewData = [ + 'categories' => $categories, + 'cate' => $data['cate'] + ]; + return view('index', $viewData); + } + } + + // 游戏列表页 + public function list() + { + // 获取分类ID + $cateId = Request::param('cate/d', 0); + + // 构建查询条件 + $where = [ + ['a.delete_time', '=', null], + ['a.status', '=', 1] + ]; + + if ($cateId > 0) { + $where[] = ['a.cate', '=', $cateId]; + } + + // 获取游戏列表 + $games = Resources::alias('a') + ->join('resources_category c', 'a.cate = c.id') + ->where($where) + ->field([ + 'a.*', + 'IF(a.icon IS NULL OR a.icon = "", c.icon, a.icon) as icon' + ]) + ->order('a.id DESC') + ->paginate([ + 'list_rows' => 10, + 'query' => Request::instance()->param() + ]); + + // 获取分类信息 + $category = null; + if ($cateId > 0) { + $category = ResourcesCategory::where('id', $cateId) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + } + + // 获取所有分类 + $categories = ResourcesCategory::where('delete_time', null) + ->where('status', 1) + ->select() + ->toArray(); + + // 如果是POST请求,返回JSON数据 + if (Request::isPost()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'games' => $games->items(), + 'total' => $games->total(), + 'current_page' => $games->currentPage(), + 'per_page' => $games->listRows(), + 'category' => $category + ] + ]); + } + + // GET请求返回渲染的视图 + View::assign([ + 'games' => $games, + 'category' => $category, + 'categories' => $categories + ]); + + return View::fetch('list'); + } + + // 游戏详情页 + public function detail() + { + $id = Request::param('id/d', 0); + $game = Resources::where('id', $id)->find(); + + if (!$game) { + return json(['code' => 0, 'msg' => '游戏不存在或已被删除']); + } + + // 如果size没有,从附件表中获取 + if (empty($game['size']) && !empty($game['fileurl'])) { + $attachment = Attachments::where('src', $game['fileurl']) + ->find(); + + if ($attachment && !empty($attachment['size'])) { + $size = $attachment['size']; + // 转换文件大小为合适的单位 + if ($size >= 1073741824) { // 1GB = 1024MB = 1024*1024KB = 1024*1024*1024B + $game['size'] = round($size / 1073741824, 2) . 'GB'; + } elseif ($size >= 1048576) { // 1MB = 1024KB = 1024*1024B + $game['size'] = round($size / 1048576, 2) . 'MB'; + } else { + $game['size'] = round($size / 1024, 2) . 'KB'; + } + } + } + + // 获取分类名称 + $cateName = ResourcesCategory::where('id', $game['cate']) + ->value('name'); + + // 获取上一个和下一个游戏 + $prevGame = Resources::where('id', '<', $id) + ->where('delete_time', null) + ->where('status', 1) + ->where('cate', $game['cate']) + ->field(['id', 'title']) + ->order('id DESC') + ->find(); + + $nextGame = Resources::where('id', '>', $id) + ->where('delete_time', null) + ->where('status', 1) + ->where('cate', $game['cate']) + ->field(['id', 'title']) + ->order('id ASC') + ->find(); + + // 获取相关游戏(同分类的其他游戏) + $relatedGames = Db::table('yz_resources') + ->alias('g') + ->join('yz_resources_category c', 'g.cate = c.id') + ->where('g.cate', $game['cate']) + ->where('g.id', '<>', $id) + ->where('g.delete_time', null) + ->where('g.status', 1) + ->field([ + 'g.id', + 'g.title', + 'IF(g.icon IS NULL OR g.icon = "", c.icon, g.icon) as icon' + ]) + ->order('g.id DESC') + ->limit(3) + ->select() + ->toArray(); + + // 如果是 AJAX 请求,返回 JSON 数据 + if (Request::isAjax()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'game' => $game, + 'cateName' => $cateName, + 'prevGame' => $prevGame, + 'nextGame' => $nextGame, + 'relatedGames' => $relatedGames + ] + ]); + } + + // 非 AJAX 请求返回视图 + View::assign([ + 'game' => $game, + 'cateName' => $cateName, + 'prevGame' => $prevGame, + 'nextGame' => $nextGame, + 'relatedGames' => $relatedGames + ]); + + return View::fetch('detail'); + } + + // 游戏下载 + public function downloadurl() + { + if (!Request::isAjax()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $id = Request::param('id/d', 0); + + // 获取游戏信息 + $game = Resources::where('id', $id) + ->where('delete_time', null) + ->find(); + + if (!$game) { + return json(['code' => 0, 'msg' => '游戏不存在']); + } + + // 更新下载次数 + $result = Resources::where('id', $id) + ->where('delete_time', null) + ->inc('downloads', 1) + ->update(); + + if ($result) { + return json([ + 'code' => 1, + 'msg' => '下载成功', + 'data' => [ + 'url' => $game['url'] + ] + ]); + } else { + return json(['code' => 0, 'msg' => '下载失败']); + } + } + + // 获取访问统计 + public function viewStats() + { + $id = Request::param('id/d', 0); + + // 获取总访问量 + $totalViews = Resources::where('id', $id) + ->value('views'); + + return json([ + 'code' => 1, + 'data' => [ + 'total' => $totalViews + ] + ]); + } + + /** + * 更新游戏访问次数 + */ + public function updateViews() + { + if (!Request::isPost()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $id = Request::post('id'); + if (!$id) { + return json(['code' => 0, 'msg' => '参数错误']); + } + + try { + // 更新访问次数 + $game = Resources::where('id', $id)->find(); + if (!$game) { + return json(['code' => 0, 'msg' => '游戏不存在']); + } + + // 更新访问次数 + Resources::where('id', $id)->inc('views')->update(); + + // 获取更新后的访问次数 + $newViews = Resources::where('id', $id)->value('views'); + + return json(['code' => 1, 'msg' => '更新成功', 'data' => ['views' => $newViews]]); + } catch (\Exception $e) { + return json(['code' => 0, 'msg' => '更新失败:' . $e->getMessage()]); + } + } +} diff --git a/app/index/controller/IndexController.php b/app/index/controller/IndexController.php new file mode 100644 index 0000000..698605f --- /dev/null +++ b/app/index/controller/IndexController.php @@ -0,0 +1,427 @@ +order('sort DESC, id DESC') + ->select() + ->toArray(); + + View::assign('bannerList', $bannerList); + return View::fetch(); + } + + /** + * 获取站点资讯列表 + */ + public function siteNewslist() + { + // 获取站点资讯分类(顶级分类id为1的子分类) + $categories = ArticlesCategory::where('cid', 1) + ->where('delete_time', null) + ->select() + ->toArray(); + + $articles = []; + $categoryData = []; + + // 提取分类名称和ID用于前端tab显示 + foreach ($categories as $category) { + $categoryData[] = [ + 'id' => $category['id'], + 'name' => $category['name'] + ]; + + // 获取该分类下的文章,限制4条 + $articles = Articles::where('cate', $category['id']) + ->where('delete_time', null) + ->where('status', 2) + ->order('id', 'desc') + ->field('id, cate, title, image, author, publishdate, views') + ->limit(4) + ->select() + ->toArray(); + + } + + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'articles' => $articles, + 'categories' => $categoryData + ]); + } + + /** + * 获取技术文章列表 + */ + public function technicalArticleslist() + { + // 获取技术文章分类(顶级分类id为3的子分类) + $categories = ArticlesCategory::where('cid', 3) + ->where('delete_time', null) + ->select() + ->toArray(); + + // 组装分类数据,方便后续查找 + $categoryData = []; + $categoryImageMap = []; + $articlesByCategory = []; + + foreach ($categories as $category) { + $categoryData[] = [ + 'id' => $category['id'], + 'name' => $category['name'] + ]; + $categoryImageMap[$category['id']] = $category['image'] ?? ''; + + // 获取每个分类下的文章,限制12条 + $articles = Articles::where('cate', $category['id']) + ->where('delete_time', null) + ->where('status', 2) + ->order('id', 'desc') + ->field('id, cate, title, image, author, publishdate, views') + ->limit(12) + ->select() + ->toArray(); + + // 替换image为空的文章 + foreach ($articles as &$article) { + if (empty($article['image']) && !empty($categoryImageMap[$article['cate']])) { + $article['image'] = $categoryImageMap[$article['cate']]; + } + } + unset($article); + + $articlesByCategory[$category['id']] = $articles; + } + + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'articles' => $articlesByCategory, + 'categories' => $categoryData + ]); + } + + /** + * 获取banner列表 + */ + public function bannerlist() + { + // 获取启用状态的banner列表,按排序倒序 + $bannerList = Banner::where('delete_time', null) + ->order('sort DESC, id DESC') + ->select() + ->toArray(); + + return json(['code' => 1, 'msg' => '获取成功', 'banner' => $bannerList]); + } + + /** + * 获取资源下载列表 + */ + public function resourcesList() + { + // 获取资源分类(顶级分类id为2的子分类) + $categories = ResourcesCategory::where('cid', 2) + ->where('delete_time', null) + ->select() + ->toArray(); + + // 组装分类数据 + $categoryData = []; + $categoryImageMap = []; + $resourcesByCategory = []; + + foreach ($categories as $category) { + $categoryData[] = [ + 'id' => $category['id'], + 'name' => $category['name'] + ]; + $categoryImageMap[$category['id']] = $category['image'] ?? ''; + + // 获取每个分类下的资源,限制8条 + $resources = Resources::where('cate', $category['id']) + ->where('delete_time', null) + ->where('status', 1) + ->order('id', 'desc') + ->field('id, cate, title, desc, downloads, create_time, icon, views, uploader') + ->limit(8) + ->select() + ->toArray(); + + // 替换thumbnail为空的资源 + foreach ($resources as &$resource) { + if (empty($resource['thumbnail']) && !empty($categoryImageMap[$resource['cate']])) { + $resource['thumbnail'] = $categoryImageMap[$resource['cate']]; + } + } + unset($resource); + + $resourcesByCategory[$category['id']] = $resources; + } + + // 合并所有分类的资源 + $allResources = []; + foreach ($resourcesByCategory as $resources) { + $allResources = array_merge($allResources, $resources); + } + + // 按上传时间排序 + usort($allResources, function ($a, $b) { + return $b['create_time'] - $a['create_time']; + }); + + // 只取最新的8条 + $allResources = array_slice($allResources, 0, 8); + + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'resources' => $allResources, + 'categories' => $categoryData + ]); + } + + /** + * 获取程序下载列表 + */ + public function programList() + { + // 获取程序分类(顶级分类id为4的子分类) + $categories = ResourcesCategory::where('cid', 1) + ->where('delete_time', null) + ->select() + ->toArray(); + + // 组装分类数据 + $categoryData = []; + $categoryImageMap = []; + $programsByCategory = []; + + foreach ($categories as $category) { + $categoryData[] = [ + 'id' => $category['id'], + 'name' => $category['name'] + ]; + $categoryImageMap[$category['id']] = $category['image'] ?? ''; + + // 获取每个分类下的程序,限制8条 + $programs = Resources::where('cate', $category['id']) + ->where('delete_time', null) + ->where('status', 1) + ->order('id', 'desc') + ->field('id, cate, title, desc, downloads, create_time, icon, views, uploader') + ->limit(8) + ->select() + ->toArray(); + + // 替换thumbnail为空的程序 + foreach ($programs as &$program) { + if (empty($program['thumbnail']) && !empty($categoryImageMap[$program['cate']])) { + $program['thumbnail'] = $categoryImageMap[$program['cate']]; + } + } + unset($program); + + $programsByCategory[$category['id']] = $programs; + } + + // 合并所有分类的程序 + $allPrograms = []; + foreach ($programsByCategory as $programs) { + $allPrograms = array_merge($allPrograms, $programs); + } + + // 按上传时间排序 + usort($allPrograms, function ($a, $b) { + return $b['create_time'] - $a['create_time']; + }); + + // 只取最新的8条 + $allPrograms = array_slice($allPrograms, 0, 8); + + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'programs' => $allPrograms, + 'categories' => $categoryData + ]); + } + + /** + * 获取游戏下载列表 + */ + public function gameList() + { + // 获取游戏分类(顶级分类id为8的子分类) + $categories = ResourcesCategory::where('cid', 8) + ->where('delete_time', null) + ->select() + ->toArray(); + + // 组装分类数据 + $categoryData = []; + $categoryImageMap = []; + $programsByCategory = []; + + foreach ($categories as $category) { + $categoryData[] = [ + 'id' => $category['id'], + 'name' => $category['name'], + 'image' => $category['image'] ?? '' + ]; + + // 获取每个分类下的游戏,限制8条 + $programs = Resources::where('cate', $category['id']) + ->where('delete_time', null) + ->where('status', 1) + ->order('id', 'desc') + ->field('id, cate, title, desc, downloads, create_time, icon, views, uploader, number, url, code') + ->limit(8) + ->select() + ->toArray(); + + // 处理游戏数据 + foreach ($programs as &$program) { + // 如果没有图标,使用分类图片 + if (empty($program['icon']) && !empty($category['image'])) { + $program['icon'] = $category['image']; + } + // 格式化时间 + $program['create_time'] = date('Y-m-d H:i:s', $program['create_time']); + } + unset($program); + + $programsByCategory[$category['id']] = $programs; + } + + // 合并所有分类的游戏 + $allPrograms = []; + foreach ($programsByCategory as $programs) { + $allPrograms = array_merge($allPrograms, $programs); + } + + // 按上传时间排序 + usort($allPrograms, function ($a, $b) { + return strtotime($b['create_time']) - strtotime($a['create_time']); + }); + + // 只取最新的8条 + $allPrograms = array_slice($allPrograms, 0, 8); + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'games' => $allPrograms, + 'categories' => $categoryData + ] + ]); + } + + //保存附件信息到数据库 + private function saveAttachment($name, $type, $size, $src) + { + $data = [ + 'name' => $name, + 'type' => $type, + 'size' => $size, + 'src' => $src, + 'create_time' => time(), + 'update_time' => time() + ]; + return Attachments::insertGetId($data); + } + + //上传图片接口 + public function update_imgs() + { + // 获取上传的文件 + $file = request()->file(); + $files = request()->file('file'); + + // 检查是否有文件上传 + if (empty($file)) { + return json(['code' => 1, 'msg' => '没有文件上传']); + } + + try { + // 验证上传的文件 + validate([ + 'image' => 'filesize:51200|fileExt:jpg,png,gif,jpeg,webp' + ])->check($file); + + // 存储文件到public磁盘的uploads目录 + $info = Filesystem::disk('public')->putFile('uploads', $files); + + // 处理文件路径,统一使用正斜杠 + $info = str_replace("\\", "/", $info); + $img = '/storage/' . $info; + + // 保存附件信息 + $fileName = $files->getOriginalName(); + $fileSize = $files->getSize(); + $attachmentId = $this->saveAttachment($fileName, 1, $fileSize, $img); // 1: 图片 + + // 返回成功信息 + return json([ + 'code' => 0, + 'data' => [ + 'url' => $img + ], + 'msg' => '上传成功' + ]); + + } catch (\think\exception\ValidateException $e) { + // 捕获验证异常并返回错误信息 + return json(['code' => 1, 'msg' => $e->getMessage()]); + } catch (\Exception $e) { + // 捕获其他异常 + return json(['code' => 1, 'msg' => '上传失败:' . $e->getMessage()]); + } + } + +} diff --git a/app/index/controller/ProgramController.php b/app/index/controller/ProgramController.php new file mode 100644 index 0000000..83e3c75 --- /dev/null +++ b/app/index/controller/ProgramController.php @@ -0,0 +1,427 @@ +where('delete_time', null) + ->where('status', 1) + ->select() + ->toArray(); + + // 获取顶级分类信息 + $category = null; + if ($cateid > 0) { + $category = ResourcesCategory::where('id', $cateid) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + } + + // 获取所有子分类 + $subCategories = []; + if ($cateid > 0) { + $subCategories = ResourcesCategory::where('cid', $cateid) + ->where('delete_time', null) + ->where('status', 1) + ->select() + ->toArray(); + } + + // 获取所有子分类ID + $subCategoryIds = array_column($subCategories, 'id'); + if ($cateid > 0) { + $subCategoryIds[] = $cateid; + } + + // 构建资源查询条件 + $where = [ + ['delete_time', '=', null], + ['status', '=', 1] + ]; + + if (!empty($subCategoryIds)) { + $where[] = ['cate', 'in', $subCategoryIds]; + } + + // 查询资源 + $programs = Resources::where($where) + ->order('id DESC') + ->page($page, $limit) + ->select() + ->toArray(); + + // 处理每个资源的size + foreach ($programs as &$program) { + if (empty($program['size'])) { + // 从Attachments表中查找对应的src + $attachment = Attachments::where('src', $program['icon']) + ->field('size') + ->find(); + if ($attachment) { + $program['size'] = $attachment['size']; + } + } + } + + // 按子分类分组资源 + $groupedPrograms = []; + foreach ($subCategories as $subCategory) { + $groupedPrograms[$subCategory['id']] = [ + 'id' => $subCategory['id'], + 'name' => $subCategory['name'], + 'list' => [] + ]; + } + + // 将资源分配到对应的子分类 + foreach ($programs as $program) { + if (isset($groupedPrograms[$program['cate']])) { + $groupedPrograms[$program['cate']]['list'][] = $program; + } + } + + // 获取总数 + $total = Resources::where($where)->count(); + + // 准备返回数据 + $data = [ + 'cate' => [ + 'id' => $cateid, + 'name' => $category ? $category['name'] : '', + 'desc' => $category ? $category['desc'] : '', + 'image' => $category ? $category['image'] : '', + 'subCategories' => array_values($groupedPrograms), + 'total' => $total, + 'page' => $page, + 'limit' => $limit + ] + ]; + + // 根据请求方式返回不同的输出 + if ($this->request->isPost()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => $data + ]); + } + + // GET请求渲染页面 + return view('index', [ + 'categories' => $categories, + 'cate' => $data['cate'] + ]); + } + + // 程序列表页 + public function list() + { + // 获取分类ID + $cateId = Request::param('cate/d', 0); + + // 构建查询条件 + $where = [ + ['a.delete_time', '=', null], + ['a.status', '=', 1] + ]; + + if ($cateId > 0) { + $where[] = ['a.cate', '=', $cateId]; + } + + // 获取程序列表 + $programs = Resources::alias('a') + ->join('resources_category c', 'a.cate = c.id') + ->where($where) + ->field([ + 'a.*', + 'IF(a.icon IS NULL OR a.icon = "", c.icon, a.icon) as icon' + ]) + ->order('a.id DESC') + ->paginate([ + 'list_rows' => 10, + 'query' => Request::instance()->param() + ]); + + // 获取分类信息 + $category = null; + if ($cateId > 0) { + $category = ResourcesCategory::where('id', $cateId) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + } + + // 获取所有分类 + $categories = ResourcesCategory::where('delete_time', null) + ->where('status', 1) + ->select() + ->toArray(); + + // 如果是POST请求,返回JSON数据 + if (Request::isPost()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'programs' => $programs->items(), + 'total' => $programs->total(), + 'current_page' => $programs->currentPage(), + 'per_page' => $programs->listRows(), + 'category' => $category + ] + ]); + } + + // GET请求返回渲染的视图 + View::assign([ + 'programs' => $programs, + 'category' => $category, + 'categories' => $categories + ]); + + return View::fetch('list'); + } + + // 程序详情页 + public function detail($id) + { + // 获取资源详情 + $program = Resources::where('id', $id) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + + if (!$program) { + if ($this->request->isPost()) { + return json(['code' => 0, 'msg' => '资源不存在']); + } + $this->error('资源不存在'); + } + + // 获取分类名称 + $cateName = ResourcesCategory::where('id', $program['cate']) + ->value('name'); + + // 获取上传者信息 + $uploaderInfo = Users::where('name', $program['uploader']) + ->field(['name', 'avatar']) + ->find(); + + if ($uploaderInfo) { + $uploaderInfo = $uploaderInfo->toArray(); + // 如果没有头像,使用默认头像 + if (empty($uploaderInfo['avatar'])) { + $uploaderInfo['avatar'] = '/static/images/avatar.png'; + } + } else { + $uploaderInfo = [ + 'name' => $program['uploader'], + 'avatar' => '/static/images/avatar.png' + ]; + } + + // 添加上传者的资源数和文章数统计 + $uploaderInfo['resource_count'] = Resources::where('uploader', $program['uploader']) + ->where('delete_time', null) + ->where('status', 1) + ->count(); + $uploaderInfo['article_count'] = Articles::where('author', $program['uploader']) + ->where('delete_time', null) + ->where('status', 2) + ->count(); + + // 处理资源size + if (empty($program['size'])) { + $attachment = Attachments::where('src', $program['icon']) + ->field('size') + ->find(); + if ($attachment) { + $program['size'] = $attachment['size']; + } + } + + // 转换文件大小为合适的单位 + if (!empty($program['size']) && is_numeric($program['size'])) { + $size = $program['size']; + if ($size >= 1073741824) { // 1GB = 1024MB = 1024*1024KB = 1024*1024*1024B + $program['size'] = round($size / 1073741824, 2) . 'GB'; + } elseif ($size >= 1048576) { // 1MB = 1024KB = 1024*1024B + $program['size'] = round($size / 1048576, 2) . 'MB'; + } else { + $program['size'] = round($size / 1024, 2) . 'KB'; + } + } + + // 获取上一个和下一个程序 + $prevProgram = Resources::where('id', '<', $id) + ->where('delete_time', null) + ->where('status', 1) + ->where('cate', $program['cate']) + ->field(['id', 'title']) + ->order('id DESC') + ->find(); + + $nextProgram = Resources::where('id', '>', $id) + ->where('delete_time', null) + ->where('status', 1) + ->where('cate', $program['cate']) + ->field(['id', 'title']) + ->order('id ASC') + ->find(); + + // 获取相关程序(同分类的其他程序) + $relatedPrograms = Resources::alias('p') + ->join('yz_resources_category c', 'p.cate = c.id') + ->where('p.cate', $program['cate']) + ->where('p.id', '<>', $id) + ->where('p.delete_time', null) + ->where('p.status', 1) + ->field([ + 'p.id', + 'p.title', + 'COALESCE(p.icon, c.icon) as icon' + ]) + ->order('p.id DESC') + ->limit(3) + ->select() + ->toArray(); + + // 准备返回数据 + $data = [ + 'program' => $program, + 'uploaderInfo' => $uploaderInfo, + 'cateName' => $cateName, + 'prevProgram' => $prevProgram, + 'nextProgram' => $nextProgram, + 'relatedPrograms' => $relatedPrograms + ]; + + // 根据请求方式返回不同的输出 + if ($this->request->isPost()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => $data + ]); + } + + // GET请求渲染页面 + return view('', $data); + } + + // 程序下载 + public function download() + { + $id = Request::param('id/d', 0); + $program = Resources::where('id', $id) + ->where('delete_time', null) + ->where('status', 1) + ->find(); + + if (!$program) { + return json(['code' => 0, 'msg' => '程序不存在或已被删除']); + } + + // 更新下载次数 + Resources::where('id', $id)->inc('downloads')->update(); + + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'fileurl' => $program['fileurl'] + ] + ]); + } + + // 获取访问统计 + public function viewStats() + { + $id = Request::param('id/d', 0); + + // 获取总访问量 + $totalViews = Resources::where('id', $id) + ->value('views'); + + return json([ + 'code' => 1, + 'data' => [ + 'total' => $totalViews + ] + ]); + } + + /** + * 更新程序访问次数 + */ + public function updateViews() + { + if (!Request::isPost()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $id = Request::post('id'); + if (!$id) { + return json(['code' => 0, 'msg' => '参数错误']); + } + + try { + // 更新访问次数 + $program = Resources::where('id', $id)->find(); + if (!$program) { + return json(['code' => 0, 'msg' => '程序不存在']); + } + + // 更新访问次数 + Resources::where('id', $id)->inc('views')->update(); + + // 获取更新后的访问次数 + $newViews = Resources::where('id', $id)->value('views'); + + return json(['code' => 1, 'msg' => '更新成功', 'data' => ['views' => $newViews]]); + } catch (\Exception $e) { + return json(['code' => 0, 'msg' => '更新失败:' . $e->getMessage()]); + } + } +} diff --git a/app/index/controller/ResourcesController.php b/app/index/controller/ResourcesController.php new file mode 100644 index 0000000..cf2ab8a --- /dev/null +++ b/app/index/controller/ResourcesController.php @@ -0,0 +1,309 @@ +where('status', 1) + ->order('sort', 'asc') + ->select(); + + // 获取每个顶级分类下的子分类 + $categories = []; + foreach ($parentCategories as $parent) { + $subCategories = ResourcesCategory::where('cid', $parent->id) + ->where('status', 1) + ->order('sort', 'asc') + ->select(); + + // 获取每个子分类下的资源数量 + foreach ($subCategories as &$subCategory) { + $subCategory['resource_count'] = Resources::where('cate', $subCategory->id) + ->where('status', 1) + ->count(); + } + + $categories[] = [ + 'parent' => $parent, + 'subCategories' => $subCategories + ]; + } + + // 将数据传递给视图 + View::assign('categories', $categories); + + return View::fetch(); + } + + // 资源列表页 + public function list() + { + $cid = input('cid/d', 0); + $page = input('page/d', 1); + + // 获取分类信息 + $category = ResourcesCategory::where('id', $cid) + ->where('status', 1) + ->find(); + + if (!$category) { + $this->error('分类不存在'); + } + + // 获取该分类下的资源,带分页 + $resources = Resources::where('cate', $cid) + ->where('status', 1) + ->order('sort', 'asc') + ->paginate([ + 'list_rows' => 20, + 'page' => $page, + 'query' => Request::instance()->param() + ]); + + // 将数据传递给视图 + View::assign('category', $category); + View::assign('data', $resources); + View::assign('page', $resources->render()); // 新增这一行 + + return View::fetch('list'); + } + + // 资源详情页 + public function detail() + { + $id = Request::param('id/d', 0); + $resources = Resources::where('id', $id)->find(); + + if (!$resources) { + return json(['code' => 0, 'msg' => '资源不存在或已被删除']); + } + + // 如果size没有,从附件表中获取 + if (empty($resources['size']) && !empty($resources['fileurl'])) { + $attachment = Attachments::where('src', $resources['fileurl']) + ->find(); + + if ($attachment && !empty($attachment['size'])) { + $size = $attachment['size']; + // 转换文件大小为合适的单位 + if ($size >= 1073741824) { // 1GB = 1024MB = 1024*1024KB = 1024*1024*1024B + $resources['size'] = round($size / 1073741824, 2) . 'GB'; + } elseif ($size >= 1048576) { // 1MB = 1024KB = 1024*1024B + $resources['size'] = round($size / 1048576, 2) . 'MB'; + } else { + $resources['size'] = round($size / 1024, 2) . 'KB'; + } + } + } + + // 获取分类名称 + $cateName = ResourcesCategory::where('id', $resources['cate']) + ->value('name'); + + // 获取作者信息 + $authorInfo = Users::where('name', $resources['uploader'])->find(); + // var_dump($authorInfo); + if ($authorInfo) { + // 统计作者的文章数 + $resourcesCount = Articles::where('author', $resources['uploader'])->count(); + // 统计作者的资源数 + $resourceCount = Resources::where('uploader', $resources['uploader'])->count(); + + $authorData = [ + 'avatar' => $authorInfo['avatar'] ?: '/static/images/avatar.png', + 'name' => $authorInfo['name'], + 'resource_count' => $resourceCount, + 'article_count' => $resourcesCount + ]; + } else { + $authorData = [ + 'avatar' => '/static/images/avatar.png', + 'name' => $resources['author'], + 'resource_count' => 0, + 'article_count' => 0 + ]; + } + + // 获取上一个和下一个资源 + $prevResources = Resources::where('id', '<', $id) + ->where('delete_time', null) + ->where('status', 1) + ->where('cate', $resources['cate']) + ->field(['id', 'title']) + ->order('id DESC') + ->find(); + + $nextResources = Resources::where('id', '>', $id) + ->where('delete_time', null) + ->where('status', 1) + ->where('cate', $resources['cate']) + ->field(['id', 'title']) + ->order('id ASC') + ->find(); + + // 获取相关资源(同分类的其他资源) + $relatedResourcess = Db::table('yz_resources') + ->alias('g') + ->join('yz_resources_category c', 'g.cate = c.id') + ->where('g.cate', $resources['cate']) + ->where('g.id', '<>', $id) + ->where('g.delete_time', null) + ->where('g.status', 1) + ->field([ + 'g.id', + 'g.title', + 'IF(g.icon IS NULL OR g.icon = "", c.icon, g.icon) as icon' + ]) + ->order('g.id DESC') + ->limit(5) + ->select() + ->toArray(); + + // 如果是 AJAX 请求,返回 JSON 数据 + if (Request::isAjax()) { + return json([ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'resources' => $resources, + 'cateName' => $cateName, + 'prevResources' => $prevResources, + 'nextResources' => $nextResources, + 'relatedResourcess' => $relatedResourcess + ] + ]); + } + + // 非 AJAX 请求返回视图 + View::assign([ + 'resources' => $resources, + 'cateName' => $cateName, + 'authorInfo' => $authorData, + 'prevResources' => $prevResources, + 'nextResources' => $nextResources, + 'relatedResourcess' => $relatedResourcess + ]); + + return View::fetch('detail'); + } + + // 资源下载 + public function downloadurl() + { + if (!Request::isAjax()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $id = Request::param('id/d', 0); + + // 获取资源信息 + $resources = Resources::where('id', $id) + ->where('delete_time', null) + ->find(); + + if (!$resources) { + return json(['code' => 0, 'msg' => '资源不存在']); + } + + // 更新下载次数 + $result = Resources::where('id', $id) + ->where('delete_time', null) + ->inc('downloads', 1) + ->update(); + + if ($result) { + return json([ + 'code' => 1, + 'msg' => '下载成功', + 'data' => [ + 'url' => $resources['url'] + ] + ]); + } else { + return json(['code' => 0, 'msg' => '下载失败']); + } + } + + // 获取访问统计 + public function viewStats() + { + $id = Request::param('id/d', 0); + + // 获取总访问量 + $totalViews = Resources::where('id', $id) + ->value('views'); + + return json([ + 'code' => 1, + 'data' => [ + 'total' => $totalViews + ] + ]); + } + + /** + * 更新资源访问次数 + */ + public function updateViews() + { + if (!Request::isPost()) { + return json(['code' => 0, 'msg' => '非法请求']); + } + + $id = Request::post('id'); + if (!$id) { + return json(['code' => 0, 'msg' => '参数错误']); + } + + try { + // 更新访问次数 + $resources = Resources::where('id', $id)->find(); + if (!$resources) { + return json(['code' => 0, 'msg' => '资源不存在']); + } + + // 更新访问次数 + Resources::where('id', $id)->inc('views')->update(); + + // 获取更新后的访问次数 + $newViews = Resources::where('id', $id)->value('views'); + + return json(['code' => 1, 'msg' => '更新成功', 'data' => ['views' => $newViews]]); + } catch (\Exception $e) { + return json(['code' => 0, 'msg' => '更新失败:' . $e->getMessage()]); + } + } +} diff --git a/app/index/controller/SearchController.php b/app/index/controller/SearchController.php new file mode 100644 index 0000000..82d45d6 --- /dev/null +++ b/app/index/controller/SearchController.php @@ -0,0 +1,128 @@ +error('请输入搜索关键词'); + } + + // 根据类型选择对应的表和分类表 + if ($type == 'articles') { + $model = new Articles(); + $categoryModel = new ArticlesCategory(); + $detailUrl = '/index/articles/detail'; + $categoryUrl = '/index/articles/category'; + $status = 2; // 文章状态为2 + $fields = 'id, title, cate, image, author, FROM_UNIXTIME(create_time, "%Y-%m-%d") as publishdate'; + } else if ($type == 'resources') { + $model = new Resources(); + $categoryModel = new ResourcesCategory(); + $detailUrl = '/index/resources/detail'; + $categoryUrl = '/index/resources/category'; + $status = 1; // 资源状态为1 + $fields = 'id, title, cate, icon, uploader, FROM_UNIXTIME(create_time, "%Y-%m-%d") as publishdate'; + } else { + $this->error('无效的搜索类型'); + } + + // 搜索内容 + $items = $model->where('title', 'like', "%{$keyword}%") + ->where('status', $status) + ->field($fields) + ->order('create_time desc') + ->page($page, $limit) + ->select(); + + // 获取总数 + $count = $model->where('title', 'like', "%{$keyword}%") + ->where('status', $status) + ->count(); + + // 获取分类名称和图片 + foreach ($items as &$item) { + if ($type == 'articles') { + $category = $categoryModel->where('id', $item['cate']) + ->field('id, name, image') + ->find(); + + $item['category'] = $category ?: ['id' => 0, 'name' => '未分类', 'image' => '']; + $item['cate'] = $item['category']['name']; // 使用分类名称替换分类ID + + // 如果文章的图片为空,使用分类的图片 + if (empty($item['image'])) { + $item['image'] = $item['category']['image']; + } + if (empty($item['image'])) { + $item['image'] = '/static/images/default.jpg'; + } + } else { + $category = $categoryModel->where('id', $item['cate']) + ->field('id, name, icon, cid') + ->find(); + + $item['category'] = $category ?: ['id' => 0, 'name' => '未分类', 'icon' => '', 'cid' => 0]; + $item['cate'] = $item['category']['name']; // 使用分类名称替换分类ID + + // 如果资源的图片为空,使用分类的图片 + if (empty($item['icon'])) { + $item['icon'] = $item['category']['icon']; + } + if (empty($item['icon'])) { + $item['icon'] = '/static/images/default.jpg'; + } + + // 根据分类cid判断资源类型 + if ($item['category']['cid'] == 8) { + $item['detail_url'] = url('game/detail', ['id' => $item['id']]); + } else { + $item['detail_url'] = url('program/detail', ['id' => $item['id']]); + } + } + } + + // 准备视图数据 + $viewData = [ + 'keyword' => $keyword, + 'type' => $type, + 'items' => $items, + 'detailUrl' => $detailUrl, + 'count' => $count, + 'page' => $page, + 'limit' => $limit + ]; + + return view('index', $viewData); + } +} diff --git a/app/index/controller/StorageController.php b/app/index/controller/StorageController.php new file mode 100644 index 0000000..87b33ba --- /dev/null +++ b/app/index/controller/StorageController.php @@ -0,0 +1,70 @@ + $fileType, + 'Content-Disposition' => 'attachment; filename="' . $fileName . '"', + 'Content-Length' => $fileSize, + 'Cache-Control' => 'no-cache, must-revalidate', + 'Pragma' => 'no-cache', + 'Expires' => '0' + ]; + + // 读取文件内容 + $content = file_get_contents($filePath); + + // 返回文件下载响应 + return Response::create($content, 'file', 200, $headers); + } +} \ No newline at end of file diff --git a/app/index/controller/UserController.php b/app/index/controller/UserController.php new file mode 100644 index 0000000..a8fe4b1 --- /dev/null +++ b/app/index/controller/UserController.php @@ -0,0 +1,754 @@ +request->isPost()) { + $data = $this->request->post(); + + try { + // 验证数据 + $validate = validate([ + 'account' => 'require|email', + 'password' => 'require' + ]); + + if (!$validate->check($data)) { + return json(['code' => 1, 'msg' => $validate->getError()]); + } + + // 查询用户 + $user = Users::where('account', $data['account'])->find(); + if (!$user) { + return json(['code' => 1, 'msg' => '用户不存在']); + } + + // 验证密码 + if ($user->password !== md5($data['password'])) { + return json(['code' => 1, 'msg' => '密码错误']); + } + + // 登录成功,设置session + session('user_id', $user->id); + session('user_name', $user->name); + session('user_avatar', $user->avatar ?? '/static/images/avatar.png'); + + // 设置cookie,有效期7天 + $expire = 7 * 24 * 3600; + cookie('user_id', $user->id, ['expire' => $expire]); + cookie('user_account', $user->account, ['expire' => $expire]); + cookie('user_name', $user->name, ['expire' => $expire]); + cookie('user_avatar', $user->avatar ?? '/static/images/avatar.png', ['expire' => $expire]); + + // 记录登录日志 + Log::record('用户登录成功:' . $user->account, 'info'); + + return json(['code' => 0, 'msg' => '登录成功']); + + } catch (\Exception $e) { + Log::record('登录失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '登录失败:' . $e->getMessage()]); + } + } + + return view('login'); + } + + /** + * 用户注册 + * + * @return \think\Response + */ + public function register() + { + if ($this->request->isPost()) { + $data = $this->request->post(); + + try { + // 验证数据 + $validate = validate([ + 'account' => 'require|email|unique:users', + 'code' => 'require|number|length:6', + 'password' => 'require|min:6|max:20', + 'repassword' => 'require|confirm:password' + ], [ + 'account.require' => '账户不能为空', + 'account.email' => '邮箱格式不正确', + 'account.unique' => '该邮箱已注册', + 'code.require' => '验证码不能为空', + 'code.number' => '验证码必须为数字', + 'code.length' => '验证码长度必须为6位', + 'password.require' => '密码不能为空', + 'password.min' => '密码长度不能小于6个字符', + 'password.max' => '密码长度不能超过20个字符', + 'repassword.require' => '确认密码不能为空', + 'repassword.confirm' => '两次输入的密码不一致' + ]); + + if (!$validate->check($data)) { + return json(['code' => 1, 'msg' => $validate->getError()]); + } + + // 验证邮箱验证码 + $emailCode = cache('email_code_' . $data['account']); + if (!$emailCode || $emailCode != $data['code']) { + return json(['code' => 1, 'msg' => '验证码错误或已过期']); + } + + // 创建用户 + $user = new Users; + $user->account = $data['account']; + $user->password = md5($data['password']); + $user->name = $this->generateRandomName(); + $user->create_time = time(); + $user->save(); + + // 清除验证码缓存 + cache('email_code_' . $data['account'], null); + + return json(['code' => 0, 'msg' => '注册成功']); + + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '注册失败:' . $e->getMessage()]); + } + } + + return view('register'); + } + + /** + * 退出登录 + * + * @return \think\Response + */ + public function logout() + { + try { + Log::record('用户退出登录', 'info'); + + // 清除所有会话和缓存数据 + session(null); + Cache::tag('user_cache')->clear(); + + // 清除所有cookie + $cookies = [ + 'user_id', + 'user_account', + 'user_name', + 'user_avatar', + 'expire_time', + 'is_auto_login', + 'auto_login_attempted', + 'PHPSESSID' + ]; + foreach ($cookies as $cookie) { + cookie($cookie, null, ['expire' => -1]); + } + + return json(['code' => 0, 'msg' => '退出成功', 'data' => ['clear_storage' => true]]); + } catch (\Exception $e) { + Log::record('退出登录失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '退出失败:' . $e->getMessage()]); + } + } + + // 生成随机用户名 + private function generateRandomName() + { + return '云朵_' . mt_rand(100000, 999999); + } + + // 发送短信验证码 + public function sendSmsCode() + { + if ($this->request->isPost()) { + $phone = $this->request->post('phone'); + + // 验证手机号 + $validate = validate([ + 'phone' => 'require|mobile|unique:user' + ], [ + 'phone.require' => '手机号不能为空', + 'phone.mobile' => '手机号格式不正确', + 'phone.unique' => '该手机号已注册' + ]); + + if (!$validate->check(['phone' => $phone])) { + return json(['code' => 0, 'msg' => $validate->getError()]); + } + + // 生成6位随机验证码 + $code = mt_rand(100000, 999999); + + // 这里应该调用短信服务商API发送验证码 + // 示例代码,实际使用时需要替换为真实的短信发送逻辑 + try { + // TODO: 调用短信服务商API发送验证码 + // $result = sendSms($phone, $code); + + // 将验证码保存到缓存,有效期5分钟 + cache('sms_code_' . $phone, $code, 300); + + return json(['code' => 1, 'msg' => '验证码发送成功']); + } catch (\Exception $e) { + return json(['code' => 0, 'msg' => '验证码发送失败:' . $e->getMessage()]); + } + } + + return json(['code' => 0, 'msg' => '非法请求']); + } + + // 微信授权回调 + // public function wechatCallback() + // { + // $code = $this->request->get('code'); + // if (!$code) { + // return json(['code' => 0, 'msg' => '微信授权失败']); + // } + + // try { + // // 这里应该调用微信API获取用户信息 + // // 示例代码,实际使用时需要替换为真实的微信API调用逻辑 + // // $wechatUser = getWechatUserInfo($code); + + // // 模拟获取到的微信用户信息 + // $wechatUser = [ + // 'openid' => 'test_openid_' . time(), + // 'nickname' => '微信用户', + // 'avatar' => '' + // ]; + + // // 检查用户是否已注册 + // $user = Users::where('openid', $wechatUser['openid'])->find(); + // if ($user) { + // // 已注册,直接登录 + // session('user_id', $user->id); + // return json(['code' => 1, 'msg' => '登录成功']); + // } + + // // 未注册,返回注册所需信息 + // return json([ + // 'code' => 2, + // 'msg' => '需要注册', + // 'data' => $wechatUser + // ]); + // } catch (\Exception $e) { + // return json(['code' => 0, 'msg' => '微信授权失败:' . $e->getMessage()]); + // } + // } + + // 发送邮箱验证码 + public function sendEmailCode() + { + // 设置响应头 + header('Content-Type: application/json; charset=utf-8'); + + if (!$this->request->isPost()) { + return json(['code' => 1, 'msg' => '请求方法无效']); + } + + $email = $this->request->post('account'); + if (empty($email)) { + return json(['code' => 1, 'msg' => '邮箱不能为空']); + } + + // 验证邮箱格式 + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + return json(['code' => 1, 'msg' => '邮箱格式不正确']); + } + + // 检查邮箱是否已注册 + $exists = Users::where('account', $email)->find(); + if ($exists) { + return json(['code' => 1, 'msg' => '该邮箱已注册']); + } + + // 生成6位随机验证码 + $code = mt_rand(100000, 999999); + + // 发送验证码邮件 + $result = parent::sendEmail($email, "您的注册验证码是:{$code},有效期为5分钟。", '注册验证码'); + + if ($result === '发送成功') { // 修改这里的判断条件 + // 将验证码存入缓存,有效期5分钟 + cache('email_code_' . $email, $code, 300); + return json(['code' => 0, 'msg' => '验证码已发送']); + } else { + return json(['code' => 1, 'msg' => '发送失败:' . $result]); + } + } + + //个人中心 + public function profile() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return redirect('/index/user/login'); + } + + // 获取用户信息 + $user = Users::where('account', cookie('user_account'))->find(); + // var_dump($user); + if (!$user) { + return redirect('/index/user/login'); + } + + View::assign('user', $user); + return $this->fetch(); + } + + //个人资料 + public function saveBasic() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + // 获取用户信息 + $user = Users::where('account', cookie('user_account'))->find(); + if (!$user) { + return json(['code' => 1, 'msg' => '用户不存在']); + } + + // 获取表单数据 + $data = $this->request->post(); + + // 验证用户名 + if (empty($data['name'])) { + return json(['code' => 1, 'msg' => '用户名不能为空']); + } + + // 验证手机号格式 + if (!empty($data['phone']) && !preg_match('/^1[3-9]\d{9}$/', $data['phone'])) { + return json(['code' => 1, 'msg' => '请检查手机号']); + } + + // 检查用户名是否已被使用(排除当前用户) + $existingUser = Users::where('name', $data['name']) + ->where('uid', '<>', $user->uid) // 排除当前用户 + ->find(); + + if ($existingUser) { + return json(['code' => 1, 'msg' => '该用户名已被使用']); + } + + // 更新用户信息 + $user->name = $data['name']; + $user->phone = $data['phone'] ?? ''; + $user->sex = $data['sex'] ?? 0; + $user->qq = $data['qq'] ?? ''; + $user->wechat = $data['wechat'] ?? ''; + $user->update_time = time(); + + if ($user->save()) { + return json(['code' => 0, 'msg' => '保存成功']); + } else { + return json(['code' => 1, 'msg' => '保存失败']); + } + } + + //更新头像 + public function update_avatar() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + // 获取用户信息 + $user = Users::where('account', cookie('user_account'))->find(); + if (!$user) { + return json(['code' => 1, 'msg' => '用户不存在']); + } + + // 获取上传的文件 + $file = $this->request->file('avatar'); + if (!$file) { + return json(['code' => 1, 'msg' => '请选择要上传的头像']); + } + + try { + // 验证文件大小和类型 + if ($file->getSize() > 2097152) { // 2MB + return json(['code' => 1, 'msg' => '图片大小不能超过2MB']); + } + + $ext = strtolower($file->getOriginalExtension()); + if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) { + return json(['code' => 1, 'msg' => '只支持jpg、jpeg、png、gif、webp格式的图片']); + } + + // 移动到指定目录 + $savename = \think\facade\Filesystem::disk('public')->putFile('avatar', $file); + if (!$savename) { + return json(['code' => 1, 'msg' => '图片上传失败']); + } + + // 获取文件URL + $avatarUrl = '/storage/' . $savename; + + // 更新用户头像 + $user->avatar = $avatarUrl; + $user->update_time = time(); + + if ($user->save()) { + return json(['code' => 0, 'msg' => '头像更新成功', 'data' => ['url' => $avatarUrl]]); + } else { + return json(['code' => 1, 'msg' => '头像更新失败']); + } + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '系统错误:' . $e->getMessage()]); + } + } + + /** + * 获取系统通知列表 + */ + public function getNotifications() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + $type = $this->request->get('type', 'all'); // 获取通知类型:all, unread, read + $userId = cookie('user_id'); + + try { + // 构建查询条件 + $where = [ + ['status', '=', 1] // 只获取启用的通知 + ]; + + // 查询系统通知 + $notices = SystemNotice::where($where) + ->order('is_top', 'desc') // 置顶的排在前面 + ->order('create_time', 'desc') + ->select(); + + // 格式化数据 + $data = []; + foreach ($notices as $notice) { + // 检查用户是否已读该通知 + $isRead = SystemNotice::where([ + ['user_id', '=', $userId], + ['notice_id', '=', $notice->id], + ['is_read', '=', 1] + ])->find(); + + // 根据type过滤 + if ($type == 'unread' && $isRead) + continue; + if ($type == 'read' && !$isRead) + continue; + + $data[] = [ + 'id' => $notice->id, + 'title' => $notice->title, + 'content' => $notice->content, + 'type' => $notice->type, + 'is_top' => $notice->is_top, + 'is_read' => $isRead ? 1 : 0, + 'create_time' => date('Y-m-d H:i:s', $notice->create_time) + ]; + } + + return json(['code' => 0, 'msg' => '获取成功', 'data' => $data]); + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '获取失败:' . $e->getMessage()]); + } + } + + /** + * 查看通知详情 + */ + public function readNotification() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + $data = $this->request->post(); + $noticeId = $data['id'] ?? 0; + $userId = cookie('user_id'); + + try { + // 查询通知 + $notice = SystemNotice::where('id', $noticeId) + ->where('status', 1) + ->find(); + + if (!$notice) { + return json(['code' => 1, 'msg' => '通知不存在']); + } + + // 记录用户已读状态 + $message = SystemNotice::where([ + ['user_id', '=', $userId], + ['notice_id', '=', $noticeId] + ])->find(); + + if (!$message) { + // 创建新的已读记录 + $message = new SystemNotice; + $message->user_id = $userId; + $message->notice_id = $noticeId; + $message->is_read = 1; + $message->read_time = time(); + $message->save(); + } elseif (!$message->is_read) { + // 更新已读状态 + $message->is_read = 1; + $message->read_time = time(); + $message->save(); + } + + return json(['code' => 0, 'msg' => '操作成功']); + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '操作失败:' . $e->getMessage()]); + } + } + + /** + * 通知详情页面 + */ + public function notificationDetail() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return redirect('/index/user/login'); + } + + $noticeId = $this->request->get('id'); + $userId = cookie('user_id'); + + try { + // 查询通知 + $notice = SystemNotice::where('id', $noticeId) + ->where('status', 1) + ->find(); + + if (!$notice) { + return $this->error('通知不存在'); + } + + // 记录用户已读状态 + $message = SystemNotice::where([ + ['user_id', '=', $userId], + ['notice_id', '=', $noticeId] + ])->find(); + + if (!$message) { + // 创建新的已读记录 + $message = new SystemNotice; + $message->user_id = $userId; + $message->notice_id = $noticeId; + $message->is_read = 1; + $message->read_time = time(); + $message->save(); + } elseif (!$message->is_read) { + // 更新已读状态 + $message->is_read = 1; + $message->read_time = time(); + $message->save(); + } + + // 增加查看次数 + $notice->view_count = $notice->view_count + 1; + $notice->save(); + + View::assign('notice', $notice); + return $this->fetch('notification_detail'); + } catch (\Exception $e) { + return $this->error('获取通知详情失败:' . $e->getMessage()); + } + } + + /** + * 获取系统通知列表 + */ + public function getMessages() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + $type = $this->request->get('type', 'all'); // 获取通知类型:all, unread, read + $userId = cookie('user_id'); + + try { + // 构建查询条件 + $where = [ + ['status', '=', 1] // 只获取启用的通知 + ]; + + // 查询系统通知 + $notices = UserMessage::where($where) + ->order('is_top', 'desc') // 置顶的排在前面 + ->order('create_time', 'desc') + ->select(); + + // 格式化数据 + $data = []; + foreach ($notices as $notice) { + // 检查用户是否已读该通知 + $isRead = UserMessage::where([ + ['user_id', '=', $userId], + ['notice_id', '=', $notice->id], + ['is_read', '=', 1] + ])->find(); + + // 根据type过滤 + if ($type == 'unread' && $isRead) + continue; + if ($type == 'read' && !$isRead) + continue; + + $data[] = [ + 'id' => $notice->id, + 'title' => $notice->title, + 'content' => $notice->content, + 'type' => $notice->type, + 'is_top' => $notice->is_top, + 'is_read' => $isRead ? 1 : 0, + 'create_time' => date('Y-m-d H:i:s', $notice->create_time) + ]; + } + + return json(['code' => 0, 'msg' => '获取成功', 'data' => $data]); + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '获取失败:' . $e->getMessage()]); + } + } + + //修改密码 + public function updatePassword() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return redirect('/index/user/login'); + } + + // 获取用户信息 + $user = Users::where('account', cookie('user_account'))->find(); + if (!$user) { + return redirect('/index/user/login'); + } + + // 如果是GET请求,显示修改密码页面 + if ($this->request->isGet()) { + return $this->fetch(); + } + + // 如果是POST请求,处理密码修改 + if ($this->request->isPost()) { + $data = $this->request->post(); + + // 验证旧密码 + if ($user->password !== md5($data['old_password'])) { + return json(['code' => 1, 'msg' => '旧密码错误']); + } + + // 验证新密码 + if ($data['new_password'] !== $data['confirm_password']) { + return json(['code' => 1, 'msg' => '两次输入的密码不一致']); + } + + // 更新密码 + $user->password = md5($data['new_password']); + $user->update_time = time(); + + if ($user->save()) { + // 清除登录状态 + cookie('user_id', null, ['expire' => -1]); + cookie('user_account', null, ['expire' => -1]); + cookie('user_name', null, ['expire' => -1]); + cookie('user_avatar', null, ['expire' => -1]); + + return json(['code' => 0, 'msg' => '密码修改成功,请重新登录']); + } else { + return json(['code' => 1, 'msg' => '密码修改失败']); + } + } + } + + //生成二维码绑定微信 + public function qrcode() + { + // 检查用户是否登录 + if (!cookie('user_account')) { + return json(['code'=> -1,'msg'=> '请先登录']); + } + + // 获取当前用户信息 + $user = Users::where('account', cookie('user_account'))->find(); + if (!$user) { + return json(['code' => -1, 'msg' => '用户信息获取失败']); + } + + // 假设这里生成一个唯一的绑定标识,例如使用用户ID和时间戳组合 + $bindToken = md5($user->id . time()); + + // 生成实际的绑定 URL + $domain = $this->request->domain(); + $bindUrl = "{$domain}/wechat_bind?token={$bindToken}"; + + // 将绑定标识存入缓存,设置有效期,例如30分钟 + cache('wechat_bind_token_' . $user->id, $bindToken, 1800); + + try { + // 创建二维码实例 + $qrCode = QrCode::create($bindUrl); + $writer = new PngWriter(); + + // 生成二维码图片 + $result = $writer->write($qrCode); + $qrCodeDataUri = $result->getDataUri(); + + return json(['code' => 0, 'msg' => '二维码生成成功', 'data' => ['qrcode_url' => $qrCodeDataUri]]); + } catch (\Exception $e) { + return json(['code' => -1, 'msg' => '二维码生成失败: ' . $e->getMessage()]); + } + } +} diff --git a/app/index/controller/WechatController.php b/app/index/controller/WechatController.php new file mode 100644 index 0000000..2c52930 --- /dev/null +++ b/app/index/controller/WechatController.php @@ -0,0 +1,867 @@ + date('Y-m-d H:i:s'), + 'status' => 'ok', + 'server' => [ + 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'] ?? '', + 'REQUEST_URI' => $_SERVER['REQUEST_URI'] ?? '', + 'HTTP_HOST' => $_SERVER['HTTP_HOST'] ?? '', + 'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'] ?? '', + ] + ]; + + // 记录详细日志 + Log::info('接口测试 - 请求信息:' . json_encode($data, JSON_UNESCAPED_UNICODE)); + + return json($data, JSON_UNESCAPED_UNICODE); + } catch (\Exception $e) { + $error = [ + 'error' => $e->getMessage(), + 'time' => date('Y-m-d H:i:s') + ]; + Log::error('接口测试错误:' . json_encode($error, JSON_UNESCAPED_UNICODE)); + return json($error, JSON_UNESCAPED_UNICODE); + } + } + + public function index() + { + try { + // 设置响应头 + header('Content-Type: text/html; charset=utf-8'); + + // 记录原始请求信息 + $requestInfo = [ + 'time' => date('Y-m-d H:i:s'), + 'method' => Request::method(), + 'url' => Request::url(true), + 'ip' => Request::ip(), + 'real_ip' => Request::server('HTTP_X_REAL_IP'), + 'forwarded_ip' => Request::server('HTTP_X_FORWARDED_FOR'), + 'params' => Request::param(), + 'headers' => getallheaders(), + 'server' => [ + 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'] ?? '', + 'REQUEST_URI' => $_SERVER['REQUEST_URI'] ?? '', + 'HTTP_HOST' => $_SERVER['HTTP_HOST'] ?? '', + 'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'] ?? '', + 'QUERY_STRING' => $_SERVER['QUERY_STRING'] ?? '', + 'HTTP_USER_AGENT' => $_SERVER['HTTP_USER_AGENT'] ?? '', + 'HTTP_REFERER' => $_SERVER['HTTP_REFERER'] ?? '', + 'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'] ?? '', + 'CONTENT_LENGTH' => $_SERVER['CONTENT_LENGTH'] ?? '', + 'SERVER_NAME' => $_SERVER['SERVER_NAME'] ?? '', + 'SERVER_ADDR' => $_SERVER['SERVER_ADDR'] ?? '', + 'SERVER_PORT' => $_SERVER['SERVER_PORT'] ?? '', + 'REQUEST_SCHEME' => $_SERVER['REQUEST_SCHEME'] ?? '', + ], + 'env' => [ + 'app_debug' => config('app.debug'), + 'app_env' => config('app.env'), + 'domain' => config('app.domain'), + ] + ]; + + Log::info('微信接口访问请求信息:' . json_encode($requestInfo, JSON_UNESCAPED_UNICODE)); + + // 获取原始数据 + $rawData = file_get_contents("php://input"); + if (!empty($rawData)) { + $rawData = mb_convert_encoding($rawData, 'UTF-8', 'UTF-8,GBK,GB2312'); + Log::info('微信接口原始数据:' . $rawData); + } + + // 检查请求方法 + if (Request::method() == 'GET') { + Log::info('微信接口:GET请求,进行签名验证'); + // 首次验证服务器地址的有效性 + $this->checkSignature(); + } elseif (Request::method() == 'POST') { + Log::info('微信接口:POST请求,处理消息'); + // 接收消息并回复 + $response = $this->receiveMessage(); + // 直接输出回复的XML字符串 + echo $response; + exit; + } else { + Log::error('微信接口:不支持的请求方法 - ' . Request::method()); + echo ''; + exit; + } + } catch (\Exception $e) { + Log::error('微信接口错误:' . $e->getMessage()); + Log::error('错误堆栈:' . $e->getTraceAsString()); + // 返回空字符串,避免微信服务器重试 + echo ''; + exit; + } + } + + // 验证签名 + protected function checkSignature() + { + try { + $signature = Request::get('signature'); + $timestamp = Request::get('timestamp'); + $nonce = Request::get('nonce'); + $echostr = Request::get('echostr'); + + $debugInfo = [ + 'signature' => $signature, + 'timestamp' => $timestamp, + 'nonce' => $nonce, + 'echostr' => $echostr, + 'url' => Request::url(true), + 'time' => date('Y-m-d H:i:s'), + 'ip' => Request::ip(), + 'headers' => getallheaders() + ]; + + Log::info('微信验证参数:' . json_encode($debugInfo, JSON_UNESCAPED_UNICODE)); + + if (empty($signature) || empty($timestamp) || empty($nonce)) { + Log::error('微信验证参数缺失', $debugInfo); + exit; + } + + $token = AdminConfig::where('config_name', 'wechat_token')->value('config_value'); + if (empty($token)) { + Log::error('微信token未配置'); + exit; + } + + Log::info('微信token:' . $token); + + $tmpArr = array($token, $timestamp, $nonce); + sort($tmpArr, SORT_STRING); + $tmpStr = implode($tmpArr); + $tmpStr = sha1($tmpStr); + + $verifyInfo = [ + 'tmpStr' => $tmpStr, + 'signature' => $signature, + 'token' => $token, + 'timestamp' => $timestamp, + 'nonce' => $nonce + ]; + + Log::info('签名验证:' . json_encode($verifyInfo, JSON_UNESCAPED_UNICODE)); + + if ($tmpStr == $signature) { + Log::info('微信签名验证成功'); + echo $echostr; + exit; + } else { + Log::error('微信签名验证失败', $verifyInfo); + exit; + } + } catch (\Exception $e) { + Log::error('微信验证签名错误:' . $e->getMessage()); + Log::error('错误堆栈:' . $e->getTraceAsString()); + exit; + } + } + + // 接收消息 + protected function receiveMessage() + { + try { + $postStr = file_get_contents("php://input"); + if (empty($postStr)) { + Log::error('微信消息:接收数据为空'); + return ''; + } + + // 转换编码 + $postStr = mb_convert_encoding($postStr, 'UTF-8', 'UTF-8,GBK,GB2312'); + Log::info('微信消息原始数据:' . $postStr); + + // 使用DOMDocument替代simplexml_load_string + $dom = new \DOMDocument(); + $dom->loadXML($postStr, LIBXML_NOCDATA | LIBXML_NOBLANKS); + $postObj = simplexml_import_dom($dom); + + if ($postObj === false) { + Log::error('微信消息:XML解析失败'); + return ''; + } + + // 检查消息类型 + $msgType = strtolower((string) $postObj->MsgType); + Log::info('微信消息类型:' . $msgType); + + // 处理事件消息 + if ($msgType == 'event') { + $event = strtolower((string) $postObj->Event); + Log::info('微信事件类型:' . $event); + + // 处理扫码事件和关注事件 + if ($event == 'scan' || $event == 'subscribe') { + try { + $scene_str = trim((string) $postObj->EventKey); + // 如果是关注事件,需要去掉前缀 'qrscene_' + if ($event == 'subscribe' && strpos($scene_str, 'qrscene_') === 0) { + $scene_str = substr($scene_str, 8); + } + + $fromUsername = trim((string) $postObj->FromUserName); + $toUsername = trim((string) $postObj->ToUserName); + $ticket = (string) $postObj->Ticket; + + Log::info('微信扫码/关注事件详情', [ + 'event' => $event, + 'scene_str' => $scene_str, + 'fromUsername' => $fromUsername, + 'toUsername' => $toUsername, + 'ticket' => $ticket, + 'time' => date('Y-m-d H:i:s'), + 'raw_data' => $postStr + ]); + + // 创建票据保存目录 + $upload_dir = 'public/storage/uploads/ticket/'; + if (!is_dir($upload_dir)) { + if (!mkdir($upload_dir, 0755, true)) { + Log::error('创建票据保存目录失败:' . $upload_dir); + return 'success'; + } + Log::info('创建票据保存目录成功:' . $upload_dir); + } + + // 保存扫码数据 + $data = [ + 'openid' => $fromUsername, + 'scene_str' => $scene_str, + 'ticket' => $ticket, + 'scan_time' => time(), + 'event' => $event, + 'raw_data' => $postStr + ]; + + $content = json_encode($data, JSON_UNESCAPED_UNICODE); + $file = $upload_dir . $scene_str . "_" . $ticket . ".json"; + + if (file_put_contents($file, $content) === false) { + Log::error('保存扫码数据失败:' . $file); + return 'success'; + } + Log::info('保存扫码数据成功:' . $file); + + // 获取用户信息 + try { + $accessToken = $this->getGZHAccessToken(); + Log::info('获取access_token成功:' . $accessToken); + + $url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token={$accessToken}&openid={$fromUsername}&lang=zh_CN"; + $client = new Client(['verify' => false]); + $response = $client->get($url); + $userInfo = json_decode($response->getBody(), true); + + Log::info('获取用户信息结果:' . json_encode($userInfo, JSON_UNESCAPED_UNICODE)); + + if (isset($userInfo['openid'])) { + // 先检查是否存在该openid的用户 + $user = Users::where('openid', $fromUsername)->find(); + + if ($user) { + // 已存在用户,直接登录 + $user->login_count = $user->login_count + 1; // 增加登录次数 + $user->update_time = time(); // 更新登录时间 + + // 只在头像为空或为默认头像时更新 + if (empty($user->avatar) || $user->avatar === '/static/images/avatar.png') { + if (!empty($userInfo['headimgurl'])) { + $user->avatar = $userInfo['headimgurl']; + } elseif (empty($user->avatar)) { + $user->avatar = '/static/images/avatar.png'; + } + } + + if (!$user->save()) { + Log::error('更新用户登录信息失败:' . json_encode($user->getError(), JSON_UNESCAPED_UNICODE)); + return 'success'; + } + Log::info('用户登录成功:' . json_encode($user->toArray(), JSON_UNESCAPED_UNICODE)); + } else { + // 不存在用户,创建新用户 + $user = new Users; + $user->openid = $fromUsername; + // 生成默认账号 + $defaultAccount = 'wx_' . substr(md5($fromUsername), 0, 8); + $user->account = $defaultAccount; + $user->name = $defaultAccount; // 将默认账号同时设置为用户名 + // 设置头像,确保有值 + $user->avatar = !empty($userInfo['headimgurl']) ? $userInfo['headimgurl'] : '/static/images/avatar.png'; + // 生成随机密码 + $user->password = md5(uniqid() . rand(1000, 9999)); + $user->create_time = time(); // 设置创建时间 + $user->login_count = 1; // 首次登录,设置登录次数为1 + + if (!$user->save()) { + Log::error('创建用户失败:' . json_encode($user->getError(), JSON_UNESCAPED_UNICODE)); + return 'success'; + } + Log::info('创建新用户成功:' . json_encode($user->toArray(), JSON_UNESCAPED_UNICODE)); + } + + // 更新票据文件 + $data = [ + 'uid' => (int)$user->uid, // 确保uid是整数 + 'name' => $user->name, + 'avatar' => $user->avatar ?: '/static/images/avatar.png', // 确保avatar不为空 + 'openid' => $user->openid, + 'user_account' => $user->account, + 'user_password' => $user->password, + 'expire_time' => time() + (7 * 24 * 3600), // 7天过期 + 'is_auto_login' => true, + 'login_status' => 'success' + ]; + + if (file_put_contents($file, json_encode($data, JSON_UNESCAPED_UNICODE)) === false) { + Log::error('更新票据文件失败:' . $file); + } else { + Log::info('更新票据文件成功:' . json_encode($data, JSON_UNESCAPED_UNICODE)); + } + + // 设置cookie + $expire = 7 * 24 * 3600; // 7天过期 + cookie('user_account', $user->account, ['expire' => $expire]); + cookie('user_avatar', $user->avatar ?: '/static/images/avatar.png', ['expire' => $expire]); + cookie('user_name', $user->name, ['expire' => $expire]); + cookie('open_id', $user->openid, ['expire' => $expire]); + + // 发送登录成功消息 + $messageContent = [ + 'content' => "您好!\n您已成功登录系统。\n登录时间:" . date('Y-m-d H:i:s') + ]; + $this->sendCustomMessage($fromUsername, 'text', $messageContent); + + Log::info('用户登录成功:' . json_encode([ + 'uid' => (int)$user->uid, + 'name' => $user->name, + 'openid' => $user->openid, + 'account' => $user->account, + 'cookies' => [ + 'user_account' => $user->account, + 'user_avatar' => $user->avatar, + 'user_name' => $user->name, + 'open_id' => $user->openid + ] + ], JSON_UNESCAPED_UNICODE)); + } else { + Log::error('获取用户信息失败:' . json_encode($userInfo, JSON_UNESCAPED_UNICODE)); + } + } catch (\Exception $e) { + Log::error('获取用户信息失败:' . $e->getMessage()); + Log::error('错误堆栈:' . $e->getTraceAsString()); + } + } catch (\Exception $e) { + Log::error('处理扫码事件失败:' . $e->getMessage()); + Log::error('错误堆栈:' . $e->getTraceAsString()); + } + } + } + + return 'success'; + } catch (\Exception $e) { + Log::error('处理微信消息错误:' . $e->getMessage()); + Log::error('错误堆栈:' . $e->getTraceAsString()); + return 'success'; + } + } + + // 发送小程序卡片消息的方法 + private function sendMiniProgramCard($openid) + { + + $accessToken = $this->getGZHAccessToken(); + print_r($accessToken); + if (!$accessToken) { + // 处理获取access_token失败的情况 + return; + } + $postData = json_encode([ + 'touser' => $openid, // 接收者(用户)的openid + 'msgtype' => 'miniprogrampage', + 'miniprogrampage' => [ + 'title' => '小程序标题', + 'appid' => '小程序appid', + 'pagepath' => '小程序路径', + 'thumb_media_id' => '你的thumb_media_id' + ] + ], JSON_UNESCAPED_UNICODE); + $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={$accessToken}"; + $result = json_decode(file_get_contents($url, false, stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "Content-type: application/json\r\n", + 'content' => $postData, + 'timeout' => 60 // 超时时间(单位:s) + ] + ])), true); + // 处理返回结果 + if ($result['errcode'] == 0) { + // 发送成功 + } else { + // 发送失败 + } + } + + // 获取公众号Access token + public function getGZHAccessToken() + { + // 从数据库获取配置 + $appid = AdminConfig::where('config_name', 'wechat_appid')->value('config_value'); + $secret = AdminConfig::where('config_name', 'wechat_appsecret')->value('config_value'); + + if (empty($appid) || empty($secret)) { + throw new \Exception('微信配置信息未设置'); + } + + // 构建请求URL + $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}"; + + try { + // 使用 GuzzleHttp 发送请求,禁用SSL验证 + $client = new Client([ + 'verify' => false + ]); + $response = $client->get($url); + $data = json_decode($response->getBody(), true); + + if (!isset($data['access_token'])) { + throw new \Exception("获取access_token失败: {$data['errmsg']}", $data['errcode'] ?? -1); + } + + return $data['access_token']; + } catch (\Exception $e) { + Log::error('获取access_token失败:' . $e->getMessage()); + throw $e; + } + } + + /** + * 获取微信登录二维码 + * @return \think\response\Json + */ + public function getLoginTicket() + { + try { + Log::info('开始获取微信登录二维码'); + + // 获取access_token + $access_token = $this->getGZHAccessToken(); + Log::info('获取access_token成功:' . $access_token); + + // 构建请求URL - 使用正确的接口生成临时二维码 + $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={$access_token}"; + + // 生成唯一场景值 + $scene_str = md5(uniqid() . time()); + Log::info('生成场景值:' . $scene_str); + + // 准备请求数据 - 生成临时二维码,有效期5分钟 + $postData = json_encode([ + 'expire_seconds' => 300, // 5分钟有效期 + 'action_name' => 'QR_STR_SCENE', + 'action_info' => [ + 'scene' => [ + 'scene_str' => $scene_str + ] + ] + ]); + + // 发送请求获取ticket + $client = new Client(['verify' => false]); + $response = $client->post($url, [ + 'body' => $postData, + 'headers' => [ + 'Content-Type' => 'application/json' + ] + ]); + + $result = json_decode($response->getBody(), true); + Log::info('微信返回结果:' . json_encode($result, JSON_UNESCAPED_UNICODE)); + + if (isset($result['errcode']) && $result['errcode'] != 0) { + Log::error('获取二维码失败:' . $result['errmsg']); + return json(['code' => 1, 'msg' => '获取二维码失败:' . $result['errmsg']]); + } + + // 使用ticket获取二维码图片URL + $ticket = urlencode($result['ticket']); + $qrcodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={$ticket}"; + + // 将场景值保存到缓存中,用于后续验证 + Cache::set('wx_login_scene_' . $result['ticket'], [ + 'scene_str' => $scene_str, + 'create_time' => time(), + 'expire_time' => time() + 300 + ], 300); + + Log::info('二维码生成成功', [ + 'ticket' => $result['ticket'], + 'scene_str' => $scene_str, + 'expire_time' => time() + 300 + ]); + + return json([ + 'code' => 0, + 'msg' => '获取二维码成功', + 'data' => [ + 'ticket' => $result['ticket'], + 'expire_seconds' => $result['expire_seconds'], + 'url' => $qrcodeUrl, + 'scene_str' => $scene_str + ] + ]); + + } catch (\Exception $e) { + Log::error('获取微信登录二维码失败:' . $e->getMessage()); + Log::error('错误堆栈:' . $e->getTraceAsString()); + return json(['code' => 1, 'msg' => '获取二维码失败:' . $e->getMessage()]); + } + } + + /** + * 重新生成二维码 + * @return \think\response\Json + */ + public function reGenerateQrcode() + { + try { + $scene_str = Request::post('scene_str'); + if (empty($scene_str)) { + return json(['code' => 1, 'msg' => '参数错误']); + } + + // 清除旧的缓存 + Cache::delete('wx_login_scene_' . $scene_str); + + // 获取access_token + $access_token = $this->getGZHAccessToken(); + + // 构建请求URL + $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={$access_token}"; + + // 生成新的场景值 + $new_scene_str = md5(uniqid() . time()); + + // 准备请求数据 + $postData = json_encode([ + 'expire_seconds' => 300, + 'action_name' => 'QR_STR_SCENE', + 'action_info' => [ + 'scene' => [ + 'scene_str' => $new_scene_str + ] + ] + ]); + + // 发送请求获取ticket + $client = new Client(['verify' => false]); + $response = $client->post($url, [ + 'body' => $postData, + 'headers' => [ + 'Content-Type' => 'application/json' + ] + ]); + + $result = json_decode($response->getBody(), true); + + if (isset($result['errcode']) && $result['errcode'] != 0) { + return json(['code' => 1, 'msg' => '获取二维码失败:' . $result['errmsg']]); + } + + // 使用ticket获取二维码图片URL + $ticket = urlencode($result['ticket']); + $qrcodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={$ticket}"; + + // 将新的场景值保存到缓存中 + Cache::set('wx_login_scene_' . $new_scene_str, [ + 'scene_str' => $new_scene_str, + 'create_time' => time(), + 'expire_time' => time() + 300 + ], 300); + + return json([ + 'code' => 0, + 'msg' => '获取二维码成功', + 'data' => [ + 'ticket' => $result['ticket'], + 'expire_seconds' => $result['expire_seconds'], + 'url' => $qrcodeUrl, + 'scene_str' => $new_scene_str + ] + ]); + + } catch (\Exception $e) { + Log::error('重新生成二维码失败:' . $e->getMessage()); + return json(['code' => 1, 'msg' => '重新生成二维码失败:' . $e->getMessage()]); + } + } + + /** + * 检查微信扫码登录状态 + * @return \think\response\Json + */ + public function checkLoginStatus() + { + try { + $scene_str = Request::post('scene_str'); + $ticket = Request::post('ticket'); + + if (empty($scene_str) || empty($ticket)) { + return json(['code' => 0, 'msg' => '参数错误']); + } + + // 检查票据文件 + $file = 'public/storage/uploads/ticket/' . $scene_str . "_" . $ticket . ".json"; + + if (!file_exists($file)) { + return json(['code' => 0, 'msg' => '等待扫码']); + } + + $loginData = json_decode(file_get_contents($file), true); + if (!$loginData) { + return json(['code' => 0, 'msg' => '等待扫码']); + } + + // 检查是否已经扫码 + if (!isset($loginData['openid'])) { + return json(['code' => 0, 'msg' => '等待扫码']); + } + + // 检查登录状态 + if (!isset($loginData['login_status']) || $loginData['login_status'] !== 'success') { + return json(['code' => 0, 'msg' => '正在处理登录,请稍候...']); + } + + // 登录成功,设置session + session('user_id', $loginData['uid']); + session('user_name', $loginData['name']); + session('user_avatar', $loginData['avatar']); + session('user_account', $loginData['user_account']); + session('openid', $loginData['openid']); + + // 删除临时文件 + @unlink($file); + + // 返回用户信息 + return json([ + 'code' => 1, + 'msg' => '登录成功', + 'data' => [ + 'uid' => $loginData['uid'], + 'name' => $loginData['name'], + 'avatar' => $loginData['avatar'], + 'openid' => $loginData['openid'], + 'user_account' => $loginData['user_account'], + 'user_password' => $loginData['user_password'], + 'expire_time' => $loginData['expire_time'], + 'is_auto_login' => $loginData['is_auto_login'] + ] + ]); + + } catch (\Exception $e) { + Log::error('检查登录状态失败:' . $e->getMessage()); + return json(['code' => 0, 'msg' => '系统错误']); + } + } + + /** + * 根据openid获取用户信息 + * @param string $openid + * @return array|null + */ + private function getUserInfoByOpenid($openid) + { + try { + // 这里需要根据你的用户表结构来实现 + // 示例:从用户表中查询openid对应的用户信息 + $user = Users::where('openid', $openid)->find(); + if ($user) { + return $user->toArray(); + } + return null; + } catch (\Exception $e) { + Log::error('获取用户信息失败:' . $e->getMessage()); + return null; + } + } + + /** + * 测试微信服务器访问 + */ + public function testWechat() + { + try { + // 记录所有请求信息 + $requestInfo = [ + 'time' => date('Y-m-d H:i:s'), + 'method' => Request::method(), + 'url' => Request::url(true), + 'ip' => Request::ip(), + 'real_ip' => Request::server('HTTP_X_REAL_IP'), + 'forwarded_ip' => Request::server('HTTP_X_FORWARDED_FOR'), + 'params' => Request::param(), + 'headers' => getallheaders(), + 'server' => [ + 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'] ?? '', + 'REQUEST_URI' => $_SERVER['REQUEST_URI'] ?? '', + 'HTTP_HOST' => $_SERVER['HTTP_HOST'] ?? '', + 'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'] ?? '', + 'QUERY_STRING' => $_SERVER['QUERY_STRING'] ?? '', + 'HTTP_USER_AGENT' => $_SERVER['HTTP_USER_AGENT'] ?? '', + 'HTTP_REFERER' => $_SERVER['HTTP_REFERER'] ?? '', + 'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'] ?? '', + 'CONTENT_LENGTH' => $_SERVER['CONTENT_LENGTH'] ?? '', + 'SERVER_NAME' => $_SERVER['SERVER_NAME'] ?? '', + 'SERVER_ADDR' => $_SERVER['SERVER_ADDR'] ?? '', + 'SERVER_PORT' => $_SERVER['SERVER_PORT'] ?? '', + 'REQUEST_SCHEME' => $_SERVER['REQUEST_SCHEME'] ?? '', + ] + ]; + + Log::info('微信测试接口访问:' . json_encode($requestInfo, JSON_UNESCAPED_UNICODE)); + + // 获取原始数据 + $rawData = file_get_contents("php://input"); + if (!empty($rawData)) { + $rawData = mb_convert_encoding($rawData, 'UTF-8', 'UTF-8,GBK,GB2312'); + Log::info('微信测试接口原始数据:' . $rawData); + } + + // 如果是GET请求,进行签名验证 + if (Request::method() == 'GET') { + $signature = Request::get('signature'); + $timestamp = Request::get('timestamp'); + $nonce = Request::get('nonce'); + $echostr = Request::get('echostr'); + + $verifyInfo = [ + 'signature' => $signature, + 'timestamp' => $timestamp, + 'nonce' => $nonce, + 'echostr' => $echostr, + 'time' => date('Y-m-d H:i:s'), + 'ip' => Request::ip(), + 'headers' => getallheaders() + ]; + + Log::info('微信测试接口验证参数:' . json_encode($verifyInfo, JSON_UNESCAPED_UNICODE)); + + if (!empty($signature) && !empty($timestamp) && !empty($nonce)) { + $token = AdminConfig::where('config_name', 'wechat_token')->value('config_value'); + if (empty($token)) { + Log::error('微信token未配置'); + return 'token not configured'; + } + + $tmpArr = array($token, $timestamp, $nonce); + sort($tmpArr, SORT_STRING); + $tmpStr = implode($tmpArr); + $tmpStr = sha1($tmpStr); + + if ($tmpStr == $signature) { + Log::info('微信测试接口验证成功'); + return $echostr; + } else { + Log::error('微信测试接口验证失败'); + return 'signature verification failed'; + } + } + } + + return 'success'; + } catch (\Exception $e) { + Log::error('微信测试接口错误:' . $e->getMessage()); + Log::error('错误堆栈:' . $e->getTraceAsString()); + return 'error'; + } + } + + /** + * 发送客服消息 + * @param string $openid 接收者openid + * @param string $type 消息类型 + * @param array $content 消息内容 + * @return bool + */ + private function sendCustomMessage($openid, $type = 'text', $content = []) + { + try { + $accessToken = $this->getGZHAccessToken(); + $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={$accessToken}"; + + $data = [ + 'touser' => $openid, + 'msgtype' => $type + ]; + + if ($type === 'text') { + $data['text'] = ['content' => $content['content'] ?? '登录成功']; + } + + $jsonData = json_encode($data, JSON_UNESCAPED_UNICODE); + + $client = new Client(['verify' => false]); + $response = $client->post($url, [ + 'body' => $jsonData, + 'headers' => [ + 'Content-Type' => 'application/json; charset=utf-8' + ] + ]); + + $result = json_decode($response->getBody(), true); + + if (isset($result['errcode']) && $result['errcode'] == 0) { + Log::info('发送客服消息成功:' . json_encode($result, JSON_UNESCAPED_UNICODE)); + return true; + } else { + Log::error('发送客服消息失败:' . json_encode($result, JSON_UNESCAPED_UNICODE)); + return false; + } + } catch (\Exception $e) { + Log::error('发送客服消息异常:' . $e->getMessage()); + return false; + } + } +} \ No newline at end of file diff --git a/app/index/event.php b/app/index/event.php new file mode 100644 index 0000000..5b51e2b --- /dev/null +++ b/app/index/event.php @@ -0,0 +1,22 @@ + +
    +
    +
    + 首页 + > + {$cateName} +
    +
    +
    +
    +
    +
    +

    {$article.title}

    + {if $article.is_trans eq 1 && $article.transurl} +
    转载至:{$article.transurl} +
    + {/if} + +
    + +
    + {$article.content|raw} +
    + +
    +
    +
    免责声明:
    +
    + +
    +
    +
    + + + +
    + + +
    + +
    + + +
    + + +
    +
    +
    +
    关于作者
    +
    +
    +
    + 作者头像 +
    +
    +
    {$authorInfo.name}
    +
    +
    +
    +
    + +
    +
    资源
    + {$authorInfo.resource_count} +
    +
    +
    文章
    + {$authorInfo.article_count} +
    +
    +
    粉丝
    + + 0 + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + + +
    + +
    + +{include file="component/footer" /} + + + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/articles/index.php b/app/index/view/articles/index.php new file mode 100644 index 0000000..3e123d1 --- /dev/null +++ b/app/index/view/articles/index.php @@ -0,0 +1,562 @@ +{include file="component/head" /} +{include file="component/header" /} + + +
    + +
    +
    +

    文章中心

    +

    探索知识与洞见

    +
    +
    + + +
    +
    + + + + +
    + +
    + {volist name="cate.subCategories" id="subCategory"} + {if $cate.id == $subCategory.id} + {if !empty($subCategory.list)} + {volist name="subCategory.list" id="article"} +
    +
    + {$article.title} +
    +
    +
    +
    + + +
    + + +

    ${article.title}

    +
    + +
    +
    + {/volist} + {else} +
    +
    + +
    +

    暂无文章

    +

    当前分类下没有找到相关文章

    +
    + {/if} + {/if} + {/volist} +
    + + + +
    +
    +
    +
    + + + +{include file="component/footer" /} + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/articles/list.php b/app/index/view/articles/list.php new file mode 100644 index 0000000..6550fb5 --- /dev/null +++ b/app/index/view/articles/list.php @@ -0,0 +1,76 @@ +
    +
    + +
    +
    + +
    + {volist name="categories" id="cate"} +
    {$cate.name}
    + {/volist} +
    +
    +
    + + +
    + {if $category} +
    +

    {$category.name}

    +

    {$category.desc|default=''}

    +
    + {/if} + +
    + {volist name="articles" id="article"} +
    +
    +
    +
    + {$article.title} +
    +
    +
    +
    +

    + {$article.title} +

    +

    {$article.desc|default=''}

    + +
    +
    +
    +
    + {/volist} +
    + + +
    + {$articles|raw} +
    +
    +
    +
    + + diff --git a/app/index/view/common/error.php b/app/index/view/common/error.php new file mode 100644 index 0000000..06d8663 --- /dev/null +++ b/app/index/view/common/error.php @@ -0,0 +1,49 @@ + + + + + 错误提示 + + + + + +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/app/index/view/component/banner.php b/app/index/view/component/banner.php new file mode 100644 index 0000000..5c610ff --- /dev/null +++ b/app/index/view/component/banner.php @@ -0,0 +1,7 @@ +
    + + +
    diff --git a/app/index/view/component/foot.php b/app/index/view/component/foot.php new file mode 100644 index 0000000..0699926 --- /dev/null +++ b/app/index/view/component/foot.php @@ -0,0 +1,344 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/index/view/component/footer.php b/app/index/view/component/footer.php new file mode 100644 index 0000000..a2b6c36 --- /dev/null +++ b/app/index/view/component/footer.php @@ -0,0 +1,60 @@ + + \ No newline at end of file diff --git a/app/index/view/component/head.php b/app/index/view/component/head.php new file mode 100644 index 0000000..9e4bed7 --- /dev/null +++ b/app/index/view/component/head.php @@ -0,0 +1,341 @@ + + + + + + + {$config['web_title']} + + + + + + + + + + + + \ No newline at end of file diff --git a/app/index/view/component/header-simple.php b/app/index/view/component/header-simple.php new file mode 100644 index 0000000..b7f5573 --- /dev/null +++ b/app/index/view/component/header-simple.php @@ -0,0 +1,908 @@ + false, + 'name' => '', + 'avatar' => '/static/images/avatar.png' // 默认头像 +]; + +// 检查cookie +$userAccount = cookie('user_account'); +if ($userAccount) { + $isLoggedIn = true; + $userInfo = [ + 'is_login' => true, + 'name' => cookie('user_name'), + 'avatar' => cookie('user_avatar') ? cookie('user_avatar') : '/static/images/avatar.png' + ]; +} + +// 添加一个隐藏的div来存储登录状态 +$loginStatus = [ + 'isLoggedIn' => $isLoggedIn, + 'userAccount' => $userAccount ?? '' +]; +?> + + + + +
    + + +
    + + + + + +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/app/index/view/component/header.php b/app/index/view/component/header.php new file mode 100644 index 0000000..3a198d8 --- /dev/null +++ b/app/index/view/component/header.php @@ -0,0 +1,201 @@ + false, + 'name' => '', + 'avatar' => '/static/images/avatar.png' // 默认头像 +]; + +// 检查cookie +$userAccount = cookie('user_account'); +if ($userAccount) { + $isLoggedIn = true; + $userInfo = [ + 'is_login' => true, + 'name' => cookie('user_name'), + 'avatar' => cookie('user_avatar') ? cookie('user_avatar') : '/static/images/avatar.png' + ]; +} + +// 添加一个隐藏的div来存储登录状态 +$loginStatus = [ + 'isLoggedIn' => $isLoggedIn, + 'userAccount' => $userAccount ?? '' +]; +?> + + + + +
    + + + +
    + + + + + diff --git a/app/index/view/component/main.php b/app/index/view/component/main.php new file mode 100644 index 0000000..652d82d --- /dev/null +++ b/app/index/view/component/main.php @@ -0,0 +1,6 @@ +
    +
    + + +
    +
    \ No newline at end of file diff --git a/app/index/view/game/detail.php b/app/index/view/game/detail.php new file mode 100644 index 0000000..4324446 --- /dev/null +++ b/app/index/view/game/detail.php @@ -0,0 +1,724 @@ +{include file="component/head" /} +{include file="component/header" /} +
    +
    +
    +
    + +
    +
    +
    +
    + 首页 + > + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    Free
    +
    +
    更新时间: +
    +
    所属分类:
    +
    程序编号:
    +
    查看:
    +
    下载:
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    免责声明:
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + +
    +
    +
    + +
    +
    +
    + +
    + + +
    + +
    + +{include file="component/footer" /} + + + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/game/index.php b/app/index/view/game/index.php new file mode 100644 index 0000000..bb59b24 --- /dev/null +++ b/app/index/view/game/index.php @@ -0,0 +1,539 @@ +{include file="component/head" /} +{include file="component/header" /} + + +
    + +
    +
    +

    游戏中心

    +

    发现精彩游戏世界

    +
    +
    + + +
    +
    + + + + +
    + +
    + {volist name="cate.subCategories" id="subCategory"} + {if $cate.id == $subCategory.id} + {if !empty($subCategory.list)} + {volist name="subCategory.list" id="game"} + +
    + {$game.title} +
    +
    +
    +
    + {$subCategory.name} + +
    +

    {$game.title}

    + +
    +
    + {/volist} + {else} +
    +
    + +
    +

    暂无文章

    +

    当前分类下没有找到相关文章

    +
    + {/if} + {/if} + {/volist} +
    + + + +
    +
    +
    +
    + + + +{include file="component/footer" /} + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/game/list.php b/app/index/view/game/list.php new file mode 100644 index 0000000..efc8155 --- /dev/null +++ b/app/index/view/game/list.php @@ -0,0 +1,76 @@ +
    +
    + +
    +
    + +
    + {volist name="categories" id="cate"} +
    {$cate.name}
    + {/volist} +
    +
    +
    + + +
    + {if $category} +
    +

    {$category.name}

    +

    {$category.desc|default=''}

    +
    + {/if} + +
    + {volist name="games" id="article"} +
    +
    +
    +
    + {$article.title} +
    +
    +
    +
    +

    + {$article.title} +

    +

    {$article.desc|default=''}

    + +
    +
    +
    +
    + {/volist} +
    + + +
    + {$games|raw} +
    +
    +
    +
    + + diff --git a/app/index/view/index/index.php b/app/index/view/index/index.php new file mode 100644 index 0000000..5c382bd --- /dev/null +++ b/app/index/view/index/index.php @@ -0,0 +1,5 @@ +{include file="component/head" /} +{include file="component/header" /} +{include file="component/main" /} +{include file="component/footer" /} +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/program/detail.php b/app/index/view/program/detail.php new file mode 100644 index 0000000..f11de70 --- /dev/null +++ b/app/index/view/program/detail.php @@ -0,0 +1,1095 @@ +{include file="component/head" /} + + + + + +{include file="component/header" /} +
    +
    +
    +
    + 首页 + > + {$cateName} +
    +
    +
    +
    +
    +
    +

    {$program.title}

    +
    + {$program.author} + + {$program.create_time|date="Y-m-d"} + {$program.views} 阅读 + + {$program.downloads} 下载 +
    +
    + + + +
    +
    +
    + {php} + // 强制统一处理:无论 images 是数组还是字符串,都转为数组 + $images = isset($program['images']) ? $program['images'] : []; + if (is_string($images)) { + $images = explode(',', $images); // 按逗号分隔字符串 + } + $images = array_filter($images); // 移除空值 + {/php} + + {volist name="images" id="image"} +
    + + {$program.title} + +
    + {/volist} +
    +
    +
    +
    +
    +
    + +
    资源简介
    + +
    + {$program.content|raw} +
    + + + +
    +
    +
    免责声明:
    +
    + +
    +
    +
    + +
    +
    + {if $prevProgram} + + 上一篇:{$prevProgram.title} + + {else} + 没有上一篇了 + {/if} +
    +
    + {if $nextProgram} + + 下一篇:{$nextProgram.title} + + {else} + 没有下一篇了 + {/if} +
    +
    + + +
    +
    +
    + +
    +
    +
    + 作者头像 +
    +
    +
    {$uploaderInfo.name}
    +
    +
    +
    +
    +
    +
    资源
    + {$uploaderInfo.resource_count} +
    +
    +
    文章
    + {$uploaderInfo.article_count} +
    +
    +
    粉丝
    + 0 +
    +
    +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    +
    + 软件编码: + {$program.number} +
    +
    + 软件大小: + {$program.size|default='未知'} +
    +
    + 更新时间: + {$program.create_time|date="Y-m-d"} +
    + +
    +
    +
    +
    +
    +
    下载
    + 点击下载 +
    +
    +
    分享码
    + {if $program.code} + {$program.code} + {else} + - + {/if} +
    +
    +
    解压密码
    + {if $program.zipcode} + {$program.zipcode} + {else} + - + {/if} +
    +
    +
    +
    +
    +
    +
    +
    + + +
    + +
    + +{include file="component/footer" /} + + + + + + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/program/index.php b/app/index/view/program/index.php new file mode 100644 index 0000000..ad2d89c --- /dev/null +++ b/app/index/view/program/index.php @@ -0,0 +1,535 @@ +{include file="component/head" /} +{include file="component/header" /} + + +
    + +
    +
    +

    资源中心

    +

    发现优质程序与工具

    +
    +
    + + +
    +
    + + + + +
    + +
    + {volist name="cate.subCategories" id="subCategory"} + {if $cate.id == $subCategory.id} + {if !empty($subCategory.list)} + {volist name="subCategory.list" id="program"} + +
    + {$program.title} +
    +
    +
    +
    + {$subCategory.name} + +
    +

    {$program.title}

    + +
    +
    + {/volist} + {else} +
    +
    + +
    +

    暂无文章

    +

    当前分类下没有找到相关文章

    +
    + {/if} + {/if} + {/volist} +
    + + + +
    +
    +
    +
    + + + +{include file="component/footer" /} + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/program/list.php b/app/index/view/program/list.php new file mode 100644 index 0000000..055a3e6 --- /dev/null +++ b/app/index/view/program/list.php @@ -0,0 +1,261 @@ +
    +
    + +
    +
    + +
    + {volist name="categories" id="cate"} +
    {$cate.name}
    + {/volist} +
    +
    +
    + + +
    + {if $category} +
    +

    {$category.name}

    +

    {$category.desc|default=''}

    +
    + {/if} + +
    + {if empty($programs)} +
    + +

    暂无程序

    +
    + {else} + {volist name="programs" id="program"} +
    +
    +
    +
    + {$program.title} +
    +
    +
    +
    +

    + {$program.title} +

    +

    {$program.desc|default=''}

    +
    +
    + {$program.views|default=0} + {$program.downloads|default=0} + {$program.create_time|date="Y-m-d"} +
    + 查看详情 +
    +
    +
    +
    +
    + {/volist} + {/if} +
    + + + {if !empty($programs)} +
    + {$programs->render()|raw} +
    + {/if} +
    +
    +
    + + + + + + // 页面加载完成后,自动触发第一个分类的点击事件 + $(document).ready(function() { + var $firstMenuItem = $('.category-item').first(); + if ($firstMenuItem.length > 0) { + $firstMenuItem.click(); + } + }); + }); + diff --git a/app/index/view/resources/detail.php b/app/index/view/resources/detail.php new file mode 100644 index 0000000..e169bf2 --- /dev/null +++ b/app/index/view/resources/detail.php @@ -0,0 +1,956 @@ +{include file="component/head" /} + + + + + +{include file="component/header" /} +
    +
    +
    +
    + +
    +
    +
    +
    + 首页 + > + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + {php} + // 兼容字符串和数组 + $images = isset($resources['images']) ? $resources['images'] : []; + if (is_string($images)) { + $images = explode(',', $images); + } + $images = array_filter($images); // 移除空值 + if (empty($images) && !empty($resources['icon'])) { + $images = [$resources['icon']]; + } + {/php} + {volist name="images" id="image"} +
    + + <?php echo $resources['title']; ?> + +
    + {/volist} +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    Free
    +
    +
    +
    + 程序编号: + +
    +
    + 所属分类: + +
    +
    +
    +
    + 更新时间: + +
    +
    + 查看: + + +
    +
    + 下载: + + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + 作者头像 +
    +
    +
    {$authorInfo.name}
    +
    +
    +
    +
    + +
    +
    资源
    + {$authorInfo.resource_count} +
    +
    +
    文章
    + {$authorInfo.article_count} +
    +
    +
    粉丝
    + + 0 + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    免责声明:
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + +
    +
    +
    + +
    +
    +
    + +
    + + +
    + +
    + +{include file="component/footer" /} + + + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/resources/index.php b/app/index/view/resources/index.php new file mode 100644 index 0000000..947349f --- /dev/null +++ b/app/index/view/resources/index.php @@ -0,0 +1,357 @@ +{include file="component/head" /} +{include file="component/header" /} + + +
    + +
    +
    +

    资源中心

    +

    网络天下资源,一站式搜索

    +
    +
    + + +
    + {volist name="categories" id="category"} +
    +
    +
    +

    {$category.parent.name}

    +
    +
    + {$category.subCategories|count} 个分类 +
    +
    + + +
    + {/volist} +
    +
    + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/resources/list.php b/app/index/view/resources/list.php new file mode 100644 index 0000000..76457a9 --- /dev/null +++ b/app/index/view/resources/list.php @@ -0,0 +1,306 @@ +{include file="component/head" /} +{include file="component/header" /} + + +
    + +
    +
    +

    {$category.name}

    +
    +
    + + +
    +
    + +
    + {volist name="data" id="resource"} +
    +
    + {$resource.title} +
    +
    +
    +
    + {$resource.number} + +
    +

    {$resource.title}

    + +
    +
    + {/volist} +
    + + +
    + {$page|raw} +
    +
    +
    +
    + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/search/index.php b/app/index/view/search/index.php new file mode 100644 index 0000000..234e711 --- /dev/null +++ b/app/index/view/search/index.php @@ -0,0 +1,132 @@ +{include file="component/head" /} +{include file="component/header" /} +
    +
    +
    + +
    +
    +
    搜索结果:{$keyword}
    + +
    +
    + + +
    + {if $items} + {volist name="items" id="item"} +
    +
    +
    + + <?php echo $item['title'] ?> + + <?php echo $item['title'] ?> + +
    +
    +

    +
    + + + 作者: + + 上传者: + + +
    +
    +
    +
    + {/volist} + {else} +
    +
    +
    + +

    暂无相关{$type == 'article' ? '文章' : '资源'}

    +
    +
    +
    + {/if} +
    + + + +
    +
    +
    +{include file="component/footer" /} +{include file="component/foot" /} + + + + \ No newline at end of file diff --git a/app/index/view/user/component/avatar.php b/app/index/view/user/component/avatar.php new file mode 100644 index 0000000..940e174 --- /dev/null +++ b/app/index/view/user/component/avatar.php @@ -0,0 +1,268 @@ +
    +

    修改头像

    + +
    +
    + 当前头像 +

    当前头像

    +
    + +
    + +

    点击或拖拽图片到此处上传

    +

    支持 jpg、png、gif 格式,大小不超过 2MB

    + +
    +
    + + +
    + + + + \ No newline at end of file diff --git a/app/index/view/user/component/basic.php b/app/index/view/user/component/basic.php new file mode 100644 index 0000000..46425d1 --- /dev/null +++ b/app/index/view/user/component/basic.php @@ -0,0 +1,427 @@ +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + {if $user.wechat} + + {else} + + {/if} +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + + +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + 当前头像 +

    当前头像

    +
    + +
    + +

    点击或拖拽图片到此处上传

    +

    支持 jpg、png、gif 格式,大小不超过 2MB

    + +
    +
    + + +
    +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/app/index/view/user/component/messages.php b/app/index/view/user/component/messages.php new file mode 100644 index 0000000..eea6bd8 --- /dev/null +++ b/app/index/view/user/component/messages.php @@ -0,0 +1,326 @@ +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/app/index/view/user/component/notifications.php b/app/index/view/user/component/notifications.php new file mode 100644 index 0000000..ca2600c --- /dev/null +++ b/app/index/view/user/component/notifications.php @@ -0,0 +1,209 @@ +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/app/index/view/user/component/security.php b/app/index/view/user/component/security.php new file mode 100644 index 0000000..4cd4ec9 --- /dev/null +++ b/app/index/view/user/component/security.php @@ -0,0 +1,100 @@ +
    +

    安全设置

    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + 未绑定 + +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/app/index/view/user/component/sidebar.php b/app/index/view/user/component/sidebar.php new file mode 100644 index 0000000..26b722d --- /dev/null +++ b/app/index/view/user/component/sidebar.php @@ -0,0 +1,157 @@ + + + + + \ No newline at end of file diff --git a/app/index/view/user/component/wallet.php b/app/index/view/user/component/wallet.php new file mode 100644 index 0000000..ec9da79 --- /dev/null +++ b/app/index/view/user/component/wallet.php @@ -0,0 +1,86 @@ +
    +
    + +
    +
    +

    您当前的积分余额:0

    +
    +
    +

    您当前的云币余额:0

    +

    云币是真钱充值后获得的平台币。

    +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/app/index/view/user/login.php b/app/index/view/user/login.php new file mode 100644 index 0000000..f1f0505 --- /dev/null +++ b/app/index/view/user/login.php @@ -0,0 +1,661 @@ + + + + + + + + + + 用户登录 + + + + +
    +
    +

    欢迎登录

    +

    请选择登录方式

    +
    +
    + +
    +
    + +
    + +
    + +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/app/index/view/user/profile.php b/app/index/view/user/profile.php new file mode 100644 index 0000000..1fd1467 --- /dev/null +++ b/app/index/view/user/profile.php @@ -0,0 +1,175 @@ +{include file="component/head" /} +{include file="component/header" /} + +
    +
    + +
    + +
    +
    + +
    + {include file="user/component/basic" /} +
    + + +
    + {include file="user/component/wallet" /} +
    + + +
    + {include file="user/component/messages" /} +
    + + +
    + {include file="user/component/notifications" /} +
    + + +
    + {include file="user/component/security" /} +
    +
    +
    +
    + + + + + +{include file="component/foot" /} \ No newline at end of file diff --git a/app/index/view/user/register.php b/app/index/view/user/register.php new file mode 100644 index 0000000..93ea72f --- /dev/null +++ b/app/index/view/user/register.php @@ -0,0 +1,240 @@ + + + + + + + + + 用户注册 + + + + +
    +
    +

    用户注册

    +

    创建您的账号

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/app/index/view/user/update_password.php b/app/index/view/user/update_password.php new file mode 100644 index 0000000..6c70958 --- /dev/null +++ b/app/index/view/user/update_password.php @@ -0,0 +1,91 @@ +{include file="component/head" /} +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/app/middleware.php b/app/middleware.php new file mode 100644 index 0000000..810aac3 --- /dev/null +++ b/app/middleware.php @@ -0,0 +1,29 @@ + Request::class, + 'think\exception\Handle' => ExceptionHandle::class, +]; diff --git a/app/service.php b/app/service.php new file mode 100644 index 0000000..e8a3540 --- /dev/null +++ b/app/service.php @@ -0,0 +1,25 @@ +redis = Cache::store('redis')->handler(); + } catch (\Exception $e) { + // Redis连接失败,使用文件缓存 + $this->redis = Cache::store('file')->handler(); + Log::error('Redis连接失败,已切换到文件缓存:' . $e->getMessage()); + } + } + + /** + * 记录访问并更新统计数据 + */ + public function recordVisit(string $page = 'home', string $userId = null): array + { + try { + $date = date('Y-m-d'); + $hour = date('H'); + $userId = $userId ?? Request::ip(); + + // 使用管道提高性能 + $pipe = $this->redis->multi(); + + // 总访问量(PV) + $pipe->incr($this->prefix.'total_visits'); + + // 每日访问量 + $pipe->incr($this->prefix.'daily:'.$date); + + // 页面统计 + $pipe->zIncrBy($this->prefix.'page_views', 1, $page); + + // UV统计(使用HyperLogLog节省内存) + $pipe->pfAdd($this->prefix.'uv:'.$date, [$userId]); + + // 时段统计 + $pipe->hIncrBy($this->prefix.'hourly:'.$date, $hour, 1); + + // 执行所有命令 + $result = $pipe->exec(); + + // 使用Redis锁控制更新频率,每5分钟更新一次数据库 + $lockKey = $this->prefix.'update_lock:'.$date; + if (!$this->redis->exists($lockKey)) { + $this->redis->setex($lockKey, 300, 1); // 5分钟锁 + $this->updateDailyStats($date, [ + 'total_visits' => $result[0], + 'daily_visits' => $result[1], + 'unique_visitors' => $this->getUniqueVisitors($date) + ]); + } + + return [ + 'total' => $result[0], + 'daily' => $result[1], + 'page' => $result[2], + 'uv' => $result[3], + 'hourly'=> $result[4] + ]; + } catch (\Exception $e) { + Log::error('访问统计失败:' . $e->getMessage()); + return [ + 'total' => 0, + 'daily' => 0, + 'page' => 0, + 'uv' => 0, + 'hourly'=> 0 + ]; + } + } + + /** + * 更新每日统计数据 + */ + protected function updateDailyStats(string $date, array $stats) + { + try { + // 获取其他统计数据 + $otherStats = [ + 'total_users' => Db::name('users')->count(), + 'new_users' => Db::name('users')->whereDay('create_time', $date)->count(), + 'total_articles' => Db::name('articles')->where('delete_time', null)->count(), + 'daily_articles' => Db::name('articles')->whereDay('create_time', $date)->count(), + 'article_views' => Db::name('articles')->whereDay('update_time', $date)->sum('views'), + 'total_resources' => Db::name('resources')->where('delete_time', null)->count(), + 'daily_resources' => Db::name('resources')->whereDay('create_time', $date)->count(), + 'resource_downloads' => Db::name('resources')->whereDay('update_time', $date)->sum('downloads') + ]; + + // 只在调试模式下记录日志 + if (config('app.debug')) { + Log::info('统计数据:' . json_encode($otherStats, JSON_UNESCAPED_UNICODE)); + } + + // 合并统计数据 + $stats = array_merge($stats, $otherStats); + + // 检查记录是否存在 + $exists = Db::name('daily_stats')->where('date', $date)->find(); + + if ($exists) { + // 更新已存在的记录 + Db::name('daily_stats')->where('date', $date)->update([ + 'total_users' => $stats['total_users'], + 'new_users' => $stats['new_users'], + 'total_visits' => $stats['total_visits'], + 'daily_visits' => $stats['daily_visits'], + 'unique_visitors' => $stats['unique_visitors'], + 'total_articles' => $stats['total_articles'], + 'daily_articles' => $stats['daily_articles'], + 'article_views' => $stats['article_views'], + 'total_resources' => $stats['total_resources'], + 'daily_resources' => $stats['daily_resources'], + 'resource_downloads' => $stats['resource_downloads'] + ]); + } else { + // 插入新记录 + Db::name('daily_stats')->insert([ + 'date' => $date, + 'total_users' => $stats['total_users'], + 'new_users' => $stats['new_users'], + 'total_visits' => $stats['total_visits'], + 'daily_visits' => $stats['daily_visits'], + 'unique_visitors' => $stats['unique_visitors'], + 'total_articles' => $stats['total_articles'], + 'daily_articles' => $stats['daily_articles'], + 'article_views' => $stats['article_views'], + 'total_resources' => $stats['total_resources'], + 'daily_resources' => $stats['daily_resources'], + 'resource_downloads' => $stats['resource_downloads'] + ]); + } + } catch (\Exception $e) { + Log::error('更新统计数据失败:' . $e->getMessage()); + } + } + + /** + * 获取总访问量 + */ + public function getTotalVisits(): int + { + try { + return (int)$this->redis->get($this->prefix.'total_visits'); + } catch (\Exception $e) { + Log::error('获取总访问量失败:' . $e->getMessage()); + return 0; + } + } + + /** + * 获取当日访问量 + */ + public function getDailyVisits(string $date = null): int + { + try { + $date = $date ?? date('Y-m-d'); + return (int)$this->redis->get($this->prefix.'daily:'.$date); + } catch (\Exception $e) { + Log::error('获取当日访问量失败:' . $e->getMessage()); + return 0; + } + } + + /** + * 获取独立访客数(UV) + */ + public function getUniqueVisitors(string $date = null): int + { + try { + $date = $date ?? date('Y-m-d'); + return $this->redis->pfCount($this->prefix.'uv:'.$date); + } catch (\Exception $e) { + Log::error('获取独立访客数失败:' . $e->getMessage()); + return 0; + } + } +} + \ No newline at end of file diff --git a/app/tpl/think_exception.tpl b/app/tpl/think_exception.tpl new file mode 100644 index 0000000..93196e2 --- /dev/null +++ b/app/tpl/think_exception.tpl @@ -0,0 +1,48 @@ + + + + 跳转提示 + + + + +
    + +

    + +

    + +

    +
    + + \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..aca592c --- /dev/null +++ b/composer.json @@ -0,0 +1,60 @@ +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "https://www.thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=7.2.5", + "topthink/framework": "^6.1.0", + "topthink/think-orm": "^2.0", + "topthink/think-filesystem": "^1.0", + "topthink/think-multi-app": "^1.1", + "topthink/think-view": "^1.0", + "topthink/think-captcha": "^3.0", + "phpoffice/phpspreadsheet": "^1.25", + "phpmailer/phpmailer": "^6.9", + "overtrue/wechat": "^5.36", + "endroid/qr-code": "^4.6", + "guzzlehttp/guzzle": "^7.9" + }, + "require-dev": { + "symfony/var-dumper": "^4.2", + "topthink/think-trace": "^1.0" + }, + "autoload": { + "psr-4": { + "app\\": "app" + }, + "psr-0": { + "": "extend/" + } + }, + "config": { + "preferred-install": "dist", + "allow-plugins": { + "easywechat-composer/easywechat-composer": true + } + }, + "scripts": { + "post-autoload-dump": [ + "@php think service:discover", + "@php think vendor:publish" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..6cd1e56 --- /dev/null +++ b/composer.lock @@ -0,0 +1,4002 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4fd98121a1c29d8e7e638b3ae8591ac0", + "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + }, + "time": "2022-12-07T17:46:57+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "dasprid/enum", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" + }, + "time": "2023-08-25T16:18:39+00:00" + }, + { + "name": "easywechat-composer/easywechat-composer", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/mingyoung/easywechat-composer.git", + "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd", + "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=7.0" + }, + "require-dev": { + "composer/composer": "^1.0 || ^2.0", + "phpunit/phpunit": "^6.5 || ^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "EasyWeChatComposer\\Plugin" + }, + "autoload": { + "psr-4": { + "EasyWeChatComposer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "张铭阳", + "email": "mingyoungcheung@gmail.com" + } + ], + "description": "The composer plugin for EasyWeChat", + "support": { + "issues": "https://github.com/mingyoung/easywechat-composer/issues", + "source": "https://github.com/mingyoung/easywechat-composer/tree/1.4.1" + }, + "time": "2021-07-05T04:03:22+00:00" + }, + { + "name": "endroid/qr-code", + "version": "4.6.1", + "source": { + "type": "git", + "url": "https://github.com/endroid/qr-code.git", + "reference": "a75c913b0e4d6ad275e49a2c1de1cacffc6c2184" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/a75c913b0e4d6ad275e49a2c1de1cacffc6c2184", + "reference": "a75c913b0e4d6ad275e49a2c1de1cacffc6c2184", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "bacon/bacon-qr-code": "^2.0.5", + "php": "^7.4||^8.0" + }, + "require-dev": { + "endroid/quality": "dev-master", + "ext-gd": "*", + "khanamiryan/qrcode-detector-decoder": "^1.0.4", + "setasign/fpdf": "^1.8.2" + }, + "suggest": { + "ext-gd": "Enables you to write PNG images", + "khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator", + "roave/security-advisories": "Makes sure package versions with known security issues are not installed", + "setasign/fpdf": "Enables you to use the PDF writer" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\QrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Endroid QR Code", + "homepage": "https://github.com/endroid/qr-code", + "keywords": [ + "code", + "endroid", + "php", + "qr", + "qrcode" + ], + "support": { + "issues": "https://github.com/endroid/qr-code/issues", + "source": "https://github.com/endroid/qr-code/tree/4.6.1" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2022-10-26T08:48:17+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.18.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "cb56001e54359df7ae76dc522d08845dc741621b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b", + "reference": "cb56001e54359df7ae76dc522d08845dc741621b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0" + }, + "time": "2024-11-01T03:51:45+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2024-07-24T11:22:20+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-03-27T13:27:01+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-03-27T12:30:47+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.10", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.10" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2022-10-04T09:16:37+00:00" + }, + { + "name": "league/flysystem-cached-adapter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-cached-adapter.git", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ], + "description": "An adapter decorator to enable meta-data caching.", + "support": { + "issues": "https://github.com/thephpleague/flysystem-cached-adapter/issues", + "source": "https://github.com/thephpleague/flysystem-cached-adapter/tree/master" + }, + "time": "2020-07-25T15:56:04+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-01-28T23:22:08+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "2.2.6", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f", + "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": "^7.4 || ^8.0", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.9", + "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.4", + "phpunit/phpunit": "^8.5.8 || ^9.4.2", + "vimeo/psalm": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.2.6" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2022-11-25T18:57:19+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5cf826f2991858b54d5c3809bee745560a1042a7", + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.38 || ^9.6.19", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2024-11-12T12:43:37+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2022-08-04T09:53:51+00:00" + }, + { + "name": "overtrue/socialite", + "version": "3.5.4", + "source": { + "type": "git", + "url": "https://github.com/overtrue/socialite.git", + "reference": "6bd4f0230bcaec5ccfd64a10581a9063233b5a48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/socialite/zipball/6bd4f0230bcaec5ccfd64a10581a9063233b5a48", + "reference": "6bd4f0230bcaec5ccfd64a10581a9063233b5a48", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "~6.0|~7.0", + "php": ">=7.4", + "symfony/http-foundation": "^5.0", + "symfony/psr-http-message-bridge": "^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "mockery/mockery": "~1.2", + "phpunit/phpunit": "~9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "A collection of OAuth 2 packages.", + "keywords": [ + "Feishu", + "login", + "oauth", + "qcloud", + "qq", + "social", + "wechat", + "weibo" + ], + "support": { + "issues": "https://github.com/overtrue/socialite/issues", + "source": "https://github.com/overtrue/socialite/tree/3.5.4" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2022-11-19T13:32:42+00:00" + }, + { + "name": "overtrue/wechat", + "version": "5.36.0", + "source": { + "type": "git", + "url": "https://github.com/w7corp/easywechat.git", + "reference": "3e3af7e1195e8ad25dae196bdb6420a24b382f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/w7corp/easywechat/zipball/3e3af7e1195e8ad25dae196bdb6420a24b382f1d", + "reference": "3e3af7e1195e8ad25dae196bdb6420a24b382f1d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "easywechat-composer/easywechat-composer": "^1.1", + "ext-fileinfo": "*", + "ext-libxml": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.2 || ^7.0", + "monolog/monolog": "^1.22 || ^2.0 || ^3.0", + "overtrue/socialite": "^3.2 || ^4.0", + "php": ">=7.4", + "pimple/pimple": "^3.0", + "psr/simple-cache": "^1.0||^2.0||^3.0", + "symfony/cache": "^3.3 || ^4.3 || ^5.0 || ^6.0", + "symfony/event-dispatcher": "^4.3 || ^5.0 || ^6.0", + "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "brainmaestro/composer-git-hooks": "^2.7", + "dms/phpunit-arraysubset-asserts": "^0.2.0", + "friendsofphp/php-cs-fixer": "^3.5.0", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2.3", + "phpstan/phpstan": "^0.12.0", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "hooks": { + "pre-push": [ + "composer test", + "composer fix-style" + ], + "pre-commit": [ + "composer test", + "composer fix-style" + ] + } + }, + "autoload": { + "files": [ + "src/Kernel/Support/Helpers.php", + "src/Kernel/Helpers.php" + ], + "psr-4": { + "EasyWeChat\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "微信SDK", + "keywords": [ + "easywechat", + "sdk", + "wechat", + "weixin", + "weixin-sdk" + ], + "support": { + "issues": "https://github.com/w7corp/easywechat/issues", + "source": "https://github.com/w7corp/easywechat/tree/5.36.0" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "abandoned": "w7corp/easywechat", + "time": "2024-12-25T08:00:38+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.9.3", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2024-11-24T18:04:13+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.29.10", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "c80041b1628c4f18030407134fe88303661d4e4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/c80041b1628c4f18030407134fe88303661d4e4e", + "reference": "c80041b1628c4f18030407134fe88303661d4e4e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.10" + }, + "time": "2025-02-08T02:56:14+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1 || ^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "https://pimple.symfony.com", + "keywords": [ + "container", + "dependency injection" + ], + "support": { + "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" + }, + "time": "2021-10-28T11:13:42+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/cache", + "version": "v5.4.46", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/0fe08ee32cec2748fbfea10c52d3ee02049e0f6b", + "reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0|2.0", + "psr/simple-cache-implementation": "1.0|2.0", + "symfony/cache-implementation": "1.0|2.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "^1.6|^2.0", + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v5.4.46" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-04T11:43:55+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/517c3a3619dadfa6952c4651767fcadffb4df65e", + "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v5.4.48", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "3f38b8af283b830e1363acd79e5bc3412d055341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3f38b8af283b830e1363acd79e5bc3412d055341", + "reference": "3f38b8af283b830e1363acd79e5bc3412d055341", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "^1.0|^2.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.4.48" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T18:58:02+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/http-foundation": "^5.4 || ^6.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "psr/log": "^1.1 || ^2 || ^3", + "symfony/browser-kit": "^5.4 || ^6.0", + "symfony/config": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/http-kernel": "^5.4 || ^6.0", + "symfony/phpunit-bridge": "^6.2" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/symfony/psr-http-message-bridge/issues", + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-26T11:53:26+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "862700068db0ddfd8c5b850671e029a90246ec75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/862700068db0ddfd8c5b850671e029a90246ec75", + "reference": "862700068db0ddfd8c5b850671e029a90246ec75", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "topthink/framework", + "version": "v6.1.5", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "57d1950a1844ef8d3098ea290032aeb92e2e32c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/57d1950a1844ef8d3098ea290032aeb92e2e32c3", + "reference": "57d1950a1844ef8d3098ea290032aeb92e2e32c3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=7.2.5", + "psr/container": "~1.0", + "psr/http-message": "^1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1.1", + "topthink/think-orm": "^2.0|^3.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^2.1.0", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP Framework.", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ], + "support": { + "issues": "https://github.com/top-think/framework/issues", + "source": "https://github.com/top-think/framework/tree/v6.1.5" + }, + "time": "2024-04-16T02:01:19+00:00" + }, + { + "name": "topthink/think-captcha", + "version": "v3.0.11", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "4f24f560a31011329e3d144732e5370d7676b3fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/4f24f560a31011329e3d144732e5370d7676b3fb", + "reference": "4f24f560a31011329e3d144732e5370d7676b3fb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/framework": "^6.0|^8.0" + }, + "type": "library", + "extra": { + "think": { + "config": { + "captcha": "src/config.php" + }, + "services": [ + "think\\captcha\\CaptchaService" + ] + } + }, + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\captcha\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "captcha package for thinkphp", + "support": { + "issues": "https://github.com/top-think/think-captcha/issues", + "source": "https://github.com/top-think/think-captcha/tree/v3.0.11" + }, + "time": "2024-11-22T12:59:35+00:00" + }, + { + "name": "topthink/think-filesystem", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-filesystem.git", + "reference": "29f19f140a9267c717fecd7ccb22c84c2d72382e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-filesystem/zipball/29f19f140a9267c717fecd7ccb22c84c2d72382e", + "reference": "29f19f140a9267c717fecd7ccb22c84c2d72382e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "^1.1.4", + "league/flysystem-cached-adapter": "^1.0", + "php": ">=7.2.5", + "topthink/framework": "^6.1|^8.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6.1 Filesystem Package", + "support": { + "issues": "https://github.com/top-think/think-filesystem/issues", + "source": "https://github.com/top-think/think-filesystem/tree/v1.0.3" + }, + "time": "2023-02-08T01:25:15+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.11", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/1d6ada9b9f3130046bf6922fe1bd159c8d88a33c", + "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.11" + }, + "time": "2025-04-07T06:55:59+00:00" + }, + { + "name": "topthink/think-multi-app", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-multi-app.git", + "reference": "f93c604d5cfac2b613756273224ee2f88e457b88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-multi-app/zipball/f93c604d5cfac2b613756273224ee2f88e457b88", + "reference": "f93c604d5cfac2b613756273224ee2f88e457b88", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0|^8.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\app\\Service" + ] + } + }, + "autoload": { + "psr-4": { + "think\\app\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp multi app support", + "support": { + "issues": "https://github.com/top-think/think-multi-app/issues", + "source": "https://github.com/top-think/think-multi-app/tree/v1.1.1" + }, + "time": "2024-11-25T08:52:44+00:00" + }, + { + "name": "topthink/think-orm", + "version": "v2.0.62", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "e53bfea572a133039ad687077120de5521af617f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/e53bfea572a133039ad687077120de5521af617f", + "reference": "e53bfea572a133039ad687077120de5521af617f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-pdo": "*", + "php": ">=7.1.0", + "psr/log": "^1.0|^2.0", + "psr/simple-cache": "^1.0|^2.0", + "topthink/think-helper": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8|^9.5" + }, + "type": "library", + "autoload": { + "files": [ + "stubs/load_stubs.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think orm", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/top-think/think-orm/issues", + "source": "https://github.com/top-think/think-orm/tree/v2.0.62" + }, + "time": "2024-09-22T06:17:47+00:00" + }, + { + "name": "topthink/think-template", + "version": "v2.0.10", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-template.git", + "reference": "2b28c9f787c94f6c22312c9fe97dd3d926c03e1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-template/zipball/2b28c9f787c94f6c22312c9fe97dd3d926c03e1c", + "reference": "2b28c9f787c94f6c22312c9fe97dd3d926c03e1c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the php template engine", + "support": { + "issues": "https://github.com/top-think/think-template/issues", + "source": "https://github.com/top-think/think-template/tree/v2.0.10" + }, + "time": "2024-08-12T05:48:57+00:00" + }, + { + "name": "topthink/think-view", + "version": "v1.0.14", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-view.git", + "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-view/zipball/edce0ae2c9551ab65f9e94a222604b0dead3576d", + "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp template driver", + "support": { + "issues": "https://github.com/top-think/think-view/issues", + "source": "https://github.com/top-think/think-view/tree/v1.0.14" + }, + "time": "2019-11-06T11:40:13+00:00" + } + ], + "packages-dev": [ + { + "name": "symfony/polyfill-php72", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce", + "reference": "fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "type": "metapackage", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "1069c7a3fca74578022fab6f81643248d02f8e63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1069c7a3fca74578022fab6f81643248d02f8e63", + "reference": "1069c7a3fca74578022fab6f81643248d02f8e63", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.43|^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v4.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-03T15:15:11+00:00" + }, + { + "name": "topthink/think-trace", + "version": "v1.6", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-trace.git", + "reference": "136cd5d97e8bdb780e4b5c1637c588ed7ca3e142" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-trace/zipball/136cd5d97e8bdb780e4b5c1637c588ed7ca3e142", + "reference": "136cd5d97e8bdb780e4b5c1637c588ed7ca3e142", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0|^8.0" + }, + "type": "library", + "extra": { + "think": { + "config": { + "trace": "src/config.php" + }, + "services": [ + "think\\trace\\Service" + ] + } + }, + "autoload": { + "psr-4": { + "think\\trace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp debug trace", + "support": { + "issues": "https://github.com/top-think/think-trace/issues", + "source": "https://github.com/top-think/think-trace/tree/v1.6" + }, + "time": "2023-02-07T08:36:32+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.2.5" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..b05a882 --- /dev/null +++ b/config/app.php @@ -0,0 +1,51 @@ + env('app.host', ''), + // 应用的命名空间 + 'app_namespace' => '', + // 是否启用路由 + 'with_route' => true, + // 默认应用 + 'default_app' => 'index', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + + // 应用映射(自动多应用模式有效) + 'app_map' => [], + // 域名绑定(自动多应用模式有效) + 'domain_bind' => [], + // 禁止URL访问的应用列表(自动多应用模式有效) + 'deny_app_list' => [], + + // 异常页面的模板文件 + 'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', + 'dispatch_success_tmpl' => app()->getRootPath() . 'app/tpl/think_exception.tpl', + 'dispatch_error_tmpl' => app()->getRootPath() . 'app/tpl/think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => true, +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..43fdf11 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,64 @@ + env('cache.driver', 'file'), + + // 缓存连接方式配置 + 'stores' => [ + 'file' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => runtime_path() . 'cache', + // 缓存前缀 + 'prefix' => 'cache_', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + // 缓存标签前缀 + 'tag_prefix' => 'tag:', + // 序列化机制 例如 ['serialize', 'unserialize'] + 'serialize' => [], + ], + // 更多的缓存连接 + 'redis' => [ + // 驱动方式 + 'type' => 'redis', + // 服务器地址 + 'host' => env('redis.host', '127.0.0.1'), + // 端口 + 'port' => env('redis.port', 6379), + // 密码 + 'password' => env('redis.password', '920103'), + // 数据库索引 + 'select' => env('redis.select', 0), + // 连接超时时间 + 'timeout' => 0, + // 是否持久化连接 + 'persistent' => false, + // 缓存前缀 + 'prefix' => 'yz_:', + ], + ], +]; diff --git a/config/captcha.php b/config/captcha.php new file mode 100644 index 0000000..053cfc9 --- /dev/null +++ b/config/captcha.php @@ -0,0 +1,56 @@ + 4, + // 验证码字符集合 + 'codeSet' => '1234567890', + // 验证码过期时间 + 'expire' => 1800, + // 是否使用中文验证码 + 'useZh' => false, + // 是否使用算术验证码 + 'math' => false, + // 是否使用背景图 + 'useImgBg' => false, + //验证码字符大小 + 'fontSize' => 25, + // 是否使用混淆曲线 + 'useCurve' => false, + //是否添加杂点 + 'useNoise' => false, + // 验证码字体 不设置则随机 + 'fontttf' => '', + //背景颜色 + 'bg' => [243, 251, 254], + // 验证码图片高度 + 'imageH' => 0, + // 验证码图片宽度 + 'imageW' => 0, + + // 添加额外的验证码设置 + // verify => [ + // 'length'=>4, + // ... + //], +]; diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..289af27 --- /dev/null +++ b/config/console.php @@ -0,0 +1,27 @@ + [ + + ], +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 0000000..54091bd --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,39 @@ + '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, + // samesite 设置,支持 'strict' 'lax' + 'samesite' => '', +]; diff --git a/config/filesystem.php b/config/filesystem.php new file mode 100644 index 0000000..2c1879f --- /dev/null +++ b/config/filesystem.php @@ -0,0 +1,41 @@ + env('filesystem.driver', 'local'), + // 磁盘列表 + 'disks' => [ + 'local' => [ + 'type' => 'local', + 'root' => app()->getRuntimePath() . 'storage', + ], + 'public' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage', + // 可见性 + 'visibility' => 'public', + ], + // 更多的磁盘配置信息 + ], +]; diff --git a/config/lang.php b/config/lang.php new file mode 100644 index 0000000..3cd601a --- /dev/null +++ b/config/lang.php @@ -0,0 +1,44 @@ + env('lang.default_lang', 'zh-cn'), + // 允许的语言列表 + 'allow_lang_list' => [], + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // 是否使用Cookie记录 + 'use_cookie' => true, + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 多语言header变量 + 'header_var' => 'think-lang', + // 扩展语言包 + 'extend_list' => [], + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, +]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..d0995d2 --- /dev/null +++ b/config/log.php @@ -0,0 +1,67 @@ + 'file', + // 日志记录级别 + 'level' => ['error', 'warning', 'info', 'debug'], + // 日志类型记录的通道 + 'type_channel' => [ + 'error' => 'file', + 'warning' => 'file', + 'info' => 'file', + 'debug' => 'file', + ], + // 关闭全局日志写入 + 'close' => false, + // 全局日志处理 支持闭包 + 'processor' => null, + + // 日志通道列表 + 'channels' => [ + 'file' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => runtime_path() . 'log', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => ['error', 'warning', 'info', 'debug'], + // 最大日志文件数量 + 'max_files' => 30, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => true, + ], + // 其它日志通道配置 + ], + +]; diff --git a/config/middleware.php b/config/middleware.php new file mode 100644 index 0000000..24342da --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,30 @@ + [ + 'auth' => \app\middleware\Auth::class, + ], + // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行 + 'priority' => [], + // 全局中间件 + 'global' => [ + \app\middleware\Auth::class, + ], +]; diff --git a/config/route.php b/config/route.php new file mode 100644 index 0000000..beeae6c --- /dev/null +++ b/config/route.php @@ -0,0 +1,101 @@ + +// +---------------------------------------------------------------------- + +return [ + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // URL普通方式参数 用于自动生成 + 'url_common_param' => true, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由延迟解析 + 'lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => true, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 使用注解路由 + 'route_annotation' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // URL中的控制器访问命名空间(自动多应用模式有效) + 'controller_namespace' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 是否开启路由缓存 + 'route_check_cache' => false, + // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5 + 'route_check_cache_key' => '', + // 路由缓存类型及参数 + 'route_cache_option' => [], + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => app()->getThinkPath() . 'tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => app()->getThinkPath() . 'tpl/dispatch_jump.tpl', + // 异常页面的模板文件 + 'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + // 是否开启URL后缀 + 'url_html_suffix' => false, + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => true, + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..f05c127 --- /dev/null +++ b/config/session.php @@ -0,0 +1,36 @@ + 'PHPSESSID', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // 驱动方式 支持file cache + 'type' => 'file', + // 存储连接标识 当type使用cache的时候有效 + 'store' => null, + // 过期时间 + 'expire' => 60*60*24*7, + // 前缀 + 'prefix' => '', +]; diff --git a/config/site.php b/config/site.php new file mode 100644 index 0000000..7e1237a --- /dev/null +++ b/config/site.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- + +return [ + // 站点名称 + 'name' => '云泽科技', + // 联系电话 + 'phone' => '188-1152-3967', + // 联系邮箱 + 'email' => '357099073@qq.com', + // 微信二维码 + 'wechat_qrcode' => '/static/images/wechat_qrcode.jpg', + // 网站Logo + 'logo' => '/static/images/logo.png', + // 网站Logo(深色) + 'logo1' => '/static/images/logo1.png', + // 后台路由前缀 + 'admin_route' => '/admin/' +]; \ No newline at end of file diff --git a/config/trace.php b/config/trace.php new file mode 100644 index 0000000..81b8ceb --- /dev/null +++ b/config/trace.php @@ -0,0 +1,27 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..9a33cd5 --- /dev/null +++ b/config/view.php @@ -0,0 +1,53 @@ + 'Think', + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 模板目录名 + 'view_dir_name' => 'view', + // 模板后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + //定义JS CSS路径 + 'tpl_replace_string' => [ + '__STATIC__' => '/static', + '__ADMIN__' => '/static/admin', + '__IMAGES__' => '/static/images', + '__CSS__' => '/static/css', + '__LAYUI__' => '/static/layui', + '__JS__' => '/static/js', + ], + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => false, +]; diff --git a/database/default.sql b/database/default.sql new file mode 100644 index 0000000..159160a --- /dev/null +++ b/database/default.sql @@ -0,0 +1,604 @@ +/* + Navicat Premium Dump SQL + + Source Server : 本地数据库 + Source Server Type : MySQL + Source Server Version : 50726 (5.7.26) + Source Host : localhost:3306 + Source Schema : ruankao + + Target Server Type : MySQL + Target Server Version : 50726 (5.7.26) + File Encoding : 65001 + + Date: 14/07/2025 14:46:38 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for yz_admin_config +-- ---------------------------- +DROP TABLE IF EXISTS `yz_admin_config`; +CREATE TABLE `yz_admin_config` ( + `config_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '系统设置ID', + `config_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '功能名,代码参照使用', + `config_info` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '中文名', + `config_type` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型 1文本 2图片 3富文本 4开关', + `config_value` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '值', + `config_desc` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '描述', + `config_sort` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序', + `config_status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态 1启用 0禁用', + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '配置表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_admin_config +-- ---------------------------- +INSERT INTO `yz_admin_config` VALUES (1, 'web_title', '站点名称', 1, '测试网', '{$config[\'web_title\']}', 0, 1); +INSERT INTO `yz_admin_config` VALUES (2, 'web_phone', '站点电话', 1, '19895983967', '{$config[\'web_phone\']}', 0, 1); +INSERT INTO `yz_admin_config` VALUES (3, 'web_mail', '站点邮箱', 1, '357099073@qq.com', '{$config[\'web_mail\']}', 0, 1); +INSERT INTO `yz_admin_config` VALUES (4, 'web_wechat', '站点微信', 1, '/storage/uploads/20250516/158244a632e4106d262635ee59bd1820.jpg', '{$config[\'web_wechat\']}', 0, 1); +INSERT INTO `yz_admin_config` VALUES (5, 'logo', 'logo', 2, '/storage/uploads/20250516/c726b7e5251bb0b46f2749858fa04c0f.png', '{$config[\'logo\']}', 0, 1); +INSERT INTO `yz_admin_config` VALUES (6, 'logo1', 'logo1', 2, '/storage/uploads/20250516/2773adefac8848f009c4557183ed2fc1.png', '{$config[\'logo1\']}', 0, 1); +INSERT INTO `yz_admin_config` VALUES (7, 'admin_icp', '网站备案号', 1, '苏ICP备2023006641号', '{$config[\'admin_icp\']}', 0, 1); +INSERT INTO `yz_admin_config` VALUES (8, 'admin_route', '项目路径', 1, '/index.php/admin/', '后台项目路径', 0, 1); +INSERT INTO `yz_admin_config` VALUES (9, 'admin_domain', '本地域名', 1, 'www.yunzer.cn', '{$config[\'admin_domain\']}', 0, 1); +INSERT INTO `yz_admin_config` VALUES (10, 'admin_page', '每页数量', 1, '10', '列表数据,每页数量', 0, 1); +INSERT INTO `yz_admin_config` VALUES (11, 'admin_info', '系统介绍', 3, '', '{$config[\'admin_info\']}', 0, 1); + +-- ---------------------------- +-- Table structure for yz_admin_sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `yz_admin_sys_menu`; +CREATE TABLE `yz_admin_sys_menu` ( + `smid` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID', + `label` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '菜单名称', + `type` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '菜单类型,0:顶级菜单,1:功能模块,2:超链接,3:隐藏按钮', + `src` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '链接源', + `parent_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '上级菜单ID', + `icon_class` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图标class', + `sort` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '排序值,升序', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态 1开启 0关闭', + PRIMARY KEY (`smid`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 8015 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '左侧菜单' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_admin_sys_menu +-- ---------------------------- +INSERT INTO `yz_admin_sys_menu` VALUES (2, '基础配置', 0, '', 0, 'layui-icon-util', '99999', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (3, '站点配置', 1, 'yunzer/configvalue', 5000, 'layui-icon-set-fill', '100', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (4, '站点管理', 1, 'yunzer/configlist', 5000, 'layui-icon-slider', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5, '配置添加', 3, 'yunzer/configadd', 3, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (6, '配置修改', 3, 'yunzer/configedit', 3, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (7, '配置删除', 3, 'yunzer/configdel', 3, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (10, '菜单管理', 1, 'yunzer/menuinfo', 2, 'layui-icon-template', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (11, '菜单添加', 3, 'yunzer/menuadd', 10, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (12, '菜单修改', 3, 'yunzer/menuedit', 10, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (13, '菜单删除', 3, 'yunzer/menudel', 10, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (14, '按钮列表', 3, 'yunzer/buttoninfo', 10, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (15, '按钮添加', 3, 'yunzer/buttonadd', 10, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (16, '按钮修改', 3, 'yunzer/buttonedit', 10, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (17, '按钮删除', 3, 'yunzer/buttondel', 10, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5000, '系统配置', 0, '', 0, 'layui-icon-set', '99996', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5002, '角色管理', 1, 'yunzeradmin/groupinfo', 2, 'layui-icon-group', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5003, '用户管理', 1, 'yunzeradmin/userinfo', 2, 'layui-icon-user', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5004, '个人中心', 1, 'yunzeradmin/admininfo', 2, 'layui-icon-friends', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5005, '部门添加', 3, 'yunzeradmin/groupadd', 5000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5006, '部门修改', 3, 'yunzeradmin/groupedit', 5000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5007, '部门删除', 3, 'yunzeradmin/groupdel', 5000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5008, '管理员添加', 3, 'yunzeradmin/useradd', 5000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5009, '管理员修改', 3, 'yunzeradmin/useredit', 5000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (5010, '管理员删除', 3, 'yunzeradmin/userdel', 5000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (7000, '代码示例', 0, '', 0, 'layui-icon-app', '99995', 0); +INSERT INTO `yz_admin_sys_menu` VALUES (7001, '图标', 1, 'yunzertest/icon_list', 7000, 'layui-icon-theme', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (7002, '演示列表-方法渲染', 1, 'yunzertest/test_list', 7000, 'layui-icon-list', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (7003, '演示列表-静态表格', 1, 'yunzertest/test_static_list', 7000, 'layui-icon-list', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (7004, '演示添加-方法渲染', 3, 'yunzertest/test_add', 7000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (7005, '演示修改-方法渲染', 3, 'yunzertest/test_edit', 7000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (7006, '演示添加-静态表格', 3, 'yunzertest/test_static_add', 7000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (7007, '演示修改-静态表格', 3, 'yunzertest/test_static_edit', 7000, NULL, '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8000, '友情链接', 0, NULL, 0, 'layui-icon-link', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8001, '云泽网', 2, 'https://www.yunzer.cn', 8000, 'layui-icon-link', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8002, '云泽建站', 2, 'https://www.yunzer.com.cn', 8000, 'layui-icon-link', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8003, '文章管理', 0, '', 0, 'layui-icon-light', '999998', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8004, '文章列表', 1, 'articles/articlelist', 8003, '', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8005, '文章分类', 1, 'articles/articlecate', 8003, '', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8006, 'Banner管理', 1, 'yunzeradmin/banner', 2, 'layui-icon-picture', '1', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8007, '资源管理', 0, '', 0, 'layui-icon-senior', '999997', 0); +INSERT INTO `yz_admin_sys_menu` VALUES (8008, '资源列表', 1, 'resources/lists', 8007, '', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8009, '资源分类', 1, 'resources/cate', 8007, '', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8010, '日志管理', 0, '', 0, 'layui-icon-about', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8011, '登陆日志', 1, 'log/login', 8010, '', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8012, '操作日志', 1, 'log/operation', 8010, '', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8013, '邮箱配置', 1, 'yunzer/mailconfig', 5000, 'layui-icon-menu-fill', '0', 1); +INSERT INTO `yz_admin_sys_menu` VALUES (8014, '内容推送', 1, 'yunzeradmin/contentpush', 2, 'layui-icon-export', '0', 1); + +-- ---------------------------- +-- Table structure for yz_admin_user +-- ---------------------------- +DROP TABLE IF EXISTS `yz_admin_user`; +CREATE TABLE `yz_admin_user` ( + `uid` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `account` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号邮箱', + `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码', + `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名', + `phone` varchar(18) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号', + `qq` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'QQ号', + `sex` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '性别 0未知 1男 2女', + `group_id` int(10) UNSIGNED NOT NULL COMMENT '分组ID', + `login_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '登陆次数', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态 1开启 0禁用', + `api_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'API密钥', + `api_key_expire` datetime NULL DEFAULT NULL COMMENT 'API密钥过期时间', + `api_key_status` tinyint(1) NULL DEFAULT 1 COMMENT 'API密钥状态(1有效 0禁用)', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`uid`) USING BTREE, + UNIQUE INDEX `one_account`(`account`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_admin_user +-- ---------------------------- +INSERT INTO `yz_admin_user` VALUES (2, '357099073@qq.com', '412b7ee19961676b97a89cb04a22d7d3', '扫地僧', '19895983967', '357099073', 1, 1, 331, 1, NULL, NULL, 1, 1564124524, 1752475244, NULL); + +-- ---------------------------- +-- Table structure for yz_admin_user_group +-- ---------------------------- +DROP TABLE IF EXISTS `yz_admin_user_group`; +CREATE TABLE `yz_admin_user_group` ( + `group_id` int(11) NOT NULL AUTO_INCREMENT, + `group_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '部门名称', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否生效', + `rights` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '角色权限', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`group_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '组管理' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_admin_user_group +-- ---------------------------- +INSERT INTO `yz_admin_user_group` VALUES (1, '管理员', 1, '[8003,8005,8004,8007,8008,8009,2,8006,8014,10,5002,5003,5004,5000,3,8013,4,5008,5009,5005,5010,5007,5006,7000,7003,7002,7001,7007,7006,7005,7004,8000,8001,8002,8010,8011,8012]', 1564124524, 0, 0); + +-- ---------------------------- +-- Table structure for yz_articles +-- ---------------------------- +DROP TABLE IF EXISTS `yz_articles`; +CREATE TABLE `yz_articles` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章名称', + `cate` int(5) NOT NULL DEFAULT 0 COMMENT '文章分类', + `image` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '封面图片', + `desc` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `author` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '作者', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章内容', + `publishdate` int(11) NULL DEFAULT NULL COMMENT '发布时间', + `sort` int(11) NULL DEFAULT NULL COMMENT '排序', + `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态:0草稿1待审核2已发布3隐藏', + `views` int(11) NOT NULL DEFAULT 0 COMMENT '浏览量', + `likes` int(11) NOT NULL DEFAULT 0 COMMENT '点赞量', + `is_trans` enum('是','否') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '否' COMMENT '是否转载', + `transurl` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '转载地址', + `create_time` int(11) NOT NULL COMMENT '创建时间', + `update_time` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_articles +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_articles_category +-- ---------------------------- +DROP TABLE IF EXISTS `yz_articles_category`; +CREATE TABLE `yz_articles_category` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `cid` int(11) NOT NULL DEFAULT 0 COMMENT '分类id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分类名称', + `image` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '默认图片', + `desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `sort` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '排序', + `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态:0关闭1启用', + `create_time` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `update_time` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章分类表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_articles_category +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_attachments +-- ---------------------------- +DROP TABLE IF EXISTS `yz_attachments`; +CREATE TABLE `yz_attachments` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '附件名称', + `type` tinyint(1) NOT NULL COMMENT '附件分类 1:图片 2:文件 3:视频 4:音频', + `size` int(11) NULL DEFAULT NULL COMMENT '附件大小', + `src` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '附件地址', + `create_time` int(11) NOT NULL COMMENT '创建时间', + `update_time` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 346 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '附件表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_attachments +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_banner +-- ---------------------------- +DROP TABLE IF EXISTS `yz_banner`; +CREATE TABLE `yz_banner` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题', + `desc` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '简介', + `url` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '跳转地址', + `image` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '图片地址', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + `create_time` int(11) NOT NULL COMMENT '创建时间', + `update_time` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'banner表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_banner +-- ---------------------------- +INSERT INTO `yz_banner` VALUES (1, '智能科技 引领未来', '打造智能化解决方案,助力企业数字化转型,打造智能化解决方案,助力企业数字化转型,打造智能化解决方案,助力企业数字化转型', 'index.html', '/static/images/banner/banner1.jpg', 0, 1746798790, NULL, NULL); +INSERT INTO `yz_banner` VALUES (2, '创新技术 卓越服务', '以创新科技为核心,为客户提供优质服务', 'index.html', '/static/images/banner/banner2.jpg', 0, 1746798790, NULL, NULL); +INSERT INTO `yz_banner` VALUES (3, '专业团队 值得信赖', '资深专家团队,为您提供专业的技术支持', 'index.html', '/static/images/banner/banner3.jpg', 0, 1746798790, NULL, NULL); +INSERT INTO `yz_banner` VALUES (4, '全球视野 本地服务', '立足本地,放眼全球,打造国际化服务', 'index.html', '/static/images/banner/banner4.jpg', 0, 1746798790, NULL, NULL); + +-- ---------------------------- +-- Table structure for yz_content_push +-- ---------------------------- +DROP TABLE IF EXISTS `yz_content_push`; +CREATE TABLE `yz_content_push` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '推送标题', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '推送内容', + `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '推送图片', + `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '跳转链接', + `type` tinyint(1) NULL DEFAULT 1 COMMENT '推送类型:1=普通推送 2=重要推送', + `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + `create_time` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `update_time` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE, + INDEX `idx_type`(`type`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容推送表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_content_push +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_daily_stats +-- ---------------------------- +DROP TABLE IF EXISTS `yz_daily_stats`; +CREATE TABLE `yz_daily_stats` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `date` date NOT NULL COMMENT '统计日期', + `total_users` int(11) NOT NULL DEFAULT 0 COMMENT '总用户数', + `new_users` int(11) NOT NULL DEFAULT 0 COMMENT '新注册用户数', + `total_visits` int(11) NOT NULL DEFAULT 0 COMMENT '总访问量(历史累计)', + `daily_visits` int(11) NOT NULL DEFAULT 0 COMMENT '当日访问量', + `unique_visitors` int(11) NOT NULL DEFAULT 0 COMMENT '当日独立访客数(UV)', + `total_articles` int(11) NOT NULL DEFAULT 0 COMMENT '总文章数', + `daily_articles` int(11) NOT NULL DEFAULT 0 COMMENT '当日发布文章数', + `article_views` int(11) NOT NULL DEFAULT 0 COMMENT '当日文章访问量', + `daily_resources` int(11) NOT NULL DEFAULT 0 COMMENT '当日发布资源数', + `total_resources` int(11) NOT NULL COMMENT '总资源数', + `resource_downloads` int(11) NOT NULL DEFAULT 0 COMMENT '当日资源下载量', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_date`(`date`) USING BTREE COMMENT '日期唯一索引' +) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '每日统计表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_daily_stats +-- ---------------------------- +INSERT INTO `yz_daily_stats` VALUES (24, '2025-07-14', 0, 0, 88, 88, 1, 0, 0, 0, 0, 0, 0, '2025-07-14 11:04:01', '2025-07-14 14:45:42'); + +-- ---------------------------- +-- Table structure for yz_logs_login +-- ---------------------------- +DROP TABLE IF EXISTS `yz_logs_login`; +CREATE TABLE `yz_logs_login` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NULL DEFAULT NULL COMMENT '用户ID(未登录用户可为空)', + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名', + `ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'IP地址', + `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户代理(浏览器信息)', + `login_status` tinyint(1) NOT NULL COMMENT '登录状态(1成功 0失败)', + `failure_reason` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '失败原因', + `login_time` datetime NOT NULL COMMENT '登录时间', + `logout_time` datetime NULL DEFAULT NULL COMMENT '登出时间', + `session_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '会话ID', + `device_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备类型(mobile/pc/tablet)', + `location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地理位置', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`user_id`) USING BTREE, + INDEX `idx_login_time`(`login_time`) USING BTREE, + INDEX `idx_ip`(`ip_address`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 53 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户登录日志表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_logs_login +-- ---------------------------- +INSERT INTO `yz_logs_login` VALUES (49, NULL, '357099073@qq.com', '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', 0, '账号不存在', '2025-07-14 11:56:02', NULL, NULL, 'PC端', '未知'); +INSERT INTO `yz_logs_login` VALUES (50, NULL, 'hero920103', '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', 0, '邮箱格式不正确', '2025-07-14 11:56:10', NULL, NULL, 'PC端', '未知'); +INSERT INTO `yz_logs_login` VALUES (51, NULL, '357099073@qq.com', '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', 1, '', '2025-07-14 11:56:43', NULL, NULL, 'PC端', '未知'); +INSERT INTO `yz_logs_login` VALUES (52, NULL, '357099073@qq.com', '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', 1, '', '2025-07-14 14:40:44', NULL, NULL, 'PC端', '未知'); + +-- ---------------------------- +-- Table structure for yz_logs_operation +-- ---------------------------- +DROP TABLE IF EXISTS `yz_logs_operation`; +CREATE TABLE `yz_logs_operation` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NULL DEFAULT NULL COMMENT '操作用户ID', + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作用户名', + `module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作模块', + `operation` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作类型', + `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求方法(GET/POST等)', + `request_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求参数', + `ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'IP地址', + `status` tinyint(1) NULL DEFAULT NULL COMMENT '操作状态(1成功 0失败)', + `error_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '错误信息', + `operation_time` datetime NOT NULL COMMENT '操作时间', + `execution_time` bigint(20) NULL DEFAULT NULL COMMENT '执行耗时(ms)', + `request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求URL', + `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户代理', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`user_id`) USING BTREE, + INDEX `idx_operation_time`(`operation_time`) USING BTREE, + INDEX `idx_module_operation`(`module`, `operation`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 256 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户操作日志表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_logs_operation +-- ---------------------------- +INSERT INTO `yz_logs_operation` VALUES (254, NULL, '扫地僧', '菜单管理', '编辑菜单', 'POST', '{\"smid\":\"8007\",\"label\":\"资源管理\",\"icon_class\":\"layui-icon-senior\",\"sort\":\"999997\",\"status\":\"0\"}', '127.0.0.1', 1, '', '2025-07-14 14:40:57', 0, 'http://rk.yunzer.cn/index.php/admin/yunzer/menuedit', NULL); +INSERT INTO `yz_logs_operation` VALUES (255, NULL, '扫地僧', '菜单管理', '编辑菜单', 'POST', '{\"smid\":\"7000\",\"label\":\"代码示例\",\"icon_class\":\"layui-icon-app\",\"sort\":\"99995\",\"status\":\"0\"}', '127.0.0.1', 1, '', '2025-07-14 14:41:16', 0, 'http://rk.yunzer.cn/index.php/admin/yunzer/menuedit', NULL); + +-- ---------------------------- +-- Table structure for yz_mail_config +-- ---------------------------- +DROP TABLE IF EXISTS `yz_mail_config`; +CREATE TABLE `yz_mail_config` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '配置ID', + `smtp_host` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'SMTP服务器地址', + `smtp_port` int(11) NOT NULL DEFAULT 465 COMMENT 'SMTP端口', + `smtp_email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '发件人邮箱', + `smtp_password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '邮箱密码或授权码', + `smtp_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '发件人名称', + `create_time` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '邮件配置表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_mail_config +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_resources +-- ---------------------------- +DROP TABLE IF EXISTS `yz_resources`; +CREATE TABLE `yz_resources` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '资源ID', + `cate` int(11) NOT NULL DEFAULT 0 COMMENT '分类ID', + `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '资源名称', + `desc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '资源描述', + `number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '资源编号', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '资源介绍内容', + `size` int(11) NULL DEFAULT NULL COMMENT '资源大小', + `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资源图标', + `images` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '资源图片', + `url` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '资源链接', + `fileurl` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '服务器路径', + `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分享码', + `zipcode` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '解压密码', + `uploader` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '上传者', + `views` int(11) NOT NULL DEFAULT 0 COMMENT '浏览次数', + `downloads` int(11) NOT NULL DEFAULT 0 COMMENT '下载次数', + `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序(数字越大越靠前)', + `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态(0未审核,1已审核)', + `create_time` int(11) NOT NULL COMMENT '创建时间', + `update_time` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_cate_id`(`cate`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE, + INDEX `idx_sort`(`sort`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 40 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '资源表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_resources +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_resources_category +-- ---------------------------- +DROP TABLE IF EXISTS `yz_resources_category`; +CREATE TABLE `yz_resources_category` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类ID', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分类名称', + `cid` int(11) NOT NULL DEFAULT 0 COMMENT '父级分类ID', + `number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类编号', + `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类图标', + `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序(数字越大越靠前)', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态(0禁用,1正常)', + `create_time` int(11) NOT NULL COMMENT '创建时间', + `update_time` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_cid`(`cid`) USING BTREE, + INDEX `idx_sort`(`sort`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '资源分类表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_resources_category +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_system_notice +-- ---------------------------- +DROP TABLE IF EXISTS `yz_system_notice`; +CREATE TABLE `yz_system_notice` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '通知ID', + `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '通知标题', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '通知内容', + `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '通知类型:1=系统通知,2=活动通知,3=更新通知', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:0=禁用,1=启用', + `is_top` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否置顶:0=否,1=是', + `is_read` tinyint(1) NOT NULL DEFAULT 0 COMMENT '未读:0 已读:1', + `view_count` int(11) NOT NULL DEFAULT 0 COMMENT '查看次数', + `create_time` int(11) NOT NULL COMMENT '创建时间', + `update_time` int(11) NOT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_type_status`(`type`, `status`) USING BTREE, + INDEX `idx_create_time`(`create_time`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统通知表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_system_notice +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_user_message +-- ---------------------------- +DROP TABLE IF EXISTS `yz_user_message`; +CREATE TABLE `yz_user_message` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '消息ID', + `user_id` int(11) NOT NULL COMMENT '用户ID', + `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息标题', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息内容', + `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '消息类型:1=系统消息,2=互动消息,3=通知消息', + `is_top` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否置顶:0=否,1=是', + `is_read` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已读:0=未读,1=已读', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:0=禁用,1=启用', + `read_time` int(11) NULL DEFAULT NULL COMMENT '阅读时间', + `create_time` int(11) NOT NULL COMMENT '创建时间', + `update_time` int(11) NOT NULL COMMENT '更新时间', + `delete_time` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`user_id`) USING BTREE, + INDEX `idx_type`(`type`) USING BTREE, + INDEX `idx_is_read`(`is_read`) USING BTREE, + INDEX `idx_create_time`(`create_time`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '我的消息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_user_message +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_users +-- ---------------------------- +DROP TABLE IF EXISTS `yz_users`; +CREATE TABLE `yz_users` ( + `uid` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `account` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号邮箱', + `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码', + `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名', + `avatar` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '头像', + `phone` varchar(18) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号', + `qq` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'QQ号', + `wechat` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信号', + `sex` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '性别 0未知 1男 2女', + `group_id` int(10) UNSIGNED NOT NULL DEFAULT 2 COMMENT '分组ID', + `login_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '登陆次数', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态 1开启 0禁用', + `api_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'API密钥', + `api_key_expire` datetime NULL DEFAULT NULL COMMENT 'API密钥过期时间', + `api_key_status` tinyint(1) NULL DEFAULT 1 COMMENT 'API密钥状态(1有效 0禁用)', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + `openid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信openid', + PRIMARY KEY (`uid`) USING BTREE, + UNIQUE INDEX `one_account`(`account`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_users +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_users_group +-- ---------------------------- +DROP TABLE IF EXISTS `yz_users_group`; +CREATE TABLE `yz_users_group` ( + `group_id` int(11) NOT NULL AUTO_INCREMENT, + `group_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '部门名称', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否生效', + `rights` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '角色权限', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`group_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '组管理' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_users_group +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_z_iconfont +-- ---------------------------- +DROP TABLE IF EXISTS `yz_z_iconfont`; +CREATE TABLE `yz_z_iconfont` ( + `icon_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '图标ID', + `icon_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标名称', + `icon_css` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标css', + `icon_html` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标html', + `icon_source` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '来源 1layui', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态 1开启 0关闭', + PRIMARY KEY (`icon_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 169 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '图标库' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_z_iconfont +-- ---------------------------- + +-- ---------------------------- +-- Table structure for yz_z_test +-- ---------------------------- +DROP TABLE IF EXISTS `yz_z_test`; +CREATE TABLE `yz_z_test` ( + `test_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID', + `test_input` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文本', + `test_rich` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '富文本', + `test_rich_baidu` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '百度富文本', + `test_img` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图片', + `test_reference` tinyint(1) UNSIGNED NULL DEFAULT NULL COMMENT '参照', + `test_time` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '时间戳', + `test_data` date NULL DEFAULT NULL COMMENT '日期', + `test_datatime` datetime NULL DEFAULT NULL COMMENT '日期时间', + `test_url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '网址', + PRIMARY KEY (`test_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '演示表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of yz_z_test +-- ---------------------------- + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/migrations/20240321_create_article_category_table.php b/database/migrations/20240321_create_article_category_table.php new file mode 100644 index 0000000..924173b --- /dev/null +++ b/database/migrations/20240321_create_article_category_table.php @@ -0,0 +1,40 @@ +table('yz_article_category', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_unicode_ci']); + $table + ->addColumn('name', 'string', ['limit' => 50, 'null' => false, 'comment' => '分类名称']) + ->addColumn('cid', 'integer', ['signed' => true, 'null' => false, 'default' => 0, 'comment' => '父级ID']) + ->addColumn('image', 'string', ['limit' => 255, 'null' => true, 'comment' => '分类图片']) + ->addColumn('sort', 'integer', ['signed' => true, 'null' => false, 'default' => 0, 'comment' => '排序']) + ->addColumn('status', 'integer', ['signed' => true, 'null' => false, 'default' => 1, 'comment' => '状态:1=正常,2=禁用']) + ->addColumn('create_time', 'integer', ['signed' => true, 'null' => false, 'default' => 0, 'comment' => '创建时间']) + ->addColumn('update_time', 'integer', ['signed' => true, 'null' => false, 'default' => 0, 'comment' => '更新时间']) + ->addColumn('delete_time', 'integer', ['signed' => true, 'null' => true, 'comment' => '删除时间']) + ->addIndex(['cid'], ['name' => 'idx_cid']) + ->addIndex(['status'], ['name' => 'idx_status']) + ->create(); + } +} \ No newline at end of file diff --git a/database/migrations/20240321_create_article_view_table.php b/database/migrations/20240321_create_article_view_table.php new file mode 100644 index 0000000..cc5a1f9 --- /dev/null +++ b/database/migrations/20240321_create_article_view_table.php @@ -0,0 +1,34 @@ +table('yz_article_view', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_unicode_ci']); + $table + ->addColumn('article_id', 'integer', ['signed' => false, 'null' => false, 'comment' => '文章ID']) + ->addColumn('ip', 'string', ['limit' => 50, 'null' => false, 'comment' => '访问IP']) + ->addColumn('view_time', 'integer', ['signed' => false, 'null' => false, 'comment' => '访问时间']) + ->addIndex(['article_id', 'ip', 'view_time'], ['name' => 'idx_article_ip_time']) + ->create(); + } +} \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..e69de29 diff --git a/public/.htaccess_mac b/public/.htaccess_mac new file mode 100644 index 0000000..d0d5125 --- /dev/null +++ b/public/.htaccess_mac @@ -0,0 +1,8 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] + \ No newline at end of file diff --git a/public/.htaccess_win b/public/.htaccess_win new file mode 100644 index 0000000..87f952d --- /dev/null +++ b/public/.htaccess_win @@ -0,0 +1,8 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php [L,E=PATH_INFO:$1] + \ No newline at end of file diff --git a/public/event/js-sdk-event.min.js b/public/event/js-sdk-event.min.js new file mode 100644 index 0000000..1cbfef7 --- /dev/null +++ b/public/event/js-sdk-event.min.js @@ -0,0 +1,6 @@ +/*! +* 51LA Event Analysis Javascript Software Development Kit +* js-sdk-event v1.6.0 +* Copyright © 2016-2022 51.la All Rights Reserved +*/ +!function(e){"use strict";var c=window;function t(t,e){var n,r=Object.keys(t);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(t),e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)),r}function v(r){for(var e=1;ee.length)&&(t=e.length);for(var n=0,r=new Array(t);n>>0,i=0;if(3<=arguments.length)n=arguments[2];else{for(;i>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(2>>0,o=3<=arguments.length?arguments[2]:void 0,i=0;i>>0;if("function"!=typeof t)throw new TypeError;for(2>>0,o=new Array(r),i=e,a=0,u=-1;if(void 0===n)for(;++u!=r;)u in e&&t(i[u],u,i)&&(o[a++]=i[u]);else for(;++u!=r;)u in e&&t.call(n,i[u],u,i)&&(o[a++]=i[u]);return o.length=a,o},x=function(e,t){if(Array.indexOf)return e.indexOf(t);for(var n=0;nt.length?t:e).length;if("[object Object]"!==e&&"[object Object]"!==t){for(var r=0;rt.charCodeAt(r))return 1;if(e.charCodeAt(r)==t.charCodeAt(r))continue;return}if(!isNaN(e.charCodeAt(n))&&isNaN(t.charCodeAt(n))&&e.charCodeAt(n-1)==t.charCodeAt(n-1))return 1}}return o};function s(t,e){var n=window;if(n.document)return!1;n.document=e.context||function(){for(var e=t;e.parent;)e=e.parent;return e}();n=Object.getPrototypeOf(n.document);return Object.getOwnPropertyDescriptor(n,"childTags")||Object.defineProperty(n,"childTags",{enumerable:!0,get:function(){return S(this.children,function(e){return"tag"===e.type||"script"===e.type||"style"===e.type})}}),Object.getOwnPropertyDescriptor(n,"attributes")||Object.defineProperty(n,"attributes",{enumerable:!0,get:function(){var r=this.attribs,e=y(r),t=esEeduce(e,function(e,t,n){return e[n]={name:t,value:r[t]},e},{});return Object.defineProperty(t,"length",{enumerable:!1,configurable:!1,value:e.length}),t}}),n.getAttribute||(n.getAttribute=function(e){return this.attribs[e]||null}),n.getElementsByTagName||(n.getElementsByTagName=function(t){var n=[];return E(this.childTags,function(e){e.name!==t&&"*"!==t||n.push(e)}),n}),n.getElementsByClassName||(n.getElementsByClassName=function(e){var n=e.replace(/(^\s*)|(\s*$)/g,"").replace(/\s+/g," ").split(" "),r=[];return E([this],function(e){var t=e.attribs["class"];t&&m(n,function(e){return-1)(\S)/g,"$1 $2").replace(/(^\s*)|(\s*$)/g,""),e=function(e){var t=[];if(e)for(var n=0;n/.test(n):i=function(t){return function(e){return e(t.parent)&&t.parent}};break;case/^\./.test(n):var a=n.substr(1).split("."),o=function(e){var t=e.attribs["class"];return t&&m(a,function(e){return-1@~]/g,"\\$&").replace(/\n/g,"A")}var d={attribute:function(e){return-1 /g,">").split(/\s+(?=(?:(?:[^"]*"){2})*[^"]*$)/);if(r.length<2)return k("",e,"",t);for(var o=[r.pop()];1/g,"> ").replace(/(^\s*)|(\s*$)/g,"")}function k(n,r,o,i){if(n.length&&(n="".concat(n," ")),o.length&&(o=" ".concat(o)),/\[*\]/.test(r)){var e=r.replace(/=.*$/,"]"),a="".concat(n).concat(e).concat(o);if(_(N()?window.LASel(a):document.querySelectorAll(a),i))r=e;else for(var u=N()?window.LASel("".concat(n).concat(e)):document.querySelectorAll("".concat(n).concat(e)),l=0,t=u.length;l/.test(r)&&(c=r.replace(/>/,""),a="".concat(n).concat(c).concat(o),_(N()?window.LASel(a):document.querySelectorAll(a),i)&&(r=c)),/:nth-child/.test(r)&&(c=r.replace(/nth-child/g,"nth-of-type"),a="".concat(n).concat(c).concat(o),_(N()?window.LASel(a):document.querySelectorAll(a),i)&&(r=c)),/\.\S+\.\S+/.test(r)){var s=[];for((!r.match(/\[.*?\]/g)||r.match(/\[.*?\]/g).length<=0)&&A(w(r.replace(/^\s\s*/,"").replace(/\s\s*$/,"").split(".").slice(1),function(e){return".".concat(e)}),function(e,t){return e.length-t.length});s.length;){var f=r.replace(s.shift(),"").replace(/^\s\s*/,"").replace(/\s\s*$/,"");if(!(a="".concat(n).concat(f).concat(o).replace(/^\s\s*/,"").replace(/\s\s*$/,"")).length||">"===a.charAt(0)||">"===a.charAt(a.length-1))break;_(N()?window.LASel(a):document.querySelectorAll(a),i)&&(r=f)}if((s=r&&r.match(/\./g))&&2+~]|[\\x20\\t\\r\\n\\f])[\\x20\\t\\r\\n\\f]*"),Ae=new RegExp(me+"|>"),Ee=new RegExp(we),Ce=new RegExp("^"+ye+"$"),Te={ID:new RegExp("^#("+ye+")"),CLASS:new RegExp("^\\.("+ye+")"),TAG:new RegExp("^("+ye+"|[*])"),ATTR:new RegExp("^"+ve),PSEUDO:new RegExp("^"+we),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\([\\x20\\t\\r\\n\\f]*(even|odd|(([+-]|)(\\d*)n|)[\\x20\\t\\r\\n\\f]*(?:([+-]|)[\\x20\\t\\r\\n\\f]*(\\d+)|))[\\x20\\t\\r\\n\\f]*\\)|)","i"),bool:new RegExp("^(?:"+ge+")$","i"),needsContext:new RegExp("^[\\x20\\t\\r\\n\\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\([\\x20\\t\\r\\n\\f]*((?:-\\d)?\\d*)[\\x20\\t\\r\\n\\f]*\\)|)(?=[^-]|$)","i")},Le=/HTML$/i,Oe=/^(?:input|select|textarea|button)$/i,Ie=/^h\d$/i,De=/^[^{]+\{\s*\[native \w/,ke=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_e=/[+~]/,qe=new RegExp("\\\\[\\da-fA-F]{1,6}[\\x20\\t\\r\\n\\f]?|\\\\([^\\r\\n\\f])","g"),Me=function(e,t){e="0x"+e.slice(1)-65536;return t||(e<0?String.fromCharCode(65536+e):String.fromCharCode(e>>10|55296,1023&e|56320))},je=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,Re=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},Be=function(){Q()},Pe=Ve(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{de.apply(ce=pe.call(ee.childNodes),ee.childNodes),ce[ee.childNodes.length].nodeType}catch(e){de={apply:ce.length?function(e,t){fe.apply(e,pe.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}function $e(e,t,n,r){var o,i,a,u,l,c,s=t&&t.ownerDocument,f=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==f&&9!==f&&11!==f)return n;if(!r&&(Q(t),t=t||J,X)){if(11!==f&&(u=ke.exec(e)))if(c=u[1]){if(9===f){if(!(i=t.getElementById(c)))return n;if(i.id===c)return n.push(i),n}else if(s&&(i=s.getElementById(c))&&K(t,i)&&i.id===c)return n.push(i),n}else{if(u[2])return de.apply(n,t.getElementsByTagName(e)),n;if((c=u[3])&&M.getElementsByClassName&&t.getElementsByClassName)return de.apply(n,t.getElementsByClassName(c)),n}if(M.qsa&&!ae[e+" "]&&(!Y||!Y.test(e))&&(1!==f||"object"!==t.nodeName.toLowerCase())){if(c=e,s=t,1===f&&(Ae.test(e)||Ne.test(e))){for((s=_e.test(e)&&Xe(t.parentNode)||t)===t&&M.scope||((a=t.getAttribute("id"))?a=a.replace(je,Re):t.setAttribute("id",a=Z)),o=(l=P(e)).length;o--;)l[o]=(a?"#"+a:":scope")+" "+Ge(l[o]);c=l.join(",")}var d=t;try{return de.apply(n,s.querySelectorAll(c)),n}catch(t){ae(e,!0)}finally{a===Z&&d.removeAttribute("id")}}}return t=d,H(e.replace(Se,"$1"),t,n,r)}function He(){var n=[];return function c(e,t){return n.push(e+" ")>j.cacheLength&&delete c[n.shift()],c[e+" "]=t}}function Fe(e){return e[Z]=!0,e}function Ue(e){var t=J.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t)}}function ze(e,t){for(var n=e.split("|"),r=n.length;r--;)j.attrHandle[n[r]]=t}function Qe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function Je(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&Pe(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function We(a){return Fe(function(i){return i=+i,Fe(function(e,t){for(var n,r=a([],e.length,i),o=r.length;o--;)e[n=r[o]]&&(e[n]=!(t[n]=e[n]))})})}function Xe(e){return e&&void 0!==e.getElementsByTagName&&e}for(n in M=$e.support={},B=$e.isXML=function(e){var t=e&&e.namespaceURI,e=e&&(e.ownerDocument||e).documentElement;return!Le.test(t||e&&e.nodeName||"HTML")},Q=$e.setDocument=function(e){var t,e=e?e.ownerDocument||e:ee;return e!=J&&9===e.nodeType&&e.documentElement&&(W=(J=e).documentElement,X=!B(J),ee!=J&&(t=J.defaultView)&&t.top!==t&&(t.addEventListener?t.addEventListener("unload",Be,!1):t.attachEvent&&t.attachEvent("onunload",Be)),M.scope=Ue(function(e){return W.appendChild(e).appendChild(J.createElement("div")),void 0!==e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),M.attributes=Ue(function(e){return e.className="i",!e.getAttribute("className")}),M.getElementsByTagName=Ue(function(e){return e.appendChild(J.createComment("")),!e.getElementsByTagName("*").length}),M.getElementsByClassName=De.test(J.getElementsByClassName),M.getById=Ue(function(e){return W.appendChild(e).id=Z,!J.getElementsByName||!J.getElementsByName(Z).length}),M.getById?(j.filter.ID=function(e){var t=e.replace(qe,Me);return function(e){return e.getAttribute("id")===t}},j.find.ID=function(e,t){if(void 0!==t.getElementById&&X){t=t.getElementById(e);return t?[t]:[]}}):(j.filter.ID=function(e){var t=e.replace(qe,Me);return function(e){e=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return e&&e.value===t}},j.find.ID=function(e,t){if(void 0!==t.getElementById&&X){var n,r,o,i=t.getElementById(e);if(i){if((n=i.getAttributeNode("id"))&&n.value===e)return[i];for(o=t.getElementsByName(e),r=0;i=o[r++];)if((n=i.getAttributeNode("id"))&&n.value===e)return[i]}return[]}}),j.find.TAG=M.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):M.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],o=0,i=t.getElementsByTagName(e);if("*"!==e)return i;for(;n=i[o++];)1===n.nodeType&&r.push(n);return r},j.find.CLASS=M.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&X)return t.getElementsByClassName(e)},G=[],Y=[],(M.qsa=De.test(J.querySelectorAll))&&(Ue(function(e){var t;W.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&Y.push("[*^$]=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),e.querySelectorAll("[selected]").length||Y.push("\\[[\\x20\\t\\r\\n\\f]*(?:value|"+ge+")"),e.querySelectorAll("[id~="+Z+"-]").length||Y.push("~="),(t=J.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||Y.push("\\[[\\x20\\t\\r\\n\\f]*name[\\x20\\t\\r\\n\\f]*=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),e.querySelectorAll(":checked").length||Y.push(":checked"),e.querySelectorAll("a#"+Z+"+*").length||Y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),Y.push("[\\r\\n\\f]")}),Ue(function(e){e.innerHTML="";var t=J.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&Y.push("name[\\x20\\t\\r\\n\\f]*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&Y.push(":enabled",":disabled"),W.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&Y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),Y.push(",.*:")})),(M.matchesSelector=De.test(V=W.matches||W.webkitMatchesSelector||W.mozMatchesSelector||W.oMatchesSelector||W.msMatchesSelector))&&Ue(function(e){M.disconnectedMatch=V.call(e,"*"),V.call(e,"[s!='']:x"),G.push("!=",we)}),Y=Y.length&&new RegExp(Y.join("|")),G=G.length&&new RegExp(G.join("|")),t=De.test(W.compareDocumentPosition),K=t||De.test(W.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,t=t&&t.parentNode;return e===t||!(!t||1!==t.nodeType||!(n.contains?n.contains(t):e.compareDocumentPosition&&16&e.compareDocumentPosition(t)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},ue=t?function(e,t){return e===t?(z=!0,0):(n=!e.compareDocumentPosition-!t.compareDocumentPosition)||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!M.sortDetached&&t.compareDocumentPosition(e)===n?e==J||e.ownerDocument==ee&&K(ee,e)?-1:t==J||t.ownerDocument==ee&&K(ee,t)?1:U?he(U,e)-he(U,t):0:4&n?-1:1);var n}:function(e,t){if(e===t)return z=!0,0;var n,r=0,o=e.parentNode,i=t.parentNode,a=[e],u=[t];if(!o||!i)return e==J?-1:t==J?1:o?-1:i?1:U?he(U,e)-he(U,t):0;if(o===i)return Qe(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;a[r]===u[r];)r++;return r?Qe(a[r],u[r]):a[r]==ee?-1:u[r]==ee?1:0}),J},$e.matches=function(e,t){return $e(e,null,null,t)},$e.matchesSelector=function(e,t){if(Q(e),M.matchesSelector&&X&&!ae[t+" "]&&(!G||!G.test(t))&&(!Y||!Y.test(t)))try{var n=V.call(e,t);if(n||M.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){ae(t,!0)}return 0<$e(t,J,null,[e]).length},$e.contains=function(e,t){return(e.ownerDocument||e)!=J&&Q(e),K(e,t)},$e.attr=function(e,t){(e.ownerDocument||e)!=J&&Q(e);var n=j.attrHandle[t.toLowerCase()],n=n&&le.call(j.attrHandle,t.toLowerCase())?n(e,t,!X):void 0;return void 0!==n?n:M.attributes||!X?e.getAttribute(t):(n=e.getAttributeNode(t))&&n.specified?n.value:null},$e.escape=function(e){return(e+"").replace(je,Re)},$e.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},$e.uniqueSort=function(e){var t,n=[],r=0,o=0;if(z=!M.detectDuplicates,U=!M.sortStable&&e.slice(0),e.sort(ue),z){for(;t=e[o++];)t===e[o]&&(r=n.push(o));for(;r--;)e.splice(n[r],1)}return U=null,e},R=$e.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=R(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r++];)n+=R(t);return n},(j=$e.selectors={cacheLength:50,createPseudo:Fe,match:Te,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(qe,Me),e[3]=(e[3]||e[4]||e[5]||"").replace(qe,Me),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||$e.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&$e.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Te.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&Ee.test(n)&&(t=P(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(qe,Me).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=re[e+" "];return t||(t=new RegExp("(^|[\\x20\\t\\r\\n\\f])"+e+"("+me+"|$)"))&&re(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(t,n,r){return function(e){e=$e.attr(e,t);return null==e?"!="===n:!n||(e+="","="===n?e===r:"!="===n?e!==r:"^="===n?r&&0===e.indexOf(r):"*="===n?r&&-1+~]|[\\x20\\t\\r\\n\\f])[\\x20\\t\\r\\n\\f]*"),cn=new RegExp(nn+"|>"),sn=new RegExp(rn),fn=new RegExp("^"+ve+"$"),dn={ID:new RegExp("^#("+ve+")"),CLASS:new RegExp("^\\.("+ve+")"),TAG:new RegExp("^("+ve+"|[*])"),ATTR:new RegExp("^"+ce),PSEUDO:new RegExp("^"+rn),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\([\\x20\\t\\r\\n\\f]*(even|odd|(([+-]|)(\\d*)n|)[\\x20\\t\\r\\n\\f]*(?:([+-]|)[\\x20\\t\\r\\n\\f]*(\\d+)|))[\\x20\\t\\r\\n\\f]*\\)|)","i"),bool:new RegExp("^(?:"+tn+")$","i"),needsContext:new RegExp("^[\\x20\\t\\r\\n\\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\([\\x20\\t\\r\\n\\f]*((?:-\\d)?\\d*)[\\x20\\t\\r\\n\\f]*\\)|)(?=[^-]|$)","i")},pn=/HTML$/i,hn=/^(?:input|select|textarea|button)$/i,gn=/^h\d$/i,mn=/^[^{]+\{\s*\[native \w/,yn=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,vn=/[+~]/,wn=new RegExp("\\\\[\\da-fA-F]{1,6}[\\x20\\t\\r\\n\\f]?|\\\\([^\\r\\n\\f])","g"),bn=function(e,t){e="0x"+e.slice(1)-65536;return t||(e<0?String.fromCharCode(65536+e):String.fromCharCode(e>>10|55296,1023&e|56320))},Sn=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,xn=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},Nn=function(){kt()},An=jn(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{Kt.apply(ye=Zt.call(Ht.childNodes),Ht.childNodes),ye[Ht.childNodes.length].nodeType}catch(e){Kt={apply:ye.length?function(e,t){Vt.apply(e,Zt.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}function En(e,t,n,r){var o,i,a,u,l,c,s=t&&t.ownerDocument,f=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==f&&9!==f&&11!==f)return n;if(!r&&(kt(t),t=t||_t,Mt)){if(11!==f&&(u=yn.exec(e)))if(c=u[1]){if(9===f){if(!(i=t.getElementById(c)))return n;if(i.id===c)return n.push(i),n}else if(s&&(i=s.getElementById(c))&&Pt(t,i)&&i.id===c)return n.push(i),n}else{if(u[2])return Kt.apply(n,t.getElementsByTagName(e)),n;if((c=u[3])&&xt.getElementsByClassName&&t.getElementsByClassName)return Kt.apply(n,t.getElementsByClassName(c)),n}if(xt.qsa&&!Wt[e+" "]&&(!jt||!jt.test(e))&&(1!==f||"object"!==t.nodeName.toLowerCase())){if(c=e,s=t,1===f&&(cn.test(e)||ln.test(e))){for((s=vn.test(e)&&_n(t.parentNode)||t)===t&&xt.scope||((a=t.getAttribute("id"))?a=a.replace(Sn,xn):t.setAttribute("id",a=$t)),o=(l=Ct(e)).length;o--;)l[o]=(a?"#"+a:":scope")+" "+Mn(l[o]);c=l.join(",")}var d=t;try{return Kt.apply(n,s.querySelectorAll(c)),n}catch(t){Wt(e,!0)}finally{a===$t&&d.removeAttribute("id")}}}return t=d,Lt(e.replace(an,"$1"),t,n,r)}function Cn(){var n=[];return function c(e,t){return n.push(e+" ")>Nt.cacheLength&&delete c[n.shift()],c[e+" "]=t}}function Tn(e){return e[$t]=!0,e}function Ln(e){var t=_t.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t)}}function On(e,t){for(var n=e.split("|"),r=n.length;r--;)Nt.attrHandle[n[r]]=t}function In(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function Dn(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&An(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function kn(a){return Tn(function(i){return i=+i,Tn(function(e,t){for(var n,r=a([],e.length,i),o=r.length;o--;)e[n=r[o]]&&(e[n]=!(t[n]=e[n]))})})}function _n(e){return e&&void 0!==e.getElementsByTagName&&e}for(St in xt=En.support={},Et=En.isXML=function(e){var t=e&&e.namespaceURI,e=e&&(e.ownerDocument||e).documentElement;return!pn.test(t||e&&e.nodeName||"HTML")},kt=En.setDocument=function(e){var t,e=e?e.ownerDocument||e:Ht;return e!=_t&&9===e.nodeType&&e.documentElement&&(qt=(_t=e).documentElement,Mt=!Et(_t),Ht!=_t&&(t=_t.defaultView)&&t.top!==t&&(t.addEventListener?t.addEventListener("unload",Nn,!1):t.attachEvent&&t.attachEvent("onunload",Nn)),xt.scope=Ln(function(e){return qt.appendChild(e).appendChild(_t.createElement("div")),void 0!==e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),xt.attributes=Ln(function(e){return e.className="i",!e.getAttribute("className")}),xt.getElementsByTagName=Ln(function(e){return e.appendChild(_t.createComment("")),!e.getElementsByTagName("*").length}),xt.getElementsByClassName=mn.test(_t.getElementsByClassName),xt.getById=Ln(function(e){return qt.appendChild(e).id=$t,!_t.getElementsByName||!_t.getElementsByName($t).length}),xt.getById?(Nt.filter.ID=function(e){var t=e.replace(wn,bn);return function(e){return e.getAttribute("id")===t}},Nt.find.ID=function(e,t){if(void 0!==t.getElementById&&Mt){t=t.getElementById(e);return t?[t]:[]}}):(Nt.filter.ID=function(e){var t=e.replace(wn,bn);return function(e){e=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return e&&e.value===t}},Nt.find.ID=function(e,t){if(void 0!==t.getElementById&&Mt){var n,r,o,i=t.getElementById(e);if(i){if((n=i.getAttributeNode("id"))&&n.value===e)return[i];for(o=t.getElementsByName(e),r=0;i=o[r++];)if((n=i.getAttributeNode("id"))&&n.value===e)return[i]}return[]}}),Nt.find.TAG=xt.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):xt.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],o=0,i=t.getElementsByTagName(e);if("*"!==e)return i;for(;n=i[o++];)1===n.nodeType&&r.push(n);return r},Nt.find.CLASS=xt.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&Mt)return t.getElementsByClassName(e)},Rt=[],jt=[],(xt.qsa=mn.test(_t.querySelectorAll))&&(Ln(function(e){var t;qt.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&jt.push("[*^$]=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),e.querySelectorAll("[selected]").length||jt.push("\\[[\\x20\\t\\r\\n\\f]*(?:value|"+tn+")"),e.querySelectorAll("[id~="+$t+"-]").length||jt.push("~="),(t=_t.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||jt.push("\\[[\\x20\\t\\r\\n\\f]*name[\\x20\\t\\r\\n\\f]*=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),e.querySelectorAll(":checked").length||jt.push(":checked"),e.querySelectorAll("a#"+$t+"+*").length||jt.push(".#.+[+~]"),e.querySelectorAll("\\\f"),jt.push("[\\r\\n\\f]")}),Ln(function(e){e.innerHTML="";var t=_t.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&jt.push("name[\\x20\\t\\r\\n\\f]*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&jt.push(":enabled",":disabled"),qt.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&jt.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),jt.push(",.*:")})),(xt.matchesSelector=mn.test(Bt=qt.matches||qt.webkitMatchesSelector||qt.mozMatchesSelector||qt.oMatchesSelector||qt.msMatchesSelector))&&Ln(function(e){xt.disconnectedMatch=Bt.call(e,"*"),Bt.call(e,"[s!='']:x"),Rt.push("!=",rn)}),jt=jt.length&&new RegExp(jt.join("|")),Rt=Rt.length&&new RegExp(Rt.join("|")),t=mn.test(qt.compareDocumentPosition),Pt=t||mn.test(qt.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,t=t&&t.parentNode;return e===t||!(!t||1!==t.nodeType||!(n.contains?n.contains(t):e.compareDocumentPosition&&16&e.compareDocumentPosition(t)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},Xt=t?function(e,t){return e===t?(Dt=!0,0):(n=!e.compareDocumentPosition-!t.compareDocumentPosition)||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!xt.sortDetached&&t.compareDocumentPosition(e)===n?e==_t||e.ownerDocument==Ht&&Pt(Ht,e)?-1:t==_t||t.ownerDocument==Ht&&Pt(Ht,t)?1:It?en(It,e)-en(It,t):0:4&n?-1:1);var n}:function(e,t){if(e===t)return Dt=!0,0;var n,r=0,o=e.parentNode,i=t.parentNode,a=[e],u=[t];if(!o||!i)return e==_t?-1:t==_t?1:o?-1:i?1:It?en(It,e)-en(It,t):0;if(o===i)return In(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;a[r]===u[r];)r++;return r?In(a[r],u[r]):a[r]==Ht?-1:u[r]==Ht?1:0}),_t},En.matches=function(e,t){return En(e,null,null,t)},En.matchesSelector=function(e,t){if(kt(e),xt.matchesSelector&&Mt&&!Wt[t+" "]&&(!Rt||!Rt.test(t))&&(!jt||!jt.test(t)))try{var n=Bt.call(e,t);if(n||xt.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){Wt(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(wn,bn),e[3]=(e[3]||e[4]||e[5]||"").replace(wn,bn),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||En.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&En.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return dn.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&sn.test(n)&&(t=Ct(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(wn,bn).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=zt[e+" "];return t||(t=new RegExp("(^|[\\x20\\t\\r\\n\\f])"+e+"("+nn+"|$)"))&&zt(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(t,n,r){return function(e){e=En.attr(e,t);return null==e?"!="===n:!n||(e+="","="===n?e===r:"!="===n?e!==r:"^="===n?r&&0===e.indexOf(r):"*="===n?r&&-1 +// +---------------------------------------------------------------------- + +// [ 应用入口文件 ] +namespace think; + +require __DIR__ . '/../vendor/autoload.php'; + +// 执行HTTP应用并响应 +$http = (new App())->http; + +$response = $http->run(); + +$response->send(); + +$http->end($response); diff --git a/public/nginx.htaccess b/public/nginx.htaccess new file mode 100644 index 0000000..fad7d30 --- /dev/null +++ b/public/nginx.htaccess @@ -0,0 +1,11 @@ +location / { + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php?s=$1 last; + break; + } +} + +# ???? storage ???????? +location /storage/ { + try_files $uri $uri/ /index.php?s=$uri&$args; +} \ No newline at end of file diff --git a/public/plugins/js-sdk-pro.min.js b/public/plugins/js-sdk-pro.min.js new file mode 100644 index 0000000..7e7532d --- /dev/null +++ b/public/plugins/js-sdk-pro.min.js @@ -0,0 +1,6 @@ +/*! + * 51LA Analysis Javascript Software Development Kit + * js-sdk-pro v1.58.3 + * Copyright © 2016-2022 51.la All Rights Reserved + */ +(function(c){'use strict';var e=window,g=e['document'],h=encodeURIComponent,i=A('Object'),j=A('Number'),k=A('String'),m=A('Array'),n=A('Function'),o=A('RegExp');function q(t,u){return void 0x0!==t&&-0x1!==t['indexOf'](u);}function v(w,x){for(var y=0x0,z=w['length'];yS;}var V={'root':e,'doc':g,'NFKivY':i,'SkKBjD':j,'qQXzeL':n,'QauvcB':o,'jPvmCm':k,'pWExzw':m,'xdaPuS':q,'extend':D,'NOwfJP':K,'trim':O,'now':Q,'arrayIndexOf':v,'checkChormeMoblie':R},W=function(){var X,Y=a2('meta'),Z=a2('title'),a0={'kw':'','ds':''};a0['tt']=V['trim'](Z['length']?Z[0x0]['innerHTML']:''),a0['tt']=a0['tt']['slice'](0x0,0x3e8);for(var a1=0x0;a1>>0x8,am[0x2*an+0x1]=ap%0x100;}return am;},'zvqA':function(ag){return null==ag?'':ah['RCWS'](ag,0x6,function(ag){return'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$'['charAt'](ag);});},'Isoq':function(as){return ah['RCWS'](as,0x10,function(ah){return ag(ah);});},'RCWS':function(ag,ah,aw){if(null==ag)return'';var ax,ay,az,aA={},aB={},aC='',aD='',aE='',aF=0x2,aG=0x3,aH=0x2,aI=[],aJ=0x0,aK=0x0;for(az=0x0;az>=0x1;}else{for(ay=0x1,ax=0x0;ax>=0x1;}0x0==--aF&&(aF=Math['pow'](0x2,aH),aH++),delete aB[aE];}else for(ay=aA[aE],ax=0x0;ax>=0x1;0x0==--aF&&(aF=Math['pow'](0x2,aH),aH++),aA[aD]=aG++,aE=String(aC);}if(''!==aE){if(Object['prototype']['hasOwnProperty']['call'](aB,aE)){if(aE['charCodeAt'](0x0)<0x100){for(ax=0x0;ax>=0x1;}else{for(ay=0x1,ax=0x0;ax>=0x1;}0x0==--aF&&(aF=Math['pow'](0x2,aH),aH++),delete aB[aE];}else for(ay=aA[aE],ax=0x0;ax>=0x1;0x0==--aF&&(aF=Math['pow'](0x2,aH),aH++);}for(ay=0x2,ax=0x0;ax>=0x1;for(;;){if(aJ<<=0x1,aK==ah-0x1){aI['push'](aw(aJ));break;}aK++;}return aI['join']('');}};return ah;}();function aL(aM){return(aL='function'==typeof Symbol&&'symbol'==typeof Symbol['iterator']?function(aM){return typeof aM;}:function(aM){return aM&&'function'==typeof Symbol&&aM['constructor']===Symbol&&aM!==Symbol['prototype']?'symbol':typeof aM;})(aM);}var aP={'parse':function gw(aV){return eval('('+aV+')');},'stringify':(aQ=Object['prototype']['toString'],aR=Object['prototype']['hasOwnProperty'],aS={'"':'\x5c\x22','\\':'\x5c\x5c','\b':'\x5cb','\f':'\x5cf','\n':'\x5cn','\r':'\x5cr','\t':'\x5ct'},aT=function(aW){return aS[aW]||'\x5cu'+(aW['charCodeAt'](0x0)+0x10000)['toString'](0x10)['substr'](0x1);},aU=/[\\"\u0000-\u001F\u2028\u2029]/g,function t(aX){if(null==aX)return'null';if('number'==typeof aX)return isFinite(aX)?aX['toString']():'null';if('boolean'==typeof aX)return aX['toString']();if('object'===aL(aX)){if('function'==typeof aX['toJSON'])return t(aX['toJSON']());if(b2=aX,'[object\x20Array]'===aQ['call'](b2)){for(var aY='[',aZ=0x0;aZ>>0x1|(0x5555&bn)<<0x1;bo=(0xf0f0&(bo=(0xcccc&bo)>>>0x2|(0x3333&bo)<<0x2))>>>0x4|(0xf0f&bo)<<0x4,bm[bn]=((0xff00&bo)>>>0x8|(0xff&bo)<<0x8)>>>0x1;}var bp=function(b4,b6,b7){for(var b8=b4['length'],b9=0x0,ba=new b5(b6);b9>>bd]=bk;}else for(bb=new b5(b8),b9=0x0;b9>>0xf-b4[b9]);return bb;},bq=new b4(0x120);for(bn=0x0;bn<0x90;++bn)bq[bn]=0x8;for(bn=0x90;bn<0x100;++bn)bq[bn]=0x9;for(bn=0x100;bn<0x118;++bn)bq[bn]=0x7;for(bn=0x118;bn<0x120;++bn)bq[bn]=0x8;var bE=new b4(0x20);for(bn=0x0;bn<0x20;++bn)bE[bn]=0x5;var bF=bp(bq,0x9,0x0),bG=bp(bE,0x5,0x0),bH=function(b4){return(b4/0x8|0x0)+(0x7&b4&&0x1);},bI=function(b7,b8,b9){(null==b8||b8<0x0)&&(b8=0x0),(null==b9||b9>b7['length'])&&(b9=b7['length']);var ba=new(b7 instanceof b5?b5:b7 instanceof b6?b6:b4)(b9-b8);return ba['set'](b7['subarray'](b8,b9)),ba;},bJ=function(b4,b5,b6){b6<<=0x7&b5;var b7=b5/0x8|0x0;b4[b7]|=b6,b4[b7+0x1]|=b6>>>0x8;},bK=function(b4,b5,b6){b6<<=0x7&b5;var b7=b5/0x8|0x0;b4[b7]|=b6,b4[b7+0x1]|=b6>>>0x8,b4[b7+0x2]|=b6>>>0x10;},bL=function(b6,b7){for(var b8=[],b9=0x0;b9bo&&(bo=bb[b9]['s']);var bp=new b5(bo+0x1),bq=bM(b8[bm-0x1],bp,0x0);if(bq>b7){b9=0x0;var bE=0x0,bF=bq-b7,bG=0x1<b7))break;bE+=bG-(0x1<>>=bF;bE>0x0;){var bI=bb[b9]['s'];bp[bI]=0x0&&bE;--b9){var bJ=bb[b9]['s'];bp[bJ]==b7&&(--bp[bJ],++bE);}bq=b7;}return[new b4(bp),bq];},bM=function b4(b5,b6,b7){return-0x1==b5['s']?Math['max'](b4(b5['l'],b6,b7+0x1),b4(b5['r'],b6,b7+0x1)):b6[b5['s']]=b7;},bN=function(b4){for(var b6=b4['length'];b6&&!b4[--b6];);for(var b7=new b5(++b6),b8=0x0,b9=b4[0x0],ba=0x1,bb=function(b4){b7[b8++]=b4;},bc=0x1;bc<=b6;++bc)if(b4[bc]==b9&&bc!=b6)++ba;else{if(!b9&&ba>0x2){for(;ba>0x8a;ba-=0x8a)bb(0x7ff2);ba>0x2&&(bb(ba>0xa?ba-0xb<<0x5|0x7012:ba-0x3<<0x5|0x3011),ba=0x0);}else if(ba>0x3){for(bb(b9),--ba;ba>0x6;ba-=0x6)bb(0x2070);ba>0x2&&(bb(ba-0x3<<0x5|0x2010),ba=0x0);}for(;ba--;)bb(b9);ba=0x1,b9=b4[bc];}return[b7['subarray'](0x0,b8),b6];},bO=function(b4,b5){for(var b6=0x0,b7=0x0;b7>>0x8,b4[b8+0x2]=0xff^b4[b8],b4[b8+0x3]=0xff^b4[b8+0x1];for(var b9=0x0;b90x4&&!dx[b9[dz-0x1]];--dz);var dA,dB,dC,dD,dE=bn+0x5<<0x3,dF=bO(bc,bq)+bO(bd,bE)+bk,dG=bO(bc,bI)+bO(bd,bR)+bk+0xe+0x3*dz+bO(bZ,dx)+(0x2*bZ[0x10]+0x3*bZ[0x11]+0x7*bZ[0x12]);if(dE<=dF&&dE<=dG)return bP(b6,bo,b4['subarray'](bm,bm+bn));if(bJ(b6,bo,0x1+(dG0xf&&(bJ(b6,bo,dK[c0]>>>0x5&0x7f),bo+=dK[c0]>>>0xc);}}}else dA=bF,dB=bq,dC=bG,dD=bE;for(c0=0x0;c00xff){dL=bb[c0]>>>0x12&0x1f;bK(b6,bo,dA[dL+0x101]),bo+=dB[dL+0x101],dL>0x7&&(bJ(b6,bo,bb[c0]>>>0x17&0x1f),bo+=b7[dL]);var dM=0x1f&bb[c0];bK(b6,bo,dC[dM]),bo+=dD[dM],dM>0x3&&(bK(b6,bo,bb[c0]>>>0x5&0x1fff),bo+=b8[dM]);}else bK(b6,bo,dA[bb[c0]]),bo+=dB[bb[c0]];return bK(b6,bo,dA[0x100]),bo+dB[0x100];},bR=new b6([0x10004,0x20008,0x20010,0x20020,0x40020,0x100080,0x100100,0x204400,0x205000]),bS=new b4(0x0),bT=function(){for(var b4=new Int32Array(0x100),b5=0x0;b5<0x100;++b5){for(var b6=b5,b7=0x9;--b7;)b6=(0x1&b6&&-0x12477ce0)^b6>>>0x1;b4[b5]=b6;}return b4;}(),bU=function(){var b4=-0x1;return{'p':function(b5){for(var b6=b4,b7=0x0;b7>>0x8;b4=b6;},'d':function(){return~b4;}};},bV=function(b9,ba,bb,bc,bk){return function(b9,ba,bb,bc,bk,bm){var bn=b9['length'],bo=new b4(bc+bn+0x5*(0x1+Math['ceil'](bn/0x1b58))+bk),bp=bo['subarray'](bc,bo['length']-bk),bq=0x0;if(!ba||bn<0x8)for(var bE=0x0;bE<=bn;bE+=0xffff){var bF=bE+0xffff;bF>>0xd,bK=0x1fff&bG,bL=(0x1<0x1b58||c0>0x6000)&&ex>0x1a7){bq=bQ(b9,bp,0x0,bV,bW,bX,bZ,c0,es,bE-es,bq),c0=bY=bZ=0x0,es=bE;for(var ey=0x0;ey<0x11e;++ey)bW[ey]=0x0;for(ey=0x0;ey<0x1e;++ey)bX[ey]=0x0;}var ez=0x2,eA=0x0,eB=bK,eC=ev-ew&0x7fff;if(ex>0x2&&eu==bU(bE-eC))for(var eD=Math['min'](bJ,ex)-0x1,eE=Math['min'](0x7fff,bE),eF=Math['min'](0x102,ex);eC<=eE&&--eB&&ev!=ew;){if(b9[bE+ez]==b9[bE+ez-eC]){for(var eG=0x0;eGez){if(ez=eG,eA=eC,eG>eD)break;var eH=Math['min'](eC,eG-0x2),eI=0x0;for(ey=0x0;eyeI&&(eI=eK,ew=eJ);}}}eC+=(ev=ew)-(ew=bM[ev])+0x8000&0x7fff;}if(eA){bV[c0++]=0x10000000|bd[ez]<<0x12|bl[eA];var eL=0x1f&bd[ez],eM=0x1f&bl[eA];bZ+=b7[eL]+b8[eM],++bW[0x101+eL],++bX[eM],er=bE+ez,++bY;}else bV[c0++]=b9[bE],++bW[b9[bE]];}}bq=bQ(b9,bp,bm,bV,bW,bX,bZ,c0,es,bE-es,bq),!bm&&0x7&bq&&(bq=bP(bp,bq+0x1,bS));}return bI(bo,0x0,bc+bH(bq)+bk);}(b9,null==ba['level']?0x6:ba['level'],null==ba['mem']?Math['ceil'](1.5*Math['max'](0x8,Math['min'](0xd,Math['log'](b9['length'])))):0xc+ba['mem'],bb,bc,!bk);},bW=function(b4,b5,b6){for(;b6;++b5)b4[b5]=b6,b6>>>=0x8;},bX=function(b4,b5){var b6=b5['filename'];if(b4[0x0]=0x1f,b4[0x1]=0x8b,b4[0x2]=0x8,b4[0x8]=b5['level']<0x2?0x4:0x9==b5['level']?0x2:0x0,b4[0x9]=0x3,0x0!=b5['mtime']&&bW(b4,0x4,Math['floor'](new Date(b5['mtime']||Date['now']())/0x3e8)),b6){b4[0x3]=0x8;for(var b7=0x0;b7<=b6['length'];++b7)b4[b7+0xa]=b6['charCodeAt'](b7);}},bY=function(b4){return 0xa+(b4['filename']&&b4['filename']['length']+0x1||0x0);},bZ='undefined'!=typeof TextEncoder&&new TextEncoder(),c0='undefined'!=typeof TextDecoder&&new TextDecoder();try{c0['decode'](bS,{'stream':!0x0}),0x1;}catch(eV){}return{'gzipSync':function(b4,b5){b5||(b5={});var b6=bU(),b7=b4['length'];b6['p'](b4);var b8=bV(b4,b5,bY(b5),0x8),b9=b8['length'];return bX(b8,b5),bW(b8,b9-0x8,b6['d']()),bW(b8,b9-0x4,b7),b8;},'strToU8':function(b5,b6){if(b6){for(var b7=new b4(b5['length']),b8=0x0;b8>0x1)),bb=0x0,bc=function(b4){ba[bb++]=b4;};for(b8=0x0;b8ba['length']){var bd=new b4(bb+0x8+(b9-b8<<0x1));bd['set'](ba),ba=bd;}var bk=b5['charCodeAt'](b8);bk<0x80||b6?bc(bk):bk<0x800?(bc(0xc0|bk>>0x6),bc(0x80|0x3f&bk)):bk>0xd7ff&&bk<0xe000?(bc(0xf0|(bk=0x10000+(0xffc00&bk)|0x3ff&b5['charCodeAt'](++b8))>>0x12),bc(0x80|bk>>0xc&0x3f),bc(0x80|bk>>0x6&0x3f),bc(0x80|0x3f&bk)):(bc(0xe0|bk>>0xc),bc(0x80|bk>>0x6&0x3f),bc(0x80|0x3f&bk));}return bI(ba,0x0,bb);}};}return!0x1;}var fd=b3();function fe(){var ff,fg,fh,fi;return fi=V['root']['navigator']['userAgent'],V['root']['ActiveXObject']&&(fg=fi['indexOf']('MSIE\x20'))>0x0?parseInt(fi['substring'](fg+0x5,fi['indexOf']('.',fg)),0xa):fi['indexOf']('Trident/')>0x0?(fh=fi['indexOf']('rv:'),parseInt(fi['substring'](fh+0x3,fi['indexOf']('.',fh)),0xa)):(ff=fi['indexOf']('Edge/'))>0x0&&parseInt(fi['substring'](ff+0x5,fi['indexOf']('.',ff)),0xa);}function fj(fk,fl,fm){var fn,fo,fp,fq,fr,fs,ft,fu=[],fv=[],fw=0x0,fx=fe()||NaN;function fy(fk,fl){var fm;return-0x1!==(fm=function(fk,fl){var fm,fn;if(null!=Array['prototype']['indexOf'])return fk['indexOf'](fl);for(fn=fk['length'],fm=-0x1;++fmfr?fs=!0x1:(fs=!0x0,fp=navigator['userAgent'],'ArrayBufferView'in V['root']||(-0x1!==fp['indexOf']('Android')?fs=!0x1:-0x1!==fp['indexOf']('CPU\x20OS\x20')?(-0x1!==fp['indexOf']('CPU\x20OS\x206_')||-0x1!==fp['indexOf']('CPU\x20OS\x205_')||-0x1!==fp['indexOf']('CPU\x20OS\x204_')||-0x1!==fp['indexOf']('CPU\x20OS\x203_'))&&(fs=!0x1):-0x1!==fp['indexOf']('CPU\x20iPhone\x20OS\x20')?(-0x1!==fp['indexOf']('iPhone\x20OS\x206_')||-0x1!==fp['indexOf']('iPhone\x20OS\x205_')||-0x1!==fp['indexOf']('iPhone\x20OS\x204_'))&&(fs=!0x1):-0x1!==fp['indexOf']('Intel\x20Mac\x20OS\x20X')&&(-0x1!==fp['indexOf']('Mac\x20OS\x20X\x2010_6')||-0x1!==fp['indexOf']('Mac\x20OS\x20X\x2010_7'))&&(fs=!0x1))),fu=[],fv=[],ft='undefined'==typeof Uint8Array||null===Uint8Array?V['root']['XMLHttpRequest']&&0x7!==fx?af['dMsN'](aP['stringify'](fl)):af['zvqA'](aP['stringify'](fl)):fd['gzipSync'](fd['strToU8'](aP['stringify'](fl)),{'level':0x6,'mem':0x8}),fp=fk+(fk['indexOf']('?')<0x0?'?':'&')+'dt=4',V['root']['laWaitTime']=new Date()['valueOf']()+0x12c,V['root']['XMLHttpRequest']&&0x7!==fx?'withCredentials'in(fn=new XMLHttpRequest())?(fn['open']('POST',fp,!0x0),fn['withCredentials']=!0x0,fn['onreadystatechange']=function(){return 0x4===fn['readyState']&&(V['root']['laWaitTime']=new Date()['valueOf']()+0xa)&&function(fk){var fl;return-0x1!==(fl=fu['indexOf'](fk))?fu['splice'](fl,0x1):void 0x0;}(fn)&&0xc8===fn['status'];},fn['send']('undefined'!=typeof ArrayBuffer&&null!==ArrayBuffer?fs?ft:ft['buffer']:ft),fu['push'](fn)):'undefined'!=typeof XDomainRequest&&(fn=new XDomainRequest(),'http:'===document['location']['protocol']&&(fp=fk+(fk['indexOf']('?')<0x0?'?':'&')+'dt=1'),fn['open']('POST',fp),fn['onload']=function(){return fy(fn,fm);},fn['onerror']=function(fk){return fy(fn,fm);},fn['onprogress']=function(){return{};},fn['ontimeout']=function(){return{};},fn['send'](ft),fv['push'](fn)):('http:'===V['root']['location']['protocol']&&(fo=fk['replace']('https://','http://')+(fk['indexOf']('?')<0x0?'?':'&')+'dt=2&data='['concat'](ft)),fo['length']<=0x7f4&&function(fk,fl){var fm;return(fm=document['createElement']('img'))['width']=0x1,fm['height']=0x1,fm['onload']=function(){return fG(fm,fl);},fm['onerror']=fm['onabort']=function(){return fG(fm,fl);},fm['src']=fk,fv['push'](fm);}(fo,fm));}var fP={'report':fj},fQ={'get':function(fU){return decodeURIComponent(V['doc']['cookie']['replace'](new RegExp('(?:(?:^|.*;)\x5cs*'+encodeURIComponent(fU)['replace'](/[-.+*]/g,'\x5c$&')+'\x5cs*\x5c=s*([^;]*).*$)|^.*$'),'$1'))||null;},'set':function(fV,fW,fX,fY,fZ,g0){if(!fV||/^(?:expires|max-age|path|domain|secure)$/i['test'](fV))return!0x1;var g1='';if(fX)switch(fX['constructor']){case Number:g1=fX===0x1/0x0?';\x20expires=Fri,\x2031\x20Dec\x209999\x2023:59:59\x20GMT':';\x20max-age='+fX;break;case String:g1=';\x20expires='+fX;break;case Date:g1=';\x20expires='+fX['toUTCString']();}return V['doc']['cookie']=encodeURIComponent(fV)+'='+encodeURIComponent(fW)+g1+(fZ?';\x20domain='+fZ:'')+(fY?';\x20path='+fY:'')+(g0?';\x20secure':''),!0x0;}},fR={'get':function(g2){return aP['parse']((ae['isMobi']?V['root']['localStorage']['getItem'](g2):fQ['get'](g2))||'{}');},'set':function(g3,g4,g5,g6){return ae['isMobi']?V['root']['localStorage']['setItem'](g3,g4):fQ['set'](g3,g4,g5,g6);}},fS={'cookie':fQ,'store':fR},fT=V['doc']['addEventListener']?function(g7,g8,g9){g7['addEventListener'](g8,g9,!0x0);}:V['doc']['attachEvent']?function(ga,gb,gc){ga['attachEvent']('on'+gb,gc);}:function(gd,ge,gf){gd['on'+ge]=gf;};function gg(gh,gi,gj){for(var gk=gi['split']('\x20'),gl=0x0,gm=gk['length'];gl0x1&&void 0x0!==arguments[0x1]?arguments[0x1]:0x0,gv=(gq[gt[gu+0x0]]+gq[gt[gu+0x1]]+gq[gt[gu+0x2]]+gq[gt[gu+0x3]]+'-'+gq[gt[gu+0x4]]+gq[gt[gu+0x5]]+'-'+gq[gt[gu+0x6]]+gq[gt[gu+0x7]]+'-'+gq[gt[gu+0x8]]+gq[gt[gu+0x9]]+'-'+gq[gt[gu+0xa]]+gq[gt[gu+0xb]]+gq[gt[gu+0xc]]+gq[gt[gu+0xd]]+gq[gt[gu+0xe]]+gq[gt[gu+0xf]])['toLowerCase']();if(!go(gv))throw TypeError('Stringified\x20UUID\x20is\x20invalid');return gv;}function gw(gx){if(!go(gx))throw TypeError('Invalid\x20UUID');var gy,gz=new Uint8Array(0x10);return gz[0x0]=(gy=parseInt(gx['slice'](0x0,0x8),0x10))>>>0x18,gz[0x1]=gy>>>0x10&0xff,gz[0x2]=gy>>>0x8&0xff,gz[0x3]=0xff&gy,gz[0x4]=(gy=parseInt(gx['slice'](0x9,0xd),0x10))>>>0x8,gz[0x5]=0xff&gy,gz[0x6]=(gy=parseInt(gx['slice'](0xe,0x12),0x10))>>>0x8,gz[0x7]=0xff&gy,gz[0x8]=(gy=parseInt(gx['slice'](0x13,0x17),0x10))>>>0x8,gz[0x9]=0xff&gy,gz[0xa]=(gy=parseInt(gx['slice'](0x18,0x24),0x10))/0x10000000000&0xff,gz[0xb]=gy/0x100000000&0xff,gz[0xc]=gy>>>0x18&0xff,gz[0xd]=gy>>>0x10&0xff,gz[0xe]=gy>>>0x8&0xff,gz[0xf]=0xff&gy,gz;}function gA(gB){gB=unescape(encodeURIComponent(gB));for(var gC=[],gD=0x0;gD>>0x20-gZ;}function h0(h1){var h2=[0x5a827999,0x6ed9eba1,0x8f1bbcdc,0xca62c1d6],h3=[0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0];if('string'==typeof h1){var h4=unescape(encodeURIComponent(h1));h1=[];for(var h5=0x0;h5>>0x0;hk=hj,hj=hi,hi=gX(hh,0x1e)>>>0x0,hh=hg,hg=hn;}h3[0x0]=h3[0x0]+hg>>>0x0,h3[0x1]=h3[0x1]+hh>>>0x0,h3[0x2]=h3[0x2]+hi>>>0x0,h3[0x3]=h3[0x3]+hj>>>0x0,h3[0x4]=h3[0x4]+hk>>>0x0;}return[h3[0x0]>>0x18&0xff,h3[0x0]>>0x10&0xff,h3[0x0]>>0x8&0xff,0xff&h3[0x0],h3[0x1]>>0x18&0xff,h3[0x1]>>0x10&0xff,h3[0x1]>>0x8&0xff,0xff&h3[0x1],h3[0x2]>>0x18&0xff,h3[0x2]>>0x10&0xff,h3[0x2]>>0x8&0xff,0xff&h3[0x2],h3[0x3]>>0x18&0xff,h3[0x3]>>0x10&0xff,h3[0x3]>>0x8&0xff,0xff&h3[0x3],h3[0x4]>>0x18&0xff,h3[0x4]>>0x10&0xff,h3[0x4]>>0x8&0xff,0xff&h3[0x4]];}var ho=gG('v5',0x50,h0),hp=fS['store'];function hq(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'['replace'](/[xy]/g,function(hr){var hs=0x10*Math['random']()|0x0;return('x'===hr?hs:0x3&hs|0x8)['toString'](0x10);})['toUpperCase']();}function ht(){return'placeholder'in document['createElement']('input');}function hu(){var hv=navigator['userAgent'];return!!(hv['indexOf']('compatible')>-0x1&&hv['indexOf']('MSIE')>-0x1)&&(new RegExp('MSIE\x20(\x5cd+\x5c.\x5cd+);')['test'](hv),parseFloat(RegExp['$1'])<0xa||!ht()||void 0x0);}function hw(){return'undefined'==typeof Uint8Array||null===Uint8Array;}function hx(hy){var hz=hy['connection']||hy['mozConnection']||hy['webkitConnection']||hy['oConnection'];return V['xdaPuS'](hy['userAgent'],'mobile')&&hz?hz['type']:'unknow';}function hA(hB,hC){var hD=new Date(new Date(new Date(new Date(new Date()['getTime']())['setHours'](0x0,0x0,0x0,0x0)))['getTime']()+0x5265c00-0x1)['getTime']();return hD-hB<=0x1b7740?hD:hB+hC;}function hE(hF,hG,hH){var hI,hJ=hA(hF,hG['SessionTimeout']),gr=hG['ck'];try{(hI=hp['get']('__vtins__'+gr))&&V['SkKBjD'](hI['expires'])&&hI['expires']hF?0x0:0x1,hN=hM?0x1:hI['vd']+0x1,hO=hM?iE['xnFNCY']()||iE['noUint8Array']()?iE['KhuHSO']():ho('\x0a\x20\x20\x20\x20'['concat'](gr,'\x0a\x20\x20\x20\x20')['concat'](hH['userAgent'],'\x0a\x20\x20\x20\x20')['concat'](hF,'\x0a\x20\x20\x20\x20')['concat'](Math['random']()['toString'](0x24)['substr'](-0x8),'\x0a\x20\x20'),iE['KhuHSO']()):hI['sid'],hP=hJ,gS=V['SkKBjD'](hI['ct'])&&parseInt(hF)-parseInt(hI['ct'])>0x0?parseInt(hF)-parseInt(hI['ct']):0x0,hR=V['SkKBjD'](hI['stt'])?parseInt(hI['stt'])+gS:gS;hp['set']('__vtins__'+gr,aP['stringify']({'sid':hO,'vd':hN,'stt':hR,'dr':gS,'expires':hP,'ct':hF}),null,'/');var hS=fS['cookie']['get']('__51uvsct__'+gr);return!V['SkKBjD'](parseInt(hS))||isNaN(parseInt(hS))?(fS['cookie']['set']('__51uvsct__'+gr,0x1,0x1/0x0,'/'),hS=0x1):0x0==gS&&(hS=parseInt(hS)+0x1,fS['cookie']['set']('__51uvsct__'+gr,hS,0x1/0x0,'/')),[hM,hM?hO:hp['get']('__vtins__'+gr)['sid'],hN,hR,gS,hS];}function hT(hU){return hU['language']||hU['browserLanguage'];}function hV(hW){var hX,hY,hZ='';return hX=V['root']['history']['pushState'],hY=V['root']['history']['replaceState'],null!=hX&&(V['root']['history']['pushState']=function(hY){return function(){return hY['prevUrl']=V['root']['location']['toString'](),hX['apply'](V['root']['history'],arguments),setTimeout(function(){return hW();},0x0);};}(this)),null!=hY&&(V['root']['history']['replaceState']=function(){return hZ=V['root']['location']['toString'](),hY['apply'](V['root']['history'],arguments),setTimeout(function(){return hW();},0x0);}),null!=hX&&(hZ=ae['jcSWhb'](),'function'==typeof Object['defineProperty']&&Object['defineProperty'](V['doc'],'referrer',{'get':function(){return hZ;},'configurable':!0x0}),gg(V['root'],'popstate',hW)),gg(V['root'],'hashchange',hW);}function i1(i2){if(null==i2)throw new TypeError('Cannot\x20convert\x20undefined\x20or\x20null\x20to\x20object');var i3=[];for(var i4 in i2)i2['hasOwnProperty'](i4)&&i3['push'](i4);return i3;}function i5(i6){return'[object\x20Array]'==Object['prototype']['toString']['call'](i6);}function i7(i8,i9){if(i8['length']&&i5(i8)&&'function'==typeof i9){for(var ia=[],ib=0x0;ib-0x1&&ih[ii]&&ih[ii](ig[ii]))return ij['push'](ii),!0x0;}),gr=0x0;gr-0x1))return null;var iu={};if(-0x1!=(it='?'['concat'](is['split']('?')[0x1]))['indexOf']('?'))for(var iv=it['substr'](0x1)['split']('&'),iw=0x0;iw=0x0){iP=!iV(iU);break;}}return iP?!iQ&&/^http[s]?/['test'](iO)?''['concat'](iO['replace'](/\/$/,''),'/js-sdk-event.min.js?u=')['concat'](iN):!iQ&&/^\/\//['test'](iO)&&'//'!==iO?''['concat']('https:'===self['location']['protocol']?'https:':'http:')['concat'](iO['replace'](/\/$/,''),'/js-sdk-event.min.js?u=')['concat'](iN):!iQ&&iO?'/'['concat'](iO['replace'](/\/$/,'')['replace'](/^\//,''),'/js-sdk-event.min.js?u=')['concat'](iN):'/js-sdk-event.min.js?u='['concat'](iN):('https:'===self['location']['protocol']?'https:':'http:')+'//sdk.51.la/event/js-sdk-event.min.js?u='['concat'](iN);}function iV(iW){return!!iW&&iW['indexOf']('sdk.51.la')>=0x0;}function iX(iY,iZ,j0){var j1=iM(iY,iZ?decodeURIComponent(iZ):''),j2=document['createElement']('script');j2['setAttribute']('id','LA_CODELESS'),j2['setAttribute']('src',j1),j2['setAttribute']('data-LA-ev',iY),j2['setAttribute']('crossorigin','anonymous'),j2['setAttribute']('charset','UTF-8'),document['getElementsByTagName']('head')[0x0]['appendChild'](j2),j2['readyState']?j2['onreadystatechange']=function(){('complete'==j2['readyState']||'loaded'==j2['readyState'])&&j0&&j0();}:j2['onload']=function(){j0&&j0();};}function j3(){var j4=arguments['length']>0x0&&void 0x0!==arguments[0x0]?arguments[0x0]:{};'{}'===aP['stringify'](j4)?V['root']['LA']&&(j4=V['root']['LA']['config']?V['extend'](j4,V['root']['LA']['config']):V['extend'](j4,V['root']['LA']['_config'])):V['root']['LA']['config']=j4;var j5=iE['YCrNdE'](j4,{'hashMode':function(j4){return'boolean'==typeof j4;},'SessionTimeout':function(j4){return!!(V['SkKBjD'](j4)&&j4>=0x1b7740&&j4<=0x6ddd00);},'autoTrack':function(j4){return'boolean'==typeof j4;},'prefix':function(j4){return'/'===j4?'':j4;}}),j6=V['root']['location'],j7=V['extend']({'url':('https:'===j6['protocol']?'https:':'http:')+'//collect-v6.51.la/v6/collect','id':j4['id'],'ck':j4['ck']||j4['id'],'hashMode':!0x1,'autoTrack':!0x1,'SessionTimeout':0x1b7740,'prefix':''},j5),j8=V['now'](),gr=V['root']['navigator'],ja=iE['AczpQq'](j8,j7,gr),jb=V['root']['screen'],jc=iE['xnFNCY']()||iE['noUint8Array']()?iE['KhuHSO']():ho('\x0a\x20\x20\x20\x20'['concat'](j7['id'],'\x0a\x20\x20\x20\x20')['concat'](gr['userAgent'],'\x0a\x20\x20\x20\x20')['concat'](j8,'\x0a\x20\x20\x20\x20')['concat'](ae['jcSWhb'](),'\x0a\x20\x20\x20\x20')['concat'](jb['width']+'*'+jb['height'],'\x0a\x20\x20\x20\x20')['concat'](ja[0x1],'\x0a\x20\x20\x20\x20')['concat'](iE['hJdYQm'](gr),'\x0a\x20\x20\x20\x20')['concat'](Math['random']()['toString'](0x24)['substr'](-0x8),'\x0a\x20\x20'),iE['KhuHSO']()),jd=j6['href'],gS={'id':j7['id'],'rt':j8,'tt':ae['ZNMTWj']['tt'],'kw':ae['ZNMTWj']['kw'],'ds':ae['ZNMTWj']['ds'],'sid':ja[0x1],'cu':j6['href'],'pu':ae['jcSWhb'](),'rl':jb['width']+'*'+jb['height'],'lang':iE['hKJlvN'](gr),'ct':iE['hJdYQm'](gr),'svd':ja[0x2],'ce':gr['cookieEnabled']?0x1:0x0,'cd':jb['colorDepth']||jb['pixelDepth'],'stt':ja[0x3],'dr':ja[0x4],'uvsc':ja[0x5]},jf=iF(V['extend'](j7,gS),jc);gS['uid']=jf['uid'],gS['uft']=jf['uft'];var jk=iE['getQueryStringInURL']('LA_RM_TK')||'',jl=V['checkChormeMoblie'](0x2d);!jk&&jl&&fP['report'](j7['url'],gS,gS['uid']),j7['autoTrack']||!V['root']['LA']||V['root']['LA']['track']||(V['root']['LA']['track']=function(){V['root']['console']&&V['root']['console']['warn']&&console['warn']('自2022年5月1日起,51.LA事件分析功能将调整为默认不开启,如您需要继续使用,可查看配置教程进行开启->https://support.qq.com/products/400900/faqs/117264');}),j7['hashMode']&&iE['DvIuqD'](function(){if(jd!==V['root']['location']['href']){jd=V['root']['location']['href'];var j4=V['now'](),j5=iE['AczpQq'](j4,j7,gr),j6=ae['getMeta1'](),j8=(gS=V['extend'](gS,{'id':j7['id'],'pu':gS['cu'],'rt':j4,'tt':j6['tt'],'kw':j6['kw'],'ds':j6['ds'],'sid':j5[0x1],'svd':j5[0x2],'stt':j5[0x3],'dr':j5[0x4],'uvsc':j5[0x5],'cu':V['root']['location']['href']}))['uid']=iF(V['extend'](j7,gS),jc);gS['uid']=j8['uid'],gS['uft']=j8['uft'],jl&&fP['report'](j7['url'],gS,gS['uid']);}}),j7['autoTrack']&&jl&&iX(gS['id'],j7['prefix']);}V['root']['LA']&&V['root']['LA']['config']&&j3(),V['root']['LA']&&V['root']['LA']['ids']&&V['pWExzw'](V['root']['LA']['ids'])&&V['root']['LA']['ids']['length']>0x0&&(V['root']['LA']['_config']=V['root']['LA']['ids']['shift'](),j3());try{var jq=V['doc']['getElementById']('LA_COLLECT');if(jq){var jr=jq&&jq['getAttribute']('src')||'';if(jr){var js=iE['GetQueryString'](jr);js&&j3(js);}}}catch(jt){}c['init']=j3;}(this['LA']=this['LA']||{})); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/public/router.php b/public/router.php new file mode 100644 index 0000000..8fb3b2e --- /dev/null +++ b/public/router.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { + return false; +} else { + // 检查是否访问后台路径 + if (isset($_SERVER['REQUEST_URI']) && preg_match('#^/admin(/.*)?$#', $_SERVER['REQUEST_URI'])) { + // 将请求重定向到后台入口 + $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; + $_SERVER["PATH_INFO"] = '/admin' . (isset($_SERVER["PATH_INFO"]) ? $_SERVER["PATH_INFO"] : ''); + $_SERVER["REQUEST_URI"] = '/index.php/admin' . (isset($_SERVER["PATH_INFO"]) ? $_SERVER["PATH_INFO"] : ''); + } else { + // 前端入口 + $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; + } + + require __DIR__ . "/index.php"; +} diff --git a/public/static/css/bootstrap.min.css b/public/static/css/bootstrap.min.css new file mode 100644 index 0000000..2f10cde --- /dev/null +++ b/public/static/css/bootstrap.min.css @@ -0,0 +1,13866 @@ +@charset "UTF-8"; + +/*! + * Bootstrap v5.3.6 (https://getbootstrap.com/) + * Copyright 2011-2025 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +:root, +[data-bs-theme=light] { + --bs-blue: #0d6efd; + --bs-indigo: #6610f2; + --bs-purple: #6f42c1; + --bs-pink: #d63384; + --bs-red: #dc3545; + --bs-orange: #fd7e14; + --bs-yellow: #ffc107; + --bs-green: #198754; + --bs-teal: #20c997; + --bs-cyan: #0dcaf0; + --bs-black: #000; + --bs-white: #fff; + --bs-gray: #6c757d; + --bs-gray-dark: #343a40; + --bs-gray-100: #f8f9fa; + --bs-gray-200: #e9ecef; + --bs-gray-300: #dee2e6; + --bs-gray-400: #ced4da; + --bs-gray-500: #adb5bd; + --bs-gray-600: #6c757d; + --bs-gray-700: #495057; + --bs-gray-800: #343a40; + --bs-gray-900: #212529; + --bs-primary: #0d6efd; + --bs-secondary: #6c757d; + --bs-success: #198754; + --bs-info: #0dcaf0; + --bs-warning: #ffc107; + --bs-danger: #dc3545; + --bs-light: #f8f9fa; + --bs-dark: #212529; + --bs-primary-rgb: 13, 110, 253; + --bs-secondary-rgb: 108, 117, 125; + --bs-success-rgb: 25, 135, 84; + --bs-info-rgb: 13, 202, 240; + --bs-warning-rgb: 255, 193, 7; + --bs-danger-rgb: 220, 53, 69; + --bs-light-rgb: 248, 249, 250; + --bs-dark-rgb: 33, 37, 41; + --bs-primary-text-emphasis: #052c65; + --bs-secondary-text-emphasis: #2b2f32; + --bs-success-text-emphasis: #0a3622; + --bs-info-text-emphasis: #055160; + --bs-warning-text-emphasis: #664d03; + --bs-danger-text-emphasis: #58151c; + --bs-light-text-emphasis: #495057; + --bs-dark-text-emphasis: #495057; + --bs-primary-bg-subtle: #cfe2ff; + --bs-secondary-bg-subtle: #e2e3e5; + --bs-success-bg-subtle: #d1e7dd; + --bs-info-bg-subtle: #cff4fc; + --bs-warning-bg-subtle: #fff3cd; + --bs-danger-bg-subtle: #f8d7da; + --bs-light-bg-subtle: #fcfcfd; + --bs-dark-bg-subtle: #ced4da; + --bs-primary-border-subtle: #9ec5fe; + --bs-secondary-border-subtle: #c4c8cb; + --bs-success-border-subtle: #a3cfbb; + --bs-info-border-subtle: #9eeaf9; + --bs-warning-border-subtle: #ffe69c; + --bs-danger-border-subtle: #f1aeb5; + --bs-light-border-subtle: #e9ecef; + --bs-dark-border-subtle: #adb5bd; + --bs-white-rgb: 255, 255, 255; + --bs-black-rgb: 0, 0, 0; + --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); + --bs-body-font-family: var(--bs-font-sans-serif); + --bs-body-font-size: 1rem; + --bs-body-font-weight: 400; + --bs-body-line-height: 1.5; + --bs-body-color: #212529; + --bs-body-color-rgb: 33, 37, 41; + /* --bs-body-bg: #fff; */ + --bs-body-bg:#f4f6f9; + --bs-body-bg-rgb: 255, 255, 255; + --bs-emphasis-color: #000; + --bs-emphasis-color-rgb: 0, 0, 0; + --bs-secondary-color: rgba(33, 37, 41, 0.75); + --bs-secondary-color-rgb: 33, 37, 41; + --bs-secondary-bg: #e9ecef; + --bs-secondary-bg-rgb: 233, 236, 239; + --bs-tertiary-color: rgba(33, 37, 41, 0.5); + --bs-tertiary-color-rgb: 33, 37, 41; + --bs-tertiary-bg: #f8f9fa; + --bs-tertiary-bg-rgb: 248, 249, 250; + --bs-heading-color: inherit; + --bs-link-color: #0d6efd; + --bs-link-color-rgb: 13, 110, 253; + --bs-link-decoration: underline; + --bs-link-hover-color: #0a58ca; + --bs-link-hover-color-rgb: 10, 88, 202; + --bs-code-color: #d63384; + --bs-highlight-color: #212529; + --bs-highlight-bg: #fff3cd; + --bs-border-width: 1px; + --bs-border-style: solid; + --bs-border-color: #dee2e6; + --bs-border-color-translucent: rgba(0, 0, 0, 0.175); + --bs-border-radius: 0.375rem; + --bs-border-radius-sm: 0.25rem; + --bs-border-radius-lg: 0.5rem; + --bs-border-radius-xl: 1rem; + --bs-border-radius-xxl: 2rem; + --bs-border-radius-2xl: var(--bs-border-radius-xxl); + --bs-border-radius-pill: 50rem; + --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); + --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); + --bs-focus-ring-width: 0.25rem; + --bs-focus-ring-opacity: 0.25; + --bs-focus-ring-color: rgba(13, 110, 253, 0.25); + --bs-form-valid-color: #198754; + --bs-form-valid-border-color: #198754; + --bs-form-invalid-color: #dc3545; + --bs-form-invalid-border-color: #dc3545 +} + +[data-bs-theme=dark] { + color-scheme: dark; + --bs-body-color: #dee2e6; + --bs-body-color-rgb: 222, 226, 230; + --bs-body-bg: #212529; + --bs-body-bg-rgb: 33, 37, 41; + --bs-emphasis-color: #fff; + --bs-emphasis-color-rgb: 255, 255, 255; + --bs-secondary-color: rgba(222, 226, 230, 0.75); + --bs-secondary-color-rgb: 222, 226, 230; + --bs-secondary-bg: #343a40; + --bs-secondary-bg-rgb: 52, 58, 64; + --bs-tertiary-color: rgba(222, 226, 230, 0.5); + --bs-tertiary-color-rgb: 222, 226, 230; + --bs-tertiary-bg: #2b3035; + --bs-tertiary-bg-rgb: 43, 48, 53; + --bs-primary-text-emphasis: #6ea8fe; + --bs-secondary-text-emphasis: #a7acb1; + --bs-success-text-emphasis: #75b798; + --bs-info-text-emphasis: #6edff6; + --bs-warning-text-emphasis: #ffda6a; + --bs-danger-text-emphasis: #ea868f; + --bs-light-text-emphasis: #f8f9fa; + --bs-dark-text-emphasis: #dee2e6; + --bs-primary-bg-subtle: #031633; + --bs-secondary-bg-subtle: #161719; + --bs-success-bg-subtle: #051b11; + --bs-info-bg-subtle: #032830; + --bs-warning-bg-subtle: #332701; + --bs-danger-bg-subtle: #2c0b0e; + --bs-light-bg-subtle: #343a40; + --bs-dark-bg-subtle: #1a1d20; + --bs-primary-border-subtle: #084298; + --bs-secondary-border-subtle: #41464b; + --bs-success-border-subtle: #0f5132; + --bs-info-border-subtle: #087990; + --bs-warning-border-subtle: #997404; + --bs-danger-border-subtle: #842029; + --bs-light-border-subtle: #495057; + --bs-dark-border-subtle: #343a40; + --bs-heading-color: inherit; + --bs-link-color: #6ea8fe; + --bs-link-hover-color: #8bb9fe; + --bs-link-color-rgb: 110, 168, 254; + --bs-link-hover-color-rgb: 139, 185, 254; + --bs-code-color: #e685b5; + --bs-highlight-color: #dee2e6; + --bs-highlight-bg: #664d03; + --bs-border-color: #495057; + --bs-border-color-translucent: rgba(255, 255, 255, 0.15); + --bs-form-valid-color: #75b798; + --bs-form-valid-border-color: #75b798; + --bs-form-invalid-color: #ea868f; + --bs-form-invalid-border-color: #ea868f +} + +*, +::after, +::before { + box-sizing: border-box +} + +@media (prefers-reduced-motion:no-preference) { + :root { + scroll-behavior: smooth + } +} + +body { + margin: 0; + font-family: var(--bs-body-font-family); + font-size: var(--bs-body-font-size); + font-weight: var(--bs-body-font-weight); + line-height: var(--bs-body-line-height); + color: var(--bs-body-color); + text-align: var(--bs-body-text-align); + background-color: var(--bs-body-bg); + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent +} + +hr { + margin: 1rem 0; + color: inherit; + border: 0; + border-top: var(--bs-border-width) solid; + opacity: .25 +} + +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: .5rem; + font-weight: 500; + line-height: 1.2; + color: var(--bs-heading-color) +} + +.h1, +h1 { + font-size: calc(1.375rem + 1.5vw) +} + +@media (min-width:1200px) { + + .h1, + h1 { + font-size: 2.5rem + } +} + +.h2, +h2 { + font-size: calc(1.325rem + .9vw) +} + +@media (min-width:1200px) { + + .h2, + h2 { + font-size: 2rem + } +} + +.h3, +h3 { + font-size: calc(1.3rem + .6vw) +} + +@media (min-width:1200px) { + + .h3, + h3 { + font-size: 1.75rem + } +} + +.h4, +h4 { + font-size: calc(1.275rem + .3vw) +} + +@media (min-width:1200px) { + + .h4, + h4 { + font-size: 1.5rem + } +} + +.h5, +h5 { + font-size: 1.25rem +} + +.h6, +h6 { + font-size: 1rem +} + +p { + margin-top: 0; + margin-bottom: 1rem +} + +abbr[title] { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit +} + +ol, +ul { + padding-left: 2rem +} + +dl, +ol, +ul { + margin-top: 0; + margin-bottom: 1rem +} + +ol ol, +ol ul, +ul ol, +ul ul { + margin-bottom: 0 +} + +dt { + font-weight: 700 +} + +dd { + margin-bottom: .5rem; + margin-left: 0 +} + +blockquote { + margin: 0 0 1rem +} + +b, +strong { + font-weight: bolder +} + +.small, +small { + font-size: .875em +} + +.mark, +mark { + padding: .1875em; + color: var(--bs-highlight-color); + background-color: var(--bs-highlight-bg) +} + +sub, +sup { + position: relative; + font-size: .75em; + line-height: 0; + vertical-align: baseline +} + +sub { + bottom: -.25em +} + +sup { + top: -.5em +} + +a { + color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); + text-decoration: underline +} + +a:hover { + --bs-link-color-rgb: var(--bs-link-hover-color-rgb) +} + +a:not([href]):not([class]), +a:not([href]):not([class]):hover { + color: inherit; + text-decoration: none +} + +code, +kbd, +pre, +samp { + font-family: var(--bs-font-monospace); + font-size: 1em +} + +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + font-size: .875em +} + +pre code { + font-size: inherit; + color: inherit; + word-break: normal +} + +code { + font-size: .875em; + color: var(--bs-code-color); + word-wrap: break-word +} + +a>code { + color: inherit +} + +kbd { + padding: .1875rem .375rem; + font-size: .875em; + color: var(--bs-body-bg); + background-color: var(--bs-body-color); + border-radius: .25rem +} + +kbd kbd { + padding: 0; + font-size: 1em +} + +figure { + margin: 0 0 1rem +} + +img, +svg { + vertical-align: middle +} + +table { + caption-side: bottom; + border-collapse: collapse +} + +caption { + padding-top: .5rem; + padding-bottom: .5rem; + color: var(--bs-secondary-color); + text-align: left +} + +th { + text-align: inherit; + text-align: -webkit-match-parent +} + +tbody, +td, +tfoot, +th, +thead, +tr { + border-color: inherit; + border-style: solid; + border-width: 0 +} + +label { + display: inline-block +} + +button { + border-radius: 0 +} + +button:focus:not(:focus-visible) { + outline: 0 +} + +button, +input, +optgroup, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit +} + +button, +select { + text-transform: none +} + +[role=button] { + cursor: pointer +} + +select { + word-wrap: normal +} + +select:disabled { + opacity: 1 +} + +[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { + display: none !important +} + +[type=button], +[type=reset], +[type=submit], +button { + -webkit-appearance: button +} + +[type=button]:not(:disabled), +[type=reset]:not(:disabled), +[type=submit]:not(:disabled), +button:not(:disabled) { + cursor: pointer +} + +::-moz-focus-inner { + padding: 0; + border-style: none +} + +textarea { + resize: vertical +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0 +} + +legend { + float: left; + width: 100%; + padding: 0; + margin-bottom: .5rem; + line-height: inherit; + font-size: calc(1.275rem + .3vw) +} + +@media (min-width:1200px) { + legend { + font-size: 1.5rem + } +} + +legend+* { + clear: left +} + +::-webkit-datetime-edit-day-field, +::-webkit-datetime-edit-fields-wrapper, +::-webkit-datetime-edit-hour-field, +::-webkit-datetime-edit-minute, +::-webkit-datetime-edit-month-field, +::-webkit-datetime-edit-text, +::-webkit-datetime-edit-year-field { + padding: 0 +} + +::-webkit-inner-spin-button { + height: auto +} + +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px +} + +::-webkit-search-decoration { + -webkit-appearance: none +} + +::-webkit-color-swatch-wrapper { + padding: 0 +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button +} + +::file-selector-button { + font: inherit; + -webkit-appearance: button +} + +output { + display: inline-block +} + +iframe { + border: 0 +} + +summary { + display: list-item; + cursor: pointer +} + +progress { + vertical-align: baseline +} + +[hidden] { + display: none !important +} + +.lead { + font-size: 1.25rem; + font-weight: 300 +} + +.display-1 { + font-weight: 300; + line-height: 1.2; + font-size: calc(1.625rem + 4.5vw) +} + +@media (min-width:1200px) { + .display-1 { + font-size: 5rem + } +} + +.display-2 { + font-weight: 300; + line-height: 1.2; + font-size: calc(1.575rem + 3.9vw) +} + +@media (min-width:1200px) { + .display-2 { + font-size: 4.5rem + } +} + +.display-3 { + font-weight: 300; + line-height: 1.2; + font-size: calc(1.525rem + 3.3vw) +} + +@media (min-width:1200px) { + .display-3 { + font-size: 4rem + } +} + +.display-4 { + font-weight: 300; + line-height: 1.2; + font-size: calc(1.475rem + 2.7vw) +} + +@media (min-width:1200px) { + .display-4 { + font-size: 3.5rem + } +} + +.display-5 { + font-weight: 300; + line-height: 1.2; + font-size: calc(1.425rem + 2.1vw) +} + +@media (min-width:1200px) { + .display-5 { + font-size: 3rem + } +} + +.display-6 { + font-weight: 300; + line-height: 1.2; + font-size: calc(1.375rem + 1.5vw) +} + +@media (min-width:1200px) { + .display-6 { + font-size: 2.5rem + } +} + +.list-unstyled { + padding-left: 0; + list-style: none +} + +.list-inline { + padding-left: 0; + list-style: none +} + +.list-inline-item { + display: inline-block +} + +.list-inline-item:not(:last-child) { + margin-right: .5rem +} + +.initialism { + font-size: .875em; + text-transform: uppercase +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem +} + +.blockquote>:last-child { + margin-bottom: 0 +} + +.blockquote-footer { + margin-top: -1rem; + margin-bottom: 1rem; + font-size: .875em; + color: #6c757d +} + +.blockquote-footer::before { + content: "— " +} + +.img-fluid { + max-width: 100%; + height: auto +} + +.img-thumbnail { + padding: .25rem; + background-color: var(--bs-body-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + max-width: 100%; + height: auto +} + +.figure { + display: inline-block +} + +.figure-img { + margin-bottom: .5rem; + line-height: 1 +} + +.figure-caption { + font-size: .875em; + color: var(--bs-secondary-color) +} + +.container, +.container-fluid, +.container-lg, +.container-md, +.container-sm, +.container-xl, +.container-xxl { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + width: 100%; + padding-right: calc(var(--bs-gutter-x) * .5); + padding-left: calc(var(--bs-gutter-x) * .5); + margin-right: auto; + margin-left: auto +} + +@media (min-width:576px) { + + .container, + .container-sm { + max-width: 540px + } +} + +@media (min-width:768px) { + + .container, + .container-md, + .container-sm { + max-width: 720px + } +} + +@media (min-width:992px) { + + .container, + .container-lg, + .container-md, + .container-sm { + max-width: 960px + } +} + +@media (min-width:1200px) { + + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl { + max-width: 1140px + } +} + +@media (min-width:1400px) { + + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl, + .container-xxl { + max-width: 1320px + } +} + +:root { + --bs-breakpoint-xs: 0; + --bs-breakpoint-sm: 576px; + --bs-breakpoint-md: 768px; + --bs-breakpoint-lg: 992px; + --bs-breakpoint-xl: 1200px; + --bs-breakpoint-xxl: 1400px +} + +.row { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + display: flex; + flex-wrap: wrap; + margin-top: calc(-1 * var(--bs-gutter-y)); + margin-right: calc(-.5 * var(--bs-gutter-x)); + margin-left: calc(-.5 * var(--bs-gutter-x)) +} + +.row>* { + flex-shrink: 0; + width: 100%; + max-width: 100%; + padding-right: calc(var(--bs-gutter-x) * .5); + padding-left: calc(var(--bs-gutter-x) * .5); + margin-top: var(--bs-gutter-y) +} + +.col { + flex: 1 0 0 +} + +.row-cols-auto>* { + flex: 0 0 auto; + width: auto +} + +.row-cols-1>* { + flex: 0 0 auto; + width: 100% +} + +.row-cols-2>* { + flex: 0 0 auto; + width: 50% +} + +.row-cols-3>* { + flex: 0 0 auto; + width: 33.33333333% +} + +.row-cols-4>* { + flex: 0 0 auto; + width: 25% +} + +.row-cols-5>* { + flex: 0 0 auto; + width: 20% +} + +.row-cols-6>* { + flex: 0 0 auto; + width: 16.66666667% +} + +.col-auto { + flex: 0 0 auto; + width: auto +} + +.col-1 { + flex: 0 0 auto; + width: 8.33333333% +} + +.col-2 { + flex: 0 0 auto; + width: 16.66666667% +} + +.col-3 { + flex: 0 0 auto; + width: 25% +} + +.col-4 { + flex: 0 0 auto; + width: 33.33333333% +} + +.col-5 { + flex: 0 0 auto; + width: 41.66666667% +} + +.col-6 { + flex: 0 0 auto; + width: 50% +} + +.col-7 { + flex: 0 0 auto; + width: 58.33333333% +} + +.col-8 { + flex: 0 0 auto; + width: 66.66666667% +} + +.col-9 { + flex: 0 0 auto; + width: 75% +} + +.col-10 { + flex: 0 0 auto; + width: 83.33333333% +} + +.col-11 { + flex: 0 0 auto; + width: 91.66666667% +} + +.col-12 { + flex: 0 0 auto; + width: 100% +} + +.offset-1 { + margin-left: 8.33333333% +} + +.offset-2 { + margin-left: 16.66666667% +} + +.offset-3 { + margin-left: 25% +} + +.offset-4 { + margin-left: 33.33333333% +} + +.offset-5 { + margin-left: 41.66666667% +} + +.offset-6 { + margin-left: 50% +} + +.offset-7 { + margin-left: 58.33333333% +} + +.offset-8 { + margin-left: 66.66666667% +} + +.offset-9 { + margin-left: 75% +} + +.offset-10 { + margin-left: 83.33333333% +} + +.offset-11 { + margin-left: 91.66666667% +} + +.g-0, +.gx-0 { + --bs-gutter-x: 0 +} + +.g-0, +.gy-0 { + --bs-gutter-y: 0 +} + +.g-1, +.gx-1 { + --bs-gutter-x: 0.25rem +} + +.g-1, +.gy-1 { + --bs-gutter-y: 0.25rem +} + +.g-2, +.gx-2 { + --bs-gutter-x: 0.5rem +} + +.g-2, +.gy-2 { + --bs-gutter-y: 0.5rem +} + +.g-3, +.gx-3 { + --bs-gutter-x: 1rem +} + +.g-3, +.gy-3 { + --bs-gutter-y: 1rem +} + +.g-4, +.gx-4 { + --bs-gutter-x: 1.5rem +} + +.g-4, +.gy-4 { + --bs-gutter-y: 1.5rem +} + +.g-5, +.gx-5 { + --bs-gutter-x: 3rem +} + +.g-5, +.gy-5 { + --bs-gutter-y: 3rem +} + +@media (min-width:576px) { + .col-sm { + flex: 1 0 0 + } + + .row-cols-sm-auto>* { + flex: 0 0 auto; + width: auto + } + + .row-cols-sm-1>* { + flex: 0 0 auto; + width: 100% + } + + .row-cols-sm-2>* { + flex: 0 0 auto; + width: 50% + } + + .row-cols-sm-3>* { + flex: 0 0 auto; + width: 33.33333333% + } + + .row-cols-sm-4>* { + flex: 0 0 auto; + width: 25% + } + + .row-cols-sm-5>* { + flex: 0 0 auto; + width: 20% + } + + .row-cols-sm-6>* { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-sm-auto { + flex: 0 0 auto; + width: auto + } + + .col-sm-1 { + flex: 0 0 auto; + width: 8.33333333% + } + + .col-sm-2 { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-sm-3 { + flex: 0 0 auto; + width: 25% + } + + .col-sm-4 { + flex: 0 0 auto; + width: 33.33333333% + } + + .col-sm-5 { + flex: 0 0 auto; + width: 41.66666667% + } + + .col-sm-6 { + flex: 0 0 auto; + width: 50% + } + + .col-sm-7 { + flex: 0 0 auto; + width: 58.33333333% + } + + .col-sm-8 { + flex: 0 0 auto; + width: 66.66666667% + } + + .col-sm-9 { + flex: 0 0 auto; + width: 75% + } + + .col-sm-10 { + flex: 0 0 auto; + width: 83.33333333% + } + + .col-sm-11 { + flex: 0 0 auto; + width: 91.66666667% + } + + .col-sm-12 { + flex: 0 0 auto; + width: 100% + } + + .offset-sm-0 { + margin-left: 0 + } + + .offset-sm-1 { + margin-left: 8.33333333% + } + + .offset-sm-2 { + margin-left: 16.66666667% + } + + .offset-sm-3 { + margin-left: 25% + } + + .offset-sm-4 { + margin-left: 33.33333333% + } + + .offset-sm-5 { + margin-left: 41.66666667% + } + + .offset-sm-6 { + margin-left: 50% + } + + .offset-sm-7 { + margin-left: 58.33333333% + } + + .offset-sm-8 { + margin-left: 66.66666667% + } + + .offset-sm-9 { + margin-left: 75% + } + + .offset-sm-10 { + margin-left: 83.33333333% + } + + .offset-sm-11 { + margin-left: 91.66666667% + } + + .g-sm-0, + .gx-sm-0 { + --bs-gutter-x: 0 + } + + .g-sm-0, + .gy-sm-0 { + --bs-gutter-y: 0 + } + + .g-sm-1, + .gx-sm-1 { + --bs-gutter-x: 0.25rem + } + + .g-sm-1, + .gy-sm-1 { + --bs-gutter-y: 0.25rem + } + + .g-sm-2, + .gx-sm-2 { + --bs-gutter-x: 0.5rem + } + + .g-sm-2, + .gy-sm-2 { + --bs-gutter-y: 0.5rem + } + + .g-sm-3, + .gx-sm-3 { + --bs-gutter-x: 1rem + } + + .g-sm-3, + .gy-sm-3 { + --bs-gutter-y: 1rem + } + + .g-sm-4, + .gx-sm-4 { + --bs-gutter-x: 1.5rem + } + + .g-sm-4, + .gy-sm-4 { + --bs-gutter-y: 1.5rem + } + + .g-sm-5, + .gx-sm-5 { + --bs-gutter-x: 3rem + } + + .g-sm-5, + .gy-sm-5 { + --bs-gutter-y: 3rem + } +} + +@media (min-width:768px) { + .col-md { + flex: 1 0 0 + } + + .row-cols-md-auto>* { + flex: 0 0 auto; + width: auto + } + + .row-cols-md-1>* { + flex: 0 0 auto; + width: 100% + } + + .row-cols-md-2>* { + flex: 0 0 auto; + width: 50% + } + + .row-cols-md-3>* { + flex: 0 0 auto; + width: 33.33333333% + } + + .row-cols-md-4>* { + flex: 0 0 auto; + width: 25% + } + + .row-cols-md-5>* { + flex: 0 0 auto; + width: 20% + } + + .row-cols-md-6>* { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-md-auto { + flex: 0 0 auto; + width: auto + } + + .col-md-1 { + flex: 0 0 auto; + width: 8.33333333% + } + + .col-md-2 { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-md-3 { + flex: 0 0 auto; + width: 25% + } + + .col-md-4 { + flex: 0 0 auto; + width: 33.33333333% + } + + .col-md-5 { + flex: 0 0 auto; + width: 41.66666667% + } + + .col-md-6 { + flex: 0 0 auto; + width: 50% + } + + .col-md-7 { + flex: 0 0 auto; + width: 58.33333333% + } + + .col-md-8 { + flex: 0 0 auto; + width: 66.66666667% + } + + .col-md-9 { + flex: 0 0 auto; + width: 75% + } + + .col-md-10 { + flex: 0 0 auto; + width: 83.33333333% + } + + .col-md-11 { + flex: 0 0 auto; + width: 91.66666667% + } + + .col-md-12 { + flex: 0 0 auto; + width: 100% + } + + .offset-md-0 { + margin-left: 0 + } + + .offset-md-1 { + margin-left: 8.33333333% + } + + .offset-md-2 { + margin-left: 16.66666667% + } + + .offset-md-3 { + margin-left: 25% + } + + .offset-md-4 { + margin-left: 33.33333333% + } + + .offset-md-5 { + margin-left: 41.66666667% + } + + .offset-md-6 { + margin-left: 50% + } + + .offset-md-7 { + margin-left: 58.33333333% + } + + .offset-md-8 { + margin-left: 66.66666667% + } + + .offset-md-9 { + margin-left: 75% + } + + .offset-md-10 { + margin-left: 83.33333333% + } + + .offset-md-11 { + margin-left: 91.66666667% + } + + .g-md-0, + .gx-md-0 { + --bs-gutter-x: 0 + } + + .g-md-0, + .gy-md-0 { + --bs-gutter-y: 0 + } + + .g-md-1, + .gx-md-1 { + --bs-gutter-x: 0.25rem + } + + .g-md-1, + .gy-md-1 { + --bs-gutter-y: 0.25rem + } + + .g-md-2, + .gx-md-2 { + --bs-gutter-x: 0.5rem + } + + .g-md-2, + .gy-md-2 { + --bs-gutter-y: 0.5rem + } + + .g-md-3, + .gx-md-3 { + --bs-gutter-x: 1rem + } + + .g-md-3, + .gy-md-3 { + --bs-gutter-y: 1rem + } + + .g-md-4, + .gx-md-4 { + --bs-gutter-x: 1.5rem + } + + .g-md-4, + .gy-md-4 { + --bs-gutter-y: 1.5rem + } + + .g-md-5, + .gx-md-5 { + --bs-gutter-x: 3rem + } + + .g-md-5, + .gy-md-5 { + --bs-gutter-y: 3rem + } +} + +@media (min-width:992px) { + .col-lg { + flex: 1 0 0 + } + + .row-cols-lg-auto>* { + flex: 0 0 auto; + width: auto + } + + .row-cols-lg-1>* { + flex: 0 0 auto; + width: 100% + } + + .row-cols-lg-2>* { + flex: 0 0 auto; + width: 50% + } + + .row-cols-lg-3>* { + flex: 0 0 auto; + width: 33.33333333% + } + + .row-cols-lg-4>* { + flex: 0 0 auto; + width: 25% + } + + .row-cols-lg-5>* { + flex: 0 0 auto; + width: 20% + } + + .row-cols-lg-6>* { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-lg-auto { + flex: 0 0 auto; + width: auto + } + + .col-lg-1 { + flex: 0 0 auto; + width: 8.33333333% + } + + .col-lg-2 { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-lg-3 { + flex: 0 0 auto; + width: 25% + } + + .col-lg-4 { + flex: 0 0 auto; + width: 33.33333333% + } + + .col-lg-5 { + flex: 0 0 auto; + width: 41.66666667% + } + + .col-lg-6 { + flex: 0 0 auto; + width: 50% + } + + .col-lg-7 { + flex: 0 0 auto; + width: 58.33333333% + } + + .col-lg-8 { + flex: 0 0 auto; + width: 66.66666667% + } + + .col-lg-9 { + flex: 0 0 auto; + width: 75% + } + + .col-lg-10 { + flex: 0 0 auto; + width: 83.33333333% + } + + .col-lg-11 { + flex: 0 0 auto; + width: 91.66666667% + } + + .col-lg-12 { + flex: 0 0 auto; + width: 100% + } + + .offset-lg-0 { + margin-left: 0 + } + + .offset-lg-1 { + margin-left: 8.33333333% + } + + .offset-lg-2 { + margin-left: 16.66666667% + } + + .offset-lg-3 { + margin-left: 25% + } + + .offset-lg-4 { + margin-left: 33.33333333% + } + + .offset-lg-5 { + margin-left: 41.66666667% + } + + .offset-lg-6 { + margin-left: 50% + } + + .offset-lg-7 { + margin-left: 58.33333333% + } + + .offset-lg-8 { + margin-left: 66.66666667% + } + + .offset-lg-9 { + margin-left: 75% + } + + .offset-lg-10 { + margin-left: 83.33333333% + } + + .offset-lg-11 { + margin-left: 91.66666667% + } + + .g-lg-0, + .gx-lg-0 { + --bs-gutter-x: 0 + } + + .g-lg-0, + .gy-lg-0 { + --bs-gutter-y: 0 + } + + .g-lg-1, + .gx-lg-1 { + --bs-gutter-x: 0.25rem + } + + .g-lg-1, + .gy-lg-1 { + --bs-gutter-y: 0.25rem + } + + .g-lg-2, + .gx-lg-2 { + --bs-gutter-x: 0.5rem + } + + .g-lg-2, + .gy-lg-2 { + --bs-gutter-y: 0.5rem + } + + .g-lg-3, + .gx-lg-3 { + --bs-gutter-x: 1rem + } + + .g-lg-3, + .gy-lg-3 { + --bs-gutter-y: 1rem + } + + .g-lg-4, + .gx-lg-4 { + --bs-gutter-x: 1.5rem + } + + .g-lg-4, + .gy-lg-4 { + --bs-gutter-y: 1.5rem + } + + .g-lg-5, + .gx-lg-5 { + --bs-gutter-x: 3rem + } + + .g-lg-5, + .gy-lg-5 { + --bs-gutter-y: 3rem + } +} + +@media (min-width:1200px) { + .col-xl { + flex: 1 0 0 + } + + .row-cols-xl-auto>* { + flex: 0 0 auto; + width: auto + } + + .row-cols-xl-1>* { + flex: 0 0 auto; + width: 100% + } + + .row-cols-xl-2>* { + flex: 0 0 auto; + width: 50% + } + + .row-cols-xl-3>* { + flex: 0 0 auto; + width: 33.33333333% + } + + .row-cols-xl-4>* { + flex: 0 0 auto; + width: 25% + } + + .row-cols-xl-5>* { + flex: 0 0 auto; + width: 20% + } + + .row-cols-xl-6>* { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-xl-auto { + flex: 0 0 auto; + width: auto + } + + .col-xl-1 { + flex: 0 0 auto; + width: 8.33333333% + } + + .col-xl-2 { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-xl-3 { + flex: 0 0 auto; + width: 25% + } + + .col-xl-4 { + flex: 0 0 auto; + width: 33.33333333% + } + + .col-xl-5 { + flex: 0 0 auto; + width: 41.66666667% + } + + .col-xl-6 { + flex: 0 0 auto; + width: 50% + } + + .col-xl-7 { + flex: 0 0 auto; + width: 58.33333333% + } + + .col-xl-8 { + flex: 0 0 auto; + width: 66.66666667% + } + + .col-xl-9 { + flex: 0 0 auto; + width: 75% + } + + .col-xl-10 { + flex: 0 0 auto; + width: 83.33333333% + } + + .col-xl-11 { + flex: 0 0 auto; + width: 91.66666667% + } + + .col-xl-12 { + flex: 0 0 auto; + width: 100% + } + + .offset-xl-0 { + margin-left: 0 + } + + .offset-xl-1 { + margin-left: 8.33333333% + } + + .offset-xl-2 { + margin-left: 16.66666667% + } + + .offset-xl-3 { + margin-left: 25% + } + + .offset-xl-4 { + margin-left: 33.33333333% + } + + .offset-xl-5 { + margin-left: 41.66666667% + } + + .offset-xl-6 { + margin-left: 50% + } + + .offset-xl-7 { + margin-left: 58.33333333% + } + + .offset-xl-8 { + margin-left: 66.66666667% + } + + .offset-xl-9 { + margin-left: 75% + } + + .offset-xl-10 { + margin-left: 83.33333333% + } + + .offset-xl-11 { + margin-left: 91.66666667% + } + + .g-xl-0, + .gx-xl-0 { + --bs-gutter-x: 0 + } + + .g-xl-0, + .gy-xl-0 { + --bs-gutter-y: 0 + } + + .g-xl-1, + .gx-xl-1 { + --bs-gutter-x: 0.25rem + } + + .g-xl-1, + .gy-xl-1 { + --bs-gutter-y: 0.25rem + } + + .g-xl-2, + .gx-xl-2 { + --bs-gutter-x: 0.5rem + } + + .g-xl-2, + .gy-xl-2 { + --bs-gutter-y: 0.5rem + } + + .g-xl-3, + .gx-xl-3 { + --bs-gutter-x: 1rem + } + + .g-xl-3, + .gy-xl-3 { + --bs-gutter-y: 1rem + } + + .g-xl-4, + .gx-xl-4 { + --bs-gutter-x: 1.5rem + } + + .g-xl-4, + .gy-xl-4 { + --bs-gutter-y: 1.5rem + } + + .g-xl-5, + .gx-xl-5 { + --bs-gutter-x: 3rem + } + + .g-xl-5, + .gy-xl-5 { + --bs-gutter-y: 3rem + } +} + +@media (min-width:1400px) { + .col-xxl { + flex: 1 0 0 + } + + .row-cols-xxl-auto>* { + flex: 0 0 auto; + width: auto + } + + .row-cols-xxl-1>* { + flex: 0 0 auto; + width: 100% + } + + .row-cols-xxl-2>* { + flex: 0 0 auto; + width: 50% + } + + .row-cols-xxl-3>* { + flex: 0 0 auto; + width: 33.33333333% + } + + .row-cols-xxl-4>* { + flex: 0 0 auto; + width: 25% + } + + .row-cols-xxl-5>* { + flex: 0 0 auto; + width: 20% + } + + .row-cols-xxl-6>* { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-xxl-auto { + flex: 0 0 auto; + width: auto + } + + .col-xxl-1 { + flex: 0 0 auto; + width: 8.33333333% + } + + .col-xxl-2 { + flex: 0 0 auto; + width: 16.66666667% + } + + .col-xxl-3 { + flex: 0 0 auto; + width: 25% + } + + .col-xxl-4 { + flex: 0 0 auto; + width: 33.33333333% + } + + .col-xxl-5 { + flex: 0 0 auto; + width: 41.66666667% + } + + .col-xxl-6 { + flex: 0 0 auto; + width: 50% + } + + .col-xxl-7 { + flex: 0 0 auto; + width: 58.33333333% + } + + .col-xxl-8 { + flex: 0 0 auto; + width: 66.66666667% + } + + .col-xxl-9 { + flex: 0 0 auto; + width: 75% + } + + .col-xxl-10 { + flex: 0 0 auto; + width: 83.33333333% + } + + .col-xxl-11 { + flex: 0 0 auto; + width: 91.66666667% + } + + .col-xxl-12 { + flex: 0 0 auto; + width: 100% + } + + .offset-xxl-0 { + margin-left: 0 + } + + .offset-xxl-1 { + margin-left: 8.33333333% + } + + .offset-xxl-2 { + margin-left: 16.66666667% + } + + .offset-xxl-3 { + margin-left: 25% + } + + .offset-xxl-4 { + margin-left: 33.33333333% + } + + .offset-xxl-5 { + margin-left: 41.66666667% + } + + .offset-xxl-6 { + margin-left: 50% + } + + .offset-xxl-7 { + margin-left: 58.33333333% + } + + .offset-xxl-8 { + margin-left: 66.66666667% + } + + .offset-xxl-9 { + margin-left: 75% + } + + .offset-xxl-10 { + margin-left: 83.33333333% + } + + .offset-xxl-11 { + margin-left: 91.66666667% + } + + .g-xxl-0, + .gx-xxl-0 { + --bs-gutter-x: 0 + } + + .g-xxl-0, + .gy-xxl-0 { + --bs-gutter-y: 0 + } + + .g-xxl-1, + .gx-xxl-1 { + --bs-gutter-x: 0.25rem + } + + .g-xxl-1, + .gy-xxl-1 { + --bs-gutter-y: 0.25rem + } + + .g-xxl-2, + .gx-xxl-2 { + --bs-gutter-x: 0.5rem + } + + .g-xxl-2, + .gy-xxl-2 { + --bs-gutter-y: 0.5rem + } + + .g-xxl-3, + .gx-xxl-3 { + --bs-gutter-x: 1rem + } + + .g-xxl-3, + .gy-xxl-3 { + --bs-gutter-y: 1rem + } + + .g-xxl-4, + .gx-xxl-4 { + --bs-gutter-x: 1.5rem + } + + .g-xxl-4, + .gy-xxl-4 { + --bs-gutter-y: 1.5rem + } + + .g-xxl-5, + .gx-xxl-5 { + --bs-gutter-x: 3rem + } + + .g-xxl-5, + .gy-xxl-5 { + --bs-gutter-y: 3rem + } +} + +.table { + --bs-table-color-type: initial; + --bs-table-bg-type: initial; + --bs-table-color-state: initial; + --bs-table-bg-state: initial; + --bs-table-color: var(--bs-emphasis-color); + --bs-table-bg: var(--bs-body-bg); + --bs-table-border-color: var(--bs-border-color); + --bs-table-accent-bg: transparent; + --bs-table-striped-color: var(--bs-emphasis-color); + --bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05); + --bs-table-active-color: var(--bs-emphasis-color); + --bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1); + --bs-table-hover-color: var(--bs-emphasis-color); + --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075); + width: 100%; + margin-bottom: 1rem; + vertical-align: top; + border-color: var(--bs-table-border-color) +} + +.table>:not(caption)>*>* { + padding: .5rem .5rem; + color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color))); + background-color: var(--bs-table-bg); + border-bottom-width: var(--bs-border-width); + box-shadow: inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg))) +} + +.table>tbody { + vertical-align: inherit +} + +.table>thead { + vertical-align: bottom +} + +.table-group-divider { + border-top: calc(var(--bs-border-width) * 2) solid currentcolor +} + +.caption-top { + caption-side: top +} + +.table-sm>:not(caption)>*>* { + padding: .25rem .25rem +} + +.table-bordered>:not(caption)>* { + border-width: var(--bs-border-width) 0 +} + +.table-bordered>:not(caption)>*>* { + border-width: 0 var(--bs-border-width) +} + +.table-borderless>:not(caption)>*>* { + border-bottom-width: 0 +} + +.table-borderless>:not(:first-child) { + border-top-width: 0 +} + +.table-striped>tbody>tr:nth-of-type(odd)>* { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg) +} + +.table-striped-columns>:not(caption)>tr>:nth-child(2n) { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg) +} + +.table-active { + --bs-table-color-state: var(--bs-table-active-color); + --bs-table-bg-state: var(--bs-table-active-bg) +} + +.table-hover>tbody>tr:hover>* { + --bs-table-color-state: var(--bs-table-hover-color); + --bs-table-bg-state: var(--bs-table-hover-bg) +} + +.table-primary { + --bs-table-color: #000; + --bs-table-bg: #cfe2ff; + --bs-table-border-color: #a6b5cc; + --bs-table-striped-bg: #c5d7f2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #bacbe6; + --bs-table-active-color: #000; + --bs-table-hover-bg: #bfd1ec; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color) +} + +.table-secondary { + --bs-table-color: #000; + --bs-table-bg: #e2e3e5; + --bs-table-border-color: #b5b6b7; + --bs-table-striped-bg: #d7d8da; + --bs-table-striped-color: #000; + --bs-table-active-bg: #cbccce; + --bs-table-active-color: #000; + --bs-table-hover-bg: #d1d2d4; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color) +} + +.table-success { + --bs-table-color: #000; + --bs-table-bg: #d1e7dd; + --bs-table-border-color: #a7b9b1; + --bs-table-striped-bg: #c7dbd2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #bcd0c7; + --bs-table-active-color: #000; + --bs-table-hover-bg: #c1d6cc; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color) +} + +.table-info { + --bs-table-color: #000; + --bs-table-bg: #cff4fc; + --bs-table-border-color: #a6c3ca; + --bs-table-striped-bg: #c5e8ef; + --bs-table-striped-color: #000; + --bs-table-active-bg: #badce3; + --bs-table-active-color: #000; + --bs-table-hover-bg: #bfe2e9; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color) +} + +.table-warning { + --bs-table-color: #000; + --bs-table-bg: #fff3cd; + --bs-table-border-color: #ccc2a4; + --bs-table-striped-bg: #f2e7c3; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e6dbb9; + --bs-table-active-color: #000; + --bs-table-hover-bg: #ece1be; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color) +} + +.table-danger { + --bs-table-color: #000; + --bs-table-bg: #f8d7da; + --bs-table-border-color: #c6acae; + --bs-table-striped-bg: #eccccf; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfc2c4; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e5c7ca; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color) +} + +.table-light { + --bs-table-color: #000; + --bs-table-bg: #f8f9fa; + --bs-table-border-color: #c6c7c8; + --bs-table-striped-bg: #ecedee; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfe0e1; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e5e6e7; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color) +} + +.table-dark { + --bs-table-color: #fff; + --bs-table-bg: #212529; + --bs-table-border-color: #4d5154; + --bs-table-striped-bg: #2c3034; + --bs-table-striped-color: #fff; + --bs-table-active-bg: #373b3e; + --bs-table-active-color: #fff; + --bs-table-hover-bg: #323539; + --bs-table-hover-color: #fff; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color) +} + +.table-responsive { + overflow-x: auto; + -webkit-overflow-scrolling: touch +} + +@media (max-width:575.98px) { + .table-responsive-sm { + overflow-x: auto; + -webkit-overflow-scrolling: touch + } +} + +@media (max-width:767.98px) { + .table-responsive-md { + overflow-x: auto; + -webkit-overflow-scrolling: touch + } +} + +@media (max-width:991.98px) { + .table-responsive-lg { + overflow-x: auto; + -webkit-overflow-scrolling: touch + } +} + +@media (max-width:1199.98px) { + .table-responsive-xl { + overflow-x: auto; + -webkit-overflow-scrolling: touch + } +} + +@media (max-width:1399.98px) { + .table-responsive-xxl { + overflow-x: auto; + -webkit-overflow-scrolling: touch + } +} + +.form-label { + margin-bottom: .5rem +} + +.col-form-label { + padding-top: calc(.375rem + var(--bs-border-width)); + padding-bottom: calc(.375rem + var(--bs-border-width)); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5 +} + +.col-form-label-lg { + padding-top: calc(.5rem + var(--bs-border-width)); + padding-bottom: calc(.5rem + var(--bs-border-width)); + font-size: 1.25rem +} + +.col-form-label-sm { + padding-top: calc(.25rem + var(--bs-border-width)); + padding-bottom: calc(.25rem + var(--bs-border-width)); + font-size: .875rem +} + +.form-text { + margin-top: .25rem; + font-size: .875em; + color: var(--bs-secondary-color) +} + +.form-control { + display: block; + width: 100%; + padding: .375rem .75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-clip: padding-box; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .form-control { + transition: none + } +} + +.form-control[type=file] { + overflow: hidden +} + +.form-control[type=file]:not(:disabled):not([readonly]) { + cursor: pointer +} + +.form-control:focus { + color: var(--bs-body-color); + background-color: var(--bs-body-bg); + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25) +} + +.form-control::-webkit-date-and-time-value { + min-width: 85px; + height: 1.5em; + margin: 0 +} + +.form-control::-webkit-datetime-edit { + display: block; + padding: 0 +} + +.form-control::placeholder { + color: var(--bs-secondary-color); + opacity: 1 +} + +.form-control:disabled { + background-color: var(--bs-secondary-bg); + opacity: 1 +} + +.form-control::-webkit-file-upload-button { + padding: .375rem .75rem; + margin: -.375rem -.75rem; + -webkit-margin-end: .75rem; + margin-inline-end: .75rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; + -webkit-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +.form-control::file-selector-button { + padding: .375rem .75rem; + margin: -.375rem -.75rem; + -webkit-margin-end: .75rem; + margin-inline-end: .75rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .form-control::-webkit-file-upload-button { + -webkit-transition: none; + transition: none + } + + .form-control::file-selector-button { + transition: none + } +} + +.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button { + background-color: var(--bs-secondary-bg) +} + +.form-control:hover:not(:disabled):not([readonly])::file-selector-button { + background-color: var(--bs-secondary-bg) +} + +.form-control-plaintext { + display: block; + width: 100%; + padding: .375rem 0; + margin-bottom: 0; + line-height: 1.5; + color: var(--bs-body-color); + background-color: transparent; + border: solid transparent; + border-width: var(--bs-border-width) 0 +} + +.form-control-plaintext:focus { + outline: 0 +} + +.form-control-plaintext.form-control-lg, +.form-control-plaintext.form-control-sm { + padding-right: 0; + padding-left: 0 +} + +.form-control-sm { + min-height: calc(1.5em + .5rem + calc(var(--bs-border-width) * 2)); + padding: .25rem .5rem; + font-size: .875rem; + border-radius: var(--bs-border-radius-sm) +} + +.form-control-sm::-webkit-file-upload-button { + padding: .25rem .5rem; + margin: -.25rem -.5rem; + -webkit-margin-end: .5rem; + margin-inline-end: .5rem +} + +.form-control-sm::file-selector-button { + padding: .25rem .5rem; + margin: -.25rem -.5rem; + -webkit-margin-end: .5rem; + margin-inline-end: .5rem +} + +.form-control-lg { + min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); + padding: .5rem 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg) +} + +.form-control-lg::-webkit-file-upload-button { + padding: .5rem 1rem; + margin: -.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem +} + +.form-control-lg::file-selector-button { + padding: .5rem 1rem; + margin: -.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem +} + +textarea.form-control { + min-height: calc(1.5em + .75rem + calc(var(--bs-border-width) * 2)) +} + +textarea.form-control-sm { + min-height: calc(1.5em + .5rem + calc(var(--bs-border-width) * 2)) +} + +textarea.form-control-lg { + min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)) +} + +.form-control-color { + width: 3rem; + height: calc(1.5em + .75rem + calc(var(--bs-border-width) * 2)); + padding: .375rem +} + +.form-control-color:not(:disabled):not([readonly]) { + cursor: pointer +} + +.form-control-color::-moz-color-swatch { + border: 0 !important; + border-radius: var(--bs-border-radius) +} + +.form-control-color::-webkit-color-swatch { + border: 0 !important; + border-radius: var(--bs-border-radius) +} + +.form-control-color.form-control-sm { + height: calc(1.5em + .5rem + calc(var(--bs-border-width) * 2)) +} + +.form-control-color.form-control-lg { + height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)) +} + +.form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); + display: block; + width: 100%; + padding: .375rem 2.25rem .375rem .75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-image: var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon, none); + background-repeat: no-repeat; + background-position: right .75rem center; + background-size: 16px 12px; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .form-select { + transition: none + } +} + +.form-select:focus { + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25) +} + +.form-select[multiple], +.form-select[size]:not([size="1"]) { + padding-right: .75rem; + background-image: none +} + +.form-select:disabled { + background-color: var(--bs-secondary-bg) +} + +.form-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 var(--bs-body-color) +} + +.form-select-sm { + padding-top: .25rem; + padding-bottom: .25rem; + padding-left: .5rem; + font-size: .875rem; + border-radius: var(--bs-border-radius-sm) +} + +.form-select-lg { + padding-top: .5rem; + padding-bottom: .5rem; + padding-left: 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg) +} + +[data-bs-theme=dark] .form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e") +} + +.form-check { + display: block; + min-height: 1.5rem; + padding-left: 1.5em; + margin-bottom: .125rem +} + +.form-check .form-check-input { + float: left; + margin-left: -1.5em +} + +.form-check-reverse { + padding-right: 1.5em; + padding-left: 0; + text-align: right +} + +.form-check-reverse .form-check-input { + float: right; + margin-right: -1.5em; + margin-left: 0 +} + +.form-check-input { + --bs-form-check-bg: var(--bs-body-bg); + flex-shrink: 0; + width: 1em; + height: 1em; + margin-top: .25em; + vertical-align: top; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-form-check-bg); + background-image: var(--bs-form-check-bg-image); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: var(--bs-border-width) solid var(--bs-border-color); + -webkit-print-color-adjust: exact; + color-adjust: exact; + print-color-adjust: exact +} + +.form-check-input[type=checkbox] { + border-radius: .25em +} + +.form-check-input[type=radio] { + border-radius: 50% +} + +.form-check-input:active { + filter: brightness(90%) +} + +.form-check-input:focus { + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25) +} + +.form-check-input:checked { + background-color: #0d6efd; + border-color: #0d6efd +} + +.form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e") +} + +.form-check-input:checked[type=radio] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e") +} + +.form-check-input[type=checkbox]:indeterminate { + background-color: #0d6efd; + border-color: #0d6efd; + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e") +} + +.form-check-input:disabled { + pointer-events: none; + filter: none; + opacity: .5 +} + +.form-check-input:disabled~.form-check-label, +.form-check-input[disabled]~.form-check-label { + cursor: default; + opacity: .5 +} + +.form-switch { + padding-left: 2.5em +} + +.form-switch .form-check-input { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e"); + width: 2em; + margin-left: -2.5em; + background-image: var(--bs-form-switch-bg); + background-position: left center; + border-radius: 2em; + transition: background-position .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .form-switch .form-check-input { + transition: none + } +} + +.form-switch .form-check-input:focus { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e") +} + +.form-switch .form-check-input:checked { + background-position: right center; + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e") +} + +.form-switch.form-check-reverse { + padding-right: 2.5em; + padding-left: 0 +} + +.form-switch.form-check-reverse .form-check-input { + margin-right: -2.5em; + margin-left: 0 +} + +.form-check-inline { + display: inline-block; + margin-right: 1rem +} + +.btn-check { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none +} + +.btn-check:disabled+.btn, +.btn-check[disabled]+.btn { + pointer-events: none; + filter: none; + opacity: .65 +} + +[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus) { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e") +} + +.form-range { + width: 100%; + height: 1.5rem; + padding: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: transparent +} + +.form-range:focus { + outline: 0 +} + +.form-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 .25rem rgba(13, 110, 253, .25) +} + +.form-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 .25rem rgba(13, 110, 253, .25) +} + +.form-range::-moz-focus-outer { + border: 0 +} + +.form-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -.25rem; + -webkit-appearance: none; + appearance: none; + background-color: #0d6efd; + border: 0; + border-radius: 1rem; + -webkit-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .form-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none + } +} + +.form-range::-webkit-slider-thumb:active { + background-color: #b6d4fe +} + +.form-range::-webkit-slider-runnable-track { + width: 100%; + height: .5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-secondary-bg); + border-color: transparent; + border-radius: 1rem +} + +.form-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + -moz-appearance: none; + appearance: none; + background-color: #0d6efd; + border: 0; + border-radius: 1rem; + -moz-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .form-range::-moz-range-thumb { + -moz-transition: none; + transition: none + } +} + +.form-range::-moz-range-thumb:active { + background-color: #b6d4fe +} + +.form-range::-moz-range-track { + width: 100%; + height: .5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-secondary-bg); + border-color: transparent; + border-radius: 1rem +} + +.form-range:disabled { + pointer-events: none +} + +.form-range:disabled::-webkit-slider-thumb { + background-color: var(--bs-secondary-color) +} + +.form-range:disabled::-moz-range-thumb { + background-color: var(--bs-secondary-color) +} + +.form-floating { + position: relative +} + +.form-floating>.form-control, +.form-floating>.form-control-plaintext, +.form-floating>.form-select { + height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + min-height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + line-height: 1.25 +} + +.form-floating>label { + position: absolute; + top: 0; + left: 0; + z-index: 2; + max-width: 100%; + height: 100%; + padding: 1rem .75rem; + overflow: hidden; + color: rgba(var(--bs-body-color-rgb), .65); + text-align: start; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; + border: var(--bs-border-width) solid transparent; + transform-origin: 0 0; + transition: opacity .1s ease-in-out, transform .1s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .form-floating>label { + transition: none + } +} + +.form-floating>.form-control, +.form-floating>.form-control-plaintext { + padding: 1rem .75rem +} + +.form-floating>.form-control-plaintext::placeholder, +.form-floating>.form-control::placeholder { + color: transparent +} + +.form-floating>.form-control-plaintext:focus, +.form-floating>.form-control-plaintext:not(:placeholder-shown), +.form-floating>.form-control:focus, +.form-floating>.form-control:not(:placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: .625rem +} + +.form-floating>.form-control-plaintext:-webkit-autofill, +.form-floating>.form-control:-webkit-autofill { + padding-top: 1.625rem; + padding-bottom: .625rem +} + +.form-floating>.form-select { + padding-top: 1.625rem; + padding-bottom: .625rem; + padding-left: .75rem +} + +.form-floating>.form-control-plaintext~label, +.form-floating>.form-control:focus~label, +.form-floating>.form-control:not(:placeholder-shown)~label, +.form-floating>.form-select~label { + transform: scale(.85) translateY(-.5rem) translateX(.15rem) +} + +.form-floating>.form-control:-webkit-autofill~label { + transform: scale(.85) translateY(-.5rem) translateX(.15rem) +} + +.form-floating>textarea:focus~label::after, +.form-floating>textarea:not(:placeholder-shown)~label::after { + position: absolute; + inset: 1rem 0.375rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); + border-radius: var(--bs-border-radius) +} + +.form-floating>textarea:disabled~label::after { + background-color: var(--bs-secondary-bg) +} + +.form-floating>.form-control-plaintext~label { + border-width: var(--bs-border-width) 0 +} + +.form-floating>.form-control:disabled~label, +.form-floating>:disabled~label { + color: #6c757d +} + +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100% +} + +.input-group>.form-control, +.input-group>.form-floating, +.input-group>.form-select { + position: relative; + flex: 1 1 auto; + width: 1%; + min-width: 0 +} + +.input-group>.form-control:focus, +.input-group>.form-floating:focus-within, +.input-group>.form-select:focus { + z-index: 5 +} + +.input-group .btn { + position: relative; + z-index: 2 +} + +.input-group .btn:focus { + z-index: 5 +} + +.input-group-text { + display: flex; + align-items: center; + padding: .375rem .75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-tertiary-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius) +} + +.input-group-lg>.btn, +.input-group-lg>.form-control, +.input-group-lg>.form-select, +.input-group-lg>.input-group-text { + padding: .5rem 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg) +} + +.input-group-sm>.btn, +.input-group-sm>.form-control, +.input-group-sm>.form-select, +.input-group-sm>.input-group-text { + padding: .25rem .5rem; + font-size: .875rem; + border-radius: var(--bs-border-radius-sm) +} + +.input-group-lg>.form-select, +.input-group-sm>.form-select { + padding-right: 3rem +} + +.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3), +.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control, +.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select, +.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating) { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4), +.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control, +.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select, +.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating) { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) { + margin-left: calc(-1 * var(--bs-border-width)); + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.input-group>.form-floating:not(:first-child)>.form-control, +.input-group>.form-floating:not(:first-child)>.form-select { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.valid-feedback { + display: none; + width: 100%; + margin-top: .25rem; + font-size: .875em; + color: var(--bs-form-valid-color) +} + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: .25rem .5rem; + margin-top: .1rem; + font-size: .875rem; + color: #fff; + background-color: var(--bs-success); + border-radius: var(--bs-border-radius) +} + +.is-valid~.valid-feedback, +.is-valid~.valid-tooltip, +.was-validated :valid~.valid-feedback, +.was-validated :valid~.valid-tooltip { + display: block +} + +.form-control.is-valid, +.was-validated .form-control:valid { + border-color: var(--bs-form-valid-border-color); + padding-right: calc(1.5em + .75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(.375em + .1875rem) center; + background-size: calc(.75em + .375rem) calc(.75em + .375rem) +} + +.form-control.is-valid:focus, +.was-validated .form-control:valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 .25rem rgba(var(--bs-success-rgb), .25) +} + +.was-validated textarea.form-control:valid, +textarea.form-control.is-valid { + padding-right: calc(1.5em + .75rem); + background-position: top calc(.375em + .1875rem) right calc(.375em + .1875rem) +} + +.form-select.is-valid, +.was-validated .form-select:valid { + border-color: var(--bs-form-valid-border-color) +} + +.form-select.is-valid:not([multiple]):not([size]), +.form-select.is-valid:not([multiple])[size="1"], +.was-validated .form-select:valid:not([multiple]):not([size]), +.was-validated .form-select:valid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e"); + padding-right: 4.125rem; + background-position: right .75rem center, center right 2.25rem; + background-size: 16px 12px, calc(.75em + .375rem) calc(.75em + .375rem) +} + +.form-select.is-valid:focus, +.was-validated .form-select:valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 .25rem rgba(var(--bs-success-rgb), .25) +} + +.form-control-color.is-valid, +.was-validated .form-control-color:valid { + width: calc(3rem + calc(1.5em + .75rem)) +} + +.form-check-input.is-valid, +.was-validated .form-check-input:valid { + border-color: var(--bs-form-valid-border-color) +} + +.form-check-input.is-valid:checked, +.was-validated .form-check-input:valid:checked { + background-color: var(--bs-form-valid-color) +} + +.form-check-input.is-valid:focus, +.was-validated .form-check-input:valid:focus { + box-shadow: 0 0 0 .25rem rgba(var(--bs-success-rgb), .25) +} + +.form-check-input.is-valid~.form-check-label, +.was-validated .form-check-input:valid~.form-check-label { + color: var(--bs-form-valid-color) +} + +.form-check-inline .form-check-input~.valid-feedback { + margin-left: .5em +} + +.input-group>.form-control:not(:focus).is-valid, +.input-group>.form-floating:not(:focus-within).is-valid, +.input-group>.form-select:not(:focus).is-valid, +.was-validated .input-group>.form-control:not(:focus):valid, +.was-validated .input-group>.form-floating:not(:focus-within):valid, +.was-validated .input-group>.form-select:not(:focus):valid { + z-index: 3 +} + +.invalid-feedback { + display: none; + width: 100%; + margin-top: .25rem; + font-size: .875em; + color: var(--bs-form-invalid-color) +} + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: .25rem .5rem; + margin-top: .1rem; + font-size: .875rem; + color: #fff; + background-color: var(--bs-danger); + border-radius: var(--bs-border-radius) +} + +.is-invalid~.invalid-feedback, +.is-invalid~.invalid-tooltip, +.was-validated :invalid~.invalid-feedback, +.was-validated :invalid~.invalid-tooltip { + display: block +} + +.form-control.is-invalid, +.was-validated .form-control:invalid { + border-color: var(--bs-form-invalid-border-color); + padding-right: calc(1.5em + .75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(.375em + .1875rem) center; + background-size: calc(.75em + .375rem) calc(.75em + .375rem) +} + +.form-control.is-invalid:focus, +.was-validated .form-control:invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 .25rem rgba(var(--bs-danger-rgb), .25) +} + +.was-validated textarea.form-control:invalid, +textarea.form-control.is-invalid { + padding-right: calc(1.5em + .75rem); + background-position: top calc(.375em + .1875rem) right calc(.375em + .1875rem) +} + +.form-select.is-invalid, +.was-validated .form-select:invalid { + border-color: var(--bs-form-invalid-border-color) +} + +.form-select.is-invalid:not([multiple]):not([size]), +.form-select.is-invalid:not([multiple])[size="1"], +.was-validated .form-select:invalid:not([multiple]):not([size]), +.was-validated .form-select:invalid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + padding-right: 4.125rem; + background-position: right .75rem center, center right 2.25rem; + background-size: 16px 12px, calc(.75em + .375rem) calc(.75em + .375rem) +} + +.form-select.is-invalid:focus, +.was-validated .form-select:invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 .25rem rgba(var(--bs-danger-rgb), .25) +} + +.form-control-color.is-invalid, +.was-validated .form-control-color:invalid { + width: calc(3rem + calc(1.5em + .75rem)) +} + +.form-check-input.is-invalid, +.was-validated .form-check-input:invalid { + border-color: var(--bs-form-invalid-border-color) +} + +.form-check-input.is-invalid:checked, +.was-validated .form-check-input:invalid:checked { + background-color: var(--bs-form-invalid-color) +} + +.form-check-input.is-invalid:focus, +.was-validated .form-check-input:invalid:focus { + box-shadow: 0 0 0 .25rem rgba(var(--bs-danger-rgb), .25) +} + +.form-check-input.is-invalid~.form-check-label, +.was-validated .form-check-input:invalid~.form-check-label { + color: var(--bs-form-invalid-color) +} + +.form-check-inline .form-check-input~.invalid-feedback { + margin-left: .5em +} + +.input-group>.form-control:not(:focus).is-invalid, +.input-group>.form-floating:not(:focus-within).is-invalid, +.input-group>.form-select:not(:focus).is-invalid, +.was-validated .input-group>.form-control:not(:focus):invalid, +.was-validated .input-group>.form-floating:not(:focus-within):invalid, +.was-validated .input-group>.form-select:not(:focus):invalid { + z-index: 4 +} + +.btn { + --bs-btn-padding-x: 0.75rem; + --bs-btn-padding-y: 0.375rem; + --bs-btn-font-family: ; + --bs-btn-font-size: 1rem; + --bs-btn-font-weight: 400; + --bs-btn-line-height: 1.5; + --bs-btn-color: var(--bs-body-color); + --bs-btn-bg: transparent; + --bs-btn-border-width: var(--bs-border-width); + --bs-btn-border-color: transparent; + --bs-btn-border-radius: var(--bs-border-radius); + --bs-btn-hover-border-color: transparent; + --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); + --bs-btn-disabled-opacity: 0.65; + --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5); + display: inline-block; + padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x); + font-family: var(--bs-btn-font-family); + font-size: var(--bs-btn-font-size); + font-weight: var(--bs-btn-font-weight); + line-height: var(--bs-btn-line-height); + color: var(--bs-btn-color); + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + border: var(--bs-btn-border-width) solid var(--bs-btn-border-color); + border-radius: var(--bs-btn-border-radius); + background-color: var(--bs-btn-bg); + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .btn { + transition: none + } +} + +.btn:hover { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color) +} + +.btn-check+.btn:hover { + color: var(--bs-btn-color); + background-color: var(--bs-btn-bg); + border-color: var(--bs-btn-border-color) +} + +.btn:focus-visible { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow) +} + +.btn-check:focus-visible+.btn { + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow) +} + +.btn-check:checked+.btn, +.btn.active, +.btn.show, +.btn:first-child:active, +:not(.btn-check)+.btn:active { + color: var(--bs-btn-active-color); + background-color: var(--bs-btn-active-bg); + border-color: var(--bs-btn-active-border-color) +} + +.btn-check:checked+.btn:focus-visible, +.btn.active:focus-visible, +.btn.show:focus-visible, +.btn:first-child:active:focus-visible, +:not(.btn-check)+.btn:active:focus-visible { + box-shadow: var(--bs-btn-focus-box-shadow) +} + +.btn-check:checked:focus-visible+.btn { + box-shadow: var(--bs-btn-focus-box-shadow) +} + +.btn.disabled, +.btn:disabled, +fieldset:disabled .btn { + color: var(--bs-btn-disabled-color); + pointer-events: none; + background-color: var(--bs-btn-disabled-bg); + border-color: var(--bs-btn-disabled-border-color); + opacity: var(--bs-btn-disabled-opacity) +} + +.btn-primary { + --bs-btn-color: #fff; + --bs-btn-bg: #0d6efd; + --bs-btn-border-color: #0d6efd; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #0b5ed7; + --bs-btn-hover-border-color: #0a58ca; + --bs-btn-focus-shadow-rgb: 49, 132, 253; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #0a58ca; + --bs-btn-active-border-color: #0a53be; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #0d6efd; + --bs-btn-disabled-border-color: #0d6efd +} + +.btn-secondary { + --bs-btn-color: #fff; + --bs-btn-bg: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #5c636a; + --bs-btn-hover-border-color: #565e64; + --bs-btn-focus-shadow-rgb: 130, 138, 145; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #565e64; + --bs-btn-active-border-color: #51585e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #6c757d; + --bs-btn-disabled-border-color: #6c757d +} + +.btn-success { + --bs-btn-color: #fff; + --bs-btn-bg: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #157347; + --bs-btn-hover-border-color: #146c43; + --bs-btn-focus-shadow-rgb: 60, 153, 110; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #146c43; + --bs-btn-active-border-color: #13653f; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #198754; + --bs-btn-disabled-border-color: #198754 +} + +.btn-info { + --bs-btn-color: #000; + --bs-btn-bg: #0dcaf0; + --bs-btn-border-color: #0dcaf0; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #31d2f2; + --bs-btn-hover-border-color: #25cff2; + --bs-btn-focus-shadow-rgb: 11, 172, 204; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #3dd5f3; + --bs-btn-active-border-color: #25cff2; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #0dcaf0; + --bs-btn-disabled-border-color: #0dcaf0 +} + +.btn-warning { + --bs-btn-color: #000; + --bs-btn-bg: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffca2c; + --bs-btn-hover-border-color: #ffc720; + --bs-btn-focus-shadow-rgb: 217, 164, 6; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffcd39; + --bs-btn-active-border-color: #ffc720; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #ffc107; + --bs-btn-disabled-border-color: #ffc107 +} + +.btn-danger { + --bs-btn-color: #fff; + --bs-btn-bg: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #bb2d3b; + --bs-btn-hover-border-color: #b02a37; + --bs-btn-focus-shadow-rgb: 225, 83, 97; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #b02a37; + --bs-btn-active-border-color: #a52834; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #dc3545; + --bs-btn-disabled-border-color: #dc3545 +} + +.btn-light { + --bs-btn-color: #000; + --bs-btn-bg: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #d3d4d5; + --bs-btn-hover-border-color: #c6c7c8; + --bs-btn-focus-shadow-rgb: 211, 212, 213; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #c6c7c8; + --bs-btn-active-border-color: #babbbc; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #f8f9fa; + --bs-btn-disabled-border-color: #f8f9fa +} + +.btn-dark { + --bs-btn-color: #fff; + --bs-btn-bg: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #424649; + --bs-btn-hover-border-color: #373b3e; + --bs-btn-focus-shadow-rgb: 66, 70, 73; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #4d5154; + --bs-btn-active-border-color: #373b3e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #212529; + --bs-btn-disabled-border-color: #212529 +} + +.btn-outline-primary { + --bs-btn-color: #0d6efd; + --bs-btn-border-color: #0d6efd; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #0d6efd; + --bs-btn-hover-border-color: #0d6efd; + --bs-btn-focus-shadow-rgb: 13, 110, 253; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #0d6efd; + --bs-btn-active-border-color: #0d6efd; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #0d6efd; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #0d6efd; + --bs-gradient: none +} + +.btn-outline-secondary { + --bs-btn-color: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #6c757d; + --bs-btn-hover-border-color: #6c757d; + --bs-btn-focus-shadow-rgb: 108, 117, 125; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #6c757d; + --bs-btn-active-border-color: #6c757d; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #6c757d; + --bs-gradient: none +} + +.btn-outline-success { + --bs-btn-color: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #198754; + --bs-btn-hover-border-color: #198754; + --bs-btn-focus-shadow-rgb: 25, 135, 84; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #198754; + --bs-btn-active-border-color: #198754; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #198754; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #198754; + --bs-gradient: none +} + +.btn-outline-info { + --bs-btn-color: #0dcaf0; + --bs-btn-border-color: #0dcaf0; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #0dcaf0; + --bs-btn-hover-border-color: #0dcaf0; + --bs-btn-focus-shadow-rgb: 13, 202, 240; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #0dcaf0; + --bs-btn-active-border-color: #0dcaf0; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #0dcaf0; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #0dcaf0; + --bs-gradient: none +} + +.btn-outline-warning { + --bs-btn-color: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffc107; + --bs-btn-hover-border-color: #ffc107; + --bs-btn-focus-shadow-rgb: 255, 193, 7; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffc107; + --bs-btn-active-border-color: #ffc107; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #ffc107; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #ffc107; + --bs-gradient: none +} + +.btn-outline-danger { + --bs-btn-color: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #dc3545; + --bs-btn-hover-border-color: #dc3545; + --bs-btn-focus-shadow-rgb: 220, 53, 69; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #dc3545; + --bs-btn-active-border-color: #dc3545; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #dc3545; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #dc3545; + --bs-gradient: none +} + +.btn-outline-light { + --bs-btn-color: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #f8f9fa; + --bs-btn-hover-border-color: #f8f9fa; + --bs-btn-focus-shadow-rgb: 248, 249, 250; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #f8f9fa; + --bs-btn-active-border-color: #f8f9fa; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #f8f9fa; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #f8f9fa; + --bs-gradient: none +} + +.btn-outline-dark { + --bs-btn-color: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #212529; + --bs-btn-hover-border-color: #212529; + --bs-btn-focus-shadow-rgb: 33, 37, 41; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #212529; + --bs-btn-active-border-color: #212529; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #212529; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #212529; + --bs-gradient: none +} + +.btn-link { + --bs-btn-font-weight: 400; + --bs-btn-color: var(--bs-link-color); + --bs-btn-bg: transparent; + --bs-btn-border-color: transparent; + --bs-btn-hover-color: var(--bs-link-hover-color); + --bs-btn-hover-border-color: transparent; + --bs-btn-active-color: var(--bs-link-hover-color); + --bs-btn-active-border-color: transparent; + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-border-color: transparent; + --bs-btn-box-shadow: 0 0 0 #000; + --bs-btn-focus-shadow-rgb: 49, 132, 253; + text-decoration: underline +} + +.btn-link:focus-visible { + color: var(--bs-btn-color) +} + +.btn-link:hover { + color: var(--bs-btn-hover-color) +} + +.btn-group-lg>.btn, +.btn-lg { + --bs-btn-padding-y: 0.5rem; + --bs-btn-padding-x: 1rem; + --bs-btn-font-size: 1.25rem; + --bs-btn-border-radius: var(--bs-border-radius-lg) +} + +.btn-group-sm>.btn, +.btn-sm { + --bs-btn-padding-y: 0.25rem; + --bs-btn-padding-x: 0.5rem; + --bs-btn-font-size: 0.875rem; + --bs-btn-border-radius: var(--bs-border-radius-sm) +} + +.fade { + transition: opacity .15s linear +} + +@media (prefers-reduced-motion:reduce) { + .fade { + transition: none + } +} + +.fade:not(.show) { + opacity: 0 +} + +.collapse:not(.show) { + display: none +} + +.collapsing { + height: 0; + overflow: hidden; + transition: height .35s ease +} + +@media (prefers-reduced-motion:reduce) { + .collapsing { + transition: none + } +} + +.collapsing.collapse-horizontal { + width: 0; + height: auto; + transition: width .35s ease +} + +@media (prefers-reduced-motion:reduce) { + .collapsing.collapse-horizontal { + transition: none + } +} + +.dropdown, +.dropdown-center, +.dropend, +.dropstart, +.dropup, +.dropup-center { + position: relative +} + +.dropdown-toggle { + white-space: nowrap +} + +.dropdown-toggle::after { + display: inline-block; + margin-left: .255em; + vertical-align: .255em; + content: ""; + border-top: .3em solid; + border-right: .3em solid transparent; + border-bottom: 0; + border-left: .3em solid transparent +} + +.dropdown-toggle:empty::after { + margin-left: 0 +} + +.dropdown-menu { + --bs-dropdown-zindex: 1000; + --bs-dropdown-min-width: 10rem; + --bs-dropdown-padding-x: 0; + --bs-dropdown-padding-y: 0.5rem; + --bs-dropdown-spacer: 0.125rem; + --bs-dropdown-font-size: 1rem; + --bs-dropdown-color: var(--bs-body-color); + --bs-dropdown-bg: var(--bs-body-bg); + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-border-radius: var(--bs-border-radius); + --bs-dropdown-border-width: var(--bs-border-width); + --bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width)); + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-divider-margin-y: 0.5rem; + --bs-dropdown-box-shadow: var(--bs-box-shadow); + --bs-dropdown-link-color: var(--bs-body-color); + --bs-dropdown-link-hover-color: var(--bs-body-color); + --bs-dropdown-link-hover-bg: var(--bs-tertiary-bg); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: var(--bs-tertiary-color); + --bs-dropdown-item-padding-x: 1rem; + --bs-dropdown-item-padding-y: 0.25rem; + --bs-dropdown-header-color: #6c757d; + --bs-dropdown-header-padding-x: 1rem; + --bs-dropdown-header-padding-y: 0.5rem; + position: absolute; + z-index: var(--bs-dropdown-zindex); + display: none; + min-width: var(--bs-dropdown-min-width); + padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); + margin: 0; + font-size: var(--bs-dropdown-font-size); + color: var(--bs-dropdown-color); + text-align: left; + list-style: none; + background-color: var(--bs-dropdown-bg); + background-clip: padding-box; + border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); + border-radius: var(--bs-dropdown-border-radius) +} + +.dropdown-menu[data-bs-popper] { + top: 100%; + left: 0; + margin-top: var(--bs-dropdown-spacer) +} + +.dropdown-menu-start { + --bs-position: start +} + +.dropdown-menu-start[data-bs-popper] { + right: auto; + left: 0 +} + +.dropdown-menu-end { + --bs-position: end +} + +.dropdown-menu-end[data-bs-popper] { + right: 0; + left: auto +} + +@media (min-width:576px) { + .dropdown-menu-sm-start { + --bs-position: start + } + + .dropdown-menu-sm-start[data-bs-popper] { + right: auto; + left: 0 + } + + .dropdown-menu-sm-end { + --bs-position: end + } + + .dropdown-menu-sm-end[data-bs-popper] { + right: 0; + left: auto + } +} + +@media (min-width:768px) { + .dropdown-menu-md-start { + --bs-position: start + } + + .dropdown-menu-md-start[data-bs-popper] { + right: auto; + left: 0 + } + + .dropdown-menu-md-end { + --bs-position: end + } + + .dropdown-menu-md-end[data-bs-popper] { + right: 0; + left: auto + } +} + +@media (min-width:992px) { + .dropdown-menu-lg-start { + --bs-position: start + } + + .dropdown-menu-lg-start[data-bs-popper] { + right: auto; + left: 0 + } + + .dropdown-menu-lg-end { + --bs-position: end + } + + .dropdown-menu-lg-end[data-bs-popper] { + right: 0; + left: auto + } +} + +@media (min-width:1200px) { + .dropdown-menu-xl-start { + --bs-position: start + } + + .dropdown-menu-xl-start[data-bs-popper] { + right: auto; + left: 0 + } + + .dropdown-menu-xl-end { + --bs-position: end + } + + .dropdown-menu-xl-end[data-bs-popper] { + right: 0; + left: auto + } +} + +@media (min-width:1400px) { + .dropdown-menu-xxl-start { + --bs-position: start + } + + .dropdown-menu-xxl-start[data-bs-popper] { + right: auto; + left: 0 + } + + .dropdown-menu-xxl-end { + --bs-position: end + } + + .dropdown-menu-xxl-end[data-bs-popper] { + right: 0; + left: auto + } +} + +.dropup .dropdown-menu[data-bs-popper] { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: var(--bs-dropdown-spacer) +} + +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: .255em; + vertical-align: .255em; + content: ""; + border-top: 0; + border-right: .3em solid transparent; + border-bottom: .3em solid; + border-left: .3em solid transparent +} + +.dropup .dropdown-toggle:empty::after { + margin-left: 0 +} + +.dropend .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: var(--bs-dropdown-spacer) +} + +.dropend .dropdown-toggle::after { + display: inline-block; + margin-left: .255em; + vertical-align: .255em; + content: ""; + border-top: .3em solid transparent; + border-right: 0; + border-bottom: .3em solid transparent; + border-left: .3em solid +} + +.dropend .dropdown-toggle:empty::after { + margin-left: 0 +} + +.dropend .dropdown-toggle::after { + vertical-align: 0 +} + +.dropstart .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: var(--bs-dropdown-spacer) +} + +.dropstart .dropdown-toggle::after { + display: inline-block; + margin-left: .255em; + vertical-align: .255em; + content: "" +} + +.dropstart .dropdown-toggle::after { + display: none +} + +.dropstart .dropdown-toggle::before { + display: inline-block; + margin-right: .255em; + vertical-align: .255em; + content: ""; + border-top: .3em solid transparent; + border-right: .3em solid; + border-bottom: .3em solid transparent +} + +.dropstart .dropdown-toggle:empty::after { + margin-left: 0 +} + +.dropstart .dropdown-toggle::before { + vertical-align: 0 +} + +.dropdown-divider { + height: 0; + margin: var(--bs-dropdown-divider-margin-y) 0; + overflow: hidden; + border-top: 1px solid var(--bs-dropdown-divider-bg); + opacity: 1 +} + +.dropdown-item { + display: block; + width: 100%; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + clear: both; + font-weight: 400; + color: var(--bs-dropdown-link-color); + text-align: inherit; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border: 0; + border-radius: var(--bs-dropdown-item-border-radius, 0) +} + +.dropdown-item:focus, +.dropdown-item:hover { + color: var(--bs-dropdown-link-hover-color); + background-color: var(--bs-dropdown-link-hover-bg) +} + +.dropdown-item.active, +.dropdown-item:active { + color: var(--bs-dropdown-link-active-color); + text-decoration: none; + background-color: var(--bs-dropdown-link-active-bg) +} + +.dropdown-item.disabled, +.dropdown-item:disabled { + color: var(--bs-dropdown-link-disabled-color); + pointer-events: none; + background-color: transparent +} + +.dropdown-menu.show { + display: block +} + +.dropdown-header { + display: block; + padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x); + margin-bottom: 0; + font-size: .875rem; + color: var(--bs-dropdown-header-color); + white-space: nowrap +} + +.dropdown-item-text { + display: block; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + color: var(--bs-dropdown-link-color) +} + +.dropdown-menu-dark { + --bs-dropdown-color: #dee2e6; + --bs-dropdown-bg: #343a40; + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-box-shadow: ; + --bs-dropdown-link-color: #dee2e6; + --bs-dropdown-link-hover-color: #fff; + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: #adb5bd; + --bs-dropdown-header-color: #adb5bd +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle +} + +.btn-group-vertical>.btn, +.btn-group>.btn { + position: relative; + flex: 1 1 auto +} + +.btn-group-vertical>.btn-check:checked+.btn, +.btn-group-vertical>.btn-check:focus+.btn, +.btn-group-vertical>.btn.active, +.btn-group-vertical>.btn:active, +.btn-group-vertical>.btn:focus, +.btn-group-vertical>.btn:hover, +.btn-group>.btn-check:checked+.btn, +.btn-group>.btn-check:focus+.btn, +.btn-group>.btn.active, +.btn-group>.btn:active, +.btn-group>.btn:focus, +.btn-group>.btn:hover { + z-index: 1 +} + +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start +} + +.btn-toolbar .input-group { + width: auto +} + +.btn-group { + border-radius: var(--bs-border-radius) +} + +.btn-group>.btn-group:not(:first-child), +.btn-group>:not(.btn-check:first-child)+.btn { + margin-left: calc(-1 * var(--bs-border-width)) +} + +.btn-group>.btn-group:not(:last-child)>.btn, +.btn-group>.btn.dropdown-toggle-split:first-child, +.btn-group>.btn:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.btn-group>.btn-group:not(:first-child)>.btn, +.btn-group>.btn:nth-child(n+3), +.btn-group>:not(.btn-check)+.btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.dropdown-toggle-split { + padding-right: .5625rem; + padding-left: .5625rem +} + +.dropdown-toggle-split::after, +.dropend .dropdown-toggle-split::after, +.dropup .dropdown-toggle-split::after { + margin-left: 0 +} + +.dropstart .dropdown-toggle-split::before { + margin-right: 0 +} + +.btn-group-sm>.btn+.dropdown-toggle-split, +.btn-sm+.dropdown-toggle-split { + padding-right: .375rem; + padding-left: .375rem +} + +.btn-group-lg>.btn+.dropdown-toggle-split, +.btn-lg+.dropdown-toggle-split { + padding-right: .75rem; + padding-left: .75rem +} + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center +} + +.btn-group-vertical>.btn, +.btn-group-vertical>.btn-group { + width: 100% +} + +.btn-group-vertical>.btn-group:not(:first-child), +.btn-group-vertical>.btn:not(:first-child) { + margin-top: calc(-1 * var(--bs-border-width)) +} + +.btn-group-vertical>.btn-group:not(:last-child)>.btn, +.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0 +} + +.btn-group-vertical>.btn-group:not(:first-child)>.btn, +.btn-group-vertical>.btn:nth-child(n+3), +.btn-group-vertical>:not(.btn-check)+.btn { + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.nav { + --bs-nav-link-padding-x: 1rem; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-link-color); + --bs-nav-link-hover-color: var(--bs-link-hover-color); + --bs-nav-link-disabled-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none +} + +.nav-link { + display: block; + padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); + font-size: var(--bs-nav-link-font-size); + font-weight: var(--bs-nav-link-font-weight); + color: var(--bs-nav-link-color); + text-decoration: none; + background: 0 0; + border: 0; + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .nav-link { + transition: none + } +} + +.nav-link:focus, +.nav-link:hover { + color: var(--bs-nav-link-hover-color) +} + +.nav-link:focus-visible { + outline: 0; + box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25) +} + +.nav-link.disabled, +.nav-link:disabled { + color: var(--bs-nav-link-disabled-color); + pointer-events: none; + cursor: default +} + +.nav-tabs { + --bs-nav-tabs-border-width: var(--bs-border-width); + --bs-nav-tabs-border-color: var(--bs-border-color); + --bs-nav-tabs-border-radius: var(--bs-border-radius); + --bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color); + --bs-nav-tabs-link-active-color: var(--bs-emphasis-color); + --bs-nav-tabs-link-active-bg: var(--bs-body-bg); + --bs-nav-tabs-link-active-border-color: var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg); + border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color) +} + +.nav-tabs .nav-link { + margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width)); + border: var(--bs-nav-tabs-border-width) solid transparent; + border-top-left-radius: var(--bs-nav-tabs-border-radius); + border-top-right-radius: var(--bs-nav-tabs-border-radius) +} + +.nav-tabs .nav-link:focus, +.nav-tabs .nav-link:hover { + isolation: isolate; + border-color: var(--bs-nav-tabs-link-hover-border-color) +} + +.nav-tabs .nav-item.show .nav-link, +.nav-tabs .nav-link.active { + color: var(--bs-nav-tabs-link-active-color); + background-color: var(--bs-nav-tabs-link-active-bg); + border-color: var(--bs-nav-tabs-link-active-border-color) +} + +.nav-tabs .dropdown-menu { + margin-top: calc(-1 * var(--bs-nav-tabs-border-width)); + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.nav-pills { + --bs-nav-pills-border-radius: var(--bs-border-radius); + --bs-nav-pills-link-active-color: #fff; + --bs-nav-pills-link-active-bg: #0d6efd +} + +.nav-pills .nav-link { + border-radius: var(--bs-nav-pills-border-radius) +} + +.nav-pills .nav-link.active, +.nav-pills .show>.nav-link { + color: var(--bs-nav-pills-link-active-color); + background-color: var(--bs-nav-pills-link-active-bg) +} + +.nav-underline { + --bs-nav-underline-gap: 1rem; + --bs-nav-underline-border-width: 0.125rem; + --bs-nav-underline-link-active-color: var(--bs-emphasis-color); + gap: var(--bs-nav-underline-gap) +} + +.nav-underline .nav-link { + padding-right: 0; + padding-left: 0; + border-bottom: var(--bs-nav-underline-border-width) solid transparent +} + +.nav-underline .nav-link:focus, +.nav-underline .nav-link:hover { + border-bottom-color: currentcolor +} + +.nav-underline .nav-link.active, +.nav-underline .show>.nav-link { + font-weight: 700; + color: var(--bs-nav-underline-link-active-color); + border-bottom-color: currentcolor +} + +.nav-fill .nav-item, +.nav-fill>.nav-link { + flex: 1 1 auto; + text-align: center +} + +.nav-justified .nav-item, +.nav-justified>.nav-link { + flex-grow: 1; + flex-basis: 0; + text-align: center +} + +.nav-fill .nav-item .nav-link, +.nav-justified .nav-item .nav-link { + width: 100% +} + +.tab-content>.tab-pane { + display: none +} + +.tab-content>.active { + display: block +} + +.navbar { + --bs-navbar-padding-x: 0; + --bs-navbar-padding-y: 0.5rem; + --bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65); + --bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8); + --bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3); + --bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-padding-y: 0.3125rem; + --bs-navbar-brand-margin-end: 1rem; + --bs-navbar-brand-font-size: 1.25rem; + --bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-nav-link-padding-x: 0.5rem; + --bs-navbar-toggler-padding-y: 0.25rem; + --bs-navbar-toggler-padding-x: 0.75rem; + --bs-navbar-toggler-font-size: 1.25rem; + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + --bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15); + --bs-navbar-toggler-border-radius: var(--bs-border-radius); + --bs-navbar-toggler-focus-width: 0.25rem; + --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x) +} + +.navbar>.container, +.navbar>.container-fluid, +.navbar>.container-lg, +.navbar>.container-md, +.navbar>.container-sm, +.navbar>.container-xl, +.navbar>.container-xxl { + display: flex; + flex-wrap: inherit; + align-items: center; + justify-content: space-between +} + +.navbar-brand { + padding-top: var(--bs-navbar-brand-padding-y); + padding-bottom: var(--bs-navbar-brand-padding-y); + margin-right: var(--bs-navbar-brand-margin-end); + font-size: var(--bs-navbar-brand-font-size); + color: var(--bs-navbar-brand-color); + text-decoration: none; + white-space: nowrap +} + +.navbar-brand:focus, +.navbar-brand:hover { + color: var(--bs-navbar-brand-hover-color) +} + +.navbar-nav { + --bs-nav-link-padding-x: 0; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-navbar-color); + --bs-nav-link-hover-color: var(--bs-navbar-hover-color); + --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color); + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none +} + +.navbar-nav .nav-link.active, +.navbar-nav .nav-link.show { + color: var(--bs-navbar-active-color) +} + +.navbar-nav .dropdown-menu { + position: static +} + +.navbar-text { + padding-top: .5rem; + padding-bottom: .5rem; + color: var(--bs-navbar-color) +} + +.navbar-text a, +.navbar-text a:focus, +.navbar-text a:hover { + color: var(--bs-navbar-active-color) +} + +.navbar-collapse { + flex-grow: 1; + flex-basis: 100%; + align-items: center +} + +.navbar-toggler { + padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x); + font-size: var(--bs-navbar-toggler-font-size); + line-height: 1; + color: var(--bs-navbar-color); + background-color: transparent; + border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color); + border-radius: var(--bs-navbar-toggler-border-radius); + transition: var(--bs-navbar-toggler-transition) +} + +@media (prefers-reduced-motion:reduce) { + .navbar-toggler { + transition: none + } +} + +.navbar-toggler:hover { + text-decoration: none +} + +.navbar-toggler:focus { + text-decoration: none; + outline: 0; + box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width) +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + background-image: var(--bs-navbar-toggler-icon-bg); + background-repeat: no-repeat; + background-position: center; + background-size: 100% +} + +.navbar-nav-scroll { + max-height: var(--bs-scroll-height, 75vh); + overflow-y: auto +} + +@media (min-width:576px) { + .navbar-expand-sm { + flex-wrap: nowrap; + justify-content: flex-start + } + + .navbar-expand-sm .navbar-nav { + flex-direction: row + } + + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x) + } + + .navbar-expand-sm .navbar-nav-scroll { + overflow: visible + } + + .navbar-expand-sm .navbar-collapse { + display: flex !important; + flex-basis: auto + } + + .navbar-expand-sm .navbar-toggler { + display: none + } + + .navbar-expand-sm .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none + } + + .navbar-expand-sm .offcanvas .offcanvas-header { + display: none + } + + .navbar-expand-sm .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible + } +} + +@media (min-width:768px) { + .navbar-expand-md { + flex-wrap: nowrap; + justify-content: flex-start + } + + .navbar-expand-md .navbar-nav { + flex-direction: row + } + + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-md .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x) + } + + .navbar-expand-md .navbar-nav-scroll { + overflow: visible + } + + .navbar-expand-md .navbar-collapse { + display: flex !important; + flex-basis: auto + } + + .navbar-expand-md .navbar-toggler { + display: none + } + + .navbar-expand-md .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none + } + + .navbar-expand-md .offcanvas .offcanvas-header { + display: none + } + + .navbar-expand-md .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible + } +} + +@media (min-width:992px) { + .navbar-expand-lg { + flex-wrap: nowrap; + justify-content: flex-start + } + + .navbar-expand-lg .navbar-nav { + flex-direction: row + } + + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x) + } + + .navbar-expand-lg .navbar-nav-scroll { + overflow: visible + } + + .navbar-expand-lg .navbar-collapse { + display: flex !important; + flex-basis: auto + } + + .navbar-expand-lg .navbar-toggler { + display: none + } + + .navbar-expand-lg .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none + } + + .navbar-expand-lg .offcanvas .offcanvas-header { + display: none + } + + .navbar-expand-lg .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible + } +} + +@media (min-width:1200px) { + .navbar-expand-xl { + flex-wrap: nowrap; + justify-content: flex-start + } + + .navbar-expand-xl .navbar-nav { + flex-direction: row + } + + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x) + } + + .navbar-expand-xl .navbar-nav-scroll { + overflow: visible + } + + .navbar-expand-xl .navbar-collapse { + display: flex !important; + flex-basis: auto + } + + .navbar-expand-xl .navbar-toggler { + display: none + } + + .navbar-expand-xl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none + } + + .navbar-expand-xl .offcanvas .offcanvas-header { + display: none + } + + .navbar-expand-xl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible + } +} + +@media (min-width:1400px) { + .navbar-expand-xxl { + flex-wrap: nowrap; + justify-content: flex-start + } + + .navbar-expand-xxl .navbar-nav { + flex-direction: row + } + + .navbar-expand-xxl .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-xxl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x) + } + + .navbar-expand-xxl .navbar-nav-scroll { + overflow: visible + } + + .navbar-expand-xxl .navbar-collapse { + display: flex !important; + flex-basis: auto + } + + .navbar-expand-xxl .navbar-toggler { + display: none + } + + .navbar-expand-xxl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none + } + + .navbar-expand-xxl .offcanvas .offcanvas-header { + display: none + } + + .navbar-expand-xxl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible + } +} + +.navbar-expand { + flex-wrap: nowrap; + justify-content: flex-start +} + +.navbar-expand .navbar-nav { + flex-direction: row +} + +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute +} + +.navbar-expand .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x) +} + +.navbar-expand .navbar-nav-scroll { + overflow: visible +} + +.navbar-expand .navbar-collapse { + display: flex !important; + flex-basis: auto +} + +.navbar-expand .navbar-toggler { + display: none +} + +.navbar-expand .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none +} + +.navbar-expand .offcanvas .offcanvas-header { + display: none +} + +.navbar-expand .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible +} + +.navbar-dark, +.navbar[data-bs-theme=dark] { + --bs-navbar-color: rgba(255, 255, 255, 0.55); + --bs-navbar-hover-color: rgba(255, 255, 255, 0.75); + --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); + --bs-navbar-active-color: #fff; + --bs-navbar-brand-color: #fff; + --bs-navbar-brand-hover-color: #fff; + --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") +} + +[data-bs-theme=dark] .navbar-toggler-icon { + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") +} + +.card { + --bs-card-spacer-y: 1rem; + --bs-card-spacer-x: 1rem; + --bs-card-title-spacer-y: 0.5rem; + --bs-card-title-color: ; + --bs-card-subtitle-color: ; + --bs-card-border-width: var(--bs-border-width); + --bs-card-border-color: var(--bs-border-color-translucent); + --bs-card-border-radius: var(--bs-border-radius); + --bs-card-box-shadow: ; + --bs-card-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width))); + --bs-card-cap-padding-y: 0.5rem; + --bs-card-cap-padding-x: 1rem; + --bs-card-cap-bg: rgba(var(--bs-body-color-rgb), 0.03); + --bs-card-cap-color: ; + --bs-card-height: ; + --bs-card-color: ; + --bs-card-bg: var(--bs-body-bg); + --bs-card-img-overlay-padding: 1rem; + --bs-card-group-margin: 0.75rem; + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + height: var(--bs-card-height); + color: var(--bs-body-color); + word-wrap: break-word; + background-color: var(--bs-card-bg); + background-clip: border-box; + border: var(--bs-card-border-width) solid var(--bs-card-border-color); + border-radius: var(--bs-card-border-radius) +} + +.card>hr { + margin-right: 0; + margin-left: 0 +} + +.card>.list-group { + border-top: inherit; + border-bottom: inherit +} + +.card>.list-group:first-child { + border-top-width: 0; + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius) +} + +.card>.list-group:last-child { + border-bottom-width: 0; + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius) +} + +.card>.card-header+.list-group, +.card>.list-group+.card-footer { + border-top: 0 +} + +.card-body { + flex: 1 1 auto; + padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); + color: var(--bs-card-color) +} + +.card-title { + margin-bottom: var(--bs-card-title-spacer-y); + color: var(--bs-card-title-color) +} + +.card-subtitle { + margin-top: calc(-.5 * var(--bs-card-title-spacer-y)); + margin-bottom: 0; + color: var(--bs-card-subtitle-color) +} + +.card-text:last-child { + margin-bottom: 0 +} + +.card-link+.card-link { + margin-left: var(--bs-card-spacer-x) +} + +.card-header { + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + margin-bottom: 0; + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color) +} + +.card-header:first-child { + border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0 +} + +.card-footer { + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-top: var(--bs-card-border-width) solid var(--bs-card-border-color) +} + +.card-footer:last-child { + border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) +} + +.card-header-tabs { + margin-right: calc(-.5 * var(--bs-card-cap-padding-x)); + margin-bottom: calc(-1 * var(--bs-card-cap-padding-y)); + margin-left: calc(-.5 * var(--bs-card-cap-padding-x)); + border-bottom: 0 +} + +.card-header-tabs .nav-link.active { + background-color: var(--bs-card-bg); + border-bottom-color: var(--bs-card-bg) +} + +.card-header-pills { + margin-right: calc(-.5 * var(--bs-card-cap-padding-x)); + margin-left: calc(-.5 * var(--bs-card-cap-padding-x)) +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: var(--bs-card-img-overlay-padding); + border-radius: var(--bs-card-inner-border-radius) +} + +.card-img, +.card-img-bottom, +.card-img-top { + width: 100% +} + +.card-img, +.card-img-top { + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius) +} + +.card-img, +.card-img-bottom { + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius) +} + +.card-group>.card { + margin-bottom: var(--bs-card-group-margin) +} + +@media (min-width:576px) { + .card-group { + display: flex; + flex-flow: row wrap + } + + .card-group>.card { + flex: 1 0 0; + margin-bottom: 0 + } + + .card-group>.card+.card { + margin-left: 0; + border-left: 0 + } + + .card-group>.card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0 + } + + .card-group>.card:not(:last-child)>.card-header, + .card-group>.card:not(:last-child)>.card-img-top { + border-top-right-radius: 0 + } + + .card-group>.card:not(:last-child)>.card-footer, + .card-group>.card:not(:last-child)>.card-img-bottom { + border-bottom-right-radius: 0 + } + + .card-group>.card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0 + } + + .card-group>.card:not(:first-child)>.card-header, + .card-group>.card:not(:first-child)>.card-img-top { + border-top-left-radius: 0 + } + + .card-group>.card:not(:first-child)>.card-footer, + .card-group>.card:not(:first-child)>.card-img-bottom { + border-bottom-left-radius: 0 + } +} + +.accordion { + --bs-accordion-color: var(--bs-body-color); + --bs-accordion-bg: var(--bs-body-bg); + --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease; + --bs-accordion-border-color: var(--bs-border-color); + --bs-accordion-border-width: var(--bs-border-width); + --bs-accordion-border-radius: var(--bs-border-radius); + --bs-accordion-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width))); + --bs-accordion-btn-padding-x: 1.25rem; + --bs-accordion-btn-padding-y: 1rem; + --bs-accordion-btn-color: var(--bs-body-color); + --bs-accordion-btn-bg: var(--bs-accordion-bg); + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); + --bs-accordion-btn-icon-width: 1.25rem; + --bs-accordion-btn-icon-transform: rotate(-180deg); + --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23052c65' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); + --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-accordion-body-padding-x: 1.25rem; + --bs-accordion-body-padding-y: 1rem; + --bs-accordion-active-color: var(--bs-primary-text-emphasis); + --bs-accordion-active-bg: var(--bs-primary-bg-subtle) +} + +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x); + font-size: 1rem; + color: var(--bs-accordion-btn-color); + text-align: left; + background-color: var(--bs-accordion-btn-bg); + border: 0; + border-radius: 0; + overflow-anchor: none; + transition: var(--bs-accordion-transition) +} + +@media (prefers-reduced-motion:reduce) { + .accordion-button { + transition: none + } +} + +.accordion-button:not(.collapsed) { + color: var(--bs-accordion-active-color); + background-color: var(--bs-accordion-active-bg); + box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color) +} + +.accordion-button:not(.collapsed)::after { + background-image: var(--bs-accordion-btn-active-icon); + transform: var(--bs-accordion-btn-icon-transform) +} + +.accordion-button::after { + flex-shrink: 0; + width: var(--bs-accordion-btn-icon-width); + height: var(--bs-accordion-btn-icon-width); + margin-left: auto; + content: ""; + background-image: var(--bs-accordion-btn-icon); + background-repeat: no-repeat; + background-size: var(--bs-accordion-btn-icon-width); + transition: var(--bs-accordion-btn-icon-transition) +} + +@media (prefers-reduced-motion:reduce) { + .accordion-button::after { + transition: none + } +} + +.accordion-button:hover { + z-index: 2 +} + +.accordion-button:focus { + z-index: 3; + outline: 0; + box-shadow: var(--bs-accordion-btn-focus-box-shadow) +} + +.accordion-header { + margin-bottom: 0 +} + +.accordion-item { + color: var(--bs-accordion-color); + background-color: var(--bs-accordion-bg); + border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color) +} + +.accordion-item:first-of-type { + border-top-left-radius: var(--bs-accordion-border-radius); + border-top-right-radius: var(--bs-accordion-border-radius) +} + +.accordion-item:first-of-type>.accordion-header .accordion-button { + border-top-left-radius: var(--bs-accordion-inner-border-radius); + border-top-right-radius: var(--bs-accordion-inner-border-radius) +} + +.accordion-item:not(:first-of-type) { + border-top: 0 +} + +.accordion-item:last-of-type { + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius) +} + +.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed { + border-bottom-right-radius: var(--bs-accordion-inner-border-radius); + border-bottom-left-radius: var(--bs-accordion-inner-border-radius) +} + +.accordion-item:last-of-type>.accordion-collapse { + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius) +} + +.accordion-body { + padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x) +} + +.accordion-flush>.accordion-item { + border-right: 0; + border-left: 0; + border-radius: 0 +} + +.accordion-flush>.accordion-item:first-child { + border-top: 0 +} + +.accordion-flush>.accordion-item:last-child { + border-bottom: 0 +} + +.accordion-flush>.accordion-item>.accordion-collapse, +.accordion-flush>.accordion-item>.accordion-header .accordion-button, +.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed { + border-radius: 0 +} + +[data-bs-theme=dark] .accordion-button::after { + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e"); + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e") +} + +.breadcrumb { + --bs-breadcrumb-padding-x: 0; + --bs-breadcrumb-padding-y: 0; + --bs-breadcrumb-margin-bottom: 1rem; + --bs-breadcrumb-bg: ; + --bs-breadcrumb-border-radius: ; + --bs-breadcrumb-divider-color: var(--bs-secondary-color); + --bs-breadcrumb-item-padding-x: 0.5rem; + --bs-breadcrumb-item-active-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x); + margin-bottom: var(--bs-breadcrumb-margin-bottom); + font-size: var(--bs-breadcrumb-font-size); + list-style: none; + background-color: var(--bs-breadcrumb-bg); + border-radius: var(--bs-breadcrumb-border-radius) +} + +.breadcrumb-item+.breadcrumb-item { + padding-left: var(--bs-breadcrumb-item-padding-x) +} + +.breadcrumb-item+.breadcrumb-item::before { + float: left; + padding-right: var(--bs-breadcrumb-item-padding-x); + color: var(--bs-breadcrumb-divider-color); + content: var(--bs-breadcrumb-divider, "/") +} + +.breadcrumb-item.active { + color: var(--bs-breadcrumb-item-active-color) +} + +.pagination { + --bs-pagination-padding-x: 0.75rem; + --bs-pagination-padding-y: 0.375rem; + --bs-pagination-font-size: 1rem; + --bs-pagination-color: var(--bs-link-color); + --bs-pagination-bg: var(--bs-body-bg); + --bs-pagination-border-width: var(--bs-border-width); + --bs-pagination-border-color: var(--bs-border-color); + --bs-pagination-border-radius: var(--bs-border-radius); + --bs-pagination-hover-color: var(--bs-link-hover-color); + --bs-pagination-hover-bg: var(--bs-tertiary-bg); + --bs-pagination-hover-border-color: var(--bs-border-color); + --bs-pagination-focus-color: var(--bs-link-hover-color); + --bs-pagination-focus-bg: var(--bs-secondary-bg); + --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-pagination-active-color: #fff; + --bs-pagination-active-bg: #0d6efd; + --bs-pagination-active-border-color: #0d6efd; + --bs-pagination-disabled-color: var(--bs-secondary-color); + --bs-pagination-disabled-bg: var(--bs-secondary-bg); + --bs-pagination-disabled-border-color: var(--bs-border-color); + display: flex; + padding-left: 0; + list-style: none +} + +.page-link { + position: relative; + display: block; + padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x); + font-size: var(--bs-pagination-font-size); + color: var(--bs-pagination-color); + text-decoration: none; + background-color: var(--bs-pagination-bg); + border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color); + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .page-link { + transition: none + } +} + +.page-link:hover { + z-index: 2; + color: var(--bs-pagination-hover-color); + background-color: var(--bs-pagination-hover-bg); + border-color: var(--bs-pagination-hover-border-color) +} + +.page-link:focus { + z-index: 3; + color: var(--bs-pagination-focus-color); + background-color: var(--bs-pagination-focus-bg); + outline: 0; + box-shadow: var(--bs-pagination-focus-box-shadow) +} + +.active>.page-link, +.page-link.active { + z-index: 3; + color: var(--bs-pagination-active-color); + background-color: var(--bs-pagination-active-bg); + border-color: var(--bs-pagination-active-border-color) +} + +.disabled>.page-link, +.page-link.disabled { + color: var(--bs-pagination-disabled-color); + pointer-events: none; + background-color: var(--bs-pagination-disabled-bg); + border-color: var(--bs-pagination-disabled-border-color) +} + +.page-item:not(:first-child) .page-link { + margin-left: calc(-1 * var(--bs-border-width)) +} + +.page-item:first-child .page-link { + border-top-left-radius: var(--bs-pagination-border-radius); + border-bottom-left-radius: var(--bs-pagination-border-radius) +} + +.page-item:last-child .page-link { + border-top-right-radius: var(--bs-pagination-border-radius); + border-bottom-right-radius: var(--bs-pagination-border-radius) +} + +.pagination-lg { + --bs-pagination-padding-x: 1.5rem; + --bs-pagination-padding-y: 0.75rem; + --bs-pagination-font-size: 1.25rem; + --bs-pagination-border-radius: var(--bs-border-radius-lg) +} + +.pagination-sm { + --bs-pagination-padding-x: 0.5rem; + --bs-pagination-padding-y: 0.25rem; + --bs-pagination-font-size: 0.875rem; + --bs-pagination-border-radius: var(--bs-border-radius-sm) +} + +.badge { + --bs-badge-padding-x: 0.65em; + --bs-badge-padding-y: 0.35em; + --bs-badge-font-size: 0.75em; + --bs-badge-font-weight: 700; + --bs-badge-color: #fff; + --bs-badge-border-radius: var(--bs-border-radius); + display: inline-block; + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: var(--bs-badge-border-radius) +} + +.badge:empty { + display: none +} + +.btn .badge { + position: relative; + top: -1px +} + +.alert { + --bs-alert-bg: transparent; + --bs-alert-padding-x: 1rem; + --bs-alert-padding-y: 1rem; + --bs-alert-margin-bottom: 1rem; + --bs-alert-color: inherit; + --bs-alert-border-color: transparent; + --bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color); + --bs-alert-border-radius: var(--bs-border-radius); + --bs-alert-link-color: inherit; + position: relative; + padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); + margin-bottom: var(--bs-alert-margin-bottom); + color: var(--bs-alert-color); + background-color: var(--bs-alert-bg); + border: var(--bs-alert-border); + border-radius: var(--bs-alert-border-radius) +} + +.alert-heading { + color: inherit +} + +.alert-link { + font-weight: 700; + color: var(--bs-alert-link-color) +} + +.alert-dismissible { + padding-right: 3rem +} + +.alert-dismissible .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: 2; + padding: 1.25rem 1rem +} + +.alert-primary { + --bs-alert-color: var(--bs-primary-text-emphasis); + --bs-alert-bg: var(--bs-primary-bg-subtle); + --bs-alert-border-color: var(--bs-primary-border-subtle); + --bs-alert-link-color: var(--bs-primary-text-emphasis) +} + +.alert-secondary { + --bs-alert-color: var(--bs-secondary-text-emphasis); + --bs-alert-bg: var(--bs-secondary-bg-subtle); + --bs-alert-border-color: var(--bs-secondary-border-subtle); + --bs-alert-link-color: var(--bs-secondary-text-emphasis) +} + +.alert-success { + --bs-alert-color: var(--bs-success-text-emphasis); + --bs-alert-bg: var(--bs-success-bg-subtle); + --bs-alert-border-color: var(--bs-success-border-subtle); + --bs-alert-link-color: var(--bs-success-text-emphasis) +} + +.alert-info { + --bs-alert-color: var(--bs-info-text-emphasis); + --bs-alert-bg: var(--bs-info-bg-subtle); + --bs-alert-border-color: var(--bs-info-border-subtle); + --bs-alert-link-color: var(--bs-info-text-emphasis) +} + +.alert-warning { + --bs-alert-color: var(--bs-warning-text-emphasis); + --bs-alert-bg: var(--bs-warning-bg-subtle); + --bs-alert-border-color: var(--bs-warning-border-subtle); + --bs-alert-link-color: var(--bs-warning-text-emphasis) +} + +.alert-danger { + --bs-alert-color: var(--bs-danger-text-emphasis); + --bs-alert-bg: var(--bs-danger-bg-subtle); + --bs-alert-border-color: var(--bs-danger-border-subtle); + --bs-alert-link-color: var(--bs-danger-text-emphasis) +} + +.alert-light { + --bs-alert-color: var(--bs-light-text-emphasis); + --bs-alert-bg: var(--bs-light-bg-subtle); + --bs-alert-border-color: var(--bs-light-border-subtle); + --bs-alert-link-color: var(--bs-light-text-emphasis) +} + +.alert-dark { + --bs-alert-color: var(--bs-dark-text-emphasis); + --bs-alert-bg: var(--bs-dark-bg-subtle); + --bs-alert-border-color: var(--bs-dark-border-subtle); + --bs-alert-link-color: var(--bs-dark-text-emphasis) +} + +@keyframes progress-bar-stripes { + 0% { + background-position-x: var(--bs-progress-height) + } +} + +.progress, +.progress-stacked { + --bs-progress-height: 1rem; + --bs-progress-font-size: 0.75rem; + --bs-progress-bg: var(--bs-secondary-bg); + --bs-progress-border-radius: var(--bs-border-radius); + --bs-progress-box-shadow: var(--bs-box-shadow-inset); + --bs-progress-bar-color: #fff; + --bs-progress-bar-bg: #0d6efd; + --bs-progress-bar-transition: width 0.6s ease; + display: flex; + height: var(--bs-progress-height); + overflow: hidden; + font-size: var(--bs-progress-font-size); + background-color: var(--bs-progress-bg); + border-radius: var(--bs-progress-border-radius) +} + +.progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + color: var(--bs-progress-bar-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-progress-bar-bg); + transition: var(--bs-progress-bar-transition) +} + +@media (prefers-reduced-motion:reduce) { + .progress-bar { + transition: none + } +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-size: var(--bs-progress-height) var(--bs-progress-height) +} + +.progress-stacked>.progress { + overflow: visible +} + +.progress-stacked>.progress>.progress-bar { + width: 100% +} + +.progress-bar-animated { + animation: 1s linear infinite progress-bar-stripes +} + +@media (prefers-reduced-motion:reduce) { + .progress-bar-animated { + animation: none + } +} + +.list-group { + --bs-list-group-color: var(--bs-body-color); + --bs-list-group-bg: var(--bs-body-bg); + --bs-list-group-border-color: var(--bs-border-color); + --bs-list-group-border-width: var(--bs-border-width); + --bs-list-group-border-radius: var(--bs-border-radius); + --bs-list-group-item-padding-x: 1rem; + --bs-list-group-item-padding-y: 0.5rem; + --bs-list-group-action-color: var(--bs-secondary-color); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-tertiary-bg); + --bs-list-group-action-active-color: var(--bs-body-color); + --bs-list-group-action-active-bg: var(--bs-secondary-bg); + --bs-list-group-disabled-color: var(--bs-secondary-color); + --bs-list-group-disabled-bg: var(--bs-body-bg); + --bs-list-group-active-color: #fff; + --bs-list-group-active-bg: #0d6efd; + --bs-list-group-active-border-color: #0d6efd; + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + border-radius: var(--bs-list-group-border-radius) +} + +.list-group-numbered { + list-style-type: none; + counter-reset: section +} + +.list-group-numbered>.list-group-item::before { + content: counters(section, ".") ". "; + counter-increment: section +} + +.list-group-item { + position: relative; + display: block; + padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x); + color: var(--bs-list-group-color); + text-decoration: none; + background-color: var(--bs-list-group-bg); + border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color) +} + +.list-group-item:first-child { + border-top-left-radius: inherit; + border-top-right-radius: inherit +} + +.list-group-item:last-child { + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit +} + +.list-group-item.disabled, +.list-group-item:disabled { + color: var(--bs-list-group-disabled-color); + pointer-events: none; + background-color: var(--bs-list-group-disabled-bg) +} + +.list-group-item.active { + z-index: 2; + color: var(--bs-list-group-active-color); + background-color: var(--bs-list-group-active-bg); + border-color: var(--bs-list-group-active-border-color) +} + +.list-group-item+.list-group-item { + border-top-width: 0 +} + +.list-group-item+.list-group-item.active { + margin-top: calc(-1 * var(--bs-list-group-border-width)); + border-top-width: var(--bs-list-group-border-width) +} + +.list-group-item-action { + width: 100%; + color: var(--bs-list-group-action-color); + text-align: inherit +} + +.list-group-item-action:not(.active):focus, +.list-group-item-action:not(.active):hover { + z-index: 1; + color: var(--bs-list-group-action-hover-color); + text-decoration: none; + background-color: var(--bs-list-group-action-hover-bg) +} + +.list-group-item-action:not(.active):active { + color: var(--bs-list-group-action-active-color); + background-color: var(--bs-list-group-action-active-bg) +} + +.list-group-horizontal { + flex-direction: row +} + +.list-group-horizontal>.list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0 +} + +.list-group-horizontal>.list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0 +} + +.list-group-horizontal>.list-group-item.active { + margin-top: 0 +} + +.list-group-horizontal>.list-group-item+.list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0 +} + +.list-group-horizontal>.list-group-item+.list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width) +} + +@media (min-width:576px) { + .list-group-horizontal-sm { + flex-direction: row + } + + .list-group-horizontal-sm>.list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0 + } + + .list-group-horizontal-sm>.list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0 + } + + .list-group-horizontal-sm>.list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-sm>.list-group-item+.list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0 + } + + .list-group-horizontal-sm>.list-group-item+.list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width) + } +} + +@media (min-width:768px) { + .list-group-horizontal-md { + flex-direction: row + } + + .list-group-horizontal-md>.list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0 + } + + .list-group-horizontal-md>.list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0 + } + + .list-group-horizontal-md>.list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-md>.list-group-item+.list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0 + } + + .list-group-horizontal-md>.list-group-item+.list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width) + } +} + +@media (min-width:992px) { + .list-group-horizontal-lg { + flex-direction: row + } + + .list-group-horizontal-lg>.list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0 + } + + .list-group-horizontal-lg>.list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0 + } + + .list-group-horizontal-lg>.list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-lg>.list-group-item+.list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0 + } + + .list-group-horizontal-lg>.list-group-item+.list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width) + } +} + +@media (min-width:1200px) { + .list-group-horizontal-xl { + flex-direction: row + } + + .list-group-horizontal-xl>.list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0 + } + + .list-group-horizontal-xl>.list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0 + } + + .list-group-horizontal-xl>.list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-xl>.list-group-item+.list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0 + } + + .list-group-horizontal-xl>.list-group-item+.list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width) + } +} + +@media (min-width:1400px) { + .list-group-horizontal-xxl { + flex-direction: row + } + + .list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0 + } + + .list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0 + } + + .list-group-horizontal-xxl>.list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-xxl>.list-group-item+.list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0 + } + + .list-group-horizontal-xxl>.list-group-item+.list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width) + } +} + +.list-group-flush { + border-radius: 0 +} + +.list-group-flush>.list-group-item { + border-width: 0 0 var(--bs-list-group-border-width) +} + +.list-group-flush>.list-group-item:last-child { + border-bottom-width: 0 +} + +.list-group-item-primary { + --bs-list-group-color: var(--bs-primary-text-emphasis); + --bs-list-group-bg: var(--bs-primary-bg-subtle); + --bs-list-group-border-color: var(--bs-primary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-primary-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-primary-border-subtle); + --bs-list-group-active-color: var(--bs-primary-bg-subtle); + --bs-list-group-active-bg: var(--bs-primary-text-emphasis); + --bs-list-group-active-border-color: var(--bs-primary-text-emphasis) +} + +.list-group-item-secondary { + --bs-list-group-color: var(--bs-secondary-text-emphasis); + --bs-list-group-bg: var(--bs-secondary-bg-subtle); + --bs-list-group-border-color: var(--bs-secondary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-secondary-border-subtle); + --bs-list-group-active-color: var(--bs-secondary-bg-subtle); + --bs-list-group-active-bg: var(--bs-secondary-text-emphasis); + --bs-list-group-active-border-color: var(--bs-secondary-text-emphasis) +} + +.list-group-item-success { + --bs-list-group-color: var(--bs-success-text-emphasis); + --bs-list-group-bg: var(--bs-success-bg-subtle); + --bs-list-group-border-color: var(--bs-success-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-success-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-success-border-subtle); + --bs-list-group-active-color: var(--bs-success-bg-subtle); + --bs-list-group-active-bg: var(--bs-success-text-emphasis); + --bs-list-group-active-border-color: var(--bs-success-text-emphasis) +} + +.list-group-item-info { + --bs-list-group-color: var(--bs-info-text-emphasis); + --bs-list-group-bg: var(--bs-info-bg-subtle); + --bs-list-group-border-color: var(--bs-info-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-info-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-info-border-subtle); + --bs-list-group-active-color: var(--bs-info-bg-subtle); + --bs-list-group-active-bg: var(--bs-info-text-emphasis); + --bs-list-group-active-border-color: var(--bs-info-text-emphasis) +} + +.list-group-item-warning { + --bs-list-group-color: var(--bs-warning-text-emphasis); + --bs-list-group-bg: var(--bs-warning-bg-subtle); + --bs-list-group-border-color: var(--bs-warning-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-warning-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-warning-border-subtle); + --bs-list-group-active-color: var(--bs-warning-bg-subtle); + --bs-list-group-active-bg: var(--bs-warning-text-emphasis); + --bs-list-group-active-border-color: var(--bs-warning-text-emphasis) +} + +.list-group-item-danger { + --bs-list-group-color: var(--bs-danger-text-emphasis); + --bs-list-group-bg: var(--bs-danger-bg-subtle); + --bs-list-group-border-color: var(--bs-danger-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-danger-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-danger-border-subtle); + --bs-list-group-active-color: var(--bs-danger-bg-subtle); + --bs-list-group-active-bg: var(--bs-danger-text-emphasis); + --bs-list-group-active-border-color: var(--bs-danger-text-emphasis) +} + +.list-group-item-light { + --bs-list-group-color: var(--bs-light-text-emphasis); + --bs-list-group-bg: var(--bs-light-bg-subtle); + --bs-list-group-border-color: var(--bs-light-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-light-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-light-border-subtle); + --bs-list-group-active-color: var(--bs-light-bg-subtle); + --bs-list-group-active-bg: var(--bs-light-text-emphasis); + --bs-list-group-active-border-color: var(--bs-light-text-emphasis) +} + +.list-group-item-dark { + --bs-list-group-color: var(--bs-dark-text-emphasis); + --bs-list-group-bg: var(--bs-dark-bg-subtle); + --bs-list-group-border-color: var(--bs-dark-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-dark-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-dark-border-subtle); + --bs-list-group-active-color: var(--bs-dark-bg-subtle); + --bs-list-group-active-bg: var(--bs-dark-text-emphasis); + --bs-list-group-active-border-color: var(--bs-dark-text-emphasis) +} + +.btn-close { + --bs-btn-close-color: #000; + --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e"); + --bs-btn-close-opacity: 0.5; + --bs-btn-close-hover-opacity: 0.75; + --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-btn-close-focus-opacity: 1; + --bs-btn-close-disabled-opacity: 0.25; + box-sizing: content-box; + width: 1em; + height: 1em; + padding: .25em .25em; + color: var(--bs-btn-close-color); + background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat; + filter: var(--bs-btn-close-filter); + border: 0; + border-radius: .375rem; + opacity: var(--bs-btn-close-opacity) +} + +.btn-close:hover { + color: var(--bs-btn-close-color); + text-decoration: none; + opacity: var(--bs-btn-close-hover-opacity) +} + +.btn-close:focus { + outline: 0; + box-shadow: var(--bs-btn-close-focus-shadow); + opacity: var(--bs-btn-close-focus-opacity) +} + +.btn-close.disabled, +.btn-close:disabled { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + opacity: var(--bs-btn-close-disabled-opacity) +} + +.btn-close-white { + --bs-btn-close-filter: invert(1) grayscale(100%) brightness(200%) +} + +:root, +[data-bs-theme=light] { + --bs-btn-close-filter: +} + +[data-bs-theme=dark] { + --bs-btn-close-filter: invert(1) grayscale(100%) brightness(200%) +} + +.toast { + --bs-toast-zindex: 1090; + --bs-toast-padding-x: 0.75rem; + --bs-toast-padding-y: 0.5rem; + --bs-toast-spacing: 1.5rem; + --bs-toast-max-width: 350px; + --bs-toast-font-size: 0.875rem; + --bs-toast-color: ; + --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-border-width: var(--bs-border-width); + --bs-toast-border-color: var(--bs-border-color-translucent); + --bs-toast-border-radius: var(--bs-border-radius); + --bs-toast-box-shadow: var(--bs-box-shadow); + --bs-toast-header-color: var(--bs-secondary-color); + --bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-header-border-color: var(--bs-border-color-translucent); + width: var(--bs-toast-max-width); + max-width: 100%; + font-size: var(--bs-toast-font-size); + color: var(--bs-toast-color); + pointer-events: auto; + background-color: var(--bs-toast-bg); + background-clip: padding-box; + border: var(--bs-toast-border-width) solid var(--bs-toast-border-color); + box-shadow: var(--bs-toast-box-shadow); + border-radius: var(--bs-toast-border-radius) +} + +.toast.showing { + opacity: 0 +} + +.toast:not(.show) { + display: none +} + +.toast-container { + --bs-toast-zindex: 1090; + position: absolute; + z-index: var(--bs-toast-zindex); + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + max-width: 100%; + pointer-events: none +} + +.toast-container>:not(:last-child) { + margin-bottom: var(--bs-toast-spacing) +} + +.toast-header { + display: flex; + align-items: center; + padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x); + color: var(--bs-toast-header-color); + background-color: var(--bs-toast-header-bg); + background-clip: padding-box; + border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color); + border-top-left-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)); + border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)) +} + +.toast-header .btn-close { + margin-right: calc(-.5 * var(--bs-toast-padding-x)); + margin-left: var(--bs-toast-padding-x) +} + +.toast-body { + padding: var(--bs-toast-padding-x); + word-wrap: break-word +} + +.modal { + --bs-modal-zindex: 1055; + --bs-modal-width: 500px; + --bs-modal-padding: 1rem; + --bs-modal-margin: 0.5rem; + --bs-modal-color: var(--bs-body-color); + --bs-modal-bg: var(--bs-body-bg); + --bs-modal-border-color: var(--bs-border-color-translucent); + --bs-modal-border-width: var(--bs-border-width); + --bs-modal-border-radius: var(--bs-border-radius-lg); + --bs-modal-box-shadow: var(--bs-box-shadow-sm); + --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width))); + --bs-modal-header-padding-x: 1rem; + --bs-modal-header-padding-y: 1rem; + --bs-modal-header-padding: 1rem 1rem; + --bs-modal-header-border-color: var(--bs-border-color); + --bs-modal-header-border-width: var(--bs-border-width); + --bs-modal-title-line-height: 1.5; + --bs-modal-footer-gap: 0.5rem; + --bs-modal-footer-bg: ; + --bs-modal-footer-border-color: var(--bs-border-color); + --bs-modal-footer-border-width: var(--bs-border-width); + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-modal-zindex); + display: none; + width: 100%; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + outline: 0 +} + +.modal-dialog { + position: relative; + width: auto; + margin: var(--bs-modal-margin); + pointer-events: none +} + +.modal.fade .modal-dialog { + transform: translate(0, -50px); + transition: transform .3s ease-out +} + +@media (prefers-reduced-motion:reduce) { + .modal.fade .modal-dialog { + transition: none + } +} + +.modal.show .modal-dialog { + transform: none +} + +.modal.modal-static .modal-dialog { + transform: scale(1.02) +} + +.modal-dialog-scrollable { + height: calc(100% - var(--bs-modal-margin) * 2) +} + +.modal-dialog-scrollable .modal-content { + max-height: 100%; + overflow: hidden +} + +.modal-dialog-scrollable .modal-body { + overflow-y: auto +} + +.modal-dialog-centered { + display: flex; + align-items: center; + min-height: calc(100% - var(--bs-modal-margin) * 2) +} + +.modal-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + color: var(--bs-modal-color); + pointer-events: auto; + background-color: var(--bs-modal-bg); + background-clip: padding-box; + border: var(--bs-modal-border-width) solid var(--bs-modal-border-color); + border-radius: var(--bs-modal-border-radius); + outline: 0 +} + +.modal-backdrop { + --bs-backdrop-zindex: 1050; + --bs-backdrop-bg: #000; + --bs-backdrop-opacity: 0.5; + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-backdrop-zindex); + width: 100vw; + height: 100vh; + background-color: var(--bs-backdrop-bg) +} + +.modal-backdrop.fade { + opacity: 0 +} + +.modal-backdrop.show { + opacity: var(--bs-backdrop-opacity) +} + +.modal-header { + display: flex; + flex-shrink: 0; + align-items: center; + padding: var(--bs-modal-header-padding); + border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color); + border-top-left-radius: var(--bs-modal-inner-border-radius); + border-top-right-radius: var(--bs-modal-inner-border-radius) +} + +.modal-header .btn-close { + padding: calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5); + margin-top: calc(-.5 * var(--bs-modal-header-padding-y)); + margin-right: calc(-.5 * var(--bs-modal-header-padding-x)); + margin-bottom: calc(-.5 * var(--bs-modal-header-padding-y)); + margin-left: auto +} + +.modal-title { + margin-bottom: 0; + line-height: var(--bs-modal-title-line-height) +} + +.modal-body { + position: relative; + flex: 1 1 auto; + padding: var(--bs-modal-padding) +} + +.modal-footer { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5); + background-color: var(--bs-modal-footer-bg); + border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color); + border-bottom-right-radius: var(--bs-modal-inner-border-radius); + border-bottom-left-radius: var(--bs-modal-inner-border-radius) +} + +.modal-footer>* { + margin: calc(var(--bs-modal-footer-gap) * .5) +} + +@media (min-width:576px) { + .modal { + --bs-modal-margin: 1.75rem; + --bs-modal-box-shadow: var(--bs-box-shadow) + } + + .modal-dialog { + max-width: var(--bs-modal-width); + margin-right: auto; + margin-left: auto + } + + .modal-sm { + --bs-modal-width: 300px + } +} + +@media (min-width:992px) { + + .modal-lg, + .modal-xl { + --bs-modal-width: 800px + } +} + +@media (min-width:1200px) { + .modal-xl { + --bs-modal-width: 1140px + } +} + +.modal-fullscreen { + width: 100vw; + max-width: none; + height: 100%; + margin: 0 +} + +.modal-fullscreen .modal-content { + height: 100%; + border: 0; + border-radius: 0 +} + +.modal-fullscreen .modal-footer, +.modal-fullscreen .modal-header { + border-radius: 0 +} + +.modal-fullscreen .modal-body { + overflow-y: auto +} + +@media (max-width:575.98px) { + .modal-fullscreen-sm-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0 + } + + .modal-fullscreen-sm-down .modal-content { + height: 100%; + border: 0; + border-radius: 0 + } + + .modal-fullscreen-sm-down .modal-footer, + .modal-fullscreen-sm-down .modal-header { + border-radius: 0 + } + + .modal-fullscreen-sm-down .modal-body { + overflow-y: auto + } +} + +@media (max-width:767.98px) { + .modal-fullscreen-md-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0 + } + + .modal-fullscreen-md-down .modal-content { + height: 100%; + border: 0; + border-radius: 0 + } + + .modal-fullscreen-md-down .modal-footer, + .modal-fullscreen-md-down .modal-header { + border-radius: 0 + } + + .modal-fullscreen-md-down .modal-body { + overflow-y: auto + } +} + +@media (max-width:991.98px) { + .modal-fullscreen-lg-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0 + } + + .modal-fullscreen-lg-down .modal-content { + height: 100%; + border: 0; + border-radius: 0 + } + + .modal-fullscreen-lg-down .modal-footer, + .modal-fullscreen-lg-down .modal-header { + border-radius: 0 + } + + .modal-fullscreen-lg-down .modal-body { + overflow-y: auto + } +} + +@media (max-width:1199.98px) { + .modal-fullscreen-xl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0 + } + + .modal-fullscreen-xl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0 + } + + .modal-fullscreen-xl-down .modal-footer, + .modal-fullscreen-xl-down .modal-header { + border-radius: 0 + } + + .modal-fullscreen-xl-down .modal-body { + overflow-y: auto + } +} + +@media (max-width:1399.98px) { + .modal-fullscreen-xxl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0 + } + + .modal-fullscreen-xxl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0 + } + + .modal-fullscreen-xxl-down .modal-footer, + .modal-fullscreen-xxl-down .modal-header { + border-radius: 0 + } + + .modal-fullscreen-xxl-down .modal-body { + overflow-y: auto + } +} + +.tooltip { + --bs-tooltip-zindex: 1080; + --bs-tooltip-max-width: 200px; + --bs-tooltip-padding-x: 0.5rem; + --bs-tooltip-padding-y: 0.25rem; + --bs-tooltip-margin: ; + --bs-tooltip-font-size: 0.875rem; + --bs-tooltip-color: var(--bs-body-bg); + --bs-tooltip-bg: var(--bs-emphasis-color); + --bs-tooltip-border-radius: var(--bs-border-radius); + --bs-tooltip-opacity: 0.9; + --bs-tooltip-arrow-width: 0.8rem; + --bs-tooltip-arrow-height: 0.4rem; + z-index: var(--bs-tooltip-zindex); + display: block; + margin: var(--bs-tooltip-margin); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-tooltip-font-size); + word-wrap: break-word; + opacity: 0 +} + +.tooltip.show { + opacity: var(--bs-tooltip-opacity) +} + +.tooltip .tooltip-arrow { + display: block; + width: var(--bs-tooltip-arrow-width); + height: var(--bs-tooltip-arrow-height) +} + +.tooltip .tooltip-arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid +} + +.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow, +.bs-tooltip-top .tooltip-arrow { + bottom: calc(-1 * var(--bs-tooltip-arrow-height)) +} + +.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before, +.bs-tooltip-top .tooltip-arrow::before { + top: -1px; + border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0; + border-top-color: var(--bs-tooltip-bg) +} + +.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow, +.bs-tooltip-end .tooltip-arrow { + left: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width) +} + +.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before, +.bs-tooltip-end .tooltip-arrow::before { + right: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0; + border-right-color: var(--bs-tooltip-bg) +} + +.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow, +.bs-tooltip-bottom .tooltip-arrow { + top: calc(-1 * var(--bs-tooltip-arrow-height)) +} + +.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before, +.bs-tooltip-bottom .tooltip-arrow::before { + bottom: -1px; + border-width: 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height); + border-bottom-color: var(--bs-tooltip-bg) +} + +.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow, +.bs-tooltip-start .tooltip-arrow { + right: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width) +} + +.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before, +.bs-tooltip-start .tooltip-arrow::before { + left: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height); + border-left-color: var(--bs-tooltip-bg) +} + +.tooltip-inner { + max-width: var(--bs-tooltip-max-width); + padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x); + color: var(--bs-tooltip-color); + text-align: center; + background-color: var(--bs-tooltip-bg); + border-radius: var(--bs-tooltip-border-radius) +} + +.popover { + --bs-popover-zindex: 1070; + --bs-popover-max-width: 276px; + --bs-popover-font-size: 0.875rem; + --bs-popover-bg: var(--bs-body-bg); + --bs-popover-border-width: var(--bs-border-width); + --bs-popover-border-color: var(--bs-border-color-translucent); + --bs-popover-border-radius: var(--bs-border-radius-lg); + --bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width)); + --bs-popover-box-shadow: var(--bs-box-shadow); + --bs-popover-header-padding-x: 1rem; + --bs-popover-header-padding-y: 0.5rem; + --bs-popover-header-font-size: 1rem; + --bs-popover-header-color: inherit; + --bs-popover-header-bg: var(--bs-secondary-bg); + --bs-popover-body-padding-x: 1rem; + --bs-popover-body-padding-y: 1rem; + --bs-popover-body-color: var(--bs-body-color); + --bs-popover-arrow-width: 1rem; + --bs-popover-arrow-height: 0.5rem; + --bs-popover-arrow-border: var(--bs-popover-border-color); + z-index: var(--bs-popover-zindex); + display: block; + max-width: var(--bs-popover-max-width); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-popover-font-size); + word-wrap: break-word; + background-color: var(--bs-popover-bg); + background-clip: padding-box; + border: var(--bs-popover-border-width) solid var(--bs-popover-border-color); + border-radius: var(--bs-popover-border-radius) +} + +.popover .popover-arrow { + display: block; + width: var(--bs-popover-arrow-width); + height: var(--bs-popover-arrow-height) +} + +.popover .popover-arrow::after, +.popover .popover-arrow::before { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; + border-width: 0 +} + +.bs-popover-auto[data-popper-placement^=top]>.popover-arrow, +.bs-popover-top>.popover-arrow { + bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)) +} + +.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after, +.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before, +.bs-popover-top>.popover-arrow::after, +.bs-popover-top>.popover-arrow::before { + border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0 +} + +.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before, +.bs-popover-top>.popover-arrow::before { + bottom: 0; + border-top-color: var(--bs-popover-arrow-border) +} + +.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after, +.bs-popover-top>.popover-arrow::after { + bottom: var(--bs-popover-border-width); + border-top-color: var(--bs-popover-bg) +} + +.bs-popover-auto[data-popper-placement^=right]>.popover-arrow, +.bs-popover-end>.popover-arrow { + left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width) +} + +.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after, +.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before, +.bs-popover-end>.popover-arrow::after, +.bs-popover-end>.popover-arrow::before { + border-width: calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0 +} + +.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before, +.bs-popover-end>.popover-arrow::before { + left: 0; + border-right-color: var(--bs-popover-arrow-border) +} + +.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after, +.bs-popover-end>.popover-arrow::after { + left: var(--bs-popover-border-width); + border-right-color: var(--bs-popover-bg) +} + +.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow, +.bs-popover-bottom>.popover-arrow { + top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)) +} + +.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after, +.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before, +.bs-popover-bottom>.popover-arrow::after, +.bs-popover-bottom>.popover-arrow::before { + border-width: 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) +} + +.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before, +.bs-popover-bottom>.popover-arrow::before { + top: 0; + border-bottom-color: var(--bs-popover-arrow-border) +} + +.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after, +.bs-popover-bottom>.popover-arrow::after { + top: var(--bs-popover-border-width); + border-bottom-color: var(--bs-popover-bg) +} + +.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before, +.bs-popover-bottom .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: var(--bs-popover-arrow-width); + margin-left: calc(-.5 * var(--bs-popover-arrow-width)); + content: ""; + border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg) +} + +.bs-popover-auto[data-popper-placement^=left]>.popover-arrow, +.bs-popover-start>.popover-arrow { + right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width) +} + +.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after, +.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before, +.bs-popover-start>.popover-arrow::after, +.bs-popover-start>.popover-arrow::before { + border-width: calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) +} + +.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before, +.bs-popover-start>.popover-arrow::before { + right: 0; + border-left-color: var(--bs-popover-arrow-border) +} + +.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after, +.bs-popover-start>.popover-arrow::after { + right: var(--bs-popover-border-width); + border-left-color: var(--bs-popover-bg) +} + +.popover-header { + padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x); + margin-bottom: 0; + font-size: var(--bs-popover-header-font-size); + color: var(--bs-popover-header-color); + background-color: var(--bs-popover-header-bg); + border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color); + border-top-left-radius: var(--bs-popover-inner-border-radius); + border-top-right-radius: var(--bs-popover-inner-border-radius) +} + +.popover-header:empty { + display: none +} + +.popover-body { + padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x); + color: var(--bs-popover-body-color) +} + +.carousel { + position: relative +} + +.carousel.pointer-event { + touch-action: pan-y +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden +} + +.carousel-inner::after { + display: block; + clear: both; + content: "" +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: transform .6s ease-in-out +} + +@media (prefers-reduced-motion:reduce) { + .carousel-item { + transition: none + } +} + +.carousel-item-next, +.carousel-item-prev, +.carousel-item.active { + display: block +} + +.active.carousel-item-end, +.carousel-item-next:not(.carousel-item-start) { + transform: translateX(100%) +} + +.active.carousel-item-start, +.carousel-item-prev:not(.carousel-item-end) { + transform: translateX(-100%) +} + +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none +} + +.carousel-fade .carousel-item-next.carousel-item-start, +.carousel-fade .carousel-item-prev.carousel-item-end, +.carousel-fade .carousel-item.active { + z-index: 1; + opacity: 1 +} + +.carousel-fade .active.carousel-item-end, +.carousel-fade .active.carousel-item-start { + z-index: 0; + opacity: 0; + transition: opacity 0s .6s +} + +@media (prefers-reduced-motion:reduce) { + + .carousel-fade .active.carousel-item-end, + .carousel-fade .active.carousel-item-start { + transition: none + } +} + +.carousel-control-next, +.carousel-control-prev { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 15%; + padding: 0; + color: #fff; + text-align: center; + background: 0 0; + filter: var(--bs-carousel-control-icon-filter); + border: 0; + opacity: .5; + transition: opacity .15s ease +} + +@media (prefers-reduced-motion:reduce) { + + .carousel-control-next, + .carousel-control-prev { + transition: none + } +} + +.carousel-control-next:focus, +.carousel-control-next:hover, +.carousel-control-prev:focus, +.carousel-control-prev:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: .9 +} + +.carousel-control-prev { + left: 0 +} + +.carousel-control-next { + right: 0 +} + +.carousel-control-next-icon, +.carousel-control-prev-icon { + display: inline-block; + width: 2rem; + height: 2rem; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100% +} + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e") +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e") +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: flex; + justify-content: center; + padding: 0; + margin-right: 15%; + margin-bottom: 1rem; + margin-left: 15% +} + +.carousel-indicators [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: 30px; + height: 3px; + padding: 0; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: var(--bs-carousel-indicator-active-bg); + background-clip: padding-box; + border: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: .5; + transition: opacity .6s ease +} + +@media (prefers-reduced-motion:reduce) { + .carousel-indicators [data-bs-target] { + transition: none + } +} + +.carousel-indicators .active { + opacity: 1 +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 1.25rem; + left: 15%; + padding-top: 1.25rem; + padding-bottom: 1.25rem; + color: var(--bs-carousel-caption-color); + text-align: center +} + +.carousel-dark { + --bs-carousel-indicator-active-bg: #000; + --bs-carousel-caption-color: #000; + --bs-carousel-control-icon-filter: invert(1) grayscale(100) +} + +:root, +[data-bs-theme=light] { + --bs-carousel-indicator-active-bg: #fff; + --bs-carousel-caption-color: #fff; + --bs-carousel-control-icon-filter: +} + +[data-bs-theme=dark] { + --bs-carousel-indicator-active-bg: #000; + --bs-carousel-caption-color: #000; + --bs-carousel-control-icon-filter: invert(1) grayscale(100) +} + +.spinner-border, +.spinner-grow { + display: inline-block; + width: var(--bs-spinner-width); + height: var(--bs-spinner-height); + vertical-align: var(--bs-spinner-vertical-align); + border-radius: 50%; + animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name) +} + +@keyframes spinner-border { + to { + transform: rotate(360deg) + } +} + +.spinner-border { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-border-width: 0.25em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-border; + border: var(--bs-spinner-border-width) solid currentcolor; + border-right-color: transparent +} + +.spinner-border-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; + --bs-spinner-border-width: 0.2em +} + +@keyframes spinner-grow { + 0% { + transform: scale(0) + } + + 50% { + opacity: 1; + transform: none + } +} + +.spinner-grow { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-grow; + background-color: currentcolor; + opacity: 0 +} + +.spinner-grow-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem +} + +@media (prefers-reduced-motion:reduce) { + + .spinner-border, + .spinner-grow { + --bs-spinner-animation-speed: 1.5s + } +} + +.offcanvas, +.offcanvas-lg, +.offcanvas-md, +.offcanvas-sm, +.offcanvas-xl, +.offcanvas-xxl { + --bs-offcanvas-zindex: 1045; + --bs-offcanvas-width: 400px; + --bs-offcanvas-height: 30vh; + --bs-offcanvas-padding-x: 1rem; + --bs-offcanvas-padding-y: 1rem; + --bs-offcanvas-color: var(--bs-body-color); + --bs-offcanvas-bg: var(--bs-body-bg); + --bs-offcanvas-border-width: var(--bs-border-width); + --bs-offcanvas-border-color: var(--bs-border-color-translucent); + --bs-offcanvas-box-shadow: var(--bs-box-shadow-sm); + --bs-offcanvas-transition: transform 0.3s ease-in-out; + --bs-offcanvas-title-line-height: 1.5 +} + +@media (max-width:575.98px) { + .offcanvas-sm { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition) + } +} + +@media (max-width:575.98px) and (prefers-reduced-motion:reduce) { + .offcanvas-sm { + transition: none + } +} + +@media (max-width:575.98px) { + .offcanvas-sm.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%) + } + + .offcanvas-sm.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%) + } + + .offcanvas-sm.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%) + } + + .offcanvas-sm.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%) + } + + .offcanvas-sm.show:not(.hiding), + .offcanvas-sm.showing { + transform: none + } + + .offcanvas-sm.hiding, + .offcanvas-sm.show, + .offcanvas-sm.showing { + visibility: visible + } +} + +@media (min-width:576px) { + .offcanvas-sm { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important + } + + .offcanvas-sm .offcanvas-header { + display: none + } + + .offcanvas-sm .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important + } +} + +@media (max-width:767.98px) { + .offcanvas-md { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition) + } +} + +@media (max-width:767.98px) and (prefers-reduced-motion:reduce) { + .offcanvas-md { + transition: none + } +} + +@media (max-width:767.98px) { + .offcanvas-md.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%) + } + + .offcanvas-md.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%) + } + + .offcanvas-md.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%) + } + + .offcanvas-md.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%) + } + + .offcanvas-md.show:not(.hiding), + .offcanvas-md.showing { + transform: none + } + + .offcanvas-md.hiding, + .offcanvas-md.show, + .offcanvas-md.showing { + visibility: visible + } +} + +@media (min-width:768px) { + .offcanvas-md { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important + } + + .offcanvas-md .offcanvas-header { + display: none + } + + .offcanvas-md .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important + } +} + +@media (max-width:991.98px) { + .offcanvas-lg { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition) + } +} + +@media (max-width:991.98px) and (prefers-reduced-motion:reduce) { + .offcanvas-lg { + transition: none + } +} + +@media (max-width:991.98px) { + .offcanvas-lg.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%) + } + + .offcanvas-lg.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%) + } + + .offcanvas-lg.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%) + } + + .offcanvas-lg.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%) + } + + .offcanvas-lg.show:not(.hiding), + .offcanvas-lg.showing { + transform: none + } + + .offcanvas-lg.hiding, + .offcanvas-lg.show, + .offcanvas-lg.showing { + visibility: visible + } +} + +@media (min-width:992px) { + .offcanvas-lg { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important + } + + .offcanvas-lg .offcanvas-header { + display: none + } + + .offcanvas-lg .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important + } +} + +@media (max-width:1199.98px) { + .offcanvas-xl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition) + } +} + +@media (max-width:1199.98px) and (prefers-reduced-motion:reduce) { + .offcanvas-xl { + transition: none + } +} + +@media (max-width:1199.98px) { + .offcanvas-xl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%) + } + + .offcanvas-xl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%) + } + + .offcanvas-xl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%) + } + + .offcanvas-xl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%) + } + + .offcanvas-xl.show:not(.hiding), + .offcanvas-xl.showing { + transform: none + } + + .offcanvas-xl.hiding, + .offcanvas-xl.show, + .offcanvas-xl.showing { + visibility: visible + } +} + +@media (min-width:1200px) { + .offcanvas-xl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important + } + + .offcanvas-xl .offcanvas-header { + display: none + } + + .offcanvas-xl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important + } +} + +@media (max-width:1399.98px) { + .offcanvas-xxl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition) + } +} + +@media (max-width:1399.98px) and (prefers-reduced-motion:reduce) { + .offcanvas-xxl { + transition: none + } +} + +@media (max-width:1399.98px) { + .offcanvas-xxl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%) + } + + .offcanvas-xxl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%) + } + + .offcanvas-xxl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%) + } + + .offcanvas-xxl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%) + } + + .offcanvas-xxl.show:not(.hiding), + .offcanvas-xxl.showing { + transform: none + } + + .offcanvas-xxl.hiding, + .offcanvas-xxl.show, + .offcanvas-xxl.showing { + visibility: visible + } +} + +@media (min-width:1400px) { + .offcanvas-xxl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important + } + + .offcanvas-xxl .offcanvas-header { + display: none + } + + .offcanvas-xxl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important + } +} + +.offcanvas { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition) +} + +@media (prefers-reduced-motion:reduce) { + .offcanvas { + transition: none + } +} + +.offcanvas.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%) +} + +.offcanvas.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%) +} + +.offcanvas.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%) +} + +.offcanvas.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%) +} + +.offcanvas.show:not(.hiding), +.offcanvas.showing { + transform: none +} + +.offcanvas.hiding, +.offcanvas.show, +.offcanvas.showing { + visibility: visible +} + +.offcanvas-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000 +} + +.offcanvas-backdrop.fade { + opacity: 0 +} + +.offcanvas-backdrop.show { + opacity: .5 +} + +.offcanvas-header { + display: flex; + align-items: center; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x) +} + +.offcanvas-header .btn-close { + padding: calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5); + margin-top: calc(-.5 * var(--bs-offcanvas-padding-y)); + margin-right: calc(-.5 * var(--bs-offcanvas-padding-x)); + margin-bottom: calc(-.5 * var(--bs-offcanvas-padding-y)); + margin-left: auto +} + +.offcanvas-title { + margin-bottom: 0; + line-height: var(--bs-offcanvas-title-line-height) +} + +.offcanvas-body { + flex-grow: 1; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); + overflow-y: auto +} + +.placeholder { + display: inline-block; + min-height: 1em; + vertical-align: middle; + cursor: wait; + background-color: currentcolor; + opacity: .5 +} + +.placeholder.btn::before { + display: inline-block; + content: "" +} + +.placeholder-xs { + min-height: .6em +} + +.placeholder-sm { + min-height: .8em +} + +.placeholder-lg { + min-height: 1.2em +} + +.placeholder-glow .placeholder { + animation: placeholder-glow 2s ease-in-out infinite +} + +@keyframes placeholder-glow { + 50% { + opacity: .2 + } +} + +.placeholder-wave { + -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); + mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); + -webkit-mask-size: 200% 100%; + mask-size: 200% 100%; + animation: placeholder-wave 2s linear infinite +} + +@keyframes placeholder-wave { + 100% { + -webkit-mask-position: -200% 0%; + mask-position: -200% 0% + } +} + +.clearfix::after { + display: block; + clear: both; + content: "" +} + +.text-bg-primary { + color: #fff !important; + background-color: RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity, 1)) !important +} + +.text-bg-secondary { + color: #fff !important; + background-color: RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important +} + +.text-bg-success { + color: #fff !important; + background-color: RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important +} + +.text-bg-info { + color: #000 !important; + background-color: RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important +} + +.text-bg-warning { + color: #000 !important; + background-color: RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important +} + +.text-bg-danger { + color: #fff !important; + background-color: RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important +} + +.text-bg-light { + color: #000 !important; + background-color: RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important +} + +.text-bg-dark { + color: #fff !important; + background-color: RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important +} + +.link-primary { + color: RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-primary:focus, +.link-primary:hover { + color: RGBA(10, 88, 202, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(10, 88, 202, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(10, 88, 202, var(--bs-link-underline-opacity, 1)) !important +} + +.link-secondary { + color: RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-secondary:focus, +.link-secondary:hover { + color: RGBA(86, 94, 100, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(86, 94, 100, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(86, 94, 100, var(--bs-link-underline-opacity, 1)) !important +} + +.link-success { + color: RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-success:focus, +.link-success:hover { + color: RGBA(20, 108, 67, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(20, 108, 67, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(20, 108, 67, var(--bs-link-underline-opacity, 1)) !important +} + +.link-info { + color: RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-info:focus, +.link-info:hover { + color: RGBA(61, 213, 243, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(61, 213, 243, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(61, 213, 243, var(--bs-link-underline-opacity, 1)) !important +} + +.link-warning { + color: RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-warning:focus, +.link-warning:hover { + color: RGBA(255, 205, 57, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(255, 205, 57, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(255, 205, 57, var(--bs-link-underline-opacity, 1)) !important +} + +.link-danger { + color: RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-danger:focus, +.link-danger:hover { + color: RGBA(176, 42, 55, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(176, 42, 55, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(176, 42, 55, var(--bs-link-underline-opacity, 1)) !important +} + +.link-light { + color: RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-light:focus, +.link-light:hover { + color: RGBA(249, 250, 251, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important +} + +.link-dark { + color: RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-dark:focus, +.link-dark:hover { + color: RGBA(26, 30, 33, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(26, 30, 33, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(26, 30, 33, var(--bs-link-underline-opacity, 1)) !important +} + +.link-body-emphasis { + color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-body-emphasis:focus, +.link-body-emphasis:hover { + color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, .75)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important; + text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important +} + +.focus-ring:focus { + outline: 0; + box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color) +} + +.icon-link { + display: inline-flex; + gap: .375rem; + align-items: center; + -webkit-text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5)); + text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5)); + text-underline-offset: 0.25em; + -webkit-backface-visibility: hidden; + backface-visibility: hidden +} + +.icon-link>.bi { + flex-shrink: 0; + width: 1em; + height: 1em; + fill: currentcolor; + transition: .2s ease-in-out transform +} + +@media (prefers-reduced-motion:reduce) { + .icon-link>.bi { + transition: none + } +} + +.icon-link-hover:focus-visible>.bi, +.icon-link-hover:hover>.bi { + transform: var(--bs-icon-link-transform, translate3d(.25em, 0, 0)) +} + +.ratio { + position: relative; + width: 100% +} + +.ratio::before { + display: block; + padding-top: var(--bs-aspect-ratio); + content: "" +} + +.ratio>* { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100% +} + +.ratio-1x1 { + --bs-aspect-ratio: 100% +} + +.ratio-4x3 { + --bs-aspect-ratio: 75% +} + +.ratio-16x9 { + --bs-aspect-ratio: 56.25% +} + +.ratio-21x9 { + --bs-aspect-ratio: 42.8571428571% +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030 +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030 +} + +.sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020 +} + +.sticky-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020 +} + +@media (min-width:576px) { + .sticky-sm-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020 + } + + .sticky-sm-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020 + } +} + +@media (min-width:768px) { + .sticky-md-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020 + } + + .sticky-md-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020 + } +} + +@media (min-width:992px) { + .sticky-lg-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020 + } + + .sticky-lg-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020 + } +} + +@media (min-width:1200px) { + .sticky-xl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020 + } + + .sticky-xl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020 + } +} + +@media (min-width:1400px) { + .sticky-xxl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020 + } + + .sticky-xxl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020 + } +} + +.hstack { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch +} + +.vstack { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-self: stretch +} + +.visually-hidden, +.visually-hidden-focusable:not(:focus):not(:focus-within) { + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important +} + +.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption), +.visually-hidden:not(caption) { + position: absolute !important +} + +.visually-hidden *, +.visually-hidden-focusable:not(:focus):not(:focus-within) * { + overflow: hidden !important +} + +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + content: "" +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.vr { + display: inline-block; + align-self: stretch; + width: var(--bs-border-width); + min-height: 1em; + background-color: currentcolor; + opacity: .25 +} + +.align-baseline { + vertical-align: baseline !important +} + +.align-top { + vertical-align: top !important +} + +.align-middle { + vertical-align: middle !important +} + +.align-bottom { + vertical-align: bottom !important +} + +.align-text-bottom { + vertical-align: text-bottom !important +} + +.align-text-top { + vertical-align: text-top !important +} + +.float-start { + float: left !important +} + +.float-end { + float: right !important +} + +.float-none { + float: none !important +} + +.object-fit-contain { + -o-object-fit: contain !important; + object-fit: contain !important +} + +.object-fit-cover { + -o-object-fit: cover !important; + object-fit: cover !important +} + +.object-fit-fill { + -o-object-fit: fill !important; + object-fit: fill !important +} + +.object-fit-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important +} + +.object-fit-none { + -o-object-fit: none !important; + object-fit: none !important +} + +.opacity-0 { + opacity: 0 !important +} + +.opacity-25 { + opacity: .25 !important +} + +.opacity-50 { + opacity: .5 !important +} + +.opacity-75 { + opacity: .75 !important +} + +.opacity-100 { + opacity: 1 !important +} + +.overflow-auto { + overflow: auto !important +} + +.overflow-hidden { + overflow: hidden !important +} + +.overflow-visible { + overflow: visible !important +} + +.overflow-scroll { + overflow: scroll !important +} + +.overflow-x-auto { + overflow-x: auto !important +} + +.overflow-x-hidden { + overflow-x: hidden !important +} + +.overflow-x-visible { + overflow-x: visible !important +} + +.overflow-x-scroll { + overflow-x: scroll !important +} + +.overflow-y-auto { + overflow-y: auto !important +} + +.overflow-y-hidden { + overflow-y: hidden !important +} + +.overflow-y-visible { + overflow-y: visible !important +} + +.overflow-y-scroll { + overflow-y: scroll !important +} + +.d-inline { + display: inline !important +} + +.d-inline-block { + display: inline-block !important +} + +.d-block { + display: block !important +} + +.d-grid { + display: grid !important +} + +.d-inline-grid { + display: inline-grid !important +} + +.d-table { + display: table !important +} + +.d-table-row { + display: table-row !important +} + +.d-table-cell { + display: table-cell !important +} + +.d-flex { + display: flex !important +} + +.d-inline-flex { + display: inline-flex !important +} + +.d-none { + display: none !important +} + +.shadow { + box-shadow: var(--bs-box-shadow) !important +} + +.shadow-sm { + box-shadow: var(--bs-box-shadow-sm) !important +} + +.shadow-lg { + box-shadow: var(--bs-box-shadow-lg) !important +} + +.shadow-none { + box-shadow: none !important +} + +.focus-ring-primary { + --bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity)) +} + +.focus-ring-secondary { + --bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity)) +} + +.focus-ring-success { + --bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity)) +} + +.focus-ring-info { + --bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity)) +} + +.focus-ring-warning { + --bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity)) +} + +.focus-ring-danger { + --bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity)) +} + +.focus-ring-light { + --bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity)) +} + +.focus-ring-dark { + --bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity)) +} + +.position-static { + position: static !important +} + +.position-relative { + position: relative !important +} + +.position-absolute { + position: absolute !important +} + +.position-fixed { + position: fixed !important +} + +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important +} + +.top-0 { + top: 0 !important +} + +.top-50 { + top: 50% !important +} + +.top-100 { + top: 100% !important +} + +.bottom-0 { + bottom: 0 !important +} + +.bottom-50 { + bottom: 50% !important +} + +.bottom-100 { + bottom: 100% !important +} + +.start-0 { + left: 0 !important +} + +.start-50 { + left: 50% !important +} + +.start-100 { + left: 100% !important +} + +.end-0 { + right: 0 !important +} + +.end-50 { + right: 50% !important +} + +.end-100 { + right: 100% !important +} + +.translate-middle { + transform: translate(-50%, -50%) !important +} + +.translate-middle-x { + transform: translateX(-50%) !important +} + +.translate-middle-y { + transform: translateY(-50%) !important +} + +.border { + border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important +} + +.border-0 { + border: 0 !important +} + +.border-top { + border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important +} + +.border-top-0 { + border-top: 0 !important +} + +.border-end { + border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important +} + +.border-end-0 { + border-right: 0 !important +} + +.border-bottom { + border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important +} + +.border-bottom-0 { + border-bottom: 0 !important +} + +.border-start { + border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important +} + +.border-start-0 { + border-left: 0 !important +} + +.border-primary { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important +} + +.border-secondary { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important +} + +.border-success { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important +} + +.border-info { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important +} + +.border-warning { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important +} + +.border-danger { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important +} + +.border-light { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important +} + +.border-dark { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important +} + +.border-black { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important +} + +.border-white { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important +} + +.border-primary-subtle { + border-color: var(--bs-primary-border-subtle) !important +} + +.border-secondary-subtle { + border-color: var(--bs-secondary-border-subtle) !important +} + +.border-success-subtle { + border-color: var(--bs-success-border-subtle) !important +} + +.border-info-subtle { + border-color: var(--bs-info-border-subtle) !important +} + +.border-warning-subtle { + border-color: var(--bs-warning-border-subtle) !important +} + +.border-danger-subtle { + border-color: var(--bs-danger-border-subtle) !important +} + +.border-light-subtle { + border-color: var(--bs-light-border-subtle) !important +} + +.border-dark-subtle { + border-color: var(--bs-dark-border-subtle) !important +} + +.border-1 { + border-width: 1px !important +} + +.border-2 { + border-width: 2px !important +} + +.border-3 { + border-width: 3px !important +} + +.border-4 { + border-width: 4px !important +} + +.border-5 { + border-width: 5px !important +} + +.border-opacity-10 { + --bs-border-opacity: 0.1 +} + +.border-opacity-25 { + --bs-border-opacity: 0.25 +} + +.border-opacity-50 { + --bs-border-opacity: 0.5 +} + +.border-opacity-75 { + --bs-border-opacity: 0.75 +} + +.border-opacity-100 { + --bs-border-opacity: 1 +} + +.w-25 { + width: 25% !important +} + +.w-50 { + width: 50% !important +} + +.w-75 { + width: 75% !important +} + +.w-100 { + width: 100% !important +} + +.w-auto { + width: auto !important +} + +.mw-100 { + max-width: 100% !important +} + +.vw-100 { + width: 100vw !important +} + +.min-vw-100 { + min-width: 100vw !important +} + +.h-25 { + height: 25% !important +} + +.h-50 { + height: 50% !important +} + +.h-75 { + height: 75% !important +} + +.h-100 { + height: 100% !important +} + +.h-auto { + height: auto !important +} + +.mh-100 { + max-height: 100% !important +} + +.vh-100 { + height: 100vh !important +} + +.min-vh-100 { + min-height: 100vh !important +} + +.flex-fill { + flex: 1 1 auto !important +} + +.flex-row { + flex-direction: row !important +} + +.flex-column { + flex-direction: column !important +} + +.flex-row-reverse { + flex-direction: row-reverse !important +} + +.flex-column-reverse { + flex-direction: column-reverse !important +} + +.flex-grow-0 { + flex-grow: 0 !important +} + +.flex-grow-1 { + flex-grow: 1 !important +} + +.flex-shrink-0 { + flex-shrink: 0 !important +} + +.flex-shrink-1 { + flex-shrink: 1 !important +} + +.flex-wrap { + flex-wrap: wrap !important +} + +.flex-nowrap { + flex-wrap: nowrap !important +} + +.flex-wrap-reverse { + flex-wrap: wrap-reverse !important +} + +.justify-content-start { + justify-content: flex-start !important +} + +.justify-content-end { + justify-content: flex-end !important +} + +.justify-content-center { + justify-content: center !important +} + +.justify-content-between { + justify-content: space-between !important +} + +.justify-content-around { + justify-content: space-around !important +} + +.justify-content-evenly { + justify-content: space-evenly !important +} + +.align-items-start { + align-items: flex-start !important +} + +.align-items-end { + align-items: flex-end !important +} + +.align-items-center { + align-items: center !important +} + +.align-items-baseline { + align-items: baseline !important +} + +.align-items-stretch { + align-items: stretch !important +} + +.align-content-start { + align-content: flex-start !important +} + +.align-content-end { + align-content: flex-end !important +} + +.align-content-center { + align-content: center !important +} + +.align-content-between { + align-content: space-between !important +} + +.align-content-around { + align-content: space-around !important +} + +.align-content-stretch { + align-content: stretch !important +} + +.align-self-auto { + align-self: auto !important +} + +.align-self-start { + align-self: flex-start !important +} + +.align-self-end { + align-self: flex-end !important +} + +.align-self-center { + align-self: center !important +} + +.align-self-baseline { + align-self: baseline !important +} + +.align-self-stretch { + align-self: stretch !important +} + +.order-first { + order: -1 !important +} + +.order-0 { + order: 0 !important +} + +.order-1 { + order: 1 !important +} + +.order-2 { + order: 2 !important +} + +.order-3 { + order: 3 !important +} + +.order-4 { + order: 4 !important +} + +.order-5 { + order: 5 !important +} + +.order-last { + order: 6 !important +} + +.m-0 { + margin: 0 !important +} + +.m-1 { + margin: .25rem !important +} + +.m-2 { + margin: .5rem !important +} + +.m-3 { + margin: 1rem !important +} + +.m-4 { + margin: 1.5rem !important +} + +.m-5 { + margin: 3rem !important +} + +.m-auto { + margin: auto !important +} + +.mx-0 { + margin-right: 0 !important; + margin-left: 0 !important +} + +.mx-1 { + margin-right: .25rem !important; + margin-left: .25rem !important +} + +.mx-2 { + margin-right: .5rem !important; + margin-left: .5rem !important +} + +.mx-3 { + margin-right: 1rem !important; + margin-left: 1rem !important +} + +.mx-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important +} + +.mx-5 { + margin-right: 3rem !important; + margin-left: 3rem !important +} + +.mx-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important +} + +.my-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important +} + +.my-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important +} + +.my-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important +} + +.my-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important +} + +.my-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important +} + +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important +} + +.mt-0 { + margin-top: 0 !important +} + +.mt-1 { + margin-top: .25rem !important +} + +.mt-2 { + margin-top: .5rem !important +} + +.mt-3 { + margin-top: 1rem !important +} + +.mt-4 { + margin-top: 1.5rem !important +} + +.mt-5 { + margin-top: 3rem !important +} + +.mt-auto { + margin-top: auto !important +} + +.me-0 { + margin-right: 0 !important +} + +.me-1 { + margin-right: .25rem !important +} + +.me-2 { + margin-right: .5rem !important +} + +.me-3 { + margin-right: 1rem !important +} + +.me-4 { + margin-right: 1.5rem !important +} + +.me-5 { + margin-right: 3rem !important +} + +.me-auto { + margin-right: auto !important +} + +.mb-0 { + margin-bottom: 0 !important +} + +.mb-1 { + margin-bottom: .25rem !important +} + +.mb-2 { + margin-bottom: .5rem !important +} + +.mb-3 { + margin-bottom: 1rem !important +} + +.mb-4 { + margin-bottom: 1.5rem !important +} + +.mb-5 { + margin-bottom: 3rem !important +} + +.mb-auto { + margin-bottom: auto !important +} + +.ms-0 { + margin-left: 0 !important +} + +.ms-1 { + margin-left: .25rem !important +} + +.ms-2 { + margin-left: .5rem !important +} + +.ms-3 { + margin-left: 1rem !important +} + +.ms-4 { + margin-left: 1.5rem !important +} + +.ms-5 { + margin-left: 3rem !important +} + +.ms-auto { + margin-left: auto !important +} + +.p-0 { + padding: 0 !important +} + +.p-1 { + padding: .25rem !important +} + +.p-2 { + padding: .5rem !important +} + +.p-3 { + padding: 1rem !important +} + +.p-4 { + padding: 1.5rem !important +} + +.p-5 { + padding: 3rem !important +} + +.px-0 { + padding-right: 0 !important; + padding-left: 0 !important +} + +.px-1 { + padding-right: .25rem !important; + padding-left: .25rem !important +} + +.px-2 { + padding-right: .5rem !important; + padding-left: .5rem !important +} + +.px-3 { + padding-right: 1rem !important; + padding-left: 1rem !important +} + +.px-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important +} + +.px-5 { + padding-right: 3rem !important; + padding-left: 3rem !important +} + +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important +} + +.py-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important +} + +.py-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important +} + +.py-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important +} + +.py-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important +} + +.py-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important +} + +.pt-0 { + padding-top: 0 !important +} + +.pt-1 { + padding-top: .25rem !important +} + +.pt-2 { + padding-top: .5rem !important +} + +.pt-3 { + padding-top: 1rem !important +} + +.pt-4 { + padding-top: 1.5rem !important +} + +.pt-5 { + padding-top: 3rem !important +} + +.pe-0 { + padding-right: 0 !important +} + +.pe-1 { + padding-right: .25rem !important +} + +.pe-2 { + padding-right: .5rem !important +} + +.pe-3 { + padding-right: 1rem !important +} + +.pe-4 { + padding-right: 1.5rem !important +} + +.pe-5 { + padding-right: 3rem !important +} + +.pb-0 { + padding-bottom: 0 !important +} + +.pb-1 { + padding-bottom: .25rem !important +} + +.pb-2 { + padding-bottom: .5rem !important +} + +.pb-3 { + padding-bottom: 1rem !important +} + +.pb-4 { + padding-bottom: 1.5rem !important +} + +.pb-5 { + padding-bottom: 3rem !important +} + +.ps-0 { + padding-left: 0 !important +} + +.ps-1 { + padding-left: .25rem !important +} + +.ps-2 { + padding-left: .5rem !important +} + +.ps-3 { + padding-left: 1rem !important +} + +.ps-4 { + padding-left: 1.5rem !important +} + +.ps-5 { + padding-left: 3rem !important +} + +.gap-0 { + gap: 0 !important +} + +.gap-1 { + gap: .25rem !important +} + +.gap-2 { + gap: .5rem !important +} + +.gap-3 { + gap: 1rem !important +} + +.gap-4 { + gap: 1.5rem !important +} + +.gap-5 { + gap: 3rem !important +} + +.row-gap-0 { + row-gap: 0 !important +} + +.row-gap-1 { + row-gap: .25rem !important +} + +.row-gap-2 { + row-gap: .5rem !important +} + +.row-gap-3 { + row-gap: 1rem !important +} + +.row-gap-4 { + row-gap: 1.5rem !important +} + +.row-gap-5 { + row-gap: 3rem !important +} + +.column-gap-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important +} + +.column-gap-1 { + -moz-column-gap: 0.25rem !important; + column-gap: .25rem !important +} + +.column-gap-2 { + -moz-column-gap: 0.5rem !important; + column-gap: .5rem !important +} + +.column-gap-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important +} + +.column-gap-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important +} + +.column-gap-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important +} + +.font-monospace { + font-family: var(--bs-font-monospace) !important +} + +.fs-1 { + font-size: calc(1.375rem + 1.5vw) !important +} + +.fs-2 { + font-size: calc(1.325rem + .9vw) !important +} + +.fs-3 { + font-size: calc(1.3rem + .6vw) !important +} + +.fs-4 { + font-size: calc(1.275rem + .3vw) !important +} + +.fs-5 { + font-size: 1.25rem !important +} + +.fs-6 { + font-size: 1rem !important +} + +.fst-italic { + font-style: italic !important +} + +.fst-normal { + font-style: normal !important +} + +.fw-lighter { + font-weight: lighter !important +} + +.fw-light { + font-weight: 300 !important +} + +.fw-normal { + font-weight: 400 !important +} + +.fw-medium { + font-weight: 500 !important +} + +.fw-semibold { + font-weight: 600 !important +} + +.fw-bold { + font-weight: 700 !important +} + +.fw-bolder { + font-weight: bolder !important +} + +.lh-1 { + line-height: 1 !important +} + +.lh-sm { + line-height: 1.25 !important +} + +.lh-base { + line-height: 1.5 !important +} + +.lh-lg { + line-height: 2 !important +} + +.text-start { + text-align: left !important +} + +.text-end { + text-align: right !important +} + +.text-center { + text-align: center !important +} + +.text-decoration-none { + text-decoration: none !important +} + +.text-decoration-underline { + text-decoration: underline !important +} + +.text-decoration-line-through { + text-decoration: line-through !important +} + +.text-lowercase { + text-transform: lowercase !important +} + +.text-uppercase { + text-transform: uppercase !important +} + +.text-capitalize { + text-transform: capitalize !important +} + +.text-wrap { + white-space: normal !important +} + +.text-nowrap { + white-space: nowrap !important +} + +.text-break { + word-wrap: break-word !important; + word-break: break-word !important +} + +.text-primary { + --bs-text-opacity: 1; + color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important +} + +.text-secondary { + --bs-text-opacity: 1; + color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important +} + +.text-success { + --bs-text-opacity: 1; + color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important +} + +.text-info { + --bs-text-opacity: 1; + color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important +} + +.text-warning { + --bs-text-opacity: 1; + color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important +} + +.text-danger { + --bs-text-opacity: 1; + color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important +} + +.text-light { + --bs-text-opacity: 1; + color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important +} + +.text-dark { + --bs-text-opacity: 1; + color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important +} + +.text-black { + --bs-text-opacity: 1; + color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important +} + +.text-white { + --bs-text-opacity: 1; + color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important +} + +.text-body { + --bs-text-opacity: 1; + color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important +} + +.text-muted { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important +} + +.text-black-50 { + --bs-text-opacity: 1; + color: rgba(0, 0, 0, .5) !important +} + +.text-white-50 { + --bs-text-opacity: 1; + color: rgba(255, 255, 255, .5) !important +} + +.text-body-secondary { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important +} + +.text-body-tertiary { + --bs-text-opacity: 1; + color: var(--bs-tertiary-color) !important +} + +.text-body-emphasis { + --bs-text-opacity: 1; + color: var(--bs-emphasis-color) !important +} + +.text-reset { + --bs-text-opacity: 1; + color: inherit !important +} + +.text-opacity-25 { + --bs-text-opacity: 0.25 +} + +.text-opacity-50 { + --bs-text-opacity: 0.5 +} + +.text-opacity-75 { + --bs-text-opacity: 0.75 +} + +.text-opacity-100 { + --bs-text-opacity: 1 +} + +.text-primary-emphasis { + color: var(--bs-primary-text-emphasis) !important +} + +.text-secondary-emphasis { + color: var(--bs-secondary-text-emphasis) !important +} + +.text-success-emphasis { + color: var(--bs-success-text-emphasis) !important +} + +.text-info-emphasis { + color: var(--bs-info-text-emphasis) !important +} + +.text-warning-emphasis { + color: var(--bs-warning-text-emphasis) !important +} + +.text-danger-emphasis { + color: var(--bs-danger-text-emphasis) !important +} + +.text-light-emphasis { + color: var(--bs-light-text-emphasis) !important +} + +.text-dark-emphasis { + color: var(--bs-dark-text-emphasis) !important +} + +.link-opacity-10 { + --bs-link-opacity: 0.1 +} + +.link-opacity-10-hover:hover { + --bs-link-opacity: 0.1 +} + +.link-opacity-25 { + --bs-link-opacity: 0.25 +} + +.link-opacity-25-hover:hover { + --bs-link-opacity: 0.25 +} + +.link-opacity-50 { + --bs-link-opacity: 0.5 +} + +.link-opacity-50-hover:hover { + --bs-link-opacity: 0.5 +} + +.link-opacity-75 { + --bs-link-opacity: 0.75 +} + +.link-opacity-75-hover:hover { + --bs-link-opacity: 0.75 +} + +.link-opacity-100 { + --bs-link-opacity: 1 +} + +.link-opacity-100-hover:hover { + --bs-link-opacity: 1 +} + +.link-offset-1 { + text-underline-offset: 0.125em !important +} + +.link-offset-1-hover:hover { + text-underline-offset: 0.125em !important +} + +.link-offset-2 { + text-underline-offset: 0.25em !important +} + +.link-offset-2-hover:hover { + text-underline-offset: 0.25em !important +} + +.link-offset-3 { + text-underline-offset: 0.375em !important +} + +.link-offset-3-hover:hover { + text-underline-offset: 0.375em !important +} + +.link-underline-primary { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important +} + +.link-underline-secondary { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important +} + +.link-underline-success { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important +} + +.link-underline-info { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important +} + +.link-underline-warning { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important +} + +.link-underline-danger { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important +} + +.link-underline-light { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important +} + +.link-underline-dark { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important +} + +.link-underline { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important +} + +.link-underline-opacity-0 { + --bs-link-underline-opacity: 0 +} + +.link-underline-opacity-0-hover:hover { + --bs-link-underline-opacity: 0 +} + +.link-underline-opacity-10 { + --bs-link-underline-opacity: 0.1 +} + +.link-underline-opacity-10-hover:hover { + --bs-link-underline-opacity: 0.1 +} + +.link-underline-opacity-25 { + --bs-link-underline-opacity: 0.25 +} + +.link-underline-opacity-25-hover:hover { + --bs-link-underline-opacity: 0.25 +} + +.link-underline-opacity-50 { + --bs-link-underline-opacity: 0.5 +} + +.link-underline-opacity-50-hover:hover { + --bs-link-underline-opacity: 0.5 +} + +.link-underline-opacity-75 { + --bs-link-underline-opacity: 0.75 +} + +.link-underline-opacity-75-hover:hover { + --bs-link-underline-opacity: 0.75 +} + +.link-underline-opacity-100 { + --bs-link-underline-opacity: 1 +} + +.link-underline-opacity-100-hover:hover { + --bs-link-underline-opacity: 1 +} + +.bg-primary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important +} + +.bg-secondary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important +} + +.bg-success { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important +} + +.bg-info { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important +} + +.bg-warning { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important +} + +.bg-danger { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important +} + +.bg-light { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important +} + +.bg-dark { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important +} + +.bg-black { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important +} + +.bg-white { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important +} + +.bg-body { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important +} + +.bg-transparent { + --bs-bg-opacity: 1; + background-color: transparent !important +} + +.bg-body-secondary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important +} + +.bg-body-tertiary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important +} + +.bg-opacity-10 { + --bs-bg-opacity: 0.1 +} + +.bg-opacity-25 { + --bs-bg-opacity: 0.25 +} + +.bg-opacity-50 { + --bs-bg-opacity: 0.5 +} + +.bg-opacity-75 { + --bs-bg-opacity: 0.75 +} + +.bg-opacity-100 { + --bs-bg-opacity: 1 +} + +.bg-primary-subtle { + background-color: var(--bs-primary-bg-subtle) !important +} + +.bg-secondary-subtle { + background-color: var(--bs-secondary-bg-subtle) !important +} + +.bg-success-subtle { + background-color: var(--bs-success-bg-subtle) !important +} + +.bg-info-subtle { + background-color: var(--bs-info-bg-subtle) !important +} + +.bg-warning-subtle { + background-color: var(--bs-warning-bg-subtle) !important +} + +.bg-danger-subtle { + background-color: var(--bs-danger-bg-subtle) !important +} + +.bg-light-subtle { + background-color: var(--bs-light-bg-subtle) !important +} + +.bg-dark-subtle { + background-color: var(--bs-dark-bg-subtle) !important +} + +.bg-gradient { + background-image: var(--bs-gradient) !important +} + +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + user-select: all !important +} + +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + user-select: auto !important +} + +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + user-select: none !important +} + +.pe-none { + pointer-events: none !important +} + +.pe-auto { + pointer-events: auto !important +} + +.rounded { + border-radius: var(--bs-border-radius) !important +} + +.rounded-0 { + border-radius: 0 !important +} + +.rounded-1 { + border-radius: var(--bs-border-radius-sm) !important +} + +.rounded-2 { + border-radius: var(--bs-border-radius) !important +} + +.rounded-3 { + border-radius: var(--bs-border-radius-lg) !important +} + +.rounded-4 { + border-radius: var(--bs-border-radius-xl) !important +} + +.rounded-5 { + border-radius: var(--bs-border-radius-xxl) !important +} + +.rounded-circle { + border-radius: 50% !important +} + +.rounded-pill { + border-radius: var(--bs-border-radius-pill) !important +} + +.rounded-top { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important +} + +.rounded-top-0 { + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important +} + +.rounded-top-1 { + border-top-left-radius: var(--bs-border-radius-sm) !important; + border-top-right-radius: var(--bs-border-radius-sm) !important +} + +.rounded-top-2 { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important +} + +.rounded-top-3 { + border-top-left-radius: var(--bs-border-radius-lg) !important; + border-top-right-radius: var(--bs-border-radius-lg) !important +} + +.rounded-top-4 { + border-top-left-radius: var(--bs-border-radius-xl) !important; + border-top-right-radius: var(--bs-border-radius-xl) !important +} + +.rounded-top-5 { + border-top-left-radius: var(--bs-border-radius-xxl) !important; + border-top-right-radius: var(--bs-border-radius-xxl) !important +} + +.rounded-top-circle { + border-top-left-radius: 50% !important; + border-top-right-radius: 50% !important +} + +.rounded-top-pill { + border-top-left-radius: var(--bs-border-radius-pill) !important; + border-top-right-radius: var(--bs-border-radius-pill) !important +} + +.rounded-end { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important +} + +.rounded-end-0 { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important +} + +.rounded-end-1 { + border-top-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-right-radius: var(--bs-border-radius-sm) !important +} + +.rounded-end-2 { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important +} + +.rounded-end-3 { + border-top-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-right-radius: var(--bs-border-radius-lg) !important +} + +.rounded-end-4 { + border-top-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-right-radius: var(--bs-border-radius-xl) !important +} + +.rounded-end-5 { + border-top-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-right-radius: var(--bs-border-radius-xxl) !important +} + +.rounded-end-circle { + border-top-right-radius: 50% !important; + border-bottom-right-radius: 50% !important +} + +.rounded-end-pill { + border-top-right-radius: var(--bs-border-radius-pill) !important; + border-bottom-right-radius: var(--bs-border-radius-pill) !important +} + +.rounded-bottom { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important +} + +.rounded-bottom-0 { + border-bottom-right-radius: 0 !important; + border-bottom-left-radius: 0 !important +} + +.rounded-bottom-1 { + border-bottom-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-left-radius: var(--bs-border-radius-sm) !important +} + +.rounded-bottom-2 { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important +} + +.rounded-bottom-3 { + border-bottom-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-left-radius: var(--bs-border-radius-lg) !important +} + +.rounded-bottom-4 { + border-bottom-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-left-radius: var(--bs-border-radius-xl) !important +} + +.rounded-bottom-5 { + border-bottom-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-left-radius: var(--bs-border-radius-xxl) !important +} + +.rounded-bottom-circle { + border-bottom-right-radius: 50% !important; + border-bottom-left-radius: 50% !important +} + +.rounded-bottom-pill { + border-bottom-right-radius: var(--bs-border-radius-pill) !important; + border-bottom-left-radius: var(--bs-border-radius-pill) !important +} + +.rounded-start { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important +} + +.rounded-start-0 { + border-bottom-left-radius: 0 !important; + border-top-left-radius: 0 !important +} + +.rounded-start-1 { + border-bottom-left-radius: var(--bs-border-radius-sm) !important; + border-top-left-radius: var(--bs-border-radius-sm) !important +} + +.rounded-start-2 { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important +} + +.rounded-start-3 { + border-bottom-left-radius: var(--bs-border-radius-lg) !important; + border-top-left-radius: var(--bs-border-radius-lg) !important +} + +.rounded-start-4 { + border-bottom-left-radius: var(--bs-border-radius-xl) !important; + border-top-left-radius: var(--bs-border-radius-xl) !important +} + +.rounded-start-5 { + border-bottom-left-radius: var(--bs-border-radius-xxl) !important; + border-top-left-radius: var(--bs-border-radius-xxl) !important +} + +.rounded-start-circle { + border-bottom-left-radius: 50% !important; + border-top-left-radius: 50% !important +} + +.rounded-start-pill { + border-bottom-left-radius: var(--bs-border-radius-pill) !important; + border-top-left-radius: var(--bs-border-radius-pill) !important +} + +.visible { + visibility: visible !important +} + +.invisible { + visibility: hidden !important +} + +.z-n1 { + z-index: -1 !important +} + +.z-0 { + z-index: 0 !important +} + +.z-1 { + z-index: 1 !important +} + +.z-2 { + z-index: 2 !important +} + +.z-3 { + z-index: 3 !important +} + +@media (min-width:576px) { + .float-sm-start { + float: left !important + } + + .float-sm-end { + float: right !important + } + + .float-sm-none { + float: none !important + } + + .object-fit-sm-contain { + -o-object-fit: contain !important; + object-fit: contain !important + } + + .object-fit-sm-cover { + -o-object-fit: cover !important; + object-fit: cover !important + } + + .object-fit-sm-fill { + -o-object-fit: fill !important; + object-fit: fill !important + } + + .object-fit-sm-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important + } + + .object-fit-sm-none { + -o-object-fit: none !important; + object-fit: none !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-block { + display: block !important + } + + .d-sm-grid { + display: grid !important + } + + .d-sm-inline-grid { + display: inline-grid !important + } + + .d-sm-table { + display: table !important + } + + .d-sm-table-row { + display: table-row !important + } + + .d-sm-table-cell { + display: table-cell !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline-flex { + display: inline-flex !important + } + + .d-sm-none { + display: none !important + } + + .flex-sm-fill { + flex: 1 1 auto !important + } + + .flex-sm-row { + flex-direction: row !important + } + + .flex-sm-column { + flex-direction: column !important + } + + .flex-sm-row-reverse { + flex-direction: row-reverse !important + } + + .flex-sm-column-reverse { + flex-direction: column-reverse !important + } + + .flex-sm-grow-0 { + flex-grow: 0 !important + } + + .flex-sm-grow-1 { + flex-grow: 1 !important + } + + .flex-sm-shrink-0 { + flex-shrink: 0 !important + } + + .flex-sm-shrink-1 { + flex-shrink: 1 !important + } + + .flex-sm-wrap { + flex-wrap: wrap !important + } + + .flex-sm-nowrap { + flex-wrap: nowrap !important + } + + .flex-sm-wrap-reverse { + flex-wrap: wrap-reverse !important + } + + .justify-content-sm-start { + justify-content: flex-start !important + } + + .justify-content-sm-end { + justify-content: flex-end !important + } + + .justify-content-sm-center { + justify-content: center !important + } + + .justify-content-sm-between { + justify-content: space-between !important + } + + .justify-content-sm-around { + justify-content: space-around !important + } + + .justify-content-sm-evenly { + justify-content: space-evenly !important + } + + .align-items-sm-start { + align-items: flex-start !important + } + + .align-items-sm-end { + align-items: flex-end !important + } + + .align-items-sm-center { + align-items: center !important + } + + .align-items-sm-baseline { + align-items: baseline !important + } + + .align-items-sm-stretch { + align-items: stretch !important + } + + .align-content-sm-start { + align-content: flex-start !important + } + + .align-content-sm-end { + align-content: flex-end !important + } + + .align-content-sm-center { + align-content: center !important + } + + .align-content-sm-between { + align-content: space-between !important + } + + .align-content-sm-around { + align-content: space-around !important + } + + .align-content-sm-stretch { + align-content: stretch !important + } + + .align-self-sm-auto { + align-self: auto !important + } + + .align-self-sm-start { + align-self: flex-start !important + } + + .align-self-sm-end { + align-self: flex-end !important + } + + .align-self-sm-center { + align-self: center !important + } + + .align-self-sm-baseline { + align-self: baseline !important + } + + .align-self-sm-stretch { + align-self: stretch !important + } + + .order-sm-first { + order: -1 !important + } + + .order-sm-0 { + order: 0 !important + } + + .order-sm-1 { + order: 1 !important + } + + .order-sm-2 { + order: 2 !important + } + + .order-sm-3 { + order: 3 !important + } + + .order-sm-4 { + order: 4 !important + } + + .order-sm-5 { + order: 5 !important + } + + .order-sm-last { + order: 6 !important + } + + .m-sm-0 { + margin: 0 !important + } + + .m-sm-1 { + margin: .25rem !important + } + + .m-sm-2 { + margin: .5rem !important + } + + .m-sm-3 { + margin: 1rem !important + } + + .m-sm-4 { + margin: 1.5rem !important + } + + .m-sm-5 { + margin: 3rem !important + } + + .m-sm-auto { + margin: auto !important + } + + .mx-sm-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .mx-sm-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .mx-sm-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .mx-sm-3 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .mx-sm-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .mx-sm-5 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .mx-sm-auto { + margin-right: auto !important; + margin-left: auto !important + } + + .my-sm-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .my-sm-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .my-sm-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .my-sm-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .my-sm-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .my-sm-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .my-sm-auto { + margin-top: auto !important; + margin-bottom: auto !important + } + + .mt-sm-0 { + margin-top: 0 !important + } + + .mt-sm-1 { + margin-top: .25rem !important + } + + .mt-sm-2 { + margin-top: .5rem !important + } + + .mt-sm-3 { + margin-top: 1rem !important + } + + .mt-sm-4 { + margin-top: 1.5rem !important + } + + .mt-sm-5 { + margin-top: 3rem !important + } + + .mt-sm-auto { + margin-top: auto !important + } + + .me-sm-0 { + margin-right: 0 !important + } + + .me-sm-1 { + margin-right: .25rem !important + } + + .me-sm-2 { + margin-right: .5rem !important + } + + .me-sm-3 { + margin-right: 1rem !important + } + + .me-sm-4 { + margin-right: 1.5rem !important + } + + .me-sm-5 { + margin-right: 3rem !important + } + + .me-sm-auto { + margin-right: auto !important + } + + .mb-sm-0 { + margin-bottom: 0 !important + } + + .mb-sm-1 { + margin-bottom: .25rem !important + } + + .mb-sm-2 { + margin-bottom: .5rem !important + } + + .mb-sm-3 { + margin-bottom: 1rem !important + } + + .mb-sm-4 { + margin-bottom: 1.5rem !important + } + + .mb-sm-5 { + margin-bottom: 3rem !important + } + + .mb-sm-auto { + margin-bottom: auto !important + } + + .ms-sm-0 { + margin-left: 0 !important + } + + .ms-sm-1 { + margin-left: .25rem !important + } + + .ms-sm-2 { + margin-left: .5rem !important + } + + .ms-sm-3 { + margin-left: 1rem !important + } + + .ms-sm-4 { + margin-left: 1.5rem !important + } + + .ms-sm-5 { + margin-left: 3rem !important + } + + .ms-sm-auto { + margin-left: auto !important + } + + .p-sm-0 { + padding: 0 !important + } + + .p-sm-1 { + padding: .25rem !important + } + + .p-sm-2 { + padding: .5rem !important + } + + .p-sm-3 { + padding: 1rem !important + } + + .p-sm-4 { + padding: 1.5rem !important + } + + .p-sm-5 { + padding: 3rem !important + } + + .px-sm-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .px-sm-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .px-sm-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .px-sm-3 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .px-sm-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .px-sm-5 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-sm-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .py-sm-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .py-sm-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .py-sm-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .py-sm-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .py-sm-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .pt-sm-0 { + padding-top: 0 !important + } + + .pt-sm-1 { + padding-top: .25rem !important + } + + .pt-sm-2 { + padding-top: .5rem !important + } + + .pt-sm-3 { + padding-top: 1rem !important + } + + .pt-sm-4 { + padding-top: 1.5rem !important + } + + .pt-sm-5 { + padding-top: 3rem !important + } + + .pe-sm-0 { + padding-right: 0 !important + } + + .pe-sm-1 { + padding-right: .25rem !important + } + + .pe-sm-2 { + padding-right: .5rem !important + } + + .pe-sm-3 { + padding-right: 1rem !important + } + + .pe-sm-4 { + padding-right: 1.5rem !important + } + + .pe-sm-5 { + padding-right: 3rem !important + } + + .pb-sm-0 { + padding-bottom: 0 !important + } + + .pb-sm-1 { + padding-bottom: .25rem !important + } + + .pb-sm-2 { + padding-bottom: .5rem !important + } + + .pb-sm-3 { + padding-bottom: 1rem !important + } + + .pb-sm-4 { + padding-bottom: 1.5rem !important + } + + .pb-sm-5 { + padding-bottom: 3rem !important + } + + .ps-sm-0 { + padding-left: 0 !important + } + + .ps-sm-1 { + padding-left: .25rem !important + } + + .ps-sm-2 { + padding-left: .5rem !important + } + + .ps-sm-3 { + padding-left: 1rem !important + } + + .ps-sm-4 { + padding-left: 1.5rem !important + } + + .ps-sm-5 { + padding-left: 3rem !important + } + + .gap-sm-0 { + gap: 0 !important + } + + .gap-sm-1 { + gap: .25rem !important + } + + .gap-sm-2 { + gap: .5rem !important + } + + .gap-sm-3 { + gap: 1rem !important + } + + .gap-sm-4 { + gap: 1.5rem !important + } + + .gap-sm-5 { + gap: 3rem !important + } + + .row-gap-sm-0 { + row-gap: 0 !important + } + + .row-gap-sm-1 { + row-gap: .25rem !important + } + + .row-gap-sm-2 { + row-gap: .5rem !important + } + + .row-gap-sm-3 { + row-gap: 1rem !important + } + + .row-gap-sm-4 { + row-gap: 1.5rem !important + } + + .row-gap-sm-5 { + row-gap: 3rem !important + } + + .column-gap-sm-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important + } + + .column-gap-sm-1 { + -moz-column-gap: 0.25rem !important; + column-gap: .25rem !important + } + + .column-gap-sm-2 { + -moz-column-gap: 0.5rem !important; + column-gap: .5rem !important + } + + .column-gap-sm-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important + } + + .column-gap-sm-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important + } + + .column-gap-sm-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important + } + + .text-sm-start { + text-align: left !important + } + + .text-sm-end { + text-align: right !important + } + + .text-sm-center { + text-align: center !important + } +} + +@media (min-width:768px) { + .float-md-start { + float: left !important + } + + .float-md-end { + float: right !important + } + + .float-md-none { + float: none !important + } + + .object-fit-md-contain { + -o-object-fit: contain !important; + object-fit: contain !important + } + + .object-fit-md-cover { + -o-object-fit: cover !important; + object-fit: cover !important + } + + .object-fit-md-fill { + -o-object-fit: fill !important; + object-fit: fill !important + } + + .object-fit-md-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important + } + + .object-fit-md-none { + -o-object-fit: none !important; + object-fit: none !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-block { + display: block !important + } + + .d-md-grid { + display: grid !important + } + + .d-md-inline-grid { + display: inline-grid !important + } + + .d-md-table { + display: table !important + } + + .d-md-table-row { + display: table-row !important + } + + .d-md-table-cell { + display: table-cell !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline-flex { + display: inline-flex !important + } + + .d-md-none { + display: none !important + } + + .flex-md-fill { + flex: 1 1 auto !important + } + + .flex-md-row { + flex-direction: row !important + } + + .flex-md-column { + flex-direction: column !important + } + + .flex-md-row-reverse { + flex-direction: row-reverse !important + } + + .flex-md-column-reverse { + flex-direction: column-reverse !important + } + + .flex-md-grow-0 { + flex-grow: 0 !important + } + + .flex-md-grow-1 { + flex-grow: 1 !important + } + + .flex-md-shrink-0 { + flex-shrink: 0 !important + } + + .flex-md-shrink-1 { + flex-shrink: 1 !important + } + + .flex-md-wrap { + flex-wrap: wrap !important + } + + .flex-md-nowrap { + flex-wrap: nowrap !important + } + + .flex-md-wrap-reverse { + flex-wrap: wrap-reverse !important + } + + .justify-content-md-start { + justify-content: flex-start !important + } + + .justify-content-md-end { + justify-content: flex-end !important + } + + .justify-content-md-center { + justify-content: center !important + } + + .justify-content-md-between { + justify-content: space-between !important + } + + .justify-content-md-around { + justify-content: space-around !important + } + + .justify-content-md-evenly { + justify-content: space-evenly !important + } + + .align-items-md-start { + align-items: flex-start !important + } + + .align-items-md-end { + align-items: flex-end !important + } + + .align-items-md-center { + align-items: center !important + } + + .align-items-md-baseline { + align-items: baseline !important + } + + .align-items-md-stretch { + align-items: stretch !important + } + + .align-content-md-start { + align-content: flex-start !important + } + + .align-content-md-end { + align-content: flex-end !important + } + + .align-content-md-center { + align-content: center !important + } + + .align-content-md-between { + align-content: space-between !important + } + + .align-content-md-around { + align-content: space-around !important + } + + .align-content-md-stretch { + align-content: stretch !important + } + + .align-self-md-auto { + align-self: auto !important + } + + .align-self-md-start { + align-self: flex-start !important + } + + .align-self-md-end { + align-self: flex-end !important + } + + .align-self-md-center { + align-self: center !important + } + + .align-self-md-baseline { + align-self: baseline !important + } + + .align-self-md-stretch { + align-self: stretch !important + } + + .order-md-first { + order: -1 !important + } + + .order-md-0 { + order: 0 !important + } + + .order-md-1 { + order: 1 !important + } + + .order-md-2 { + order: 2 !important + } + + .order-md-3 { + order: 3 !important + } + + .order-md-4 { + order: 4 !important + } + + .order-md-5 { + order: 5 !important + } + + .order-md-last { + order: 6 !important + } + + .m-md-0 { + margin: 0 !important + } + + .m-md-1 { + margin: .25rem !important + } + + .m-md-2 { + margin: .5rem !important + } + + .m-md-3 { + margin: 1rem !important + } + + .m-md-4 { + margin: 1.5rem !important + } + + .m-md-5 { + margin: 3rem !important + } + + .m-md-auto { + margin: auto !important + } + + .mx-md-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .mx-md-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .mx-md-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .mx-md-3 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .mx-md-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .mx-md-5 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .mx-md-auto { + margin-right: auto !important; + margin-left: auto !important + } + + .my-md-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .my-md-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .my-md-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .my-md-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .my-md-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .my-md-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .my-md-auto { + margin-top: auto !important; + margin-bottom: auto !important + } + + .mt-md-0 { + margin-top: 0 !important + } + + .mt-md-1 { + margin-top: .25rem !important + } + + .mt-md-2 { + margin-top: .5rem !important + } + + .mt-md-3 { + margin-top: 1rem !important + } + + .mt-md-4 { + margin-top: 1.5rem !important + } + + .mt-md-5 { + margin-top: 3rem !important + } + + .mt-md-auto { + margin-top: auto !important + } + + .me-md-0 { + margin-right: 0 !important + } + + .me-md-1 { + margin-right: .25rem !important + } + + .me-md-2 { + margin-right: .5rem !important + } + + .me-md-3 { + margin-right: 1rem !important + } + + .me-md-4 { + margin-right: 1.5rem !important + } + + .me-md-5 { + margin-right: 3rem !important + } + + .me-md-auto { + margin-right: auto !important + } + + .mb-md-0 { + margin-bottom: 0 !important + } + + .mb-md-1 { + margin-bottom: .25rem !important + } + + .mb-md-2 { + margin-bottom: .5rem !important + } + + .mb-md-3 { + margin-bottom: 1rem !important + } + + .mb-md-4 { + margin-bottom: 1.5rem !important + } + + .mb-md-5 { + margin-bottom: 3rem !important + } + + .mb-md-auto { + margin-bottom: auto !important + } + + .ms-md-0 { + margin-left: 0 !important + } + + .ms-md-1 { + margin-left: .25rem !important + } + + .ms-md-2 { + margin-left: .5rem !important + } + + .ms-md-3 { + margin-left: 1rem !important + } + + .ms-md-4 { + margin-left: 1.5rem !important + } + + .ms-md-5 { + margin-left: 3rem !important + } + + .ms-md-auto { + margin-left: auto !important + } + + .p-md-0 { + padding: 0 !important + } + + .p-md-1 { + padding: .25rem !important + } + + .p-md-2 { + padding: .5rem !important + } + + .p-md-3 { + padding: 1rem !important + } + + .p-md-4 { + padding: 1.5rem !important + } + + .p-md-5 { + padding: 3rem !important + } + + .px-md-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .px-md-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .px-md-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .px-md-3 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .px-md-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .px-md-5 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-md-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .py-md-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .py-md-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .py-md-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .py-md-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .py-md-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .pt-md-0 { + padding-top: 0 !important + } + + .pt-md-1 { + padding-top: .25rem !important + } + + .pt-md-2 { + padding-top: .5rem !important + } + + .pt-md-3 { + padding-top: 1rem !important + } + + .pt-md-4 { + padding-top: 1.5rem !important + } + + .pt-md-5 { + padding-top: 3rem !important + } + + .pe-md-0 { + padding-right: 0 !important + } + + .pe-md-1 { + padding-right: .25rem !important + } + + .pe-md-2 { + padding-right: .5rem !important + } + + .pe-md-3 { + padding-right: 1rem !important + } + + .pe-md-4 { + padding-right: 1.5rem !important + } + + .pe-md-5 { + padding-right: 3rem !important + } + + .pb-md-0 { + padding-bottom: 0 !important + } + + .pb-md-1 { + padding-bottom: .25rem !important + } + + .pb-md-2 { + padding-bottom: .5rem !important + } + + .pb-md-3 { + padding-bottom: 1rem !important + } + + .pb-md-4 { + padding-bottom: 1.5rem !important + } + + .pb-md-5 { + padding-bottom: 3rem !important + } + + .ps-md-0 { + padding-left: 0 !important + } + + .ps-md-1 { + padding-left: .25rem !important + } + + .ps-md-2 { + padding-left: .5rem !important + } + + .ps-md-3 { + padding-left: 1rem !important + } + + .ps-md-4 { + padding-left: 1.5rem !important + } + + .ps-md-5 { + padding-left: 3rem !important + } + + .gap-md-0 { + gap: 0 !important + } + + .gap-md-1 { + gap: .25rem !important + } + + .gap-md-2 { + gap: .5rem !important + } + + .gap-md-3 { + gap: 1rem !important + } + + .gap-md-4 { + gap: 1.5rem !important + } + + .gap-md-5 { + gap: 3rem !important + } + + .row-gap-md-0 { + row-gap: 0 !important + } + + .row-gap-md-1 { + row-gap: .25rem !important + } + + .row-gap-md-2 { + row-gap: .5rem !important + } + + .row-gap-md-3 { + row-gap: 1rem !important + } + + .row-gap-md-4 { + row-gap: 1.5rem !important + } + + .row-gap-md-5 { + row-gap: 3rem !important + } + + .column-gap-md-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important + } + + .column-gap-md-1 { + -moz-column-gap: 0.25rem !important; + column-gap: .25rem !important + } + + .column-gap-md-2 { + -moz-column-gap: 0.5rem !important; + column-gap: .5rem !important + } + + .column-gap-md-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important + } + + .column-gap-md-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important + } + + .column-gap-md-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important + } + + .text-md-start { + text-align: left !important + } + + .text-md-end { + text-align: right !important + } + + .text-md-center { + text-align: center !important + } +} + +@media (min-width:992px) { + .float-lg-start { + float: left !important + } + + .float-lg-end { + float: right !important + } + + .float-lg-none { + float: none !important + } + + .object-fit-lg-contain { + -o-object-fit: contain !important; + object-fit: contain !important + } + + .object-fit-lg-cover { + -o-object-fit: cover !important; + object-fit: cover !important + } + + .object-fit-lg-fill { + -o-object-fit: fill !important; + object-fit: fill !important + } + + .object-fit-lg-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important + } + + .object-fit-lg-none { + -o-object-fit: none !important; + object-fit: none !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-block { + display: block !important + } + + .d-lg-grid { + display: grid !important + } + + .d-lg-inline-grid { + display: inline-grid !important + } + + .d-lg-table { + display: table !important + } + + .d-lg-table-row { + display: table-row !important + } + + .d-lg-table-cell { + display: table-cell !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline-flex { + display: inline-flex !important + } + + .d-lg-none { + display: none !important + } + + .flex-lg-fill { + flex: 1 1 auto !important + } + + .flex-lg-row { + flex-direction: row !important + } + + .flex-lg-column { + flex-direction: column !important + } + + .flex-lg-row-reverse { + flex-direction: row-reverse !important + } + + .flex-lg-column-reverse { + flex-direction: column-reverse !important + } + + .flex-lg-grow-0 { + flex-grow: 0 !important + } + + .flex-lg-grow-1 { + flex-grow: 1 !important + } + + .flex-lg-shrink-0 { + flex-shrink: 0 !important + } + + .flex-lg-shrink-1 { + flex-shrink: 1 !important + } + + .flex-lg-wrap { + flex-wrap: wrap !important + } + + .flex-lg-nowrap { + flex-wrap: nowrap !important + } + + .flex-lg-wrap-reverse { + flex-wrap: wrap-reverse !important + } + + .justify-content-lg-start { + justify-content: flex-start !important + } + + .justify-content-lg-end { + justify-content: flex-end !important + } + + .justify-content-lg-center { + justify-content: center !important + } + + .justify-content-lg-between { + justify-content: space-between !important + } + + .justify-content-lg-around { + justify-content: space-around !important + } + + .justify-content-lg-evenly { + justify-content: space-evenly !important + } + + .align-items-lg-start { + align-items: flex-start !important + } + + .align-items-lg-end { + align-items: flex-end !important + } + + .align-items-lg-center { + align-items: center !important + } + + .align-items-lg-baseline { + align-items: baseline !important + } + + .align-items-lg-stretch { + align-items: stretch !important + } + + .align-content-lg-start { + align-content: flex-start !important + } + + .align-content-lg-end { + align-content: flex-end !important + } + + .align-content-lg-center { + align-content: center !important + } + + .align-content-lg-between { + align-content: space-between !important + } + + .align-content-lg-around { + align-content: space-around !important + } + + .align-content-lg-stretch { + align-content: stretch !important + } + + .align-self-lg-auto { + align-self: auto !important + } + + .align-self-lg-start { + align-self: flex-start !important + } + + .align-self-lg-end { + align-self: flex-end !important + } + + .align-self-lg-center { + align-self: center !important + } + + .align-self-lg-baseline { + align-self: baseline !important + } + + .align-self-lg-stretch { + align-self: stretch !important + } + + .order-lg-first { + order: -1 !important + } + + .order-lg-0 { + order: 0 !important + } + + .order-lg-1 { + order: 1 !important + } + + .order-lg-2 { + order: 2 !important + } + + .order-lg-3 { + order: 3 !important + } + + .order-lg-4 { + order: 4 !important + } + + .order-lg-5 { + order: 5 !important + } + + .order-lg-last { + order: 6 !important + } + + .m-lg-0 { + margin: 0 !important + } + + .m-lg-1 { + margin: .25rem !important + } + + .m-lg-2 { + margin: .5rem !important + } + + .m-lg-3 { + margin: 1rem !important + } + + .m-lg-4 { + margin: 1.5rem !important + } + + .m-lg-5 { + margin: 3rem !important + } + + .m-lg-auto { + margin: auto !important + } + + .mx-lg-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .mx-lg-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .mx-lg-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .mx-lg-3 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .mx-lg-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .mx-lg-5 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .mx-lg-auto { + margin-right: auto !important; + margin-left: auto !important + } + + .my-lg-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .my-lg-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .my-lg-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .my-lg-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .my-lg-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .my-lg-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .my-lg-auto { + margin-top: auto !important; + margin-bottom: auto !important + } + + .mt-lg-0 { + margin-top: 0 !important + } + + .mt-lg-1 { + margin-top: .25rem !important + } + + .mt-lg-2 { + margin-top: .5rem !important + } + + .mt-lg-3 { + margin-top: 1rem !important + } + + .mt-lg-4 { + margin-top: 1.5rem !important + } + + .mt-lg-5 { + margin-top: 3rem !important + } + + .mt-lg-auto { + margin-top: auto !important + } + + .me-lg-0 { + margin-right: 0 !important + } + + .me-lg-1 { + margin-right: .25rem !important + } + + .me-lg-2 { + margin-right: .5rem !important + } + + .me-lg-3 { + margin-right: 1rem !important + } + + .me-lg-4 { + margin-right: 1.5rem !important + } + + .me-lg-5 { + margin-right: 3rem !important + } + + .me-lg-auto { + margin-right: auto !important + } + + .mb-lg-0 { + margin-bottom: 0 !important + } + + .mb-lg-1 { + margin-bottom: .25rem !important + } + + .mb-lg-2 { + margin-bottom: .5rem !important + } + + .mb-lg-3 { + margin-bottom: 1rem !important + } + + .mb-lg-4 { + margin-bottom: 1.5rem !important + } + + .mb-lg-5 { + margin-bottom: 3rem !important + } + + .mb-lg-auto { + margin-bottom: auto !important + } + + .ms-lg-0 { + margin-left: 0 !important + } + + .ms-lg-1 { + margin-left: .25rem !important + } + + .ms-lg-2 { + margin-left: .5rem !important + } + + .ms-lg-3 { + margin-left: 1rem !important + } + + .ms-lg-4 { + margin-left: 1.5rem !important + } + + .ms-lg-5 { + margin-left: 3rem !important + } + + .ms-lg-auto { + margin-left: auto !important + } + + .p-lg-0 { + padding: 0 !important + } + + .p-lg-1 { + padding: .25rem !important + } + + .p-lg-2 { + padding: .5rem !important + } + + .p-lg-3 { + padding: 1rem !important + } + + .p-lg-4 { + padding: 1.5rem !important + } + + .p-lg-5 { + padding: 3rem !important + } + + .px-lg-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .px-lg-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .px-lg-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .px-lg-3 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .px-lg-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .px-lg-5 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-lg-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .py-lg-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .py-lg-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .py-lg-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .py-lg-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .py-lg-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .pt-lg-0 { + padding-top: 0 !important + } + + .pt-lg-1 { + padding-top: .25rem !important + } + + .pt-lg-2 { + padding-top: .5rem !important + } + + .pt-lg-3 { + padding-top: 1rem !important + } + + .pt-lg-4 { + padding-top: 1.5rem !important + } + + .pt-lg-5 { + padding-top: 3rem !important + } + + .pe-lg-0 { + padding-right: 0 !important + } + + .pe-lg-1 { + padding-right: .25rem !important + } + + .pe-lg-2 { + padding-right: .5rem !important + } + + .pe-lg-3 { + padding-right: 1rem !important + } + + .pe-lg-4 { + padding-right: 1.5rem !important + } + + .pe-lg-5 { + padding-right: 3rem !important + } + + .pb-lg-0 { + padding-bottom: 0 !important + } + + .pb-lg-1 { + padding-bottom: .25rem !important + } + + .pb-lg-2 { + padding-bottom: .5rem !important + } + + .pb-lg-3 { + padding-bottom: 1rem !important + } + + .pb-lg-4 { + padding-bottom: 1.5rem !important + } + + .pb-lg-5 { + padding-bottom: 3rem !important + } + + .ps-lg-0 { + padding-left: 0 !important + } + + .ps-lg-1 { + padding-left: .25rem !important + } + + .ps-lg-2 { + padding-left: .5rem !important + } + + .ps-lg-3 { + padding-left: 1rem !important + } + + .ps-lg-4 { + padding-left: 1.5rem !important + } + + .ps-lg-5 { + padding-left: 3rem !important + } + + .gap-lg-0 { + gap: 0 !important + } + + .gap-lg-1 { + gap: .25rem !important + } + + .gap-lg-2 { + gap: .5rem !important + } + + .gap-lg-3 { + gap: 1rem !important + } + + .gap-lg-4 { + gap: 1.5rem !important + } + + .gap-lg-5 { + gap: 3rem !important + } + + .row-gap-lg-0 { + row-gap: 0 !important + } + + .row-gap-lg-1 { + row-gap: .25rem !important + } + + .row-gap-lg-2 { + row-gap: .5rem !important + } + + .row-gap-lg-3 { + row-gap: 1rem !important + } + + .row-gap-lg-4 { + row-gap: 1.5rem !important + } + + .row-gap-lg-5 { + row-gap: 3rem !important + } + + .column-gap-lg-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important + } + + .column-gap-lg-1 { + -moz-column-gap: 0.25rem !important; + column-gap: .25rem !important + } + + .column-gap-lg-2 { + -moz-column-gap: 0.5rem !important; + column-gap: .5rem !important + } + + .column-gap-lg-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important + } + + .column-gap-lg-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important + } + + .column-gap-lg-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important + } + + .text-lg-start { + text-align: left !important + } + + .text-lg-end { + text-align: right !important + } + + .text-lg-center { + text-align: center !important + } +} + +@media (min-width:1200px) { + .float-xl-start { + float: left !important + } + + .float-xl-end { + float: right !important + } + + .float-xl-none { + float: none !important + } + + .object-fit-xl-contain { + -o-object-fit: contain !important; + object-fit: contain !important + } + + .object-fit-xl-cover { + -o-object-fit: cover !important; + object-fit: cover !important + } + + .object-fit-xl-fill { + -o-object-fit: fill !important; + object-fit: fill !important + } + + .object-fit-xl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important + } + + .object-fit-xl-none { + -o-object-fit: none !important; + object-fit: none !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-block { + display: block !important + } + + .d-xl-grid { + display: grid !important + } + + .d-xl-inline-grid { + display: inline-grid !important + } + + .d-xl-table { + display: table !important + } + + .d-xl-table-row { + display: table-row !important + } + + .d-xl-table-cell { + display: table-cell !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline-flex { + display: inline-flex !important + } + + .d-xl-none { + display: none !important + } + + .flex-xl-fill { + flex: 1 1 auto !important + } + + .flex-xl-row { + flex-direction: row !important + } + + .flex-xl-column { + flex-direction: column !important + } + + .flex-xl-row-reverse { + flex-direction: row-reverse !important + } + + .flex-xl-column-reverse { + flex-direction: column-reverse !important + } + + .flex-xl-grow-0 { + flex-grow: 0 !important + } + + .flex-xl-grow-1 { + flex-grow: 1 !important + } + + .flex-xl-shrink-0 { + flex-shrink: 0 !important + } + + .flex-xl-shrink-1 { + flex-shrink: 1 !important + } + + .flex-xl-wrap { + flex-wrap: wrap !important + } + + .flex-xl-nowrap { + flex-wrap: nowrap !important + } + + .flex-xl-wrap-reverse { + flex-wrap: wrap-reverse !important + } + + .justify-content-xl-start { + justify-content: flex-start !important + } + + .justify-content-xl-end { + justify-content: flex-end !important + } + + .justify-content-xl-center { + justify-content: center !important + } + + .justify-content-xl-between { + justify-content: space-between !important + } + + .justify-content-xl-around { + justify-content: space-around !important + } + + .justify-content-xl-evenly { + justify-content: space-evenly !important + } + + .align-items-xl-start { + align-items: flex-start !important + } + + .align-items-xl-end { + align-items: flex-end !important + } + + .align-items-xl-center { + align-items: center !important + } + + .align-items-xl-baseline { + align-items: baseline !important + } + + .align-items-xl-stretch { + align-items: stretch !important + } + + .align-content-xl-start { + align-content: flex-start !important + } + + .align-content-xl-end { + align-content: flex-end !important + } + + .align-content-xl-center { + align-content: center !important + } + + .align-content-xl-between { + align-content: space-between !important + } + + .align-content-xl-around { + align-content: space-around !important + } + + .align-content-xl-stretch { + align-content: stretch !important + } + + .align-self-xl-auto { + align-self: auto !important + } + + .align-self-xl-start { + align-self: flex-start !important + } + + .align-self-xl-end { + align-self: flex-end !important + } + + .align-self-xl-center { + align-self: center !important + } + + .align-self-xl-baseline { + align-self: baseline !important + } + + .align-self-xl-stretch { + align-self: stretch !important + } + + .order-xl-first { + order: -1 !important + } + + .order-xl-0 { + order: 0 !important + } + + .order-xl-1 { + order: 1 !important + } + + .order-xl-2 { + order: 2 !important + } + + .order-xl-3 { + order: 3 !important + } + + .order-xl-4 { + order: 4 !important + } + + .order-xl-5 { + order: 5 !important + } + + .order-xl-last { + order: 6 !important + } + + .m-xl-0 { + margin: 0 !important + } + + .m-xl-1 { + margin: .25rem !important + } + + .m-xl-2 { + margin: .5rem !important + } + + .m-xl-3 { + margin: 1rem !important + } + + .m-xl-4 { + margin: 1.5rem !important + } + + .m-xl-5 { + margin: 3rem !important + } + + .m-xl-auto { + margin: auto !important + } + + .mx-xl-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .mx-xl-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .mx-xl-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .mx-xl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .mx-xl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .mx-xl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .mx-xl-auto { + margin-right: auto !important; + margin-left: auto !important + } + + .my-xl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .my-xl-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .my-xl-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .my-xl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .my-xl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .my-xl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .my-xl-auto { + margin-top: auto !important; + margin-bottom: auto !important + } + + .mt-xl-0 { + margin-top: 0 !important + } + + .mt-xl-1 { + margin-top: .25rem !important + } + + .mt-xl-2 { + margin-top: .5rem !important + } + + .mt-xl-3 { + margin-top: 1rem !important + } + + .mt-xl-4 { + margin-top: 1.5rem !important + } + + .mt-xl-5 { + margin-top: 3rem !important + } + + .mt-xl-auto { + margin-top: auto !important + } + + .me-xl-0 { + margin-right: 0 !important + } + + .me-xl-1 { + margin-right: .25rem !important + } + + .me-xl-2 { + margin-right: .5rem !important + } + + .me-xl-3 { + margin-right: 1rem !important + } + + .me-xl-4 { + margin-right: 1.5rem !important + } + + .me-xl-5 { + margin-right: 3rem !important + } + + .me-xl-auto { + margin-right: auto !important + } + + .mb-xl-0 { + margin-bottom: 0 !important + } + + .mb-xl-1 { + margin-bottom: .25rem !important + } + + .mb-xl-2 { + margin-bottom: .5rem !important + } + + .mb-xl-3 { + margin-bottom: 1rem !important + } + + .mb-xl-4 { + margin-bottom: 1.5rem !important + } + + .mb-xl-5 { + margin-bottom: 3rem !important + } + + .mb-xl-auto { + margin-bottom: auto !important + } + + .ms-xl-0 { + margin-left: 0 !important + } + + .ms-xl-1 { + margin-left: .25rem !important + } + + .ms-xl-2 { + margin-left: .5rem !important + } + + .ms-xl-3 { + margin-left: 1rem !important + } + + .ms-xl-4 { + margin-left: 1.5rem !important + } + + .ms-xl-5 { + margin-left: 3rem !important + } + + .ms-xl-auto { + margin-left: auto !important + } + + .p-xl-0 { + padding: 0 !important + } + + .p-xl-1 { + padding: .25rem !important + } + + .p-xl-2 { + padding: .5rem !important + } + + .p-xl-3 { + padding: 1rem !important + } + + .p-xl-4 { + padding: 1.5rem !important + } + + .p-xl-5 { + padding: 3rem !important + } + + .px-xl-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .px-xl-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .px-xl-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .px-xl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .px-xl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .px-xl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-xl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .py-xl-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .py-xl-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .py-xl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .py-xl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .py-xl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .pt-xl-0 { + padding-top: 0 !important + } + + .pt-xl-1 { + padding-top: .25rem !important + } + + .pt-xl-2 { + padding-top: .5rem !important + } + + .pt-xl-3 { + padding-top: 1rem !important + } + + .pt-xl-4 { + padding-top: 1.5rem !important + } + + .pt-xl-5 { + padding-top: 3rem !important + } + + .pe-xl-0 { + padding-right: 0 !important + } + + .pe-xl-1 { + padding-right: .25rem !important + } + + .pe-xl-2 { + padding-right: .5rem !important + } + + .pe-xl-3 { + padding-right: 1rem !important + } + + .pe-xl-4 { + padding-right: 1.5rem !important + } + + .pe-xl-5 { + padding-right: 3rem !important + } + + .pb-xl-0 { + padding-bottom: 0 !important + } + + .pb-xl-1 { + padding-bottom: .25rem !important + } + + .pb-xl-2 { + padding-bottom: .5rem !important + } + + .pb-xl-3 { + padding-bottom: 1rem !important + } + + .pb-xl-4 { + padding-bottom: 1.5rem !important + } + + .pb-xl-5 { + padding-bottom: 3rem !important + } + + .ps-xl-0 { + padding-left: 0 !important + } + + .ps-xl-1 { + padding-left: .25rem !important + } + + .ps-xl-2 { + padding-left: .5rem !important + } + + .ps-xl-3 { + padding-left: 1rem !important + } + + .ps-xl-4 { + padding-left: 1.5rem !important + } + + .ps-xl-5 { + padding-left: 3rem !important + } + + .gap-xl-0 { + gap: 0 !important + } + + .gap-xl-1 { + gap: .25rem !important + } + + .gap-xl-2 { + gap: .5rem !important + } + + .gap-xl-3 { + gap: 1rem !important + } + + .gap-xl-4 { + gap: 1.5rem !important + } + + .gap-xl-5 { + gap: 3rem !important + } + + .row-gap-xl-0 { + row-gap: 0 !important + } + + .row-gap-xl-1 { + row-gap: .25rem !important + } + + .row-gap-xl-2 { + row-gap: .5rem !important + } + + .row-gap-xl-3 { + row-gap: 1rem !important + } + + .row-gap-xl-4 { + row-gap: 1.5rem !important + } + + .row-gap-xl-5 { + row-gap: 3rem !important + } + + .column-gap-xl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important + } + + .column-gap-xl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: .25rem !important + } + + .column-gap-xl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: .5rem !important + } + + .column-gap-xl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important + } + + .column-gap-xl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important + } + + .column-gap-xl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important + } + + .text-xl-start { + text-align: left !important + } + + .text-xl-end { + text-align: right !important + } + + .text-xl-center { + text-align: center !important + } +} + +@media (min-width:1400px) { + .float-xxl-start { + float: left !important + } + + .float-xxl-end { + float: right !important + } + + .float-xxl-none { + float: none !important + } + + .object-fit-xxl-contain { + -o-object-fit: contain !important; + object-fit: contain !important + } + + .object-fit-xxl-cover { + -o-object-fit: cover !important; + object-fit: cover !important + } + + .object-fit-xxl-fill { + -o-object-fit: fill !important; + object-fit: fill !important + } + + .object-fit-xxl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important + } + + .object-fit-xxl-none { + -o-object-fit: none !important; + object-fit: none !important + } + + .d-xxl-inline { + display: inline !important + } + + .d-xxl-inline-block { + display: inline-block !important + } + + .d-xxl-block { + display: block !important + } + + .d-xxl-grid { + display: grid !important + } + + .d-xxl-inline-grid { + display: inline-grid !important + } + + .d-xxl-table { + display: table !important + } + + .d-xxl-table-row { + display: table-row !important + } + + .d-xxl-table-cell { + display: table-cell !important + } + + .d-xxl-flex { + display: flex !important + } + + .d-xxl-inline-flex { + display: inline-flex !important + } + + .d-xxl-none { + display: none !important + } + + .flex-xxl-fill { + flex: 1 1 auto !important + } + + .flex-xxl-row { + flex-direction: row !important + } + + .flex-xxl-column { + flex-direction: column !important + } + + .flex-xxl-row-reverse { + flex-direction: row-reverse !important + } + + .flex-xxl-column-reverse { + flex-direction: column-reverse !important + } + + .flex-xxl-grow-0 { + flex-grow: 0 !important + } + + .flex-xxl-grow-1 { + flex-grow: 1 !important + } + + .flex-xxl-shrink-0 { + flex-shrink: 0 !important + } + + .flex-xxl-shrink-1 { + flex-shrink: 1 !important + } + + .flex-xxl-wrap { + flex-wrap: wrap !important + } + + .flex-xxl-nowrap { + flex-wrap: nowrap !important + } + + .flex-xxl-wrap-reverse { + flex-wrap: wrap-reverse !important + } + + .justify-content-xxl-start { + justify-content: flex-start !important + } + + .justify-content-xxl-end { + justify-content: flex-end !important + } + + .justify-content-xxl-center { + justify-content: center !important + } + + .justify-content-xxl-between { + justify-content: space-between !important + } + + .justify-content-xxl-around { + justify-content: space-around !important + } + + .justify-content-xxl-evenly { + justify-content: space-evenly !important + } + + .align-items-xxl-start { + align-items: flex-start !important + } + + .align-items-xxl-end { + align-items: flex-end !important + } + + .align-items-xxl-center { + align-items: center !important + } + + .align-items-xxl-baseline { + align-items: baseline !important + } + + .align-items-xxl-stretch { + align-items: stretch !important + } + + .align-content-xxl-start { + align-content: flex-start !important + } + + .align-content-xxl-end { + align-content: flex-end !important + } + + .align-content-xxl-center { + align-content: center !important + } + + .align-content-xxl-between { + align-content: space-between !important + } + + .align-content-xxl-around { + align-content: space-around !important + } + + .align-content-xxl-stretch { + align-content: stretch !important + } + + .align-self-xxl-auto { + align-self: auto !important + } + + .align-self-xxl-start { + align-self: flex-start !important + } + + .align-self-xxl-end { + align-self: flex-end !important + } + + .align-self-xxl-center { + align-self: center !important + } + + .align-self-xxl-baseline { + align-self: baseline !important + } + + .align-self-xxl-stretch { + align-self: stretch !important + } + + .order-xxl-first { + order: -1 !important + } + + .order-xxl-0 { + order: 0 !important + } + + .order-xxl-1 { + order: 1 !important + } + + .order-xxl-2 { + order: 2 !important + } + + .order-xxl-3 { + order: 3 !important + } + + .order-xxl-4 { + order: 4 !important + } + + .order-xxl-5 { + order: 5 !important + } + + .order-xxl-last { + order: 6 !important + } + + .m-xxl-0 { + margin: 0 !important + } + + .m-xxl-1 { + margin: .25rem !important + } + + .m-xxl-2 { + margin: .5rem !important + } + + .m-xxl-3 { + margin: 1rem !important + } + + .m-xxl-4 { + margin: 1.5rem !important + } + + .m-xxl-5 { + margin: 3rem !important + } + + .m-xxl-auto { + margin: auto !important + } + + .mx-xxl-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .mx-xxl-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .mx-xxl-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .mx-xxl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .mx-xxl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .mx-xxl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .mx-xxl-auto { + margin-right: auto !important; + margin-left: auto !important + } + + .my-xxl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .my-xxl-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .my-xxl-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .my-xxl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .my-xxl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .my-xxl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .my-xxl-auto { + margin-top: auto !important; + margin-bottom: auto !important + } + + .mt-xxl-0 { + margin-top: 0 !important + } + + .mt-xxl-1 { + margin-top: .25rem !important + } + + .mt-xxl-2 { + margin-top: .5rem !important + } + + .mt-xxl-3 { + margin-top: 1rem !important + } + + .mt-xxl-4 { + margin-top: 1.5rem !important + } + + .mt-xxl-5 { + margin-top: 3rem !important + } + + .mt-xxl-auto { + margin-top: auto !important + } + + .me-xxl-0 { + margin-right: 0 !important + } + + .me-xxl-1 { + margin-right: .25rem !important + } + + .me-xxl-2 { + margin-right: .5rem !important + } + + .me-xxl-3 { + margin-right: 1rem !important + } + + .me-xxl-4 { + margin-right: 1.5rem !important + } + + .me-xxl-5 { + margin-right: 3rem !important + } + + .me-xxl-auto { + margin-right: auto !important + } + + .mb-xxl-0 { + margin-bottom: 0 !important + } + + .mb-xxl-1 { + margin-bottom: .25rem !important + } + + .mb-xxl-2 { + margin-bottom: .5rem !important + } + + .mb-xxl-3 { + margin-bottom: 1rem !important + } + + .mb-xxl-4 { + margin-bottom: 1.5rem !important + } + + .mb-xxl-5 { + margin-bottom: 3rem !important + } + + .mb-xxl-auto { + margin-bottom: auto !important + } + + .ms-xxl-0 { + margin-left: 0 !important + } + + .ms-xxl-1 { + margin-left: .25rem !important + } + + .ms-xxl-2 { + margin-left: .5rem !important + } + + .ms-xxl-3 { + margin-left: 1rem !important + } + + .ms-xxl-4 { + margin-left: 1.5rem !important + } + + .ms-xxl-5 { + margin-left: 3rem !important + } + + .ms-xxl-auto { + margin-left: auto !important + } + + .p-xxl-0 { + padding: 0 !important + } + + .p-xxl-1 { + padding: .25rem !important + } + + .p-xxl-2 { + padding: .5rem !important + } + + .p-xxl-3 { + padding: 1rem !important + } + + .p-xxl-4 { + padding: 1.5rem !important + } + + .p-xxl-5 { + padding: 3rem !important + } + + .px-xxl-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .px-xxl-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .px-xxl-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .px-xxl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .px-xxl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .px-xxl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-xxl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .py-xxl-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .py-xxl-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .py-xxl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .py-xxl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .py-xxl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .pt-xxl-0 { + padding-top: 0 !important + } + + .pt-xxl-1 { + padding-top: .25rem !important + } + + .pt-xxl-2 { + padding-top: .5rem !important + } + + .pt-xxl-3 { + padding-top: 1rem !important + } + + .pt-xxl-4 { + padding-top: 1.5rem !important + } + + .pt-xxl-5 { + padding-top: 3rem !important + } + + .pe-xxl-0 { + padding-right: 0 !important + } + + .pe-xxl-1 { + padding-right: .25rem !important + } + + .pe-xxl-2 { + padding-right: .5rem !important + } + + .pe-xxl-3 { + padding-right: 1rem !important + } + + .pe-xxl-4 { + padding-right: 1.5rem !important + } + + .pe-xxl-5 { + padding-right: 3rem !important + } + + .pb-xxl-0 { + padding-bottom: 0 !important + } + + .pb-xxl-1 { + padding-bottom: .25rem !important + } + + .pb-xxl-2 { + padding-bottom: .5rem !important + } + + .pb-xxl-3 { + padding-bottom: 1rem !important + } + + .pb-xxl-4 { + padding-bottom: 1.5rem !important + } + + .pb-xxl-5 { + padding-bottom: 3rem !important + } + + .ps-xxl-0 { + padding-left: 0 !important + } + + .ps-xxl-1 { + padding-left: .25rem !important + } + + .ps-xxl-2 { + padding-left: .5rem !important + } + + .ps-xxl-3 { + padding-left: 1rem !important + } + + .ps-xxl-4 { + padding-left: 1.5rem !important + } + + .ps-xxl-5 { + padding-left: 3rem !important + } + + .gap-xxl-0 { + gap: 0 !important + } + + .gap-xxl-1 { + gap: .25rem !important + } + + .gap-xxl-2 { + gap: .5rem !important + } + + .gap-xxl-3 { + gap: 1rem !important + } + + .gap-xxl-4 { + gap: 1.5rem !important + } + + .gap-xxl-5 { + gap: 3rem !important + } + + .row-gap-xxl-0 { + row-gap: 0 !important + } + + .row-gap-xxl-1 { + row-gap: .25rem !important + } + + .row-gap-xxl-2 { + row-gap: .5rem !important + } + + .row-gap-xxl-3 { + row-gap: 1rem !important + } + + .row-gap-xxl-4 { + row-gap: 1.5rem !important + } + + .row-gap-xxl-5 { + row-gap: 3rem !important + } + + .column-gap-xxl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important + } + + .column-gap-xxl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: .25rem !important + } + + .column-gap-xxl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: .5rem !important + } + + .column-gap-xxl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important + } + + .column-gap-xxl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important + } + + .column-gap-xxl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important + } + + .text-xxl-start { + text-align: left !important + } + + .text-xxl-end { + text-align: right !important + } + + .text-xxl-center { + text-align: center !important + } +} + +@media (min-width:1200px) { + .fs-1 { + font-size: 2.5rem !important + } + + .fs-2 { + font-size: 2rem !important + } + + .fs-3 { + font-size: 1.75rem !important + } + + .fs-4 { + font-size: 1.5rem !important + } +} + +@media print { + .d-print-inline { + display: inline !important + } + + .d-print-inline-block { + display: inline-block !important + } + + .d-print-block { + display: block !important + } + + .d-print-grid { + display: grid !important + } + + .d-print-inline-grid { + display: inline-grid !important + } + + .d-print-table { + display: table !important + } + + .d-print-table-row { + display: table-row !important + } + + .d-print-table-cell { + display: table-cell !important + } + + .d-print-flex { + display: flex !important + } + + .d-print-inline-flex { + display: inline-flex !important + } + + .d-print-none { + display: none !important + } +} + +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/public/static/css/fontawesome.css b/public/static/css/fontawesome.css new file mode 100644 index 0000000..2c5249a --- /dev/null +++ b/public/static/css/fontawesome.css @@ -0,0 +1,9 @@ +/*! + * Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-classic,.fa-regular,.fa-sharp,.fa-solid,.fab,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-classic,.fa-regular,.fa-solid,.far,.fas{font-family:"Font Awesome 6 Free"}.fa-brands,.fab{font-family:"Font Awesome 6 Brands"}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{-webkit-animation-name:fa-beat;animation-name:fa-beat;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{-webkit-animation-name:fa-bounce;animation-name:fa-bounce;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{-webkit-animation-name:fa-fade;animation-name:fa-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{-webkit-animation-name:fa-beat-fade;animation-name:fa-beat-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{-webkit-animation-name:fa-flip;animation-name:fa-flip;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{-webkit-animation-name:fa-shake;animation-name:fa-shake;-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-duration:var(--fa-animation-duration,2s);animation-duration:var(--fa-animation-duration,2s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,steps(8));animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{-webkit-animation-delay:-1ms;animation-delay:-1ms;-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1;-webkit-transition-delay:0s;transition-delay:0s;-webkit-transition-duration:0s;transition-duration:0s}}@-webkit-keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@-webkit-keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@-webkit-keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@-webkit-keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@-webkit-keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@-webkit-keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}.fa-rotate-by{-webkit-transform:rotate(var(--fa-rotate-angle,none));transform:rotate(var(--fa-rotate-angle,none))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} + +.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-fill-drip:before{content:"\f576"}.fa-arrows-to-circle:before{content:"\e4bd"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-at:before{content:"\40"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-text-height:before{content:"\f034"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-stethoscope:before{content:"\f0f1"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-info:before{content:"\f129"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-explosion:before{content:"\e4e9"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-wave-square:before{content:"\f83e"}.fa-ring:before{content:"\f70b"}.fa-building-un:before{content:"\e4d9"}.fa-dice-three:before{content:"\f527"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-anchor-circle-check:before{content:"\e4aa"}.fa-building-circle-arrow-right:before{content:"\e4d1"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-arrows-up-to-line:before{content:"\e4c2"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-door-open:before{content:"\f52b"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-atom:before{content:"\f5d2"}.fa-soap:before{content:"\e06e"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-bridge-circle-check:before{content:"\e4c9"}.fa-pump-medical:before{content:"\e06a"}.fa-fingerprint:before{content:"\f577"}.fa-hand-point-right:before{content:"\f0a4"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-flag-checkered:before{content:"\f11e"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-school-circle-exclamation:before{content:"\e56c"}.fa-crop:before{content:"\f125"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-users-rectangle:before{content:"\e594"}.fa-people-roof:before{content:"\e537"}.fa-people-line:before{content:"\e534"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-diagram-predecessor:before{content:"\e477"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-laptop:before{content:"\f109"}.fa-file-csv:before{content:"\f6dd"}.fa-menorah:before{content:"\f676"}.fa-truck-plane:before{content:"\e58f"}.fa-record-vinyl:before{content:"\f8d9"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-bong:before{content:"\f55c"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-arrow-down-up-across-line:before{content:"\e4af"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-jar-wheat:before{content:"\e517"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-file-circle-exclamation:before{content:"\e4eb"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-pager:before{content:"\f815"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-strikethrough:before{content:"\f0cc"}.fa-k:before{content:"\4b"}.fa-landmark-flag:before{content:"\e51c"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-backward:before{content:"\f04a"}.fa-caret-right:before{content:"\f0da"}.fa-comments:before{content:"\f086"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-code-pull-request:before{content:"\e13c"}.fa-clipboard-list:before{content:"\f46d"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-user-check:before{content:"\f4fc"}.fa-vial-virus:before{content:"\e597"}.fa-sheet-plastic:before{content:"\e571"}.fa-blog:before{content:"\f781"}.fa-user-ninja:before{content:"\f504"}.fa-person-arrow-up-from-line:before{content:"\e539"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-toggle-off:before{content:"\f204"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-person-drowning:before{content:"\e545"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-spray-can:before{content:"\f5bd"}.fa-truck-monster:before{content:"\f63b"}.fa-w:before{content:"\57"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-rainbow:before{content:"\f75b"}.fa-circle-notch:before{content:"\f1ce"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-paw:before{content:"\f1b0"}.fa-cloud:before{content:"\f0c2"}.fa-trowel-bricks:before{content:"\e58a"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-hospital-user:before{content:"\f80d"}.fa-tent-arrow-left-right:before{content:"\e57f"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-binoculars:before{content:"\f1e5"}.fa-microphone-slash:before{content:"\f131"}.fa-box-tissue:before{content:"\e05b"}.fa-motorcycle:before{content:"\f21c"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-mars-and-venus-burst:before{content:"\e523"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-sun-plant-wilt:before{content:"\e57a"}.fa-toilets-portable:before{content:"\e584"}.fa-hockey-puck:before{content:"\f453"}.fa-table:before{content:"\f0ce"}.fa-magnifying-glass-arrow-right:before{content:"\e521"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-users-slash:before{content:"\e073"}.fa-clover:before{content:"\e139"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-star-and-crescent:before{content:"\f699"}.fa-house-fire:before{content:"\e50c"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-helicopter:before{content:"\f533"}.fa-compass:before{content:"\f14e"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-file-circle-question:before{content:"\e4ef"}.fa-laptop-code:before{content:"\f5fc"}.fa-swatchbook:before{content:"\f5c3"}.fa-prescription-bottle:before{content:"\f485"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-people-group:before{content:"\e533"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-film:before{content:"\f008"}.fa-ruler-horizontal:before{content:"\f547"}.fa-people-robbery:before{content:"\e536"}.fa-lightbulb:before{content:"\f0eb"}.fa-caret-left:before{content:"\f0d9"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-school-circle-xmark:before{content:"\e56d"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-sitemap:before{content:"\f0e8"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-memory:before{content:"\f538"}.fa-road-spikes:before{content:"\e568"}.fa-fire-burner:before{content:"\e4f1"}.fa-flag:before{content:"\f024"}.fa-hanukiah:before{content:"\f6e6"}.fa-feather:before{content:"\f52d"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-comment-slash:before{content:"\f4b3"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-compress:before{content:"\f066"}.fa-wheat-alt:before,.fa-wheat-awn:before{content:"\e2cd"}.fa-ankh:before{content:"\f644"}.fa-hands-holding-child:before{content:"\e4fa"}.fa-asterisk:before{content:"\2a"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-peseta-sign:before{content:"\e221"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-ghost:before{content:"\f6e2"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-cart-plus:before{content:"\f217"}.fa-gamepad:before{content:"\f11b"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-egg:before{content:"\f7fb"}.fa-house-medical-circle-xmark:before{content:"\e513"}.fa-campground:before{content:"\f6bb"}.fa-folder-plus:before{content:"\f65e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-lock:before{content:"\f023"}.fa-gas-pump:before{content:"\f52f"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-house-flood-water:before{content:"\e50e"}.fa-tree:before{content:"\f1bb"}.fa-bridge-lock:before{content:"\e4cc"}.fa-sack-dollar:before{content:"\f81d"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-car-side:before{content:"\f5e4"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-heart-circle-minus:before{content:"\e4ff"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-microscope:before{content:"\f610"}.fa-sink:before{content:"\e06d"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-mitten:before{content:"\f7b5"}.fa-person-rays:before{content:"\e54d"}.fa-users:before{content:"\f0c0"}.fa-eye-slash:before{content:"\f070"}.fa-flask-vial:before{content:"\e4f3"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-om:before{content:"\f679"}.fa-worm:before{content:"\e599"}.fa-house-circle-xmark:before{content:"\e50b"}.fa-plug:before{content:"\f1e6"}.fa-chevron-up:before{content:"\f077"}.fa-hand-spock:before{content:"\f259"}.fa-stopwatch:before{content:"\f2f2"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-bridge-circle-xmark:before{content:"\e4cb"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-chess-bishop:before{content:"\f43a"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-road-circle-check:before{content:"\e564"}.fa-dice-five:before{content:"\f523"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-land-mine-on:before{content:"\e51b"}.fa-i-cursor:before{content:"\f246"}.fa-stamp:before{content:"\f5bf"}.fa-stairs:before{content:"\e289"}.fa-i:before{content:"\49"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-pills:before{content:"\f484"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-tooth:before{content:"\f5c9"}.fa-v:before{content:"\56"}.fa-bangladeshi-taka-sign:before{content:"\e2e6"}.fa-bicycle:before{content:"\f206"}.fa-rod-asclepius:before,.fa-rod-snake:before,.fa-staff-aesculapius:before,.fa-staff-snake:before{content:"\e579"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-wheat-awn-circle-exclamation:before{content:"\e598"}.fa-snowman:before{content:"\f7d0"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-road-barrier:before{content:"\e562"}.fa-school:before{content:"\f549"}.fa-igloo:before{content:"\f7ae"}.fa-joint:before{content:"\f595"}.fa-angle-right:before{content:"\f105"}.fa-horse:before{content:"\f6f0"}.fa-q:before{content:"\51"}.fa-g:before{content:"\47"}.fa-notes-medical:before{content:"\f481"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-dong-sign:before{content:"\e169"}.fa-capsules:before{content:"\f46b"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-hand-point-up:before{content:"\f0a6"}.fa-money-bill:before{content:"\f0d6"}.fa-bookmark:before{content:"\f02e"}.fa-align-justify:before{content:"\f039"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-helmet-un:before{content:"\e503"}.fa-bullseye:before{content:"\f140"}.fa-bacon:before{content:"\f7e5"}.fa-hand-point-down:before{content:"\f0a7"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-folder-blank:before,.fa-folder:before{content:"\f07b"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-radiation:before{content:"\f7b9"}.fa-chart-simple:before{content:"\e473"}.fa-mars-stroke:before{content:"\f229"}.fa-vial:before{content:"\f492"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-e:before{content:"\45"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-bridge-circle-exclamation:before{content:"\e4ca"}.fa-user:before{content:"\f007"}.fa-school-circle-check:before{content:"\e56b"}.fa-dumpster:before{content:"\f793"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-building-user:before{content:"\e4da"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-highlighter:before{content:"\f591"}.fa-key:before{content:"\f084"}.fa-bullhorn:before{content:"\f0a1"}.fa-globe:before{content:"\f0ac"}.fa-synagogue:before{content:"\f69b"}.fa-person-half-dress:before{content:"\e548"}.fa-road-bridge:before{content:"\e563"}.fa-location-arrow:before{content:"\f124"}.fa-c:before{content:"\43"}.fa-tablet-button:before{content:"\f10a"}.fa-building-lock:before{content:"\e4d6"}.fa-pizza-slice:before{content:"\f818"}.fa-money-bill-wave:before{content:"\f53a"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-house-flag:before{content:"\e50d"}.fa-person-circle-minus:before{content:"\e540"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-camera-rotate:before{content:"\e0d8"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-star:before{content:"\f005"}.fa-repeat:before{content:"\f363"}.fa-cross:before{content:"\f654"}.fa-box:before{content:"\f466"}.fa-venus-mars:before{content:"\f228"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-charging-station:before{content:"\f5e7"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-mobile-retro:before{content:"\e527"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-spider:before{content:"\f717"}.fa-hands-bound:before{content:"\e4f9"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-plane-circle-exclamation:before{content:"\e556"}.fa-x-ray:before{content:"\f497"}.fa-spell-check:before{content:"\f891"}.fa-slash:before{content:"\f715"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-server:before{content:"\f233"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-shop-lock:before{content:"\e4a5"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-blender-phone:before{content:"\f6b6"}.fa-building-wheat:before{content:"\e4db"}.fa-person-breastfeeding:before{content:"\e53a"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-venus:before{content:"\f221"}.fa-passport:before{content:"\f5ab"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-temperature-high:before{content:"\f769"}.fa-microchip:before{content:"\f2db"}.fa-crown:before{content:"\f521"}.fa-weight-hanging:before{content:"\f5cd"}.fa-xmarks-lines:before{content:"\e59a"}.fa-file-prescription:before{content:"\f572"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-chess-knight:before{content:"\f441"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-wheelchair:before{content:"\f193"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-toggle-on:before{content:"\f205"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-l:before{content:"\4c"}.fa-fire:before{content:"\f06d"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-folder-open:before{content:"\f07c"}.fa-heart-circle-plus:before{content:"\e500"}.fa-code-fork:before{content:"\e13b"}.fa-city:before{content:"\f64f"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-pepper-hot:before{content:"\f816"}.fa-unlock:before{content:"\f09c"}.fa-colon-sign:before{content:"\e140"}.fa-headset:before{content:"\f590"}.fa-store-slash:before{content:"\e071"}.fa-road-circle-xmark:before{content:"\e566"}.fa-user-minus:before{content:"\f503"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-clipboard:before{content:"\f328"}.fa-house-circle-exclamation:before{content:"\e50a"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-underline:before{content:"\f0cd"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-signature:before{content:"\f5b7"}.fa-stroopwafel:before{content:"\f551"}.fa-bold:before{content:"\f032"}.fa-anchor-lock:before{content:"\e4ad"}.fa-building-ngo:before{content:"\e4d7"}.fa-manat-sign:before{content:"\e1d5"}.fa-not-equal:before{content:"\f53e"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-jedi:before{content:"\f669"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-mug-hot:before{content:"\f7b6"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-gift:before{content:"\f06b"}.fa-dice-two:before{content:"\f528"}.fa-chess-queen:before{content:"\f445"}.fa-glasses:before{content:"\f530"}.fa-chess-board:before{content:"\f43c"}.fa-building-circle-check:before{content:"\e4d2"}.fa-person-chalkboard:before{content:"\e53d"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-cloud-showers-water:before{content:"\e4e4"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-less-than-equal:before{content:"\f537"}.fa-train:before{content:"\f238"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-crow:before{content:"\f520"}.fa-sailboat:before{content:"\e445"}.fa-window-restore:before{content:"\f2d2"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-torii-gate:before{content:"\f6a1"}.fa-frog:before{content:"\f52e"}.fa-bucket:before{content:"\e4cf"}.fa-image:before{content:"\f03e"}.fa-microphone:before{content:"\f130"}.fa-cow:before{content:"\f6c8"}.fa-caret-up:before{content:"\f0d8"}.fa-screwdriver:before{content:"\f54a"}.fa-folder-closed:before{content:"\e185"}.fa-house-tsunami:before{content:"\e515"}.fa-square-nfi:before{content:"\e576"}.fa-arrow-up-from-ground-water:before{content:"\e4b5"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-lemon:before{content:"\f094"}.fa-head-side-mask:before{content:"\e063"}.fa-handshake:before{content:"\f2b5"}.fa-gem:before{content:"\f3a5"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-smoking:before{content:"\f48d"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-monument:before{content:"\f5a6"}.fa-snowplow:before{content:"\f7d2"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-cannabis:before{content:"\f55f"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-tablets:before{content:"\f490"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-chair:before{content:"\f6c0"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-plate-wheat:before{content:"\e55a"}.fa-icicles:before{content:"\f7ad"}.fa-person-shelter:before{content:"\e54f"}.fa-neuter:before{content:"\f22c"}.fa-id-badge:before{content:"\f2c1"}.fa-marker:before{content:"\f5a1"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-helicopter-symbol:before{content:"\e502"}.fa-universal-access:before{content:"\f29a"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-lari-sign:before{content:"\e1c8"}.fa-volcano:before{content:"\f770"}.fa-person-walking-dashed-line-arrow-right:before{content:"\e553"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-viruses:before{content:"\e076"}.fa-square-person-confined:before{content:"\e577"}.fa-user-tie:before{content:"\f508"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-tent-arrow-down-to-line:before{content:"\e57e"}.fa-certificate:before{content:"\f0a3"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-suitcase:before{content:"\f0f2"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-camera-retro:before{content:"\f083"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-box-open:before{content:"\f49e"}.fa-scroll:before{content:"\f70e"}.fa-spa:before{content:"\f5bb"}.fa-location-pin-lock:before{content:"\e51f"}.fa-pause:before{content:"\f04c"}.fa-hill-avalanche:before{content:"\e507"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-bomb:before{content:"\f1e2"}.fa-registered:before{content:"\f25d"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-subscript:before{content:"\f12c"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-burst:before{content:"\e4dc"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-money-bills:before{content:"\e1f3"}.fa-smog:before{content:"\f75f"}.fa-crutch:before{content:"\f7f7"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-palette:before{content:"\f53f"}.fa-arrows-turn-right:before{content:"\e4c0"}.fa-vest:before{content:"\e085"}.fa-ferry:before{content:"\e4ea"}.fa-arrows-down-to-people:before{content:"\e4b9"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-boxes-packing:before{content:"\e4c7"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-group-arrows-rotate:before{content:"\e4f6"}.fa-bowl-food:before{content:"\e4c6"}.fa-candy-cane:before{content:"\f786"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-cloud-bolt:before,.fa-thunderstorm:before{content:"\f76c"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-file-word:before{content:"\f1c2"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-house-lock:before{content:"\e510"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-children:before{content:"\e4e1"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-envelope-open:before{content:"\f2b6"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-mattress-pillow:before{content:"\e525"}.fa-guarani-sign:before{content:"\e19a"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-fire-extinguisher:before{content:"\f134"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-greater-than-equal:before{content:"\f532"}.fa-shield-alt:before,.fa-shield-halved:before{content:"\f3ed"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-virus:before{content:"\e074"}.fa-envelope-circle-check:before{content:"\e4e8"}.fa-layer-group:before{content:"\f5fd"}.fa-arrows-to-dot:before{content:"\e4be"}.fa-archway:before{content:"\f557"}.fa-heart-circle-check:before{content:"\e4fd"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-square:before{content:"\f0c8"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-couch:before{content:"\f4b8"}.fa-cedi-sign:before{content:"\e0df"}.fa-italic:before{content:"\f033"}.fa-church:before{content:"\f51d"}.fa-comments-dollar:before{content:"\f653"}.fa-democrat:before{content:"\f747"}.fa-z:before{content:"\5a"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-road-lock:before{content:"\e567"}.fa-a:before{content:"\41"}.fa-temperature-arrow-down:before,.fa-temperature-down:before{content:"\e03f"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-p:before{content:"\50"}.fa-snowflake:before{content:"\f2dc"}.fa-newspaper:before{content:"\f1ea"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-locust:before{content:"\e520"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-person-dress-burst:before{content:"\e544"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-vector-square:before{content:"\f5cb"}.fa-bread-slice:before{content:"\f7ec"}.fa-language:before{content:"\f1ab"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-filter:before{content:"\f0b0"}.fa-question:before{content:"\3f"}.fa-file-signature:before{content:"\f573"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-house-chimney-user:before{content:"\e065"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-puzzle-piece:before{content:"\f12e"}.fa-money-check:before{content:"\f53c"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-code:before{content:"\f121"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-building-circle-exclamation:before{content:"\e4d3"}.fa-magnifying-glass-chart:before{content:"\e522"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-cubes-stacked:before{content:"\e4e6"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-virus-covid:before{content:"\e4a8"}.fa-austral-sign:before{content:"\e0a9"}.fa-f:before{content:"\46"}.fa-leaf:before{content:"\f06c"}.fa-road:before{content:"\f018"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-person-circle-plus:before{content:"\e541"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-sack-xmark:before{content:"\e56a"}.fa-file-excel:before{content:"\f1c3"}.fa-file-contract:before{content:"\f56c"}.fa-fish-fins:before{content:"\e4f2"}.fa-building-flag:before{content:"\e4d5"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-object-ungroup:before{content:"\f248"}.fa-poop:before{content:"\f619"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-kaaba:before{content:"\f66b"}.fa-toilet-paper:before{content:"\f71e"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-eject:before{content:"\f052"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-plane-circle-check:before{content:"\e555"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-object-group:before{content:"\f247"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-mask-ventilator:before{content:"\e524"}.fa-arrow-right:before{content:"\f061"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-cash-register:before{content:"\f788"}.fa-person-circle-question:before{content:"\e542"}.fa-h:before{content:"\48"}.fa-tarp:before{content:"\e57b"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-arrows-to-eye:before{content:"\e4bf"}.fa-plug-circle-bolt:before{content:"\e55b"}.fa-heart:before{content:"\f004"}.fa-mars-and-venus:before{content:"\f224"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-dumpster-fire:before{content:"\f794"}.fa-house-crack:before{content:"\e3b1"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-bottle-water:before{content:"\e4c5"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-kitchen-set:before{content:"\e51a"}.fa-r:before{content:"\52"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-cube:before{content:"\f1b2"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-shield-dog:before{content:"\e573"}.fa-solar-panel:before{content:"\f5ba"}.fa-lock-open:before{content:"\f3c1"}.fa-elevator:before{content:"\e16d"}.fa-money-bill-transfer:before{content:"\e528"}.fa-money-bill-trend-up:before{content:"\e529"}.fa-house-flood-water-circle-arrow-right:before{content:"\e50f"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-circle:before{content:"\f111"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-recycle:before{content:"\f1b8"}.fa-user-astronaut:before{content:"\f4fb"}.fa-plane-slash:before{content:"\e069"}.fa-trademark:before{content:"\f25c"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-satellite-dish:before{content:"\f7c0"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-users-rays:before{content:"\e593"}.fa-wallet:before{content:"\f555"}.fa-clipboard-check:before{content:"\f46c"}.fa-file-audio:before{content:"\f1c7"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-wrench:before{content:"\f0ad"}.fa-bugs:before{content:"\e4d0"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-file-image:before{content:"\f1c5"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-plane-departure:before{content:"\f5b0"}.fa-handshake-slash:before{content:"\e060"}.fa-book-bookmark:before{content:"\e0bb"}.fa-code-branch:before{content:"\f126"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-bridge:before{content:"\e4c8"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-truck-front:before{content:"\e2b7"}.fa-cat:before{content:"\f6be"}.fa-anchor-circle-exclamation:before{content:"\e4ab"}.fa-truck-field:before{content:"\e58d"}.fa-route:before{content:"\f4d7"}.fa-clipboard-question:before{content:"\e4e3"}.fa-panorama:before{content:"\e209"}.fa-comment-medical:before{content:"\f7f5"}.fa-teeth-open:before{content:"\f62f"}.fa-file-circle-minus:before{content:"\e4ed"}.fa-tags:before{content:"\f02c"}.fa-wine-glass:before{content:"\f4e3"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-house-signal:before{content:"\e012"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-faucet-drip:before{content:"\e006"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-terminal:before{content:"\f120"}.fa-mobile-button:before{content:"\f10b"}.fa-house-medical-flag:before{content:"\e514"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-tape:before{content:"\f4db"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-eye:before{content:"\f06e"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-audio-description:before{content:"\f29e"}.fa-person-military-to-person:before{content:"\e54c"}.fa-file-shield:before{content:"\e4f0"}.fa-user-slash:before{content:"\f506"}.fa-pen:before{content:"\f304"}.fa-tower-observation:before{content:"\e586"}.fa-file-code:before{content:"\f1c9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-bus:before{content:"\f207"}.fa-heart-circle-xmark:before{content:"\e501"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-window-maximize:before{content:"\f2d0"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-prescription:before{content:"\f5b1"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-vihara:before{content:"\f6a7"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-plant-wilt:before{content:"\e5aa"}.fa-diamond:before{content:"\f219"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-bacterium:before{content:"\e05a"}.fa-hand-pointer:before{content:"\f25a"}.fa-drum-steelpan:before{content:"\f56a"}.fa-hand-scissors:before{content:"\f257"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-biohazard:before{content:"\f780"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-mars-double:before{content:"\f227"}.fa-child-dress:before{content:"\e59c"}.fa-users-between-lines:before{content:"\e591"}.fa-lungs-virus:before{content:"\e067"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-phone:before{content:"\f095"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-child-reaching:before{content:"\e59d"}.fa-head-side-virus:before{content:"\e064"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-door-closed:before{content:"\f52a"}.fa-shield-virus:before{content:"\e06c"}.fa-dice-six:before{content:"\f526"}.fa-mosquito-net:before{content:"\e52c"}.fa-bridge-water:before{content:"\e4ce"}.fa-person-booth:before{content:"\f756"}.fa-text-width:before{content:"\f035"}.fa-hat-wizard:before{content:"\f6e8"}.fa-pen-fancy:before{content:"\f5ac"}.fa-digging:before,.fa-person-digging:before{content:"\f85e"}.fa-trash:before{content:"\f1f8"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-book-medical:before{content:"\f7e6"}.fa-poo:before{content:"\f2fe"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-cubes:before{content:"\f1b3"}.fa-divide:before{content:"\f529"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-headphones:before{content:"\f025"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-clapping:before{content:"\e1a8"}.fa-republican:before{content:"\f75e"}.fa-arrow-left:before{content:"\f060"}.fa-person-circle-xmark:before{content:"\e543"}.fa-ruler:before{content:"\f545"}.fa-align-left:before{content:"\f036"}.fa-dice-d6:before{content:"\f6d1"}.fa-restroom:before{content:"\f7bd"}.fa-j:before{content:"\4a"}.fa-users-viewfinder:before{content:"\e595"}.fa-file-video:before{content:"\f1c8"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-file-pdf:before{content:"\f1c1"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-o:before{content:"\4f"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-user-secret:before{content:"\f21b"}.fa-otter:before{content:"\f700"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-comment-dollar:before{content:"\f651"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-book-tanakh:before,.fa-tanakh:before{content:"\f827"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-clipboard-user:before{content:"\f7f3"}.fa-child:before{content:"\f1ae"}.fa-lira-sign:before{content:"\f195"}.fa-satellite:before{content:"\f7bf"}.fa-plane-lock:before{content:"\e558"}.fa-tag:before{content:"\f02b"}.fa-comment:before{content:"\f075"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-envelope:before{content:"\f0e0"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-paperclip:before{content:"\f0c6"}.fa-arrow-right-to-city:before{content:"\e4b3"}.fa-ribbon:before{content:"\f4d6"}.fa-lungs:before{content:"\f604"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-border-none:before{content:"\f850"}.fa-circle-nodes:before{content:"\e4e2"}.fa-parachute-box:before{content:"\f4cd"}.fa-indent:before{content:"\f03c"}.fa-truck-field-un:before{content:"\e58e"}.fa-hourglass-empty:before,.fa-hourglass:before{content:"\f254"}.fa-mountain:before{content:"\f6fc"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-cloud-meatball:before{content:"\f73b"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-square-virus:before{content:"\e578"}.fa-meteor:before{content:"\f753"}.fa-car-on:before{content:"\e4dd"}.fa-sleigh:before{content:"\f7cc"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-water:before{content:"\f773"}.fa-calendar-check:before{content:"\f274"}.fa-braille:before{content:"\f2a1"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-landmark:before{content:"\f66f"}.fa-truck:before{content:"\f0d1"}.fa-crosshairs:before{content:"\f05b"}.fa-person-cane:before{content:"\e53c"}.fa-tent:before{content:"\e57d"}.fa-vest-patches:before{content:"\e086"}.fa-check-double:before{content:"\f560"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-money-bill-wheat:before{content:"\e52a"}.fa-cookie:before{content:"\f563"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-dumbbell:before{content:"\f44b"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-tarp-droplet:before{content:"\e57c"}.fa-house-medical-circle-check:before{content:"\e511"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-calendar-plus:before{content:"\f271"}.fa-plane-arrival:before{content:"\f5af"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-chart-gantt:before{content:"\e0e4"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-dna:before{content:"\f471"}.fa-virus-slash:before{content:"\e075"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-chess:before{content:"\f439"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-plug-circle-check:before{content:"\e55c"}.fa-street-view:before{content:"\f21d"}.fa-franc-sign:before{content:"\e18f"}.fa-volume-off:before{content:"\f026"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-mosque:before{content:"\f678"}.fa-mosquito:before{content:"\e52b"}.fa-star-of-david:before{content:"\f69a"}.fa-person-military-rifle:before{content:"\e54b"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-vials:before{content:"\f493"}.fa-plug-circle-plus:before{content:"\e55f"}.fa-place-of-worship:before{content:"\f67f"}.fa-grip-vertical:before{content:"\f58e"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-u:before{content:"\55"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-pallet:before{content:"\f482"}.fa-faucet:before{content:"\e005"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-s:before{content:"\53"}.fa-timeline:before{content:"\e29c"}.fa-keyboard:before{content:"\f11c"}.fa-caret-down:before{content:"\f0d7"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-mobile-android-alt:before,.fa-mobile-screen:before{content:"\f3cf"}.fa-plane-up:before{content:"\e22d"}.fa-piggy-bank:before{content:"\f4d3"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-mountain-city:before{content:"\e52e"}.fa-coins:before{content:"\f51e"}.fa-khanda:before{content:"\f66d"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-folder-tree:before{content:"\f802"}.fa-network-wired:before{content:"\f6ff"}.fa-map-pin:before{content:"\f276"}.fa-hamsa:before{content:"\f665"}.fa-cent-sign:before{content:"\e3f5"}.fa-flask:before{content:"\f0c3"}.fa-person-pregnant:before{content:"\e31e"}.fa-wand-sparkles:before{content:"\f72b"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-ticket:before{content:"\f145"}.fa-power-off:before{content:"\f011"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-flag-usa:before{content:"\f74d"}.fa-laptop-file:before{content:"\e51d"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-diagram-next:before{content:"\e476"}.fa-person-rifle:before{content:"\e54e"}.fa-house-medical-circle-exclamation:before{content:"\e512"}.fa-closed-captioning:before{content:"\f20a"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-venus-double:before{content:"\f226"}.fa-images:before{content:"\f302"}.fa-calculator:before{content:"\f1ec"}.fa-people-pulling:before{content:"\e535"}.fa-n:before{content:"\4e"}.fa-cable-car:before,.fa-tram:before{content:"\f7da"}.fa-cloud-rain:before{content:"\f73d"}.fa-building-circle-xmark:before{content:"\e4d4"}.fa-ship:before{content:"\f21a"}.fa-arrows-down-to-line:before{content:"\e4b8"}.fa-download:before{content:"\f019"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-file-circle-check:before{content:"\e5a0"}.fa-forward:before{content:"\f04e"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-align-center:before{content:"\f037"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-heart-circle-exclamation:before{content:"\e4fe"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-calendar-week:before{content:"\f784"}.fa-laptop-medical:before{content:"\f812"}.fa-b:before{content:"\42"}.fa-file-medical:before{content:"\f477"}.fa-dice-one:before{content:"\f525"}.fa-kiwi-bird:before{content:"\f535"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-mill-sign:before{content:"\e1ed"}.fa-bowl-rice:before{content:"\e2eb"}.fa-skull:before{content:"\f54c"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-truck-pickup:before{content:"\f63c"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-stop:before{content:"\f04d"}.fa-code-merge:before{content:"\f387"}.fa-upload:before{content:"\f093"}.fa-hurricane:before{content:"\f751"}.fa-mound:before{content:"\e52d"}.fa-toilet-portable:before{content:"\e583"}.fa-compact-disc:before{content:"\f51f"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-caravan:before{content:"\f8ff"}.fa-shield-cat:before{content:"\e572"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-glass-water:before{content:"\e4f4"}.fa-oil-well:before{content:"\e532"}.fa-vault:before{content:"\e2c5"}.fa-mars:before{content:"\f222"}.fa-toilet:before{content:"\f7d8"}.fa-plane-circle-xmark:before{content:"\e557"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-sun:before{content:"\f185"}.fa-guitar:before{content:"\f7a6"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-horse-head:before{content:"\f7ab"}.fa-bore-hole:before{content:"\e4c3"}.fa-industry:before{content:"\f275"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-arrows-turn-to-dots:before{content:"\e4c1"}.fa-florin-sign:before{content:"\e184"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-less-than:before{content:"\3c"}.fa-angle-down:before{content:"\f107"}.fa-car-tunnel:before{content:"\e4de"}.fa-head-side-cough:before{content:"\e061"}.fa-grip-lines:before{content:"\f7a4"}.fa-thumbs-down:before{content:"\f165"}.fa-user-lock:before{content:"\f502"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-anchor-circle-xmark:before{content:"\e4ac"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-chess-pawn:before{content:"\f443"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-person-through-window:before{content:"\e5a9"}.fa-toolbox:before{content:"\f552"}.fa-hands-holding-circle:before{content:"\e4fb"}.fa-bug:before{content:"\f188"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-hand-holding-hand:before{content:"\e4f7"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-mountain-sun:before{content:"\e52f"}.fa-arrows-left-right-to-line:before{content:"\e4ba"}.fa-dice-d20:before{content:"\f6cf"}.fa-truck-droplet:before{content:"\e58c"}.fa-file-circle-xmark:before{content:"\e5a1"}.fa-temperature-arrow-up:before,.fa-temperature-up:before{content:"\e040"}.fa-medal:before{content:"\f5a2"}.fa-bed:before{content:"\f236"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-podcast:before{content:"\f2ce"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-bell:before{content:"\f0f3"}.fa-superscript:before{content:"\f12b"}.fa-plug-circle-xmark:before{content:"\e560"}.fa-star-of-life:before{content:"\f621"}.fa-phone-slash:before{content:"\f3dd"}.fa-paint-roller:before{content:"\f5aa"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-file:before{content:"\f15b"}.fa-greater-than:before{content:"\3e"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-arrow-down:before{content:"\f063"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-eraser:before{content:"\f12d"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-person-burst:before{content:"\e53b"}.fa-dove:before{content:"\f4ba"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-socks:before{content:"\f696"}.fa-inbox:before{content:"\f01c"}.fa-section:before{content:"\e447"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-envelope-open-text:before{content:"\f658"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-wine-bottle:before{content:"\f72f"}.fa-chess-rook:before{content:"\f447"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-dharmachakra:before{content:"\f655"}.fa-hotdog:before{content:"\f80f"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-drum:before{content:"\f569"}.fa-ice-cream:before{content:"\f810"}.fa-heart-circle-bolt:before{content:"\e4fc"}.fa-fax:before{content:"\f1ac"}.fa-paragraph:before{content:"\f1dd"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-star-half:before{content:"\f089"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-tree-city:before{content:"\e587"}.fa-play:before{content:"\f04b"}.fa-font:before{content:"\f031"}.fa-rupiah-sign:before{content:"\e23d"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-naira-sign:before{content:"\e1f6"}.fa-cart-arrow-down:before{content:"\f218"}.fa-walkie-talkie:before{content:"\f8ef"}.fa-file-edit:before,.fa-file-pen:before{content:"\f31c"}.fa-receipt:before{content:"\f543"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-person-circle-exclamation:before{content:"\e53f"}.fa-chevron-down:before{content:"\f078"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-skull-crossbones:before{content:"\f714"}.fa-code-compare:before{content:"\e13a"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-school-lock:before{content:"\e56f"}.fa-tower-cell:before{content:"\e585"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-ranking-star:before{content:"\e561"}.fa-chess-king:before{content:"\f43f"}.fa-person-harassing:before{content:"\e549"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-landmark-alt:before,.fa-landmark-dome:before{content:"\f752"}.fa-arrow-up:before{content:"\f062"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-shrimp:before{content:"\e448"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-jug-detergent:before{content:"\e519"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-user-shield:before{content:"\f505"}.fa-wind:before{content:"\f72e"}.fa-car-burst:before,.fa-car-crash:before{content:"\f5e1"}.fa-y:before{content:"\59"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-fish:before{content:"\f578"}.fa-user-graduate:before{content:"\f501"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-clapperboard:before{content:"\e131"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-jet-fighter-up:before{content:"\e518"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-copy:before{content:"\f0c5"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-hand-sparkles:before{content:"\e05d"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-child-combatant:before,.fa-child-rifle:before{content:"\e4e0"}.fa-gun:before{content:"\e19b"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-expand:before{content:"\f065"}.fa-computer:before{content:"\e4e5"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-peso-sign:before{content:"\e222"}.fa-building-shield:before{content:"\e4d8"}.fa-baby:before{content:"\f77c"}.fa-users-line:before{content:"\e592"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-tractor:before{content:"\f722"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-arrow-down-up-lock:before{content:"\e4b0"}.fa-lines-leaning:before{content:"\e51e"}.fa-ruler-combined:before{content:"\f546"}.fa-copyright:before{content:"\f1f9"}.fa-equals:before{content:"\3d"}.fa-blender:before{content:"\f517"}.fa-teeth:before{content:"\f62e"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-map:before{content:"\f279"}.fa-rocket:before{content:"\f135"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-folder-minus:before{content:"\f65d"}.fa-store:before{content:"\f54e"}.fa-arrow-trend-up:before{content:"\e098"}.fa-plug-circle-minus:before{content:"\e55e"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-bezier-curve:before{content:"\f55b"}.fa-bell-slash:before{content:"\f1f6"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-school-flag:before{content:"\e56e"}.fa-fill:before{content:"\f575"}.fa-angle-up:before{content:"\f106"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-holly-berry:before{content:"\f7aa"}.fa-chevron-left:before{content:"\f053"}.fa-bacteria:before{content:"\e059"}.fa-hand-lizard:before{content:"\f258"}.fa-notdef:before{content:"\e1fe"}.fa-disease:before{content:"\f7fa"}.fa-briefcase-medical:before{content:"\f469"}.fa-genderless:before{content:"\f22d"}.fa-chevron-right:before{content:"\f054"}.fa-retweet:before{content:"\f079"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-pump-soap:before{content:"\e06b"}.fa-video-slash:before{content:"\f4e2"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-radio:before{content:"\f8d7"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-traffic-light:before{content:"\f637"}.fa-thermometer:before{content:"\f491"}.fa-vr-cardboard:before{content:"\f729"}.fa-hand-middle-finger:before{content:"\f806"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-truck-moving:before{content:"\f4df"}.fa-glass-water-droplet:before{content:"\e4f5"}.fa-display:before{content:"\e163"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-trophy:before{content:"\f091"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-hammer:before{content:"\f6e3"}.fa-hand-peace:before{content:"\f25b"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-spinner:before{content:"\f110"}.fa-robot:before{content:"\f544"}.fa-peace:before{content:"\f67c"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-warehouse:before{content:"\f494"}.fa-arrow-up-right-dots:before{content:"\e4b7"}.fa-splotch:before{content:"\f5bc"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-dice-four:before{content:"\f524"}.fa-sim-card:before{content:"\f7c4"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-mercury:before{content:"\f223"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-person-falling-burst:before{content:"\e547"}.fa-award:before{content:"\f559"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-building:before{content:"\f1ad"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-qrcode:before{content:"\f029"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-shield-blank:before,.fa-shield:before{content:"\f132"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-house-medical:before{content:"\e3b2"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-house-chimney-window:before{content:"\e00d"}.fa-pen-nib:before{content:"\f5ad"}.fa-tent-arrow-turn-left:before{content:"\e580"}.fa-tents:before{content:"\e582"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-dog:before{content:"\f6d3"}.fa-carrot:before{content:"\f787"}.fa-moon:before{content:"\f186"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-cheese:before{content:"\f7ef"}.fa-yin-yang:before{content:"\f6ad"}.fa-music:before{content:"\f001"}.fa-code-commit:before{content:"\f386"}.fa-temperature-low:before{content:"\f76b"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-broom:before{content:"\f51a"}.fa-shield-heart:before{content:"\e574"}.fa-gopuram:before{content:"\f664"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-hashtag:before{content:"\23"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-oil-can:before{content:"\f613"}.fa-t:before{content:"\54"}.fa-hippo:before{content:"\f6ed"}.fa-chart-column:before{content:"\e0e3"}.fa-infinity:before{content:"\f534"}.fa-vial-circle-check:before{content:"\e596"}.fa-person-arrow-down-to-line:before{content:"\e538"}.fa-voicemail:before{content:"\f897"}.fa-fan:before{content:"\f863"}.fa-person-walking-luggage:before{content:"\e554"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-calendar:before{content:"\f133"}.fa-trailer:before{content:"\e041"}.fa-bahai:before,.fa-haykal:before{content:"\f666"}.fa-sd-card:before{content:"\f7c2"}.fa-dragon:before{content:"\f6d5"}.fa-shoe-prints:before{content:"\f54b"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-hand-holding:before{content:"\f4bd"}.fa-plug-circle-exclamation:before{content:"\e55d"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-clone:before{content:"\f24d"}.fa-person-walking-arrow-loop-left:before{content:"\e551"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-tornado:before{content:"\f76f"}.fa-file-circle-plus:before{content:"\e494"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-anchor:before{content:"\f13d"}.fa-border-all:before{content:"\f84c"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-cookie-bite:before{content:"\f564"}.fa-arrow-trend-down:before{content:"\e097"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-draw-polygon:before{content:"\f5ee"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-shower:before{content:"\f2cc"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-m:before{content:"\4d"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-book:before{content:"\f02d"}.fa-user-plus:before{content:"\f234"}.fa-check:before{content:"\f00c"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-house-circle-check:before{content:"\e509"}.fa-angle-left:before{content:"\f104"}.fa-diagram-successor:before{content:"\e47a"}.fa-truck-arrow-right:before{content:"\e58b"}.fa-arrows-split-up-and-left:before{content:"\e4bc"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-cloud-moon:before{content:"\f6c3"}.fa-briefcase:before{content:"\f0b1"}.fa-person-falling:before{content:"\e546"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-user-tag:before{content:"\f507"}.fa-rug:before{content:"\e569"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-baht-sign:before{content:"\e0ac"}.fa-book-open:before{content:"\f518"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-handcuffs:before{content:"\e4f8"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-database:before{content:"\f1c0"}.fa-arrow-turn-right:before,.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-bottle-droplet:before{content:"\e4c4"}.fa-mask-face:before{content:"\e1d7"}.fa-hill-rockslide:before{content:"\e508"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-paper-plane:before{content:"\f1d8"}.fa-road-circle-exclamation:before{content:"\e565"}.fa-dungeon:before{content:"\f6d9"}.fa-align-right:before{content:"\f038"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-life-ring:before{content:"\f1cd"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-calendar-day:before{content:"\f783"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-wheelchair-alt:before,.fa-wheelchair-move:before{content:"\e2ce"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-person-walking-arrow-right:before{content:"\e552"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-dice:before{content:"\f522"}.fa-bowling-ball:before{content:"\f436"}.fa-brain:before{content:"\f5dc"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-calendar-minus:before{content:"\f272"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-gifts:before{content:"\f79c"}.fa-hotel:before{content:"\f594"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-thumbs-up:before{content:"\f164"}.fa-user-clock:before{content:"\f4fd"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-file-invoice:before{content:"\f570"}.fa-window-minimize:before{content:"\f2d1"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-brush:before{content:"\f55d"}.fa-mask:before{content:"\f6fa"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-ruler-vertical:before{content:"\f548"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-train-tram:before{content:"\e5b4"}.fa-user-nurse:before{content:"\f82f"}.fa-syringe:before{content:"\f48e"}.fa-cloud-sun:before{content:"\f6c4"}.fa-stopwatch-20:before{content:"\e06f"}.fa-square-full:before{content:"\f45c"}.fa-magnet:before{content:"\f076"}.fa-jar:before{content:"\e516"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-bug-slash:before{content:"\e490"}.fa-arrow-up-from-water-pump:before{content:"\e4b6"}.fa-bone:before{content:"\f5d7"}.fa-user-injured:before{content:"\f728"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-plane:before{content:"\f072"}.fa-tent-arrows-down:before{content:"\e581"}.fa-exclamation:before{content:"\21"}.fa-arrows-spin:before{content:"\e4bb"}.fa-print:before{content:"\f02f"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-x:before{content:"\58"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-person-military-pointing:before{content:"\e54a"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-umbrella:before{content:"\f0e9"}.fa-trowel:before{content:"\e589"}.fa-d:before{content:"\44"}.fa-stapler:before{content:"\e5af"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-kip-sign:before{content:"\e1c4"}.fa-hand-point-left:before{content:"\f0a5"}.fa-handshake-alt:before,.fa-handshake-simple:before{content:"\f4c6"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-barcode:before{content:"\f02a"}.fa-plus-minus:before{content:"\e43c"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-person-circle-check:before{content:"\e53e"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"} +.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../fonts/fa-brands-400.woff2) format("woff2"),url(../fonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-weight:400}.fa-monero:before{content:"\f3d0"}.fa-hooli:before{content:"\f427"}.fa-yelp:before{content:"\f1e9"}.fa-cc-visa:before{content:"\f1f0"}.fa-lastfm:before{content:"\f202"}.fa-shopware:before{content:"\f5b5"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-aws:before{content:"\f375"}.fa-redhat:before{content:"\f7bc"}.fa-yoast:before{content:"\f2b1"}.fa-cloudflare:before{content:"\e07d"}.fa-ups:before{content:"\f7e0"}.fa-wpexplorer:before{content:"\f2de"}.fa-dyalog:before{content:"\f399"}.fa-bity:before{content:"\f37a"}.fa-stackpath:before{content:"\f842"}.fa-buysellads:before{content:"\f20d"}.fa-first-order:before{content:"\f2b0"}.fa-modx:before{content:"\f285"}.fa-guilded:before{content:"\e07e"}.fa-vnv:before{content:"\f40b"}.fa-js-square:before,.fa-square-js:before{content:"\f3b9"}.fa-microsoft:before{content:"\f3ca"}.fa-qq:before{content:"\f1d6"}.fa-orcid:before{content:"\f8d2"}.fa-java:before{content:"\f4e4"}.fa-invision:before{content:"\f7b0"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-centercode:before{content:"\f380"}.fa-glide-g:before{content:"\f2a6"}.fa-drupal:before{content:"\f1a9"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-unity:before{content:"\e049"}.fa-whmcs:before{content:"\f40d"}.fa-rocketchat:before{content:"\f3e8"}.fa-vk:before{content:"\f189"}.fa-untappd:before{content:"\f405"}.fa-mailchimp:before{content:"\f59e"}.fa-css3-alt:before{content:"\f38b"}.fa-reddit-square:before,.fa-square-reddit:before{content:"\f1a2"}.fa-vimeo-v:before{content:"\f27d"}.fa-contao:before{content:"\f26d"}.fa-square-font-awesome:before{content:"\e5ad"}.fa-deskpro:before{content:"\f38f"}.fa-sistrix:before{content:"\f3ee"}.fa-instagram-square:before,.fa-square-instagram:before{content:"\e055"}.fa-battle-net:before{content:"\f835"}.fa-the-red-yeti:before{content:"\f69d"}.fa-hacker-news-square:before,.fa-square-hacker-news:before{content:"\f3af"}.fa-edge:before{content:"\f282"}.fa-napster:before{content:"\f3d2"}.fa-snapchat-square:before,.fa-square-snapchat:before{content:"\f2ad"}.fa-google-plus-g:before{content:"\f0d5"}.fa-artstation:before{content:"\f77a"}.fa-markdown:before{content:"\f60f"}.fa-sourcetree:before{content:"\f7d3"}.fa-google-plus:before{content:"\f2b3"}.fa-diaspora:before{content:"\f791"}.fa-foursquare:before{content:"\f180"}.fa-stack-overflow:before{content:"\f16c"}.fa-github-alt:before{content:"\f113"}.fa-phoenix-squadron:before{content:"\f511"}.fa-pagelines:before{content:"\f18c"}.fa-algolia:before{content:"\f36c"}.fa-red-river:before{content:"\f3e3"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-safari:before{content:"\f267"}.fa-google:before{content:"\f1a0"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-atlassian:before{content:"\f77b"}.fa-linkedin-in:before{content:"\f0e1"}.fa-digital-ocean:before{content:"\f391"}.fa-nimblr:before{content:"\f5a8"}.fa-chromecast:before{content:"\f838"}.fa-evernote:before{content:"\f839"}.fa-hacker-news:before{content:"\f1d4"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-adversal:before{content:"\f36a"}.fa-creative-commons:before{content:"\f25e"}.fa-watchman-monitoring:before{content:"\e087"}.fa-fonticons:before{content:"\f280"}.fa-weixin:before{content:"\f1d7"}.fa-shirtsinbulk:before{content:"\f214"}.fa-codepen:before{content:"\f1cb"}.fa-git-alt:before{content:"\f841"}.fa-lyft:before{content:"\f3c3"}.fa-rev:before{content:"\f5b2"}.fa-windows:before{content:"\f17a"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-square-viadeo:before,.fa-viadeo-square:before{content:"\f2aa"}.fa-meetup:before{content:"\f2e0"}.fa-centos:before{content:"\f789"}.fa-adn:before{content:"\f170"}.fa-cloudsmith:before{content:"\f384"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-dribbble-square:before,.fa-square-dribbble:before{content:"\f397"}.fa-codiepie:before{content:"\f284"}.fa-node:before{content:"\f419"}.fa-mix:before{content:"\f3cb"}.fa-steam:before{content:"\f1b6"}.fa-cc-apple-pay:before{content:"\f416"}.fa-scribd:before{content:"\f28a"}.fa-openid:before{content:"\f19b"}.fa-instalod:before{content:"\e081"}.fa-expeditedssl:before{content:"\f23e"}.fa-sellcast:before{content:"\f2da"}.fa-square-twitter:before,.fa-twitter-square:before{content:"\f081"}.fa-r-project:before{content:"\f4f7"}.fa-delicious:before{content:"\f1a5"}.fa-freebsd:before{content:"\f3a4"}.fa-vuejs:before{content:"\f41f"}.fa-accusoft:before{content:"\f369"}.fa-ioxhost:before{content:"\f208"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-app-store:before{content:"\f36f"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-itunes-note:before{content:"\f3b5"}.fa-golang:before{content:"\e40f"}.fa-kickstarter:before{content:"\f3bb"}.fa-grav:before{content:"\f2d6"}.fa-weibo:before{content:"\f18a"}.fa-uncharted:before{content:"\e084"}.fa-firstdraft:before{content:"\f3a1"}.fa-square-youtube:before,.fa-youtube-square:before{content:"\f431"}.fa-wikipedia-w:before{content:"\f266"}.fa-rendact:before,.fa-wpressr:before{content:"\f3e4"}.fa-angellist:before{content:"\f209"}.fa-galactic-republic:before{content:"\f50c"}.fa-nfc-directional:before{content:"\e530"}.fa-skype:before{content:"\f17e"}.fa-joget:before{content:"\f3b7"}.fa-fedora:before{content:"\f798"}.fa-stripe-s:before{content:"\f42a"}.fa-meta:before{content:"\e49b"}.fa-laravel:before{content:"\f3bd"}.fa-hotjar:before{content:"\f3b1"}.fa-bluetooth-b:before{content:"\f294"}.fa-sticker-mule:before{content:"\f3f7"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-hips:before{content:"\f452"}.fa-behance:before{content:"\f1b4"}.fa-reddit:before{content:"\f1a1"}.fa-discord:before{content:"\f392"}.fa-chrome:before{content:"\f268"}.fa-app-store-ios:before{content:"\f370"}.fa-cc-discover:before{content:"\f1f2"}.fa-wpbeginner:before{content:"\f297"}.fa-confluence:before{content:"\f78d"}.fa-mdb:before{content:"\f8ca"}.fa-dochub:before{content:"\f394"}.fa-accessible-icon:before{content:"\f368"}.fa-ebay:before{content:"\f4f4"}.fa-amazon:before{content:"\f270"}.fa-unsplash:before{content:"\e07c"}.fa-yarn:before{content:"\f7e3"}.fa-square-steam:before,.fa-steam-square:before{content:"\f1b7"}.fa-500px:before{content:"\f26e"}.fa-square-vimeo:before,.fa-vimeo-square:before{content:"\f194"}.fa-asymmetrik:before{content:"\f372"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-gratipay:before{content:"\f184"}.fa-apple:before{content:"\f179"}.fa-hive:before{content:"\e07f"}.fa-gitkraken:before{content:"\f3a6"}.fa-keybase:before{content:"\f4f5"}.fa-apple-pay:before{content:"\f415"}.fa-padlet:before{content:"\e4a0"}.fa-amazon-pay:before{content:"\f42c"}.fa-github-square:before,.fa-square-github:before{content:"\f092"}.fa-stumbleupon:before{content:"\f1a4"}.fa-fedex:before{content:"\f797"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-shopify:before{content:"\e057"}.fa-neos:before{content:"\f612"}.fa-hackerrank:before{content:"\f5f7"}.fa-researchgate:before{content:"\f4f8"}.fa-swift:before{content:"\f8e1"}.fa-angular:before{content:"\f420"}.fa-speakap:before{content:"\f3f3"}.fa-angrycreative:before{content:"\f36e"}.fa-y-combinator:before{content:"\f23b"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-gitlab-square:before,.fa-square-gitlab:before{content:"\e5ae"}.fa-studiovinari:before{content:"\f3f8"}.fa-pied-piper:before{content:"\f2ae"}.fa-wordpress:before{content:"\f19a"}.fa-product-hunt:before{content:"\f288"}.fa-firefox:before{content:"\f269"}.fa-linode:before{content:"\f2b8"}.fa-goodreads:before{content:"\f3a8"}.fa-odnoklassniki-square:before,.fa-square-odnoklassniki:before{content:"\f264"}.fa-jsfiddle:before{content:"\f1cc"}.fa-sith:before{content:"\f512"}.fa-themeisle:before{content:"\f2b2"}.fa-page4:before{content:"\f3d7"}.fa-hashnode:before{content:"\e499"}.fa-react:before{content:"\f41b"}.fa-cc-paypal:before{content:"\f1f4"}.fa-squarespace:before{content:"\f5be"}.fa-cc-stripe:before{content:"\f1f5"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-bitcoin:before{content:"\f379"}.fa-keycdn:before{content:"\f3ba"}.fa-opera:before{content:"\f26a"}.fa-itch-io:before{content:"\f83a"}.fa-umbraco:before{content:"\f8e8"}.fa-galactic-senate:before{content:"\f50d"}.fa-ubuntu:before{content:"\f7df"}.fa-draft2digital:before{content:"\f396"}.fa-stripe:before{content:"\f429"}.fa-houzz:before{content:"\f27c"}.fa-gg:before{content:"\f260"}.fa-dhl:before{content:"\f790"}.fa-pinterest-square:before,.fa-square-pinterest:before{content:"\f0d3"}.fa-xing:before{content:"\f168"}.fa-blackberry:before{content:"\f37b"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-playstation:before{content:"\f3df"}.fa-quinscape:before{content:"\f459"}.fa-less:before{content:"\f41d"}.fa-blogger-b:before{content:"\f37d"}.fa-opencart:before{content:"\f23d"}.fa-vine:before{content:"\f1ca"}.fa-paypal:before{content:"\f1ed"}.fa-gitlab:before{content:"\f296"}.fa-typo3:before{content:"\f42b"}.fa-reddit-alien:before{content:"\f281"}.fa-yahoo:before{content:"\f19e"}.fa-dailymotion:before{content:"\e052"}.fa-affiliatetheme:before{content:"\f36b"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-bootstrap:before{content:"\f836"}.fa-odnoklassniki:before{content:"\f263"}.fa-nfc-symbol:before{content:"\e531"}.fa-ethereum:before{content:"\f42e"}.fa-speaker-deck:before{content:"\f83c"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-patreon:before{content:"\f3d9"}.fa-avianex:before{content:"\f374"}.fa-ello:before{content:"\f5f1"}.fa-gofore:before{content:"\f3a7"}.fa-bimobject:before{content:"\f378"}.fa-facebook-f:before{content:"\f39e"}.fa-google-plus-square:before,.fa-square-google-plus:before{content:"\f0d4"}.fa-mandalorian:before{content:"\f50f"}.fa-first-order-alt:before{content:"\f50a"}.fa-osi:before{content:"\f41a"}.fa-google-wallet:before{content:"\f1ee"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-periscope:before{content:"\f3da"}.fa-fulcrum:before{content:"\f50b"}.fa-cloudscale:before{content:"\f383"}.fa-forumbee:before{content:"\f211"}.fa-mizuni:before{content:"\f3cc"}.fa-schlix:before{content:"\f3ea"}.fa-square-xing:before,.fa-xing-square:before{content:"\f169"}.fa-bandcamp:before{content:"\f2d5"}.fa-wpforms:before{content:"\f298"}.fa-cloudversify:before{content:"\f385"}.fa-usps:before{content:"\f7e1"}.fa-megaport:before{content:"\f5a3"}.fa-magento:before{content:"\f3c4"}.fa-spotify:before{content:"\f1bc"}.fa-optin-monster:before{content:"\f23c"}.fa-fly:before{content:"\f417"}.fa-aviato:before{content:"\f421"}.fa-itunes:before{content:"\f3b4"}.fa-cuttlefish:before{content:"\f38c"}.fa-blogger:before{content:"\f37c"}.fa-flickr:before{content:"\f16e"}.fa-viber:before{content:"\f409"}.fa-soundcloud:before{content:"\f1be"}.fa-digg:before{content:"\f1a6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-symfony:before{content:"\f83d"}.fa-maxcdn:before{content:"\f136"}.fa-etsy:before{content:"\f2d7"}.fa-facebook-messenger:before{content:"\f39f"}.fa-audible:before{content:"\f373"}.fa-think-peaks:before{content:"\f731"}.fa-bilibili:before{content:"\e3d9"}.fa-erlang:before{content:"\f39d"}.fa-cotton-bureau:before{content:"\f89e"}.fa-dashcube:before{content:"\f210"}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-stack-exchange:before{content:"\f18d"}.fa-elementor:before{content:"\f430"}.fa-pied-piper-square:before,.fa-square-pied-piper:before{content:"\e01e"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-palfed:before{content:"\f3d8"}.fa-superpowers:before{content:"\f2dd"}.fa-resolving:before{content:"\f3e7"}.fa-xbox:before{content:"\f412"}.fa-searchengin:before{content:"\f3eb"}.fa-tiktok:before{content:"\e07b"}.fa-facebook-square:before,.fa-square-facebook:before{content:"\f082"}.fa-renren:before{content:"\f18b"}.fa-linux:before{content:"\f17c"}.fa-glide:before{content:"\f2a5"}.fa-linkedin:before{content:"\f08c"}.fa-hubspot:before{content:"\f3b2"}.fa-deploydog:before{content:"\f38e"}.fa-twitch:before{content:"\f1e8"}.fa-ravelry:before{content:"\f2d9"}.fa-mixer:before{content:"\e056"}.fa-lastfm-square:before,.fa-square-lastfm:before{content:"\f203"}.fa-vimeo:before{content:"\f40a"}.fa-mendeley:before{content:"\f7b3"}.fa-uniregistry:before{content:"\f404"}.fa-figma:before{content:"\f799"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-dropbox:before{content:"\f16b"}.fa-instagram:before{content:"\f16d"}.fa-cmplid:before{content:"\e360"}.fa-facebook:before{content:"\f09a"}.fa-gripfire:before{content:"\f3ac"}.fa-jedi-order:before{content:"\f50e"}.fa-uikit:before{content:"\f403"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-phabricator:before{content:"\f3db"}.fa-ussunnah:before{content:"\f407"}.fa-earlybirds:before{content:"\f39a"}.fa-trade-federation:before{content:"\f513"}.fa-autoprefixer:before{content:"\f41c"}.fa-whatsapp:before{content:"\f232"}.fa-slideshare:before{content:"\f1e7"}.fa-google-play:before{content:"\f3ab"}.fa-viadeo:before{content:"\f2a9"}.fa-line:before{content:"\f3c0"}.fa-google-drive:before{content:"\f3aa"}.fa-servicestack:before{content:"\f3ec"}.fa-simplybuilt:before{content:"\f215"}.fa-bitbucket:before{content:"\f171"}.fa-imdb:before{content:"\f2d8"}.fa-deezer:before{content:"\e077"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-jira:before{content:"\f7b1"}.fa-docker:before{content:"\f395"}.fa-screenpal:before{content:"\e570"}.fa-bluetooth:before{content:"\f293"}.fa-gitter:before{content:"\f426"}.fa-d-and-d:before{content:"\f38d"}.fa-microblog:before{content:"\e01a"}.fa-cc-diners-club:before{content:"\f24c"}.fa-gg-circle:before{content:"\f261"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-yandex:before{content:"\f413"}.fa-readme:before{content:"\f4d5"}.fa-html5:before{content:"\f13b"}.fa-sellsy:before{content:"\f213"}.fa-sass:before{content:"\f41e"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-buromobelexperte:before{content:"\f37f"}.fa-salesforce:before{content:"\f83b"}.fa-octopus-deploy:before{content:"\e082"}.fa-medapps:before{content:"\f3c6"}.fa-ns8:before{content:"\f3d5"}.fa-pinterest-p:before{content:"\f231"}.fa-apper:before{content:"\f371"}.fa-fort-awesome:before{content:"\f286"}.fa-waze:before{content:"\f83f"}.fa-cc-jcb:before{content:"\f24b"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-rust:before{content:"\e07a"}.fa-wix:before{content:"\f5cf"}.fa-behance-square:before,.fa-square-behance:before{content:"\f1b5"}.fa-supple:before{content:"\f3f9"}.fa-rebel:before{content:"\f1d0"}.fa-css3:before{content:"\f13c"}.fa-staylinked:before{content:"\f3f5"}.fa-kaggle:before{content:"\f5fa"}.fa-space-awesome:before{content:"\e5ac"}.fa-deviantart:before{content:"\f1bd"}.fa-cpanel:before{content:"\f388"}.fa-goodreads-g:before{content:"\f3a9"}.fa-git-square:before,.fa-square-git:before{content:"\f1d2"}.fa-square-tumblr:before,.fa-tumblr-square:before{content:"\f174"}.fa-trello:before{content:"\f181"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-get-pocket:before{content:"\f265"}.fa-perbyte:before{content:"\e083"}.fa-grunt:before{content:"\f3ad"}.fa-weebly:before{content:"\f5cc"}.fa-connectdevelop:before{content:"\f20e"}.fa-leanpub:before{content:"\f212"}.fa-black-tie:before{content:"\f27e"}.fa-themeco:before{content:"\f5c6"}.fa-python:before{content:"\f3e2"}.fa-android:before{content:"\f17b"}.fa-bots:before{content:"\e340"}.fa-free-code-camp:before{content:"\f2c5"}.fa-hornbill:before{content:"\f592"}.fa-js:before{content:"\f3b8"}.fa-ideal:before{content:"\e013"}.fa-git:before{content:"\f1d3"}.fa-dev:before{content:"\f6cc"}.fa-sketch:before{content:"\f7c6"}.fa-yandex-international:before{content:"\f414"}.fa-cc-amex:before{content:"\f1f3"}.fa-uber:before{content:"\f402"}.fa-github:before{content:"\f09b"}.fa-php:before{content:"\f457"}.fa-alipay:before{content:"\f642"}.fa-youtube:before{content:"\f167"}.fa-skyatlas:before{content:"\f216"}.fa-firefox-browser:before{content:"\e007"}.fa-replyd:before{content:"\f3e6"}.fa-suse:before{content:"\f7d6"}.fa-jenkins:before{content:"\f3b6"}.fa-twitter:before{content:"\f099"}.fa-rockrms:before{content:"\f3e9"}.fa-pinterest:before{content:"\f0d2"}.fa-buffer:before{content:"\f837"}.fa-npm:before{content:"\f3d4"}.fa-yammer:before{content:"\f840"}.fa-btc:before{content:"\f15a"}.fa-dribbble:before{content:"\f17d"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-internet-explorer:before{content:"\f26b"}.fa-stubber:before{content:"\e5c7"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-old-republic:before{content:"\f510"}.fa-odysee:before{content:"\e5c6"}.fa-square-whatsapp:before,.fa-whatsapp-square:before{content:"\f40c"}.fa-node-js:before{content:"\f3d3"}.fa-edge-legacy:before{content:"\e078"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-medrt:before{content:"\f3c8"}.fa-usb:before{content:"\f287"}.fa-tumblr:before{content:"\f173"}.fa-vaadin:before{content:"\f408"}.fa-quora:before{content:"\f2c4"}.fa-reacteurope:before{content:"\f75d"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-amilia:before{content:"\f36d"}.fa-mixcloud:before{content:"\f289"}.fa-flipboard:before{content:"\f44d"}.fa-viacoin:before{content:"\f237"}.fa-critical-role:before{content:"\f6c9"}.fa-sitrox:before{content:"\e44a"}.fa-discourse:before{content:"\f393"}.fa-joomla:before{content:"\f1aa"}.fa-mastodon:before{content:"\f4f6"}.fa-airbnb:before{content:"\f834"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-buy-n-large:before{content:"\f8a6"}.fa-gulp:before{content:"\f3ae"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-strava:before{content:"\f428"}.fa-ember:before{content:"\f423"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-teamspeak:before{content:"\f4f9"}.fa-pushed:before{content:"\f3e1"}.fa-wordpress-simple:before{content:"\f411"}.fa-nutritionix:before{content:"\f3d6"}.fa-wodu:before{content:"\e088"}.fa-google-pay:before{content:"\e079"}.fa-intercom:before{content:"\f7af"}.fa-zhihu:before{content:"\f63f"}.fa-korvue:before{content:"\f42f"}.fa-pix:before{content:"\e43a"}.fa-steam-symbol:before{content:"\f3f6"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../fonts/fa-regular-400.woff2) format("woff2"),url(../fonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../fonts/fa-solid-900.woff2) format("woff2"),url(../fonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../fonts/fa-brands-400.woff2) format("woff2"),url(../fonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../fonts/fa-solid-900.woff2) format("woff2"),url(../fonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../fonts/fa-regular-400.woff2) format("woff2"),url(../fonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../fonts/fa-solid-900.woff2) format("woff2"),url(../fonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../fonts/fa-brands-400.woff2) format("woff2"),url(../fonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../fonts/fa-regular-400.woff2) format("woff2"),url(../fonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../fonts/fa-v4compatibility.woff2) format("woff2"),url(../fonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} \ No newline at end of file diff --git a/public/static/css/index.css b/public/static/css/index.css new file mode 100644 index 0000000..f1c0fe5 --- /dev/null +++ b/public/static/css/index.css @@ -0,0 +1,849 @@ +html #layuicss-layuiAdmin { + display: none; + position: absolute; + width: 1989px; +} + +/* 系统 */ +::-webkit-input-placeholder { + color: #ccc +} + +/* 全局 */ +html { + /* background-color: #f2f2f2; */ + color: #666; +} + +*[template], +.layadmin-tabsbody-item { + display: none; +} + +*[lay-href], +*[lay-tips], +*[layadmin-event] { + cursor: pointer; +} + +/* 重置布局结构 */ +.layui-layout-admin .layui-header { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 70px; +} + +.layui-layout-admin .layui-header .layui-nav .layui-nav-child a { + color: #333; +} + +.layui-layout-admin .layui-side { + width: 220px; + top: 0; + z-index: 1001; +} + +.layui-layout-admin .layui-logo, +.layui-layout-admin .layui-header .layui-nav .layui-nav-item { + height: 70px; + line-height: 70px; +} + +.layui-layout-admin .layui-logo { + position: fixed; + left: 0; + top: 0; + z-index: 1002; + width: 220px; + height: 49px; + padding: 0 15px; + box-sizing: border-box; + overflow: hidden; + font-weight: 300; + background-repeat: no-repeat; + background-position: center center; +} + +.layui-layout-admin .layui-layout-left, +.layadmin-pagetabs, +.layui-layout-admin .layui-body, +.layui-layout-admin .layui-footer { + left: 220px; +} + +.layadmin-pagetabs { + position: fixed; + top: 50px; + right: 0; + z-index: 999; +} + +.layadmin-pagetabs .layui-breadcrumb { + padding: 0 15px; +} + +.layui-layout-admin .layui-body { + position: fixed; + top: 90px; + bottom: 0; +} + +.layui-layout-admin .layui-body .layadmin-tabsbody-item { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow: hidden; + overflow-y: auto; +} + +.layui-layout-admin .layui-header .layui-nav-img { + width: 26px; + height: 26px; +} + +.layui-layout-admin .layui-header .layui-nav-child { + top: 55px; +} + +.layui-layout-admin .layui-header .layui-layout-right .layui-nav-child { + left: auto; + right: 0; +} + +.layui-layout-admin .layui-header .layui-nav .layui-nav-child dd.layui-this a, +.layui-layout-admin .layui-header .layui-nav .layui-nav-child dd.layui-this { + background: none +} + +/* 统一动画 */ +.layui-layout-admin .layui-header .layui-nav .layui-nav-item, +.layui-layout-admin .layui-layout-left, +.layadmin-pagetabs, +.layui-layout-admin .layui-body, +.layui-layout-admin .layui-footer, +.layui-layout-admin .layui-side, +.layui-layout-admin .layui-logo, +.layui-layout-admin .layui-header .layui-layout-right { + transition: all .3s; + -webkit-transition: all .3s; +} + +/* 图标 */ +.layui-icon-login-qq { + color: #3492ED; +} + +.layui-icon-login-wechat { + color: #4DAF29; +} + +.layui-icon-login-weibo { + color: #CF1900; +} + +/* 表单 */ +.layui-form[wid100] .layui-form-label { + width: 100px; +} + +.layui-form[wid100] .layui-input-block { + margin-left: 130px; +} + +@media screen and (max-width: 450px) { + .layui-form[wid100] .layui-form-item .layui-input-inline { + margin-left: 132px; + } + + .layui-form[wid100] .layui-form-item .layui-input-inline+.layui-form-mid { + margin-left: 130px; + } +} + +.layui-form-item .layui-input-company { + width: auto; + padding-right: 10px; + line-height: 38px; +} + +/* 辅助 */ +.layadmin-flexible {} + +.layui-bg-white { + background-color: #fff; +} + +.layadmin-loading { + position: absolute; + left: 50%; + top: 50%; + margin: -16px -15px; + font-size: 30px; + color: #c2c2c2; +} + +.layadmin-fixed { + position: fixed; + left: 0; + top: 0; + z-index: 999; +} + +.layadmin-link { + color: #029789 !important; +} + +.layadmin-link:hover { + opacity: 0.8; +} + +/* 头部导航 */ +.layui-layout-admin .layui-layout-left { + padding: 0 10px; +} + +.layui-layout-admin .layui-layout-left .layui-nav-item { + margin: 0 20px; +} + +.layui-layout-admin .layui-input-search { + display: inline-block; + vertical-align: middle; + height: 32px; + border: none; + cursor: text; +} + +.layui-layout-admin .layui-layout-left a { + padding: 0; +} + +.layui-layout-admin .layui-layout-right { + padding: 0; +} + +.layui-header .layui-nav-item .layui-icon { + position: relative; + top: 1px; + font-size: 16px; +} + +.layui-header .layui-nav-item:hover {} + +.layui-header .layui-layout-right .layui-badge-dot { + margin-left: 0px; +} + +.layui-header .layui-nav .layui-this:after, +.layui-layout-admin .layui-header .layui-nav-bar { + top: 0 !important; + bottom: auto; + height: 3px; + background-color: #fff; + background-color: rgba(255, 255, 255, .3); +} + +/* 遮罩 */ +.layadmin-body-shade { + position: fixed; + display: none; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: rgba(0, 0, 0, .3); + z-index: 1000; +} + +/* 侧边菜单 */ +.layui-side-menu .layui-side-scroll { + width: 240px; +} + +.layui-side-menu .layui-nav { + width: 220px; + margin-top: 70px; + background: none; +} + +.layui-side-menu .layui-nav .layui-nav-item a { + height: 40px; + line-height: 40px; + padding-left: 45px; + padding-right: 30px; +} + +.layui-side-menu .layui-nav .layui-nav-item>a { + padding-top: 8px; + padding-bottom: 8px; +} + +.layui-side-menu .layui-nav .layui-nav-item a:hover { + background: none; +} + +.layui-side-menu .layui-nav .layui-nav-itemed>.layui-nav-child { + padding: 5px 0; +} + +.layui-side-menu .layui-nav .layui-nav-item .layui-icon { + position: absolute; + top: 50%; + left: 20px; + margin-top: -25px; +} + +.layui-side-menu .layui-nav .layui-nav-item .layui-icons { + margin-top: -20px; +} + +.layui-side-menu .layui-nav .layui-nav-child .layui-nav-child { + background: none !important; +} + +.layui-side-menu .layui-nav .layui-nav-child .layui-nav-child a { + padding-left: 60px +} + +.layui-side-menu .layui-nav .layui-nav-more { + right: 15px; +} + +/* 侧边菜单 - 平板移动设备 */ +@media screen and (max-width: 992px) { + .layui-layout-admin .layui-side { + transform: translate3d(-220px, 0, 0); + -webkit-transform: translate3d(-220px, 0, 0); + width: 220px; + } + + .layui-layout-admin .layui-layout-left, + .layadmin-pagetabs, + .layui-layout-admin .layui-body, + .layui-layout-admin .layui-footer { + left: 0; + } +} + +/* 侧边收缩模式 */ +.layadmin-side-shrink .layui-layout-admin .layui-logo { + width: 60px; + background-image: url(res/logo.png); + /*background-size: 20px;)*/ +} + +.layadmin-side-shrink .layui-layout-admin .layui-logo span { + display: none; +} + +.layadmin-side-shrink .layui-side { + left: 0; + width: 60px; +} + +.layadmin-side-shrink .layui-layout-admin .layui-layout-left, +.layadmin-side-shrink .layadmin-pagetabs, +.layadmin-side-shrink .layui-layout-admin .layui-body, +.layadmin-side-shrink .layui-layout-admin .layui-footer { + left: 60px; +} + +.layadmin-side-shrink .layui-side-menu .layui-nav { + position: static; + width: 60px; +} + +.layadmin-side-shrink .layui-side-menu .layui-nav-item { + position: static; +} + +.layadmin-side-shrink .layui-side-menu .layui-nav-item>a { + padding-right: 0; +} + +.layadmin-side-shrink .layui-side-menu .layui-nav-item cite, +.layadmin-side-shrink .layui-side-menu .layui-nav>.layui-nav-item>a .layui-nav-more, +.layadmin-side-shrink .layui-side-menu .layui-nav>.layui-nav-item>.layui-nav-child { + display: none; + padding: 8px 0; + width: 200px; +} + +.layadmin-side-shrink .layui-side-menu .layui-nav>.layui-nav-itemed>a { + background: rgba(0, 0, 0, .3); +} + +/* 移动端展开模式 */ +.layadmin-side-spread-sm .layui-layout-admin .layui-layout-left, +.layadmin-side-spread-sm .layadmin-pagetabs, +.layadmin-side-spread-sm .layui-layout-admin .layui-body, +.layadmin-side-spread-sm .layui-layout-admin .layui-footer { + left: 0; + transform: translate3d(220px, 0, 0); + -webkit-transform: translate3d(220px, 0, 0); +} + +.layadmin-side-spread-sm .layui-layout-admin .layui-layout-right { + transform: translate3d(220px, 0, 0); + -webkit-transform: translate3d(220px, 0, 0); +} + +.layadmin-side-spread-sm .layui-side { + transform: translate3d(0, 0, 0); + -webkit-transform: translate3d(0, 0, 0); +} + +.layadmin-side-spread-sm .layadmin-body-shade { + display: block; +} + +/* 页面标签 */ +.layadmin-pagetabs { + height: 40px; + line-height: 40px; + padding: 0 80px 0 40px; + /*border-bottom: 2px solid #292B34;*/ + background-color: #fff; + box-sizing: border-box; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .1); +} + +.layadmin-pagetabs .layadmin-tabs-control { + position: absolute; + top: 0; + width: 40px; + height: 100%; + text-align: center; + cursor: pointer; + transition: all .3s; + -webkit-transition: all .3s; + box-sizing: border-box; + border-left: 1px solid #f6f6f6; +} + +.layadmin-pagetabs .layadmin-tabs-control:hover { + background-color: #f6f6f6; +} + +.layadmin-pagetabs .layui-icon-prev { + left: 0; + border-left: none; + border-right: 1px solid #f6f6f6; +} + +.layadmin-pagetabs .layui-icon-next { + right: 40px; + right: 40px; +} + +.layadmin-pagetabs .layui-icon-down { + right: 0; +} + +.layadmin-tabs-select.layui-nav { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + padding: 0; + background: none; +} + +.layadmin-tabs-select.layui-nav .layui-nav-item { + line-height: 40px; +} + +.layadmin-tabs-select.layui-nav .layui-nav-item>a { + height: 40px; +} + +.layadmin-tabs-select.layui-nav .layui-nav-item a { + color: #666; +} + +.layadmin-tabs-select.layui-nav .layui-nav-child { + top: 40px; + left: auto; + right: 0; +} + +.layadmin-tabs-select.layui-nav .layui-nav-child dd.layui-this, +.layadmin-tabs-select.layui-nav .layui-nav-child dd.layui-this a { + background-color: #f2f2f2 !important; + color: #333; +} + +.layadmin-tabs-select.layui-nav .layui-nav-more, +.layadmin-tabs-select.layui-nav .layui-nav-bar { + display: none; +} + +.layadmin-pagetabs .layui-tab { + margin: 0; + overflow: hidden; +} + +.layadmin-pagetabs .layui-tab-title { + height: 40px; + border: none; +} + +.layadmin-pagetabs .layui-tab-title li { + min-width: 0; + line-height: 40px; + max-width: 160px; + text-overflow: ellipsis; + padding-right: 40px; + overflow: hidden; + border-right: 1px solid #f6f6f6; + vertical-align: top; +} + +.layadmin-pagetabs .layui-tab-title li:first-child { + padding-right: 15px; +} + +.layadmin-pagetabs .layui-tab-title li:first-child .layui-tab-close { + display: none; +} + +.layadmin-pagetabs .layui-tab-title li .layui-tab-close { + position: absolute; + right: 8px; + top: 50%; + margin: -7px 0 0 0; + width: 16px; + height: 16px; + line-height: 16px; + border-radius: 50%; + font-size: 12px; +} + +.layadmin-pagetabs .layui-tab-title li:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 2px; + border-radius: 0; + background-color: #292B34; + transition: all .3s; + -webkit-transition: all .3s; +} + +.layadmin-pagetabs .layui-tab-title li:hover:after { + width: 100%; +} + +.layadmin-pagetabs .layui-tab-title li:hover, +.layadmin-pagetabs .layui-tab-title li.layui-this { + background-color: #f6f6f6; +} + +.layadmin-pagetabs .layui-tab-title li.layui-this:after { + width: 100%; + border: none; + height: 2px; + background-color: #292B34; +} + +/* 不开启页面标签时 */ +.layadmin-tabspage-none .layui-layout-admin .layui-header { + height: 70px; + border-bottom: none; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05); +} + +.layadmin-tabspage-none .layui-layout-admin .layui-body { + top: 70px; +} + +.layadmin-tabspage-none .layadmin-header { + display: block; +} + +.layadmin-tabspage-none .layadmin-header .layui-breadcrumb { + border-top: 1px solid #f6f6f6; +} + +/* 底部固定区域 */ +.layui-layout-admin .layui-footer { + padding: 10px 0; + text-align: center; +} + +/* 默认主题修饰 */ +.layui-layout-admin .layui-header { + border-bottom: 1px solid #f6f6f6; + box-sizing: border-box; + background-color: #fff; +} + +.layui-layout-admin .layui-header a, +.layui-layout-admin .layui-header a cite { + color: #333; +} + +.layui-layout-admin .layui-header a:hover { + color: #000; +} + +.layui-layout-admin .layui-header .layui-nav .layui-nav-more { + border-top-color: #666 +} + +.layui-layout-admin .layui-header .layui-nav .layui-nav-mored { + border-color: transparent; + border-bottom-color: #666; +} + +.layui-layout-admin .layui-header .layui-nav .layui-this:after, +.layui-layout-admin .layui-header .layui-nav-bar { + height: 2px; + background-color: #337EFD +} + +.layui-layout-admin .layui-logo { + background-color: #337efd; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .15); +} + +.layui-layout-admin .layui-logo, +.layui-layout-admin .layui-logo a { + color: #fff; + color: rgba(255, 255, 255, .8); + height: 70px; +} + +.layui-side-menu { + box-shadow: 1px 0 2px 0 rgba(0, 0, 0, .05); +} + +.layui-layout-admin .layui-footer { + box-shadow: 0 -1px 2px 0 rgba(0, 0, 0, .05); +} + +.layui-side-menu, +.layadmin-setTheme-side { + background-color: #1A263B; + color: #fff; +} + +.layadmin-setTheme-header { + background-color: #fff; +} + +.layui-layout-admin .layui-footer { + background-color: #fff; +} + +.layui-tab-admin .layui-tab-title { + background-color: #393D49; + color: #fff; +} + +/* + 格局 +*/ + +.layui-fluid { + padding: 15px; +} + +.layadmin-header { + display: none; + height: 50px; + line-height: 50px; + margin-bottom: 0; + border-radius: 0; +} + +.layadmin-header .layui-breadcrumb { + padding: 0 15px; +} + +.layui-card-header { + position: relative; +} + +.layui-card-header .layui-icon { + line-height: initial; + position: absolute; + right: 15px; + top: 50%; + margin-top: -7px; +} + +.layadmin-iframe { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + right: 0; + bottom: 0; +} + +/* + 控制台 +*/ + +/* 重置轮播样式 */ +.layadmin-carousel { + height: 185px !important; + background-color: #fff; +} + +.layadmin-carousel .layui-carousel-ind li { + background-color: #e2e2e2; +} + +.layadmin-carousel .layui-carousel-ind li:hover { + background-color: #c2c2c2; +} + +.layadmin-carousel .layui-carousel-ind li.layui-this { + background-color: #999; +} + +.layadmin-carousel .layui-carousel, +.layadmin-carousel>*[carousel-item]>* { + background-color: #fff; +} + +.layadmin-carousel .layui-col-space10 { + margin: 0; +} + +.layadmin-carousel .layui-carousel-ind { + position: absolute; + top: -41px; + text-align: right; +} + +.layadmin-carousel .layui-carousel-ind ul { + background: none; +} + +/* 重置tab样式 */ +.layui-card .layui-tab-brief .layui-tab-title { + height: 42px; + border-bottom-color: #f6f6f6; +} + +.layui-card .layui-tab-brief .layui-tab-title li { + margin: 0 15px; + padding: 0; + line-height: 42px; +} + +.layui-card .layui-tab-brief .layui-tab-title li.layui-this { + color: #333; +} + +.layui-card .layui-tab-brief .layui-tab-title .layui-this:after { + height: 43px; +} + +.layui-card .layui-tab-brief .layui-tab-content { + padding: 15px; +} + +.layui-card .layui-table-view { + margin: 0; +} + + +/* 头部图标重置 */ +.layui-card-header.layuiadmin-card-header-auto { + padding-top: 15px; + padding-bottom: 15px; + height: auto; +} + +.layuiadmin-card-header-auto i.layuiadmin-button-btn { + position: relative; + right: 0; + top: 0; + vertical-align: middle; +} + +.layuiadmin-card-header-auto .layui-form-item:last-child { + margin-bottom: 0; +} + +/* 动画 */ +@-webkit-keyframes layui-rl { + + /* 从右往左滑入 */ + from { + -webkit-transform: translate3d(100%, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + } +} + +@keyframes layui-rl { + from { + transform: translate3d(100%, 0, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes layui-lr { + + /* 从右往左滑入 */ + from { + -webkit-transform: translate3d(0 0, 0); + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0); + opacity: 1; + } +} + +@keyframes layui-lr { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(100%, 0, 0); + } +} + +/* + 响应式补充 +*/ +@media screen and (max-width: 768px) { + + /* 产品清单模板 */ + .layadmin-panel-selection { + margin: 0; + width: auto; + } + + /* 导航 */ + .layui-body .layui-nav .layui-nav-item { + display: block; + } + + /* 主体容器 */ + .layui-layout-admin .layui-body .layadmin-tabsbody-item { + -webkit-overflow-scrolling: touch; + overflow: auto; + } +} diff --git a/public/static/css/lightbox.min.css b/public/static/css/lightbox.min.css new file mode 100644 index 0000000..272388f --- /dev/null +++ b/public/static/css/lightbox.min.css @@ -0,0 +1,195 @@ +.lb-loader, +.lightbox { + text-align: center; + line-height: 0 +} + +body.lb-disable-scrolling { + overflow: hidden +} + +.lightboxOverlay { + position: absolute; + top: 0; + left: 0; + z-index: 9999; + background-color: #000; + opacity: .8; + display: none +} + +.lightbox { + position: absolute; + left: 0; + width: 100%; + z-index: 10000; + font-weight: 400; + outline: 0 +} + +.lightbox .lb-image { + display: block; + height: auto; + max-width: inherit; + max-height: none; + border-radius: 3px; + border: 4px solid #fff +} + +.lightbox a img { + border: none +} + +.lb-outerContainer { + position: relative; + width: 250px; + height: 250px; + margin: 0 auto; + border-radius: 4px; + background-color: #fff +} + +.lb-loader, +.lb-nav { + position: absolute; + left: 0 +} + +.lb-outerContainer:after { + content: ""; + display: table; + clear: both +} + +.lb-loader { + top: 43%; + height: 25%; + width: 100% +} + +.lb-cancel { + display: block; + width: 32px; + height: 32px; + margin: 0 auto; + background: url(../images/loading.gif) no-repeat +} + +.lb-nav { + top: 0; + height: 100%; + width: 100%; + z-index: 10 +} + +.lb-container>.nav { + left: 0 +} + +.lb-nav a { + outline: 0; + background-image: url(data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==) +} + +.lb-next, +.lb-prev { + height: 100%; + cursor: pointer; + display: block +} + +.lb-nav a.lb-prev { + width: 34%; + left: 0; + float: left; + background: url(../images/prev.png) left 48% no-repeat; + opacity: 0; + -webkit-transition: opacity .6s; + -moz-transition: opacity .6s; + -o-transition: opacity .6s; + transition: opacity .6s +} + +.lb-nav a.lb-prev:hover { + opacity: 1 +} + +.lb-nav a.lb-next { + width: 64%; + right: 0; + float: right; + background: url(../images/next.png) right 48% no-repeat; + opacity: 0; + -webkit-transition: opacity .6s; + -moz-transition: opacity .6s; + -o-transition: opacity .6s; + transition: opacity .6s +} + +.lb-nav a.lb-next:hover { + opacity: 1 +} + +.lb-dataContainer { + margin: 0 auto; + padding-top: 5px; + width: 100%; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px +} + +.lb-dataContainer:after { + content: ""; + display: table; + clear: both +} + +.lb-data { + padding: 0 4px; + color: #ccc +} + +.lb-data .lb-details { + width: 85%; + float: left; + text-align: left; + line-height: 1.1em +} + +.lb-data .lb-caption { + font-size: 13px; + font-weight: 700; + line-height: 1em +} + +.lb-data .lb-caption a { + color: #4ae +} + +.lb-data .lb-number { + display: block; + clear: left; + padding-bottom: 1em; + font-size: 12px; + color: #999 +} + +.lb-data .lb-close { + display: block; + float: right; + width: 30px; + height: 30px; + background: url(../images/close.png) top right no-repeat; + text-align: right; + outline: 0; + opacity: .7; + -webkit-transition: opacity .2s; + -moz-transition: opacity .2s; + -o-transition: opacity .2s; + transition: opacity .2s +} + +.lb-data .lb-close:hover { + cursor: pointer; + opacity: 1 +} \ No newline at end of file diff --git a/public/static/css/login.css b/public/static/css/login.css new file mode 100644 index 0000000..91ebce9 --- /dev/null +++ b/public/static/css/login.css @@ -0,0 +1,23 @@ +#LAY-user-login, +.layadmin-user-display-show{display: block !important;} +.layadmin-user-login{position: relative; left: 0; top: 0; padding: 110px 0; min-height: 100%; box-sizing: border-box;} +.layadmin-user-login-main{width: 375px; margin: 0 auto; box-sizing: border-box;} +.layadmin-user-login-box{padding: 20px;} +.layadmin-user-login-header{text-align: center;} +.layadmin-user-login-header h2{margin-bottom: 10px; font-weight: 300; font-size: 30px; color: #000;} +.layadmin-user-login-header img{height:100px;margin-bottom:20px;} + +.layadmin-user-login-body .layui-form-item{position: relative;} +.layadmin-user-login-icon{position: absolute; left: 1px; top: 1px; width: 38px; line-height: 36px; text-align: center; color: #d2d2d2;} +.layadmin-user-login-body .layui-form-item .layui-input{padding-left: 38px;} +.layui-form-item input:-internal-autofill-selected { background: #ffea94 !important } +.layadmin-user-login-codeimg{max-height: 38px; width: 100%; cursor: pointer; box-sizing: border-box;} + +/* 有背景图时 */ +.layadmin-user-login-main[bgimg]{background-color: #fff; box-shadow: 0 0 5px rgba(0,0,0,0.05);} + +@media screen and (max-width: 768px) { + .layadmin-user-login{padding-top: 60px;} + .layadmin-user-login-main{width: 300px;} + .layadmin-user-login-box{padding: 10px;} +} \ No newline at end of file diff --git a/public/static/css/moban.css b/public/static/css/moban.css new file mode 100644 index 0000000..145cce0 --- /dev/null +++ b/public/static/css/moban.css @@ -0,0 +1,50 @@ +/* 模板 */ +.config-container { + padding: 20px; + background-color: #fff; + border-radius: 8px; + /* box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); */ +} +.config-header { + display: flex; + align-items: center; + margin-bottom: 30px; + padding-bottom: 15px; + border-bottom: 1px solid #eee; +} +.config-header span { + font-size: 18px; + color: #2c3e50; + font-weight: 500; + margin-right: 20px; + line-height: 50px; +} +.config-header a { + text-decoration: none; +} +.config-header a span { + padding: 6px 15px; + background: #f8f9fa; + border-radius: 4px; + color: #606266; + font-size: 14px; + transition: all 0.3s; +} +.config-header a span:hover { + background: #e9ecef; + color: #409eff; +} +.shaixuan { + /* margin-left: 20px; */ + display: flex; + align-items: center; +} + +.maintitle { + font-size: 20px; + font-weight: bold; + color: #333; + margin-bottom: 20px; + border-bottom: 1px solid #eee; + width: 100%; +} diff --git a/public/static/css/style.css b/public/static/css/style.css new file mode 100644 index 0000000..bf701e8 --- /dev/null +++ b/public/static/css/style.css @@ -0,0 +1,664 @@ +/* 基础样式 */ +body { + font-family: "Muli", sans-serif; + color: rgb(52, 58, 64); +} + +a { + text-decoration: none !important; + color: var(--bs-white); +} + +a:hover { + color: var(--bs-orange); +} + +.fas { + color: #fff !important; +} + +/* 代码块样式 */ +pre { + background-color: #f8f9fa; + border-radius: 6px; + padding: 16px; + margin: 20px 0; + overflow: auto; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + font-size: 14px; + line-height: 1.6; + color: #333; + border-left: 4px solid #0d6efd; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +pre code { + background-color: transparent; + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre; + word-break: normal; +} + +/* 代码块响应式样式 */ +@media (max-width: 768px) { + pre { + padding: 12px; + font-size: 13px; + } +} + +/* 代码高亮样式 */ +.hljs-keyword, +.hljs-selector-tag { + color: #d73a49; +} + +.hljs-string, +.hljs-attr { + color: #032f62; +} + +.hljs-number, +.hljs-literal { + color: #005cc5; +} + +.hljs-comment { + color: #6a737d; + font-style: italic; +} + +.hljs-function, +.hljs-title { + color: #6f42c1; +} + +/* 字体大小类 */ +.f20, +.f-20 { + font-size: 20px; +} + +.f18, +.f-18 { + font-size: 18px; +} + +.f16, +.f-16 { + font-size: 16px; +} + +.f14, +.f-14 { + font-size: 14px; +} + +.f12, +.f-12 { + font-size: 12px; +} + +.mr-20 { + margin-right: 20px; +} + +.mr-10 { + margin-right: 10px; +} + +/* 页脚样式 */ +.footer { + margin-top: 200px; + padding: 80px 0; + position: relative; + background-color: #2a254d; +} + +.footer .container { + display: flex; + justify-content: center; +} + +.row-main { + display: flex; + justify-content: space-between; + width: 100%; +} + +.footer-sub-menu li { + font-size: 16px; + margin-bottom: 5px; +} + +.footer-sub-menu li a:hover { + color: #f57005; + text-decoration: none; + transition: all 0.3s ease; +} + +.main-footer { + background-color: var(--eduact-black); + position: relative; + padding: 132px 0 120px; +} + +.copyright { + position: relative; + background-color: #1f1944; + padding: 27px 0 28px; +} + +.copyright__text { + color: #697585; + margin: 0; + font-family: var(--eduact-font); + font-size: 16px; + line-height: 25px; + font-weight: 400; +} + +.copyright__text a { + color: #f57005; +} + +.copyright__text a:hover { + color: #f57005; +} + +.copyright .tongji { + width: 100%; + height: 20px; + color: #fff; + margin-top: 20px; +} + +/* 响应式容器 */ +@media (min-width: 1400px) { + .container { + padding: 0 18px; + max-width: 1356px; + } +} + +/* 头部样式 */ +.main-header { + position: relative; + width: 100%; + /* height: 800px; */ +} + +.topbar-one__social { + display: flex; + align-items: center; + gap: 10px; + justify-content: end; +} + +/* 主导航菜单 */ +.main-menu { + background: #fff; + width: 100%; +} + +.main-menu .container { + height: 100px; + display: flex; + align-items: center; + justify-content: space-between; + position: relative; +} + +.main-menu .container .main-menu__right .layui-btn-primary:hover, +.sticky-nav .container .main-menu__right .layui-btn-primary:hover { + color: #3492ed; +} + +.main-menu .container .main-menu__nav a { + color: #3492ed !important; +} + +.main-menu .container .main-menu__nav a:hover { + color: #fff ; +} + +.main-menu .container a:hover { + color: var(--bs-white); +} + +#userDropdownMain a:hover { + color: #0d6efd !important; +} + +.main-menu__logo { + display: flex; + align-items: center; + position: relative; +} + +.main-menu__list { + display: flex; + justify-content: center; + gap: 10px; + margin: 0; + padding: 0; + list-style: none; +} + +.main-menu__list a { + color: #2c3e50; + font-size: 16px; + font-weight: 500; + text-decoration: none; + padding: 16px 10px; + border-radius: 6px; + transition: all 0.3s ease; + position: relative; +} + +.main-menu__list a:hover { + color: #0081ff; + /* background-color: #3498db; */ + /* box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3); */ + transform: translateY(-2px); + text-decoration: none; +} + +.main-menu__list a::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 2px; + background-color: #3498db; + transition: all 0.3s ease; + transform: translateX(-50%); +} + +.main-menu__list a:hover::after { + width: 80%; +} + +/* 固定导航 */ +.sticky-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + background: #fff; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + z-index: 1000; + padding: 10px 0; +} + +.sticky-nav .container { + display: flex; + align-items: center; + justify-content: space-between; +} + +.sticky-nav__logo { + display: flex; + align-items: center; + position: relative; +} + +.sticky-nav__menu a { + color: #212529 !important; +} + +.sticky-nav__menu a:hover { + color: #0081ff !important; + border-bottom: 3px solid #0081ff; + padding-bottom: 5px; + transition: all ease 0.2s; +} + +.sticky-nav__menu ul { + display: flex; + justify-content: center; + gap: 30px; + margin: 0; + padding: 0; + list-style: none; +} + +.sticky-nav__menu ul li a { + text-decoration: none; + font-size: 16px; + transition: color 0.3s; +} + +/* 轮播动画 */ +@keyframes slide { + 0%, + 33% { + transform: translateX(0); + } + + 34%, + 66% { + transform: translateX(-33.33%); + } + + 67%, + 100% { + transform: translateX(-66.66%); + } +} + +/* 顶部栏 */ +.topbar-one { + background: #3492ed; +} + +.topbar-one .container { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.topbar-one .container a { + color: #fff; +} + +.topbar-one .container .topbar-one__info { + display: flex; + align-items: center; + gap: 20px; +} + +.topbar-one .container .topbar-one__info a { + color: #fff !important; +} + +.topbar-one .container ul { + display: flex; + align-items: center; + margin: 10px 0; + padding: 0; +} + +.topbar-one .container ul li { + margin-right: 10px; +} + +/* 主要内容区 */ +.main-content { + max-width: 1250px; + margin: 0 auto; + padding: 0 15px; +} + +.core-block { + margin-top: 80px; +} + +.module-header { + display: flex; + justify-content: space-between; + border-bottom: 1px solid #efefef; + margin-bottom: 20px; + padding-bottom: 15px; +} + +.ModuleTitle_titleWrapper { + display: flex; +} + +.ModuleTitle_title { + margin-right: 24px; + font-size: 22px; + font-weight: 600; + color: #404040; +} + +.tab-header { + display: flex; + align-items: center; + gap: 20px; +} + +.tab-header .active { + color: #f57005; +} + +.tab-content { + display: none; + opacity: 0; + transition: opacity 0.3s ease; +} + +.tab-content.active { + opacity: 1; + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: flex-start; +} + +.tab-item { + cursor: pointer; +} + +.product-item { + transition: transform 0.3s ease; +} + +.product-item:hover { + transform: translateY(-5px); + transition: transform 0.5s ease; +} + +.ModuleTitle_subtitle { + font-size: 16px; + color: #888; +} + +.more-btn { + padding: 3px 15px; + font-size: 14px; + color: #7f848c; + line-height: 30px; + cursor: pointer; +} + +.product-list { + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: flex-start; +} + +.product-item { + cursor: pointer; +} + +.opencourse { + width: 280px; + height: 300px; + background: #fff; + box-shadow: 0 4px 30px 0 rgba(238, 242, 245, 0.8); + border-radius: 8px; + cursor: pointer; + transition: box-shadow 0.2s linear; + padding: 15px; + overflow: hidden; +} + +.opencourse:hover .title { + color: #fa8919; +} + +.video { + position: relative; + background: #eee; + border-radius: 8px; + overflow: hidden; + width: 250px; + height: 140px; +} + +.video img { + width: 100%; + object-fit: cover; +} + +.introduction { + margin: 12px 12px 0 10px; +} + +.bottom { + display: flex; + align-items: center; + justify-content: space-between; + margin: 16px 12px 0 10px; +} + +.bottom .desc, +.bottom .author, +.bottom .views, +.publishdate { + font-weight: 400; + color: #b2b2b2; + font-size: 14px; + line-height: 20px; +} + +.publishdate { + margin-top: 10px; +} + +.bottom .btn { + display: flex; + justify-content: center; + align-items: center; + width: 92px; + height: 28px; + background: #fbf5ee; + border-radius: 14px; + font-weight: 500; + color: #fa8919; + cursor: pointer; + font-size: 14px; + line-height: 20px; +} + +.introduction .title { + height: 50px; + font-size: 17px; + font-weight: 500; + color: #404040; + line-height: 25px; + transition: color 0.2s ease; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.introduction .subtitle { + height: 45px; + margin-top: 4px; + font-size: 15px; + font-weight: 400; + color: #888; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.layui-layer-btn .layui-layer-btn1 { + background: #fff; + color: #212529 !important; + border: 1px solid #212529; +} + +.program-content img, +.game-content img { + width: 100%; + margin: 20px auto; +} + +.article-detail-container { +} + +.article-detail-container .article-header .trans, +.program-detail-container .program-header .trans { + margin-bottom: 10px; + color: #aaa; + font-size: 12px; +} + +.article-detail-container .article-content h1, +.article-detail-container .article-content h2, +.article-detail-container .article-content h3, +.article-detail-container .article-content h4, +.article-detail-container .article-content h5, +.program-detail-container .program-content h1, +.program-detail-container .program-content h2, +.program-detail-container .program-content h3, +.program-detail-container .program-content h4, +.program-detail-container .program-content h5 { + font-weight: 700; + margin-bottom: 20px; +} + +.article-detail-container .article-content h1, +.program-detail-container .program-content h1 { + font-size: 2rem; +} + +.article-detail-container .article-content h2, +.program-detail-container .program-content h2 { + font-size: 1.75rem; +} + +.article-detail-container .article-content h3, +.program-detail-container .program-content h3 { + font-size: 1.45rem; +} + +.article-detail-container .article-content h4, +.program-detail-container .program-content h4 { + font-size: 1.25rem; +} + +.article-detail-container .article-content h5, +.program-detail-container .program-content h5 { + font-size: 1rem; +} + +.article-detail-container .article-content video, +.program-detail-container .program-content video { + width: 100%; + margin: 10px auto; +} + +.article-detail-container .article-content span, +.program-detail-container .program-content span { + margin: 0 5px; + padding: 2px 6px; + border-radius: 4px; +} + +.article-detail-container .article-content pre, +.program-detail-container .program-content pre { + margin-bottom: 2.5rem; +} + +.article-detail-container .article-content pre code, +.program-detail-container .program-content pre code { + white-space: pre-wrap; + word-wrap: break-word; +} + +.article-detail-container .article-content hr, +.program-detail-container .program-content hr { + margin: 40px auto; +} diff --git a/public/static/css/swiper-bundle.min.css b/public/static/css/swiper-bundle.min.css new file mode 100644 index 0000000..a91e297 --- /dev/null +++ b/public/static/css/swiper-bundle.min.css @@ -0,0 +1,733 @@ +/** + * Swiper 8.4.7 + * Most modern mobile touch slider and framework with hardware accelerated transitions + * https://swiperjs.com + * + * Copyright 2014-2023 Vladimir Kharlampidi + * + * Released under the MIT License + * + * Released on: January 30, 2023 + */ + + @font-face { + font-family: swiper-icons; + src: url('data:application/font-woff;charset=utf-8;base64, d09GRgABAAAAAAZgABAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAGRAAAABoAAAAci6qHkUdERUYAAAWgAAAAIwAAACQAYABXR1BPUwAABhQAAAAuAAAANuAY7+xHU1VCAAAFxAAAAFAAAABm2fPczU9TLzIAAAHcAAAASgAAAGBP9V5RY21hcAAAAkQAAACIAAABYt6F0cBjdnQgAAACzAAAAAQAAAAEABEBRGdhc3AAAAWYAAAACAAAAAj//wADZ2x5ZgAAAywAAADMAAAD2MHtryVoZWFkAAABbAAAADAAAAA2E2+eoWhoZWEAAAGcAAAAHwAAACQC9gDzaG10eAAAAigAAAAZAAAArgJkABFsb2NhAAAC0AAAAFoAAABaFQAUGG1heHAAAAG8AAAAHwAAACAAcABAbmFtZQAAA/gAAAE5AAACXvFdBwlwb3N0AAAFNAAAAGIAAACE5s74hXjaY2BkYGAAYpf5Hu/j+W2+MnAzMYDAzaX6QjD6/4//Bxj5GA8AuRwMYGkAPywL13jaY2BkYGA88P8Agx4j+/8fQDYfA1AEBWgDAIB2BOoAeNpjYGRgYNBh4GdgYgABEMnIABJzYNADCQAACWgAsQB42mNgYfzCOIGBlYGB0YcxjYGBwR1Kf2WQZGhhYGBiYGVmgAFGBiQQkOaawtDAoMBQxXjg/wEGPcYDDA4wNUA2CCgwsAAAO4EL6gAAeNpj2M0gyAACqxgGNWBkZ2D4/wMA+xkDdgAAAHjaY2BgYGaAYBkGRgYQiAHyGMF8FgYHIM3DwMHABGQrMOgyWDLEM1T9/w8UBfEMgLzE////P/5//f/V/xv+r4eaAAeMbAxwIUYmIMHEgKYAYjUcsDAwsLKxc3BycfPw8jEQA/gZBASFhEVExcQlJKWkZWTl5BUUlZRVVNXUNTQZBgMAAMR+E+gAEQFEAAAAKgAqACoANAA+AEgAUgBcAGYAcAB6AIQAjgCYAKIArAC2AMAAygDUAN4A6ADyAPwBBgEQARoBJAEuATgBQgFMAVYBYAFqAXQBfgGIAZIBnAGmAbIBzgHsAAB42u2NMQ6CUAyGW568x9AneYYgm4MJbhKFaExIOAVX8ApewSt4Bic4AfeAid3VOBixDxfPYEza5O+Xfi04YADggiUIULCuEJK8VhO4bSvpdnktHI5QCYtdi2sl8ZnXaHlqUrNKzdKcT8cjlq+rwZSvIVczNiezsfnP/uznmfPFBNODM2K7MTQ45YEAZqGP81AmGGcF3iPqOop0r1SPTaTbVkfUe4HXj97wYE+yNwWYxwWu4v1ugWHgo3S1XdZEVqWM7ET0cfnLGxWfkgR42o2PvWrDMBSFj/IHLaF0zKjRgdiVMwScNRAoWUoH78Y2icB/yIY09An6AH2Bdu/UB+yxopYshQiEvnvu0dURgDt8QeC8PDw7Fpji3fEA4z/PEJ6YOB5hKh4dj3EvXhxPqH/SKUY3rJ7srZ4FZnh1PMAtPhwP6fl2PMJMPDgeQ4rY8YT6Gzao0eAEA409DuggmTnFnOcSCiEiLMgxCiTI6Cq5DZUd3Qmp10vO0LaLTd2cjN4fOumlc7lUYbSQcZFkutRG7g6JKZKy0RmdLY680CDnEJ+UMkpFFe1RN7nxdVpXrC4aTtnaurOnYercZg2YVmLN/d/gczfEimrE/fs/bOuq29Zmn8tloORaXgZgGa78yO9/cnXm2BpaGvq25Dv9S4E9+5SIc9PqupJKhYFSSl47+Qcr1mYNAAAAeNptw0cKwkAAAMDZJA8Q7OUJvkLsPfZ6zFVERPy8qHh2YER+3i/BP83vIBLLySsoKimrqKqpa2hp6+jq6RsYGhmbmJqZSy0sraxtbO3sHRydnEMU4uR6yx7JJXveP7WrDycAAAAAAAH//wACeNpjYGRgYOABYhkgZgJCZgZNBkYGLQZtIJsFLMYAAAw3ALgAeNolizEKgDAQBCchRbC2sFER0YD6qVQiBCv/H9ezGI6Z5XBAw8CBK/m5iQQVauVbXLnOrMZv2oLdKFa8Pjuru2hJzGabmOSLzNMzvutpB3N42mNgZGBg4GKQYzBhYMxJLMlj4GBgAYow/P/PAJJhLM6sSoWKfWCAAwDAjgbRAAB42mNgYGBkAIIbCZo5IPrmUn0hGA0AO8EFTQAA'); + font-weight: 400; + font-style: normal +} + +:root { + --swiper-theme-color: #007aff +} + +.swiper { + margin-left: auto; + margin-right: auto; + position: relative; + overflow: hidden; + list-style: none; + padding: 0; + z-index: 1 +} + +.swiper-vertical>.swiper-wrapper { + flex-direction: column +} + +.swiper-wrapper { + position: relative; + width: 100%; + height: 100%; + z-index: 1; + display: flex; + transition-property: transform; + box-sizing: content-box +} + +.swiper-android .swiper-slide, +.swiper-wrapper { + transform: translate3d(0px, 0, 0) +} + +.swiper-pointer-events { + touch-action: pan-y +} + +.swiper-pointer-events.swiper-vertical { + touch-action: pan-x +} + +.swiper-slide { + flex-shrink: 0; + width: 100%; + height: 100%; + position: relative; + transition-property: transform +} + +.swiper-slide-invisible-blank { + visibility: hidden +} + +.swiper-autoheight, +.swiper-autoheight .swiper-slide { + height: auto +} + +.swiper-autoheight .swiper-wrapper { + align-items: flex-start; + transition-property: transform, height +} + +.swiper-backface-hidden .swiper-slide { + transform: translateZ(0); + -webkit-backface-visibility: hidden; + backface-visibility: hidden +} + +.swiper-3d, +.swiper-3d.swiper-css-mode .swiper-wrapper { + perspective: 1200px +} + +.swiper-3d .swiper-cube-shadow, +.swiper-3d .swiper-slide, +.swiper-3d .swiper-slide-shadow, +.swiper-3d .swiper-slide-shadow-bottom, +.swiper-3d .swiper-slide-shadow-left, +.swiper-3d .swiper-slide-shadow-right, +.swiper-3d .swiper-slide-shadow-top, +.swiper-3d .swiper-wrapper { + transform-style: preserve-3d +} + +.swiper-3d .swiper-slide-shadow, +.swiper-3d .swiper-slide-shadow-bottom, +.swiper-3d .swiper-slide-shadow-left, +.swiper-3d .swiper-slide-shadow-right, +.swiper-3d .swiper-slide-shadow-top { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 10 +} + +.swiper-3d .swiper-slide-shadow { + background: rgba(0, 0, 0, .15) +} + +.swiper-3d .swiper-slide-shadow-left { + background-image: linear-gradient(to left, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)) +} + +.swiper-3d .swiper-slide-shadow-right { + background-image: linear-gradient(to right, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)) +} + +.swiper-3d .swiper-slide-shadow-top { + background-image: linear-gradient(to top, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)) +} + +.swiper-3d .swiper-slide-shadow-bottom { + background-image: linear-gradient(to bottom, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)) +} + +.swiper-css-mode>.swiper-wrapper { + overflow: auto; + scrollbar-width: none; + -ms-overflow-style: none +} + +.swiper-css-mode>.swiper-wrapper::-webkit-scrollbar { + display: none +} + +.swiper-css-mode>.swiper-wrapper>.swiper-slide { + scroll-snap-align: start start +} + +.swiper-horizontal.swiper-css-mode>.swiper-wrapper { + scroll-snap-type: x mandatory +} + +.swiper-vertical.swiper-css-mode>.swiper-wrapper { + scroll-snap-type: y mandatory +} + +.swiper-centered>.swiper-wrapper::before { + content: ''; + flex-shrink: 0; + order: 9999 +} + +.swiper-centered.swiper-horizontal>.swiper-wrapper>.swiper-slide:first-child { + margin-inline-start: var(--swiper-centered-offset-before) +} + +.swiper-centered.swiper-horizontal>.swiper-wrapper::before { + height: 100%; + min-height: 1px; + width: var(--swiper-centered-offset-after) +} + +.swiper-centered.swiper-vertical>.swiper-wrapper>.swiper-slide:first-child { + margin-block-start: var(--swiper-centered-offset-before) +} + +.swiper-centered.swiper-vertical>.swiper-wrapper::before { + width: 100%; + min-width: 1px; + height: var(--swiper-centered-offset-after) +} + +.swiper-centered>.swiper-wrapper>.swiper-slide { + scroll-snap-align: center center; + scroll-snap-stop: always +} + +.swiper-virtual .swiper-slide { + -webkit-backface-visibility: hidden; + transform: translateZ(0) +} + +.swiper-virtual.swiper-css-mode .swiper-wrapper::after { + content: ''; + position: absolute; + left: 0; + top: 0; + pointer-events: none +} + +.swiper-virtual.swiper-css-mode.swiper-horizontal .swiper-wrapper::after { + height: 1px; + width: var(--swiper-virtual-size) +} + +.swiper-virtual.swiper-css-mode.swiper-vertical .swiper-wrapper::after { + width: 1px; + height: var(--swiper-virtual-size) +} + +:root { + --swiper-navigation-size: 44px +} + +.swiper-button-next, +.swiper-button-prev { + position: absolute; + top: 50%; + width: calc(var(--swiper-navigation-size)/ 44 * 27); + height: var(--swiper-navigation-size); + margin-top: calc(0px - (var(--swiper-navigation-size)/ 2)); + z-index: 10; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--swiper-navigation-color, var(--swiper-theme-color)) +} + +.swiper-button-next.swiper-button-disabled, +.swiper-button-prev.swiper-button-disabled { + opacity: .35; + cursor: auto; + pointer-events: none +} + +.swiper-button-next.swiper-button-hidden, +.swiper-button-prev.swiper-button-hidden { + opacity: 0; + cursor: auto; + pointer-events: none +} + +.swiper-navigation-disabled .swiper-button-next, +.swiper-navigation-disabled .swiper-button-prev { + display: none !important +} + +.swiper-button-next:after, +.swiper-button-prev:after { + font-family: swiper-icons; + font-size: var(--swiper-navigation-size); + text-transform: none !important; + letter-spacing: 0; + font-variant: initial; + line-height: 1 +} + +.swiper-button-prev, +.swiper-rtl .swiper-button-next { + left: 10px; + right: auto +} + +.swiper-button-prev:after, +.swiper-rtl .swiper-button-next:after { + content: 'prev' +} + +.swiper-button-next, +.swiper-rtl .swiper-button-prev { + right: 10px; + left: auto +} + +.swiper-button-next:after, +.swiper-rtl .swiper-button-prev:after { + content: 'next' +} + +.swiper-button-lock { + display: none +} + +.swiper-pagination { + position: absolute; + text-align: center; + transition: .3s opacity; + transform: translate3d(0, 0, 0); + z-index: 10 +} + +.swiper-pagination.swiper-pagination-hidden { + opacity: 0 +} + +.swiper-pagination-disabled>.swiper-pagination, +.swiper-pagination.swiper-pagination-disabled { + display: none !important +} + +.swiper-horizontal>.swiper-pagination-bullets, +.swiper-pagination-bullets.swiper-pagination-horizontal, +.swiper-pagination-custom, +.swiper-pagination-fraction { + bottom: 10px; + left: 0; + width: 100% +} + +.swiper-pagination-bullets-dynamic { + overflow: hidden; + font-size: 0 +} + +.swiper-pagination-bullets-dynamic .swiper-pagination-bullet { + transform: scale(.33); + position: relative +} + +.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active { + transform: scale(1) +} + +.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-main { + transform: scale(1) +} + +.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev { + transform: scale(.66) +} + +.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev-prev { + transform: scale(.33) +} + +.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next { + transform: scale(.66) +} + +.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next-next { + transform: scale(.33) +} + +.swiper-pagination-bullet { + width: var(--swiper-pagination-bullet-width, var(--swiper-pagination-bullet-size, 8px)); + height: var(--swiper-pagination-bullet-height, var(--swiper-pagination-bullet-size, 8px)); + display: inline-block; + border-radius: 50%; + background: var(--swiper-pagination-bullet-inactive-color, #000); + opacity: var(--swiper-pagination-bullet-inactive-opacity, .2) +} + +button.swiper-pagination-bullet { + border: none; + margin: 0; + padding: 0; + box-shadow: none; + -webkit-appearance: none; + appearance: none +} + +.swiper-pagination-clickable .swiper-pagination-bullet { + cursor: pointer +} + +.swiper-pagination-bullet:only-child { + display: none !important +} + +.swiper-pagination-bullet-active { + opacity: var(--swiper-pagination-bullet-opacity, 1); + background: var(--swiper-pagination-color, var(--swiper-theme-color)) +} + +.swiper-pagination-vertical.swiper-pagination-bullets, +.swiper-vertical>.swiper-pagination-bullets { + right: 10px; + top: 50%; + transform: translate3d(0px, -50%, 0) +} + +.swiper-pagination-vertical.swiper-pagination-bullets .swiper-pagination-bullet, +.swiper-vertical>.swiper-pagination-bullets .swiper-pagination-bullet { + margin: var(--swiper-pagination-bullet-vertical-gap, 6px) 0; + display: block +} + +.swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic, +.swiper-vertical>.swiper-pagination-bullets.swiper-pagination-bullets-dynamic { + top: 50%; + transform: translateY(-50%); + width: 8px +} + +.swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet, +.swiper-vertical>.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet { + display: inline-block; + transition: .2s transform, .2s top +} + +.swiper-horizontal>.swiper-pagination-bullets .swiper-pagination-bullet, +.swiper-pagination-horizontal.swiper-pagination-bullets .swiper-pagination-bullet { + margin: 0 var(--swiper-pagination-bullet-horizontal-gap, 4px) +} + +.swiper-horizontal>.swiper-pagination-bullets.swiper-pagination-bullets-dynamic, +.swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic { + left: 50%; + transform: translateX(-50%); + white-space: nowrap +} + +.swiper-horizontal>.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet, +.swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet { + transition: .2s transform, .2s left +} + +.swiper-horizontal.swiper-rtl>.swiper-pagination-bullets-dynamic .swiper-pagination-bullet { + transition: .2s transform, .2s right +} + +.swiper-pagination-progressbar { + background: rgba(0, 0, 0, .25); + position: absolute +} + +.swiper-pagination-progressbar .swiper-pagination-progressbar-fill { + background: var(--swiper-pagination-color, var(--swiper-theme-color)); + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + transform: scale(0); + transform-origin: left top +} + +.swiper-rtl .swiper-pagination-progressbar .swiper-pagination-progressbar-fill { + transform-origin: right top +} + +.swiper-horizontal>.swiper-pagination-progressbar, +.swiper-pagination-progressbar.swiper-pagination-horizontal, +.swiper-pagination-progressbar.swiper-pagination-vertical.swiper-pagination-progressbar-opposite, +.swiper-vertical>.swiper-pagination-progressbar.swiper-pagination-progressbar-opposite { + width: 100%; + height: 4px; + left: 0; + top: 0 +} + +.swiper-horizontal>.swiper-pagination-progressbar.swiper-pagination-progressbar-opposite, +.swiper-pagination-progressbar.swiper-pagination-horizontal.swiper-pagination-progressbar-opposite, +.swiper-pagination-progressbar.swiper-pagination-vertical, +.swiper-vertical>.swiper-pagination-progressbar { + width: 4px; + height: 100%; + left: 0; + top: 0 +} + +.swiper-pagination-lock { + display: none +} + +.swiper-scrollbar { + border-radius: 10px; + position: relative; + -ms-touch-action: none; + background: rgba(0, 0, 0, .1) +} + +.swiper-scrollbar-disabled>.swiper-scrollbar, +.swiper-scrollbar.swiper-scrollbar-disabled { + display: none !important +} + +.swiper-horizontal>.swiper-scrollbar, +.swiper-scrollbar.swiper-scrollbar-horizontal { + position: absolute; + left: 1%; + bottom: 3px; + z-index: 50; + height: 5px; + width: 98% +} + +.swiper-scrollbar.swiper-scrollbar-vertical, +.swiper-vertical>.swiper-scrollbar { + position: absolute; + right: 3px; + top: 1%; + z-index: 50; + width: 5px; + height: 98% +} + +.swiper-scrollbar-drag { + height: 100%; + width: 100%; + position: relative; + background: rgba(0, 0, 0, .5); + border-radius: 10px; + left: 0; + top: 0 +} + +.swiper-scrollbar-cursor-drag { + cursor: move +} + +.swiper-scrollbar-lock { + display: none +} + +.swiper-zoom-container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + text-align: center +} + +.swiper-zoom-container>canvas, +.swiper-zoom-container>img, +.swiper-zoom-container>svg { + max-width: 100%; + max-height: 100%; + object-fit: contain +} + +.swiper-slide-zoomed { + cursor: move +} + +.swiper-lazy-preloader { + width: 42px; + height: 42px; + position: absolute; + left: 50%; + top: 50%; + margin-left: -21px; + margin-top: -21px; + z-index: 10; + transform-origin: 50%; + box-sizing: border-box; + border: 4px solid var(--swiper-preloader-color, var(--swiper-theme-color)); + border-radius: 50%; + border-top-color: transparent +} + +.swiper-watch-progress .swiper-slide-visible .swiper-lazy-preloader, +.swiper:not(.swiper-watch-progress) .swiper-lazy-preloader { + animation: swiper-preloader-spin 1s infinite linear +} + +.swiper-lazy-preloader-white { + --swiper-preloader-color: #fff +} + +.swiper-lazy-preloader-black { + --swiper-preloader-color: #000 +} + +@keyframes swiper-preloader-spin { + 0% { + transform: rotate(0deg) + } + + 100% { + transform: rotate(360deg) + } +} + +.swiper .swiper-notification { + position: absolute; + left: 0; + top: 0; + pointer-events: none; + opacity: 0; + z-index: -1000 +} + +.swiper-free-mode>.swiper-wrapper { + transition-timing-function: ease-out; + margin: 0 auto +} + +.swiper-grid>.swiper-wrapper { + flex-wrap: wrap +} + +.swiper-grid-column>.swiper-wrapper { + flex-wrap: wrap; + flex-direction: column +} + +.swiper-fade.swiper-free-mode .swiper-slide { + transition-timing-function: ease-out +} + +.swiper-fade .swiper-slide { + pointer-events: none; + transition-property: opacity +} + +.swiper-fade .swiper-slide .swiper-slide { + pointer-events: none +} + +.swiper-fade .swiper-slide-active, +.swiper-fade .swiper-slide-active .swiper-slide-active { + pointer-events: auto +} + +.swiper-cube { + overflow: visible +} + +.swiper-cube .swiper-slide { + pointer-events: none; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + z-index: 1; + visibility: hidden; + transform-origin: 0 0; + width: 100%; + height: 100% +} + +.swiper-cube .swiper-slide .swiper-slide { + pointer-events: none +} + +.swiper-cube.swiper-rtl .swiper-slide { + transform-origin: 100% 0 +} + +.swiper-cube .swiper-slide-active, +.swiper-cube .swiper-slide-active .swiper-slide-active { + pointer-events: auto +} + +.swiper-cube .swiper-slide-active, +.swiper-cube .swiper-slide-next, +.swiper-cube .swiper-slide-next+.swiper-slide, +.swiper-cube .swiper-slide-prev { + pointer-events: auto; + visibility: visible +} + +.swiper-cube .swiper-slide-shadow-bottom, +.swiper-cube .swiper-slide-shadow-left, +.swiper-cube .swiper-slide-shadow-right, +.swiper-cube .swiper-slide-shadow-top { + z-index: 0; + -webkit-backface-visibility: hidden; + backface-visibility: hidden +} + +.swiper-cube .swiper-cube-shadow { + position: absolute; + left: 0; + bottom: 0px; + width: 100%; + height: 100%; + opacity: .6; + z-index: 0 +} + +.swiper-cube .swiper-cube-shadow:before { + content: ''; + background: #000; + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + filter: blur(50px) +} + +.swiper-flip { + overflow: visible +} + +.swiper-flip .swiper-slide { + pointer-events: none; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + z-index: 1 +} + +.swiper-flip .swiper-slide .swiper-slide { + pointer-events: none +} + +.swiper-flip .swiper-slide-active, +.swiper-flip .swiper-slide-active .swiper-slide-active { + pointer-events: auto +} + +.swiper-flip .swiper-slide-shadow-bottom, +.swiper-flip .swiper-slide-shadow-left, +.swiper-flip .swiper-slide-shadow-right, +.swiper-flip .swiper-slide-shadow-top { + z-index: 0; + -webkit-backface-visibility: hidden; + backface-visibility: hidden +} + +.swiper-creative .swiper-slide { + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + overflow: hidden; + transition-property: transform, opacity, height +} + +.swiper-cards { + overflow: visible +} + +.swiper-cards .swiper-slide { + transform-origin: center bottom; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + overflow: hidden +} \ No newline at end of file diff --git a/public/static/css/wangeditor.css b/public/static/css/wangeditor.css new file mode 100644 index 0000000..6d13c91 --- /dev/null +++ b/public/static/css/wangeditor.css @@ -0,0 +1,27 @@ +:root, +:host { + --w-e-textarea-bg-color: #fff; + --w-e-textarea-color: #333; + --w-e-textarea-border-color: #ccc; + --w-e-textarea-slight-border-color: #e8e8e8; + --w-e-textarea-slight-color: #d4d4d4; + --w-e-textarea-slight-bg-color: #f5f2f0; + --w-e-textarea-selected-border-color: #B4D5FF; + --w-e-textarea-handler-bg-color: #4290f7; + --w-e-toolbar-color: #595959; + --w-e-toolbar-bg-color: #fff; + --w-e-toolbar-active-color: #333; + --w-e-toolbar-active-bg-color: #f1f1f1; + --w-e-toolbar-disabled-color: #999; + --w-e-toolbar-border-color: #e8e8e8; + --w-e-modal-button-bg-color: #fafafa; + --w-e-modal-button-border-color: #d9d9d9; +} + +.w-e-text-container *,.w-e-toolbar *{box-sizing:border-box;margin:0;outline:none;padding:0}.w-e-text-container blockquote,.w-e-text-container li,.w-e-text-container p,.w-e-text-container td,.w-e-text-container th,.w-e-toolbar *{line-height:1.5}.w-e-text-container{background-color:var(--w-e-textarea-bg-color);color:var(--w-e-textarea-color);height:100%;position:relative}.w-e-text-container .w-e-scroll{-webkit-overflow-scrolling:touch;height:100%}.w-e-text-container [data-slate-editor]{word-wrap:break-word;border-top:1px solid transparent;min-height:100%;outline:0;padding:0 10px;white-space:pre-wrap}.w-e-text-container [data-slate-editor] p{margin:15px 0}.w-e-text-container [data-slate-editor] h1,.w-e-text-container [data-slate-editor] h2,.w-e-text-container [data-slate-editor] h3,.w-e-text-container [data-slate-editor] h4,.w-e-text-container [data-slate-editor] h5{margin:20px 0}.w-e-text-container [data-slate-editor] img{cursor:default;display:inline!important;max-width:100%;min-height:20px;min-width:20px}.w-e-text-container [data-slate-editor] span{text-indent:0}.w-e-text-container [data-slate-editor] [data-selected=true]{box-shadow:0 0 0 2px var(--w-e-textarea-selected-border-color)}.w-e-text-placeholder{font-style:italic;left:10px;top:17px;width:90%}.w-e-max-length-info,.w-e-text-placeholder{color:var(--w-e-textarea-slight-color);pointer-events:none;position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none}.w-e-max-length-info{bottom:.5em;right:1em}.w-e-bar{background-color:var(--w-e-toolbar-bg-color);color:var(--w-e-toolbar-color);font-size:14px;padding:0 5px}.w-e-bar svg{fill:var(--w-e-toolbar-color);height:14px;width:14px}.w-e-bar-show{display:flex}.w-e-bar-hidden{display:none}.w-e-hover-bar{border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 5px #0000001f;position:absolute}.w-e-toolbar{flex-wrap:wrap;position:relative}.w-e-bar-divider{background-color:var(--w-e-toolbar-border-color);display:inline-flex;height:40px;margin:0 5px;width:1px}.w-e-bar-item{display:flex;height:40px;padding:4px;position:relative;text-align:center}.w-e-bar-item,.w-e-bar-item button{align-items:center;justify-content:center}.w-e-bar-item button{background:transparent;border:none;color:var(--w-e-toolbar-color);cursor:pointer;display:inline-flex;height:32px;overflow:hidden;padding:0 8px;white-space:nowrap}.w-e-bar-item button:hover{background-color:var(--w-e-toolbar-active-bg-color);color:var(--w-e-toolbar-active-color)}.w-e-bar-item button .title{margin-left:5px}.w-e-bar-item .active{background-color:var(--w-e-toolbar-active-bg-color);color:var(--w-e-toolbar-active-color)}.w-e-bar-item .disabled{color:var(--w-e-toolbar-disabled-color);cursor:not-allowed}.w-e-bar-item .disabled svg{fill:var(--w-e-toolbar-disabled-color)}.w-e-bar-item .disabled:hover{background-color:var(--w-e-toolbar-bg-color);color:var(--w-e-toolbar-disabled-color)}.w-e-bar-item .disabled:hover svg{fill:var(--w-e-toolbar-disabled-color)}.w-e-menu-tooltip-v5:before{background-color:var(--w-e-toolbar-active-color);border-radius:5px;color:var(--w-e-toolbar-bg-color);content:attr(data-tooltip);font-size:.75em;opacity:0;padding:5px 10px;position:absolute;text-align:center;top:40px;transition:opacity .6s;visibility:hidden;white-space:pre;z-index:1}.w-e-menu-tooltip-v5:after{border:5px solid transparent;border-bottom:5px solid var(--w-e-toolbar-active-color);content:"";opacity:0;position:absolute;top:30px;transition:opacity .6s;visibility:hidden}.w-e-menu-tooltip-v5:hover:after,.w-e-menu-tooltip-v5:hover:before{opacity:1;visibility:visible}.w-e-menu-tooltip-v5.tooltip-right:before{left:100%;top:10px}.w-e-menu-tooltip-v5.tooltip-right:after{border-bottom-color:transparent;border-left-color:transparent;border-right-color:var(--w-e-toolbar-active-color);border-top-color:transparent;left:100%;margin-left:-10px;top:16px}.w-e-bar-item-group .w-e-bar-item-menus-container{background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 10px #0000001f;display:none;left:0;margin-top:40px;position:absolute;top:0;z-index:1}.w-e-bar-item-group:hover .w-e-bar-item-menus-container{display:block}.w-e-select-list{background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 10px #0000001f;left:0;margin-top:40px;max-height:350px;min-width:100px;overflow-y:auto;position:absolute;top:0;z-index:1}.w-e-select-list ul{line-height:1;list-style:none}.w-e-select-list ul .selected{background-color:var(--w-e-toolbar-active-bg-color)}.w-e-select-list ul li{cursor:pointer;padding:7px 0 7px 25px;position:relative;text-align:left;white-space:nowrap}.w-e-select-list ul li:hover{background-color:var(--w-e-toolbar-active-bg-color)}.w-e-select-list ul li svg{left:0;margin-left:5px;margin-top:-7px;position:absolute;top:50%}.w-e-bar-bottom .w-e-select-list{bottom:0;margin-bottom:40px;margin-top:0;top:inherit}.w-e-drop-panel{background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 10px #0000001f;margin-top:40px;min-width:200px;padding:10px;position:absolute;top:0;z-index:1}.w-e-bar-bottom .w-e-drop-panel{bottom:0;margin-bottom:40px;margin-top:0;top:inherit}.w-e-modal{background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 10px #0000001f;color:var(--w-e-toolbar-color);font-size:14px;min-height:40px;min-width:100px;padding:20px 15px 0;position:absolute;text-align:left;z-index:1}.w-e-modal .btn-close{cursor:pointer;line-height:1;padding:5px;position:absolute;right:8px;top:7px}.w-e-modal .btn-close svg{fill:var(--w-e-toolbar-color);height:10px;width:10px}.w-e-modal .babel-container{display:block;margin-bottom:15px}.w-e-modal .babel-container span{display:block;margin-bottom:10px}.w-e-modal .button-container{margin-bottom:15px}.w-e-modal button{background-color:var(--w-e-modal-button-bg-color);border:1px solid var(--w-e-modal-button-border-color);border-radius:4px;color:var(--w-e-toolbar-color);cursor:pointer;font-weight:400;height:32px;padding:4.5px 15px;text-align:center;touch-action:manipulation;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.w-e-modal input[type=number],.w-e-modal input[type=text],.w-e-modal textarea{font-feature-settings:"tnum";background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-modal-button-border-color);border-radius:4px;color:var(--w-e-toolbar-color);font-variant:tabular-nums;padding:4.5px 11px;transition:all .3s;width:100%}.w-e-modal textarea{min-height:60px}body .w-e-modal,body .w-e-modal *{box-sizing:border-box}.w-e-progress-bar{background-color:var(--w-e-textarea-handler-bg-color);height:1px;position:absolute;transition:width .3s;width:0}.w-e-full-screen-container{bottom:0!important;display:flex!important;flex-direction:column!important;height:100%!important;left:0!important;margin:0!important;padding:0!important;position:fixed;right:0!important;top:0!important;width:100%!important}.w-e-full-screen-container [data-w-e-textarea=true]{flex:1!important} +.w-e-text-container [data-slate-editor] code{background-color:var(--w-e-textarea-slight-bg-color);border-radius:3px;font-family:monospace;padding:3px}.w-e-panel-content-color{list-style:none;text-align:left;width:230px}.w-e-panel-content-color li{border:1px solid var(--w-e-toolbar-bg-color);border-radius:3px 3px;cursor:pointer;display:inline-block;padding:2px}.w-e-panel-content-color li:hover{border-color:var(--w-e-toolbar-color)}.w-e-panel-content-color li .color-block{border:1px solid var(--w-e-toolbar-border-color);border-radius:3px 3px;height:17px;width:17px}.w-e-panel-content-color .active{border-color:var(--w-e-toolbar-color)}.w-e-panel-content-color .clear{line-height:1.5;margin-bottom:5px;width:100%}.w-e-panel-content-color .clear svg{height:16px;margin-bottom:-4px;width:16px}.w-e-text-container [data-slate-editor] blockquote{background-color:var(--w-e-textarea-slight-bg-color);border-left:8px solid var(--w-e-textarea-selected-border-color);display:block;font-size:100%;line-height:1.5;margin:10px 0;padding:10px}.w-e-panel-content-emotion{font-size:20px;list-style:none;text-align:left;width:300px}.w-e-panel-content-emotion li{border-radius:3px 3px;cursor:pointer;display:inline-block;padding:0 5px}.w-e-panel-content-emotion li:hover{background-color:var(--w-e-textarea-slight-bg-color)}.w-e-textarea-divider{border-radius:3px;margin:20px auto;padding:20px}.w-e-textarea-divider hr{background-color:var(--w-e-textarea-border-color);border:0;display:block;height:1px}.w-e-text-container [data-slate-editor] pre>code{background-color:var(--w-e-textarea-slight-bg-color);border:1px solid var(--w-e-textarea-slight-border-color);border-radius:4px 4px;display:block;font-size:14px;padding:10px;text-indent:0}.w-e-text-container [data-slate-editor] .w-e-image-container{display:inline-block;margin:0 3px}.w-e-text-container [data-slate-editor] .w-e-image-container:hover{box-shadow:0 0 0 2px var(--w-e-textarea-selected-border-color)}.w-e-text-container [data-slate-editor] .w-e-selected-image-container{overflow:hidden;position:relative}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .w-e-image-dragger{background-color:var(--w-e-textarea-handler-bg-color);height:7px;position:absolute;width:7px}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .left-top{cursor:nwse-resize;left:0;top:0}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .right-top{cursor:nesw-resize;right:0;top:0}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .left-bottom{bottom:0;cursor:nesw-resize;left:0}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .right-bottom{bottom:0;cursor:nwse-resize;right:0}.w-e-text-container [data-slate-editor] .w-e-selected-image-container:hover{box-shadow:none}.w-e-text-container [contenteditable=false] .w-e-image-container:hover{box-shadow:none} + +.w-e-text-container [data-slate-editor] .table-container{border:1px dashed var(--w-e-textarea-border-color);border-radius:5px;margin-top:10px;overflow-x:auto;padding:10px;width:100%}.w-e-text-container [data-slate-editor] table{border-collapse:collapse}.w-e-text-container [data-slate-editor] table td,.w-e-text-container [data-slate-editor] table th{border:1px solid var(--w-e-textarea-border-color);line-height:1.5;min-width:30px;padding:3px 5px;text-align:left}.w-e-text-container [data-slate-editor] table th{background-color:var(--w-e-textarea-slight-bg-color);font-weight:700;text-align:center}.w-e-panel-content-table{background-color:var(--w-e-toolbar-bg-color)}.w-e-panel-content-table table{border-collapse:collapse}.w-e-panel-content-table td{border:1px solid var(--w-e-toolbar-border-color);cursor:pointer;height:15px;padding:3px 5px;width:20px}.w-e-panel-content-table td.active{background-color:var(--w-e-toolbar-active-bg-color)} +.w-e-textarea-video-container{background-image:linear-gradient(45deg,#eee 25%,transparent 0,transparent 75%,#eee 0,#eee),linear-gradient(45deg,#eee 25%,#fff 0,#fff 75%,#eee 0,#eee);background-position:0 0,10px 10px;background-size:20px 20px;border:1px dashed var(--w-e-textarea-border-color);border-radius:5px;margin:10px auto 0;padding:10px 0;text-align:center} + +.w-e-text-container [data-slate-editor] pre>code{word-wrap:normal;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;-webkit-hyphens:none;hyphens:none;line-height:1.5;margin:.5em 0;overflow:auto;padding:1em;-moz-tab-size:4;-o-tab-size:4;tab-size:4;text-align:left;text-shadow:0 1px #fff;white-space:pre;word-break:normal;word-spacing:normal}.w-e-text-container [data-slate-editor] pre>code .token.cdata,.w-e-text-container [data-slate-editor] pre>code .token.comment,.w-e-text-container [data-slate-editor] pre>code .token.doctype,.w-e-text-container [data-slate-editor] pre>code .token.prolog{color:#708090}.w-e-text-container [data-slate-editor] pre>code .token.punctuation{color:#999}.w-e-text-container [data-slate-editor] pre>code .token.namespace{opacity:.7}.w-e-text-container [data-slate-editor] pre>code .token.boolean,.w-e-text-container [data-slate-editor] pre>code .token.constant,.w-e-text-container [data-slate-editor] pre>code .token.deleted,.w-e-text-container [data-slate-editor] pre>code .token.number,.w-e-text-container [data-slate-editor] pre>code .token.property,.w-e-text-container [data-slate-editor] pre>code .token.symbol,.w-e-text-container [data-slate-editor] pre>code .token.tag{color:#905}.w-e-text-container [data-slate-editor] pre>code .token.attr-name,.w-e-text-container [data-slate-editor] pre>code .token.builtin,.w-e-text-container [data-slate-editor] pre>code .token.char,.w-e-text-container [data-slate-editor] pre>code .token.inserted,.w-e-text-container [data-slate-editor] pre>code .token.selector,.w-e-text-container [data-slate-editor] pre>code .token.string{color:#690}.w-e-text-container [data-slate-editor] pre>code .language-css .token.string,.w-e-text-container [data-slate-editor] pre>code .style .token.string,.w-e-text-container [data-slate-editor] pre>code .token.entity,.w-e-text-container [data-slate-editor] pre>code .token.operator,.w-e-text-container [data-slate-editor] pre>code .token.url{color:#9a6e3a}.w-e-text-container [data-slate-editor] pre>code .token.atrule,.w-e-text-container [data-slate-editor] pre>code .token.attr-value,.w-e-text-container [data-slate-editor] pre>code .token.keyword{color:#07a}.w-e-text-container [data-slate-editor] pre>code .token.class-name,.w-e-text-container [data-slate-editor] pre>code .token.function{color:#dd4a68}.w-e-text-container [data-slate-editor] pre>code .token.important,.w-e-text-container [data-slate-editor] pre>code .token.regex,.w-e-text-container [data-slate-editor] pre>code .token.variable{color:#e90}.w-e-text-container [data-slate-editor] pre>code .token.bold,.w-e-text-container [data-slate-editor] pre>code .token.important{font-weight:700}.w-e-text-container [data-slate-editor] pre>code .token.italic{font-style:italic}.w-e-text-container [data-slate-editor] pre>code .token.entity{cursor:help} \ No newline at end of file diff --git a/public/static/fonts/fa-brands-400.ttf b/public/static/fonts/fa-brands-400.ttf new file mode 100644 index 0000000..774d51a Binary files /dev/null and b/public/static/fonts/fa-brands-400.ttf differ diff --git a/public/static/fonts/fa-brands-400.woff2 b/public/static/fonts/fa-brands-400.woff2 new file mode 100644 index 0000000..71e3185 Binary files /dev/null and b/public/static/fonts/fa-brands-400.woff2 differ diff --git a/public/static/fonts/fa-regular-400.ttf b/public/static/fonts/fa-regular-400.ttf new file mode 100644 index 0000000..8a9d634 Binary files /dev/null and b/public/static/fonts/fa-regular-400.ttf differ diff --git a/public/static/fonts/fa-regular-400.woff2 b/public/static/fonts/fa-regular-400.woff2 new file mode 100644 index 0000000..7f02168 Binary files /dev/null and b/public/static/fonts/fa-regular-400.woff2 differ diff --git a/public/static/fonts/fa-solid-900.ttf b/public/static/fonts/fa-solid-900.ttf new file mode 100644 index 0000000..993dbe1 Binary files /dev/null and b/public/static/fonts/fa-solid-900.ttf differ diff --git a/public/static/fonts/fa-solid-900.woff2 b/public/static/fonts/fa-solid-900.woff2 new file mode 100644 index 0000000..5c16cd3 Binary files /dev/null and b/public/static/fonts/fa-solid-900.woff2 differ diff --git a/public/static/fonts/fa-v4compatibility.ttf b/public/static/fonts/fa-v4compatibility.ttf new file mode 100644 index 0000000..ab6ae22 Binary files /dev/null and b/public/static/fonts/fa-v4compatibility.ttf differ diff --git a/public/static/fonts/fa-v4compatibility.woff2 b/public/static/fonts/fa-v4compatibility.woff2 new file mode 100644 index 0000000..9027e38 Binary files /dev/null and b/public/static/fonts/fa-v4compatibility.woff2 differ diff --git a/public/static/images/48bf53df92855c12c85ae32643c1191e.png b/public/static/images/48bf53df92855c12c85ae32643c1191e.png new file mode 100644 index 0000000..0be157c Binary files /dev/null and b/public/static/images/48bf53df92855c12c85ae32643c1191e.png differ diff --git a/public/static/images/article1.webp b/public/static/images/article1.webp new file mode 100644 index 0000000..f0f7ef7 Binary files /dev/null and b/public/static/images/article1.webp differ diff --git a/public/static/images/avatar.png b/public/static/images/avatar.png new file mode 100644 index 0000000..bd9311d Binary files /dev/null and b/public/static/images/avatar.png differ diff --git a/public/static/images/avatar.webp b/public/static/images/avatar.webp new file mode 100644 index 0000000..0d68416 Binary files /dev/null and b/public/static/images/avatar.webp differ diff --git a/public/static/images/banner/banner1.JPG b/public/static/images/banner/banner1.JPG new file mode 100644 index 0000000..fa210c9 Binary files /dev/null and b/public/static/images/banner/banner1.JPG differ diff --git a/public/static/images/banner/banner1.png b/public/static/images/banner/banner1.png new file mode 100644 index 0000000..8e3a8cf Binary files /dev/null and b/public/static/images/banner/banner1.png differ diff --git a/public/static/images/banner/banner2.JPG b/public/static/images/banner/banner2.JPG new file mode 100644 index 0000000..75a11ca Binary files /dev/null and b/public/static/images/banner/banner2.JPG differ diff --git a/public/static/images/banner/banner2.png b/public/static/images/banner/banner2.png new file mode 100644 index 0000000..6aed399 Binary files /dev/null and b/public/static/images/banner/banner2.png differ diff --git a/public/static/images/banner/banner3.JPG b/public/static/images/banner/banner3.JPG new file mode 100644 index 0000000..23dc132 Binary files /dev/null and b/public/static/images/banner/banner3.JPG differ diff --git a/public/static/images/banner/banner3.png b/public/static/images/banner/banner3.png new file mode 100644 index 0000000..28b6efc Binary files /dev/null and b/public/static/images/banner/banner3.png differ diff --git a/public/static/images/banner/banner4.JPG b/public/static/images/banner/banner4.JPG new file mode 100644 index 0000000..a0f55b4 Binary files /dev/null and b/public/static/images/banner/banner4.JPG differ diff --git a/public/static/images/close_img.png b/public/static/images/close_img.png new file mode 100644 index 0000000..125d142 Binary files /dev/null and b/public/static/images/close_img.png differ diff --git a/public/static/images/code.png b/public/static/images/code.png new file mode 100644 index 0000000..0dd1f2f Binary files /dev/null and b/public/static/images/code.png differ diff --git a/public/static/images/default-resource.jpg b/public/static/images/default-resource.jpg new file mode 100644 index 0000000..25caaed Binary files /dev/null and b/public/static/images/default-resource.jpg differ diff --git a/public/static/images/footer-bg-1.png b/public/static/images/footer-bg-1.png new file mode 100644 index 0000000..f603e3d Binary files /dev/null and b/public/static/images/footer-bg-1.png differ diff --git a/public/static/images/hero-1-bg-img.png b/public/static/images/hero-1-bg-img.png new file mode 100644 index 0000000..aced7e2 Binary files /dev/null and b/public/static/images/hero-1-bg-img.png differ diff --git a/public/static/images/loading.gif b/public/static/images/loading.gif new file mode 100644 index 0000000..e90f0e5 Binary files /dev/null and b/public/static/images/loading.gif differ diff --git a/public/static/images/logo-l-w.png b/public/static/images/logo-l-w.png new file mode 100644 index 0000000..7170f1b Binary files /dev/null and b/public/static/images/logo-l-w.png differ diff --git a/public/static/images/logo-light.png b/public/static/images/logo-light.png new file mode 100644 index 0000000..41d0929 Binary files /dev/null and b/public/static/images/logo-light.png differ diff --git a/public/static/images/logo.png b/public/static/images/logo.png new file mode 100644 index 0000000..41d0929 Binary files /dev/null and b/public/static/images/logo.png differ diff --git a/public/static/images/logo1.png b/public/static/images/logo1.png new file mode 100644 index 0000000..ab69e49 Binary files /dev/null and b/public/static/images/logo1.png differ diff --git a/public/static/images/logo32.jpg b/public/static/images/logo32.jpg new file mode 100644 index 0000000..2bd73c8 Binary files /dev/null and b/public/static/images/logo32.jpg differ diff --git a/public/static/images/logob32.jpg b/public/static/images/logob32.jpg new file mode 100644 index 0000000..3b21cd4 Binary files /dev/null and b/public/static/images/logob32.jpg differ diff --git a/public/static/images/pattern.png b/public/static/images/pattern.png new file mode 100644 index 0000000..966c4ee Binary files /dev/null and b/public/static/images/pattern.png differ diff --git a/public/static/images/wechat_qrcode.jpg b/public/static/images/wechat_qrcode.jpg new file mode 100644 index 0000000..1300672 Binary files /dev/null and b/public/static/images/wechat_qrcode.jpg differ diff --git a/public/static/js/admin.js b/public/static/js/admin.js new file mode 100644 index 0000000..dfc0a6a --- /dev/null +++ b/public/static/js/admin.js @@ -0,0 +1,77 @@ +var ICON_SHRINK = 'layui-icon-shrink-right', +ICON_SPREAD = 'layui-icon-spread-left', +APP_SPREAD_SM = 'layadmin-side-spread-sm', +SIDE_SHRINK = 'layadmin-side-shrink', +full = 1, +status = ''; + +//全屏 +function fullScreen(){ + if(full==1){ + var ele = document.documentElement; + var reqFullScreen = ele.requestFullScreen || ele.webkitRequestFullScreen || ele.mozRequestFullScreen || ele.msRequestFullscreen; + if(typeof reqFullScreen !== 'undefined' && reqFullScreen) { + reqFullScreen.call(ele); + }; + full = 2; + }else{ + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitCancelFullScreen) { + document.webkitCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + full = 1; + } +} +// 侧边伸缩 +function shrink(){ + var app = $('#LAY_app'), + iconElem=$('#LAY_app_flexible'); + //设置状态,PC:默认展开、移动:默认收缩 + if(status === 'spread'){ + //切换到展开状态的 icon,箭头:← + iconElem.removeClass(ICON_SPREAD).addClass(ICON_SHRINK); + //移动:从左到右位移;PC:清除多余选择器恢复默认 + if(screen() < 2){ + app.addClass(APP_SPREAD_SM); + } else { + app.removeClass(APP_SPREAD_SM); + } + app.removeClass(SIDE_SHRINK); + status = ''; + } else { + //切换到搜索状态的 icon,箭头:→ + iconElem.removeClass(ICON_SHRINK).addClass(ICON_SPREAD); + //移动:清除多余选择器恢复默认;PC:从右往左收缩 + if(screen() < 2){ + app.removeClass(SIDE_SHRINK); + } else { + app.addClass(SIDE_SHRINK); + } + app.removeClass(APP_SPREAD_SM) + status = 'spread'; + } +} +//屏幕类型 +function screen(){ + var width = $(window).width(); + if(width > 1200){ + return 3; //大屏幕 + } else if(width > 992){ + return 2; //中屏幕 + } else if(width > 768){ + return 1; //小屏幕 + } else { + return 0; //超小屏幕 + } +} +//xss 转义 +function escape(html){ + return String(html || '').replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&') + .replace(//g, '>') + .replace(/'/g, ''').replace(/"/g, '"'); +} \ No newline at end of file diff --git a/public/static/js/banner.js b/public/static/js/banner.js new file mode 100644 index 0000000..3318441 --- /dev/null +++ b/public/static/js/banner.js @@ -0,0 +1,84 @@ +// 优化后的banner.js +(function () { + // 设置innerHTML + function setInnerHTML(el, html) { + if (el) el.innerHTML = html; + } + + // AJAX GET请求 + function ajaxGet(url, callback, errorCallback) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + try { + var res = JSON.parse(xhr.responseText); + callback(res); + } catch (e) { + console.error('JSON解析失败', e); + if (errorCallback) errorCallback(e); + } + } else { + console.error('请求失败', xhr.status); + if (errorCallback) errorCallback(xhr.status); + } + } + }; + xhr.send(); + } + + // 渲染banner + function renderBanner(bannerList) { + var bannerHtml = ''; + bannerList.forEach(function (banner) { + bannerHtml += '
    ' + + '' + + '
    '; + }); + var carouselItem = document.querySelector('#test10 div[carousel-item]'); + setInnerHTML(carouselItem, bannerHtml); + } + + // 主流程 + ajaxGet('/index/index/bannerlist', function (res) { + // console.log('banner接口返回:', res); + if (res && res.code === 1 && Array.isArray(res.banner)) { + renderBanner(res.banner); + + // 确保layui和carousel已加载 + if (window.layui) { + layui.use(['carousel'], function () { + var carousel = layui.carousel; + carousel.render({ + elem: '#test10', + width: '100%', + height: '100vh', + interval: 4000, + anim: 'fade', + autoplay: true, + full: false, + arrow: 'hover' + }); + }); + } else { + console.error('layui未加载'); + } + } else { + console.error('banner数据异常', res); + } + }, function (err) { + console.error('banner数据请求失败', err); + }); +})(); \ No newline at end of file diff --git a/public/static/js/bootstrap.bundle.js b/public/static/js/bootstrap.bundle.js new file mode 100644 index 0000000..859e9d2 --- /dev/null +++ b/public/static/js/bootstrap.bundle.js @@ -0,0 +1,6315 @@ +/*! + * Bootstrap v5.3.6 (https://getbootstrap.com/) + * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory()); +})(this, (function () { 'use strict'; + + /** + * -------------------------------------------------------------------------- + * Bootstrap dom/data.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + /** + * Constants + */ + + const elementMap = new Map(); + const Data = { + set(element, key, instance) { + if (!elementMap.has(element)) { + elementMap.set(element, new Map()); + } + const instanceMap = elementMap.get(element); + + // make it clear we only want one instance per element + // can be removed later when multiple key/instances are fine to be used + if (!instanceMap.has(key) && instanceMap.size !== 0) { + // eslint-disable-next-line no-console + console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`); + return; + } + instanceMap.set(key, instance); + }, + get(element, key) { + if (elementMap.has(element)) { + return elementMap.get(element).get(key) || null; + } + return null; + }, + remove(element, key) { + if (!elementMap.has(element)) { + return; + } + const instanceMap = elementMap.get(element); + instanceMap.delete(key); + + // free up element references if there are no instances left for an element + if (instanceMap.size === 0) { + elementMap.delete(element); + } + } + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/index.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + const MAX_UID = 1000000; + const MILLISECONDS_MULTIPLIER = 1000; + const TRANSITION_END = 'transitionend'; + + /** + * Properly escape IDs selectors to handle weird IDs + * @param {string} selector + * @returns {string} + */ + const parseSelector = selector => { + if (selector && window.CSS && window.CSS.escape) { + // document.querySelector needs escaping to handle IDs (html5+) containing for instance / + selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`); + } + return selector; + }; + + // Shout-out Angus Croll (https://goo.gl/pxwQGp) + const toType = object => { + if (object === null || object === undefined) { + return `${object}`; + } + return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)[1].toLowerCase(); + }; + + /** + * Public Util API + */ + + const getUID = prefix => { + do { + prefix += Math.floor(Math.random() * MAX_UID); + } while (document.getElementById(prefix)); + return prefix; + }; + const getTransitionDurationFromElement = element => { + if (!element) { + return 0; + } + + // Get transition-duration of the element + let { + transitionDuration, + transitionDelay + } = window.getComputedStyle(element); + const floatTransitionDuration = Number.parseFloat(transitionDuration); + const floatTransitionDelay = Number.parseFloat(transitionDelay); + + // Return 0 if element or transition duration is not found + if (!floatTransitionDuration && !floatTransitionDelay) { + return 0; + } + + // If multiple durations are defined, take the first + transitionDuration = transitionDuration.split(',')[0]; + transitionDelay = transitionDelay.split(',')[0]; + return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; + }; + const triggerTransitionEnd = element => { + element.dispatchEvent(new Event(TRANSITION_END)); + }; + const isElement$1 = object => { + if (!object || typeof object !== 'object') { + return false; + } + if (typeof object.jquery !== 'undefined') { + object = object[0]; + } + return typeof object.nodeType !== 'undefined'; + }; + const getElement = object => { + // it's a jQuery object or a node element + if (isElement$1(object)) { + return object.jquery ? object[0] : object; + } + if (typeof object === 'string' && object.length > 0) { + return document.querySelector(parseSelector(object)); + } + return null; + }; + const isVisible = element => { + if (!isElement$1(element) || element.getClientRects().length === 0) { + return false; + } + const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'; + // Handle `details` element as its content may falsie appear visible when it is closed + const closedDetails = element.closest('details:not([open])'); + if (!closedDetails) { + return elementIsVisible; + } + if (closedDetails !== element) { + const summary = element.closest('summary'); + if (summary && summary.parentNode !== closedDetails) { + return false; + } + if (summary === null) { + return false; + } + } + return elementIsVisible; + }; + const isDisabled = element => { + if (!element || element.nodeType !== Node.ELEMENT_NODE) { + return true; + } + if (element.classList.contains('disabled')) { + return true; + } + if (typeof element.disabled !== 'undefined') { + return element.disabled; + } + return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'; + }; + const findShadowRoot = element => { + if (!document.documentElement.attachShadow) { + return null; + } + + // Can find the shadow root otherwise it'll return the document + if (typeof element.getRootNode === 'function') { + const root = element.getRootNode(); + return root instanceof ShadowRoot ? root : null; + } + if (element instanceof ShadowRoot) { + return element; + } + + // when we don't find a shadow root + if (!element.parentNode) { + return null; + } + return findShadowRoot(element.parentNode); + }; + const noop = () => {}; + + /** + * Trick to restart an element's animation + * + * @param {HTMLElement} element + * @return void + * + * @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation + */ + const reflow = element => { + element.offsetHeight; // eslint-disable-line no-unused-expressions + }; + const getjQuery = () => { + if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { + return window.jQuery; + } + return null; + }; + const DOMContentLoadedCallbacks = []; + const onDOMContentLoaded = callback => { + if (document.readyState === 'loading') { + // add listener on the first call when the document is in loading state + if (!DOMContentLoadedCallbacks.length) { + document.addEventListener('DOMContentLoaded', () => { + for (const callback of DOMContentLoadedCallbacks) { + callback(); + } + }); + } + DOMContentLoadedCallbacks.push(callback); + } else { + callback(); + } + }; + const isRTL = () => document.documentElement.dir === 'rtl'; + const defineJQueryPlugin = plugin => { + onDOMContentLoaded(() => { + const $ = getjQuery(); + /* istanbul ignore if */ + if ($) { + const name = plugin.NAME; + const JQUERY_NO_CONFLICT = $.fn[name]; + $.fn[name] = plugin.jQueryInterface; + $.fn[name].Constructor = plugin; + $.fn[name].noConflict = () => { + $.fn[name] = JQUERY_NO_CONFLICT; + return plugin.jQueryInterface; + }; + } + }); + }; + const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { + return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue; + }; + const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { + if (!waitForTransition) { + execute(callback); + return; + } + const durationPadding = 5; + const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding; + let called = false; + const handler = ({ + target + }) => { + if (target !== transitionElement) { + return; + } + called = true; + transitionElement.removeEventListener(TRANSITION_END, handler); + execute(callback); + }; + transitionElement.addEventListener(TRANSITION_END, handler); + setTimeout(() => { + if (!called) { + triggerTransitionEnd(transitionElement); + } + }, emulatedDuration); + }; + + /** + * Return the previous/next element of a list. + * + * @param {array} list The list of elements + * @param activeElement The active element + * @param shouldGetNext Choose to get next or previous element + * @param isCycleAllowed + * @return {Element|elem} The proper element + */ + const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + const listLength = list.length; + let index = list.indexOf(activeElement); + + // if the element does not exist in the list return an element + // depending on the direction and if cycle is allowed + if (index === -1) { + return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]; + } + index += shouldGetNext ? 1 : -1; + if (isCycleAllowed) { + index = (index + listLength) % listLength; + } + return list[Math.max(0, Math.min(index, listLength - 1))]; + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap dom/event-handler.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const namespaceRegex = /[^.]*(?=\..*)\.|.*/; + const stripNameRegex = /\..*/; + const stripUidRegex = /::\d+$/; + const eventRegistry = {}; // Events storage + let uidEvent = 1; + const customEvents = { + mouseenter: 'mouseover', + mouseleave: 'mouseout' + }; + const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']); + + /** + * Private methods + */ + + function makeEventUid(element, uid) { + return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++; + } + function getElementEvents(element) { + const uid = makeEventUid(element); + element.uidEvent = uid; + eventRegistry[uid] = eventRegistry[uid] || {}; + return eventRegistry[uid]; + } + function bootstrapHandler(element, fn) { + return function handler(event) { + hydrateObj(event, { + delegateTarget: element + }); + if (handler.oneOff) { + EventHandler.off(element, event.type, fn); + } + return fn.apply(element, [event]); + }; + } + function bootstrapDelegationHandler(element, selector, fn) { + return function handler(event) { + const domElements = element.querySelectorAll(selector); + for (let { + target + } = event; target && target !== this; target = target.parentNode) { + for (const domElement of domElements) { + if (domElement !== target) { + continue; + } + hydrateObj(event, { + delegateTarget: target + }); + if (handler.oneOff) { + EventHandler.off(element, event.type, selector, fn); + } + return fn.apply(target, [event]); + } + } + }; + } + function findHandler(events, callable, delegationSelector = null) { + return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector); + } + function normalizeParameters(originalTypeEvent, handler, delegationFunction) { + const isDelegated = typeof handler === 'string'; + // TODO: tooltip passes `false` instead of selector, so we need to check + const callable = isDelegated ? delegationFunction : handler || delegationFunction; + let typeEvent = getTypeEvent(originalTypeEvent); + if (!nativeEvents.has(typeEvent)) { + typeEvent = originalTypeEvent; + } + return [isDelegated, callable, typeEvent]; + } + function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) { + if (typeof originalTypeEvent !== 'string' || !element) { + return; + } + let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); + + // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position + // this prevents the handler from being dispatched the same way as mouseover or mouseout does + if (originalTypeEvent in customEvents) { + const wrapFunction = fn => { + return function (event) { + if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) { + return fn.call(this, event); + } + }; + }; + callable = wrapFunction(callable); + } + const events = getElementEvents(element); + const handlers = events[typeEvent] || (events[typeEvent] = {}); + const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null); + if (previousFunction) { + previousFunction.oneOff = previousFunction.oneOff && oneOff; + return; + } + const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, '')); + const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable); + fn.delegationSelector = isDelegated ? handler : null; + fn.callable = callable; + fn.oneOff = oneOff; + fn.uidEvent = uid; + handlers[uid] = fn; + element.addEventListener(typeEvent, fn, isDelegated); + } + function removeHandler(element, events, typeEvent, handler, delegationSelector) { + const fn = findHandler(events[typeEvent], handler, delegationSelector); + if (!fn) { + return; + } + element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)); + delete events[typeEvent][fn.uidEvent]; + } + function removeNamespacedHandlers(element, events, typeEvent, namespace) { + const storeElementEvent = events[typeEvent] || {}; + for (const [handlerKey, event] of Object.entries(storeElementEvent)) { + if (handlerKey.includes(namespace)) { + removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); + } + } + } + function getTypeEvent(event) { + // allow to get the native events from namespaced events ('click.bs.button' --> 'click') + event = event.replace(stripNameRegex, ''); + return customEvents[event] || event; + } + const EventHandler = { + on(element, event, handler, delegationFunction) { + addHandler(element, event, handler, delegationFunction, false); + }, + one(element, event, handler, delegationFunction) { + addHandler(element, event, handler, delegationFunction, true); + }, + off(element, originalTypeEvent, handler, delegationFunction) { + if (typeof originalTypeEvent !== 'string' || !element) { + return; + } + const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); + const inNamespace = typeEvent !== originalTypeEvent; + const events = getElementEvents(element); + const storeElementEvent = events[typeEvent] || {}; + const isNamespace = originalTypeEvent.startsWith('.'); + if (typeof callable !== 'undefined') { + // Simplest case: handler is passed, remove that listener ONLY. + if (!Object.keys(storeElementEvent).length) { + return; + } + removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null); + return; + } + if (isNamespace) { + for (const elementEvent of Object.keys(events)) { + removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)); + } + } + for (const [keyHandlers, event] of Object.entries(storeElementEvent)) { + const handlerKey = keyHandlers.replace(stripUidRegex, ''); + if (!inNamespace || originalTypeEvent.includes(handlerKey)) { + removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); + } + } + }, + trigger(element, event, args) { + if (typeof event !== 'string' || !element) { + return null; + } + const $ = getjQuery(); + const typeEvent = getTypeEvent(event); + const inNamespace = event !== typeEvent; + let jQueryEvent = null; + let bubbles = true; + let nativeDispatch = true; + let defaultPrevented = false; + if (inNamespace && $) { + jQueryEvent = $.Event(event, args); + $(element).trigger(jQueryEvent); + bubbles = !jQueryEvent.isPropagationStopped(); + nativeDispatch = !jQueryEvent.isImmediatePropagationStopped(); + defaultPrevented = jQueryEvent.isDefaultPrevented(); + } + const evt = hydrateObj(new Event(event, { + bubbles, + cancelable: true + }), args); + if (defaultPrevented) { + evt.preventDefault(); + } + if (nativeDispatch) { + element.dispatchEvent(evt); + } + if (evt.defaultPrevented && jQueryEvent) { + jQueryEvent.preventDefault(); + } + return evt; + } + }; + function hydrateObj(obj, meta = {}) { + for (const [key, value] of Object.entries(meta)) { + try { + obj[key] = value; + } catch (_unused) { + Object.defineProperty(obj, key, { + configurable: true, + get() { + return value; + } + }); + } + } + return obj; + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap dom/manipulator.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + function normalizeData(value) { + if (value === 'true') { + return true; + } + if (value === 'false') { + return false; + } + if (value === Number(value).toString()) { + return Number(value); + } + if (value === '' || value === 'null') { + return null; + } + if (typeof value !== 'string') { + return value; + } + try { + return JSON.parse(decodeURIComponent(value)); + } catch (_unused) { + return value; + } + } + function normalizeDataKey(key) { + return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`); + } + const Manipulator = { + setDataAttribute(element, key, value) { + element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value); + }, + removeDataAttribute(element, key) { + element.removeAttribute(`data-bs-${normalizeDataKey(key)}`); + }, + getDataAttributes(element) { + if (!element) { + return {}; + } + const attributes = {}; + const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')); + for (const key of bsKeys) { + let pureKey = key.replace(/^bs/, ''); + pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1); + attributes[pureKey] = normalizeData(element.dataset[key]); + } + return attributes; + }, + getDataAttribute(element, key) { + return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); + } + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/config.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Class definition + */ + + class Config { + // Getters + static get Default() { + return {}; + } + static get DefaultType() { + return {}; + } + static get NAME() { + throw new Error('You have to implement the static method "NAME", for each component!'); + } + _getConfig(config) { + config = this._mergeConfigObj(config); + config = this._configAfterMerge(config); + this._typeCheckConfig(config); + return config; + } + _configAfterMerge(config) { + return config; + } + _mergeConfigObj(config, element) { + const jsonConfig = isElement$1(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse + + return { + ...this.constructor.Default, + ...(typeof jsonConfig === 'object' ? jsonConfig : {}), + ...(isElement$1(element) ? Manipulator.getDataAttributes(element) : {}), + ...(typeof config === 'object' ? config : {}) + }; + } + _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { + for (const [property, expectedTypes] of Object.entries(configTypes)) { + const value = config[property]; + const valueType = isElement$1(value) ? 'element' : toType(value); + if (!new RegExp(expectedTypes).test(valueType)) { + throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`); + } + } + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap base-component.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const VERSION = '5.3.6'; + + /** + * Class definition + */ + + class BaseComponent extends Config { + constructor(element, config) { + super(); + element = getElement(element); + if (!element) { + return; + } + this._element = element; + this._config = this._getConfig(config); + Data.set(this._element, this.constructor.DATA_KEY, this); + } + + // Public + dispose() { + Data.remove(this._element, this.constructor.DATA_KEY); + EventHandler.off(this._element, this.constructor.EVENT_KEY); + for (const propertyName of Object.getOwnPropertyNames(this)) { + this[propertyName] = null; + } + } + + // Private + _queueCallback(callback, element, isAnimated = true) { + executeAfterTransition(callback, element, isAnimated); + } + _getConfig(config) { + config = this._mergeConfigObj(config, this._element); + config = this._configAfterMerge(config); + this._typeCheckConfig(config); + return config; + } + + // Static + static getInstance(element) { + return Data.get(getElement(element), this.DATA_KEY); + } + static getOrCreateInstance(element, config = {}) { + return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null); + } + static get VERSION() { + return VERSION; + } + static get DATA_KEY() { + return `bs.${this.NAME}`; + } + static get EVENT_KEY() { + return `.${this.DATA_KEY}`; + } + static eventName(name) { + return `${name}${this.EVENT_KEY}`; + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap dom/selector-engine.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + const getSelector = element => { + let selector = element.getAttribute('data-bs-target'); + if (!selector || selector === '#') { + let hrefAttribute = element.getAttribute('href'); + + // The only valid content that could double as a selector are IDs or classes, + // so everything starting with `#` or `.`. If a "real" URL is used as the selector, + // `document.querySelector` will rightfully complain it is invalid. + // See https://github.com/twbs/bootstrap/issues/32273 + if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) { + return null; + } + + // Just in case some CMS puts out a full URL with the anchor appended + if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { + hrefAttribute = `#${hrefAttribute.split('#')[1]}`; + } + selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null; + } + return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null; + }; + const SelectorEngine = { + find(selector, element = document.documentElement) { + return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); + }, + findOne(selector, element = document.documentElement) { + return Element.prototype.querySelector.call(element, selector); + }, + children(element, selector) { + return [].concat(...element.children).filter(child => child.matches(selector)); + }, + parents(element, selector) { + const parents = []; + let ancestor = element.parentNode.closest(selector); + while (ancestor) { + parents.push(ancestor); + ancestor = ancestor.parentNode.closest(selector); + } + return parents; + }, + prev(element, selector) { + let previous = element.previousElementSibling; + while (previous) { + if (previous.matches(selector)) { + return [previous]; + } + previous = previous.previousElementSibling; + } + return []; + }, + // TODO: this is now unused; remove later along with prev() + next(element, selector) { + let next = element.nextElementSibling; + while (next) { + if (next.matches(selector)) { + return [next]; + } + next = next.nextElementSibling; + } + return []; + }, + focusableChildren(element) { + const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(','); + return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)); + }, + getSelectorFromElement(element) { + const selector = getSelector(element); + if (selector) { + return SelectorEngine.findOne(selector) ? selector : null; + } + return null; + }, + getElementFromSelector(element) { + const selector = getSelector(element); + return selector ? SelectorEngine.findOne(selector) : null; + }, + getMultipleElementsFromSelector(element) { + const selector = getSelector(element); + return selector ? SelectorEngine.find(selector) : []; + } + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/component-functions.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + const enableDismissTrigger = (component, method = 'hide') => { + const clickEvent = `click.dismiss${component.EVENT_KEY}`; + const name = component.NAME; + EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault(); + } + if (isDisabled(this)) { + return; + } + const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`); + const instance = component.getOrCreateInstance(target); + + // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method + instance[method](); + }); + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap alert.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$f = 'alert'; + const DATA_KEY$a = 'bs.alert'; + const EVENT_KEY$b = `.${DATA_KEY$a}`; + const EVENT_CLOSE = `close${EVENT_KEY$b}`; + const EVENT_CLOSED = `closed${EVENT_KEY$b}`; + const CLASS_NAME_FADE$5 = 'fade'; + const CLASS_NAME_SHOW$8 = 'show'; + + /** + * Class definition + */ + + class Alert extends BaseComponent { + // Getters + static get NAME() { + return NAME$f; + } + + // Public + close() { + const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE); + if (closeEvent.defaultPrevented) { + return; + } + this._element.classList.remove(CLASS_NAME_SHOW$8); + const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5); + this._queueCallback(() => this._destroyElement(), this._element, isAnimated); + } + + // Private + _destroyElement() { + this._element.remove(); + EventHandler.trigger(this._element, EVENT_CLOSED); + this.dispose(); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Alert.getOrCreateInstance(this); + if (typeof config !== 'string') { + return; + } + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`); + } + data[config](this); + }); + } + } + + /** + * Data API implementation + */ + + enableDismissTrigger(Alert, 'close'); + + /** + * jQuery + */ + + defineJQueryPlugin(Alert); + + /** + * -------------------------------------------------------------------------- + * Bootstrap button.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$e = 'button'; + const DATA_KEY$9 = 'bs.button'; + const EVENT_KEY$a = `.${DATA_KEY$9}`; + const DATA_API_KEY$6 = '.data-api'; + const CLASS_NAME_ACTIVE$3 = 'active'; + const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle="button"]'; + const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`; + + /** + * Class definition + */ + + class Button extends BaseComponent { + // Getters + static get NAME() { + return NAME$e; + } + + // Public + toggle() { + // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method + this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3)); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Button.getOrCreateInstance(this); + if (config === 'toggle') { + data[config](); + } + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => { + event.preventDefault(); + const button = event.target.closest(SELECTOR_DATA_TOGGLE$5); + const data = Button.getOrCreateInstance(button); + data.toggle(); + }); + + /** + * jQuery + */ + + defineJQueryPlugin(Button); + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/swipe.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$d = 'swipe'; + const EVENT_KEY$9 = '.bs.swipe'; + const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`; + const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`; + const EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`; + const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`; + const EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`; + const POINTER_TYPE_TOUCH = 'touch'; + const POINTER_TYPE_PEN = 'pen'; + const CLASS_NAME_POINTER_EVENT = 'pointer-event'; + const SWIPE_THRESHOLD = 40; + const Default$c = { + endCallback: null, + leftCallback: null, + rightCallback: null + }; + const DefaultType$c = { + endCallback: '(function|null)', + leftCallback: '(function|null)', + rightCallback: '(function|null)' + }; + + /** + * Class definition + */ + + class Swipe extends Config { + constructor(element, config) { + super(); + this._element = element; + if (!element || !Swipe.isSupported()) { + return; + } + this._config = this._getConfig(config); + this._deltaX = 0; + this._supportPointerEvents = Boolean(window.PointerEvent); + this._initEvents(); + } + + // Getters + static get Default() { + return Default$c; + } + static get DefaultType() { + return DefaultType$c; + } + static get NAME() { + return NAME$d; + } + + // Public + dispose() { + EventHandler.off(this._element, EVENT_KEY$9); + } + + // Private + _start(event) { + if (!this._supportPointerEvents) { + this._deltaX = event.touches[0].clientX; + return; + } + if (this._eventIsPointerPenTouch(event)) { + this._deltaX = event.clientX; + } + } + _end(event) { + if (this._eventIsPointerPenTouch(event)) { + this._deltaX = event.clientX - this._deltaX; + } + this._handleSwipe(); + execute(this._config.endCallback); + } + _move(event) { + this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX; + } + _handleSwipe() { + const absDeltaX = Math.abs(this._deltaX); + if (absDeltaX <= SWIPE_THRESHOLD) { + return; + } + const direction = absDeltaX / this._deltaX; + this._deltaX = 0; + if (!direction) { + return; + } + execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback); + } + _initEvents() { + if (this._supportPointerEvents) { + EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event)); + EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event)); + this._element.classList.add(CLASS_NAME_POINTER_EVENT); + } else { + EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event)); + EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event)); + EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event)); + } + } + _eventIsPointerPenTouch(event) { + return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH); + } + + // Static + static isSupported() { + return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap carousel.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$c = 'carousel'; + const DATA_KEY$8 = 'bs.carousel'; + const EVENT_KEY$8 = `.${DATA_KEY$8}`; + const DATA_API_KEY$5 = '.data-api'; + const ARROW_LEFT_KEY$1 = 'ArrowLeft'; + const ARROW_RIGHT_KEY$1 = 'ArrowRight'; + const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch + + const ORDER_NEXT = 'next'; + const ORDER_PREV = 'prev'; + const DIRECTION_LEFT = 'left'; + const DIRECTION_RIGHT = 'right'; + const EVENT_SLIDE = `slide${EVENT_KEY$8}`; + const EVENT_SLID = `slid${EVENT_KEY$8}`; + const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`; + const EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`; + const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`; + const EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`; + const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`; + const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`; + const CLASS_NAME_CAROUSEL = 'carousel'; + const CLASS_NAME_ACTIVE$2 = 'active'; + const CLASS_NAME_SLIDE = 'slide'; + const CLASS_NAME_END = 'carousel-item-end'; + const CLASS_NAME_START = 'carousel-item-start'; + const CLASS_NAME_NEXT = 'carousel-item-next'; + const CLASS_NAME_PREV = 'carousel-item-prev'; + const SELECTOR_ACTIVE = '.active'; + const SELECTOR_ITEM = '.carousel-item'; + const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM; + const SELECTOR_ITEM_IMG = '.carousel-item img'; + const SELECTOR_INDICATORS = '.carousel-indicators'; + const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'; + const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'; + const KEY_TO_DIRECTION = { + [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT, + [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT + }; + const Default$b = { + interval: 5000, + keyboard: true, + pause: 'hover', + ride: false, + touch: true, + wrap: true + }; + const DefaultType$b = { + interval: '(number|boolean)', + // TODO:v6 remove boolean support + keyboard: 'boolean', + pause: '(string|boolean)', + ride: '(boolean|string)', + touch: 'boolean', + wrap: 'boolean' + }; + + /** + * Class definition + */ + + class Carousel extends BaseComponent { + constructor(element, config) { + super(element, config); + this._interval = null; + this._activeElement = null; + this._isSliding = false; + this.touchTimeout = null; + this._swipeHelper = null; + this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element); + this._addEventListeners(); + if (this._config.ride === CLASS_NAME_CAROUSEL) { + this.cycle(); + } + } + + // Getters + static get Default() { + return Default$b; + } + static get DefaultType() { + return DefaultType$b; + } + static get NAME() { + return NAME$c; + } + + // Public + next() { + this._slide(ORDER_NEXT); + } + nextWhenVisible() { + // FIXME TODO use `document.visibilityState` + // Don't call next when the page isn't visible + // or the carousel or its parent isn't visible + if (!document.hidden && isVisible(this._element)) { + this.next(); + } + } + prev() { + this._slide(ORDER_PREV); + } + pause() { + if (this._isSliding) { + triggerTransitionEnd(this._element); + } + this._clearInterval(); + } + cycle() { + this._clearInterval(); + this._updateInterval(); + this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval); + } + _maybeEnableCycle() { + if (!this._config.ride) { + return; + } + if (this._isSliding) { + EventHandler.one(this._element, EVENT_SLID, () => this.cycle()); + return; + } + this.cycle(); + } + to(index) { + const items = this._getItems(); + if (index > items.length - 1 || index < 0) { + return; + } + if (this._isSliding) { + EventHandler.one(this._element, EVENT_SLID, () => this.to(index)); + return; + } + const activeIndex = this._getItemIndex(this._getActive()); + if (activeIndex === index) { + return; + } + const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV; + this._slide(order, items[index]); + } + dispose() { + if (this._swipeHelper) { + this._swipeHelper.dispose(); + } + super.dispose(); + } + + // Private + _configAfterMerge(config) { + config.defaultInterval = config.interval; + return config; + } + _addEventListeners() { + if (this._config.keyboard) { + EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event)); + } + if (this._config.pause === 'hover') { + EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause()); + EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle()); + } + if (this._config.touch && Swipe.isSupported()) { + this._addTouchEventListeners(); + } + } + _addTouchEventListeners() { + for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) { + EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault()); + } + const endCallBack = () => { + if (this._config.pause !== 'hover') { + return; + } + + // If it's a touch-enabled device, mouseenter/leave are fired as + // part of the mouse compatibility events on first tap - the carousel + // would stop cycling until user tapped out of it; + // here, we listen for touchend, explicitly pause the carousel + // (as if it's the second time we tap on it, mouseenter compat event + // is NOT fired) and after a timeout (to allow for mouse compatibility + // events to fire) we explicitly restart cycling + + this.pause(); + if (this.touchTimeout) { + clearTimeout(this.touchTimeout); + } + this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval); + }; + const swipeConfig = { + leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)), + rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)), + endCallback: endCallBack + }; + this._swipeHelper = new Swipe(this._element, swipeConfig); + } + _keydown(event) { + if (/input|textarea/i.test(event.target.tagName)) { + return; + } + const direction = KEY_TO_DIRECTION[event.key]; + if (direction) { + event.preventDefault(); + this._slide(this._directionToOrder(direction)); + } + } + _getItemIndex(element) { + return this._getItems().indexOf(element); + } + _setActiveIndicatorElement(index) { + if (!this._indicatorsElement) { + return; + } + const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement); + activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2); + activeIndicator.removeAttribute('aria-current'); + const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement); + if (newActiveIndicator) { + newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2); + newActiveIndicator.setAttribute('aria-current', 'true'); + } + } + _updateInterval() { + const element = this._activeElement || this._getActive(); + if (!element) { + return; + } + const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10); + this._config.interval = elementInterval || this._config.defaultInterval; + } + _slide(order, element = null) { + if (this._isSliding) { + return; + } + const activeElement = this._getActive(); + const isNext = order === ORDER_NEXT; + const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap); + if (nextElement === activeElement) { + return; + } + const nextElementIndex = this._getItemIndex(nextElement); + const triggerEvent = eventName => { + return EventHandler.trigger(this._element, eventName, { + relatedTarget: nextElement, + direction: this._orderToDirection(order), + from: this._getItemIndex(activeElement), + to: nextElementIndex + }); + }; + const slideEvent = triggerEvent(EVENT_SLIDE); + if (slideEvent.defaultPrevented) { + return; + } + if (!activeElement || !nextElement) { + // Some weirdness is happening, so we bail + // TODO: change tests that use empty divs to avoid this check + return; + } + const isCycling = Boolean(this._interval); + this.pause(); + this._isSliding = true; + this._setActiveIndicatorElement(nextElementIndex); + this._activeElement = nextElement; + const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END; + const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV; + nextElement.classList.add(orderClassName); + reflow(nextElement); + activeElement.classList.add(directionalClassName); + nextElement.classList.add(directionalClassName); + const completeCallBack = () => { + nextElement.classList.remove(directionalClassName, orderClassName); + nextElement.classList.add(CLASS_NAME_ACTIVE$2); + activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName); + this._isSliding = false; + triggerEvent(EVENT_SLID); + }; + this._queueCallback(completeCallBack, activeElement, this._isAnimated()); + if (isCycling) { + this.cycle(); + } + } + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_SLIDE); + } + _getActive() { + return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); + } + _getItems() { + return SelectorEngine.find(SELECTOR_ITEM, this._element); + } + _clearInterval() { + if (this._interval) { + clearInterval(this._interval); + this._interval = null; + } + } + _directionToOrder(direction) { + if (isRTL()) { + return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT; + } + return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV; + } + _orderToDirection(order) { + if (isRTL()) { + return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT; + } + return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT; + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Carousel.getOrCreateInstance(this, config); + if (typeof config === 'number') { + data.to(config); + return; + } + if (typeof config === 'string') { + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + } + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) { + const target = SelectorEngine.getElementFromSelector(this); + if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { + return; + } + event.preventDefault(); + const carousel = Carousel.getOrCreateInstance(target); + const slideIndex = this.getAttribute('data-bs-slide-to'); + if (slideIndex) { + carousel.to(slideIndex); + carousel._maybeEnableCycle(); + return; + } + if (Manipulator.getDataAttribute(this, 'slide') === 'next') { + carousel.next(); + carousel._maybeEnableCycle(); + return; + } + carousel.prev(); + carousel._maybeEnableCycle(); + }); + EventHandler.on(window, EVENT_LOAD_DATA_API$3, () => { + const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE); + for (const carousel of carousels) { + Carousel.getOrCreateInstance(carousel); + } + }); + + /** + * jQuery + */ + + defineJQueryPlugin(Carousel); + + /** + * -------------------------------------------------------------------------- + * Bootstrap collapse.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$b = 'collapse'; + const DATA_KEY$7 = 'bs.collapse'; + const EVENT_KEY$7 = `.${DATA_KEY$7}`; + const DATA_API_KEY$4 = '.data-api'; + const EVENT_SHOW$6 = `show${EVENT_KEY$7}`; + const EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`; + const EVENT_HIDE$6 = `hide${EVENT_KEY$7}`; + const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`; + const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`; + const CLASS_NAME_SHOW$7 = 'show'; + const CLASS_NAME_COLLAPSE = 'collapse'; + const CLASS_NAME_COLLAPSING = 'collapsing'; + const CLASS_NAME_COLLAPSED = 'collapsed'; + const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`; + const CLASS_NAME_HORIZONTAL = 'collapse-horizontal'; + const WIDTH = 'width'; + const HEIGHT = 'height'; + const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'; + const SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle="collapse"]'; + const Default$a = { + parent: null, + toggle: true + }; + const DefaultType$a = { + parent: '(null|element)', + toggle: 'boolean' + }; + + /** + * Class definition + */ + + class Collapse extends BaseComponent { + constructor(element, config) { + super(element, config); + this._isTransitioning = false; + this._triggerArray = []; + const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4); + for (const elem of toggleList) { + const selector = SelectorEngine.getSelectorFromElement(elem); + const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element); + if (selector !== null && filterElement.length) { + this._triggerArray.push(elem); + } + } + this._initializeChildren(); + if (!this._config.parent) { + this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()); + } + if (this._config.toggle) { + this.toggle(); + } + } + + // Getters + static get Default() { + return Default$a; + } + static get DefaultType() { + return DefaultType$a; + } + static get NAME() { + return NAME$b; + } + + // Public + toggle() { + if (this._isShown()) { + this.hide(); + } else { + this.show(); + } + } + show() { + if (this._isTransitioning || this._isShown()) { + return; + } + let activeChildren = []; + + // find active children + if (this._config.parent) { + activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, { + toggle: false + })); + } + if (activeChildren.length && activeChildren[0]._isTransitioning) { + return; + } + const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6); + if (startEvent.defaultPrevented) { + return; + } + for (const activeInstance of activeChildren) { + activeInstance.hide(); + } + const dimension = this._getDimension(); + this._element.classList.remove(CLASS_NAME_COLLAPSE); + this._element.classList.add(CLASS_NAME_COLLAPSING); + this._element.style[dimension] = 0; + this._addAriaAndCollapsedClass(this._triggerArray, true); + this._isTransitioning = true; + const complete = () => { + this._isTransitioning = false; + this._element.classList.remove(CLASS_NAME_COLLAPSING); + this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); + this._element.style[dimension] = ''; + EventHandler.trigger(this._element, EVENT_SHOWN$6); + }; + const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); + const scrollSize = `scroll${capitalizedDimension}`; + this._queueCallback(complete, this._element, true); + this._element.style[dimension] = `${this._element[scrollSize]}px`; + } + hide() { + if (this._isTransitioning || !this._isShown()) { + return; + } + const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6); + if (startEvent.defaultPrevented) { + return; + } + const dimension = this._getDimension(); + this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`; + reflow(this._element); + this._element.classList.add(CLASS_NAME_COLLAPSING); + this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); + for (const trigger of this._triggerArray) { + const element = SelectorEngine.getElementFromSelector(trigger); + if (element && !this._isShown(element)) { + this._addAriaAndCollapsedClass([trigger], false); + } + } + this._isTransitioning = true; + const complete = () => { + this._isTransitioning = false; + this._element.classList.remove(CLASS_NAME_COLLAPSING); + this._element.classList.add(CLASS_NAME_COLLAPSE); + EventHandler.trigger(this._element, EVENT_HIDDEN$6); + }; + this._element.style[dimension] = ''; + this._queueCallback(complete, this._element, true); + } + + // Private + _isShown(element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW$7); + } + _configAfterMerge(config) { + config.toggle = Boolean(config.toggle); // Coerce string values + config.parent = getElement(config.parent); + return config; + } + _getDimension() { + return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT; + } + _initializeChildren() { + if (!this._config.parent) { + return; + } + const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4); + for (const element of children) { + const selected = SelectorEngine.getElementFromSelector(element); + if (selected) { + this._addAriaAndCollapsedClass([element], this._isShown(selected)); + } + } + } + _getFirstLevelChildren(selector) { + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); + // remove children if greater depth + return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element)); + } + _addAriaAndCollapsedClass(triggerArray, isOpen) { + if (!triggerArray.length) { + return; + } + for (const element of triggerArray) { + element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen); + element.setAttribute('aria-expanded', isOpen); + } + } + + // Static + static jQueryInterface(config) { + const _config = {}; + if (typeof config === 'string' && /show|hide/.test(config)) { + _config.toggle = false; + } + return this.each(function () { + const data = Collapse.getOrCreateInstance(this, _config); + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + } + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) { + // preventDefault only for elements (which change the URL) not inside the collapsible element + if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') { + event.preventDefault(); + } + for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { + Collapse.getOrCreateInstance(element, { + toggle: false + }).toggle(); + } + }); + + /** + * jQuery + */ + + defineJQueryPlugin(Collapse); + + var top = 'top'; + var bottom = 'bottom'; + var right = 'right'; + var left = 'left'; + var auto = 'auto'; + var basePlacements = [top, bottom, right, left]; + var start = 'start'; + var end = 'end'; + var clippingParents = 'clippingParents'; + var viewport = 'viewport'; + var popper = 'popper'; + var reference = 'reference'; + var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) { + return acc.concat([placement + "-" + start, placement + "-" + end]); + }, []); + var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) { + return acc.concat([placement, placement + "-" + start, placement + "-" + end]); + }, []); // modifiers that need to read the DOM + + var beforeRead = 'beforeRead'; + var read = 'read'; + var afterRead = 'afterRead'; // pure-logic modifiers + + var beforeMain = 'beforeMain'; + var main = 'main'; + var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state) + + var beforeWrite = 'beforeWrite'; + var write = 'write'; + var afterWrite = 'afterWrite'; + var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite]; + + function getNodeName(element) { + return element ? (element.nodeName || '').toLowerCase() : null; + } + + function getWindow(node) { + if (node == null) { + return window; + } + + if (node.toString() !== '[object Window]') { + var ownerDocument = node.ownerDocument; + return ownerDocument ? ownerDocument.defaultView || window : window; + } + + return node; + } + + function isElement(node) { + var OwnElement = getWindow(node).Element; + return node instanceof OwnElement || node instanceof Element; + } + + function isHTMLElement(node) { + var OwnElement = getWindow(node).HTMLElement; + return node instanceof OwnElement || node instanceof HTMLElement; + } + + function isShadowRoot(node) { + // IE 11 has no ShadowRoot + if (typeof ShadowRoot === 'undefined') { + return false; + } + + var OwnElement = getWindow(node).ShadowRoot; + return node instanceof OwnElement || node instanceof ShadowRoot; + } + + // and applies them to the HTMLElements such as popper and arrow + + function applyStyles(_ref) { + var state = _ref.state; + Object.keys(state.elements).forEach(function (name) { + var style = state.styles[name] || {}; + var attributes = state.attributes[name] || {}; + var element = state.elements[name]; // arrow is optional + virtual elements + + if (!isHTMLElement(element) || !getNodeName(element)) { + return; + } // Flow doesn't support to extend this property, but it's the most + // effective way to apply styles to an HTMLElement + // $FlowFixMe[cannot-write] + + + Object.assign(element.style, style); + Object.keys(attributes).forEach(function (name) { + var value = attributes[name]; + + if (value === false) { + element.removeAttribute(name); + } else { + element.setAttribute(name, value === true ? '' : value); + } + }); + }); + } + + function effect$2(_ref2) { + var state = _ref2.state; + var initialStyles = { + popper: { + position: state.options.strategy, + left: '0', + top: '0', + margin: '0' + }, + arrow: { + position: 'absolute' + }, + reference: {} + }; + Object.assign(state.elements.popper.style, initialStyles.popper); + state.styles = initialStyles; + + if (state.elements.arrow) { + Object.assign(state.elements.arrow.style, initialStyles.arrow); + } + + return function () { + Object.keys(state.elements).forEach(function (name) { + var element = state.elements[name]; + var attributes = state.attributes[name] || {}; + var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them + + var style = styleProperties.reduce(function (style, property) { + style[property] = ''; + return style; + }, {}); // arrow is optional + virtual elements + + if (!isHTMLElement(element) || !getNodeName(element)) { + return; + } + + Object.assign(element.style, style); + Object.keys(attributes).forEach(function (attribute) { + element.removeAttribute(attribute); + }); + }); + }; + } // eslint-disable-next-line import/no-unused-modules + + + const applyStyles$1 = { + name: 'applyStyles', + enabled: true, + phase: 'write', + fn: applyStyles, + effect: effect$2, + requires: ['computeStyles'] + }; + + function getBasePlacement(placement) { + return placement.split('-')[0]; + } + + var max = Math.max; + var min = Math.min; + var round = Math.round; + + function getUAString() { + var uaData = navigator.userAgentData; + + if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) { + return uaData.brands.map(function (item) { + return item.brand + "/" + item.version; + }).join(' '); + } + + return navigator.userAgent; + } + + function isLayoutViewport() { + return !/^((?!chrome|android).)*safari/i.test(getUAString()); + } + + function getBoundingClientRect(element, includeScale, isFixedStrategy) { + if (includeScale === void 0) { + includeScale = false; + } + + if (isFixedStrategy === void 0) { + isFixedStrategy = false; + } + + var clientRect = element.getBoundingClientRect(); + var scaleX = 1; + var scaleY = 1; + + if (includeScale && isHTMLElement(element)) { + scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1; + scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1; + } + + var _ref = isElement(element) ? getWindow(element) : window, + visualViewport = _ref.visualViewport; + + var addVisualOffsets = !isLayoutViewport() && isFixedStrategy; + var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX; + var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY; + var width = clientRect.width / scaleX; + var height = clientRect.height / scaleY; + return { + width: width, + height: height, + top: y, + right: x + width, + bottom: y + height, + left: x, + x: x, + y: y + }; + } + + // means it doesn't take into account transforms. + + function getLayoutRect(element) { + var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed. + // Fixes https://github.com/popperjs/popper-core/issues/1223 + + var width = element.offsetWidth; + var height = element.offsetHeight; + + if (Math.abs(clientRect.width - width) <= 1) { + width = clientRect.width; + } + + if (Math.abs(clientRect.height - height) <= 1) { + height = clientRect.height; + } + + return { + x: element.offsetLeft, + y: element.offsetTop, + width: width, + height: height + }; + } + + function contains(parent, child) { + var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method + + if (parent.contains(child)) { + return true; + } // then fallback to custom implementation with Shadow DOM support + else if (rootNode && isShadowRoot(rootNode)) { + var next = child; + + do { + if (next && parent.isSameNode(next)) { + return true; + } // $FlowFixMe[prop-missing]: need a better way to handle this... + + + next = next.parentNode || next.host; + } while (next); + } // Give up, the result is false + + + return false; + } + + function getComputedStyle$1(element) { + return getWindow(element).getComputedStyle(element); + } + + function isTableElement(element) { + return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0; + } + + function getDocumentElement(element) { + // $FlowFixMe[incompatible-return]: assume body is always available + return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing] + element.document) || window.document).documentElement; + } + + function getParentNode(element) { + if (getNodeName(element) === 'html') { + return element; + } + + return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle + // $FlowFixMe[incompatible-return] + // $FlowFixMe[prop-missing] + element.assignedSlot || // step into the shadow DOM of the parent of a slotted node + element.parentNode || ( // DOM Element detected + isShadowRoot(element) ? element.host : null) || // ShadowRoot detected + // $FlowFixMe[incompatible-call]: HTMLElement is a Node + getDocumentElement(element) // fallback + + ); + } + + function getTrueOffsetParent(element) { + if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837 + getComputedStyle$1(element).position === 'fixed') { + return null; + } + + return element.offsetParent; + } // `.offsetParent` reports `null` for fixed elements, while absolute elements + // return the containing block + + + function getContainingBlock(element) { + var isFirefox = /firefox/i.test(getUAString()); + var isIE = /Trident/i.test(getUAString()); + + if (isIE && isHTMLElement(element)) { + // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport + var elementCss = getComputedStyle$1(element); + + if (elementCss.position === 'fixed') { + return null; + } + } + + var currentNode = getParentNode(element); + + if (isShadowRoot(currentNode)) { + currentNode = currentNode.host; + } + + while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) { + var css = getComputedStyle$1(currentNode); // This is non-exhaustive but covers the most common CSS properties that + // create a containing block. + // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block + + if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') { + return currentNode; + } else { + currentNode = currentNode.parentNode; + } + } + + return null; + } // Gets the closest ancestor positioned element. Handles some edge cases, + // such as table ancestors and cross browser bugs. + + + function getOffsetParent(element) { + var window = getWindow(element); + var offsetParent = getTrueOffsetParent(element); + + while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === 'static') { + offsetParent = getTrueOffsetParent(offsetParent); + } + + if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle$1(offsetParent).position === 'static')) { + return window; + } + + return offsetParent || getContainingBlock(element) || window; + } + + function getMainAxisFromPlacement(placement) { + return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y'; + } + + function within(min$1, value, max$1) { + return max(min$1, min(value, max$1)); + } + function withinMaxClamp(min, value, max) { + var v = within(min, value, max); + return v > max ? max : v; + } + + function getFreshSideObject() { + return { + top: 0, + right: 0, + bottom: 0, + left: 0 + }; + } + + function mergePaddingObject(paddingObject) { + return Object.assign({}, getFreshSideObject(), paddingObject); + } + + function expandToHashMap(value, keys) { + return keys.reduce(function (hashMap, key) { + hashMap[key] = value; + return hashMap; + }, {}); + } + + var toPaddingObject = function toPaddingObject(padding, state) { + padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, { + placement: state.placement + })) : padding; + return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements)); + }; + + function arrow(_ref) { + var _state$modifiersData$; + + var state = _ref.state, + name = _ref.name, + options = _ref.options; + var arrowElement = state.elements.arrow; + var popperOffsets = state.modifiersData.popperOffsets; + var basePlacement = getBasePlacement(state.placement); + var axis = getMainAxisFromPlacement(basePlacement); + var isVertical = [left, right].indexOf(basePlacement) >= 0; + var len = isVertical ? 'height' : 'width'; + + if (!arrowElement || !popperOffsets) { + return; + } + + var paddingObject = toPaddingObject(options.padding, state); + var arrowRect = getLayoutRect(arrowElement); + var minProp = axis === 'y' ? top : left; + var maxProp = axis === 'y' ? bottom : right; + var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len]; + var startDiff = popperOffsets[axis] - state.rects.reference[axis]; + var arrowOffsetParent = getOffsetParent(arrowElement); + var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0; + var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is + // outside of the popper bounds + + var min = paddingObject[minProp]; + var max = clientSize - arrowRect[len] - paddingObject[maxProp]; + var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference; + var offset = within(min, center, max); // Prevents breaking syntax highlighting... + + var axisProp = axis; + state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$); + } + + function effect$1(_ref2) { + var state = _ref2.state, + options = _ref2.options; + var _options$element = options.element, + arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element; + + if (arrowElement == null) { + return; + } // CSS selector + + + if (typeof arrowElement === 'string') { + arrowElement = state.elements.popper.querySelector(arrowElement); + + if (!arrowElement) { + return; + } + } + + if (!contains(state.elements.popper, arrowElement)) { + return; + } + + state.elements.arrow = arrowElement; + } // eslint-disable-next-line import/no-unused-modules + + + const arrow$1 = { + name: 'arrow', + enabled: true, + phase: 'main', + fn: arrow, + effect: effect$1, + requires: ['popperOffsets'], + requiresIfExists: ['preventOverflow'] + }; + + function getVariation(placement) { + return placement.split('-')[1]; + } + + var unsetSides = { + top: 'auto', + right: 'auto', + bottom: 'auto', + left: 'auto' + }; // Round the offsets to the nearest suitable subpixel based on the DPR. + // Zooming can change the DPR, but it seems to report a value that will + // cleanly divide the values into the appropriate subpixels. + + function roundOffsetsByDPR(_ref, win) { + var x = _ref.x, + y = _ref.y; + var dpr = win.devicePixelRatio || 1; + return { + x: round(x * dpr) / dpr || 0, + y: round(y * dpr) / dpr || 0 + }; + } + + function mapToStyles(_ref2) { + var _Object$assign2; + + var popper = _ref2.popper, + popperRect = _ref2.popperRect, + placement = _ref2.placement, + variation = _ref2.variation, + offsets = _ref2.offsets, + position = _ref2.position, + gpuAcceleration = _ref2.gpuAcceleration, + adaptive = _ref2.adaptive, + roundOffsets = _ref2.roundOffsets, + isFixed = _ref2.isFixed; + var _offsets$x = offsets.x, + x = _offsets$x === void 0 ? 0 : _offsets$x, + _offsets$y = offsets.y, + y = _offsets$y === void 0 ? 0 : _offsets$y; + + var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({ + x: x, + y: y + }) : { + x: x, + y: y + }; + + x = _ref3.x; + y = _ref3.y; + var hasX = offsets.hasOwnProperty('x'); + var hasY = offsets.hasOwnProperty('y'); + var sideX = left; + var sideY = top; + var win = window; + + if (adaptive) { + var offsetParent = getOffsetParent(popper); + var heightProp = 'clientHeight'; + var widthProp = 'clientWidth'; + + if (offsetParent === getWindow(popper)) { + offsetParent = getDocumentElement(popper); + + if (getComputedStyle$1(offsetParent).position !== 'static' && position === 'absolute') { + heightProp = 'scrollHeight'; + widthProp = 'scrollWidth'; + } + } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it + + + offsetParent = offsetParent; + + if (placement === top || (placement === left || placement === right) && variation === end) { + sideY = bottom; + var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing] + offsetParent[heightProp]; + y -= offsetY - popperRect.height; + y *= gpuAcceleration ? 1 : -1; + } + + if (placement === left || (placement === top || placement === bottom) && variation === end) { + sideX = right; + var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing] + offsetParent[widthProp]; + x -= offsetX - popperRect.width; + x *= gpuAcceleration ? 1 : -1; + } + } + + var commonStyles = Object.assign({ + position: position + }, adaptive && unsetSides); + + var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ + x: x, + y: y + }, getWindow(popper)) : { + x: x, + y: y + }; + + x = _ref4.x; + y = _ref4.y; + + if (gpuAcceleration) { + var _Object$assign; + + return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? "translate(" + x + "px, " + y + "px)" : "translate3d(" + x + "px, " + y + "px, 0)", _Object$assign)); + } + + return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + "px" : '', _Object$assign2[sideX] = hasX ? x + "px" : '', _Object$assign2.transform = '', _Object$assign2)); + } + + function computeStyles(_ref5) { + var state = _ref5.state, + options = _ref5.options; + var _options$gpuAccelerat = options.gpuAcceleration, + gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat, + _options$adaptive = options.adaptive, + adaptive = _options$adaptive === void 0 ? true : _options$adaptive, + _options$roundOffsets = options.roundOffsets, + roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; + var commonStyles = { + placement: getBasePlacement(state.placement), + variation: getVariation(state.placement), + popper: state.elements.popper, + popperRect: state.rects.popper, + gpuAcceleration: gpuAcceleration, + isFixed: state.options.strategy === 'fixed' + }; + + if (state.modifiersData.popperOffsets != null) { + state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, { + offsets: state.modifiersData.popperOffsets, + position: state.options.strategy, + adaptive: adaptive, + roundOffsets: roundOffsets + }))); + } + + if (state.modifiersData.arrow != null) { + state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, { + offsets: state.modifiersData.arrow, + position: 'absolute', + adaptive: false, + roundOffsets: roundOffsets + }))); + } + + state.attributes.popper = Object.assign({}, state.attributes.popper, { + 'data-popper-placement': state.placement + }); + } // eslint-disable-next-line import/no-unused-modules + + + const computeStyles$1 = { + name: 'computeStyles', + enabled: true, + phase: 'beforeWrite', + fn: computeStyles, + data: {} + }; + + var passive = { + passive: true + }; + + function effect(_ref) { + var state = _ref.state, + instance = _ref.instance, + options = _ref.options; + var _options$scroll = options.scroll, + scroll = _options$scroll === void 0 ? true : _options$scroll, + _options$resize = options.resize, + resize = _options$resize === void 0 ? true : _options$resize; + var window = getWindow(state.elements.popper); + var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper); + + if (scroll) { + scrollParents.forEach(function (scrollParent) { + scrollParent.addEventListener('scroll', instance.update, passive); + }); + } + + if (resize) { + window.addEventListener('resize', instance.update, passive); + } + + return function () { + if (scroll) { + scrollParents.forEach(function (scrollParent) { + scrollParent.removeEventListener('scroll', instance.update, passive); + }); + } + + if (resize) { + window.removeEventListener('resize', instance.update, passive); + } + }; + } // eslint-disable-next-line import/no-unused-modules + + + const eventListeners = { + name: 'eventListeners', + enabled: true, + phase: 'write', + fn: function fn() {}, + effect: effect, + data: {} + }; + + var hash$1 = { + left: 'right', + right: 'left', + bottom: 'top', + top: 'bottom' + }; + function getOppositePlacement(placement) { + return placement.replace(/left|right|bottom|top/g, function (matched) { + return hash$1[matched]; + }); + } + + var hash = { + start: 'end', + end: 'start' + }; + function getOppositeVariationPlacement(placement) { + return placement.replace(/start|end/g, function (matched) { + return hash[matched]; + }); + } + + function getWindowScroll(node) { + var win = getWindow(node); + var scrollLeft = win.pageXOffset; + var scrollTop = win.pageYOffset; + return { + scrollLeft: scrollLeft, + scrollTop: scrollTop + }; + } + + function getWindowScrollBarX(element) { + // If has a CSS width greater than the viewport, then this will be + // incorrect for RTL. + // Popper 1 is broken in this case and never had a bug report so let's assume + // it's not an issue. I don't think anyone ever specifies width on + // anyway. + // Browsers where the left scrollbar doesn't cause an issue report `0` for + // this (e.g. Edge 2019, IE11, Safari) + return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft; + } + + function getViewportRect(element, strategy) { + var win = getWindow(element); + var html = getDocumentElement(element); + var visualViewport = win.visualViewport; + var width = html.clientWidth; + var height = html.clientHeight; + var x = 0; + var y = 0; + + if (visualViewport) { + width = visualViewport.width; + height = visualViewport.height; + var layoutViewport = isLayoutViewport(); + + if (layoutViewport || !layoutViewport && strategy === 'fixed') { + x = visualViewport.offsetLeft; + y = visualViewport.offsetTop; + } + } + + return { + width: width, + height: height, + x: x + getWindowScrollBarX(element), + y: y + }; + } + + // of the `` and `` rect bounds if horizontally scrollable + + function getDocumentRect(element) { + var _element$ownerDocumen; + + var html = getDocumentElement(element); + var winScroll = getWindowScroll(element); + var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body; + var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0); + var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0); + var x = -winScroll.scrollLeft + getWindowScrollBarX(element); + var y = -winScroll.scrollTop; + + if (getComputedStyle$1(body || html).direction === 'rtl') { + x += max(html.clientWidth, body ? body.clientWidth : 0) - width; + } + + return { + width: width, + height: height, + x: x, + y: y + }; + } + + function isScrollParent(element) { + // Firefox wants us to check `-x` and `-y` variations as well + var _getComputedStyle = getComputedStyle$1(element), + overflow = _getComputedStyle.overflow, + overflowX = _getComputedStyle.overflowX, + overflowY = _getComputedStyle.overflowY; + + return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX); + } + + function getScrollParent(node) { + if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) { + // $FlowFixMe[incompatible-return]: assume body is always available + return node.ownerDocument.body; + } + + if (isHTMLElement(node) && isScrollParent(node)) { + return node; + } + + return getScrollParent(getParentNode(node)); + } + + /* + given a DOM element, return the list of all scroll parents, up the list of ancesors + until we get to the top window object. This list is what we attach scroll listeners + to, because if any of these parent elements scroll, we'll need to re-calculate the + reference element's position. + */ + + function listScrollParents(element, list) { + var _element$ownerDocumen; + + if (list === void 0) { + list = []; + } + + var scrollParent = getScrollParent(element); + var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body); + var win = getWindow(scrollParent); + var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent; + var updatedList = list.concat(target); + return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here + updatedList.concat(listScrollParents(getParentNode(target))); + } + + function rectToClientRect(rect) { + return Object.assign({}, rect, { + left: rect.x, + top: rect.y, + right: rect.x + rect.width, + bottom: rect.y + rect.height + }); + } + + function getInnerBoundingClientRect(element, strategy) { + var rect = getBoundingClientRect(element, false, strategy === 'fixed'); + rect.top = rect.top + element.clientTop; + rect.left = rect.left + element.clientLeft; + rect.bottom = rect.top + element.clientHeight; + rect.right = rect.left + element.clientWidth; + rect.width = element.clientWidth; + rect.height = element.clientHeight; + rect.x = rect.left; + rect.y = rect.top; + return rect; + } + + function getClientRectFromMixedType(element, clippingParent, strategy) { + return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element))); + } // A "clipping parent" is an overflowable container with the characteristic of + // clipping (or hiding) overflowing elements with a position different from + // `initial` + + + function getClippingParents(element) { + var clippingParents = listScrollParents(getParentNode(element)); + var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle$1(element).position) >= 0; + var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element; + + if (!isElement(clipperElement)) { + return []; + } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414 + + + return clippingParents.filter(function (clippingParent) { + return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body'; + }); + } // Gets the maximum area that the element is visible in due to any number of + // clipping parents + + + function getClippingRect(element, boundary, rootBoundary, strategy) { + var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary); + var clippingParents = [].concat(mainClippingParents, [rootBoundary]); + var firstClippingParent = clippingParents[0]; + var clippingRect = clippingParents.reduce(function (accRect, clippingParent) { + var rect = getClientRectFromMixedType(element, clippingParent, strategy); + accRect.top = max(rect.top, accRect.top); + accRect.right = min(rect.right, accRect.right); + accRect.bottom = min(rect.bottom, accRect.bottom); + accRect.left = max(rect.left, accRect.left); + return accRect; + }, getClientRectFromMixedType(element, firstClippingParent, strategy)); + clippingRect.width = clippingRect.right - clippingRect.left; + clippingRect.height = clippingRect.bottom - clippingRect.top; + clippingRect.x = clippingRect.left; + clippingRect.y = clippingRect.top; + return clippingRect; + } + + function computeOffsets(_ref) { + var reference = _ref.reference, + element = _ref.element, + placement = _ref.placement; + var basePlacement = placement ? getBasePlacement(placement) : null; + var variation = placement ? getVariation(placement) : null; + var commonX = reference.x + reference.width / 2 - element.width / 2; + var commonY = reference.y + reference.height / 2 - element.height / 2; + var offsets; + + switch (basePlacement) { + case top: + offsets = { + x: commonX, + y: reference.y - element.height + }; + break; + + case bottom: + offsets = { + x: commonX, + y: reference.y + reference.height + }; + break; + + case right: + offsets = { + x: reference.x + reference.width, + y: commonY + }; + break; + + case left: + offsets = { + x: reference.x - element.width, + y: commonY + }; + break; + + default: + offsets = { + x: reference.x, + y: reference.y + }; + } + + var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null; + + if (mainAxis != null) { + var len = mainAxis === 'y' ? 'height' : 'width'; + + switch (variation) { + case start: + offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2); + break; + + case end: + offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2); + break; + } + } + + return offsets; + } + + function detectOverflow(state, options) { + if (options === void 0) { + options = {}; + } + + var _options = options, + _options$placement = _options.placement, + placement = _options$placement === void 0 ? state.placement : _options$placement, + _options$strategy = _options.strategy, + strategy = _options$strategy === void 0 ? state.strategy : _options$strategy, + _options$boundary = _options.boundary, + boundary = _options$boundary === void 0 ? clippingParents : _options$boundary, + _options$rootBoundary = _options.rootBoundary, + rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary, + _options$elementConte = _options.elementContext, + elementContext = _options$elementConte === void 0 ? popper : _options$elementConte, + _options$altBoundary = _options.altBoundary, + altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary, + _options$padding = _options.padding, + padding = _options$padding === void 0 ? 0 : _options$padding; + var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements)); + var altContext = elementContext === popper ? reference : popper; + var popperRect = state.rects.popper; + var element = state.elements[altBoundary ? altContext : elementContext]; + var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy); + var referenceClientRect = getBoundingClientRect(state.elements.reference); + var popperOffsets = computeOffsets({ + reference: referenceClientRect, + element: popperRect, + placement: placement + }); + var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets)); + var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect + // 0 or negative = within the clipping rect + + var overflowOffsets = { + top: clippingClientRect.top - elementClientRect.top + paddingObject.top, + bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom, + left: clippingClientRect.left - elementClientRect.left + paddingObject.left, + right: elementClientRect.right - clippingClientRect.right + paddingObject.right + }; + var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element + + if (elementContext === popper && offsetData) { + var offset = offsetData[placement]; + Object.keys(overflowOffsets).forEach(function (key) { + var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1; + var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x'; + overflowOffsets[key] += offset[axis] * multiply; + }); + } + + return overflowOffsets; + } + + function computeAutoPlacement(state, options) { + if (options === void 0) { + options = {}; + } + + var _options = options, + placement = _options.placement, + boundary = _options.boundary, + rootBoundary = _options.rootBoundary, + padding = _options.padding, + flipVariations = _options.flipVariations, + _options$allowedAutoP = _options.allowedAutoPlacements, + allowedAutoPlacements = _options$allowedAutoP === void 0 ? placements : _options$allowedAutoP; + var variation = getVariation(placement); + var placements$1 = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) { + return getVariation(placement) === variation; + }) : basePlacements; + var allowedPlacements = placements$1.filter(function (placement) { + return allowedAutoPlacements.indexOf(placement) >= 0; + }); + + if (allowedPlacements.length === 0) { + allowedPlacements = placements$1; + } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... + + + var overflows = allowedPlacements.reduce(function (acc, placement) { + acc[placement] = detectOverflow(state, { + placement: placement, + boundary: boundary, + rootBoundary: rootBoundary, + padding: padding + })[getBasePlacement(placement)]; + return acc; + }, {}); + return Object.keys(overflows).sort(function (a, b) { + return overflows[a] - overflows[b]; + }); + } + + function getExpandedFallbackPlacements(placement) { + if (getBasePlacement(placement) === auto) { + return []; + } + + var oppositePlacement = getOppositePlacement(placement); + return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)]; + } + + function flip(_ref) { + var state = _ref.state, + options = _ref.options, + name = _ref.name; + + if (state.modifiersData[name]._skip) { + return; + } + + var _options$mainAxis = options.mainAxis, + checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis, + _options$altAxis = options.altAxis, + checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis, + specifiedFallbackPlacements = options.fallbackPlacements, + padding = options.padding, + boundary = options.boundary, + rootBoundary = options.rootBoundary, + altBoundary = options.altBoundary, + _options$flipVariatio = options.flipVariations, + flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio, + allowedAutoPlacements = options.allowedAutoPlacements; + var preferredPlacement = state.options.placement; + var basePlacement = getBasePlacement(preferredPlacement); + var isBasePlacement = basePlacement === preferredPlacement; + var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement)); + var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) { + return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, { + placement: placement, + boundary: boundary, + rootBoundary: rootBoundary, + padding: padding, + flipVariations: flipVariations, + allowedAutoPlacements: allowedAutoPlacements + }) : placement); + }, []); + var referenceRect = state.rects.reference; + var popperRect = state.rects.popper; + var checksMap = new Map(); + var makeFallbackChecks = true; + var firstFittingPlacement = placements[0]; + + for (var i = 0; i < placements.length; i++) { + var placement = placements[i]; + + var _basePlacement = getBasePlacement(placement); + + var isStartVariation = getVariation(placement) === start; + var isVertical = [top, bottom].indexOf(_basePlacement) >= 0; + var len = isVertical ? 'width' : 'height'; + var overflow = detectOverflow(state, { + placement: placement, + boundary: boundary, + rootBoundary: rootBoundary, + altBoundary: altBoundary, + padding: padding + }); + var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top; + + if (referenceRect[len] > popperRect[len]) { + mainVariationSide = getOppositePlacement(mainVariationSide); + } + + var altVariationSide = getOppositePlacement(mainVariationSide); + var checks = []; + + if (checkMainAxis) { + checks.push(overflow[_basePlacement] <= 0); + } + + if (checkAltAxis) { + checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0); + } + + if (checks.every(function (check) { + return check; + })) { + firstFittingPlacement = placement; + makeFallbackChecks = false; + break; + } + + checksMap.set(placement, checks); + } + + if (makeFallbackChecks) { + // `2` may be desired in some cases – research later + var numberOfChecks = flipVariations ? 3 : 1; + + var _loop = function _loop(_i) { + var fittingPlacement = placements.find(function (placement) { + var checks = checksMap.get(placement); + + if (checks) { + return checks.slice(0, _i).every(function (check) { + return check; + }); + } + }); + + if (fittingPlacement) { + firstFittingPlacement = fittingPlacement; + return "break"; + } + }; + + for (var _i = numberOfChecks; _i > 0; _i--) { + var _ret = _loop(_i); + + if (_ret === "break") break; + } + } + + if (state.placement !== firstFittingPlacement) { + state.modifiersData[name]._skip = true; + state.placement = firstFittingPlacement; + state.reset = true; + } + } // eslint-disable-next-line import/no-unused-modules + + + const flip$1 = { + name: 'flip', + enabled: true, + phase: 'main', + fn: flip, + requiresIfExists: ['offset'], + data: { + _skip: false + } + }; + + function getSideOffsets(overflow, rect, preventedOffsets) { + if (preventedOffsets === void 0) { + preventedOffsets = { + x: 0, + y: 0 + }; + } + + return { + top: overflow.top - rect.height - preventedOffsets.y, + right: overflow.right - rect.width + preventedOffsets.x, + bottom: overflow.bottom - rect.height + preventedOffsets.y, + left: overflow.left - rect.width - preventedOffsets.x + }; + } + + function isAnySideFullyClipped(overflow) { + return [top, right, bottom, left].some(function (side) { + return overflow[side] >= 0; + }); + } + + function hide(_ref) { + var state = _ref.state, + name = _ref.name; + var referenceRect = state.rects.reference; + var popperRect = state.rects.popper; + var preventedOffsets = state.modifiersData.preventOverflow; + var referenceOverflow = detectOverflow(state, { + elementContext: 'reference' + }); + var popperAltOverflow = detectOverflow(state, { + altBoundary: true + }); + var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect); + var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets); + var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets); + var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets); + state.modifiersData[name] = { + referenceClippingOffsets: referenceClippingOffsets, + popperEscapeOffsets: popperEscapeOffsets, + isReferenceHidden: isReferenceHidden, + hasPopperEscaped: hasPopperEscaped + }; + state.attributes.popper = Object.assign({}, state.attributes.popper, { + 'data-popper-reference-hidden': isReferenceHidden, + 'data-popper-escaped': hasPopperEscaped + }); + } // eslint-disable-next-line import/no-unused-modules + + + const hide$1 = { + name: 'hide', + enabled: true, + phase: 'main', + requiresIfExists: ['preventOverflow'], + fn: hide + }; + + function distanceAndSkiddingToXY(placement, rects, offset) { + var basePlacement = getBasePlacement(placement); + var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1; + + var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, { + placement: placement + })) : offset, + skidding = _ref[0], + distance = _ref[1]; + + skidding = skidding || 0; + distance = (distance || 0) * invertDistance; + return [left, right].indexOf(basePlacement) >= 0 ? { + x: distance, + y: skidding + } : { + x: skidding, + y: distance + }; + } + + function offset(_ref2) { + var state = _ref2.state, + options = _ref2.options, + name = _ref2.name; + var _options$offset = options.offset, + offset = _options$offset === void 0 ? [0, 0] : _options$offset; + var data = placements.reduce(function (acc, placement) { + acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset); + return acc; + }, {}); + var _data$state$placement = data[state.placement], + x = _data$state$placement.x, + y = _data$state$placement.y; + + if (state.modifiersData.popperOffsets != null) { + state.modifiersData.popperOffsets.x += x; + state.modifiersData.popperOffsets.y += y; + } + + state.modifiersData[name] = data; + } // eslint-disable-next-line import/no-unused-modules + + + const offset$1 = { + name: 'offset', + enabled: true, + phase: 'main', + requires: ['popperOffsets'], + fn: offset + }; + + function popperOffsets(_ref) { + var state = _ref.state, + name = _ref.name; + // Offsets are the actual position the popper needs to have to be + // properly positioned near its reference element + // This is the most basic placement, and will be adjusted by + // the modifiers in the next step + state.modifiersData[name] = computeOffsets({ + reference: state.rects.reference, + element: state.rects.popper, + placement: state.placement + }); + } // eslint-disable-next-line import/no-unused-modules + + + const popperOffsets$1 = { + name: 'popperOffsets', + enabled: true, + phase: 'read', + fn: popperOffsets, + data: {} + }; + + function getAltAxis(axis) { + return axis === 'x' ? 'y' : 'x'; + } + + function preventOverflow(_ref) { + var state = _ref.state, + options = _ref.options, + name = _ref.name; + var _options$mainAxis = options.mainAxis, + checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis, + _options$altAxis = options.altAxis, + checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis, + boundary = options.boundary, + rootBoundary = options.rootBoundary, + altBoundary = options.altBoundary, + padding = options.padding, + _options$tether = options.tether, + tether = _options$tether === void 0 ? true : _options$tether, + _options$tetherOffset = options.tetherOffset, + tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset; + var overflow = detectOverflow(state, { + boundary: boundary, + rootBoundary: rootBoundary, + padding: padding, + altBoundary: altBoundary + }); + var basePlacement = getBasePlacement(state.placement); + var variation = getVariation(state.placement); + var isBasePlacement = !variation; + var mainAxis = getMainAxisFromPlacement(basePlacement); + var altAxis = getAltAxis(mainAxis); + var popperOffsets = state.modifiersData.popperOffsets; + var referenceRect = state.rects.reference; + var popperRect = state.rects.popper; + var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, { + placement: state.placement + })) : tetherOffset; + var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? { + mainAxis: tetherOffsetValue, + altAxis: tetherOffsetValue + } : Object.assign({ + mainAxis: 0, + altAxis: 0 + }, tetherOffsetValue); + var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null; + var data = { + x: 0, + y: 0 + }; + + if (!popperOffsets) { + return; + } + + if (checkMainAxis) { + var _offsetModifierState$; + + var mainSide = mainAxis === 'y' ? top : left; + var altSide = mainAxis === 'y' ? bottom : right; + var len = mainAxis === 'y' ? 'height' : 'width'; + var offset = popperOffsets[mainAxis]; + var min$1 = offset + overflow[mainSide]; + var max$1 = offset - overflow[altSide]; + var additive = tether ? -popperRect[len] / 2 : 0; + var minLen = variation === start ? referenceRect[len] : popperRect[len]; + var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go + // outside the reference bounds + + var arrowElement = state.elements.arrow; + var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : { + width: 0, + height: 0 + }; + var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject(); + var arrowPaddingMin = arrowPaddingObject[mainSide]; + var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want + // to include its full size in the calculation. If the reference is small + // and near the edge of a boundary, the popper can overflow even if the + // reference is not overflowing as well (e.g. virtual elements with no + // width or height) + + var arrowLen = within(0, referenceRect[len], arrowRect[len]); + var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis; + var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis; + var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow); + var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0; + var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0; + var tetherMin = offset + minOffset - offsetModifierValue - clientOffset; + var tetherMax = offset + maxOffset - offsetModifierValue; + var preventedOffset = within(tether ? min(min$1, tetherMin) : min$1, offset, tether ? max(max$1, tetherMax) : max$1); + popperOffsets[mainAxis] = preventedOffset; + data[mainAxis] = preventedOffset - offset; + } + + if (checkAltAxis) { + var _offsetModifierState$2; + + var _mainSide = mainAxis === 'x' ? top : left; + + var _altSide = mainAxis === 'x' ? bottom : right; + + var _offset = popperOffsets[altAxis]; + + var _len = altAxis === 'y' ? 'height' : 'width'; + + var _min = _offset + overflow[_mainSide]; + + var _max = _offset - overflow[_altSide]; + + var isOriginSide = [top, left].indexOf(basePlacement) !== -1; + + var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0; + + var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis; + + var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max; + + var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max); + + popperOffsets[altAxis] = _preventedOffset; + data[altAxis] = _preventedOffset - _offset; + } + + state.modifiersData[name] = data; + } // eslint-disable-next-line import/no-unused-modules + + + const preventOverflow$1 = { + name: 'preventOverflow', + enabled: true, + phase: 'main', + fn: preventOverflow, + requiresIfExists: ['offset'] + }; + + function getHTMLElementScroll(element) { + return { + scrollLeft: element.scrollLeft, + scrollTop: element.scrollTop + }; + } + + function getNodeScroll(node) { + if (node === getWindow(node) || !isHTMLElement(node)) { + return getWindowScroll(node); + } else { + return getHTMLElementScroll(node); + } + } + + function isElementScaled(element) { + var rect = element.getBoundingClientRect(); + var scaleX = round(rect.width) / element.offsetWidth || 1; + var scaleY = round(rect.height) / element.offsetHeight || 1; + return scaleX !== 1 || scaleY !== 1; + } // Returns the composite rect of an element relative to its offsetParent. + // Composite means it takes into account transforms as well as layout. + + + function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) { + if (isFixed === void 0) { + isFixed = false; + } + + var isOffsetParentAnElement = isHTMLElement(offsetParent); + var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent); + var documentElement = getDocumentElement(offsetParent); + var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed); + var scroll = { + scrollLeft: 0, + scrollTop: 0 + }; + var offsets = { + x: 0, + y: 0 + }; + + if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { + if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078 + isScrollParent(documentElement)) { + scroll = getNodeScroll(offsetParent); + } + + if (isHTMLElement(offsetParent)) { + offsets = getBoundingClientRect(offsetParent, true); + offsets.x += offsetParent.clientLeft; + offsets.y += offsetParent.clientTop; + } else if (documentElement) { + offsets.x = getWindowScrollBarX(documentElement); + } + } + + return { + x: rect.left + scroll.scrollLeft - offsets.x, + y: rect.top + scroll.scrollTop - offsets.y, + width: rect.width, + height: rect.height + }; + } + + function order(modifiers) { + var map = new Map(); + var visited = new Set(); + var result = []; + modifiers.forEach(function (modifier) { + map.set(modifier.name, modifier); + }); // On visiting object, check for its dependencies and visit them recursively + + function sort(modifier) { + visited.add(modifier.name); + var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []); + requires.forEach(function (dep) { + if (!visited.has(dep)) { + var depModifier = map.get(dep); + + if (depModifier) { + sort(depModifier); + } + } + }); + result.push(modifier); + } + + modifiers.forEach(function (modifier) { + if (!visited.has(modifier.name)) { + // check for visited object + sort(modifier); + } + }); + return result; + } + + function orderModifiers(modifiers) { + // order based on dependencies + var orderedModifiers = order(modifiers); // order based on phase + + return modifierPhases.reduce(function (acc, phase) { + return acc.concat(orderedModifiers.filter(function (modifier) { + return modifier.phase === phase; + })); + }, []); + } + + function debounce(fn) { + var pending; + return function () { + if (!pending) { + pending = new Promise(function (resolve) { + Promise.resolve().then(function () { + pending = undefined; + resolve(fn()); + }); + }); + } + + return pending; + }; + } + + function mergeByName(modifiers) { + var merged = modifiers.reduce(function (merged, current) { + var existing = merged[current.name]; + merged[current.name] = existing ? Object.assign({}, existing, current, { + options: Object.assign({}, existing.options, current.options), + data: Object.assign({}, existing.data, current.data) + }) : current; + return merged; + }, {}); // IE11 does not support Object.values + + return Object.keys(merged).map(function (key) { + return merged[key]; + }); + } + + var DEFAULT_OPTIONS = { + placement: 'bottom', + modifiers: [], + strategy: 'absolute' + }; + + function areValidElements() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return !args.some(function (element) { + return !(element && typeof element.getBoundingClientRect === 'function'); + }); + } + + function popperGenerator(generatorOptions) { + if (generatorOptions === void 0) { + generatorOptions = {}; + } + + var _generatorOptions = generatorOptions, + _generatorOptions$def = _generatorOptions.defaultModifiers, + defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def, + _generatorOptions$def2 = _generatorOptions.defaultOptions, + defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2; + return function createPopper(reference, popper, options) { + if (options === void 0) { + options = defaultOptions; + } + + var state = { + placement: 'bottom', + orderedModifiers: [], + options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions), + modifiersData: {}, + elements: { + reference: reference, + popper: popper + }, + attributes: {}, + styles: {} + }; + var effectCleanupFns = []; + var isDestroyed = false; + var instance = { + state: state, + setOptions: function setOptions(setOptionsAction) { + var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction; + cleanupModifierEffects(); + state.options = Object.assign({}, defaultOptions, state.options, options); + state.scrollParents = { + reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [], + popper: listScrollParents(popper) + }; // Orders the modifiers based on their dependencies and `phase` + // properties + + var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers + + state.orderedModifiers = orderedModifiers.filter(function (m) { + return m.enabled; + }); + runModifierEffects(); + return instance.update(); + }, + // Sync update – it will always be executed, even if not necessary. This + // is useful for low frequency updates where sync behavior simplifies the + // logic. + // For high frequency updates (e.g. `resize` and `scroll` events), always + // prefer the async Popper#update method + forceUpdate: function forceUpdate() { + if (isDestroyed) { + return; + } + + var _state$elements = state.elements, + reference = _state$elements.reference, + popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements + // anymore + + if (!areValidElements(reference, popper)) { + return; + } // Store the reference and popper rects to be read by modifiers + + + state.rects = { + reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'), + popper: getLayoutRect(popper) + }; // Modifiers have the ability to reset the current update cycle. The + // most common use case for this is the `flip` modifier changing the + // placement, which then needs to re-run all the modifiers, because the + // logic was previously ran for the previous placement and is therefore + // stale/incorrect + + state.reset = false; + state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier + // is filled with the initial data specified by the modifier. This means + // it doesn't persist and is fresh on each update. + // To ensure persistent data, use `${name}#persistent` + + state.orderedModifiers.forEach(function (modifier) { + return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); + }); + + for (var index = 0; index < state.orderedModifiers.length; index++) { + if (state.reset === true) { + state.reset = false; + index = -1; + continue; + } + + var _state$orderedModifie = state.orderedModifiers[index], + fn = _state$orderedModifie.fn, + _state$orderedModifie2 = _state$orderedModifie.options, + _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2, + name = _state$orderedModifie.name; + + if (typeof fn === 'function') { + state = fn({ + state: state, + options: _options, + name: name, + instance: instance + }) || state; + } + } + }, + // Async and optimistically optimized update – it will not be executed if + // not necessary (debounced to run at most once-per-tick) + update: debounce(function () { + return new Promise(function (resolve) { + instance.forceUpdate(); + resolve(state); + }); + }), + destroy: function destroy() { + cleanupModifierEffects(); + isDestroyed = true; + } + }; + + if (!areValidElements(reference, popper)) { + return instance; + } + + instance.setOptions(options).then(function (state) { + if (!isDestroyed && options.onFirstUpdate) { + options.onFirstUpdate(state); + } + }); // Modifiers have the ability to execute arbitrary code before the first + // update cycle runs. They will be executed in the same order as the update + // cycle. This is useful when a modifier adds some persistent data that + // other modifiers need to use, but the modifier is run after the dependent + // one. + + function runModifierEffects() { + state.orderedModifiers.forEach(function (_ref) { + var name = _ref.name, + _ref$options = _ref.options, + options = _ref$options === void 0 ? {} : _ref$options, + effect = _ref.effect; + + if (typeof effect === 'function') { + var cleanupFn = effect({ + state: state, + name: name, + instance: instance, + options: options + }); + + var noopFn = function noopFn() {}; + + effectCleanupFns.push(cleanupFn || noopFn); + } + }); + } + + function cleanupModifierEffects() { + effectCleanupFns.forEach(function (fn) { + return fn(); + }); + effectCleanupFns = []; + } + + return instance; + }; + } + var createPopper$2 = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules + + var defaultModifiers$1 = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1]; + var createPopper$1 = /*#__PURE__*/popperGenerator({ + defaultModifiers: defaultModifiers$1 + }); // eslint-disable-next-line import/no-unused-modules + + var defaultModifiers = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1, offset$1, flip$1, preventOverflow$1, arrow$1, hide$1]; + var createPopper = /*#__PURE__*/popperGenerator({ + defaultModifiers: defaultModifiers + }); // eslint-disable-next-line import/no-unused-modules + + const Popper = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ + __proto__: null, + afterMain, + afterRead, + afterWrite, + applyStyles: applyStyles$1, + arrow: arrow$1, + auto, + basePlacements, + beforeMain, + beforeRead, + beforeWrite, + bottom, + clippingParents, + computeStyles: computeStyles$1, + createPopper, + createPopperBase: createPopper$2, + createPopperLite: createPopper$1, + detectOverflow, + end, + eventListeners, + flip: flip$1, + hide: hide$1, + left, + main, + modifierPhases, + offset: offset$1, + placements, + popper, + popperGenerator, + popperOffsets: popperOffsets$1, + preventOverflow: preventOverflow$1, + read, + reference, + right, + start, + top, + variationPlacements, + viewport, + write + }, Symbol.toStringTag, { value: 'Module' })); + + /** + * -------------------------------------------------------------------------- + * Bootstrap dropdown.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$a = 'dropdown'; + const DATA_KEY$6 = 'bs.dropdown'; + const EVENT_KEY$6 = `.${DATA_KEY$6}`; + const DATA_API_KEY$3 = '.data-api'; + const ESCAPE_KEY$2 = 'Escape'; + const TAB_KEY$1 = 'Tab'; + const ARROW_UP_KEY$1 = 'ArrowUp'; + const ARROW_DOWN_KEY$1 = 'ArrowDown'; + const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button + + const EVENT_HIDE$5 = `hide${EVENT_KEY$6}`; + const EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`; + const EVENT_SHOW$5 = `show${EVENT_KEY$6}`; + const EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`; + const EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`; + const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`; + const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`; + const CLASS_NAME_SHOW$6 = 'show'; + const CLASS_NAME_DROPUP = 'dropup'; + const CLASS_NAME_DROPEND = 'dropend'; + const CLASS_NAME_DROPSTART = 'dropstart'; + const CLASS_NAME_DROPUP_CENTER = 'dropup-center'; + const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'; + const SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)'; + const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`; + const SELECTOR_MENU = '.dropdown-menu'; + const SELECTOR_NAVBAR = '.navbar'; + const SELECTOR_NAVBAR_NAV = '.navbar-nav'; + const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'; + const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'; + const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'; + const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'; + const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'; + const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'; + const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'; + const PLACEMENT_TOPCENTER = 'top'; + const PLACEMENT_BOTTOMCENTER = 'bottom'; + const Default$9 = { + autoClose: true, + boundary: 'clippingParents', + display: 'dynamic', + offset: [0, 2], + popperConfig: null, + reference: 'toggle' + }; + const DefaultType$9 = { + autoClose: '(boolean|string)', + boundary: '(string|element)', + display: 'string', + offset: '(array|string|function)', + popperConfig: '(null|object|function)', + reference: '(string|element|object)' + }; + + /** + * Class definition + */ + + class Dropdown extends BaseComponent { + constructor(element, config) { + super(element, config); + this._popper = null; + this._parent = this._element.parentNode; // dropdown wrapper + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ + this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent); + this._inNavbar = this._detectNavbar(); + } + + // Getters + static get Default() { + return Default$9; + } + static get DefaultType() { + return DefaultType$9; + } + static get NAME() { + return NAME$a; + } + + // Public + toggle() { + return this._isShown() ? this.hide() : this.show(); + } + show() { + if (isDisabled(this._element) || this._isShown()) { + return; + } + const relatedTarget = { + relatedTarget: this._element + }; + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget); + if (showEvent.defaultPrevented) { + return; + } + this._createPopper(); + + // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) { + for (const element of [].concat(...document.body.children)) { + EventHandler.on(element, 'mouseover', noop); + } + } + this._element.focus(); + this._element.setAttribute('aria-expanded', true); + this._menu.classList.add(CLASS_NAME_SHOW$6); + this._element.classList.add(CLASS_NAME_SHOW$6); + EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget); + } + hide() { + if (isDisabled(this._element) || !this._isShown()) { + return; + } + const relatedTarget = { + relatedTarget: this._element + }; + this._completeHide(relatedTarget); + } + dispose() { + if (this._popper) { + this._popper.destroy(); + } + super.dispose(); + } + update() { + this._inNavbar = this._detectNavbar(); + if (this._popper) { + this._popper.update(); + } + } + + // Private + _completeHide(relatedTarget) { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget); + if (hideEvent.defaultPrevented) { + return; + } + + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + for (const element of [].concat(...document.body.children)) { + EventHandler.off(element, 'mouseover', noop); + } + } + if (this._popper) { + this._popper.destroy(); + } + this._menu.classList.remove(CLASS_NAME_SHOW$6); + this._element.classList.remove(CLASS_NAME_SHOW$6); + this._element.setAttribute('aria-expanded', 'false'); + Manipulator.removeDataAttribute(this._menu, 'popper'); + EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget); + + // Explicitly return focus to the trigger element + this._element.focus(); + } + _getConfig(config) { + config = super._getConfig(config); + if (typeof config.reference === 'object' && !isElement$1(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') { + // Popper virtual elements require a getBoundingClientRect method + throw new TypeError(`${NAME$a.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`); + } + return config; + } + _createPopper() { + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)'); + } + let referenceElement = this._element; + if (this._config.reference === 'parent') { + referenceElement = this._parent; + } else if (isElement$1(this._config.reference)) { + referenceElement = getElement(this._config.reference); + } else if (typeof this._config.reference === 'object') { + referenceElement = this._config.reference; + } + const popperConfig = this._getPopperConfig(); + this._popper = createPopper(referenceElement, this._menu, popperConfig); + } + _isShown() { + return this._menu.classList.contains(CLASS_NAME_SHOW$6); + } + _getPlacement() { + const parentDropdown = this._parent; + if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { + return PLACEMENT_RIGHT; + } + if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { + return PLACEMENT_LEFT; + } + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { + return PLACEMENT_TOPCENTER; + } + if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { + return PLACEMENT_BOTTOMCENTER; + } + + // We need to trim the value because custom properties can also include spaces + const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'; + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) { + return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP; + } + return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM; + } + _detectNavbar() { + return this._element.closest(SELECTOR_NAVBAR) !== null; + } + _getOffset() { + const { + offset + } = this._config; + if (typeof offset === 'string') { + return offset.split(',').map(value => Number.parseInt(value, 10)); + } + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element); + } + return offset; + } + _getPopperConfig() { + const defaultBsPopperConfig = { + placement: this._getPlacement(), + modifiers: [{ + name: 'preventOverflow', + options: { + boundary: this._config.boundary + } + }, { + name: 'offset', + options: { + offset: this._getOffset() + } + }] + }; + + // Disable Popper if we have a static display or Dropdown is in Navbar + if (this._inNavbar || this._config.display === 'static') { + Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove + defaultBsPopperConfig.modifiers = [{ + name: 'applyStyles', + enabled: false + }]; + } + return { + ...defaultBsPopperConfig, + ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) + }; + } + _selectMenuItem({ + key, + target + }) { + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element)); + if (!items.length) { + return; + } + + // if target isn't included in items (e.g. when expanding the dropdown) + // allow cycling to get the last item in case key equals ARROW_UP_KEY + getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus(); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Dropdown.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + }); + } + static clearMenus(event) { + if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) { + return; + } + const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN); + for (const toggle of openToggles) { + const context = Dropdown.getInstance(toggle); + if (!context || context._config.autoClose === false) { + continue; + } + const composedPath = event.composedPath(); + const isMenuTarget = composedPath.includes(context._menu); + if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) { + continue; + } + + // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) { + continue; + } + const relatedTarget = { + relatedTarget: context._element + }; + if (event.type === 'click') { + relatedTarget.clickEvent = event; + } + context._completeHide(relatedTarget); + } + } + static dataApiKeydownHandler(event) { + // If not an UP | DOWN | ESCAPE key => not a dropdown command + // If input/textarea && if key is other than ESCAPE => not a dropdown command + + const isInput = /input|textarea/i.test(event.target.tagName); + const isEscapeEvent = event.key === ESCAPE_KEY$2; + const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key); + if (!isUpOrDownEvent && !isEscapeEvent) { + return; + } + if (isInput && !isEscapeEvent) { + return; + } + event.preventDefault(); + + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ + const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode); + const instance = Dropdown.getOrCreateInstance(getToggleButton); + if (isUpOrDownEvent) { + event.stopPropagation(); + instance.show(); + instance._selectMenuItem(event); + return; + } + if (instance._isShown()) { + // else is escape and we check if it is shown + event.stopPropagation(); + instance.hide(); + getToggleButton.focus(); + } + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler); + EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler); + EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus); + EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus); + EventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) { + event.preventDefault(); + Dropdown.getOrCreateInstance(this).toggle(); + }); + + /** + * jQuery + */ + + defineJQueryPlugin(Dropdown); + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/backdrop.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$9 = 'backdrop'; + const CLASS_NAME_FADE$4 = 'fade'; + const CLASS_NAME_SHOW$5 = 'show'; + const EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`; + const Default$8 = { + className: 'modal-backdrop', + clickCallback: null, + isAnimated: false, + isVisible: true, + // if false, we use the backdrop helper without adding any element to the dom + rootElement: 'body' // give the choice to place backdrop under different elements + }; + const DefaultType$8 = { + className: 'string', + clickCallback: '(function|null)', + isAnimated: 'boolean', + isVisible: 'boolean', + rootElement: '(element|string)' + }; + + /** + * Class definition + */ + + class Backdrop extends Config { + constructor(config) { + super(); + this._config = this._getConfig(config); + this._isAppended = false; + this._element = null; + } + + // Getters + static get Default() { + return Default$8; + } + static get DefaultType() { + return DefaultType$8; + } + static get NAME() { + return NAME$9; + } + + // Public + show(callback) { + if (!this._config.isVisible) { + execute(callback); + return; + } + this._append(); + const element = this._getElement(); + if (this._config.isAnimated) { + reflow(element); + } + element.classList.add(CLASS_NAME_SHOW$5); + this._emulateAnimation(() => { + execute(callback); + }); + } + hide(callback) { + if (!this._config.isVisible) { + execute(callback); + return; + } + this._getElement().classList.remove(CLASS_NAME_SHOW$5); + this._emulateAnimation(() => { + this.dispose(); + execute(callback); + }); + } + dispose() { + if (!this._isAppended) { + return; + } + EventHandler.off(this._element, EVENT_MOUSEDOWN); + this._element.remove(); + this._isAppended = false; + } + + // Private + _getElement() { + if (!this._element) { + const backdrop = document.createElement('div'); + backdrop.className = this._config.className; + if (this._config.isAnimated) { + backdrop.classList.add(CLASS_NAME_FADE$4); + } + this._element = backdrop; + } + return this._element; + } + _configAfterMerge(config) { + // use getElement() with the default "body" to get a fresh Element on each instantiation + config.rootElement = getElement(config.rootElement); + return config; + } + _append() { + if (this._isAppended) { + return; + } + const element = this._getElement(); + this._config.rootElement.append(element); + EventHandler.on(element, EVENT_MOUSEDOWN, () => { + execute(this._config.clickCallback); + }); + this._isAppended = true; + } + _emulateAnimation(callback) { + executeAfterTransition(callback, this._getElement(), this._config.isAnimated); + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/focustrap.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$8 = 'focustrap'; + const DATA_KEY$5 = 'bs.focustrap'; + const EVENT_KEY$5 = `.${DATA_KEY$5}`; + const EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`; + const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`; + const TAB_KEY = 'Tab'; + const TAB_NAV_FORWARD = 'forward'; + const TAB_NAV_BACKWARD = 'backward'; + const Default$7 = { + autofocus: true, + trapElement: null // The element to trap focus inside of + }; + const DefaultType$7 = { + autofocus: 'boolean', + trapElement: 'element' + }; + + /** + * Class definition + */ + + class FocusTrap extends Config { + constructor(config) { + super(); + this._config = this._getConfig(config); + this._isActive = false; + this._lastTabNavDirection = null; + } + + // Getters + static get Default() { + return Default$7; + } + static get DefaultType() { + return DefaultType$7; + } + static get NAME() { + return NAME$8; + } + + // Public + activate() { + if (this._isActive) { + return; + } + if (this._config.autofocus) { + this._config.trapElement.focus(); + } + EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop + EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event)); + EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)); + this._isActive = true; + } + deactivate() { + if (!this._isActive) { + return; + } + this._isActive = false; + EventHandler.off(document, EVENT_KEY$5); + } + + // Private + _handleFocusin(event) { + const { + trapElement + } = this._config; + if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) { + return; + } + const elements = SelectorEngine.focusableChildren(trapElement); + if (elements.length === 0) { + trapElement.focus(); + } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { + elements[elements.length - 1].focus(); + } else { + elements[0].focus(); + } + } + _handleKeydown(event) { + if (event.key !== TAB_KEY) { + return; + } + this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/scrollBar.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; + const SELECTOR_STICKY_CONTENT = '.sticky-top'; + const PROPERTY_PADDING = 'padding-right'; + const PROPERTY_MARGIN = 'margin-right'; + + /** + * Class definition + */ + + class ScrollBarHelper { + constructor() { + this._element = document.body; + } + + // Public + getWidth() { + // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes + const documentWidth = document.documentElement.clientWidth; + return Math.abs(window.innerWidth - documentWidth); + } + hide() { + const width = this.getWidth(); + this._disableOverFlow(); + // give padding to element to balance the hidden scrollbar width + this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); + // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth + this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width); + this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width); + } + reset() { + this._resetElementAttributes(this._element, 'overflow'); + this._resetElementAttributes(this._element, PROPERTY_PADDING); + this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING); + this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN); + } + isOverflowing() { + return this.getWidth() > 0; + } + + // Private + _disableOverFlow() { + this._saveInitialAttribute(this._element, 'overflow'); + this._element.style.overflow = 'hidden'; + } + _setElementAttributes(selector, styleProperty, callback) { + const scrollbarWidth = this.getWidth(); + const manipulationCallBack = element => { + if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { + return; + } + this._saveInitialAttribute(element, styleProperty); + const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty); + element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`); + }; + this._applyManipulationCallback(selector, manipulationCallBack); + } + _saveInitialAttribute(element, styleProperty) { + const actualValue = element.style.getPropertyValue(styleProperty); + if (actualValue) { + Manipulator.setDataAttribute(element, styleProperty, actualValue); + } + } + _resetElementAttributes(selector, styleProperty) { + const manipulationCallBack = element => { + const value = Manipulator.getDataAttribute(element, styleProperty); + // We only want to remove the property if the value is `null`; the value can also be zero + if (value === null) { + element.style.removeProperty(styleProperty); + return; + } + Manipulator.removeDataAttribute(element, styleProperty); + element.style.setProperty(styleProperty, value); + }; + this._applyManipulationCallback(selector, manipulationCallBack); + } + _applyManipulationCallback(selector, callBack) { + if (isElement$1(selector)) { + callBack(selector); + return; + } + for (const sel of SelectorEngine.find(selector, this._element)) { + callBack(sel); + } + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap modal.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$7 = 'modal'; + const DATA_KEY$4 = 'bs.modal'; + const EVENT_KEY$4 = `.${DATA_KEY$4}`; + const DATA_API_KEY$2 = '.data-api'; + const ESCAPE_KEY$1 = 'Escape'; + const EVENT_HIDE$4 = `hide${EVENT_KEY$4}`; + const EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`; + const EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`; + const EVENT_SHOW$4 = `show${EVENT_KEY$4}`; + const EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`; + const EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`; + const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`; + const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`; + const EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`; + const EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`; + const CLASS_NAME_OPEN = 'modal-open'; + const CLASS_NAME_FADE$3 = 'fade'; + const CLASS_NAME_SHOW$4 = 'show'; + const CLASS_NAME_STATIC = 'modal-static'; + const OPEN_SELECTOR$1 = '.modal.show'; + const SELECTOR_DIALOG = '.modal-dialog'; + const SELECTOR_MODAL_BODY = '.modal-body'; + const SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle="modal"]'; + const Default$6 = { + backdrop: true, + focus: true, + keyboard: true + }; + const DefaultType$6 = { + backdrop: '(boolean|string)', + focus: 'boolean', + keyboard: 'boolean' + }; + + /** + * Class definition + */ + + class Modal extends BaseComponent { + constructor(element, config) { + super(element, config); + this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element); + this._backdrop = this._initializeBackDrop(); + this._focustrap = this._initializeFocusTrap(); + this._isShown = false; + this._isTransitioning = false; + this._scrollBar = new ScrollBarHelper(); + this._addEventListeners(); + } + + // Getters + static get Default() { + return Default$6; + } + static get DefaultType() { + return DefaultType$6; + } + static get NAME() { + return NAME$7; + } + + // Public + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget); + } + show(relatedTarget) { + if (this._isShown || this._isTransitioning) { + return; + } + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, { + relatedTarget + }); + if (showEvent.defaultPrevented) { + return; + } + this._isShown = true; + this._isTransitioning = true; + this._scrollBar.hide(); + document.body.classList.add(CLASS_NAME_OPEN); + this._adjustDialog(); + this._backdrop.show(() => this._showElement(relatedTarget)); + } + hide() { + if (!this._isShown || this._isTransitioning) { + return; + } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4); + if (hideEvent.defaultPrevented) { + return; + } + this._isShown = false; + this._isTransitioning = true; + this._focustrap.deactivate(); + this._element.classList.remove(CLASS_NAME_SHOW$4); + this._queueCallback(() => this._hideModal(), this._element, this._isAnimated()); + } + dispose() { + EventHandler.off(window, EVENT_KEY$4); + EventHandler.off(this._dialog, EVENT_KEY$4); + this._backdrop.dispose(); + this._focustrap.deactivate(); + super.dispose(); + } + handleUpdate() { + this._adjustDialog(); + } + + // Private + _initializeBackDrop() { + return new Backdrop({ + isVisible: Boolean(this._config.backdrop), + // 'static' option will be translated to true, and booleans will keep their value, + isAnimated: this._isAnimated() + }); + } + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }); + } + _showElement(relatedTarget) { + // try to append dynamic modal + if (!document.body.contains(this._element)) { + document.body.append(this._element); + } + this._element.style.display = 'block'; + this._element.removeAttribute('aria-hidden'); + this._element.setAttribute('aria-modal', true); + this._element.setAttribute('role', 'dialog'); + this._element.scrollTop = 0; + const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog); + if (modalBody) { + modalBody.scrollTop = 0; + } + reflow(this._element); + this._element.classList.add(CLASS_NAME_SHOW$4); + const transitionComplete = () => { + if (this._config.focus) { + this._focustrap.activate(); + } + this._isTransitioning = false; + EventHandler.trigger(this._element, EVENT_SHOWN$4, { + relatedTarget + }); + }; + this._queueCallback(transitionComplete, this._dialog, this._isAnimated()); + } + _addEventListeners() { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => { + if (event.key !== ESCAPE_KEY$1) { + return; + } + if (this._config.keyboard) { + this.hide(); + return; + } + this._triggerBackdropTransition(); + }); + EventHandler.on(window, EVENT_RESIZE$1, () => { + if (this._isShown && !this._isTransitioning) { + this._adjustDialog(); + } + }); + EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => { + // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks + EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => { + if (this._element !== event.target || this._element !== event2.target) { + return; + } + if (this._config.backdrop === 'static') { + this._triggerBackdropTransition(); + return; + } + if (this._config.backdrop) { + this.hide(); + } + }); + }); + } + _hideModal() { + this._element.style.display = 'none'; + this._element.setAttribute('aria-hidden', true); + this._element.removeAttribute('aria-modal'); + this._element.removeAttribute('role'); + this._isTransitioning = false; + this._backdrop.hide(() => { + document.body.classList.remove(CLASS_NAME_OPEN); + this._resetAdjustments(); + this._scrollBar.reset(); + EventHandler.trigger(this._element, EVENT_HIDDEN$4); + }); + } + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_FADE$3); + } + _triggerBackdropTransition() { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1); + if (hideEvent.defaultPrevented) { + return; + } + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; + const initialOverflowY = this._element.style.overflowY; + // return if the following background transition hasn't yet completed + if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) { + return; + } + if (!isModalOverflowing) { + this._element.style.overflowY = 'hidden'; + } + this._element.classList.add(CLASS_NAME_STATIC); + this._queueCallback(() => { + this._element.classList.remove(CLASS_NAME_STATIC); + this._queueCallback(() => { + this._element.style.overflowY = initialOverflowY; + }, this._dialog); + }, this._dialog); + this._element.focus(); + } + + /** + * The following methods are used to handle overflowing modals + */ + + _adjustDialog() { + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; + const scrollbarWidth = this._scrollBar.getWidth(); + const isBodyOverflowing = scrollbarWidth > 0; + if (isBodyOverflowing && !isModalOverflowing) { + const property = isRTL() ? 'paddingLeft' : 'paddingRight'; + this._element.style[property] = `${scrollbarWidth}px`; + } + if (!isBodyOverflowing && isModalOverflowing) { + const property = isRTL() ? 'paddingRight' : 'paddingLeft'; + this._element.style[property] = `${scrollbarWidth}px`; + } + } + _resetAdjustments() { + this._element.style.paddingLeft = ''; + this._element.style.paddingRight = ''; + } + + // Static + static jQueryInterface(config, relatedTarget) { + return this.each(function () { + const data = Modal.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](relatedTarget); + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) { + const target = SelectorEngine.getElementFromSelector(this); + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault(); + } + EventHandler.one(target, EVENT_SHOW$4, showEvent => { + if (showEvent.defaultPrevented) { + // only register focus restorer if modal will actually get shown + return; + } + EventHandler.one(target, EVENT_HIDDEN$4, () => { + if (isVisible(this)) { + this.focus(); + } + }); + }); + + // avoid conflict when clicking modal toggler while another one is open + const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1); + if (alreadyOpen) { + Modal.getInstance(alreadyOpen).hide(); + } + const data = Modal.getOrCreateInstance(target); + data.toggle(this); + }); + enableDismissTrigger(Modal); + + /** + * jQuery + */ + + defineJQueryPlugin(Modal); + + /** + * -------------------------------------------------------------------------- + * Bootstrap offcanvas.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$6 = 'offcanvas'; + const DATA_KEY$3 = 'bs.offcanvas'; + const EVENT_KEY$3 = `.${DATA_KEY$3}`; + const DATA_API_KEY$1 = '.data-api'; + const EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`; + const ESCAPE_KEY = 'Escape'; + const CLASS_NAME_SHOW$3 = 'show'; + const CLASS_NAME_SHOWING$1 = 'showing'; + const CLASS_NAME_HIDING = 'hiding'; + const CLASS_NAME_BACKDROP = 'offcanvas-backdrop'; + const OPEN_SELECTOR = '.offcanvas.show'; + const EVENT_SHOW$3 = `show${EVENT_KEY$3}`; + const EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`; + const EVENT_HIDE$3 = `hide${EVENT_KEY$3}`; + const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`; + const EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`; + const EVENT_RESIZE = `resize${EVENT_KEY$3}`; + const EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`; + const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`; + const SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle="offcanvas"]'; + const Default$5 = { + backdrop: true, + keyboard: true, + scroll: false + }; + const DefaultType$5 = { + backdrop: '(boolean|string)', + keyboard: 'boolean', + scroll: 'boolean' + }; + + /** + * Class definition + */ + + class Offcanvas extends BaseComponent { + constructor(element, config) { + super(element, config); + this._isShown = false; + this._backdrop = this._initializeBackDrop(); + this._focustrap = this._initializeFocusTrap(); + this._addEventListeners(); + } + + // Getters + static get Default() { + return Default$5; + } + static get DefaultType() { + return DefaultType$5; + } + static get NAME() { + return NAME$6; + } + + // Public + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget); + } + show(relatedTarget) { + if (this._isShown) { + return; + } + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, { + relatedTarget + }); + if (showEvent.defaultPrevented) { + return; + } + this._isShown = true; + this._backdrop.show(); + if (!this._config.scroll) { + new ScrollBarHelper().hide(); + } + this._element.setAttribute('aria-modal', true); + this._element.setAttribute('role', 'dialog'); + this._element.classList.add(CLASS_NAME_SHOWING$1); + const completeCallBack = () => { + if (!this._config.scroll || this._config.backdrop) { + this._focustrap.activate(); + } + this._element.classList.add(CLASS_NAME_SHOW$3); + this._element.classList.remove(CLASS_NAME_SHOWING$1); + EventHandler.trigger(this._element, EVENT_SHOWN$3, { + relatedTarget + }); + }; + this._queueCallback(completeCallBack, this._element, true); + } + hide() { + if (!this._isShown) { + return; + } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3); + if (hideEvent.defaultPrevented) { + return; + } + this._focustrap.deactivate(); + this._element.blur(); + this._isShown = false; + this._element.classList.add(CLASS_NAME_HIDING); + this._backdrop.hide(); + const completeCallback = () => { + this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING); + this._element.removeAttribute('aria-modal'); + this._element.removeAttribute('role'); + if (!this._config.scroll) { + new ScrollBarHelper().reset(); + } + EventHandler.trigger(this._element, EVENT_HIDDEN$3); + }; + this._queueCallback(completeCallback, this._element, true); + } + dispose() { + this._backdrop.dispose(); + this._focustrap.deactivate(); + super.dispose(); + } + + // Private + _initializeBackDrop() { + const clickCallback = () => { + if (this._config.backdrop === 'static') { + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); + return; + } + this.hide(); + }; + + // 'static' option will be translated to true, and booleans will keep their value + const isVisible = Boolean(this._config.backdrop); + return new Backdrop({ + className: CLASS_NAME_BACKDROP, + isVisible, + isAnimated: true, + rootElement: this._element.parentNode, + clickCallback: isVisible ? clickCallback : null + }); + } + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }); + } + _addEventListeners() { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { + if (event.key !== ESCAPE_KEY) { + return; + } + if (this._config.keyboard) { + this.hide(); + return; + } + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); + }); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Offcanvas.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`); + } + data[config](this); + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) { + const target = SelectorEngine.getElementFromSelector(this); + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault(); + } + if (isDisabled(this)) { + return; + } + EventHandler.one(target, EVENT_HIDDEN$3, () => { + // focus on trigger when it is closed + if (isVisible(this)) { + this.focus(); + } + }); + + // avoid conflict when clicking a toggler of an offcanvas, while another is open + const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR); + if (alreadyOpen && alreadyOpen !== target) { + Offcanvas.getInstance(alreadyOpen).hide(); + } + const data = Offcanvas.getOrCreateInstance(target); + data.toggle(this); + }); + EventHandler.on(window, EVENT_LOAD_DATA_API$2, () => { + for (const selector of SelectorEngine.find(OPEN_SELECTOR)) { + Offcanvas.getOrCreateInstance(selector).show(); + } + }); + EventHandler.on(window, EVENT_RESIZE, () => { + for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) { + if (getComputedStyle(element).position !== 'fixed') { + Offcanvas.getOrCreateInstance(element).hide(); + } + } + }); + enableDismissTrigger(Offcanvas); + + /** + * jQuery + */ + + defineJQueryPlugin(Offcanvas); + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/sanitizer.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + // js-docs-start allow-list + const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; + const DefaultAllowlist = { + // Global attributes allowed on any supplied element below. + '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], + a: ['target', 'href', 'title', 'rel'], + area: [], + b: [], + br: [], + col: [], + code: [], + dd: [], + div: [], + dl: [], + dt: [], + em: [], + hr: [], + h1: [], + h2: [], + h3: [], + h4: [], + h5: [], + h6: [], + i: [], + img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], + li: [], + ol: [], + p: [], + pre: [], + s: [], + small: [], + span: [], + sub: [], + sup: [], + strong: [], + u: [], + ul: [] + }; + // js-docs-end allow-list + + const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); + + /** + * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation + * contexts. + * + * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 + */ + // eslint-disable-next-line unicorn/better-regex + const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i; + const allowedAttribute = (attribute, allowedAttributeList) => { + const attributeName = attribute.nodeName.toLowerCase(); + if (allowedAttributeList.includes(attributeName)) { + if (uriAttributes.has(attributeName)) { + return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)); + } + return true; + } + + // Check if a regular expression validates the attribute. + return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName)); + }; + function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { + if (!unsafeHtml.length) { + return unsafeHtml; + } + if (sanitizeFunction && typeof sanitizeFunction === 'function') { + return sanitizeFunction(unsafeHtml); + } + const domParser = new window.DOMParser(); + const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); + const elements = [].concat(...createdDocument.body.querySelectorAll('*')); + for (const element of elements) { + const elementName = element.nodeName.toLowerCase(); + if (!Object.keys(allowList).includes(elementName)) { + element.remove(); + continue; + } + const attributeList = [].concat(...element.attributes); + const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); + for (const attribute of attributeList) { + if (!allowedAttribute(attribute, allowedAttributes)) { + element.removeAttribute(attribute.nodeName); + } + } + } + return createdDocument.body.innerHTML; + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/template-factory.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$5 = 'TemplateFactory'; + const Default$4 = { + allowList: DefaultAllowlist, + content: {}, + // { selector : text , selector2 : text2 , } + extraClass: '', + html: false, + sanitize: true, + sanitizeFn: null, + template: '
    ' + }; + const DefaultType$4 = { + allowList: 'object', + content: 'object', + extraClass: '(string|function)', + html: 'boolean', + sanitize: 'boolean', + sanitizeFn: '(null|function)', + template: 'string' + }; + const DefaultContentType = { + entry: '(string|element|function|null)', + selector: '(string|element)' + }; + + /** + * Class definition + */ + + class TemplateFactory extends Config { + constructor(config) { + super(); + this._config = this._getConfig(config); + } + + // Getters + static get Default() { + return Default$4; + } + static get DefaultType() { + return DefaultType$4; + } + static get NAME() { + return NAME$5; + } + + // Public + getContent() { + return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean); + } + hasContent() { + return this.getContent().length > 0; + } + changeContent(content) { + this._checkContent(content); + this._config.content = { + ...this._config.content, + ...content + }; + return this; + } + toHtml() { + const templateWrapper = document.createElement('div'); + templateWrapper.innerHTML = this._maybeSanitize(this._config.template); + for (const [selector, text] of Object.entries(this._config.content)) { + this._setContent(templateWrapper, text, selector); + } + const template = templateWrapper.children[0]; + const extraClass = this._resolvePossibleFunction(this._config.extraClass); + if (extraClass) { + template.classList.add(...extraClass.split(' ')); + } + return template; + } + + // Private + _typeCheckConfig(config) { + super._typeCheckConfig(config); + this._checkContent(config.content); + } + _checkContent(arg) { + for (const [selector, content] of Object.entries(arg)) { + super._typeCheckConfig({ + selector, + entry: content + }, DefaultContentType); + } + } + _setContent(template, content, selector) { + const templateElement = SelectorEngine.findOne(selector, template); + if (!templateElement) { + return; + } + content = this._resolvePossibleFunction(content); + if (!content) { + templateElement.remove(); + return; + } + if (isElement$1(content)) { + this._putElementInTemplate(getElement(content), templateElement); + return; + } + if (this._config.html) { + templateElement.innerHTML = this._maybeSanitize(content); + return; + } + templateElement.textContent = content; + } + _maybeSanitize(arg) { + return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg; + } + _resolvePossibleFunction(arg) { + return execute(arg, [undefined, this]); + } + _putElementInTemplate(element, templateElement) { + if (this._config.html) { + templateElement.innerHTML = ''; + templateElement.append(element); + return; + } + templateElement.textContent = element.textContent; + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap tooltip.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$4 = 'tooltip'; + const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']); + const CLASS_NAME_FADE$2 = 'fade'; + const CLASS_NAME_MODAL = 'modal'; + const CLASS_NAME_SHOW$2 = 'show'; + const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'; + const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`; + const EVENT_MODAL_HIDE = 'hide.bs.modal'; + const TRIGGER_HOVER = 'hover'; + const TRIGGER_FOCUS = 'focus'; + const TRIGGER_CLICK = 'click'; + const TRIGGER_MANUAL = 'manual'; + const EVENT_HIDE$2 = 'hide'; + const EVENT_HIDDEN$2 = 'hidden'; + const EVENT_SHOW$2 = 'show'; + const EVENT_SHOWN$2 = 'shown'; + const EVENT_INSERTED = 'inserted'; + const EVENT_CLICK$1 = 'click'; + const EVENT_FOCUSIN$1 = 'focusin'; + const EVENT_FOCUSOUT$1 = 'focusout'; + const EVENT_MOUSEENTER = 'mouseenter'; + const EVENT_MOUSELEAVE = 'mouseleave'; + const AttachmentMap = { + AUTO: 'auto', + TOP: 'top', + RIGHT: isRTL() ? 'left' : 'right', + BOTTOM: 'bottom', + LEFT: isRTL() ? 'right' : 'left' + }; + const Default$3 = { + allowList: DefaultAllowlist, + animation: true, + boundary: 'clippingParents', + container: false, + customClass: '', + delay: 0, + fallbackPlacements: ['top', 'right', 'bottom', 'left'], + html: false, + offset: [0, 6], + placement: 'top', + popperConfig: null, + sanitize: true, + sanitizeFn: null, + selector: false, + template: '', + title: '', + trigger: 'hover focus' + }; + const DefaultType$3 = { + allowList: 'object', + animation: 'boolean', + boundary: '(string|element)', + container: '(string|element|boolean)', + customClass: '(string|function)', + delay: '(number|object)', + fallbackPlacements: 'array', + html: 'boolean', + offset: '(array|string|function)', + placement: '(string|function)', + popperConfig: '(null|object|function)', + sanitize: 'boolean', + sanitizeFn: '(null|function)', + selector: '(string|boolean)', + template: 'string', + title: '(string|element|function)', + trigger: 'string' + }; + + /** + * Class definition + */ + + class Tooltip extends BaseComponent { + constructor(element, config) { + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)'); + } + super(element, config); + + // Private + this._isEnabled = true; + this._timeout = 0; + this._isHovered = null; + this._activeTrigger = {}; + this._popper = null; + this._templateFactory = null; + this._newContent = null; + + // Protected + this.tip = null; + this._setListeners(); + if (!this._config.selector) { + this._fixTitle(); + } + } + + // Getters + static get Default() { + return Default$3; + } + static get DefaultType() { + return DefaultType$3; + } + static get NAME() { + return NAME$4; + } + + // Public + enable() { + this._isEnabled = true; + } + disable() { + this._isEnabled = false; + } + toggleEnabled() { + this._isEnabled = !this._isEnabled; + } + toggle() { + if (!this._isEnabled) { + return; + } + if (this._isShown()) { + this._leave(); + return; + } + this._enter(); + } + dispose() { + clearTimeout(this._timeout); + EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); + if (this._element.getAttribute('data-bs-original-title')) { + this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title')); + } + this._disposePopper(); + super.dispose(); + } + show() { + if (this._element.style.display === 'none') { + throw new Error('Please use show on visible elements'); + } + if (!(this._isWithContent() && this._isEnabled)) { + return; + } + const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2)); + const shadowRoot = findShadowRoot(this._element); + const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element); + if (showEvent.defaultPrevented || !isInTheDom) { + return; + } + + // TODO: v6 remove this or make it optional + this._disposePopper(); + const tip = this._getTipElement(); + this._element.setAttribute('aria-describedby', tip.getAttribute('id')); + const { + container + } = this._config; + if (!this._element.ownerDocument.documentElement.contains(this.tip)) { + container.append(tip); + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED)); + } + this._popper = this._createPopper(tip); + tip.classList.add(CLASS_NAME_SHOW$2); + + // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + if ('ontouchstart' in document.documentElement) { + for (const element of [].concat(...document.body.children)) { + EventHandler.on(element, 'mouseover', noop); + } + } + const complete = () => { + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2)); + if (this._isHovered === false) { + this._leave(); + } + this._isHovered = false; + }; + this._queueCallback(complete, this.tip, this._isAnimated()); + } + hide() { + if (!this._isShown()) { + return; + } + const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2)); + if (hideEvent.defaultPrevented) { + return; + } + const tip = this._getTipElement(); + tip.classList.remove(CLASS_NAME_SHOW$2); + + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + for (const element of [].concat(...document.body.children)) { + EventHandler.off(element, 'mouseover', noop); + } + } + this._activeTrigger[TRIGGER_CLICK] = false; + this._activeTrigger[TRIGGER_FOCUS] = false; + this._activeTrigger[TRIGGER_HOVER] = false; + this._isHovered = null; // it is a trick to support manual triggering + + const complete = () => { + if (this._isWithActiveTrigger()) { + return; + } + if (!this._isHovered) { + this._disposePopper(); + } + this._element.removeAttribute('aria-describedby'); + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2)); + }; + this._queueCallback(complete, this.tip, this._isAnimated()); + } + update() { + if (this._popper) { + this._popper.update(); + } + } + + // Protected + _isWithContent() { + return Boolean(this._getTitle()); + } + _getTipElement() { + if (!this.tip) { + this.tip = this._createTipElement(this._newContent || this._getContentForTemplate()); + } + return this.tip; + } + _createTipElement(content) { + const tip = this._getTemplateFactory(content).toHtml(); + + // TODO: remove this check in v6 + if (!tip) { + return null; + } + tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); + // TODO: v6 the following can be achieved with CSS only + tip.classList.add(`bs-${this.constructor.NAME}-auto`); + const tipId = getUID(this.constructor.NAME).toString(); + tip.setAttribute('id', tipId); + if (this._isAnimated()) { + tip.classList.add(CLASS_NAME_FADE$2); + } + return tip; + } + setContent(content) { + this._newContent = content; + if (this._isShown()) { + this._disposePopper(); + this.show(); + } + } + _getTemplateFactory(content) { + if (this._templateFactory) { + this._templateFactory.changeContent(content); + } else { + this._templateFactory = new TemplateFactory({ + ...this._config, + // the `content` var has to be after `this._config` + // to override config.content in case of popover + content, + extraClass: this._resolvePossibleFunction(this._config.customClass) + }); + } + return this._templateFactory; + } + _getContentForTemplate() { + return { + [SELECTOR_TOOLTIP_INNER]: this._getTitle() + }; + } + _getTitle() { + return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title'); + } + + // Private + _initializeOnDelegatedTarget(event) { + return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()); + } + _isAnimated() { + return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2); + } + _isShown() { + return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2); + } + _createPopper(tip) { + const placement = execute(this._config.placement, [this, tip, this._element]); + const attachment = AttachmentMap[placement.toUpperCase()]; + return createPopper(this._element, tip, this._getPopperConfig(attachment)); + } + _getOffset() { + const { + offset + } = this._config; + if (typeof offset === 'string') { + return offset.split(',').map(value => Number.parseInt(value, 10)); + } + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element); + } + return offset; + } + _resolvePossibleFunction(arg) { + return execute(arg, [this._element, this._element]); + } + _getPopperConfig(attachment) { + const defaultBsPopperConfig = { + placement: attachment, + modifiers: [{ + name: 'flip', + options: { + fallbackPlacements: this._config.fallbackPlacements + } + }, { + name: 'offset', + options: { + offset: this._getOffset() + } + }, { + name: 'preventOverflow', + options: { + boundary: this._config.boundary + } + }, { + name: 'arrow', + options: { + element: `.${this.constructor.NAME}-arrow` + } + }, { + name: 'preSetPlacement', + enabled: true, + phase: 'beforeMain', + fn: data => { + // Pre-set Popper's placement attribute in order to read the arrow sizes properly. + // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement + this._getTipElement().setAttribute('data-popper-placement', data.state.placement); + } + }] + }; + return { + ...defaultBsPopperConfig, + ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) + }; + } + _setListeners() { + const triggers = this._config.trigger.split(' '); + for (const trigger of triggers) { + if (trigger === 'click') { + EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => { + const context = this._initializeOnDelegatedTarget(event); + context.toggle(); + }); + } else if (trigger !== TRIGGER_MANUAL) { + const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1); + const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1); + EventHandler.on(this._element, eventIn, this._config.selector, event => { + const context = this._initializeOnDelegatedTarget(event); + context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true; + context._enter(); + }); + EventHandler.on(this._element, eventOut, this._config.selector, event => { + const context = this._initializeOnDelegatedTarget(event); + context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget); + context._leave(); + }); + } + } + this._hideModalHandler = () => { + if (this._element) { + this.hide(); + } + }; + EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); + } + _fixTitle() { + const title = this._element.getAttribute('title'); + if (!title) { + return; + } + if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) { + this._element.setAttribute('aria-label', title); + } + this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility + this._element.removeAttribute('title'); + } + _enter() { + if (this._isShown() || this._isHovered) { + this._isHovered = true; + return; + } + this._isHovered = true; + this._setTimeout(() => { + if (this._isHovered) { + this.show(); + } + }, this._config.delay.show); + } + _leave() { + if (this._isWithActiveTrigger()) { + return; + } + this._isHovered = false; + this._setTimeout(() => { + if (!this._isHovered) { + this.hide(); + } + }, this._config.delay.hide); + } + _setTimeout(handler, timeout) { + clearTimeout(this._timeout); + this._timeout = setTimeout(handler, timeout); + } + _isWithActiveTrigger() { + return Object.values(this._activeTrigger).includes(true); + } + _getConfig(config) { + const dataAttributes = Manipulator.getDataAttributes(this._element); + for (const dataAttribute of Object.keys(dataAttributes)) { + if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) { + delete dataAttributes[dataAttribute]; + } + } + config = { + ...dataAttributes, + ...(typeof config === 'object' && config ? config : {}) + }; + config = this._mergeConfigObj(config); + config = this._configAfterMerge(config); + this._typeCheckConfig(config); + return config; + } + _configAfterMerge(config) { + config.container = config.container === false ? document.body : getElement(config.container); + if (typeof config.delay === 'number') { + config.delay = { + show: config.delay, + hide: config.delay + }; + } + if (typeof config.title === 'number') { + config.title = config.title.toString(); + } + if (typeof config.content === 'number') { + config.content = config.content.toString(); + } + return config; + } + _getDelegateConfig() { + const config = {}; + for (const [key, value] of Object.entries(this._config)) { + if (this.constructor.Default[key] !== value) { + config[key] = value; + } + } + config.selector = false; + config.trigger = 'manual'; + + // In the future can be replaced with: + // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) + // `Object.fromEntries(keysWithDifferentValues)` + return config; + } + _disposePopper() { + if (this._popper) { + this._popper.destroy(); + this._popper = null; + } + if (this.tip) { + this.tip.remove(); + this.tip = null; + } + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Tooltip.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + }); + } + } + + /** + * jQuery + */ + + defineJQueryPlugin(Tooltip); + + /** + * -------------------------------------------------------------------------- + * Bootstrap popover.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$3 = 'popover'; + const SELECTOR_TITLE = '.popover-header'; + const SELECTOR_CONTENT = '.popover-body'; + const Default$2 = { + ...Tooltip.Default, + content: '', + offset: [0, 8], + placement: 'right', + template: '', + trigger: 'click' + }; + const DefaultType$2 = { + ...Tooltip.DefaultType, + content: '(null|string|element|function)' + }; + + /** + * Class definition + */ + + class Popover extends Tooltip { + // Getters + static get Default() { + return Default$2; + } + static get DefaultType() { + return DefaultType$2; + } + static get NAME() { + return NAME$3; + } + + // Overrides + _isWithContent() { + return this._getTitle() || this._getContent(); + } + + // Private + _getContentForTemplate() { + return { + [SELECTOR_TITLE]: this._getTitle(), + [SELECTOR_CONTENT]: this._getContent() + }; + } + _getContent() { + return this._resolvePossibleFunction(this._config.content); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Popover.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + }); + } + } + + /** + * jQuery + */ + + defineJQueryPlugin(Popover); + + /** + * -------------------------------------------------------------------------- + * Bootstrap scrollspy.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$2 = 'scrollspy'; + const DATA_KEY$2 = 'bs.scrollspy'; + const EVENT_KEY$2 = `.${DATA_KEY$2}`; + const DATA_API_KEY = '.data-api'; + const EVENT_ACTIVATE = `activate${EVENT_KEY$2}`; + const EVENT_CLICK = `click${EVENT_KEY$2}`; + const EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`; + const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'; + const CLASS_NAME_ACTIVE$1 = 'active'; + const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]'; + const SELECTOR_TARGET_LINKS = '[href]'; + const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'; + const SELECTOR_NAV_LINKS = '.nav-link'; + const SELECTOR_NAV_ITEMS = '.nav-item'; + const SELECTOR_LIST_ITEMS = '.list-group-item'; + const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`; + const SELECTOR_DROPDOWN = '.dropdown'; + const SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle'; + const Default$1 = { + offset: null, + // TODO: v6 @deprecated, keep it for backwards compatibility reasons + rootMargin: '0px 0px -25%', + smoothScroll: false, + target: null, + threshold: [0.1, 0.5, 1] + }; + const DefaultType$1 = { + offset: '(number|null)', + // TODO v6 @deprecated, keep it for backwards compatibility reasons + rootMargin: 'string', + smoothScroll: 'boolean', + target: 'element', + threshold: 'array' + }; + + /** + * Class definition + */ + + class ScrollSpy extends BaseComponent { + constructor(element, config) { + super(element, config); + + // this._element is the observablesContainer and config.target the menu links wrapper + this._targetLinks = new Map(); + this._observableSections = new Map(); + this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element; + this._activeTarget = null; + this._observer = null; + this._previousScrollData = { + visibleEntryTop: 0, + parentScrollTop: 0 + }; + this.refresh(); // initialize + } + + // Getters + static get Default() { + return Default$1; + } + static get DefaultType() { + return DefaultType$1; + } + static get NAME() { + return NAME$2; + } + + // Public + refresh() { + this._initializeTargetsAndObservables(); + this._maybeEnableSmoothScroll(); + if (this._observer) { + this._observer.disconnect(); + } else { + this._observer = this._getNewObserver(); + } + for (const section of this._observableSections.values()) { + this._observer.observe(section); + } + } + dispose() { + this._observer.disconnect(); + super.dispose(); + } + + // Private + _configAfterMerge(config) { + // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case + config.target = getElement(config.target) || document.body; + + // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only + config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin; + if (typeof config.threshold === 'string') { + config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value)); + } + return config; + } + _maybeEnableSmoothScroll() { + if (!this._config.smoothScroll) { + return; + } + + // unregister any previous listeners + EventHandler.off(this._config.target, EVENT_CLICK); + EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => { + const observableSection = this._observableSections.get(event.target.hash); + if (observableSection) { + event.preventDefault(); + const root = this._rootElement || window; + const height = observableSection.offsetTop - this._element.offsetTop; + if (root.scrollTo) { + root.scrollTo({ + top: height, + behavior: 'smooth' + }); + return; + } + + // Chrome 60 doesn't support `scrollTo` + root.scrollTop = height; + } + }); + } + _getNewObserver() { + const options = { + root: this._rootElement, + threshold: this._config.threshold, + rootMargin: this._config.rootMargin + }; + return new IntersectionObserver(entries => this._observerCallback(entries), options); + } + + // The logic of selection + _observerCallback(entries) { + const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`); + const activate = entry => { + this._previousScrollData.visibleEntryTop = entry.target.offsetTop; + this._process(targetElement(entry)); + }; + const parentScrollTop = (this._rootElement || document.documentElement).scrollTop; + const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop; + this._previousScrollData.parentScrollTop = parentScrollTop; + for (const entry of entries) { + if (!entry.isIntersecting) { + this._activeTarget = null; + this._clearActiveClass(targetElement(entry)); + continue; + } + const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; + // if we are scrolling down, pick the bigger offsetTop + if (userScrollsDown && entryIsLowerThanPrevious) { + activate(entry); + // if parent isn't scrolled, let's keep the first visible item, breaking the iteration + if (!parentScrollTop) { + return; + } + continue; + } + + // if we are scrolling up, pick the smallest offsetTop + if (!userScrollsDown && !entryIsLowerThanPrevious) { + activate(entry); + } + } + } + _initializeTargetsAndObservables() { + this._targetLinks = new Map(); + this._observableSections = new Map(); + const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target); + for (const anchor of targetLinks) { + // ensure that the anchor has an id and is not disabled + if (!anchor.hash || isDisabled(anchor)) { + continue; + } + const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element); + + // ensure that the observableSection exists & is visible + if (isVisible(observableSection)) { + this._targetLinks.set(decodeURI(anchor.hash), anchor); + this._observableSections.set(anchor.hash, observableSection); + } + } + } + _process(target) { + if (this._activeTarget === target) { + return; + } + this._clearActiveClass(this._config.target); + this._activeTarget = target; + target.classList.add(CLASS_NAME_ACTIVE$1); + this._activateParents(target); + EventHandler.trigger(this._element, EVENT_ACTIVATE, { + relatedTarget: target + }); + } + _activateParents(target) { + // Activate dropdown parents + if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { + SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1); + return; + } + for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) { + // Set triggered links parents as active + // With both
      and