Compare commits

...

12 Commits

Author SHA1 Message Date
hi2hi
86b45b5f2a 🚀 0.5.1 2024-12-31 07:51:59 +00:00
hi2hi
3ede341f3d 让AI写了一个烟花和新年快乐的灯笼 2024-12-31 07:51:53 +00:00
hi2hi
d3e549cad0 🎨 修复交互样式 2024-12-31 06:53:55 +00:00
hi2hi
1f7e87c28d 🎨 遗漏的换行未处理 2024-12-31 06:44:07 +00:00
hi2hi
26de335304 🚀 0.5.0 2024-12-31 06:19:05 +00:00
hi2hi
efbf38738f 添加卡片与列表的切换设置,优化相关样式 2024-12-31 06:18:18 +00:00
hi2hi
9d301b9681 🪄 判断服务器数量决定是否才有过渡效果,数量多了会卡顿 2024-12-31 05:56:36 +00:00
hi2hi
786d6c0a87 匹配一些超长系统发行版本,比如Windows的server版本 2024-12-30 14:29:25 +00:00
hi2hi
bdbd083d45 🚀 0.4.27 2024-12-30 09:44:06 +00:00
hi2hi
2910c2bf41 claw全面开启ipv6,嗯,添加一下ipv4\ipv6的标签显示 2024-12-30 09:43:39 +00:00
hi2hi
bcfc53b784 🚀 0.4.26 2024-12-30 05:07:26 +00:00
hi2hi
963c06dfce 💥 兼容setting接口的新版本,影响内容site_name与custom_code 2024-12-30 05:04:03 +00:00
18 changed files with 692 additions and 97 deletions

View File

