1303 lines
46 KiB
HTML
1303 lines
46 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN" class="bg-[#0f1a2f]">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>文本转语音 - TTS服务</title>
|
||
<script src="{{.BasePath}}/static/js/tailwind.js"></script>
|
||
<link rel="icon" type="image/svg+xml" href="{{.BasePath}}/static/icons/favicon.svg">
|
||
<meta name="description" content="基于Microsoft Azure语音服务的在线文本转语音工具">
|
||
<script src="{{.BasePath}}/static/js/app.js"></script>
|
||
<style>
|
||
html, body {
|
||
background-color: #0f1a2f; /* 确保即使在滚动时也有深色背景 */
|
||
}
|
||
|
||
/* 固定渐变背景,移除动画效果 */
|
||
.bg-gradient-animated {
|
||
background: linear-gradient(-45deg, #0f1a2f, #2d3748, #1a365d, #0d2f62);
|
||
background-size: 400% 400%;
|
||
position: relative;
|
||
}
|
||
|
||
/* 激光效果 - 修复宽度和溢出问题 */
|
||
.laser-container {
|
||
position: fixed; /* 改为fixed定位,使激光效果固定在视口中 */
|
||
top: 0;
|
||
left: 0;
|
||
width: 100vw; /* 使用视口宽度单位而非百分比 */
|
||
height: 100vh; /* 使用视口高度单位 */
|
||
z-index: 0;
|
||
opacity: 0.7;
|
||
pointer-events: none; /* 确保不会拦截鼠标事件 */
|
||
overflow: hidden; /* 防止激光光束溢出 */
|
||
}
|
||
|
||
.laser-beam {
|
||
position: absolute;
|
||
background: linear-gradient(to right, transparent, rgba(0, 195, 255, 0.5), transparent);
|
||
height: 2px;
|
||
width: 150vw; /* 增加宽度确保激光覆盖旋转后的视口 */
|
||
transform-origin: 0 0;
|
||
left: -25vw; /* 偏移,确保即使旋转也能覆盖整个屏幕 */
|
||
}
|
||
|
||
.laser-beam:nth-child(2n) {
|
||
background: linear-gradient(to right, transparent, rgba(255, 0, 200, 0.5), transparent);
|
||
}
|
||
|
||
.laser-beam:nth-child(3n) {
|
||
background: linear-gradient(to right, transparent, rgba(0, 255, 170, 0.5), transparent);
|
||
}
|
||
|
||
.laser-beam:nth-child(5n) {
|
||
background: linear-gradient(to right, transparent, rgba(255, 250, 0, 0.3), transparent);
|
||
}
|
||
|
||
/* 移除霓虹光效果动画 */
|
||
.glow {
|
||
position: absolute;
|
||
width: 60%;
|
||
height: 60%;
|
||
border-radius: 50%;
|
||
filter: blur(80px);
|
||
z-index: 0;
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.glow:nth-child(1) {
|
||
top: -30%;
|
||
left: -10%;
|
||
background: linear-gradient(45deg, #ff00c8, #0084ff);
|
||
}
|
||
|
||
.glow:nth-child(2) {
|
||
bottom: -30%;
|
||
right: -10%;
|
||
background: linear-gradient(-45deg, #00f2ff, #8f00ff);
|
||
}
|
||
|
||
/* 内容区域 - 确保不溢出 */
|
||
.content-area {
|
||
position: relative;
|
||
z-index: 10;
|
||
background: rgba(255, 255, 255, 0.7);
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
border-radius: 1rem;
|
||
box-shadow: 0 5px 30px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(255, 255, 255, 0.3);
|
||
width: 100%;
|
||
max-width: 100%; /* 确保不超出父容器 */
|
||
overflow: hidden; /* 防止子元素溢出 */
|
||
}
|
||
|
||
/* 毛玻璃按钮效果 */
|
||
.btn-frosted {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn-frosted:hover {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
box-shadow: 0 7px 15px rgba(0, 0, 0, 0.15);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
/* 毛玻璃面板效果 */
|
||
.panel-frosted {
|
||
background: rgba(255, 255, 255, 0.8);
|
||
backdrop-filter: blur(15px);
|
||
-webkit-backdrop-filter: blur(15px);
|
||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* 为滑块输入元素添加毛玻璃效果 - 更新样式 */
|
||
input[type="range"] {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
background: rgba(13, 47, 98, 0.2);
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
border-radius: 10px;
|
||
height: 8px;
|
||
outline: none;
|
||
margin: 10px 0;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
/* 滑块填充效果 */
|
||
input[type="range"]::-webkit-slider-runnable-track {
|
||
-webkit-appearance: none;
|
||
height: 8px;
|
||
}
|
||
|
||
input[type="range"]::-moz-range-track {
|
||
background: rgba(13, 47, 98, 0.2);
|
||
height: 8px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* Chrome/Safari滑块拇指 */
|
||
input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #3b82f6, #1e40af);
|
||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||
cursor: pointer;
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||
margin-top: -5px; /* 用于垂直居中 */
|
||
position: relative;
|
||
z-index: 2;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
/* Firefox滑块拇指 */
|
||
input[type="range"]::-moz-range-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #3b82f6, #1e40af);
|
||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||
cursor: pointer;
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
/* 滑块交互效果 */
|
||
input[type="range"]:hover::-webkit-slider-thumb {
|
||
transform: scale(1.1);
|
||
box-shadow: 0 3px 8px rgba(59, 130, 246, 0.5);
|
||
}
|
||
|
||
input[type="range"]:hover::-moz-range-thumb {
|
||
transform: scale(1.1);
|
||
box-shadow: 0 3px 8px rgba(59, 130, 246, 0.5);
|
||
}
|
||
|
||
input[type="range"]:active::-webkit-slider-thumb {
|
||
transform: scale(1.2);
|
||
background: linear-gradient(135deg, #60a5fa, #3b82f6);
|
||
}
|
||
|
||
input[type="range"]:active::-moz-range-thumb {
|
||
transform: scale(1.2);
|
||
background: linear-gradient(135deg, #60a5fa, #3b82f6);
|
||
}
|
||
|
||
/* 滑块值显示区域 */
|
||
.range-value {
|
||
background: rgba(59, 130, 246, 0.1);
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-weight: 600;
|
||
min-width: 40px;
|
||
text-align: center;
|
||
color: #3b82f6;
|
||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||
}
|
||
|
||
/* 滑块中央光晕效果 */
|
||
input[type="range"].rate-slider::-webkit-slider-thumb::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 25px;
|
||
height: 25px;
|
||
background: radial-gradient(circle, rgba(59, 130, 246, 0.6) 0%, rgba(59, 130, 246, 0) 70%);
|
||
border-radius: 50%;
|
||
z-index: -1;
|
||
}
|
||
|
||
/* 标签文本增强样式 */
|
||
.slider-label {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.slider-label span {
|
||
margin-left: 8px;
|
||
font-size: 0.75rem;
|
||
color: rgba(107, 114, 128, 0.7);
|
||
}
|
||
|
||
/* 防止表格和代码块溢出 */
|
||
.overflow-x-auto {
|
||
max-width: 100%; /* 确保不超过父容器 */
|
||
}
|
||
|
||
pre, code {
|
||
max-width: 100%;
|
||
word-break: break-word; /* 允许在必要时断词 */
|
||
}
|
||
|
||
/* 输入框与主题风格一致化 */
|
||
textarea, select {
|
||
background: rgba(255, 255, 255, 0.1) !important;
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||
color: #1a365d !important;
|
||
transition: all 0.3s ease;
|
||
font-weight: 500;
|
||
}
|
||
|
||
textarea:focus, select:focus {
|
||
background: rgba(255, 255, 255, 0.15) !important;
|
||
box-shadow: 0 0 15px rgba(59, 130, 246, 0.3) !important;
|
||
border: 1px solid rgba(59, 130, 246, 0.5) !important;
|
||
}
|
||
|
||
/* 选择框美化 */
|
||
select {
|
||
appearance: none;
|
||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231a365d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||
background-repeat: no-repeat;
|
||
background-position: right 0.7rem center;
|
||
background-size: 1em;
|
||
padding-right: 2.5rem;
|
||
}
|
||
|
||
/* 选择框选项组样式 */
|
||
optgroup {
|
||
background-color: rgba(45, 55, 72, 0.95);
|
||
color: white;
|
||
font-weight: bold;
|
||
}
|
||
|
||
option {
|
||
background-color: rgba(255, 255, 255, 0.9);
|
||
color: #2d3748;
|
||
padding: 8px;
|
||
}
|
||
|
||
/* 自定义音频播放器 */
|
||
audio {
|
||
filter: hue-rotate(200deg) saturate(110%) brightness(90%);
|
||
border-radius: 24px;
|
||
height: 36px;
|
||
background: rgba(26, 54, 93, 0.1);
|
||
}
|
||
|
||
/* 更新分割线样式 */
|
||
.border-b {
|
||
border-image: linear-gradient(to right, transparent, rgba(59, 130, 246, 0.3), transparent) 1;
|
||
}
|
||
|
||
/* 加载中状态优化 */
|
||
.loading-text {
|
||
position: relative;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.loading-text:after {
|
||
content: "...";
|
||
position: absolute;
|
||
width: 24px;
|
||
text-align: left;
|
||
animation: loading-dots 1.5s infinite;
|
||
}
|
||
|
||
@keyframes loading-dots {
|
||
0% {
|
||
content: ".";
|
||
}
|
||
33% {
|
||
content: "..";
|
||
}
|
||
66% {
|
||
content: "...";
|
||
}
|
||
}
|
||
|
||
/* API Key输入区域科技感增强 */
|
||
#api-key-group {
|
||
background: linear-gradient(to right, rgba(13, 47, 98, 0.05), rgba(59, 130, 246, 0.05), rgba(13, 47, 98, 0.05));
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
}
|
||
|
||
/* 文本输入框字数计数器增强 */
|
||
.char-counter {
|
||
background: rgba(59, 130, 246, 0.1);
|
||
border-radius: 12px;
|
||
padding: 2px 8px;
|
||
font-weight: 500;
|
||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||
}
|
||
|
||
/* 添加脉冲动画到主按钮 */
|
||
#speak {
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#speak:after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
border-radius: 4px;
|
||
box-shadow: 0 0 15px 3px rgba(59, 130, 246, 0.6);
|
||
opacity: 0;
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% {
|
||
opacity: 0;
|
||
}
|
||
50% {
|
||
opacity: 0.3;
|
||
}
|
||
100% {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
/* 重新设计的按钮基础样式 */
|
||
.btn-neo {
|
||
position: relative;
|
||
overflow: hidden;
|
||
background: rgba(13, 47, 98, 0.6);
|
||
color: #dbeafe;
|
||
border: none;
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
border-radius: 6px;
|
||
padding: 0.6rem 1.2rem;
|
||
font-weight: 500;
|
||
letter-spacing: 0.03em;
|
||
transition: all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||
box-shadow: 0 4px 8px -2px rgba(0, 0, 0, 0.2),
|
||
inset 0 1px 0 0 rgba(255, 255, 255, 0.2),
|
||
inset 0 -1px 0 0 rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
/* 按钮边框发光效果 */
|
||
.btn-neo::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
border-radius: 6px;
|
||
padding: 1px;
|
||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(147, 197, 253, 0.3), rgba(59, 130, 246, 0.1));
|
||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||
-webkit-mask-composite: xor;
|
||
mask-composite: exclude;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 按钮悬浮效果 */
|
||
.btn-neo:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.3),
|
||
inset 0 1px 0 0 rgba(255, 255, 255, 0.3),
|
||
0 0 10px 2px rgba(59, 130, 246, 0.4);
|
||
background: rgba(26, 86, 219, 0.7);
|
||
color: #ffffff;
|
||
}
|
||
|
||
/* 按钮点击效果 */
|
||
.btn-neo:active {
|
||
transform: translateY(0);
|
||
box-shadow: 0 2px 4px -2px rgba(0, 0, 0, 0.2),
|
||
inset 0 1px 2px 0 rgba(0, 0, 0, 0.4);
|
||
background: rgba(30, 64, 175, 0.8);
|
||
}
|
||
|
||
/* 主按钮变体 */
|
||
.btn-neo-primary {
|
||
background: linear-gradient(180deg, rgba(37, 99, 235, 0.8), rgba(30, 64, 175, 0.9));
|
||
color: white;
|
||
}
|
||
|
||
.btn-neo-primary:hover {
|
||
background: linear-gradient(180deg, rgba(59, 130, 246, 0.9), rgba(37, 99, 235, 0.95));
|
||
box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.3),
|
||
0 0 20px 5px rgba(59, 130, 246, 0.4);
|
||
}
|
||
|
||
/* 次要按钮变体 */
|
||
.btn-neo-secondary {
|
||
background: linear-gradient(180deg, rgba(15, 23, 42, 0.5), rgba(15, 23, 42, 0.7));
|
||
color: rgba(255, 255, 255, 0.95);
|
||
border: 1px solid rgba(100, 116, 139, 0.2);
|
||
}
|
||
|
||
.btn-neo-secondary:hover {
|
||
background: linear-gradient(180deg, rgba(30, 41, 59, 0.6), rgba(30, 41, 59, 0.8));
|
||
}
|
||
|
||
/* 带脉冲光环效果的按钮 */
|
||
.btn-neo-pulse::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
border-radius: 6px;
|
||
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% {
|
||
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
|
||
}
|
||
70% {
|
||
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
|
||
}
|
||
100% {
|
||
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
|
||
}
|
||
}
|
||
|
||
/* 禁用状态 */
|
||
.btn-neo:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* 带图标的按钮 */
|
||
.btn-neo-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.btn-neo-icon svg {
|
||
width: 18px;
|
||
height: 18px;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.btn-neo-icon:hover svg {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
/* 重新设计的Tab栏样式 - 更加和谐 */
|
||
.tab-bar {
|
||
display: flex;
|
||
justify-content: center;
|
||
background: rgba(13, 47, 98, 0.15); /* 更轻微的背景色 */
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
border-radius: 10px;
|
||
padding: 4px;
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15),
|
||
0 4px 10px rgba(0, 0, 0, 0.1);
|
||
position: relative;
|
||
max-width: 380px;
|
||
margin: 0 auto;
|
||
z-index: 1;
|
||
}
|
||
|
||
.tab-bar::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
border-radius: 10px;
|
||
padding: 1px;
|
||
background: rgba(255, 255, 255, 0.2); /* 更微妙的边框 */
|
||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||
-webkit-mask-composite: xor;
|
||
mask-composite: exclude;
|
||
z-index: -1;
|
||
}
|
||
|
||
.tab-item {
|
||
position: relative;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
font-weight: 500;
|
||
padding: 8px 18px;
|
||
flex-grow: 1;
|
||
text-align: center;
|
||
transition: all 0.25s ease;
|
||
border-radius: 6px;
|
||
z-index: 2;
|
||
letter-spacing: 0.01em;
|
||
}
|
||
|
||
.tab-item:hover {
|
||
color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
|
||
.tab-item.active {
|
||
color: #1a365d; /* 更深的文字颜色,与主题对比 */
|
||
font-weight: 600;
|
||
}
|
||
|
||
.tab-indicator {
|
||
position: absolute;
|
||
background: rgba(255, 255, 255, 0.9); /* 纯白色指示器 */
|
||
border-radius: 6px;
|
||
height: calc(100% - 8px);
|
||
z-index: 1;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
top: 4px;
|
||
left: 4px;
|
||
bottom: 4px;
|
||
}
|
||
|
||
/* Tab中的图标样式 - 简化 */
|
||
.tab-icon {
|
||
display: inline-flex;
|
||
vertical-align: middle;
|
||
margin-right: 6px;
|
||
height: 18px;
|
||
width: 18px;
|
||
transition: transform 0.2s ease;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.tab-item:hover .tab-icon {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.tab-item.active .tab-icon {
|
||
color: #1a365d; /* 图标颜色与文本匹配 */
|
||
}
|
||
|
||
/* 简化下方线条效果 */
|
||
.tab-item::after {
|
||
content: '';
|
||
position: absolute;
|
||
width: 20px;
|
||
height: 2px;
|
||
background: rgba(255, 255, 255, 0.5);
|
||
bottom: -2px;
|
||
left: 50%;
|
||
transform: translateX(-50%) scaleX(0);
|
||
transition: transform 0.2s ease;
|
||
opacity: 0;
|
||
}
|
||
|
||
.tab-item.active::after {
|
||
opacity: 0; /* 移除活动状态下的额外线条,保持简洁 */
|
||
}
|
||
|
||
/* 重新设计的API Key输入组件 */
|
||
.api-key-container {
|
||
position: relative;
|
||
background: rgba(15, 26, 47, 0.05);
|
||
border-radius: 12px;
|
||
padding: 1rem;
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
box-shadow: inset 0 0 0 1px rgba(30, 64, 175, 0.1),
|
||
0 4px 20px rgba(0, 0, 0, 0.05);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.api-key-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
|
||
.api-key-title {
|
||
display: flex;
|
||
align-items: center;
|
||
font-weight: 600;
|
||
color: #1e40af;
|
||
}
|
||
|
||
.api-key-icon {
|
||
background: rgba(59, 130, 246, 0.1);
|
||
padding: 0.5rem;
|
||
border-radius: 8px;
|
||
margin-right: 0.75rem;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.api-key-input-wrapper {
|
||
position: relative;
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
|
||
.api-key-input {
|
||
width: 100%;
|
||
padding: 0.75rem 3rem 0.75rem 1rem;
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||
background: rgba(255, 255, 255, 0.7);
|
||
transition: all 0.2s ease;
|
||
color: #1e293b;
|
||
font-family: monospace;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.api-key-input:focus {
|
||
border-color: rgba(59, 130, 246, 0.5);
|
||
background: rgba(255, 255, 255, 0.9);
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
||
}
|
||
|
||
.api-key-toggle {
|
||
position: absolute;
|
||
right: 0.5rem;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background: none;
|
||
border: none;
|
||
color: #64748b;
|
||
cursor: pointer;
|
||
padding: 0.25rem;
|
||
border-radius: 4px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.api-key-toggle:hover {
|
||
color: #3b82f6;
|
||
background: rgba(59, 130, 246, 0.1);
|
||
}
|
||
|
||
.api-key-status {
|
||
font-size: 0.85rem;
|
||
padding: 0.4rem 0.75rem;
|
||
border-radius: 6px;
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
|
||
.api-key-status.valid {
|
||
background: rgba(16, 185, 129, 0.1);
|
||
color: #065f46;
|
||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||
}
|
||
|
||
.api-key-status.invalid {
|
||
background: rgba(239, 68, 68, 0.1);
|
||
color: #991b1b;
|
||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||
}
|
||
|
||
.api-key-buttons {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.api-key-button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 6px;
|
||
font-weight: 500;
|
||
transition: all 0.2s ease;
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
|
||
.api-key-save {
|
||
background: linear-gradient(180deg, rgba(37, 99, 235, 0.9), rgba(30, 64, 175, 1));
|
||
color: white;
|
||
}
|
||
|
||
.api-key-save:hover {
|
||
background: linear-gradient(180deg, rgba(37, 99, 235, 1), rgba(30, 64, 175, 0.9));
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.api-key-clear {
|
||
background: transparent;
|
||
color: #64748b;
|
||
border: 1px solid rgba(100, 116, 139, 0.3);
|
||
}
|
||
|
||
.api-key-clear:hover {
|
||
background: rgba(100, 116, 139, 0.1);
|
||
color: #475569;
|
||
}
|
||
|
||
.api-key-test {
|
||
background: transparent;
|
||
color: #3b82f6;
|
||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||
}
|
||
|
||
.api-key-test:hover {
|
||
background: rgba(59, 130, 246, 0.1);
|
||
color: #2563eb;
|
||
}
|
||
|
||
.api-key-notification {
|
||
position: fixed;
|
||
top: 1rem;
|
||
right: 1rem;
|
||
padding: 0.75rem 1rem;
|
||
border-radius: 8px;
|
||
background: white;
|
||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
z-index: 50;
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
transition: all 0.3s ease;
|
||
max-width: 350px;
|
||
}
|
||
|
||
.api-key-notification.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.api-key-notification.success {
|
||
border-left: 4px solid #10b981;
|
||
}
|
||
|
||
.api-key-notification.error {
|
||
border-left: 4px solid #ef4444;
|
||
}
|
||
|
||
.api-key-notification.warning {
|
||
border-left: 4px solid #f59e0b;
|
||
}
|
||
|
||
/* 管理API按钮增强 */
|
||
.manage-api-btn {
|
||
background: rgba(59, 130, 246, 0);
|
||
color: #3b82f6;
|
||
padding: 0.35rem 0.75rem;
|
||
border-radius: 6px;
|
||
font-size: 1rem;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.manage-api-btn:hover {
|
||
background: rgba(59, 130, 246, 0);
|
||
color: #2563eb;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.manage-api-btn svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
/* 自定义通知系统 */
|
||
.custom-alert-container {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 9999;
|
||
pointer-events: none;
|
||
width: 360px;
|
||
max-width: calc(100vw - 40px);
|
||
}
|
||
|
||
.custom-alert {
|
||
position: relative;
|
||
margin-bottom: 10px;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
border-radius: 10px;
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.5) inset;
|
||
overflow: hidden;
|
||
opacity: 0;
|
||
transform: translateX(20px) translateY(-5px);
|
||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
pointer-events: auto;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
padding: 0;
|
||
}
|
||
|
||
.custom-alert.show {
|
||
opacity: 1;
|
||
transform: translateX(0) translateY(0);
|
||
}
|
||
|
||
.custom-alert-icon {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 50px;
|
||
height: 100%;
|
||
padding: 16px 0 16px 16px;
|
||
}
|
||
|
||
.custom-alert-icon svg {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
.custom-alert-content {
|
||
padding: 16px;
|
||
flex: 1;
|
||
}
|
||
|
||
.custom-alert-message {
|
||
margin: 0;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.custom-alert-close {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
background: transparent;
|
||
border: none;
|
||
padding: 0;
|
||
width: 20px;
|
||
height: 20px;
|
||
cursor: pointer;
|
||
opacity: 0.5;
|
||
transition: opacity 0.2s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.custom-alert-close:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
.custom-alert-close svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.custom-alert-progress {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 3px;
|
||
background: rgba(59, 130, 246, 0.2);
|
||
}
|
||
|
||
.custom-alert-progress::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
height: 100%;
|
||
width: 100%;
|
||
background: linear-gradient(90deg, #3b82f6, #2563eb);
|
||
transform-origin: left;
|
||
transform: scaleX(0);
|
||
}
|
||
|
||
.custom-alert.info .custom-alert-icon {
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.custom-alert.info .custom-alert-progress::after {
|
||
background: linear-gradient(90deg, #3b82f6, #1d4ed8);
|
||
}
|
||
|
||
.custom-alert.success .custom-alert-icon {
|
||
color: #10b981;
|
||
}
|
||
|
||
.custom-alert.success .custom-alert-progress::after {
|
||
background: linear-gradient(90deg, #10b981, #059669);
|
||
}
|
||
|
||
.custom-alert.error .custom-alert-icon {
|
||
color: #ef4444;
|
||
}
|
||
|
||
.custom-alert.error .custom-alert-progress::after {
|
||
background: linear-gradient(90deg, #ef4444, #b91c1c);
|
||
}
|
||
|
||
.custom-alert.warning .custom-alert-icon {
|
||
color: #f59e0b;
|
||
}
|
||
|
||
.custom-alert.warning .custom-alert-progress::after {
|
||
background: linear-gradient(90deg, #f59e0b, #d97706);
|
||
}
|
||
|
||
.custom-alert-content h4 {
|
||
margin: 0 0 5px 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.custom-alert-content p {
|
||
margin: 0;
|
||
color: #64748b;
|
||
}
|
||
|
||
@keyframes progress {
|
||
from {
|
||
transform: scaleX(0);
|
||
}
|
||
to {
|
||
transform: scaleX(1);
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="bg-gradient-animated text-slate-800 font-sans min-h-screen">
|
||
<!-- 激光效果 -->
|
||
<div class="laser-container">
|
||
<div class="glow"></div>
|
||
<div class="glow"></div>
|
||
</div>
|
||
|
||
<div class="max-w-4xl mx-auto p-4 relative">
|
||
<div class="content-area p-6 shadow-xl my-8 rounded-xl backdrop-blur-xl bg-white/70">
|
||
<header class="text-center mb-8 py-4">
|
||
<h1 class="text-4xl font-bold mb-2 text-slate-800">文本转语音 (TTS)</h1>
|
||
<p class="text-xl text-slate-600 mb-4">将文本转换为自然流畅的语音</p>
|
||
|
||
<!-- 新Tab栏设计 -->
|
||
<div class="tab-bar mt-6">
|
||
<span class="tab-indicator" style="width: 50%;"></span>
|
||
<a href="{{.BasePath}}/" class="tab-item active">
|
||
<span class="tab-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||
stroke="currentColor" stroke-width="2">
|
||
<path stroke-linecap="round" stroke-linejoin="round"
|
||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
|
||
</svg>
|
||
</span>
|
||
主页
|
||
</a>
|
||
<a href="{{.BasePath}}/api-doc" class="tab-item">
|
||
<span class="tab-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||
stroke="currentColor" stroke-width="2">
|
||
<path stroke-linecap="round" stroke-linejoin="round"
|
||
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
||
</svg>
|
||
</span>
|
||
API文档
|
||
</a>
|
||
</div>
|
||
</header>
|
||
|
||
<main>
|
||
<section class="panel-frosted bg-white/90 rounded-lg shadow-lg p-6 mb-6">
|
||
<div class="flex justify-end mb-4">
|
||
<button id="toggle-api-key" class="manage-api-btn inline-flex items-center gap-1.5 text-blue-600 bg-blue-50/80 px-3 py-1.5 rounded-md transition hover:bg-blue-100 hover:-translate-y-0.5">
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||
class="w-5 h-5">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
|
||
</svg>
|
||
<span id="api-key-text">设置</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 替换API Key输入区域 -->
|
||
<div id="api-key-group" class="api-key-container mb-6 hidden">
|
||
<div class="api-key-header">
|
||
<div class="api-key-title">
|
||
<div class="api-key-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none"
|
||
viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
|
||
</svg>
|
||
</div>
|
||
<span>API Key 设置</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="api-key-input-wrapper">
|
||
<input type="password" id="api-key" class="api-key-input" placeholder="输入您的API Key"
|
||
autocomplete="off">
|
||
<button id="toggle-password" class="api-key-toggle" title="显示/隐藏密钥">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none"
|
||
viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="api-key-buttons">
|
||
<button id="api-key-save" class="api-key-button api-key-save" onclick="saveApiKey()">保存</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="text-xl font-bold mb-4 pb-2 border-b border-slate-200 text-slate-800">输入文本</h2>
|
||
<div class="relative mb-4">
|
||
<textarea id="text" placeholder="输入要转换的文本..." rows="6" maxlength="5000"
|
||
class="w-full p-3 border border-slate-300 rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white/80 backdrop-blur-sm"></textarea>
|
||
<div class="absolute bottom-2 right-2 text-sm text-slate-500">
|
||
<span id="charCount" class="char-counter">0</span><span class="text-slate-500">/5000</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||
<div class="flex flex-col">
|
||
<label for="voice" class="mb-1 font-semibold text-slate-700">语音:</label>
|
||
<select id="voice"
|
||
class="p-2 border border-slate-300 rounded-md bg-white/90 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
|
||
<option value="loading" class="loading-text">加载中</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="flex flex-col">
|
||
<label for="style" class="mb-1 font-semibold text-slate-700">风格:</label>
|
||
<select id="style"
|
||
class="p-2 border border-slate-300 rounded-md bg-white/90 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
|
||
<option value="loading">加载中...</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 修改滑块组件HTML结构,以适应新样式 -->
|
||
<div class="flex flex-wrap -mx-2">
|
||
<div class="w-full md:w-1/2 px-2 mb-4">
|
||
<div class="slider-label">
|
||
<label for="rate" class="font-semibold text-slate-700">语速:</label>
|
||
<span class="text-xs text-slate-500 ml-2">(调节语音的快慢程度)</span>
|
||
</div>
|
||
<div class="flex items-center">
|
||
<input type="range" id="rate" min="-100" max="100" value="0"
|
||
class="w-full mr-2 focus:outline-none rate-slider accent-blue-600">
|
||
<span id="rateValue" class="text-sm text-slate-600 range-value">0%</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="w-full md:w-1/2 px-2 mb-4">
|
||
<div class="slider-label">
|
||
<label for="pitch" class="font-semibold text-slate-700">语调:</label>
|
||
<span class="text-xs text-slate-500 ml-2">(调节语音的高低音)</span>
|
||
</div>
|
||
<div class="flex items-center">
|
||
<input type="range" id="pitch" min="-100" max="100" value="0"
|
||
class="w-full mr-2 focus:outline-none pitch-slider accent-blue-600">
|
||
<span id="pitchValue" class="text-sm text-slate-600 range-value">0%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex justify-center mt-6">
|
||
<button id="speak"
|
||
class="btn-neo btn-neo-primary btn-neo-pulse btn-neo-icon inline-flex items-center gap-2 bg-gradient-to-b from-blue-500 to-blue-700 hover:from-blue-600 hover:to-blue-800 text-white px-5 py-2.5 rounded-md font-medium shadow-lg shadow-blue-500/20 hover:shadow-blue-500/40 transition hover:-translate-y-0.5 active:translate-y-0 focus:outline-none focus:ring-2 focus:ring-blue-500/50">
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||
stroke-width="2" class="w-5 h-5">
|
||
<path stroke-linecap="round" stroke-linejoin="round"
|
||
d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/>
|
||
</svg>
|
||
转换为语音
|
||
</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="resultSection" class="panel-frosted bg-white/90 rounded-lg shadow-lg p-6 mb-6 hidden">
|
||
<h2 class="text-xl font-bold mb-4 pb-2 border-b border-slate-200 text-slate-800">语音输出</h2>
|
||
<div class="flex flex-col items-center">
|
||
<audio id="audioPlayer" controls class="w-full mb-4"></audio>
|
||
<div class="flex flex-wrap justify-center gap-2">
|
||
<button id="download"
|
||
class="btn-neo btn-neo-secondary btn-neo-icon inline-flex items-center gap-1.5 bg-slate-100 hover:bg-slate-200 text-slate-700 px-4 py-2 rounded-md transition hover:-translate-y-0.5 active:translate-y-0 shadow border border-slate-200">
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||
stroke="currentColor" stroke-width="2" class="w-5 h-5">
|
||
<path stroke-linecap="round" stroke-linejoin="round"
|
||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||
</svg>
|
||
下载音频
|
||
</button>
|
||
|
||
<button id="copyLink"
|
||
class="btn-neo btn-neo-secondary btn-neo-icon inline-flex items-center gap-1.5 bg-slate-100 hover:bg-slate-200 text-slate-700 px-4 py-2 rounded-md transition hover:-translate-y-0.5 active:translate-y-0 shadow border border-slate-200">
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||
stroke="currentColor" stroke-width="2" class="w-5 h-5">
|
||
<path stroke-linecap="round" stroke-linejoin="round"
|
||
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/>
|
||
</svg>
|
||
复制链接
|
||
</button>
|
||
|
||
<button id="copyHttpTtsLink"
|
||
class="btn-neo btn-neo-secondary btn-neo-icon inline-flex items-center gap-1.5 bg-slate-100 hover:bg-slate-200 text-slate-700 px-4 py-2 rounded-md transition hover:-translate-y-0.5 active:translate-y-0 shadow border border-slate-200">
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||
stroke="currentColor" stroke-width="2" class="w-5 h-5">
|
||
<path stroke-linecap="round" stroke-linejoin="round"
|
||
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
||
</svg>
|
||
复制HttpTTS链接
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer class="text-center mt-10 py-4 text-slate-600 text-sm">
|
||
<p>© 2025 TTS服务 | <a href="{{.BasePath}}/api-doc" class="text-blue-500 hover:underline">API文档</a></p>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 自定义通知容器 -->
|
||
<div id="custom-alert-container" class="custom-alert-container"></div>
|
||
|
||
<script>
|
||
// 存储一些全局配置
|
||
const config = {
|
||
basePath: "{{.BasePath}}",
|
||
defaultVoice: "{{.DefaultVoice}}",
|
||
defaultRate: "{{.DefaultRate}}",
|
||
defaultPitch: "{{.DefaultPitch}}",
|
||
defaultStyle: "{{.DefaultStyle}}"
|
||
};
|
||
|
||
// 添加动态激光效果
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
const laserContainer = document.querySelector('.laser-container');
|
||
|
||
// 清除任何现有的激光束
|
||
while (laserContainer.lastChild) {
|
||
laserContainer.removeChild(laserContainer.lastChild);
|
||
}
|
||
|
||
// 保留发光效果
|
||
const glow1 = document.createElement('div');
|
||
glow1.className = 'glow';
|
||
glow1.style.top = '-30%';
|
||
glow1.style.left = '-10%';
|
||
laserContainer.appendChild(glow1);
|
||
|
||
const glow2 = document.createElement('div');
|
||
glow2.className = 'glow';
|
||
glow2.style.bottom = '-30%';
|
||
glow2.style.right = '-10%';
|
||
laserContainer.appendChild(glow2);
|
||
|
||
// 添加更随机方向的激光束,同时确保不会导致水平溢出
|
||
const laserCount = 8;
|
||
for (let i = 0; i < laserCount; i++) {
|
||
const laser = document.createElement('div');
|
||
laser.className = 'laser-beam';
|
||
|
||
// 随机高度
|
||
const height = Math.random() * 2 + 1;
|
||
laser.style.height = `${height}px`;
|
||
|
||
// 随机位置
|
||
laser.style.top = `${Math.random() * 100}%`;
|
||
|
||
// 随机角度 (限制角度范围,防止水平溢出)
|
||
const angle = Math.random() * 160 + 10; // 10-170度,避免接近水平的角度
|
||
|
||
// 静态定位,不使用动画
|
||
laser.style.transform = `rotate(${angle}deg)`;
|
||
|
||
// 随机不透明度
|
||
laser.style.opacity = Math.random() * 0.5 + 0.3;
|
||
|
||
laserContainer.appendChild(laser);
|
||
}
|
||
|
||
// 监听窗口大小变化,确保页面元素适应
|
||
window.addEventListener('resize', function () {
|
||
// 强制刷新激光和背景元素的布局
|
||
document.body.classList.add('layout-refresh');
|
||
setTimeout(() => document.body.classList.remove('layout-refresh'), 100);
|
||
});
|
||
});
|
||
|
||
// 增强滑块交互效果
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
// 为滑块增加视觉反馈
|
||
const rateSlider = document.getElementById('rate');
|
||
const pitchSlider = document.getElementById('pitch');
|
||
|
||
function updateSliderBackground(slider) {
|
||
// 计算填充百分比
|
||
const min = parseFloat(slider.min);
|
||
const max = parseFloat(slider.max);
|
||
const val = parseFloat(slider.value);
|
||
const percentage = ((val - min) / (max - min)) * 100;
|
||
|
||
// 创建渐变背景,从中间的0点向两边扩散
|
||
let gradient;
|
||
if (val < 0) {
|
||
// 负值 - 从中点到左侧
|
||
const middlePos = 50; // 0值在滑块中间
|
||
const fillPos = middlePos + (percentage / 2); // 填充位置
|
||
gradient = `linear-gradient(to right, rgba(59, 130, 246, 0.1) ${fillPos}%, rgba(13, 47, 98, 0.2) ${fillPos}%)`;
|
||
} else {
|
||
// 正值或0 - 从中点到右侧
|
||
const middlePos = 50; // 0值在滑块中间
|
||
const fillPos = middlePos + (percentage / 2); // 填充位置
|
||
gradient = `linear-gradient(to right, rgba(13, 47, 98, 0.2) ${middlePos}%, rgba(59, 130, 246, 0.15) ${middlePos}%, rgba(59, 130, 246, 0.3) ${fillPos}%)`;
|
||
}
|
||
|
||
slider.style.background = gradient;
|
||
}
|
||
|
||
// 初始化滑块背景
|
||
updateSliderBackground(rateSlider);
|
||
updateSliderBackground(pitchSlider);
|
||
|
||
// 滑块值变化时更新背景
|
||
rateSlider.addEventListener('input', function () {
|
||
updateSliderBackground(this);
|
||
});
|
||
|
||
pitchSlider.addEventListener('input', function () {
|
||
updateSliderBackground(this);
|
||
});
|
||
});
|
||
|
||
// Tab栏交互效果
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
// 初始化Tab指示器的位置
|
||
const tabBar = document.querySelector('.tab-bar');
|
||
const activeTab = tabBar.querySelector('.active');
|
||
const indicator = tabBar.querySelector('.tab-indicator');
|
||
|
||
// 设置指示器初始位置和宽度来匹配活动tab
|
||
if (indicator && activeTab) {
|
||
indicator.style.width = `${activeTab.offsetWidth}px`;
|
||
indicator.style.transform = `translateX(${activeTab.offsetLeft}px)`;
|
||
}
|
||
|
||
// 当窗口大小改变时调整指示器
|
||
window.addEventListener('resize', function () {
|
||
if (indicator && activeTab) {
|
||
indicator.style.width = `${activeTab.offsetWidth}px`;
|
||
indicator.style.transform = `translateX(${activeTab.offsetLeft}px)`;
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |