first commit

This commit is contained in:
Jane Doe
2025-03-04 15:25:38 +08:00
commit 4ad00dedd5
188 changed files with 7420 additions and 0 deletions

641
pages/aitools/aitools.js Normal file
View File

@@ -0,0 +1,641 @@
//index.js
const app = getApp()
import TextDecoder from '../../utils/miniprogram-text-decoder'
Page({
data: {
currentTab: 'ToText', // 初始选择文生视频
uploadedImage: '',
userInfo: {},
videoUrl: '',
uuid: '',
inputValue: '',
charCount: 0,
picInputValue: '',
picCharCount: 0,
sizes: [{
ratio: '1:1',
width: 1080,
height: 1080
},
{
ratio: '16:9',
width: 1920,
height: 1080
},
{
ratio: '9:16',
width: 1080,
height: 1920
},
{
ratio: '4:3',
width: 1440,
height: 1080
},
{
ratio: '3:4',
width: 1080,
height: 1440
}
],
selectedSize: '9:16',
selectedWidth: 1080,
selectedHeight: 1920,
styles: {
abandoned: '废弃',
abstract_sculpture: '抽象',
advertising: '广告',
anime: '动漫',
cine_lens: '电影镜头',
cinematic: '电影',
concept_art: '艺术',
forestpunk: '赛博朋克',
frost: '雪',
graphite: '石墨',
macro_photography: '宏观',
pixel_art: '像素艺术',
retro_photography: '复古',
sci_fi_art: '科幻',
thriller: '惊悚',
'35mm': '35mm',
vector: '矢量',
watercolor: '水彩'
},
selectedStyle: '',
enhanceValue: 5, // 初始值设置为5
picEnhanceValue: 5,
ShowPicUrl: '',
tempImg: null, // 图片
messages: {},
messagesTemp: {
is_response: true,
content: '您好我是智能AI助手请问有什么可以帮助您的'
},
inputMessage: "",
toView: 'toBottom1',
showRoleDropdown: false,
currentRole: 9, // 默认角色
currentRoleText: '选择角色',
roles: []
},
// 文生视频提交
textGenerateSubmit() {
if (this.data.inputValue === "") {
wx.showToast({
title: '请输入视频描述',
icon: 'error'
})
return
}
let userId = wx.getStorageSync('userId')
const data = {
user_id: userId,
text_prompt: this.data.inputValue,
width: this.data.selectedWidth,
height: this.data.selectedHeight,
motion_score: this.data.enhanceValue,
style: this.data.selectedStyle
}
wx.showLoading({
title: '任务提交中',
})
app.apiRequest({
url: "/myapp/generate_video/",
method: "POST",
data,
success: res => {
console.log('res', res);
if (res.data.success === false) {
wx.showModal({
title: "文生视频",
content: "哎呀!创意点数不足, 请充值",
confirmColor: "#00B269",
cancelColor: "#858585",
success: function (e) {
e.confirm ? (console.log("确定"), wx.navigateTo({
url: "../vip_recharge/vip_recharge?show=true"
})) : e.cancel && console.log("取消");
}
})
} else {
wx.navigateTo({
url: '/pages/tips/tips',
})
}
},
fail: err => {
console.log('err', err);
},
complete: () => {
wx.hideLoading()
}
})
},
// 图生视频提交
async picGenerateSubmit() {
if (this.data.ShowPicUrl === "") {
wx.showToast({
title: '请上传图片',
icon: 'error'
})
return
}
if (this.data.picInputValue === "") {
wx.showToast({
title: '请输入视频描述',
icon: 'error'
})
return
}
let imageUrl = ''
wx.showLoading({
title: '任务提交中',
})
try {
let res = await app.uploadFile(this.data.ShowPicUrl)
imageUrl = app.globalData.apiDomain + JSON.parse(res).file_url
} catch (error) {
console.log('error', error);
wx.hideLoading()
wx.showToast({
title: '图片上传失败,请稍后重试~',
icon: 'error'
});
return
}
let userId = wx.getStorageSync('userId')
const data = {
user_id: userId,
image_url: imageUrl,
text_prompt: this.data.picInputValue,
motion_score: this.data.picEnhanceValue
}
app.apiRequest({
url: "/myapp/generate_image_video/",
method: "POST",
data,
success: res => {
console.log('res', res);
if (res.data.success === false) {
wx.showModal({
title: "图生视频",
content: "哎呀!创意点数不足, 请充值",
confirmColor: "#00B269",
cancelColor: "#858585",
success: function (e) {
e.confirm ? (console.log("确定"), wx.navigateTo({
url: "../vip_recharge/vip_recharge?show=true"
})) : e.cancel && console.log("取消");
}
})
} else {
wx.navigateTo({
url: '/pages/tips/tips',
})
}
},
fail: err => {
console.log('err', err);
},
complete: () => {
wx.hideLoading()
}
})
},
// 点击上传图片
uploadImage() {
let that = this
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album'],
success(res) {
console.log(res)
let tempImg = res.tempFiles[0]
let pictureSize = tempImg.size
let ShowPicUrl = tempImg.tempFilePath
if (pictureSize > 1024 * 1024 * 10) {
wx.showToast({
title: '图片大小不能超过10MB',
icon: 'error'
})
return
}
that.setData({
tempImg,
ShowPicUrl
})
}
})
},
switchNav(e) {
const nav = e.currentTarget.dataset.nav;
console.log(nav)
this.setData({
currentTab: nav
});
},
// 文生视频输入
onInput(e) {
const value = e.detail.value;
this.setData({
inputValue: value,
charCount: value.length
});
},
// 图生视频输入
onPicInput(e) {
const value = e.detail.value;
this.setData({
picInputValue: value,
picCharCount: value.length
});
},
optimizePrompt() {
// 优化提示词的逻辑
console.log("优化提示词按钮被点击");
// 可以在这里实现优化提示词的功能
},
// 文生视频运动增强
onSliderChange(e) {
const value = e.detail.value;
this.setData({
enhanceValue: value
});
},
// 图生视频运动增强
onPicSliderChange(e) {
const value = e.detail.value;
this.setData({
picEnhanceValue: value
});
},
selectStyle(e) {
const {
key
} = e.currentTarget.dataset;
this.setData({
selectedStyle: key
});
},
selectSize(e) {
const {
ratio,
width,
height
} = e.currentTarget.dataset;
this.setData({
selectedSize: ratio,
selectedWidth: width,
selectedHeight: height
});
},
onSliderChange(e) {
const value = e.detail.value;
this.setData({
enhanceValue: value
});
},
onLoad: function (options) {
let uuid = options.uuid || ''
this.setData({
uuid: uuid
})
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
// 获取角色列表
this.getRoles()
// 获取聊天记录
this.getChatRecords()
},
// 获取角色列表
getRoles() {
let that = this
wx.showLoading({
title: '加载中',
})
app.chatApiRequest({
url: "/chat/getRoles",
method: "GET",
success: res => {
console.log('res', res)
this.setData({
roles: res.data.roles,
currentRole: 9 // 默认9 AI助手
})
},
fail: err => {
console.log('err', err)
},
complete: () => {
}
})
},
// 获取聊天记录
getChatRecords() {
let that = this
let userId = wx.getStorageSync('userId')
let openId = wx.getStorageSync('openid')
wx.showLoading({
title: '加载中',
})
app.chatApiRequest({
url: '/chat/getRecord',
method: 'POST',
data: {
nickname: userId,
openid: openId,
role_id: this.data.currentRole
},
success: (res) => {
let messages = this.data.messages
messages[that.data.currentRole] = res.data.records
that.setData({
messages
})
that.scrollToBottom()
},
fail: (err) => {
console.log('err', err)
},
complete: () => {
wx.hideLoading()
}
})
},
bindInput(e) {
this.setData({
inputMessage: e.detail.value
});
},
// 滚动条置底
scrollToBottom() {
this.setData({
toView: this.data.toView === 'toBottom1' ? 'toBottom2' : 'toBottom1'
});
},
// 发送
sendMessage() {
let that = this
let inputMessage = this.data.inputMessage
if (inputMessage === '') {
wx.showToast({
title: '请输入消息',
icon: 'none'
})
return false
}
let messages = that.data.messages
// 获取会话session_id
let sessionID
if (messages[this.data.currentRole].length !== 0) {
sessionID = messages[this.data.currentRole][messages[this.data.currentRole].length - 1].session_id
}
messages[this.data.currentRole].push({
is_response: false,
message_content: inputMessage
})
this.setData({
inputMessage: ''
})
messages[this.data.currentRole].push({
is_response: true,
message_content: '',
isGenerating: true
})
that.setData({
messages: messages,
})
this.scrollToBottom()
let userId = wx.getStorageSync('userId')
let openid = wx.getStorageSync('openid')
let role = this.data.currentRole
let reqTask = app.chatApiRequest({
url: '/chat/send',
method: 'POST',
enableChunked: true,
data: {
openid,
userid: userId,
prompt: inputMessage,
role_id: role,
session_id: sessionID
},
success: (res) => {
// console.log('res', res)
},
fail: (err) => {
console.log('err', err)
}
})
let newMessages = this.data.messages
let currentMessages = newMessages[this.data.currentRole]
let lastMessages = currentMessages[currentMessages.length - 1]
reqTask.onChunkReceived(r => {
let decoder = new TextDecoder('utf-8');
let str = decoder.decode(r.data);
let lines = str.split('\n\n')
lines.pop()
for (let i of lines) {
let jsonStr = i
if (i.startsWith("data:")) {
jsonStr = jsonStr.substring(5);
}
let messageObject = JSON.parse(jsonStr)
lastMessages.isGenerating = false
lastMessages.message_content += messageObject.output.text
lastMessages.session_id = messageObject.output.session_id
this.setData({
messages: newMessages
})
}
that.scrollToBottom()
})
},
utf8ArrayToStr(array) {
var out, i, len, c;
var char2, char3;
out = "";
len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
return out;
},
toggleRoleDropdown() {
this.setData({
showRoleDropdown: !this.data.showRoleDropdown,
});
},
switchRole(event) {
const roleIndex = event.currentTarget.dataset.role;
this.setData({
currentRole: this.data.roles[roleIndex].id,
currentRoleText: this.data.roles[roleIndex].name,
showRoleDropdown: false,
});
this.getChatRecords()
// 在这里你可以根据需要添加更多逻辑比如切换到不同的AI模型
},
// 判断邀请用户
sendReward_invitation: function (uuid) {
app.apiRequest({
url: '/myapp/reward_invitation', // 后端接口URL
method: 'POST',
data: {
openid: wx.getStorageSync('openid'),
uuid: uuid,
},
success(res) {
console.log(res);
if (res.data.success == true) {
wx.setStorageSync('defaultDailyFreeParseNum', wx.getStorageSync('defaultDailyFreeParseNum') + 10);
}
},
fail(err) {
console.error(err);
// 错误处理
}
});
},
onShow() {
app.getCurrentTabbar(1, this);
app.checkUpdateVersion()
wx.showLoading()
app.getUserInfo().then(() => {
if (this.data.uuid != '' && wx.getStorageSync('openid') != '' && wx.getStorageSync('uuid') != this.data.uuid) {
this.sendReward_invitation(this.data.uuid);
}
}).catch(error => {
console.error('获取用户信息失败:', error);
}).finally(() => {
console.log('getUserInfo调用完成');
});
},
// 清空输入框
inputClear: function () {
this.setData({
videoUrl: ''
})
},
copyContent(e) {
console.log(e)
const content = e.target.dataset.content;
wx.setClipboardData({
data: content,
success() {
wx.showToast({
title: '内容已复制',
icon: 'none',
});
},
fail(err) {
wx.showToast({
title: '复制失败',
icon: 'none',
});
console.error('复制失败', err);
}
});
},
onShareAppMessage: function () {
return {
title: '推荐一款免费又超好用的AI视频文案创作工具快来体验吧',
path: '/pages/index/index?uuid=' + wx.getStorageSync('uuid'),
imageUrl: '/images/index.jpg',
success: function (e) {
wx.showToast({
title: "分享成功",
icon: "success",
duration: 2e3
});
},
fail: function (e) {
wx.showToast({
title: "分享失败",
icon: "none",
duration: 2e3
});
}
}
},
onShareTimeline: function () {
return {
title: '推荐一款免费又超好用的AI视频文案创作工具快来体验吧',
path: '/pages/index/index?uuid=' + wx.getStorageSync('uuid'),
imageUrl: '/images/index.jpg',
success: function (e) {
wx.showToast({
title: "分享成功",
icon: "success",
duration: 2e3
});
},
fail: function (e) {
wx.showToast({
title: "分享失败",
icon: "none",
duration: 2e3
});
}
}
},
})