@ -66,6 +66,7 @@ module.exports = {
'no-param-reassign': 'off',
'no-underscore-dangle': 'off',
'no-unsafe-optional-chaining': 'off',
'max-classes-per-file': 'off',
'max-len': ['warn', 120],
'vue/max-len': ['warn', 120],
'object-property-newline': ['error', {

View File

@ -1,6 +1,6 @@
{
"name": "nazhua",
"version": "0.4.25",
"version": "0.5.1",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -5,12 +5,15 @@ window.$$nazhuaConfig = {
// buyBtnText: '购买', // 购买按钮文案
// customBackgroundImage: '', // 自定义的背景图片地址
// lightBackground: true, // 启用了浅色系背景图,会强制关闭点点背景
// showFireworks: true, // 是否显示烟花,建议开启浅色系背景
// showLantern: true, // 是否显示灯笼
// listServerItemTypeToggle: true, // 服务器列表项类型切换
// listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式移动端自动切换至card
// listServerStatusType: 'progress', // 服务器状态类型--列表
// listServerRealTimeShowLoad: false, // 列表显示服务器实时负载
// listServerRealTimeShowLoad: true, // 列表显示服务器实时负载
// detailServerStatusType: 'progress', // 服务器状态类型--详情页
// serverStatusLinear: true, // 服务器状态渐变线性显示
// disableSarasaTermSC: false, // 禁用Sarasa Term SC字体
// disableSarasaTermSC: true, // 禁用Sarasa Term SC字体
// hideWorldMap: false, // 隐藏地图
// hideHomeWorldMap: false, // 隐藏首页地图
// hideDetailWorldMap: false, // 隐藏详情地图

View File

@ -71,12 +71,17 @@ Nazhua对这个支持大概在90%左右,参与数据处理了的字段如下
"bandwidth": "30Mbps",
"trafficVol": "1TB/月",
"trafficType": "1",
"IPv4": "1",
"IPv6": "1",
"networkRoute": "CN2,GIA",
"extra": "传家宝,AS9929"
}
}
```
其中IPv4、IPv6暂未参与到处理中后续可能会支持。
~~其中IPv4、IPv6暂未参与到处理中后续可能会支持。~~
- 都有显示标签双栈IP;
- 单IPv4显示标签仅IPv4;
- 单IPv6显示标签仅IPv6;
## 数据来源
1-0. 公开的全量配置其中包括“公开备注”PublicNote来自探针主页上暴露的服务器节点列表配置信息。此处是根据正则匹配的方式获取到的节点列表。在主题项目中默认将访问`/nezha/`的指向此处。

View File

@ -0,0 +1,158 @@
<template>
<canvas
ref="canvas"
class="fireworks-canvas"
/>
</template>
<script setup>
import {
ref,
onMounted,
onUnmounted,
} from 'vue';
const canvas = ref(null);
let ctx = null;
let particles = [];
let rockets = [];
let animationFrameId = null;
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.velocity = {
x: (Math.random() - 0.5) * 8,
y: (Math.random() - 0.5) * 12 - 8,
};
this.alpha = 1;
this.decay = 0.02;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${this.color}, ${this.alpha})`;
ctx.fill();
}
update() {
this.velocity.y += 0.1;
this.x += this.velocity.x;
this.y += this.velocity.y;
this.alpha -= this.decay;
}
}
function createFirework(x, y) {
const colors = [
'255, 0, 0',
'0, 255, 0',
'0, 0, 255',
'255, 255, 0',
'255, 0, 255',
'0, 255, 255',
];
const color = colors[Math.floor(Math.random() * colors.length)];
for (let i = 0; i < 80; i += 1) {
particles.push(new Particle(x, y, color));
}
}
class Rocket {
constructor() {
this.x = Math.random() * canvas.value.width;
this.y = canvas.value.height;
this.targetY = canvas.value.height * 0.5;
this.speed = 15;
this.trail = [];
this.maxTrailLength = 5;
}
draw() {
//
ctx.beginPath();
this.trail.forEach((pos, index) => {
ctx.fillStyle = `rgba(255, 200, 0, ${index / this.trail.length})`;
ctx.fillRect(pos.x, pos.y, 2, 2);
});
//
ctx.fillStyle = 'rgba(255, 220, 0, 1)';
ctx.fillRect(this.x, this.y, 3, 3);
}
update() {
this.trail.push({
x: this.x,
y: this.y,
});
if (this.trail.length > this.maxTrailLength) {
this.trail.shift();
}
this.y -= this.speed;
if (this.y <= this.targetY) {
createFirework(this.x, this.y);
return false;
}
return true;
}
}
function animate() {
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
//
rockets = rockets.filter((rocket) => {
rocket.draw();
return rocket.update();
});
//
particles = particles.filter((particle) => particle.alpha > 0);
particles.forEach((particle) => {
particle.draw();
particle.update();
});
//
if (Math.random() < 0.03 && rockets.length < 3) {
rockets.push(new Rocket());
}
animationFrameId = requestAnimationFrame(animate);
}
function resizeCanvas() {
if (canvas.value) {
canvas.value.width = window.innerWidth;
canvas.value.height = window.innerHeight;
}
}
onMounted(() => {
ctx = canvas.value.getContext('2d');
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
animate();
});
onUnmounted(() => {
window.removeEventListener('resize', resizeCanvas);
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
});
</script>
<style scoped>
.fireworks-canvas {
position: fixed;
top: 0;
left: 0;
z-index: 8;
pointer-events: none;
}
</style>

257
src/components/lantern.vue Normal file
View File

@ -0,0 +1,257 @@
<template>
<div class="lantern-container">
<div class="lantern-group right-group">
<div class="deng-box">
<div class="deng">
<div class="xian" />
<div class="deng-a">
<div class="deng-b">
<div class="deng-t"></div>
</div>
</div>
<div class="shui shui-a">
<div class="shui-c" />
<div class="shui-b" />
</div>
</div>
</div>
<div class="deng-box deng-box--2">
<div class="deng">
<div class="xian" />
<div class="deng-a">
<div class="deng-b">
<div class="deng-t"></div>
</div>
</div>
<div class="shui shui-a">
<div class="shui-c" />
<div class="shui-b" />
</div>
</div>
</div>
</div>
<div class="lantern-group left-group">
<div class="deng-box">
<div class="deng">
<div class="xian" />
<div class="deng-a">
<div class="deng-b">
<div class="deng-t"></div>
</div>
</div>
<div class="shui shui-a">
<div class="shui-c" />
<div class="shui-b" />
</div>
</div>
</div>
<div class="deng-box deng-box--2">
<div class="deng">
<div class="xian" />
<div class="deng-a">
<div class="deng-b">
<div class="deng-t"></div>
</div>
</div>
<div class="shui shui-a">
<div class="shui-c" />
<div class="shui-b" />
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
//
// AI
</script>
<style lang="scss" scoped>
.lantern-container {
position: fixed;
top: calc(var(--layout-header-height) + 5px);
width: 100%;
z-index: 50;
pointer-events: none;
}
.lantern-group {
position: fixed;
top: 70px;
animation: swing 3s infinite ease-in-out;
transform-origin: 50% -10px;
&.left-group {
left: 40px;
animation-delay: -1.5s;
.deng-box:nth-child(2) {
margin-top: -12px;
.deng {
animation: swing-extra 2s infinite ease-in-out;
animation-delay: -0.5s;
}
}
}
&.right-group {
right: 30px;
animation-delay: -0.5s;
.deng-box:nth-child(2) {
.deng {
animation: swing-extra 2s infinite ease-in-out;
animation-delay: -1s;
}
}
}
.deng {
animation: none;
}
.deng-box {
position: relative;
top: -40px;
&:first-child {
z-index: 2;
.deng {
margin-bottom: 23px;
}
}
&:nth-child(2) {
z-index: 1;
}
}
}
.deng {
position: relative;
width: 120px;
height: 90px;
margin: 50px;
background: rgba(216, 0, 15, 0.8);
border-radius: 50% 50%;
transform-origin: 50% -100px;
animation: swing 3s infinite ease-in-out;
box-shadow: -5px 5px 50px 4px rgba(250, 108, 0, 1);
}
.deng-a {
width: 100px;
height: 90px;
background: rgba(216, 0, 15, 0.1);
margin: 12px 8px 8px 10px;
border-radius: 50% 50%;
border: 2px solid #dc8f03;
}
.deng-b {
width: 45px;
height: 90px;
background: rgba(216, 0, 15, 0.1);
margin: -4px 8px 8px 26px;
border-radius: 50% 50%;
border: 2px solid #dc8f03;
}
.xian {
position: absolute;
top: -20px;
left: 60px;
width: 2px;
height: 20px;
background: #dc8f03;
}
.shui-a {
position: relative;
width: 5px;
height: 20px;
margin: -5px 0 0 59px;
transform-origin: 50% -45px;
background: #ffa500;
border-radius: 0 0 5px 5px;
}
.shui-b {
position: absolute;
top: 14px;
left: -2px;
width: 10px;
height: 10px;
background: #dc8f03;
border-radius: 50%;
}
.shui-c {
position: absolute;
top: 18px;
left: -2px;
width: 10px;
height: 35px;
background: #ffa500;
border-radius: 0 0 0 5px;
}
.deng:before {
position: absolute;
top: -7px;
left: 29px;
height: 12px;
width: 60px;
content: " ";
display: block;
z-index: 999;
border-radius: 5px 5px 0 0;
border: solid 1px #dc8f03;
background: linear-gradient(to right, #dc8f03, #ffa500, #dc8f03, #ffa500, #dc8f03);
}
.deng:after {
position: absolute;
bottom: -7px;
left: 10px;
height: 12px;
width: 60px;
content: " ";
display: block;
margin-left: 20px;
border-radius: 0 0 5px 5px;
border: solid 1px #dc8f03;
background: linear-gradient(to right, #dc8f03, #ffa500, #dc8f03, #ffa500, #dc8f03);
}
.deng-t {
font-family: 华文行楷, Arial, Lucida Grande, Tahoma, sans-serif;
font-size: 3.2rem;
color: #ffd000;
line-height: 85px;
text-align: center;
margin-left: -5px;
}
@keyframes swing {
0% { transform: rotate(-6deg) }
50% { transform: rotate(6deg) }
100% { transform: rotate(-6deg) }
}
@keyframes swing-extra {
0% { transform: rotate(-3deg) }
50% { transform: rotate(3deg) }
100% { transform: rotate(-3deg) }
}
@media screen and (max-width: 1024px) {
.lantern-container {
display: none;
}
}
</style>

View File

@ -46,6 +46,9 @@ const store = useStore();
const dynamicContentRef = ref();
const dynamicContent = computed(() => {
if (store.state.setting?.config?.custom_code) {
return store.state.setting.config.custom_code;
}
if (store.state.setting?.custom_code) {
return store.state.setting.custom_code;
}

View File

@ -335,6 +335,10 @@ const dashboardUrl = computed(() => config.nazhua.v1DashboardUrl || '/dashboard'
color: #ddd;
line-height: 30px;
.value {
font-weight: bold;
}
&.server-count--total {
.value {
color: #70f3ff;

View File

@ -12,6 +12,12 @@
<slot />
<layout-footer />
</div>
<template v-if="config.nazhua.showFireworks">
<fireworks />
</template>
<template v-if="config.nazhua.showLantern">
<lantern />
</template>
</div>
</template>
@ -21,6 +27,8 @@
*/
import { computed } from 'vue';
import config from '@/config';
import Fireworks from '@/components/fireworks.vue';
import Lantern from '@/components/lantern.vue';
import LayoutHeader from './components/header.vue';
import LayoutFooter from './components/footer.vue';

View File

@ -118,7 +118,7 @@ const store = createStore({
commit('SET_SETTING', res);
// 如果自定义配置没有设置title使用站点名称
if (!window.$$nazhuaConfig.title) {
config.nazhua.title = res.site_name;
config.nazhua.title = res.config?.site_name || res.site_name;
if (route?.name === 'Home' || !route) {
document.title = config.nazhua.title;
}

View File

@ -212,8 +212,13 @@ export function getPlatformLogoIconClassName(platform) {
/**
* 获取系统发行版本
*/
export function getSystemOSLabel(platform) {
switch (platform) {
export function getSystemOSLabel(platform, short = false) {
const platformStr = (platform || '').toLowerCase();
// 匹配一些超长系统发行版本
if (short && platformStr.includes('windows')) {
return 'Windows';
}
switch (platformStr) {
case 'windows':
return 'Windows';
case 'linux':

View File

@ -26,7 +26,8 @@ export const loadServerGroup = async () => request({
/**
* 加载网站配置
*
* 暂时只使用site_name
* 暂时只使用site_name\custom_code
* 哪吒v1.4.9之后上面的参数调整至data.config
*/
export const loadSetting = async () => request({
url: config.nazhua.v1ApiSettingPath,

View File

@ -212,6 +212,9 @@
v-for="(tag, index) in tagList"
:key="`${tag}_${index}`"
class="server-info-tag-item"
:class="{
'has-sarasa-term': $hasSarasaTerm && config.nazhua.disableSarasaTermSC !== true,
}"
>
{{ tag }}
</span>
@ -415,11 +418,24 @@ const billPlanData = computed(() => ['billing', 'remainingTime', 'bandwidth', 't
const tagList = computed(() => {
const list = [];
if (props?.info?.PublicNote?.planDataMod?.networkRoute) {
list.push(...props.info.PublicNote.planDataMod.networkRoute.split(','));
const {
networkRoute,
extra,
IPv4,
IPv6,
} = props?.info?.PublicNote?.planDataMod || {};
if (networkRoute) {
list.push(...networkRoute?.split?.(','));
}
if (props?.info?.PublicNote?.planDataMod?.extra) {
list.push(...props.info.PublicNote.planDataMod.extra.split(','));
if (extra) {
list.push(...extra?.split?.(','));
}
if (IPv4 === '1' && IPv6 === '1') {
list.push('双栈IP');
} else if (IPv4 === '1') {
list.push('仅IPv4');
} else if (IPv6 === '1') {
list.push('仅IPv6');
}
return list;
});
@ -592,12 +608,16 @@ const processCount = computed(() => props.info?.State?.ProcessCount);
.server-info-tag-item {
height: 18px;
padding: 0 5px 0 6px;
line-height: 20px;
line-height: 18px;
font-size: 12px;
color: var(--public-note-tag-color);
background: var(--public-note-tag-bg);
text-shadow: 1px 1px 2px rgba(#000, 0.2);
border-radius: 4px;
&.has-sarasa-term {
line-height: 20px;
}
}
}
}

View File

@ -37,7 +37,7 @@
:key="`${tagItem}_${index}`"
class="tag-item"
:class="{
'has-sarasa-term': $hasSarasaTerm,
'has-sarasa-term': $hasSarasaTerm && config.nazhua.disableSarasaTermSC !== true,
}"
>
{{ tagItem }}
@ -106,11 +106,24 @@ function toBuy() {
const tagList = computed(() => {
const list = [];
if (props?.info?.PublicNote?.planDataMod?.networkRoute) {
list.push(...props.info.PublicNote.planDataMod.networkRoute.split(','));
const {
networkRoute,
extra,
IPv4,
IPv6,
} = props?.info?.PublicNote?.planDataMod || {};
if (networkRoute) {
list.push(...networkRoute.split(','));
}
if (props?.info?.PublicNote?.planDataMod?.extra) {
list.push(...props.info.PublicNote.planDataMod.extra.split(','));
if (extra) {
list.push(...extra.split(','));
}
if (IPv4 === '1' && IPv6 === '1') {
list.push('双栈IP');
} else if (IPv4 === '1') {
list.push('仅IPv4');
} else if (IPv6 === '1') {
list.push('仅IPv6');
}
// 5
return list.slice(0, 5);

View File

@ -92,7 +92,7 @@ const { cpuAndMemAndDisk } = handleServerInfo({
props,
});
const platformSystemLabel = computed(() => hostUtils.getSystemOSLabel(props.info?.Host?.Platform));
const platformSystemLabel = computed(() => hostUtils.getSystemOSLabel(props.info?.Host?.Platform, true));
function openDetail() {
router.push({
@ -156,6 +156,7 @@ function openDetail() {
height: 32px;
line-height: 34px;
font-size: 16px;
font-weight: bold;
width: 100%;
text-overflow: ellipsis;
white-space: nowrap;

View File

@ -0,0 +1,113 @@
<template>
<transition-group
v-if="showTransition"
name="list"
tag="div"
class="server-list-container"
:class="{
'server-list--row': showListRow,
'server-list--card': showListCard,
}"
>
<slot />
</transition-group>
<div
v-else
class="server-list-container"
:class="{
'server-list--row': showListRow,
'server-list--card': showListCard,
}"
>
<slot />
</div>
</template>
<script setup>
/**
* 服务器列表
*/
defineProps({
showTransition: {
type: Boolean,
default: true,
},
showListRow: {
type: Boolean,
default: false,
},
showListCard: {
type: Boolean,
default: false,
},
});
</script>
<style lang="scss" scoped>
.server-list-container.server-list--card {
--list-padding: 20px;
--list-gap-size: 20px;
--list-item-num: 3;
--list-item-width: calc(
(
var(--list-container-width)
- (var(--list-padding) * 2)
- (
var(--list-gap-size)
* (var(--list-item-num) - 1)
)
)
/ var(--list-item-num)
);
position: relative;
display: flex;
flex-wrap: wrap;
gap: var(--list-gap-size);
padding: 0 var(--list-padding);
width: var(--list-container-width);
margin: auto;
// 1440px
@media screen and (max-width: 1440px) {
--list-gap-size: 10px;
}
@media screen and (max-width: 1024px) {
--list-item-num: 2;
}
@media screen and (max-width: 680px) {
--list-item-num: 1;
}
}
.server-list-container.server-list--row {
--list-padding: 20px;
--list-gap-size: 12px;
position: relative;
display: flex;
flex-direction: column;
gap: var(--list-gap-size);
width: var(--list-container-width);
padding: 0 var(--list-padding);
margin: auto;
}
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from {
opacity: 0;
transform: translateY(-30px);
}
.list-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-leave-active {
position: absolute;
}
</style>

View File

@ -10,12 +10,22 @@
:key="item.key"
class="server-option-item"
:class="{
'has-icon': item.icon,
active: activeValue === item.value,
}"
:title="item?.title || false"
@click="toggleModelValue(item)"
>
<span class="option-label">{{ item.label }}</span>
<i
v-if="item.icon"
class="option-icon"
:class="item.icon"
:title="item.label"
/>
<span
v-else
class="option-label"
>{{ item.label }}</span>
</div>
</div>
</template>
@ -85,13 +95,27 @@ function toggleModelValue(item) {
background: rgba(#000, 0.3);
transition: all 0.3s linear;
cursor: pointer;
&.has-icon {
padding: 0 10px;
}
@media screen and (max-width: 768px) {
height: 30px;
padding: 0 10px;
border-radius: 3px;
background-color: rgba(#000, 0.8);
cursor: default;
}
.option-icon {
line-height: 1;
font-size: 18px;
}
.option-label {
color: #fff;
font-weight: bold;
transition: all 0.3s linear;
}
@ -120,6 +144,10 @@ function toggleModelValue(item) {
&:hover {
background: rgba(#000, 0.8);
}
&.active {
background: rgba(#ff7500, 0.75);
}
}
}
}

View File

@ -31,34 +31,36 @@
v-model="filterFormData.online"
:options="onlineOptions"
/>
<server-option-box
v-if="config.nazhua.listServerItemTypeToggle"
v-model="listType"
:options="listTypeOptions"
:accpet-empty="false"
/>
</div>
</div>
<transition-group
<server-list-warp
v-if="showListRow"
name="list"
tag="div"
class="server-list-container"
:class="`server-list--row`"
:show-transition="showTransition"
:show-list-row="showListRow"
>
<server-row-item
v-for="item in filterServerList.list"
:key="item.ID"
:info="item"
/>
</transition-group>
<transition-group
</server-list-warp>
<server-list-warp
v-if="showListCard"
name="list"
tag="div"
class="server-list-container"
:class="`server-list--card`"
:show-transition="showTransition"
:show-list-card="showListCard"
>
<server-card-item
v-for="item in filterServerList.list"
:key="item.ID"
:info="item"
/>
</transition-group>
</server-list-warp>
</div>
</div>
</template>
@ -93,6 +95,7 @@ import validate from '@/utils/validate';
import WorldMap from '@/components/world-map/world-map.vue';
import ServerOptionBox from './components/server-list/server-option-box.vue';
import ServerListWarp from './components/server-list/server-list-warp.vue';
import ServerCardItem from './components/server-list/card/server-list-item.vue';
import ServerRowItem from './components/server-list/row/server-list-item.vue';
@ -100,14 +103,30 @@ const store = useStore();
const worldMapWidth = ref();
const windowWidth = ref(window.innerWidth);
const listType = ref(config.nazhua.listServerItemType || 'card');
const showTransition = computed(() => {
//
if (config.nazhua.forceTransition) {
return true;
}
// 7
return store.state.serverList.length < 7;
});
const showListRow = computed(() => {
if (windowWidth.value > 1024) {
if (config.nazhua.listServerItemTypeToggle) {
return listType.value === 'row';
}
return config.nazhua.listServerItemType === 'row';
}
return false;
});
const showListCard = computed(() => {
if (windowWidth.value > 1024) {
if (config.nazhua.listServerItemTypeToggle) {
return listType.value !== 'row';
}
return config.nazhua.listServerItemType !== 'row';
}
return true;
@ -159,6 +178,18 @@ const onlineOptions = computed(() => {
return [];
});
const listTypeOptions = computed(() => [{
key: 'card',
label: '卡片',
value: 'card',
icon: 'ri-gallery-view-2',
}, {
key: 'row',
label: '列表',
value: 'row',
icon: 'ri-list-view',
}]);
const filterServerList = computed(() => {
const fields = {};
const locationMap = {};
@ -329,75 +360,19 @@ onActivated(() => {
justify-content: space-between;
gap: 10px 20px;
width: var(--list-container-width);
padding: 0 20px;
margin: auto;
&.list-is-card {
padding: 0 20px;
}
}
.server-list-container.server-list--card {
--list-padding: 20px;
--list-gap-size: 20px;
--list-item-num: 3;
--list-item-width: calc(
(
var(--list-container-width)
- (var(--list-padding) * 2)
- (
var(--list-gap-size)
* (var(--list-item-num) - 1)
)
)
/ var(--list-item-num)
);
position: relative;
display: flex;
flex-wrap: wrap;
gap: var(--list-gap-size);
padding: 0 var(--list-padding);
width: var(--list-container-width);
margin: auto;
// 1440px
@media screen and (max-width: 1440px) {
--list-gap-size: 10px;
.left-box {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
@media screen and (max-width: 1024px) {
--list-item-num: 2;
.right-box {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
@media screen and (max-width: 680px) {
--list-item-num: 1;
}
}
.server-list-container.server-list--row {
--list-padding: 20px;
--list-gap-size: 12px;
position: relative;
display: flex;
flex-direction: column;
gap: var(--list-gap-size);
width: var(--list-container-width);
margin: auto;
}
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from {
opacity: 0;
transform: translateY(-30px);
}
.list-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-leave-active {
position: absolute;
}
</style>