<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文字之墙:博客随笔 - 藤原的数字空间</title>
<!-- 引入Font Awesome图标库 --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
/* ================= 全局样式 ================= */
@import url("https://fonts.cdnfonts.com/css/satoshi");
@import url("https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap");
* {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
scroll-behavior: smooth;
}
html, body {
min-height: 100%;
}
body {
margin: 0;
font-family: "Satoshi", sans-serif, monospace;
color: #eee;
font-size: 18px;
line-height: 161.8%;
background: #212431;
interpolate-size: allow-keywords;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 14px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #ea5c1f;
border: 4px solid #212431;
cursor: pointer;
}
::-webkit-scrollbar-thumb:hover {
background: #ea5c1f77;
}
/* ================= 天气通知样式 ================= */
.weather-notification {
position: fixed;
top: 20px;
right: 20px;
width: 320px;
background: #0c1629;
border: 2px solid #212431;
border-radius: 15px;
padding: 16px;
z-index: 9999;
transform: translateX(calc(100% + 40px));
transition: transform 0.3s ease-in-out;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
image-rendering: pixelated;
}
.weather-notification.show {
transform: translateX(0);
}
.weather-notification-header {
background: #212431;
margin: -16px -16px 12px -16px;
padding: 12px 16px;
border-radius: 13px 13px 0 0;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid #212431;
}
.weather-notification-title {
color: #f5f5dc;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.weather-notification-close {
background: none;
border: none;
color: #f5f5dc;
cursor: pointer;
padding: 4px;
border-radius: 4px;
font-size: 16px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.weather-notification-close:hover {
background: rgba(245, 245, 220, 0.2);
}
.weather-notification-content {
color: #f5f5dc;
font-size: 13px;
line-height: 1.4;
margin-bottom: 12px;
}
.welcome-line {
margin-bottom: 8px;
font-size: 14px;
}
.weather-info-line {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 5px;
}
.weather-icon {
width: 24px;
height: 24px;
object-fit: contain;
}
.weather-temp {
font-weight: bold;
font-size: 16px;
margin-right: 5px;
}
.weather-extra {
font-size: 12px;
color: #eec7b7;
display: flex;
gap: 15px;
}
.weather-notification-progress {
height: 3px;
background: rgba(245, 245, 220, 0.2);
border-radius: 2px;
overflow: hidden;
margin: -4px -16px -16px -16px;
}
.weather-notification-progress-bar {
height: 100%;
background: #3a86ff;
width: 100%;
transform-origin: left;
animation: countdown 4s linear forwards;
}
@keyframes countdown {
0% {
transform: scaleX(1);
}
100% {
transform: scaleX(0);
}
}
/* ================= 安全通知样式 ================= */
.security-notification {
position: fixed;
top: 20px;
right: 20px;
width: 320px;
background: #0c1629;
border: 2px solid #212431;
border-radius: 15px;
padding: 16px;
z-index: 10000;
transform: translateX(calc(100% + 40px));
transition: transform 0.3s ease-in-out;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
image-rendering: pixelated;
}
.security-notification.show {
transform: translateX(0);
}
.security-notification-header {
background: #212431;
margin: -16px -16px 12px -16px;
padding: 12px 16px;
border-radius: 13px 13px 0 0;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid #212431;
}
.security-notification-title {
color: #f5f5dc;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.security-notification-close {
background: none;
border: none;
color: #f5f5dc;
cursor: pointer;
padding: 4px;
border-radius: 4px;
font-size: 16px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.security-notification-close:hover {
background: rgba(245, 245, 220, 0.2);
}
.security-notification-content {
color: #f5f5dc;
font-size: 13px;
line-height: 1.4;
margin-bottom: 12px;
}
.security-notification-progress {
height: 3px;
background: rgba(245, 245, 220, 0.2);
border-radius: 2px;
overflow: hidden;
margin: -4px -16px -16px -16px;
}
.security-notification-progress-bar {
height: 100%;
background: #ea5c1f;
width: 100%;
transform-origin: left;
animation: countdown 4s linear forwards;
}
.security-notification.success .security-notification-progress-bar {
background: #4caf50;
}
.security-notification.error .security-notification-progress-bar {
background: #f44336;
}
@keyframes countdown {
0% {
transform: scaleX(1);
}
100% {
transform: scaleX(0);
}
}
/* 猫咪图标样式 */
.security-cat-icon {
width: 16px;
height: 16px;
background-size: cover;
image-rendering: pixelated;
}
.security-cat-icon.success {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAJtJREFUOE9jXPHu/38GKgLGUQPhobneSoAh8NgHrKGLTw5rGII0rLj+gSFCE9NQfHIg2zEMhGmAOQ3ZUHxyMPW0NRDdBciuBLFBwYAO0IMFxYW4DMSX7nEaSI5h2MIZ7kJsAY7NiyBDQK5ClkN2Jf0MRPYOzDXoLsPrZZAk1WMZZiiuJIIc2yDXggB69sRZfIFciw/gyucjsDwEAN3HvKW99h2gAAAAAElFTkSuQmCC);
}
.security-cat-icon.error {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAL5JREFUOE9j/H+z4D8DFQHjqIHw0Izwn8+wYmMi1tDFJ4c1DMEarn9giNAUwDAUJDd1Yj5Ddv5ErBZiGAgzDOY0ZENhhsHksBlKWwPRXYfsShAb5FV0gO5KFBfiMhBkyNtd9VgjCKeB+AzDZyBIDtlQuAuxRsb1DyiugrkSZACy98k2EOZSsgxESTZQl4KTEJSNHKZYXQgygFAswwxDDgf0xI81HYI0YNOMbhBYHVr2xFl8gVyLD+DK5yOwPAQAKPjFoR4p7DEAAAAASUVORK5CYII=);
}
.security-cat-icon.warning {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAJ9JREFUOE9j/H9jyn8GKgLGUQPhoRkRUM2wYkMr1tDFJ4c1DEEacuffZ5icqIhhKD45kO0YBsI0wJyGbCg+OZh62hqI7gJkV4LYoGBAB+jBguJCXAbiS/c4DSTHMGzhDHchsoEgW2EA3ZvY5JBdidVAUrM2SQaCFMNcicxGthSrgSAFVI9lmKG4kgi6q0B89OyJs/gCuRYfwJXPR2B5CABijLNdGjYfkwAAAABJRU5ErkJggg==);
}
/* ================= 文章容器 ================= */
article {
max-width: 618px;
padding: 8px 24px;
margin: auto;
margin-top: 100px;
margin-bottom: 100px;
font-weight: 100;
position: relative;
overflow: hidden;
border-bottom: 4px solid #d6d7d711;
border-left: 4px solid #d6d7d711;
background: linear-gradient(90deg, #46506433 0%, #4f5d7533);
border-top-right-radius: 64px;
}
/* ================= 顶部区域 ================= */
article h1 {
position: absolute;
margin: 0;
top: 0;
padding: 16px 22px 16px 0px;
background: #292f3e;
border-bottom-right-radius: 24px;
z-index: 2;
line-height: 100%;
color: #ea5c1f;
font-size: 1.8em;
}
article .date {
position: absolute;
margin: 0;
bottom: -28px;
padding: 0px 24px 8px 0px;
border-bottom-right-radius: 22px;
z-index: 2;
color: #fff;
font-size: 16px;
font-weight: 400;
background: #292f3e;
}
/* 特色图片区域 */
.featured-image {
width: 100%;
height: 324px;
background: url("https://picsum.photos/600/400?grayscale");
background-size: cover;
background-position: 50% 16.18%;
margin-top: 16px;
border-top-right-radius: 42px;
border-bottom: 4px solid #ea5c1f;
position: relative;
z-index: 1;
}
.featured-image .expand {
position: absolute;
right: 0px;
bottom: -4px;
color: #fff;
cursor: pointer;
background: #292f3e;
border-top-left-radius: 32px;
width: 64px;
height: 64px;
border-left: 4px solid #ea5c1f;
border-top: 4px solid #ea5c1f;
}
.featured-image .expand::before {
content: "+";
display: flex;
position: absolute;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
font-size: 2em;
font-weight: bold;
color: #ea5c1f;
transition: all 0.4s ease-in-out;
}
.featured-image .expand.close::before {
content: "-";
}
.featured-image .expand:hover::before {
transform: rotate(90deg);
}
.featured-image .expand.close:hover::before {
transform: rotate(0deg);
}
/* ================= 中部内容区域 ================= */
article .content {
transition: height 1s ease-out;
visibility: visible;
height: 0;
overflow: clip;
}
article:has(.expand.close) .content {
transition: height 1s ease-in;
height: auto;
}
article aside {
border-left: 4px solid #ea5c1f;
padding-left: 16px;
margin-bottom: 16px;
line-height: 124%;
}
article strong {
font-weight: 600;
}
article a {
font-weight: 500;
color: #ea5c1f;
text-decoration: none;
cursor: pointer;
}
article a:hover {
text-decoration: underline;
}
.dotmap {
width: 100%;
height: min(30vw, 200px);
background: linear-gradient(#fff0, #fff4, #fff0);
mask-image: url("https://assets.codepen.io/3421562/dotmap.svg");
position: relative;
}
.dotmap::before {
scale: 0;
opacity: 0;
transition: all 0.2s ease-out;
content: "";
display: block;
position: absolute;
width: 100px;
height: 100px;
border-radius: 200px;
background: #ea5c1f;
filter: blur(40px);
top: var(--y);
left: var(--x);
translate: -50% -50%;
}
.dotmap:hover::before {
scale: 1;
opacity: 1;
}
.dotmap::after {
content: "";
display: block;
position: absolute;
width: 100%;
height: 100%;
background: url("https://assets.codepen.io/3421562/dotmap_h.svg");
top: 0;
left: 0;
}
.planet {
width: 100%;
height: min(30vw, 216px);
overflow: hidden;
position: relative;
background: url("https://assets.codepen.io/3421562/stars.svg");
background-size: contain;
background-blend-mode: color-burn;
box-shadow: 4px 6px 0 0 #212431, 0 8px 16px 0 #0000;
}
.planet::after {
display: block;
content: "";
width: min(60vw, 500px);
height: min(60vw, 500px);
box-shadow: -2px 0 12px 1px #ea5c1f44, inset 42px -142px 64px 1px #ea5c1faa,
inset -2px 2px 6px 1px #ea5c1f22, inset -8px 22px 16px 1px #ea5c1f22,
inset -42px 42px 42px 1px #212431, inset 0 0 100px 1px #fff3;
border-radius: 100%;
position: absolute;
right: -34%;
top: 6%;
background: #41322c;
}
.planet span {
position: absolute;
width: min(110vw, 800px);
height: min(110vw, 800px);
right: -46%;
top: -100%;
border-radius: 100%;
transform-style: preserve-3d;
transform: rotateX(75deg) rotateY(16deg) rotate3d(0, 0, 1, 0deg);
animation: pRev 20s linear infinite;
border: 2px solid #ea5c1f44;
}
@keyframes pRev {
to {
transform: rotateX(75deg) rotateY(16deg) rotate3d(0, 0, 1, 360deg);
}
}
.skyline {
width: 100%;
height: 200px;
overflow: hidden;
position: relative;
background: url("https://assets.codepen.io/3421562/skyline.svg");
background-size: max(100%, 500px);
background-position: 50% 12px;
background-repeat: no-repeat;
background-blend-mode: color-burn;
box-shadow: 0px 6px 0 0 #212431, 0 8px 16px 0 #0000;
}
/* ================= 底部区域 ================= */
article .foot {
display: flex;
gap: 24px;
align-items: center;
flex-wrap: wrap;
padding-bottom: 16px;
}
article .foot img {
max-width: 84px;
height: auto;
border: 4px solid #f5f5f5;
border-top-color: #ea5c1f;
border-right-color: #ea5c1f;
object-fit: contain;
cursor: pointer;
transition: all 0.3s ease;
}
article .foot img:hover {
transform: scale(1.05);
border-color: #ea5c1f;
box-shadow: 0 0 20px rgba(234, 92, 31, 0.3);
}
article .author a {
font-weight: 600;
}
article .author span {
font-size: 1.4em;
padding-right: 2px;
translate: 0 2px;
rotate: -33deg;
display: inline-block;
color: #eec7b7;
transition: all 0.2s ease-in-out;
}
article .author a:hover,
article .author a:hover span {
color: #ea5c1f;
rotate: 0deg;
}
/* ================= 猫咪终端样式 ================= */
:root {
--fontBody: Quicksand;
--accent: #ea5c1f;
--accentRGB: 234, 92, 31;
--support: #eec7b7;
--supportRGB: 238, 199, 183;
--feature: #a6b0b4;
--featureRGB: 166, 176, 180;
--dark: #eee;
--darkRGB: 238, 238, 238;
--darker: #292f3e;
--darkerRGB: 41, 47, 62;
--lighter: #212431;
--lighterRGB: 33, 36, 49;
--light: #46506433;
--lightRGB: 70, 80, 100;
--lightest: #ffffff;
--lightestRGB: 255, 255, 255;
--darkest: #101010;
--darkestRGB: 16, 16, 16;
--cat-bg: #212431;
--cat-brown: #000000;
--cat-light-brown: #212431;
--cat-bubble-bg: #f5f5dc;
--m: 2;
interpolate-size: allow-keywords;
}
/* 终端容器 - 猫咪风格 */
#terminal-container {
width: 100%;
max-height: 0;
background: var(--cat-bg);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
border: 2px solid var(--cat-brown);
display: flex;
flex-direction: column;
overflow: hidden;
margin-top: 20px;
transition: all 0.5s ease;
opacity: 0;
transform: translateY(-20px);
image-rendering: pixelated;
}
#terminal-container.show {
max-height: 600px;
opacity: 1;
transform: translateY(0);
}
/* 猫咪聊天区域 - 修复滚动和间距问题 */
.cat-chat-area {
background: var(--cat-light-brown);
height: 450px;
overflow-y: auto;
overflow-x: hidden;
display: block; /* 改为 block 布局 */
padding: 15px;
scrollbar-width: thin;
}
.cat-chat-area::-webkit-scrollbar {
width: 8px;
}
.cat-chat-area::-webkit-scrollbar-track {
background: transparent;
}
.cat-chat-area::-webkit-scrollbar-thumb {
background: var(--cat-brown);
border-radius: 4px;
}
/* 猫咪消息样式 - 修复AI头像靠左问题 */
.cat-message {
display: flex;
align-items: flex-end;
padding: 8px 0;
margin-bottom: 15px;
animation: forwards show-message 0.3s;
overflow: visible;
width: 100%;
box-sizing: border-box;
clear: both;
}
/* AI消息(头像在左侧) */
.cat-message:not(.user) {
justify-content: flex-start;
flex-direction: row;
}
/* 用户消息(头像在右侧) */
.cat-message.user {
justify-content: flex-end;
flex-direction: row-reverse;
}
@keyframes show-message {
0% {
opacity: 0;
transform: translateY(10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 猫咪头像 */
.cat-icon {
border-radius: 50%;
width: 40px;
height: 40px;
flex-shrink: 0;
animation: forwards open-up 0.3s;
background-size: cover;
image-rendering: pixelated;
margin: 0 8px;
}
.cat-icon.white {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAJtJREFUOE9jXPHu/38GKgLGUQPhobneSoAh8NgHrKGLTw5rGII0rLj+gSFCE9NQfHIg2zEMhGmAOQ3ZUHxyMPW0NRDdBciuBLFBwYAO0IMFxYW4DMSX7nEaSI5h2MIZ7kJsAY7NiyBDQK5ClkN2Jf0MRPYOzDXoLsPrZZAk1WMZZiiuJIIc2yDXggB69sRZfIFciw/gyucjsDwEAN3HvKW99h2gAAAAAElFTkSuQmCC);
}
.cat-icon.biscuit {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAL5JREFUOE9j/H+z4D8DFQHjqIHw0Izwn8+wYmMi1tDFJ4c1DMEarn9giNAUwDAUJDd1Yj5Ddv5ErBZiGAgzDOY0ZENhhsHksBlKWwPRXYfsShAb5FV0gO5KFBfiMhBkyNtd9VgjCKeB+AzDZyBIDtlQuAuxRsb1DyiugrkSZACy98k2EOZSsgxESTZQl4KTEJSNHKZYXQgygFAswwxDDgf0xI81HYI0YNOMbhBYHVr2xFl8gVyLD+DK5yOwPAQAKPjFoR4p7DEAAAAASUVORK5CYII=);
}
.cat-icon.sleepy {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAJ9JREFUOE9j/H9jyn8GKgLGUQPhoRkRUM2wYkMr1tDFJ4c1DEEacuffZ5icqIhhKD45kO0YBsI0wJyGbCg+OZh62hqI7gJkV4LYoGBAB+jBguJCXAbiS/c4DSTHMGzhDHchsoEgW2EA3ZvY5JBdidVAUrM2SQaCFMNcicxGthSrgSAFVI9lmKG4kgi6q0B89OyJs/gCuRYfwJXPR2B5CABijLNdGjYfkwAAAABJRU5ErkJggg==);
}
.cat-icon.choco {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAJxJREFUOE9jnP/z3X8GKgLGUQPhoblDX5HB4+J9rKGLTw5rGII0TJ2Yz5CdPxHDUHxyINsxDIRpgDkN2VB8cjD1tDUQ3QXIrgSxQcGADtCDBcWFuAzEl+5xGkiOYdjCGe5CbAGOzYsgQ0CuQpZDdiX9DET2Dsw16C7D62WQJNVjGWYoriSCHNsg14IAevbEWXyBXIsP4MrnI7A8BAC9nrt5ix+41AAAAABJRU5ErkJggg==);
}
@keyframes open-up {
0% {
opacity: 0;
transform: scale(0);
}
100% {
opacity: 1;
transform: scale(1);
}
}
/* 猫咪聊天气泡 - 修复宽度和定位 */
.cat-speech-bubble {
position: relative;
padding: 16px 20px;
font-size: 14px;
color: var(--cat-brown);
transition: transform 0.3s;
--bg: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAC1JREFUKFNjZICC/////4exQTQjIyMjmAYR6JIwhSBFjLgk4YrooICgIwl5EwDcSx/52WJoSQAAAABJRU5ErkJggg==);
border-image: var(--bg) 3 fill / calc(var(--m) * 3px) / 0 stretch;
image-rendering: pixelated;
width: auto;
max-width: 65%;
min-width: 120px;
flex-shrink: 1;
animation: forwards open-up 0.3s;
word-wrap: break-word;
word-break: break-word;
line-height: 1.4;
white-space: pre-wrap;
}
/* AI消息气泡尾巴(指向左侧头像) */
.cat-message:not(.user) .cat-speech-bubble::after {
position: absolute;
content: '';
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAAXNSR0IArs4c6QAAACFJREFUGFdj/P///38GHIARJI5LAVgSlwK4JDYFKJLoCgD2lRPytGhzEwAAAABJRU5ErkJggg==);
background-size: cover;
width: calc(var(--m) * 7px);
height: calc(var(--m) * 5px);
left: calc(var(--m) * -5px);
bottom: calc(var(--m) * 6px);
right: auto;
}
/* 用户消息气泡(右侧) */
.cat-message.user .cat-speech-bubble {
position: relative;
padding: 16px 20px;
font-size: 14px;
color: var(--cat-brown);
transition: transform 0.3s;
--bg: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAC1JREFUKFNjZICC388O/oexQTSrlD0jiAYT6JIwhSBFjLgkYYrooYCgIwl5EwDwZh6NtjBAEgAAAABJRU5ErkJggg==);
border-image: var(--bg) 3 fill / calc(var(--m) * 3px) / 0 stretch;
image-rendering: pixelated;
width: auto;
max-width: 65%;
min-width: 120px;
flex-shrink: 1;
animation: forwards open-up 0.3s;
word-wrap: break-word;
word-break: break-word;
line-height: 1.4;
white-space: pre-wrap;
}
.cat-message.user .cat-speech-bubble::after {
position: absolute;
content: '';
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAAXNSR0IArs4c6QAAACdJREFUGFdj/P3s4H8GHIARlySrlD0jVkmQBMgwDEmYBIYksgRIEgBNEROnSu9X6wAAAABJRU5ErkJggg==);
background-size: cover;
width: calc(var(--m) * 7px);
height: calc(var(--m) * 5px);
left: calc(var(--m) * -5px);
bottom: calc(var(--m) * 6px);
right: auto;
transform: scaleX(-1); /* 添加水平翻转 */
}
/* 打字机效果 */
.cat-speech-bubble span {
opacity: 0;
animation: fade-in-text 0.1s forwards;
display: inline-block; /* 确保每个字符独立动画 */
}
/* 更平滑的逐字显示效果 */
.cat-speech-bubble span {
animation-delay: calc(0.05s * var(--char-index));
}
/* 打字动画 */
@keyframes fade-in-text {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 光标闪烁效果 */
.pulse {
display: inline-block;
width: 8px;
height: 16px;
background: var(--accent);
animation: blink-caret 0.75s step-end infinite;
vertical-align: middle;
margin-left: 2px;
}
@keyframes blink-caret {
from, to { opacity: 0; }
50% { opacity: 1; }
}
/* 终端头部 */
.cat-terminal-header {
background: var(--cat-brown);
padding: 12px;
border-radius: 15px 15px 0 0;
display: flex;
align-items: center;
gap: 8px;
border-bottom: 2px solid var(--cat-brown);
flex-shrink: 0;
}
.window-btn {
width: 12px;
height: 12px;
border-radius: 50%;
position: relative;
cursor: pointer;
transition: all 0.2s ease;
}
.window-btn:hover {
transform: scale(1.1);
}
.close { background: #ff5f56; }
.minimize { background: #ffbd2e; }
.maximize { background: #27c93f; }
.cat-title-bar {
color: var(--cat-bubble-bg);
margin-left: 20px;
font-size: 0.9em;
font-family: var(--fontBody);
font-weight: 600;
}
/* 猫咪输入区域 */
.cat-input-area {
background: #fff;
height: 50px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
border-radius: 0 0 15px 15px;
flex-shrink: 0;
}
.cat-input-wrapper {
position: relative;
width: calc(100% - 60px);
flex-shrink: 1;
height: 36px;
}
.cat-input-wrapper.load::before {
position: absolute;
content: '正在思考...';
width: 0px;
height: 100%;
transform-origin: left center;
animation: forwards load 1s;
background-color: rgba(242, 209, 151, 0.9);
display: flex;
align-items: center;
z-index: 3; /* 提高层级但不遮挡按钮 */
overflow: hidden;
color: var(--cat-brown);
animation-delay: 0.3s;
pointer-events: none;
border-radius: 4px;
font-size: 13px;
}
@keyframes load {
from {
padding: 0;
width: 0px;
}
to {
padding: 0 12px;
width: calc(100% - 10px); /* 留出空间不遮挡按钮 */
}
}
.cat-input-wrapper::after {
position: absolute;
content: '';
width: 100%;
height: 100%;
left: 0;
top: 0;
--bg: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAAXNSR0IArs4c6QAAAB5JREFUGFdj/P///39GRkZGBiQAFkMWQGZTUwKX5QCwtw//YgAXXwAAAABJRU5ErkJggg==);
border-image: var(--bg) 2 fill / calc(var(--m) * 2px) / 0 stretch;
image-rendering: pixelated;
z-index: 2;
pointer-events: none;
}
.cat-input-wrapper:has(input:focus):not(.load)::after {
--bg: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAAXNSR0IArs4c6QAAAClJREFUGFdj/P/////CrhMMyKC/zIKBsaDz+P8J5ZaMyBIgMWpK4LIcAGwlKPtGw39XAAAAAElFTkSuQmCC);
}
.cat-text-input {
border: 0;
background-color: #c5d1ef;
height: 36px;
padding: 0 12px;
width: 100%;
font-size: 1rem;
color: var(--cat-brown);
position: relative;
z-index: 1;
}
.cat-text-input:focus {
outline: none;
}
.cat-send-btn {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAASCAYAAABfJS4tAAAAAXNSR0IArs4c6QAAAJdJREFUOE9jZICCTxen/4exKaH59DMZQfrBBLUMhTkIZDgjPkNTwyvAamev7EDxBC5xZEU4DYZphimGGY5LHD346G8wyAVUCwpiwg5bisGmDx4UhMKOkOvR44KgwegWEhuZKJGH7ipchuIyHDlZEp0qcOVG9DQOU0dfgwkFAbrrsbkaq4uHnsHIuY7Y5IVRVtCs2KRVQQ8ADm+M8RqPRL8AAAAASUVORK5CYII=);
--w: 22px;
--h: 18px;
width: calc(var(--w) * var(--m));
height: calc(var(--h) * var(--m));
background-size: cover;
image-rendering: pixelated;
border: 0;
background-color: transparent;
cursor: pointer;
z-index: 4; /* 确保按钮在最上层 */
position: relative;
flex-shrink: 0;
}
.cat-send-btn:hover {
filter: brightness(0.8) sepia(1);
}
/* 工具容器样式 - 猫咪风格 */
.cat-tool-container {
background: var(--cat-light-brown);
border-radius: 15px;
margin: 15px 0;
border: 2px solid var(--cat-brown);
overflow: hidden;
width: 100%;
max-width: 100%;
image-rendering: pixelated;
}
.cat-tool-header {
background: var(--cat-brown);
padding: 12px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid var(--cat-brown);
}
.cat-tool-title {
color: var(--cat-bubble-bg);
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.cat-tool-close {
background: none;
border: none;
color: var(--cat-bubble-bg);
cursor: pointer;
padding: 4px;
border-radius: 4px;
font-size: 16px;
}
.cat-tool-close:hover {
background: rgba(255, 255, 255, 0.2);
}
.cat-tool-content {
padding: 20px;
width: 100%;
box-sizing: border-box;
color: var(--cat-brown);
}
/* 代码结果显示 */
.cat-code-result {
background: var(--cat-bubble-bg);
border: 2px solid var(--cat-brown);
border-radius: 8px;
padding: 15px;
margin: 15px 0;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
color: var(--cat-brown);
white-space: pre-wrap;
word-wrap: break-word;
max-height: 300px;
overflow-y: auto;
width: 100%;
box-sizing: border-box;
}
/* 视频容器 - 猫咪风格 */
.cat-video-container {
background: var(--cat-brown);
border-radius: 15px;
margin: 15px 0;
overflow: hidden;
border: 2px solid var(--cat-brown);
width: 100%;
}
.cat-video-player {
position: relative;
width: 100%;
height: 250px;
background: #000;
}
.cat-video-player video, .cat-video-player iframe {
width: 100%;
height: 100%;
object-fit: contain;
border: none;
}
.cat-video-controls {
background: var(--cat-light-brown);
padding: 10px;
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.cat-video-button {
background: var(--cat-brown);
color: var(--cat-bubble-bg);
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.cat-video-button:hover {
background: var(--support);
color: var(--cat-brown);
}
/* 加载动画 */
.cat-loader {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(87, 40, 15, 0.3);
border-radius: 50%;
border-top-color: var(--cat-brown);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 响应式设计 */
@media screen and (max-width: 860px) {
#terminal-container {
border-radius: 8px;
}
.cat-chat-area {
height: 350px;
padding: 10px;
}
article {
margin-top: 50px;
margin-bottom: 50px;
}
.security-notification {
width: 280px;
right: 10px;
top: 10px;
}
}
/* ================= 躲猫猫样式 ================= */
#maomao {
position: fixed;
bottom: 500px;
right: calc(50% - -305px); /* 根据博客宽度调整 */
width: 57px;
height: 70px;
z-index: 0; /* 设置为0,确保在博客主体下方 */
pointer-events: auto; /* 确保可以点击 */
transition: all 0.5s ease;
cursor: pointer;
}
#maomao svg {
width: 100%;
height: 100%;
}
#maomao:hover {
transform: scale(1.1);
}
/* 音乐播放器样式 */
#music-player-container {
position: fixed; /* 固定定位 */
bottom: 330px; /* 距离底部20px */
right: 350px; /* 距离右侧20px */
width: 430px; /* 宽度 */
height: 100px; /* 高度 */
z-index: 9999; /* 层级非常高 */
opacity: 0; /* 初始透明 */
transform: translateY(20px); /* 初始下移20px */
transition: all 0.3s ease; /* 过渡动画 */
}
/* 显示时的音乐播放器样式 */
#music-player-container.show {
opacity: 1; /* 完全不透明 */
transform: translateY(0); /* 回到原位 */
}
/* 专辑背景图样式 */
#player-bg-artwork {
position: fixed; /* 固定定位 */
top: -30px; /* 上边距 */
right: -30px; /* 右边距 */
bottom: -30px; /* 下边距 */
left: -30px; /* 左边距 */
background-image: url("https://raw.githubusercontent.com/himalayasingh/music-player-1/master/img/_1.jpg");
background-repeat: no-repeat; /* 不重复 */
background-size: cover; /* 覆盖整个区域 */
background-position: 50%; /* 居中 */
filter: blur(40px); /* 模糊效果 */
-webkit-filter: blur(40px); /* 兼容webkit */
z-index: 1; /* 层级 */
}
/* 背景遮罩层 */
#player-bg-layer {
position: fixed; /* 固定定位 */
top: 0; /* 顶部对齐 */
right: 0; /* 右侧对齐 */
bottom: 0; /* 底部对齐 */
left: 0; /* 左侧对齐 */
background-color: #fff; /* 白色背景 */
opacity: 0.5; /* 半透明 */
z-index: 2; /* 层级 */
}
/* 播放器主容器 */
#player {
position: relative; /* 相对定位 */
height: 100%; /* 高度100% */
z-index: 3; /* 层级 */
}
/* 播放器轨道信息区域 */
#player-track {
position: absolute; /* 绝对定位 */
top: 0; /* 顶部对齐 */
right: 15px; /* 右边距 */
left: 15px; /* 左边距 */
padding: 13px 22px 10px 184px; /* 内边距 */
background-color: #fff7f7; /* 背景色 */
border-radius: 15px 15px 0 0; /* 圆角 */
transition: 0.3s ease top; /* 顶部过渡动画 */
z-index: 1; /* 层级 */
}
/* 激活状态的轨道信息区域 */
#player-track.active {
top: -92px; /* 上移92px */
}
/* 专辑名称样式 */
#album-name {
color: #54576f; /* 文字颜色 */
font-size: 17px; /* 字体大小 */
font-weight: bold; /* 加粗 */
}
/* 曲目名称样式 */
#track-name {
color: #acaebd; /* 文字颜色 */
font-size: 13px; /* 字体大小 */
margin: 2px 0 13px 0; /* 外边距 */
}
/* 时间显示区域 */
#track-time {
height: 12px; /* 高度 */
margin-bottom: 3px; /* 下边距 */
overflow: hidden; /* 溢出隐藏 */
}
/* 当前时间 */
#current-time {
float: left; /* 左浮动 */
}
/* 总时长 */
#track-length {
float: right; /* 右浮动 */
}
/* 时间显示通用样式 */
#current-time,
#track-length {
color: transparent; /* 文字透明 */
font-size: 11px; /* 字体大小 */
background-color: #ffe8ee; /* 背景色 */
border-radius: 10px; /* 圆角 */
transition: 0.3s ease all; /* 过渡动画 */
}
/* 激活状态的时间显示 */
#track-time.active #current-time,
#track-time.active #track-length {
color: #f86d92; /* 文字颜色 */
background-color: transparent; /* 透明背景 */
}
/* 进度条容器和进度条 */
#seek-bar-container,
#seek-bar {
position: relative; /* 相对定位 */
height: 4px; /* 高度 */
border-radius: 4px; /* 圆角 */
}
/* 进度条容器 */
#seek-bar-container {
background-color: #ffe8ee; /* 背景色 */
cursor: pointer; /* 鼠标指针 */
}
/* 悬停时间提示 */
#seek-time {
position: absolute; /* 绝对定位 */
top: -29px; /* 上边距 */
color: #fff; /* 白色文字 */
font-size: 12px; /* 字体大小 */
white-space: pre; /* 保留空白 */
padding: 5px 6px; /* 内边距 */
border-radius: 4px; /* 圆角 */
display: none; /* 默认隐藏 */
}
/* 悬停区域 */
#s-hover {
position: absolute; /* 绝对定位 */
top: 0; /* 顶部对齐 */
bottom: 0; /* 底部对齐 */
left: 0; /* 左侧对齐 */
opacity: 0.2; /* 透明度 */
z-index: 2; /* 层级 */
}
/* 悬停时间和悬停区域共用样式 */
#seek-time,
#s-hover {
background-color: #3b3d50; /* 背景色 */
}
/* 进度条 */
#seek-bar {
content: ""; /* 伪元素内容 */
position: absolute; /* 绝对定位 */
top: 0; /* 顶部对齐 */
bottom: 0; /* 底部对齐 */
left: 0; /* 左侧对齐 */
width: 0; /* 初始宽度0 */
background-color: #fd6d94; /* 背景色 */
transition: 0.2s ease width; /* 宽度过渡动画 */
z-index: 1; /* 层级 */
}
/* 播放器内容区域 */
#player-content {
position: relative; /* 相对定位 */
height: 100%; /* 高度100% */
background-color: #fff; /* 白色背景 */
box-shadow: 0 30px 80px #656565; /* 阴影 */
border-radius: 15px; /* 圆角 */
z-index: 2; /* 层级 */
}
/* 专辑封面 */
#album-art {
position: absolute; /* 绝对定位 */
top: -40px; /* 上移40px */
width: 115px; /* 宽度 */
height: 115px; /* 高度 */
margin-left: 40px; /* 左边距 */
transform: rotateZ(0); /* 初始旋转角度 */
transition: 0.3s ease all; /* 过渡动画 */
box-shadow: 0 0 0 10px #fff; /* 阴影 */
border-radius: 50%; /* 圆形 */
overflow: hidden; /* 溢出隐藏 */
}
/* 激活状态的专辑封面 */
#album-art.active {
top: -60px; /* 上移60px */
box-shadow: 0 0 0 4px #fff7f7, 0 30px 50px -15px #afb7c1; /* 阴影 */
}
/* 专辑封面中心点 */
#album-art:before {
content: ""; /* 伪元素内容 */
position: absolute; /* 绝对定位 */
top: 50%; /* 垂直居中 */
right: 0; /* 右侧对齐 */
left: 0; /* 左侧对齐 */
width: 20px; /* 宽度 */
height: 20px; /* 高度 */
margin: -10px auto 0 auto; /* 外边距 */
background-color: #d6dee7; /* 背景色 */
border-radius: 50%; /* 圆形 */
box-shadow: inset 0 0 0 2px #fff; /* 内阴影 */
z-index: 2; /* 层级 */
}
/* 专辑封面图片 */
#album-art img {
display: block; /* 块级元素 */
position: absolute; /* 绝对定位 */
top: 0; /* 顶部对齐 */
left: 0; /* 左侧对齐 */
width: 100%; /* 宽度100% */
height: 100%; /* 高度100% */
opacity: 0; /* 初始透明 */
z-index: -1; /* 层级 */
}
/* 激活状态的专辑封面图片 */
#album-art img.active {
opacity: 1; /* 完全不透明 */
z-index: 1; /* 层级 */
}
/* 激活状态下的旋转动画 */
#album-art.active img.active {
z-index: 1; /* 层级 */
animation: rotateAlbumArt 3s linear 0s infinite forwards; /* 旋转动画 */
}
/* 旋转动画定义 */
@keyframes rotateAlbumArt {
0% {
transform: rotateZ(0); /* 初始角度 */
}
100% {
transform: rotateZ(360deg); /* 旋转360度 */
}
}
/* 缓冲提示框 */
#buffer-box {
position: absolute; /* 绝对定位 */
top: 50%; /* 垂直居中 */
right: 0; /* 右侧对齐 */
left: 0; /* 左侧对齐 */
height: 13px; /* 高度 */
color: #1f1f1f; /* 文字颜色 */
font-size: 13px; /* 字体大小 */
font-family: Helvetica; /* 字体 */
text-align: center; /* 文字居中 */
font-weight: bold; /* 加粗 */
line-height: 1; /* 行高 */
padding: 6px; /* 内边距 */
margin: -12px auto 0 auto; /* 外边距 */
background-color: rgba(255, 255, 255, 0.19); /* 背景色 */
opacity: 0; /* 初始透明 */
z-index: 2; /* 层级 */
}
/* 专辑图片和缓冲提示框共用过渡效果 */
#album-art img,
#buffer-box {
transition: 0.1s linear all; /* 过渡动画 */
}
/* 缓冲状态的专辑图片 */
#album-art.buffering img {
opacity: 0.25; /* 透明度 */
}
/* 缓冲状态的激活专辑图片 */
#album-art.buffering img.active {
opacity: 0.8; /* 透明度 */
filter: blur(2px); /* 模糊效果 */
-webkit-filter: blur(2px); /* 兼容webkit */
}
/* 缓冲状态的缓冲提示框 */
#album-art.buffering #buffer-box {
opacity: 1; /* 完全不透明 */
}
/* 播放控制区域 */
#player-controls {
width: 250px; /* 宽度 */
height: 100%; /* 高度100% */
margin: 0 5px 0 141px; /* 外边距 */
float: right; /* 右浮动 */
overflow: hidden; /* 溢出隐藏 */
}
/* 控制按钮容器 */
.control {
width: 33.333%; /* 宽度1/3 */
float: left; /* 左浮动 */
padding: 12px 0; /* 内边距 */
}
.button {
width: 50px; /* 宽度 */
height: 50px; /* 高度 */
padding: 20px; /* 内边距 */
background-color: #fff; /* 白色背景 */
border-radius: 6px; /* 圆角 */
cursor: pointer; /* 鼠标指针 */
display: flex; /* 新增:启用flex布局 */
justify-content: center; /* 新增:水平居中 */
align-items: center; /* 新增:垂直居中 */
}
/* 按钮图标 */
.button i {
display: block; /* 块级元素 */
color: #d6dee7; /* 图标颜色 */
font-size: 26px; /* 图标大小 */
text-align: center; /* 居中 */
line-height: 1; /* 行高 */
}
/* 按钮和图标共用过渡效果 */
.button,
.button i {
transition: 0.2s ease all; /* 过渡动画 */
}
/* 按钮悬停效果 */
.button:hover {
background-color: #d6d6de; /* 背景色变化 */
}
/* 按钮悬停时图标效果 */
.button:hover i {
color: #fff; /* 图标变白 */
}
</style>
</head>
<body>
<!-- 躲猫猫元素 -->
<div id="maomao" onMouseOut="duoMaomao()">
<svg width="228px" height="281px" viewBox="0 0 228 281" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>mao</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="mao" transform="translate(2.000000, 2.000000)">
<path d="M208,50 C122.666667,10 74.6666667,-6 64,2 C48,14 76,50 76,66 C76,82 68,82 60,106 C52,130 56,130 52,146 C48,162 0,166 0,190 C0,214 122.87565,230.238243 152,242 C171.416233,249.841171 190.0829,260.507838 208,274 L208,50 Z" stroke="#000000" stroke-width="4" fill="#000000"></path>
<circle id="eye" stroke="#FFFFFF" stroke-width="4" fill="#FFFFFF" cx="138" cy="82" r="18" opacity="0">
<animate attributeName="opacity" values="1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 0; 0; 0; 0; 0; 0;" dur="1s" repeatCount="indefinite" />
</circle>
<circle id="eye" stroke="#FFFFFF" stroke-width="4" fill="#FFFFFF" cx="110" cy="172" r="18" opacity="0">
<animate attributeName="opacity" values="1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 0; 0; 0; 0; 0; 0;" dur="1s" repeatCount="indefinite" />
</circle>
<line x1="120" y1="82" x2="156" y2="82" id="-y-" stroke="#FFFFFF" stroke-width="4" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" transform="translate(138.000000, 82.000000) rotate(105.000000) translate(-138.000000, -82.000000) "></line>
<line x1="92" y1="172" x2="128" y2="172" id="-y-" stroke="#FFFFFF" stroke-width="4" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" transform="translate(110.000000, 172.000000) rotate(105.000000) translate(-110.000000, -172.000000) "></line>
</g>
</g>
</svg>
</div>
<!-- 音乐播放器 -->
<div id="music-player-container">
<!-- 专辑背景图 -->
<div id="player-bg-artwork"></div>
<!-- 背景遮罩层 -->
<div id="player-bg-layer"></div>
<!-- 播放器主容器 -->
<div id="player">
<!-- 轨道信息区域 -->
<div id="player-track">
<!-- 专辑名称 -->
<div id="album-name"></div>
<!-- 曲目名称 -->
<div id="track-name"></div>
<!-- 时间显示 -->
<div id="track-time">
<div id="current-time"></div>
<div id="track-length"></div>
</div>
<!-- 进度条区域 -->
<div id="seek-bar-container">
<div id="seek-time"></div>
<div id="s-hover"></div>
<div id="seek-bar"></div>
</div>
</div>
<!-- 播放器内容区域 -->
<div id="player-content">
<!-- 专辑封面 -->
<div id="album-art">
<!-- 专辑图片(多张) -->
<img src="https://singhimalaya.github.io/Codepen/assets/img/album-arts/1.jpg" class="active" id="_1" />
<img src="https://singhimalaya.github.io/Codepen/assets/img/album-arts/2.jpg" id="_2" />
<img src="https://singhimalaya.github.io/Codepen/assets/img/album-arts/3.jpg" id="_3" />
<img src="https://singhimalaya.github.io/Codepen/assets/img/album-arts/4.jpg" id="_4" />
<img src="https://singhimalaya.github.io/Codepen/assets/img/album-arts/5.jpg" id="_5" />
<!-- 缓冲提示 -->
<div id="buffer-box">缓冲中...</div>
</div>
<!-- 播放控制按钮 -->
<div id="player-controls">
<!-- 上一首按钮 -->
<div class="control">
<div class="button" id="play-previous">
<i class="fas fa-backward"></i>
</div>
</div>
<!-- 播放/暂停按钮 -->
<div class="control">
<div class="button" id="play-pause-button">
<i class="fas fa-play"></i>
</div>
</div>
<!-- 下一首按钮 -->
<div class="control">
<div class="button" id="play-next">
<i class="fas fa-forward"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 安全通知弹窗 -->
<div id="security-notification" class="security-notification">
<div class="security-notification-header">
<div class="security-notification-title">
<span class="security-cat-icon"></span>
<span id="notification-title">安全通知</span>
</div>
<button class="security-notification-close" onclick="hideSecurityNotification()">✕</button>
</div>
<div class="security-notification-content" id="notification-content">
这里是安全通知内容
</div>
<div class="security-notification-progress">
<div class="security-notification-progress-bar"></div>
</div>
</div>
<!-- 天气通知弹窗 -->
<div id="weather-notification" class="weather-notification">
<div class="weather-notification-header">
<div class="weather-notification-title">
<span id="weather-notification-title">天气通知</span>
</div>
<button class="weather-notification-close" onclick="hideWeatherNotification()">✕</button>
</div>
<div class="weather-notification-content" id="weather-notification-content">
<div class="welcome-line" id="welcome-message">欢迎福建福州的用户</div>
<div class="weather-info-line">
<img src="https://rescdn.apihz.cn/resimg/tianqi/xiaoyu.png" alt="天气图标" class="weather-icon" id="weather-icon">
<span id="weather-condition">小雨</span>
<span class="weather-temp" id="weather-temp">26°C</span>
<div class="weather-extra">
<span>湿度: <span id="weather-humidity">75</span>%</span>
<span>风速: <span id="weather-wind">3</span>m/s</span>
</div>
</div>
</div>
<div class="weather-notification-progress">
<div class="weather-notification-progress-bar"></div>
</div>
</div>
<!-- ================= 文章整体结构 ================= -->
<article>
<!-- 顶部区域:标题和日期 -->
<h1>
<span id="ai-title">文字之墙:博客随笔</span>
<div class="date">2024年11月19日</div>
</h1>
<!-- 特色图片区域 -->
<div class="featured-image">
<div class="expand" onclick="this.classList.toggle('close')"></div>
</div>
<!-- 中部:主要内容区域 -->
<div class="content">
<p id="ai-content-1"></p>
<p id="ai-content-2"></p>
<!-- AI内容生成脚本 -->
<script>
function fillWithAIContent(prompt, elementId) {
const randomValue = Math.random().toString(36).substring(2, 8);
const uniquePrompt = `${prompt} unique-${randomValue}`;
fetch(`https://text.pollinations.ai/${encodeURIComponent(uniquePrompt)}`)
.then(response => response.text())
.then(data => {
const words = data.split(" ");
const wordCount = Math.min(words.length, 2);
const randomIndexes = Array.from({
length: wordCount
}, () =>
Math.floor(Math.random() * words.length)
);
randomIndexes.forEach(index => {
words[index] = `<strong>${words[index]}</strong>`;
});
document.getElementById(elementId).innerHTML = words.join(" ");
})
.catch(error => {
console.error("获取AI内容时出错:", error);
});
}
// 生成AI内容
fillWithAIContent("写一个不超过四个字的博客标题,关于日常生活中正念的好处", "ai-title");
fillWithAIContent("写一段关于日常生活中正念好处的简短介绍段落", "ai-content-1");
fillWithAIContent("写一段关于日常生活中正念好处的简短叙述段落", "ai-content-2");
</script>
<p>从前有个智者说,生活就像一本书,需要我们细细品读。当下即是永恒,每一个瞬间都值得珍惜。
<aside>
<p><strong>活在当下</strong>。正念让我们能够真正体验生活的每一个细节,不被过去困扰,不为未来焦虑。</p>
</aside>
当我们专注于呼吸,专注于此刻,心灵便得到了净化。正念不是逃避,而是更深地融入生活,感受每一刻的真实。</p>
<!-- 点阵图效果 -->
<div class="dotmap"></div>
<script>
function hoverFollow(target) {
target.addEventListener('mousemove', (event) => {
const rect = target.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
target.style.setProperty('--x', `${x}px`);
target.style.setProperty('--y', `${y}px`);
});
}
hoverFollow(document.querySelector('.dotmap'));
</script>
<h2>文字的力量</h2>
<p>文字是人类最伟大的发明之一,它承载着思想,传递着情感。<strong>用心书写</strong>的文字能够穿越时空,触动心灵。每一篇文章都是作者心血的结晶,每一个故事都值得被认真对待。</p>
<!-- 星球动画效果 -->
<div class="planet">
<span></span>
<span></span>
<span></span>
</div>
<h2>如何让文字更有魅力</h2>
<p>好的文字需要用心雕琢,就像匠人打磨艺术品一样。首先,要有清晰的思路;其次,选择恰当的词语;最重要的是,要倾注真情实感。</p>
<!-- 天际线效果 -->
<div class="skyline"></div>
<p>在这个信息爆炸的时代,静下心来写作变得越来越珍贵。文字不仅是表达的工具,更是思考的载体。当我们放慢脚步,用心书写,文字自然会散发出独特的光芒。</p>
<p>写作是一种修行,每一篇文章都是作者与世界对话的方式。无论长短,无论题材,真诚的文字总能找到它的读者。</p>
<p>让我们珍惜文字的力量,用它记录生活,表达思想,连接心灵。在这个快速变化的世界里,文字是我们永恒的伴侣。</p>
</div>
<!-- 底部区域:作者信息 -->
<div class="foot">
<img src="https://assets.codepen.io/3421562/internal/avatars/users/default.png"
alt="作者头像"
class="profile-card"
onclick="toggleTerminal()"
title="点击打开猫猫终端">
<div class="author">作者:<br><a href="#" target="_blank"><span>@</span>TENG YUAN</a></div>
</div>
<!-- 猫咪终端容器 -->
<div id="terminal-container">
<!-- 终端头部 -->
<div class="cat-terminal-header">
<span class="window-btn close" onclick="toggleTerminal()"></span>
<span class="window-btn minimize"></span>
<span class="window-btn maximize"></span>
<span class="cat-title-bar">🐱 猫猫AI终端</span>
</div>
<!-- 猫咪聊天区域 -->
<div class="cat-chat-area" id="catChatArea">
<!-- 欢迎消息 -->
<div class="cat-message">
<div class="cat-icon biscuit"></div>
<div class="cat-speech-bubble">欢迎来到猫猫AI终端!喵~ 🐱<br>输入 'help' 查看所有可用命令<br>我是你的AI助手,有什么可以帮助你的吗?
</div>
</div>
</div>
<!-- 猫咪输入区域 -->
<div class="cat-input-area">
<div class="cat-input-wrapper">
<input class="cat-text-input" type="text" placeholder="输入命令或消息..." id="catInput" />
</div>
<button class="cat-send-btn" onclick="sendCatMessage()"></button>
</div>
</div>
</article>
<!-- JavaScript代码 -->
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
<script>
// ================ API配置 ================
const API_CONFIG = {
id: '10003788',
key: 'ffa8afb46dce4916c5a74fd73c8de9f6',
endpoints: {
ai: 'https://cn.apihz.cn/api/ai/wxtiny.php',
weather: 'https://cn.apihz.cn/api/tianqi/tqybip.php',
ping: 'https://cn.apihz.cn/api/wangzhan/ping.php',
shortVideo: 'https://cn.apihz.cn/api/fun/douyin.php',
search: 'https://cn.apihz.cn/api/search/baidu.php'
}
};
// 音乐播放器功能实现
// 获取DOM元素
const playerTrack = $("#player-track");
const bgArtwork = $("#player-bg-artwork");
const albumName = $("#album-name");
const trackName = $("#track-name");
const albumArt = $("#album-art");
const sArea = $("#seek-bar-container");
const seekBar = $("#seek-bar");
const trackTime = $("#track-time");
const seekTime = $("#seek-time");
const sHover = $("#s-hover");
const playPauseButton = $("#play-pause-button");
const tProgress = $("#current-time");
const tTime = $("#track-length");
const playPreviousTrackButton = $("#play-previous");
const playNextTrackButton = $("#play-next");
const musicPlayerContainer = $("#music-player-container");
// 专辑数据
const albums = [
"Me & You",
"Dawn",
"Electro Boy",
"Home",
"Proxy (Original Mix)"
];
// 曲目数据
const trackNames = [
"Alex Skrindo - Me & You",
"Skylike - Dawn",
"Kaaze - Electro Boy",
"Jordan Schor - Home",
"Martin Garrix - Proxy"
];
// 专辑封面标识
const albumArtworks = ["_1", "_2", "_3", "_4", "_5"];
// 曲目URL
const trackUrl = [
"https://singhimalaya.github.io/Codepen/assets/music/1.mp3",
"https://singhimalaya.github.io/Codepen/assets/music/2.mp3",
"https://singhimalaya.github.io/Codepen/assets/music/3.mp3",
"https://singhimalaya.github.io/Codepen/assets/music/4.mp3",
"https://singhimalaya.github.io/Codepen/assets/music/5.mp3"
];
// 播放器状态变量
let bgArtworkUrl,
i = playPauseButton.find("i"),
seekT,
seekLoc,
seekBarPos,
cM,
ctMinutes,
ctSeconds,
curMinutes,
curSeconds,
durMinutes,
durSeconds,
playProgress,
bTime,
nTime = 0,
buffInterval = null,
tFlag = false,
currIndex = -1,
audio;
// 播放/暂停功能
function playPause() {
setTimeout(function () {
if (audio.paused) {
// 如果当前是暂停状态,则播放
playerTrack.addClass("active");
albumArt.addClass("active");
checkBuffering();
i.attr("class", "fas fa-pause");
audio.play();
musicPlayerContainer.addClass("show");
} else {
// 如果当前是播放状态,则暂停
playerTrack.removeClass("active");
albumArt.removeClass("active");
clearInterval(buffInterval);
albumArt.removeClass("buffering");
i.attr("class", "fas fa-play");
audio.pause();
musicPlayerContainer.removeClass("show");
}
}, 300);
}
// 显示悬停时间
function showHover(event) {
seekBarPos = sArea.offset();
seekT = event.clientX - seekBarPos.left;
seekLoc = audio.duration * (seekT / sArea.outerWidth());
sHover.width(seekT);
cM = seekLoc / 60;
ctMinutes = Math.floor(cM);
ctSeconds = Math.floor(seekLoc - ctMinutes * 60);
if (ctMinutes < 0 || ctSeconds < 0) return;
if (ctMinutes < 0 || ctSeconds < 0) return;
if (ctMinutes < 10) ctMinutes = "0" + ctMinutes;
if (ctSeconds < 10) ctSeconds = "0" + ctSeconds;
if (isNaN(ctMinutes) || isNaN(ctSeconds)) seekTime.text("--:--");
else seekTime.text(ctMinutes + ":" + ctSeconds);
seekTime.css({ left: seekT, "margin-left": "-21px" }).fadeIn(0);
}
// 隐藏悬停时间
function hideHover() {
sHover.width(0);
seekTime
.text("00:00")
.css({ left: "0px", "margin-left": "0px" })
.fadeOut(0);
}
// 从点击位置播放
function playFromClickedPos() {
audio.currentTime = seekLoc;
seekBar.width(seekT);
hideHover();
}
// 更新当前时间
function updateCurrTime() {
nTime = new Date();
nTime = nTime.getTime();
if (!tFlag) {
tFlag = true;
trackTime.addClass("active");
}
curMinutes = Math.floor(audio.currentTime / 60);
curSeconds = Math.floor(audio.currentTime - curMinutes * 60);
durMinutes = Math.floor(audio.duration / 60);
durSeconds = Math.floor(audio.duration - durMinutes * 60);
playProgress = (audio.currentTime / audio.duration) * 100;
if (curMinutes < 10) curMinutes = "0" + curMinutes;
if (curSeconds < 10) curSeconds = "0" + curSeconds;
if (durMinutes < 10) durMinutes = "0" + durMinutes;
if (durSeconds < 10) durSeconds = "0" + durSeconds;
if (isNaN(curMinutes) || isNaN(curSeconds)) tProgress.text("00:00");
else tProgress.text(curMinutes + ":" + curSeconds);
if (isNaN(durMinutes) || isNaN(durSeconds)) tTime.text("00:00");
else tTime.text(durMinutes + ":" + durSeconds);
if (
isNaN(curMinutes) ||
isNaN(curSeconds) ||
isNaN(durMinutes) ||
isNaN(durSeconds)
)
trackTime.removeClass("active");
else trackTime.addClass("active");
seekBar.width(playProgress + "%");
if (playProgress == 100) {
i.attr("class", "fa fa-play");
seekBar.width(0);
tProgress.text("00:00");
albumArt.removeClass("buffering").removeClass("active");
clearInterval(buffInterval);
}
}
// 检查缓冲状态
function checkBuffering() {
clearInterval(buffInterval);
buffInterval = setInterval(function () {
if (nTime == 0 || bTime - nTime > 1000) albumArt.addClass("buffering");
else albumArt.removeClass("buffering");
bTime = new Date();
bTime = bTime.getTime();
}, 100);
}
// 选择曲目
function selectTrack(flag) {
if (flag == 0 || flag == 1) ++currIndex;
else --currIndex;
if (currIndex > -1 && currIndex < albumArtworks.length) {
if (flag == 0) i.attr("class", "fa fa-play");
else {
albumArt.removeClass("buffering");
i.attr("class", "fa fa-pause");
}
seekBar.width(0);
trackTime.removeClass("active");
tProgress.text("00:00");
tTime.text("00:00");
currAlbum = albums[currIndex];
currTrackName = trackNames[currIndex];
currArtwork = albumArtworks[currIndex];
audio.src = trackUrl[currIndex];
nTime = 0;
bTime = new Date();
bTime = bTime.getTime();
if (flag != 0) {
audio.play();
playerTrack.addClass("active");
albumArt.addClass("active");
clearInterval(buffInterval);
checkBuffering();
musicPlayerContainer.addClass("show");
}
albumName.text(currAlbum);
trackName.text(currTrackName);
albumArt.find("img.active").removeClass("active");
$("#" + currArtwork).addClass("active");
bgArtworkUrl = $("#" + currArtwork).attr("src");
bgArtwork.css({ "background-image": "url(" + bgArtworkUrl + ")" });
} else {
if (flag == 0 || flag == 1) --currIndex;
else ++currIndex;
}
}
// 初始化播放器
function initPlayer() {
audio = new Audio();
selectTrack(0);
audio.loop = false;
// 绑定事件
playPauseButton.on("click", playPause);
sArea.mousemove(function (event) {
showHover(event);
});
sArea.mouseout(hideHover);
sArea.on("click", playFromClickedPos);
$(audio).on("timeupdate", updateCurrTime);
// 上一首按钮事件
playPreviousTrackButton.on("click", function () {
selectTrack(-1);
});
// 下一首按钮事件
playNextTrackButton.on("click", function () {
selectTrack(1);
});
}
// 初始化播放器
initPlayer();
// 猫猫按钮点击事件 - 控制音乐播放/暂停
document.getElementById('maomao').addEventListener('click', function() {
playPause();
});
// ================ 安全通知功能 ================
// 显示安全通知
function showSecurityNotification(title, message, type = 'success') {
const notification = document.getElementById('security-notification');
const titleElement = document.getElementById('notification-title');
const contentElement = document.getElementById('notification-content');
const iconElement = notification.querySelector('.security-cat-icon');
// 设置内容
titleElement.textContent = title;
contentElement.textContent = message;
// 设置图标和样式
iconElement.className = `security-cat-icon ${type}`;
notification.className = `security-notification ${type}`;
// 显示通知
notification.classList.add('show');
// 4秒后自动隐藏
setTimeout(() => {
hideSecurityNotification();
}, 4000);
}
// 隐藏安全通知
function hideSecurityNotification() {
const notification = document.getElementById('security-notification');
notification.classList.remove('show');
}
// ================ 安全保护功能 ================
function setupSecurityProtection() {
// 1. 禁用右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
showSecurityNotification('🛡️ 安全提示', '右键菜单已被禁用,保护页面内容安全', 'warning');
return false;
});
// 2. 禁用开发者工具快捷键
document.addEventListener('keydown', function(e) {
// 禁用 F12
if (e.key === 'F12' || e.keyCode === 123) {
e.preventDefault();
showSecurityNotification('🔒 安全警告', '开发者工具已被禁用', 'error');
return false;
}
// 禁用 Ctrl+Shift+I (Chrome/Edge)
if ((e.ctrlKey || e.metaKey) && e.shiftKey && (e.key === 'I' || e.keyCode === 73)) {
e.preventDefault();
showSecurityNotification('🔒 安全警告', '开发者工具已被禁用', 'error');
return false;
}
// 禁用 Ctrl+Shift+J (Chrome/Edge)
if ((e.ctrlKey || e.metaKey) && e.shiftKey && (e.key === 'J' || e.keyCode === 74)) {
e.preventDefault();
showSecurityNotification('🔒 安全警告', '控制台已被禁用', 'error');
return false;
}
// 禁用 Ctrl+Shift+C (Chrome/Edge)
if ((e.ctrlKey || e.metaKey) && e.shiftKey && (e.key === 'C' || e.keyCode === 67)) {
e.preventDefault();
showSecurityNotification('🔒 安全警告', '检查元素功能已被禁用', 'error');
return false;
}
// 禁用 Ctrl+U (查看源代码)
if ((e.ctrlKey || e.metaKey) && (e.key === 'U' || e.keyCode === 85)) {
e.preventDefault();
showSecurityNotification('🔒 安全警告', '查看源代码功能已被禁用', 'error');
return false;
}
// 禁用 Ctrl+S (保存页面)
if ((e.ctrlKey || e.metaKey) && (e.key === 'S' || e.keyCode === 83)) {
e.preventDefault();
showSecurityNotification('🔒 安全警告', '页面保存功能已被禁用', 'error');
return false;
}
});
// 3. 检测开发者工具是否打开
let devToolsOpen = false;
let devToolsCheckCount = 0;
function checkDevTools() {
const widthThreshold = window.outerWidth - window.innerWidth > 100;
const heightThreshold = window.outerHeight - window.innerHeight > 100;
if ((widthThreshold || heightThreshold) && !devToolsOpen) {
devToolsOpen = true;
devToolsCheckCount++;
showSecurityNotification('⚠️ 安全警告', `检测到开发者工具已打开 (${devToolsCheckCount}次)`, 'error');
} else if (!widthThreshold && !heightThreshold && devToolsOpen) {
devToolsOpen = false;
showSecurityNotification('✅ 安全提示', '开发者工具已关闭', 'success');
}
}
// 每500ms检测一次
setInterval(checkDevTools, 500);
// 4. 禁用文本选择(可选)
document.addEventListener('selectstart', function(e) {
// 允许在输入框中选择文本
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return true;
}
e.preventDefault();
return false;
});
// 5. 禁用拖拽
document.addEventListener('dragstart', function(e) {
e.preventDefault();
return false;
});
// 6. 监听页面失焦(可能是切换到开发者工具)
let focusLostCount = 0;
window.addEventListener('blur', function() {
focusLostCount++;
if (focusLostCount > 3) {
showSecurityNotification('👀 安全提示', '检测到频繁切换窗口,请注意页面安全', 'warning');
focusLostCount = 0;
}
});
}
// 在DOM加载完成后调用安全保护
document.addEventListener('DOMContentLoaded', function() {
setupSecurityProtection();
});
// ================ 天气通知功能 ================
// 显示天气通知
function showWeatherNotification(title, weatherData) {
const notification = document.getElementById('weather-notification');
const titleElement = document.getElementById('weather-notification-title');
// 处理地点信息,确保显示"省+市"格式(如"福建福州")
let location = "当前位置";
try {
const parts = weatherData.place.split(',').map(p => p.trim());
if (parts.length >= 3) {
// 格式化为"省+市",如"福建福州"
location = `${parts[1]}${parts[2]}`;
} else if (parts.length === 2) {
location = parts[1];
}
} catch (e) {
console.log('地点解析错误,使用默认位置');
}
// 设置内容
titleElement.textContent = title;
document.getElementById('welcome-message').textContent = `欢迎${location}的用户`;
document.getElementById('weather-icon').src = weatherData.weather1img;
document.getElementById('weather-condition').textContent = weatherData.weather1;
// 温度显示(四舍五入到整数)
const temp = Math.round(weatherData.temperature);
document.getElementById('weather-temp').textContent = `${temp}°C`;
// 设置温度颜色
const tempElement = document.getElementById('weather-temp');
if (temp > 30) {
tempElement.style.color = '#ff4d4d'; // 高温红色
} else if (temp < 10) {
tempElement.style.color = '#4d9eff'; // 低温蓝色
} else {
tempElement.style.color = '#ea5c1f'; // 常温橙色
}
// 设置额外信息
document.getElementById('weather-humidity').textContent = weatherData.humidity || '--';
document.getElementById('weather-wind').textContent = weatherData.windSpeed || '--';
// 显示通知
notification.classList.add('show');
// 4秒后自动隐藏
setTimeout(() => {
hideWeatherNotification();
}, 4000);
}
// 隐藏天气通知
function hideWeatherNotification() {
const notification = document.getElementById('weather-notification');
notification.classList.remove('show');
}
// 获取天气数据
async function fetchWeatherData(ip = '') {
const url = `${API_CONFIG.endpoints.weather}?id=${API_CONFIG.id}&key=${API_CONFIG.key}${ip ? `&ip=${ip}` : ''}`;
try {
const response = await fetch(url);
const data = await response.json();
if (data.code === 200) {
return data;
} else {
console.error('获取天气数据失败:', data.msg);
return null;
}
} catch (error) {
console.error('获取天气数据出错:', error);
return null;
}
}
// ================ 终端功能代码 ================
// 全局状态管理
let commandHistoryArray = JSON.parse(localStorage.getItem('commandHistory') || '[]');
let historyIndex = -1;
let totalParseCount = parseInt(localStorage.getItem('totalParseCount') || '0');
let successParseCount = parseInt(localStorage.getItem('successParseCount') || '0');
let indentSize = 4;
let currentTheme = localStorage.getItem('terminalTheme') || 'default';
let fileStorage = JSON.parse(localStorage.getItem('fileStorage') || '{}');
// 终端相关元素
const terminalContainer = document.getElementById('terminal-container');
const catChatArea = document.getElementById('catChatArea');
const catInput = document.getElementById('catInput');
// 猫咪类型数组
const catTypes = ['biscuit', 'sleepy', 'choco'];
// 支持的视频平台列表
const supportedVideoPlatforms = [
'v.qq.com', 'iqiyi.com', 'youku.com', 'mgtv.com', 'bilibili.com',
'sohu.com', 'le.com', 'pptv.com', 'wasu.cn', '1905.com',
'fun.tv', 'acfun.cn', 'douyu.com', 'huya.com', 'yy.com'
];
// 影视解析接口
const videoApis = [
{ name: "默认线路【推荐】", url: "https://www.yemu.xyz/?url=" },
{ name: "线路一【推荐】", url: "https://jx.we-vip.com/?url=" },
{ name: "线路二【推荐】", url: "https://z1.m1907.top/?jx=" },
{ name: "线路三", url: "https://www.ckplayer.vip/jiexi/?url=" },
{ name: "线路四", url: "https://jx.jsonplayer.com/player/?url=" },
{ name: "线路五", url: "https://jx.4kdv.com/?url=" },
{ name: "线路六", url: "https://api.jiexi.la/?url=" },
{ name: "线路七", url: "https://jx.qqwtt.com/?url=" },
{ name: "线路八", url: "https://www.playm3u8.cn/jiexi.php?url=" },
{ name: "线路九", url: "https://jx.xmflv.com/?url=" }
];
// 编码工具配置
const encodingTools = {
base64: {
encode: (text) => btoa(unescape(encodeURIComponent(text))),
decode: (text) => decodeURIComponent(escape(atob(text)))
},
url: {
encode: (text) => encodeURIComponent(text),
decode: (text) => decodeURIComponent(text)
},
html: {
encode: (text) => text.replace(/[&<>"']/g, (match) => {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return map[match];
}),
decode: (text) => text.replace(/&|<|>|"|'/g, (match) => {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', ''': "'" };
return map[match];
})
}
};
// 帮助信息
const helpText = `🚀 猫猫终端命令帮助 🐱
📋 基础命令:
• help: 显示帮助信息
• clear: 清空终端
• history: 显示命令历史记录
• about: 显示关于信息
🔧 代码工具:
• format [code]: 格式化代码 (支持: json, html, css, js, sql)
例如: format {"name":"John","age":30}
• minify [code]: 压缩代码 (支持: json, html, css, js, sql)
例如: minify <div><p>Hello</p></div>
• detect [code]: 自动检测代码类型
例如: detect SELECT * FROM users
🎬 影视工具:
• video [url]: 解析影视视频(支持爱奇艺、腾讯视频等)
例如: video https://v.qq.com/x/cover/xxx.html
• shortvideo [url]: 解析短视频(支持抖音、快手、小红书)
例如: shortvideo https://v.douyin.com/xxxxxx
🌐 网络工具:
• ping [url]: 测试网站或IP的ping延迟
例如: ping www.example.com
• search [keyword]: 搜索网络内容
例如: search JavaScript教程
🤖 AI工具:
• ai [message]: 与AI助手对话
例如: ai 你好,请介绍一下JavaScript
🖼️ 图片工具:
• image: 打开图片格式转换器 (支持: jpg, png, webp, bmp)
🔐 编码工具:
• encode [type] [text]: 编码文本 (支持: base64, url, html)
例如: encode base64 Hello World
• decode [type] [text]: 解码文本 (支持: base64, url, html)
例如: decode base64 SGVsbG8gV29ybGQ=
📁 文件管理:
• files: 显示文件管理器
• save [filename] [content]: 保存文件
例如: save test.txt Hello World
• load [filename]: 加载文件内容
例如: load test.txt
• delete [filename]: 删除文件
例如: delete test.txt
⚙️ 设置:
• set indent [size]: 设置缩进大小 (1-8个空格)
例如: set indent 4
• theme [name]: 切换主题 (default, dark, blue)
例如: theme dark
📊 系统工具:
• stats: 显示系统统计信息
• time: 显示当前时间
💡 提示: 使用上下箭头键浏览命令历史,ESC键关闭终端`;
// ================ 核心功能函数 ================
// 终端切换功能
function toggleTerminal() {
if (terminalContainer.classList.contains('show')) {
terminalContainer.classList.remove('show');
} else {
terminalContainer.classList.add('show');
setTimeout(() => catInput.focus(), 300);
}
}
// 随机选择猫咪类型
function getRandomCatType() {
return catTypes[Math.floor(Math.random() * catTypes.length)];
}
// 打字机效果函数
function typeWriterEffect(element, text, speed = 50) {
return new Promise((resolve) => {
let i = 0;
element.innerHTML = '';
const typing = () => {
if (i < text.length) {
const char = text.charAt(i);
const span = document.createElement('span');
span.textContent = char;
span.style.animationDelay = `${i * 0.02}s`;
element.appendChild(span);
i++;
setTimeout(typing, speed);
} else {
resolve();
}
};
typing();
});
}
// 添加猫咪消息
async function addCatMessage(text, isUser = false, useTypewriter = true, catType = null) {
const message = document.createElement('div');
message.className = `cat-message ${isUser ? 'user' : ''}`;
const bubble = document.createElement('div');
bubble.className = 'cat-speech-bubble';
const icon = document.createElement('div');
icon.className = `cat-icon ${isUser ? 'white' : (catType || getRandomCatType())}`;
// AI消息:头像在左,气泡在右
if (!isUser) {
message.appendChild(icon);
message.appendChild(bubble);
} else {
// 用户消息:气泡在左,头像在右
message.appendChild(bubble);
message.appendChild(icon);
}
catChatArea.appendChild(message);
// 确保滚动到底部
setTimeout(() => {
catChatArea.scrollTop = catChatArea.scrollHeight;
}, 10);
if (useTypewriter && !isUser) {
await typeWriterEffect(bubble, text);
} else {
bubble.textContent = text;
}
// 再次确保滚动到底部
setTimeout(() => {
catChatArea.scrollTop = catChatArea.scrollHeight;
}, 100);
return message;
}
// 添加用户消息
function addUserMessage(text) {
return addCatMessage(text, true, false);
}
// 清空终端
function clearTerminal() {
// 保留欢迎消息,清除其他消息
const messages = catChatArea.querySelectorAll('.cat-message');
messages.forEach((message, index) => {
if (index > 0) { // 保留第一条欢迎消息
message.remove();
}
});
// 移除工具容器
const containers = document.querySelectorAll('.cat-tool-container, .cat-video-container, .cat-code-result');
containers.forEach(container => container.remove());
}
// URL提取函数
function extractURL(text) {
try {
const urlRegex = /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*))/g;
const matches = text.match(urlRegex);
return matches ? matches[0] : null;
} catch (e) {
console.error('URL提取错误:', e);
return null;
}
}
// 保存命令历史
function saveToHistory(command) {
if (commandHistoryArray.length === 0 || commandHistoryArray[commandHistoryArray.length - 1] !== command) {
commandHistoryArray.push(command);
if (commandHistoryArray.length > 50) {
commandHistoryArray.shift();
}
localStorage.setItem('commandHistory', JSON.stringify(commandHistoryArray));
}
historyIndex = -1;
}
// 更新解析统计
function updateParseStats() {
localStorage.setItem('totalParseCount', totalParseCount.toString());
localStorage.setItem('successParseCount', successParseCount.toString());
}
// 检查URL是否属于支持的视频平台
function isSupportedVideoUrl(url) {
if (!url) return false;
try {
const domain = new URL(url).hostname.toLowerCase();
return supportedVideoPlatforms.some(platform => domain.includes(platform));
} catch (e) {
return false;
}
}
// 代码格式化工具函数
function formatCode(code, type) {
try {
code = code.trim();
switch(type.toLowerCase()) {
case 'json':
try {
const parsed = JSON.parse(code);
return JSON.stringify(parsed, null, ' '.repeat(indentSize));
} catch (e) {
throw new Error(`JSON解析错误: ${e.message}`);
}
case 'html':
let html = code;
let formatted = '';
let indent = 0;
let currentLine = '';
for (let i = 0; i < html.length; i++) {
const char = html[i];
const nextChar = html[i+1];
if (char === '<' && nextChar === '/') {
indent = Math.max(0, indent - 1);
if (currentLine.trim()) {
formatted += ' '.repeat(indent * indentSize) + currentLine + '\n';
currentLine = '';
}
currentLine += char;
} else if (char === '<') {
if (currentLine.trim()) {
formatted += ' '.repeat(indent * indentSize) + currentLine + '\n';
currentLine = '';
}
currentLine += char;
if (nextChar !== '!' && nextChar !== '?') {
indent++;
}
} else if (char === '>') {
currentLine += char;
formatted += ' '.repeat(Math.max(0, indent - 1) * indentSize) + currentLine + '\n';
currentLine = '';
} else {
currentLine += char;
}
}
return formatted.trim();
case 'css':
let css = code;
let cssIndent = 0;
let cssResult = '';
css = css.replace(/([{}])/g, '\n$1\n')
.replace(/([;])\s*/g, '$1\n')
.replace(/\n+/g, '\n');
const cssLines = css.split('\n');
for (const line of cssLines) {
const trimmed = line.trim();
if (!trimmed) continue;
if (trimmed === '}') {
cssIndent--;
}
cssResult += ' '.repeat(cssIndent * indentSize) + trimmed + '\n';
if (trimmed.endsWith('{')) {
cssIndent++;
}
}
return cssResult.trim();
case 'js':
let js = code;
let jsIndent = 0;
let jsResult = '';
js = js.replace(/([{}\[\]])/g, '\n$1\n')
.replace(/([;,])\s*/g, '$1\n')
.replace(/\n+/g, '\n');
const lines = js.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
if (trimmed.endsWith('}') || trimmed.endsWith(']')) {
jsIndent--;
}
jsResult += ' '.repeat(jsIndent * indentSize) + trimmed + '\n';
if (trimmed.endsWith('{') || trimmed.endsWith('[')) {
jsIndent++;
}
}
return jsResult.trim();
case 'sql':
let sql = code.toUpperCase();
const keywords = ['SELECT', 'FROM', 'WHERE', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'ORDER BY', 'GROUP BY', 'HAVING', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP'];
keywords.forEach(keyword => {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
sql = sql.replace(regex, `\n${keyword}`);
});
return sql.split('\n').map(line => line.trim()).filter(line => line).join('\n');
default:
return code;
}
} catch (e) {
return `格式化错误: ${e.message}`;
}
}
// 代码压缩工具函数
function minifyCode(code, type) {
try {
code = code.trim();
switch(type.toLowerCase()) {
case 'json':
try {
const parsed = JSON.parse(code);
return JSON.stringify(parsed);
} catch (e) {
throw new Error(`JSON解析错误: ${e.message}`);
}
case 'html':
return code.replace(/\s+/g, ' ')
.replace(/<!--[\s\S]*?-->/g, '')
.replace(/>\s+</g, '><')
.trim();
case 'css':
return code.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\s+/g, ' ')
.replace(/\s*([{};:,])\s*/g, '$1')
.replace(/;}/g, '}')
.trim();
case 'js':
return code.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, '')
.replace(/\s+/g, ' ')
.replace(/\s*([=+\-*\/%&|^~!<>?:;,{}()[\]])\s*/g, '$1')
.replace(/;}/g, '}')
.trim();
case 'sql':
return code.replace(/\s+/g, ' ')
.replace(/--.*$/gm, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
.trim();
default:
return code;
}
} catch (e) {
return `压缩错误: ${e.message}`;
}
}
// 检测代码类型函数
function detectCodeType(code) {
code = code.trim();
try {
JSON.parse(code);
return 'json';
} catch (e) {}
if (code.startsWith('<') || code.match(/<[a-z][\s\S]*>/i)) {
return 'html';
}
if (code.match(/(^|\}|\{)[^{}]*\{[^{}]*\}/) ||
code.match(/\.([a-z][\w-]*)\s*\{|\#([a-z][\w-]*)\s*\{|@(media|keyframes|import)/i)) {
return 'css';
}
if (code.match(/\b(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|FROM|WHERE|JOIN)\b/i)) {
return 'sql';
}
if (code.match(/function\s*\(|=>|\b(let|const|var)\s+\w+\s*=|console\.log|import\s+|export\s+/)) {
return 'js';
}
return 'text';
}
// 显示代码结果
function showCodeResult(result, title) {
const codeResult = document.createElement('div');
codeResult.className = 'cat-code-result';
codeResult.textContent = result;
catChatArea.appendChild(codeResult);
setTimeout(() => {
catChatArea.scrollTop = catChatArea.scrollHeight;
}, 10);
}
// AI对话功能
async function sendAIMessage(message) {
if (!message) return;
const inputWrapper = document.querySelector('.cat-input-wrapper');
inputWrapper.classList.add('load');
try {
const endpoint = `${API_CONFIG.endpoints.ai}?id=${API_CONFIG.id}&key=${API_CONFIG.key}&words=${encodeURIComponent(message)}`;
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
inputWrapper.classList.remove('load');
if (data.code === 200 && data.msg) {
await addCatMessage(data.msg, false, true, 'biscuit');
} else {
await addCatMessage(`AI回复失败: ${data.msg || '未知错误'}`, false, false, 'sleepy');
}
} catch (error) {
inputWrapper.classList.remove('load');
await addCatMessage(`AI服务暂时不可用: ${error.message}`, false, false, 'sleepy');
console.error('AI API错误:', error);
}
}
// 视频解析功能
async function parseVideo(url) {
if (!url) {
await addCatMessage('请提供视频URL喵~', false, true, 'sleepy');
return;
}
if (!isSupportedVideoUrl(url)) {
await addCatMessage('不支持的视频平台,支持的平台:爱奇艺、腾讯视频、优酷、芒果TV、哔哩哔哩等', false, true, 'sleepy');
return;
}
totalParseCount++;
updateParseStats();
await addCatMessage('正在解析视频,请稍候...', false, true, 'choco');
// 创建视频容器
const videoContainer = document.createElement('div');
videoContainer.className = 'cat-video-container';
const videoPlayer = document.createElement('div');
videoPlayer.className = 'cat-video-player';
const videoControls = document.createElement('div');
videoControls.className = 'cat-video-controls';
videoContainer.appendChild(videoPlayer);
videoContainer.appendChild(videoControls);
catChatArea.appendChild(videoContainer);
// 创建线路按钮
videoApis.forEach((api, index) => {
const button = document.createElement('button');
button.className = 'cat-video-button';
button.textContent = api.name;
button.onclick = () => {
// 禁用所有按钮
videoControls.querySelectorAll('.cat-video-button').forEach(btn => btn.disabled = true);
button.disabled = false;
const iframe = document.createElement('iframe');
iframe.src = api.url + encodeURIComponent(url);
iframe.allowFullscreen = true;
iframe.onload = () => {
videoControls.querySelectorAll('.cat-video-button').forEach(btn => btn.disabled = false);
};
videoPlayer.innerHTML = '';
videoPlayer.appendChild(iframe);
successParseCount++;
updateParseStats();
addCatMessage(`已切换到${api.name}`, false, false, 'biscuit');
};
videoControls.appendChild(button);
});
// 默认使用第一个线路
const defaultIframe = document.createElement('iframe');
defaultIframe.src = videoApis[0].url + encodeURIComponent(url);
defaultIframe.allowFullscreen = true;
videoPlayer.appendChild(defaultIframe);
await addCatMessage(`视频解析完成!已为您提供${videoApis.length}条解析线路`, false, true, 'biscuit');
successParseCount++;
updateParseStats();
setTimeout(() => {
catChatArea.scrollTop = catChatArea.scrollHeight;
}, 100);
}
// 短视频解析功能
async function parseShortVideo(url) {
if (!url) {
await addCatMessage('请提供短视频URL喵~', false, true, 'sleepy');
return;
}
totalParseCount++;
updateParseStats();
await addCatMessage('正在解析短视频,请稍候...', false, true, 'choco');
try {
const endpoint = `${API_CONFIG.endpoints.shortVideo}?id=${API_CONFIG.id}&key=${API_CONFIG.key}&url=${encodeURIComponent(url)}`;
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.video) {
await addCatMessage('短视频解析完成!', false, true, 'biscuit');
// 创建视频容器
const videoContainer = document.createElement('div');
videoContainer.className = 'cat-video-container';
const videoPlayer = document.createElement('div');
videoPlayer.className = 'cat-video-player';
const videoControls = document.createElement('div');
videoControls.className = 'cat-video-controls';
videoContainer.appendChild(videoPlayer);
videoContainer.appendChild(videoControls);
catChatArea.appendChild(videoContainer);
// 创建视频元素
const video = document.createElement('video');
video.src = data.video;
video.controls = true;
video.style.width = '100%';
videoPlayer.appendChild(video);
// 创建下载按钮
const downloadBtn = document.createElement('button');
downloadBtn.className = 'cat-video-button';
downloadBtn.textContent = '下载视频';
downloadBtn.onclick = () => {
const a = document.createElement('a');
a.href = data.video;
a.download = 'video.mp4';
a.click();
};
videoControls.appendChild(downloadBtn);
successParseCount++;
updateParseStats();
} else {
await addCatMessage(`短视频解析失败: ${data.msg || '未知错误'}`, false, true, 'sleepy');
}
} catch (error) {
await addCatMessage(`短视频解析失败: ${error.message}`, false, true, 'sleepy');
console.error('短视频解析错误:', error);
}
}
// Ping测试功能
async function pingTest(host) {
if (!host) {
await addCatMessage('请提供要测试的网站或IP喵~', false, true, 'sleepy');
return;
}
await addCatMessage(`正在测试 ${host} 的Ping延迟,请稍候...`, false, true, 'choco');
try {
const endpoint = `${API_CONFIG.endpoints.ping}?id=${API_CONFIG.id}&key=${API_CONFIG.key}&host=${encodeURIComponent(host)}`;
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200) {
await addCatMessage(`Ping测试结果 (${data.dy} ${data.dz}):
目标: ${data.host}
IP地址: ${data.ip || '未知'}
延迟: ${data.time}ms`, false, true, 'biscuit');
} else {
await addCatMessage(`Ping测试失败: ${data.msg || '未知错误'}`, false, true, 'sleepy');
}
} catch (error) {
await addCatMessage(`Ping测试失败: ${error.message}`, false, true, 'sleepy');
console.error('Ping测试错误:', error);
}
}
// 搜索功能
async function searchWeb(keyword) {
if (!keyword) {
await addCatMessage('请提供搜索关键词喵~', false, true, 'sleepy');
return;
}
await addCatMessage('正在搜索,请稍候...', false, true, 'choco');
try {
const endpoint = `${API_CONFIG.endpoints.search}?id=${API_CONFIG.id}&key=${API_CONFIG.key}&words=${encodeURIComponent(keyword)}`;
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.data && data.data.length > 0) {
await addCatMessage(`搜索完成!找到 ${data.data.length} 个相关结果`, false, true, 'biscuit');
data.data.slice(0, 5).forEach(async (item, index) => {
await addCatMessage(`${index + 1}. ${item.title || '无标题'}
🔗 ${item.url || '无链接'}
📝 ${item.description || '无描述'}`, false, false);
});
} else {
await addCatMessage(`搜索失败: ${data.msg || '未找到相关结果'}`, false, true, 'sleepy');
}
} catch (error) {
await addCatMessage(`搜索失败: ${error.message}`, false, true, 'sleepy');
console.error('搜索错误:', error);
}
}
// 图片格式转换功能
function createImageConverter() {
const container = document.createElement('div');
container.className = 'cat-tool-container';
const header = document.createElement('div');
header.className = 'cat-tool-header';
header.innerHTML = `
<div class="cat-tool-title">🖼️ 图片格式转换器</div>
<button class="cat-tool-close" onclick="this.closest('.cat-tool-container').remove()">✕</button>
`;
const content = document.createElement('div');
content.className = 'cat-tool-content';
content.innerHTML = `
<div style="border: 2px dashed var(--cat-brown); border-radius: 8px; padding: 20px; text-align: center; cursor: pointer; margin-bottom: 15px;" onclick="document.getElementById('imageInput').click()">
📁 点击选择图片或拖拽图片到此处<br>
<small>支持 JPG, PNG, GIF, WebP, BMP 格式</small>
<input type="file" id="imageInput" accept="image/*" style="display: none;">
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap; justify-content: center;">
<button onclick="convertToFormat('png')" style="background: var(--cat-brown); color: var(--cat-bubble-bg); border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">PNG</button>
<button onclick="convertToFormat('jpg')" style="background: var(--cat-brown); color: var(--cat-bubble-bg); border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">JPG</button>
<button onclick="convertToFormat('webp')" style="background: var(--cat-brown); color: var(--cat-bubble-bg); border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">WebP</button>
<button onclick="convertToFormat('bmp')" style="background: var(--cat-brown); color: var(--cat-bubble-bg); border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">BMP</button>
</div>
<div id="imagePreview" style="text-align: center; margin-top: 15px; color: #666;">暂无图片</div>
`;
container.appendChild(header);
container.appendChild(content);
catChatArea.appendChild(container);
setTimeout(() => {
catChatArea.scrollTop = catChatArea.scrollHeight;
}, 100);
// 文件选择事件
document.getElementById('imageInput').addEventListener('change', handleImageFile);
}
// 处理图片文件
function handleImageFile(event) {
const file = event.target.files[0];
if (!file || !file.type.startsWith('image/')) {
addCatMessage('请选择有效的图片文件喵~', false, false, 'sleepy');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const preview = document.getElementById('imagePreview');
preview.innerHTML = `
<img src="${e.target.result}" style="max-width: 100%; max-height: 200px; border-radius: 8px; margin: 10px 0;">
<div style="font-size: 12px; color: var(--cat-brown);">
文件名: ${file.name} | 大小: ${(file.size / 1024).toFixed(2)} KB
</div>
`;
window.currentImageFile = file;
window.currentImageSrc = e.target.result;
};
reader.readAsDataURL(file);
}
// 转换图片格式
window.convertToFormat = function(format) {
if (!window.currentImageSrc) {
addCatMessage('请先选择图片喵~', false, false, 'sleepy');
return;
}
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
if (format === 'jpg') {
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.drawImage(img, 0, 0);
const mimeType = format === 'jpg' ? 'image/jpeg' : `image/${format}`;
const quality = format === 'jpg' ? 0.9 : undefined;
canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const baseName = window.currentImageFile.name.split('.')[0];
a.download = `${baseName}_converted.${format}`;
a.click();
URL.revokeObjectURL(url);
const newSize = (blob.size / 1024).toFixed(2);
addCatMessage(`图片已转换为 ${format.toUpperCase()} 格式并开始下载 (${newSize} KB)`, false, false, 'biscuit');
}, mimeType, quality);
};
img.src = window.currentImageSrc;
};
// 文件管理功能
function createFileManager() {
const container = document.createElement('div');
container.className = 'cat-tool-container';
const header = document.createElement('div');
header.className = 'cat-tool-header';
header.innerHTML = `
<div class="cat-tool-title">📁 文件管理器</div>
<button class="cat-tool-close" onclick="this.closest('.cat-tool-container').remove()">✕</button>
`;
const content = document.createElement('div');
content.className = 'cat-tool-content';
const files = Object.keys(fileStorage);
let fileListHTML = '';
if (files.length === 0) {
fileListHTML = '<div style="text-align: center; color: #666; padding: 20px;">暂无文件</div>';
} else {
files.forEach(filename => {
const fileInfo = fileStorage[filename];
const size = new Blob([fileInfo.content]).size;
fileListHTML += `
<div style="border: 1px solid var(--cat-brown); border-radius: 8px; padding: 10px; margin: 5px 0; display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-weight: bold;">📄 ${filename}</div>
<div style="font-size: 11px; color: #666;">
${(size / 1024).toFixed(2)} KB | ${new Date(fileInfo.created).toLocaleString()}
</div>
</div>
<div>
<button onclick="viewFile('${filename}')" style="background: var(--cat-brown); color: var(--cat-bubble-bg); border: none; padding: 4px 8px; margin: 2px; border-radius: 4px; cursor: pointer;">👁️</button>
<button onclick="downloadFile('${filename}')" style="background: var(--cat-brown); color: var(--cat-bubble-bg); border: none; padding: 4px 8px; margin: 2px; border-radius: 4px; cursor: pointer;">📥</button>
<button onclick="deleteFileFromManager('${filename}')" style="background: var(--cat-brown); color: var(--cat-bubble-bg); border: none; padding: 4px 8px; margin: 2px; border-radius: 4px; cursor: pointer;">🗑️</button>
</div>
</div>
`;
});
}
content.innerHTML = `
<div style="display: flex; gap: 10px; margin-bottom: 15px;">
<input type="text" placeholder="文件名" id="newFileName" style="flex: 1; padding: 8px; border: 1px solid var(--cat-brown); border-radius: 4px;">
<button onclick="createNewFile()" style="background: var(--cat-brown); color: var(--cat-bubble-bg); border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">📄 新建</button>
</div>
<div>${fileListHTML}</div>
`;
container.appendChild(header);
container.appendChild(content);
catChatArea.appendChild(container);
setTimeout(() => {
catChatArea.scrollTop = catChatArea.scrollHeight;
}, 100);
}
// 文件操作函数
window.createNewFile = function() {
const filename = document.getElementById('newFileName').value.trim();
if (!filename) {
addCatMessage('请输入文件名喵~', false, false, 'sleepy');
return;
}
if (fileStorage[filename]) {
addCatMessage('文件已存在喵~', false, false, 'sleepy');
return;
}
fileStorage[filename] = {
content: '',
created: new Date().toISOString(),
modified: new Date().toISOString()
};
localStorage.setItem('fileStorage', JSON.stringify(fileStorage));
document.getElementById('newFileName').value = '';
addCatMessage(`文件 ${filename} 创建成功!`, false, false, 'biscuit');
};
window.viewFile = function(filename) {
const fileInfo = fileStorage[filename];
if (!fileInfo) return;
addCatMessage(`文件内容 (${filename}):`, false, false, 'biscuit');
showCodeResult(fileInfo.content || '(空文件)', filename);
};
window.downloadFile = function(filename) {
const fileInfo = fileStorage[filename];
if (!fileInfo) return;
const blob = new Blob([fileInfo.content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
addCatMessage(`文件 ${filename} 下载完成!`, false, false, 'biscuit');
};
window.deleteFileFromManager = function(filename) {
if (confirm(`确定要删除文件 ${filename} 吗?`)) {
delete fileStorage[filename];
localStorage.setItem('fileStorage', JSON.stringify(fileStorage));
addCatMessage(`文件 ${filename} 删除成功!`, false, false, 'biscuit');
// 刷新文件管理器
document.querySelector('.cat-tool-container').remove();
createFileManager();
}
};
// 编码/解码功能
function encodeText(type, text) {
try {
if (!encodingTools[type]) {
return `不支持的编码类型: ${type}`;
}
return encodingTools[type].encode(text);
} catch (e) {
return `编码错误: ${e.message}`;
}
}
function decodeText(type, text) {
try {
if (!encodingTools[type]) {
return `不支持的解码类型: ${type}`;
}
return encodingTools[type].decode(text);
} catch (e) {
return `解码错误: ${e.message}`;
}
}
// 显示系统统计
async function showStats() {
const stats = {
totalParseCount,
successParseCount,
successRate: totalParseCount > 0 ? Math.round((successParseCount / totalParseCount) * 100) : 0,
commandHistory: commandHistoryArray.length,
currentTime: new Date().toLocaleString(),
browser: navigator.userAgent.split(' ')[0],
screenResolution: `${screen.width}x${screen.height}`,
availableMemory: navigator.deviceMemory ? navigator.deviceMemory + 'GB' : '未知',
language: navigator.language,
platform: navigator.platform,
filesStored: Object.keys(fileStorage).length,
storageUsed: JSON.stringify(fileStorage).length
};
await addCatMessage(`📊 系统统计信息:
🎯 总解析次数: ${stats.totalParseCount}
✅ 成功解析次数: ${stats.successParseCount}
📈 成功率: ${stats.successRate}%
📝 命令历史记录: ${stats.commandHistory} 条
📁 存储文件数: ${stats.filesStored} 个
💾 存储空间使用: ${(stats.storageUsed / 1024).toFixed(2)} KB
🕒 当前时间: ${stats.currentTime}
🌐 浏览器: ${stats.browser}
📱 平台: ${stats.platform}
🖥️ 屏幕分辨率: ${stats.screenResolution}
🧠 可用内存: ${stats.availableMemory}
🌍 语言: ${stats.language}`, false, true, 'choco');
}
// 显示关于信息
async function showAbout() {
await addCatMessage(`🚀 关于藤原的猫猫数字终端
👨💻 开发者: 藤原
📧 邮箱: 2083737075@qq.com
🌐 网站: http://tengyuan.icu
📝 博客: http://blog.tengyuan.icu
📦 文件盒子: http://wp.tengyuan.icu
✨ 功能特色:
• 🎬 影视解析 - 支持多平台视频解析
• 🤖 AI对话 - 智能助手交互
• 🔧 代码工具 - 格式化、压缩、检测
• 🖼️ 图片转换 - 多格式图片转换
• 📁 文件管理 - 本地文件存储管理
• 🌐 网络工具 - Ping测试、搜索功能
• 🔐 编码工具 - Base64、URL、HTML编解码
• 🛡️ 安全保护 - 页面内容安全防护
💡 座右铭: "以清简代码,筑玖维数字宇宙。"
© 2017-${new Date().getFullYear()} 藤原 版权所有
🐱 猫猫们会陪伴你一起探索数字世界!`, false, true, 'biscuit');
}
// 显示当前时间
async function showTime() {
const now = new Date();
const timeString = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
weekday: 'long'
});
await addCatMessage(`🕒 当前时间: ${timeString}
🌍 时区: ${Intl.DateTimeFormat().resolvedOptions().timeZone}
📅 Unix时间戳: ${Math.floor(now.getTime() / 1000)}`, false, true, 'choco');
}
// 显示命令历史
async function showCommandHistory() {
if (commandHistoryArray.length === 0) {
await addCatMessage('暂无命令历史记录喵~', false, true, 'sleepy');
return;
}
await addCatMessage('命令历史记录:', false, true, 'choco');
const recentCommands = commandHistoryArray.slice().reverse().slice(0, 10);
for (let i = 0; i < recentCommands.length; i++) {
await addCatMessage(`${i + 1}. ${recentCommands[i]}`, false, false);
}
}
// 文件操作函数
function saveFile(filename, content) {
if (!filename) {
addCatMessage('请提供文件名喵~', false, false, 'sleepy');
return;
}
fileStorage[filename] = {
content: content || '',
created: fileStorage[filename] ? fileStorage[filename].created : new Date().toISOString(),
modified: new Date().toISOString()
};
localStorage.setItem('fileStorage', JSON.stringify(fileStorage));
addCatMessage(`文件 ${filename} 保存成功!`, false, false, 'biscuit');
}
function loadFile(filename) {
if (!filename) {
addCatMessage('请提供文件名喵~', false, false, 'sleepy');
return;
}
const fileInfo = fileStorage[filename];
if (!fileInfo) {
addCatMessage(`文件 ${filename} 不存在喵~`, false, false, 'sleepy');
return;
}
addCatMessage(`文件 ${filename} 内容:`, false, false, 'biscuit');
showCodeResult(fileInfo.content || '(空文件)', filename);
addCatMessage(`文件大小: ${(new Blob([fileInfo.content]).size / 1024).toFixed(2)} KB
创建时间: ${new Date(fileInfo.created).toLocaleString()}
修改时间: ${new Date(fileInfo.modified).toLocaleString()}`, false, false);
}
function deleteFileByName(filename) {
if (!filename) {
addCatMessage('请提供文件名喵~', false, false, 'sleepy');
return;
}
if (!fileStorage[filename]) {
addCatMessage(`文件 ${filename} 不存在喵~`, false, false, 'sleepy');
return;
}
delete fileStorage[filename];
localStorage.setItem('fileStorage', JSON.stringify(fileStorage));
addCatMessage(`文件 ${filename} 删除成功!`, false, false, 'biscuit');
}
// 主命令处理函数
async function processCommand(input) {
const command = input.trim().toLowerCase();
const parts = input.trim().split(' ');
const cmd = parts[0].toLowerCase();
const args = parts.slice(1).join(' ');
// 保存到历史记录
saveToHistory(input.trim());
// 处理不同命令
switch (cmd) {
case 'help':
await addCatMessage(helpText, false, true, 'biscuit');
break;
case 'clear':
clearTerminal();
await addCatMessage('终端已清空喵~', false, true, 'biscuit');
break;
case 'history':
await showCommandHistory();
break;
case 'about':
await showAbout();
break;
case 'time':
await showTime();
break;
case 'format':
if (args) {
const code = args;
const type = detectCodeType(code);
const formatted = formatCode(code, type);
await addCatMessage(`格式化结果 (${type}):`, false, true, 'choco');
showCodeResult(formatted, `格式化结果 (${type})`);
} else {
await addCatMessage('请提供要格式化的代码,例如: format {"name":"John"}', false, true, 'sleepy');
}
break;
case 'minify':
if (args) {
const code = args;
const type = detectCodeType(code);
const minified = minifyCode(code, type);
await addCatMessage(`压缩结果 (${type}):`, false, true, 'choco');
showCodeResult(minified, `压缩结果 (${type})`);
} else {
await addCatMessage('请提供要压缩的代码,例如: minify <div><p>Hello</p></div>', false, true, 'sleepy');
}
break;
case 'detect':
if (args) {
const code = args;
const type = detectCodeType(code);
await addCatMessage(`检测到的代码类型: ${type}`, false, true, 'choco');
} else {
await addCatMessage('请提供要检测的代码,例如: detect SELECT * FROM users', false, true, 'sleepy');
}
break;
case 'set':
if (parts[1] === 'indent' && parts[2]) {
const size = parseInt(parts[2]);
if (size >= 1 && size <= 8) {
indentSize = size;
await addCatMessage(`缩进大小已设置为 ${size} 个空格`, false, true, 'biscuit');
} else {
await addCatMessage('缩进大小必须在1-8之间喵~', false, true, 'sleepy');
}
} else {
await addCatMessage('无效的设置命令,使用: set indent [1-8]', false, true, 'sleepy');
}
break;
case 'theme':
if (parts[1]) {
const theme = parts[1].toLowerCase();
if (['default', 'dark', 'blue'].includes(theme)) {
currentTheme = theme;
localStorage.setItem('terminalTheme', theme);
await addCatMessage(`主题已切换为: ${theme}`, false, true, 'biscuit');
} else {
await addCatMessage('支持的主题: default, dark, blue', false, true, 'sleepy');
}
} else {
await addCatMessage(`当前主题: ${currentTheme}`, false, true, 'choco');
}
break;
case 'video':
if (args) {
const url = extractURL(args) || args;
await parseVideo(url);
} else {
await addCatMessage('请提供视频URL,例如: video https://v.qq.com/x/cover/xxx.html', false, true, 'sleepy');
}
break;
case 'shortvideo':
if (args) {
const url = extractURL(args) || args;
await parseShortVideo(url);
} else {
await addCatMessage('请提供短视频URL,例如: shortvideo https://v.douyin.com/xxxxxx', false, true, 'sleepy');
}
break;
case 'ping':
if (args) {
await pingTest(args);
} else {
await addCatMessage('请提供要测试的网站或IP,例如: ping www.example.com', false, true, 'sleepy');
}
break;
case 'search':
if (args) {
await searchWeb(args);
} else {
await addCatMessage('请提供搜索关键词,例如: search JavaScript教程', false, true, 'sleepy');
}
break;
case 'ai':
if (args) {
await sendAIMessage(args);
} else {
await addCatMessage('请输入要对话的内容,例如: ai 你好', false, true, 'sleepy');
}
break;
case 'image':
createImageConverter();
await addCatMessage('图片格式转换器已打开,请选择图片文件', false, true, 'biscuit');
break;
case 'files':
createFileManager();
await addCatMessage('文件管理器已打开', false, true, 'biscuit');
break;
case 'save':
if (parts.length >= 3) {
const filename = parts[1];
const content = parts.slice(2).join(' ');
saveFile(filename, content);
} else {
await addCatMessage('用法: save [文件名] [内容]', false, true, 'sleepy');
}
break;
case 'load':
if (parts[1]) {
loadFile(parts[1]);
} else {
await addCatMessage('用法: load [文件名]', false, true, 'sleepy');
}
break;
case 'delete':
if (parts[1]) {
deleteFileByName(parts[1]);
} else {
await addCatMessage('用法: delete [文件名]', false, true, 'sleepy');
}
break;
case 'encode':
if (parts.length >= 3) {
const type = parts[1];
const text = parts.slice(2).join(' ');
const result = encodeText(type, text);
await addCatMessage(`编码结果 (${type}):`, false, true, 'choco');
showCodeResult(result, `编码结果 (${type})`);
} else {
await addCatMessage('用法: encode [类型] [文本] (支持: base64, url, html)', false, true, 'sleepy');
}
break;
case 'decode':
if (parts.length >= 3) {
const type = parts[1];
const text = parts.slice(2).join(' ');
const result = decodeText(type, text);
await addCatMessage(`解码结果 (${type}):`, false, true, 'choco');
showCodeResult(result, `解码结果 (${type})`);
} else {
await addCatMessage('用法: decode [类型] [文本] (支持: base64, url, html)', false, true, 'sleepy');
}
break;
case 'stats':
await showStats();
break;
default:
// 如果不是命令,当作AI对话处理
if (input.trim()) {
await sendAIMessage(input.trim());
} else {
await addCatMessage(`未知命令: ${cmd},输入 "help" 查看可用命令,或者直接和我聊天喵~`, false, true, 'sleepy');
}
break;
}
}
// 发送猫咪消息
async function sendCatMessage() {
const input = catInput.value.trim();
if (!input) return;
// 添加用户消息
addUserMessage(input);
catInput.value = '';
// 处理命令
await processCommand(input);
}
// 输入框事件处理
catInput.addEventListener('keydown', async (e) => {
if (e.key === 'Enter') {
e.preventDefault();
await sendCatMessage();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (historyIndex < commandHistoryArray.length - 1) {
historyIndex++;
catInput.value = commandHistoryArray[commandHistoryArray.length - 1 - historyIndex];
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyIndex > 0) {
historyIndex--;
catInput.value = commandHistoryArray[commandHistoryArray.length - 1 - historyIndex];
} else if (historyIndex === 0) {
historyIndex = -1;
catInput.value = '';
}
} else if (e.key === 'Tab') {
e.preventDefault();
// 简单的命令自动补全
const input = catInput.value.toLowerCase();
const commands = ['help', 'clear', 'history', 'about', 'time', 'format', 'minify', 'detect', 'video', 'shortvideo', 'ping', 'search', 'ai', 'image', 'files', 'save', 'load', 'delete', 'encode', 'decode', 'stats', 'set', 'theme'];
const matches = commands.filter(cmd => cmd.startsWith(input));
if (matches.length === 1) {
catInput.value = matches[0] + ' ';
} else if (matches.length > 1) {
await addCatMessage(`可能的命令: ${matches.join(', ')}`, false, false, 'choco');
}
}
});
// 初始化
document.addEventListener('DOMContentLoaded', async function() {
// 初始化统计
updateParseStats();
// 获取并显示天气信息
setTimeout(async () => {
const weatherData = await fetchWeatherData();
if (weatherData) {
showWeatherNotification('实时天气', weatherData);
} else {
// 如果获取失败,使用默认数据
const defaultData = {
place: "中国, 福建, 福州",
weather1: "多云",
temperature: 28,
humidity: 75,
windSpeed: 3,
weather1img: "https://rescdn.apihz.cn/resimg/tianqi/duoyun.png"
};
showWeatherNotification('实时天气', defaultData);
}
}, 1500);
// 点击聊天区域时聚焦输入框
catChatArea.addEventListener('click', () => {
if (terminalContainer.classList.contains('show')) {
catInput.focus();
}
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
// ESC 键关闭终端
if (e.key === 'Escape' && terminalContainer.classList.contains('show')) {
toggleTerminal();
}
// Ctrl/Cmd + K 清空终端
if ((e.ctrlKey || e.metaKey) && e.key === 'k' && terminalContainer.classList.contains('show')) {
e.preventDefault();
clearTerminal();
addCatMessage('终端已清空 (快捷键: Ctrl/Cmd+K)', false, false, 'biscuit');
}
// Ctrl/Cmd + L 聚焦输入框
if ((e.ctrlKey || e.metaKey) && e.key === 'l' && terminalContainer.classList.contains('show')) {
e.preventDefault();
catInput.focus();
}
});
// 显示欢迎信息
setTimeout(async () => {
if (!localStorage.getItem('catTerminalWelcomeShown')) {
await addCatMessage('🎉 欢迎使用猫猫AI终端!', false, true, 'biscuit');
await addCatMessage('💡 输入 "help" 查看所有可用命令', false, false);
await addCatMessage('🚀 输入 "about" 了解更多信息', false, false);
await addCatMessage('🐱 你也可以直接和我聊天哦!', false, false);
localStorage.setItem('catTerminalWelcomeShown', 'true');
}
}, 1000);
});
// 确保函数在全局作用域中可用
window.toggleTerminal = toggleTerminal;
window.sendCatMessage = sendCatMessage;
window.showWeatherNotification = showWeatherNotification;
window.hideWeatherNotification = hideWeatherNotification;
</script>
</body>
</html>
index.html