View File

@@ -0,0 +1,7 @@
{
"usingComponents": {},
"navigationBarTitleText": "创作助手",
"navigationBarBackgroundColor": "#222238",
"navigationBarTextStyle": "white",
"custom-tab-bar": "/custom-tab-bar/index"
}

View File

@@ -0,0 +1,73 @@
<view class="container">
<view class="navbar"></view>
<view wx:if="{{currentTab==='textToVideo'}}">
<view class="input-container">
<textarea bindinput="onInput" class="input-box" maxlength="320" placeholder="输入文本或者图片即可生成原创视频,请用一句话描述您的视频主题、场景、风格等,简洁明了!" value="{{inputValue}}"></textarea>
<view class="char-count">{{charCount}}/320</view>
</view>
<view class="style-selector">
<view class="style-text">选择风格:</view>
<view class="style-options">
<view bindtap="selectStyle" class="style-option {{selectedStyle===key?'active':''}}" data-key="{{key}}" wx:for="{{styles}}" wx:for-index="key" wx:for-item="style" wx:key="{{key}}"> {{style}} </view>
</view>
</view>
<view class="size-selector">
<text class="size-text">选择尺寸:</text>
<view class="size-options">
<view bindtap="selectSize" class="size-option {{selectedSize===item.ratio?'active':''}}" data-height="{{item.height}}" data-ratio="{{item.ratio}}" data-width="{{item.width}}" wx:for="{{sizes}}" wx:key="index"> {{item.ratio}} </view>
</view>
</view>
<view class="enhance-container">
<text class="enhance-text">运动增强:</text>
<slider showValue bindchange="onSliderChange" class="enhance-slider" max="10" min="0" value="{{enhanceValue}}"></slider>
</view>
<text class="description-text">数值越高,视频画面越丰富 【文生视频消耗 10创意点/次】</text>
<button bindtap="textGenerateSubmit" class="generate-button">生成视频</button>
</view>
<view class="full-container" wx:if="{{currentTab==='imageToVideo'}}">
<view bindtap="uploadImage" class="upload-container">
<image class="upload-pic" mode="aspectFit" src="{{ShowPicUrl}}" wx:if="{{ShowPicUrl}}"></image>
<view class="upload-wrap" wx:else>
<image class="upload-icon" src="/images/icon-upload.png"></image>
<text class="upload-text">
<text class="upload-link">点击上传图片</text>
</text>
<text class="upload-info">只能上传jpg/png文件且不超过10MB</text>
</view>
</view>
<view class="img-input-container">
<textarea bindinput="onPicInput" class="input-box" maxlength="320" placeholder="输入提示词来描述您想要的视频内容" value="{{picInputValue}}"></textarea>
<view class="char-count">{{picCharCount}}/320</view>
</view>
<view class="enhance-container" style="margin-top: 10px;">
<text class="enhance-text">运动增强:</text>
<slider showValue bindchange="onPicSliderChange" class="enhance-slider" max="10" min="0" value="{{picEnhanceValue}}"></slider>
</view>
<text class="description-text" style="float: left;">数值越高,视频画面越丰富 【图生视频消耗 10创意点/次】</text>
<button bindtap="picGenerateSubmit" class="generate-button">生成视频</button>
</view>
<view class="chat-box" wx:if="{{currentTab==='ToText'}}">
<view class="role-switch">
<button bindtap="toggleRoleDropdown" class="role-button">{{currentRoleText}} <image class="role-icon" src="../../images/切换角色.png"></image>
</button>
<view class="role-dropdown" wx:if="{{showRoleDropdown}}">
<view bindtap="switchRole" class="role-option" data-role="{{index}}" wx:for="{{roles}}" wx:key="index">{{item.name}}</view>
</view>
</view>
<scroll-view class="chat-window" scrollIntoView="{{toView}}" scrollY="true">
<view class="message {{item.is_response?'ai':'user'}}" wx:for="{{messages[currentRole?currentRole:9]}}" wx:key="index">
<image class="avatar" src="{{item.is_response?'../../images/人工智能机器人.png':'../../images/老师教师男人.png'}}"></image>
<view class="bubble">
<view class="loading-dot" wx:if="{{item.isGenerating}}"></view>
<text bindtap="copyContent" class="content" data-content="{{item.message_content}}" wx:else>{{item.message_content}}</text>
</view>
</view>
<view id="toBottom1"></view>
<view id="toBottom2"></view>
</scroll-view>
<view class="input-area">
<input autoHeight adjust-position='true' bindinput="bindInput" class="input-field" placeholder="请输入消息..." value="{{inputMessage}}"></input>
<button bindtap="sendMessage" class="send-button">发送</button>
</view>
</view>
</view>

414
pages/aitools/aitools.wxss Normal file
View File

@@ -0,0 +1,414 @@
.container,page {
background-color: #222238;
}
.container {
border-top: 1px solid hsla(0,31%,87%,.5);
box-sizing: border-box;
color: #fff;
padding: 10px 10px 61px;
}
.navbar {
background: linear-gradient(180deg,#8d72d2,#7183f3);
border-radius: 5px;
display: flex;
justify-content: space-between;
width: 300px;
}
.nav-item {
color: #fff;
flex: 1;
padding: 5px 10px;
text-align: center;
}
.nav-item.active {
background: linear-gradient(90deg,#6949bb,#4d65fd);
border-bottom: 3px solid #d9d4e4;
border-radius: 5px;
}
.description-text {
color: #a790e2;
font-size: 12px;
}
.input-container {
margin: 10px 0;
position: relative;
width: 100%;
}
.input-box {
background-color: #333;
border: none;
color: #fff;
height: 150px;
}
.char-count {
bottom: 10px;
color: #ccc;
position: absolute;
right: 10px;
}
.optimize-button {
background: linear-gradient(90deg,#6949bb,#4d65fd);
border-radius: 5px;
bottom: 3px;
font-size: 13px;
left: 3px;
padding: 5px;
position: absolute;
text-align: center;
z-index: 9999;
}
.platform-selector {
background-color: #333;
border-radius: 5px;
margin-bottom: 20px;
padding: 10px;
}
.platform-text {
font-weight: 700;
}
.platform-dropdown {
margin-top: 10px;
}
.size-selector,.style-selector {
margin-bottom: 20px;
width: 100%;
}
.size-text,.style-text {
font-weight: 700;
margin-bottom: 10px;
}
.size-options,.style-options {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.size-option,.style-option {
background-color: #333;
border-radius: 5px;
box-shadow: 1px 3px 3px 1px rgba(0,0,0,.3);
color: #fff;
flex: 1;
font-size: 13px;
margin-bottom: 10px;
margin-right: 10px;
padding: 10px;
text-align: center;
transition: all .3s;
white-space: nowrap;
}
.size-option.active,.style-option.active {
background-color: #4a90e2;
}
.size-option:last-child,.style-option:last-child {
margin-right: 0;
}
.enhance-container {
align-items: center;
display: flex;
width: 100%;
}
.enhance-text {
font-weight: 700;
margin-right: 10px;
}
.enhance-slider {
flex: 1;
}
.generate-button {
background: linear-gradient(90deg,#6949bb,#4d65fd);
border: none;
border-radius: 5px;
color: #fff;
margin: 10px auto;
padding: 5px;
text-align: center;
width: 150px;
}
.full-container {
align-items: center;
display: flex;
flex-direction: column;
width: 100%;
}
.upload-container {
background-color: #333;
border: 1px dashed #666;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,.1);
cursor: pointer;
height: 250px;
margin-top: 15px;
max-width: 400px;
width: 100%;
}
.upload-wrap {
align-items: center;
display: flex;
flex-direction: column;
height: 250px;
justify-content: center;
}
.upload-pic {
height: 100%;
width: 100%;
}
.upload-icon {
height: 50px;
margin-bottom: 10px;
width: 50px;
}
.upload-text {
color: #999;
font-size: 14px;
margin-bottom: 5px;
}
.upload-link {
color: #4a90e2;
text-decoration: underline;
}
.upload-info {
color: #666;
font-size: 12px;
}
.img-input-container {
margin-top: 10px;
max-width: 400px;
min-height: 200px;
position: relative;
width: 100%;
}
.input-box {
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
font-size: 14px;
min-height: 200px;
padding: 10px;
resize: none;
width: 100%;
}
.char-count {
color: #999;
font-size: 12px;
margin-top: 5px;
text-align: right;
}
.chat-box {
display: flex;
flex-direction: column;
height: calc(100vh - 127px);
margin-top: 10px;
}
.chat-box,.role-switch {
position: relative;
width: 100%;
}
.role-switch {
margin: 0 auto;
text-align: center;
}
.role-button {
align-items: center;
background: linear-gradient(180deg,#2b2b49,#26263a);
border: none;
border-radius: 5px;
box-shadow: 0 4px 6px rgba(0,0,0,.3),0 1px 3px rgba(0,0,0,.08);
color: #fff;
cursor: pointer;
font-size: 16px;
width: 100%;
}
.role-icon {
height: 16px;
margin-left: 5px;
vertical-align: middle;
width: 16px;
}
.role-dropdown {
background: linear-gradient(180deg,#2b2b49,#26263a);
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0,0,0,.3);
left: 50%;
margin-top: 5px;
position: absolute;
top: 100%;
transform: translateX(-50%);
z-index: 1000;
}
.role-option {
border-bottom: 1px solid #b39494;
color: #fff;
cursor: pointer;
padding: 10px;
}
.role-option:hover {
background: #444;
}
.chat-window {
box-sizing: border-box;
flex: 1;
overflow-y: auto;
padding: 10px;
}
.message {
display: flex;
margin-bottom: 10px;
margin-top: 10px;
}
.message.user {
flex-direction: row-reverse;
}
.message .avatar {
border-radius: 20px;
height: 40px;
margin: 0 3px;
width: 40px;
}
.bubble {
border-radius: 10px;
box-shadow: 0 0 5px rgba(0,0,0,.5);
color: #d6d3dd;
max-width: 70%;
padding: 10px;
}
.message.user .bubble {
background: #6949bb;
color: #fff;
}
.message.ai .bubble {
background: #505579;
color: #fff;
}
.username {
color: #888;
font-size: 12px;
}
.content {
font-size: 14px;
margin-top: 5px;
}
.input-area {
background-color: #222238;
box-sizing: border-box;
display: flex;
padding: 10px;
width: 100%;
height: 40px;
}
.input-field {
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
flex: 1;
min-height: 40px;
padding: 8px;
}
.send-button {
background: linear-gradient(90deg,#6949bb,#4d65fd);
border: none;
border-radius: 5px;
box-sizing: border-box;
color: #fff;
font-size: 16px;
font-weight: 700;
height: 40px;
line-height: 40px;
margin-left: 10px;
padding: 0 20px;
}
.buttom {
background-color: initial;
height: 60px;
width: 100%;
}
.loading-container {
align-items: center;
display: flex;
height: 100vh;
justify-content: center;
}
.loading-dot {
animation: pulse .8s infinite alternate;
background-color: #007bff;
border-radius: 50%;
height: 18px;
width: 18px;
}
@-webkit-keyframes pulse {
0% {
transform: scale(1);
}
100% {
transform: scale(1.4);
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
100% {
transform: scale(1.4);
}
}