[lisa]fix: page

This commit is contained in:
chenlisha02
2025-11-25 20:40:05 +08:00
parent 44f0a6ff2b
commit 43972d964e
14 changed files with 1420 additions and 21 deletions

View File

@@ -8,7 +8,8 @@
"pages/logs/logs",
"pages/history/history",
"pages/terms/terms",
"pages/privacy/privacy"
"pages/privacy/privacy",
"pages/analyze/analyze"
],
"window": {
"navigationBarTextStyle": "black",
@@ -17,16 +18,6 @@
"backgroundColor": "#ffffff",
"backgroundTextStyle": "light"
},
"permission": {
"scope.camera": {
"desc": "需要使用相机拍照识别图片"
},
"scope.writePhotosAlbum": {
"desc": "需要访问相册选择图片"
}
},
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents",
"useExtendedLib" : {

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "图片解析中",
"navigationStyle": "default",
"disableScroll": true,
"backgroundColor": "#000000"
}

View File

@@ -0,0 +1,123 @@
Page({
data: {
imageUrl: '/static/sun.png',
// 用于驱动 CSS 变量的角度与边缘接近度0-100
pointerAngle: 110,
pointerD: 0
},
// 旧的 canvas 动画相关字段不再使用
wrapperRect: null as any,
onLoad(query: Record<string, string>) {
const { imageUrl } = query
if (imageUrl) {
this.setData({ imageUrl })
}
},
onReady() {
// 预先获取容器尺寸,避免首次触摸时未取到
this.ensureWrapperRect()
// 首屏引导动画,参考 test.html 的节奏
// this.playIntroAnimation()
},
onImageLoad() {
// 不再初始化 canvas 动画;仅确保容器尺寸缓存
this.ensureWrapperRect()
},
ensureWrapperRect(cb?: Function) {
if (this.wrapperRect) {
cb && cb()
return
}
const qs = wx.createSelectorQuery()
qs.select('.bg-wrapper').boundingClientRect().exec((res) => {
const rect = res?.[0]
if (rect && rect.width && rect.height) {
this.wrapperRect = rect
}
cb && cb()
})
},
// 触控更新光晕方向与强度(边缘接近度)
// onGlowTouchMove(e: any) {
// const touch = e?.changedTouches?.[0]
// if (!touch) return
// this.ensureWrapperRect(() => {
// const rect = this.wrapperRect
// if (!rect) return
// const { left, top, width, height } = rect
// const x = Math.max(0, Math.min(width, touch.pageX - left))
// const y = Math.max(0, Math.min(height, touch.pageY - top))
// const cx = width / 2
// const cy = height / 2
// const dx = x - cx
// const dy = y - cy
// let angleRadians = 0
// let angleDegrees = 0
// if (dx !== 0 || dy !== 0) {
// angleRadians = Math.atan2(dy, dx)
// angleDegrees = angleRadians * (180 / Math.PI) + 90
// if (angleDegrees < 0) angleDegrees += 360
// }
// let kx = Infinity, ky = Infinity
// if (dx !== 0) kx = cx / Math.abs(dx)
// if (dy !== 0) ky = cy / Math.abs(dy)
// const closeness = Math.min(Math.max(1 / Math.min(kx, ky), 0), 1)
// const pointerD = Math.round(closeness * 100)
// this.setData({ pointerAngle: Math.round(angleDegrees * 100) / 100, pointerD })
// })
// },
// 引导动画:角度扫过 + 边缘接近度渐变
playIntroAnimation() {
const angleStart = 110
const angleEnd = 465
this.setData({ pointerAngle: angleStart })
// 阶段 1pointerD 从 0 -> 50
this.animateNumber({ duration: 500, startValue: 0, endValue: 50, ease: this.easeOutCubic, onUpdate: (v: number) => {
this.setData({ pointerD: Math.round(v) })
}})
// 阶段 2角度 110 -> 465pointerD 维持 50
this.animateNumber({ delay: 0, duration: 1500, startValue: angleStart, endValue: angleEnd, ease: this.easeInCubic, onUpdate: (v: number) => {
this.setData({ pointerAngle: Math.round(v * 100) / 100 })
}})
// 阶段 3继续角度pointerD 50 -> 100
this.animateNumber({ delay: 1500, duration: 2250, startValue: 50, endValue: 100, ease: this.easeOutCubic, onUpdate: (v: number) => {
this.setData({ pointerD: Math.round(v) })
}})
// 阶段 4pointerD 100 -> 0角度保持循环动画由 CSS 完成
this.animateNumber({ delay: 2500, duration: 1500, startValue: 100, endValue: 0, ease: this.easeInCubic, onUpdate: (v: number) => {
this.setData({ pointerD: Math.round(v) })
}})
},
// 简化的数值动画(基于 setTimeout 模拟 rAF
animateNumber(options: { startValue?: number, endValue?: number, duration?: number, delay?: number, ease?: Function, onUpdate?: Function }) {
const { startValue = 0, endValue = 100, duration = 1000, delay = 0, onUpdate = () => {}, ease = (t: number) => t } = options
const startTime = Date.now() + delay
const step = () => {
const now = Date.now()
const elapsed = now - startTime
const t = Math.max(0, Math.min(elapsed / duration, 1))
const eased = startValue + (endValue - startValue) * ease(t)
onUpdate(eased)
if (t < 1) setTimeout(step, 16)
}
setTimeout(step, delay)
},
easeOutCubic(x: number) { return 1 - Math.pow(1 - x, 3) },
easeInCubic(x: number) { return x * x * x },
onUnload() {
// 无需清理 canvas 计时器
}
})

View File

@@ -0,0 +1,11 @@
<view class="container">
<!-- <view class="bg-wrapper" bindtouchmove="onGlowTouchMove" bindtouchstart="onGlowTouchMove" style="--pointer-°: {{pointerAngle}}deg; --pointer-d: {{pointerD}}; --glow-sens: 30; --color-sens: 50;"> -->
<view class="card" style="--pointer-°: {{pointerAngle}}deg; --pointer-d: {{pointerD}}; --glow-sens: 30; --color-sens: 50;">
<image class="bg-image" src="{{imageUrl}}" mode="widthFix" bindload="onImageLoad"></image>
<view class="glow"></view>
</view>
<!-- <div class="mic">
<div class="mic-shadow"></div>
</div> -->
<view class="card-1">magic</view>
</view>

View File

@@ -0,0 +1,268 @@
.container {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.bg-wrapper {
position: relative;
width: 80vw;
max-width: 1000rpx;
border-radius: 24rpx;
isolation: isolate;
}
.bg-image {
width: 400rpx;
height: auto;
display: block;
object-fit: contain;
}
/* :root {
--glow-sens: 30;
--card-bg: linear-gradient(8deg,var(--dark) 75%, color-mix(in hsl, var(--dark), white 2.5%) 75.5%);
--blend: soft-light;
--glow-blend: plus-lighter;
--glow-color: 40deg 80% 80%;
--glow-boost: 0%;
--pads: 40px;
--color-sens: calc(var(--glow-sens) + 20);
--pointer-°: 45deg;
} */
.card {
position: relative;
overflow: hidden;
width: clamp(320px, calc(100svw - calc(40px * 2)), 600px);
height: calc(100svh - calc(40px * 2));
max-height: 600px;
border-radius: 1.768em;
isolation: isolate;
transform: translate3d(0, 0, 0.01px);
display: grid;
border: 1px solid rgb(255 255 255 / 25%);
/* background: var(--card-bg); */
background-repeat: no-repeat;
box-shadow:
rgba(0, 0, 0, 0.1) 0px 1px 2px,
rgba(0, 0, 0, 0.1) 0px 2px 4px,
rgba(0, 0, 0, 0.1) 0px 4px 8px,
rgba(0, 0, 0, 0.1) 0px 8px 16px,
rgba(0, 0, 0, 0.1) 0px 16px 32px,
rgba(0, 0, 0, 0.1) 0px 32px 64px;
}
.card::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
transition: opacity 0.25s ease-out;
z-index: -1;
border: 1px solid transparent;
background:
radial-gradient(at 80% 55%, hsla(268,100%,76%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 69% 34%, hsla(349,100%,74%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 8% 6%, hsla(136,100%,78%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 41% 38%, hsla(192,100%,64%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 86% 85%, hsla(186,100%,74%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 82% 18%, hsla(52,100%,65%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 51% 4%, hsla(12,100%,72%,1) 0px, transparent 50%) padding-box,
linear-gradient(#c299ff 0 100%) padding-box;
mask-image:
linear-gradient( to bottom, black, black ),
radial-gradient( ellipse at 50% 50%, black 40%, transparent 65% ),
radial-gradient( ellipse at 66% 66%, black 5%, transparent 40% ),
radial-gradient( ellipse at 33% 33%, black 5%, transparent 40% ),
radial-gradient( ellipse at 66% 33%, black 5%, transparent 40% ),
radial-gradient( ellipse at 33% 66%, black 5%, transparent 40% ),
conic-gradient( from 45deg at center, transparent 5%, black 15%, black 85%, transparent 95% );
mask-composite: subtract,add,add,add,add,add;
/* opacity increases as pointer gets near edge */
opacity: calc((var(--pointer-d) - 50) / (100 - 50));
mix-blend-mode: soft-light;
}
.card::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
transition: opacity 0.25s ease-out;
z-index: -1;
border: 1px solid transparent;
background:
linear-gradient(linear-gradient(8deg,var(--dark) 75%, color-mix(in hsl, var(--dark), white 2.5%) 75.5%) 0 100%) padding-box,
linear-gradient(rgb(255 255 255 / 0%) 0% 100%) border-box,
radial-gradient(at 80% 55%, hsla(268,100%,76%,1) 0px, transparent 50%) border-box,
radial-gradient(at 69% 34%, hsla(349,100%,74%,1) 0px, transparent 50%) border-box,
radial-gradient(at 8% 6%, hsla(136,100%,78%,1) 0px, transparent 50%) border-box,
radial-gradient(at 41% 38%, hsla(192,100%,64%,1) 0px, transparent 50%) border-box,
radial-gradient(at 86% 85%, hsla(186,100%,74%,1) 0px, transparent 50%) border-box,
radial-gradient(at 82% 18%, hsla(52,100%,65%,1) 0px, transparent 50%) border-box,
radial-gradient(at 51% 4%, hsla(12,100%,72%,1) 0px, transparent 50%) border-box,
linear-gradient(#c299ff 0 100%) border-box;
/* opacity increases as pointer gets near edge */
opacity: calc((var(--pointer-d) - 50) / (100 - 50));
/* border is masked to a cone, originating from the center towards the pointer */
mask-image:
conic-gradient(
from 45deg at center, black 25%, transparent 40%, transparent 60%, black 75%
);
}
.glow {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
transition: opacity 0.25s ease-out;
z-index: -1;
/* glowing border edges */
--outset: 40px;
/* outer padding so the glow can overflow the element without being masked */
inset: calc(var(--outset) * -1);
pointer-events: none;
z-index: 1;
/* glow is masked to a cone, originating from the center towards the pointer */
mask-image:
conic-gradient(
from 45deg at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%
);
/* opacity increases as pointer gets near edge */
opacity: calc((var(--pointer-d) - 30) / (100 - 30));
mix-blend-mode: 'plus-lighter';
}
.glow::before {
content: "";
position: absolute;
inset: var(--outset);
border-radius: inherit;
box-shadow:
inset 0 0 0 1px hsl( 40deg 80% 80% / 100%),
inset 0 0 1px 0 hsl( 40deg 80% 80% / calc(0% + 60%)),
inset 0 0 3px 0 hsl( 40deg 80% 80% / calc(0% + 50%)),
inset 0 0 6px 0 hsl( 40deg 80% 80% / calc(0% + 40%)),
inset 0 0 15px 0 hsl( 40deg 80% 80% / calc(0% + 30%)),
inset 0 0 25px 2px hsl( 40deg 80% 80% / calc(0% + 20%)),
inset 0 0 50px 2px hsl( 40deg 80% 80% / calc(0% + 10%)),
0 0 1px 0 hsl( 40deg 80% 80% / calc(0% + 60%)),
0 0 3px 0 hsl( 40deg 80% 80% / calc(0% + 50%)),
0 0 6px 0 hsl( 40deg 80% 80% / calc(0% + 40%)),
0 0 15px 0 hsl( 40deg 80% 80% / calc(0% + 30%)),
0 0 25px 2px hsl( 40deg 80% 80% / calc(0% + 20%)),
0 0 50px 2px hsl( 40deg 80% 80% / calc(0% + 10%));
}
@keyframes fadeContent {
to {
opacity: 1;
}
}
body, html {
height: 100svh;
overflow: auto;
background: hsl(var(--h), 18%, 12%);
}
main {
justify-items: center;
align-content: center;
min-height: 100%;
}
/* .sun {
opacity: 0.25;
&:hover {
opacity: 1;
}
} */
.moon {
opacty: 1;
}
.sun, .moon {
transition: all 0.2s ease;
}
.card-1 {
background: #191c29;
width:200rpx;
height: 200rpx;
padding: 10rpx;
position: relative;
border-radius: 6rpx;
justify-content: center;
align-items: center;
text-align: center;
display: flex;
font-size: 1.5em;
color: rgb(88 199 250 / 0%);
cursor: pointer;
font-family: cursive;
animation: spin 2.5s linear infinite;
}
.card-1:before {
content: "";
width: 104%;
height: 102%;
border-radius: 8rpx;
background-image: linear-gradient(
var(--rotate)
, #5ddcff, #3c67e3 43%, #4e00c2);
position: absolute;
z-index: -1;
top: -1%;
left: -2%;
animation: spin 2.5s linear infinite;
}
.card-1:after {
position: absolute;
content: "";
top: 10rpx;
left: 0;
right: 0;
z-index: -1;
height: 100%;
width: 100%;
margin: 0 auto;
transform: scale(0.8);
filter: blur(10rpx);
background-image: linear-gradient(
var(--rotate)
, #5ddcff, #3c67e3 43%, #4e00c2);
opacity: 1;
transition: opacity .5s;
animation: spin 2.5s linear infinite;
}
@keyframes spin {
0% {
--rotate: 0deg;
}
100% {
--rotate: 360deg;
}
}

View File

@@ -35,11 +35,15 @@
<view class="button-row">
<t-icon name="{{isPlaying ? 'pause' : 'play'}}" class="bottom-button" size="48rpx" bind:tap="playStandardVoice" />
<view class="bottom-button-img-wrap bottom-button" bind:tap="onTransTap">
<!-- <image src="{{transDisplayMode === 'en_ipa' ? '/static/英.png' : transDisplayMode === 'en' ? '/static/文-中.png' : '/static/文.png'}}" style="width: 32rpx; height: 32rpx;" mode="aspectFit" /> -->
<t-icon name="translate" class="trans-button left-half {{transDisplayMode === 'en_ipa' ? 'trans-active' : 'trans-deactive'}}" size="48rpx"/>
<t-icon name="translate" class="trans-button right-half {{transDisplayMode === 'en_zh' ? 'trans-active' : 'trans-deactive'}}" size="48rpx"/>
<t-icon name="translate" class="trans-button left-half {{transDisplayMode === 'en_ipa' ? 'trans-active' : 'trans-deactive'}}" size="48rpx" />
<t-icon name="translate" class="trans-button right-half {{transDisplayMode === 'en_zh' ? 'trans-active' : 'trans-deactive'}}" size="48rpx" />
</view>
<view class="bottom-button mic-wrap">
<t-icon name="microphone-1" color="{{isRecording ? '#FFFFFF' : '#333333'}}" class="microphone {{isRecording ? 'recording' : 'bottom-button'}}" size="48rpx" bind:longpress="handleRecordStart" bind:touchend="handleRecordEnd" bind:touchcancel="handleRecordEnd" />
<view wx:if="{{isRecording}}" class="mic">
<view class="mic-shadow"></view>
</view>
</view>
<t-icon name="microphone-1" class="microphone bottom-button {{isRecording ? 'recording' : ''}}" size="48rpx" bind:longpress="handleRecordStart" bind:touchend="handleRecordEnd" bind:touchcancel="handleRecordEnd" />
<t-icon name="fact-check" class="bottom-button {{hasScoreInfo ? '' : 'disabled'}}" size="48rpx" bind:tap="onScoreTap" />
<t-icon name="ellipsis" class="bottom-button {{isMoreMenuOpen ? 'more-open' : ''}}" size="48rpx" bind:tap="onMoreTap" />
</view>

View File

@@ -194,13 +194,19 @@
.microphone {
transition: all 0.3s ease;
z-index: 1;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.microphone.recording {
transform: scale(1.1);
background: #ff3b30 !important;
box-shadow: 0 4rpx 16rpx rgba(255, 59, 48, 0.3);
animation: pulse 1.5s infinite;
background: transparent;
/* transform: scale(1.1); */
/* background: #ff3b30 !important; */
/* box-shadow: 0 4rpx 16rpx rgba(255, 59, 48, 0.3); */
/* animation: pulse 1.5s infinite; */
}
@keyframes pulse {
@@ -1215,4 +1221,75 @@
line-height: 28rpx;
color: #909090;
margin-top: 20rpx;
}
/* mic 动效容器,跟随底部按钮位置 */
.mic-wrap {
position: relative;
}
/* 仅在 isRecording 时显示的动效,居中覆盖在按钮上方 */
.mic {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
pointer-events: none;
}
.mic::before, .mic::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 100%;
z-index: 2;
box-shadow: 0 0 4.8px 4.8px #1c084f; /* 原为 4px 4px */
}
.mic::before {
width: 72rpx; /* 原为 60rpx */
height: 72rpx; /* 原为 60rpx */
background-color: #1a084e;
}
.mic::after {
width: 48rpx; /* 原为 40rpx */
height: 48rpx; /* 原为 40rpx */
background-color: #2f1e5f;
animation: mic-circle-size 0.8s linear infinite alternate;
}
.mic-shadow {
width: 72rpx; /* 原为 60rpx */
height: 72rpx; /* 原为 60rpx */
position: absolute;
top: 50%;
left: 50%;
border-radius: 100%;
z-index: 1;
box-shadow: 3.6rpx -14.4rpx 7.2rpx 3.6rpx #823ca6,
9.6rpx -2.4rpx 14.4rpx 3.6rpx #aab3d2,
9.6rpx 7.2rpx 26.4rpx 3.6rpx #5acee3,
19.2rpx 2.4rpx 7.2rpx 3.6rpx #1b7d8f,
2.4rpx 1.2rpx 21.6rpx 3.6rpx #f30bf5;
transform: translate(-50%, -50%);
transform-origin: 0% 0%;
animation: mic-shadow-rotate 2s linear infinite; /* 略慢的旋转节奏 */
}
@keyframes mic-circle-size {
from {
width: 48rpx;
height: 48rpx;
}
to {
width: 57.6rpx;
height: 57.6rpx;
}
}
@keyframes mic-shadow-rotate {
from { rotate: 0deg; }
to { rotate: 360deg; }
}

View File

@@ -44,12 +44,34 @@
<view class="hello">{{DayTypeMap[day_type]}}</view>
<!-- <view class="begin-text">用一个新单词, 开启美好的一天</view> -->
<!-- 功能按钮区域 -->
<view class="action-section welcome-card">
<!-- <view class="action-section welcome-card">
<view class="action-buttons">
<t-icon name="image-add" size="140rpx" bind:click="handleImageSelect" />
<t-action-sheet id="t-action-sheet" bind:selected="handleSelected" />
</view>
</view> -->
<view class="camera" bind:click="handleImageSelect">
<view class="strip"></view>
<view class="lens">
<view class="lens-shutter"></view>
</view>
<!-- <view class="shutter">
<view class="lens-outer"></view>
<view class="lens-inner"></view>
<view class="leaf a"></view>
<view class="leaf b"></view>
<view class="leaf c"></view>
</view> -->
<view class="led"></view>
<view class="btn"></view>
<view class="bottom"></view>
</view>
<view class="photo-wrapper">
<view class="photo">
<view class="photo-inner"></view>
</view>
</view>
<t-action-sheet id="t-action-sheet" bind:selected="handleSelected" />
</view>
<view class="history-wrap">
<view class="today-card" wx:for="{{todaySummary}}" wx:key="index">

View File

@@ -722,4 +722,389 @@
@keyframes bubbleFloat {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-20px) rotate(180deg); }
}
.camera {
position:relative;
/* left:50%; top:50%; */
width:300rpx; height:300rpx;
margin-top:40rpx;
background:#eaeaea;
border:1px solid rgba(0,0,0,.2); border-radius:50rpx;
overflow:hidden;
transition: all .5s ease-in-out;
-webkit-transition: all .5s ease-in-out;
-moz-transition: all .5s ease-in-out;
-ms-transition: all .5s ease-in-out;
-o-transition: all .5s ease-in-out;
z-index:2;
}
.btn {
position: absolute;
top: 20rpx; right: 30rpx;
width: 45rpx; height: 35rpx;
background-color: #2e3e4f;
border-radius: 15rpx;
box-shadow: 0px 3rpx 0px rgba(0,0,0,.4);
transition:all .2 ease-in-out;
-webkit-transition:all .2 ease-in-out;
-moz-transition:all .2 ease-in-out;
-ms-transition:all .2 ease-in-out;
-o-transition:all .2 ease-in-out;
animation: .5s btn;
-webkit-animation: .5s btn;
-moz-animation: .5s btn;
-ms-animation: .5s btn;
-o-animation: .5s btn;
animation-iteration-count:5;
-webkit-animation-iteration-count:5;
-moz-animation-iteration-count:5;
-ms-animation-iteration-count:5;
-o-animation-iteration-count:5;
}
.strip {
height: 110rpx;
background-color: #54b59a;
border-top: 10rpx solid #479a83;
border-bottom: 10rpx solid #479a83;
margin: 80rpx 0px;
box-shadow: 0px 2rpx 0px rgba(0,0,0,.4);
}
.lens {
position:absolute;
top:50%; left:50%;
width:144rpx; height:144rpx;
margin:-90rpx;
border:18rpx solid #b44b37;
border-radius:50%;
background-color:#111;
box-shadow: 0px 5rpx 0px rgba(0,0,0,.4);
}
.lens::before {
content: '';
position: absolute;
width: 50rpx; height: 50rpx;
margin: 27rpx;
border: 20rpx solid rgb(60, 60, 60);
border-radius: 50%;
background: rgb(34, 34, 34);
}
.lens::after {
content: '';
position: absolute;
width: 8rpx; height: 8rpx;
margin: 57rpx;
border: 11rpx solid rgb(22, 22, 22);
border-radius: 50%;
background: rgb(131, 131, 131);
}
.lens-shutter {
position:relative;
width:144rpx; height:144rpx;
display: inline-block;
border-radius: 50%;
overflow: hidden;
}
.lens-shutter::after {
content: " ";
position: absolute;
top: 0;
left: 50%;
height: 100%;
width: 50%;
transition: .5s all linear;
/* animation: shutter 2.0s infinite ease-in-out; */
background:
linear-gradient(-150deg, transparent 52%, #000 calc(52% + 2rpx) calc(52% + 4rpx), grey calc(52% + 6rpx)) bottom/100% 40% no-repeat,
linear-gradient(150deg, transparent 52%, #000 calc(52% + 2rpx) calc(52% + 4rpx), grey calc(52% + 6rpx)),
linear-gradient(90deg, transparent 30%, #000 calc(30% + 2rpx) calc(30% + 4rpx), grey calc(30% + 6rpx)),
linear-gradient(30deg, transparent 52%, #000 calc(52% + 2rpx) calc(52% + 4rpx), grey calc(52% + 6rpx));
transform: rotate(180deg);
transform-origin: left;
}
.lens-shutter::before {
content: " ";
position: absolute;
top: 0;
left: 50%;
height: 100%;
width: 50%;
transition: .5s all linear;
/* animation: shutter 2.0s infinite ease-in-out; */
background:
linear-gradient(-150deg, transparent 52%, #000 calc(52% + 2rpx) calc(52% + 4rpx), grey calc(52% + 6rpx)) bottom/100% 40% no-repeat,
linear-gradient(150deg, transparent 52%, #000 calc(52% + 2rpx) calc(52% + 4rpx), grey calc(52% + 6rpx)),
linear-gradient(90deg, transparent 30%, #000 calc(30% + 2rpx) calc(30% + 4rpx), grey calc(30% + 6rpx)),
linear-gradient(30deg, transparent 52%, #000 calc(52% + 2rpx) calc(52% + 4rpx), grey calc(52% + 6rpx));
}
@keyframes shutter {
0%, 100% {
top: 0;
height: 100%;
width: 50%;
} 50% {
top: -120%;
height: 340%;
width: 170%;
opacity: 0;
}
}
.led {
position: absolute;
left: 35rpx; top: 30rpx;
width: 15rpx; height: 15rpx;
border-radius: 50%;
background-color: rgb(255, 136, 115);
box-shadow: inset 0px 1rpx 0px rgba(0,0,0,.1);
animation: led .8s infinite;
}
@keyframes led {
from {
opacity:.2;
}
to {
opacity:1;
}
}
.shutter {
width: 180rpx;
height: 180rpx;
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
margin:-90rpx;
background: #181816;
box-shadow: 0 0 0 5rpx #181816, 0 0 0 3rpx #373737;
overflow: hidden;
}
.bottom {
position: absolute;
bottom: 0;
width: 100%;
height: 24rpx;
background-color: #c6c6c6;
border-radius: 14rpx 14rpx 0 0;
}
.photo-wrapper {
position: relative;
bottom: 12rpx;
left: 0rpx;
width: 264rpx;
height: 284rpx;
overflow: hidden;
}
.photo {
position: relative;
bottom: 0rpx;
left: 0rpx;
width: 240rpx;
height: 240rpx;
background-color: white;
border-style: solid;
border-color: white;
border-width: 12rpx 12rpx 32rpx 12rpx;
/* box-shadow: 0 4rpx 6rpx rgba(0, 0, 0, 0.1); */
overflow: hidden;
z-index: 2;
animation: reveal 5s;
}
.photo-inner {
position: relative;
width: 100%;
height: 100%;
background-color: black;
filter: blur(10rpx) brightness(0%) saturate(0%);
}
@keyframes reveal {
0% {
transform: translateY(-100%);
}
30% {
transform: translateY(-100%);
}
80% {
transform: translateY(0);
opacity: 1;
}
100% {
opacity: 0;
transform: translateY(100%);
}
}
.lens-outer{
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin: 50rpx 0 0 50rpx;
position: relative;
background: -moz-radial-gradient(83% 83%, circle closest-side, #c0b9c0, #968d9a 35%, #495057);
background: -webkit-radial-gradient(83% 83%, circle closest-side, #c0b9c0, #968d9a 35%, #495057);
background: -o-radial-gradient(83% 83%, circle closest-side, #c0b9c0, #968d9a 35%, #495057);
background: -ms-radial-gradient(83% 83%, circle closest-side, #c0b9c0, #968d9a 35%, #495057);
}
.lens-outer::before{
content: "";
display: block;
width: 68rpx;
height: 68rpx;
position: absolute;
top: 6rpx;
left: 6rpx;
border-radius: 50%;
background-color: #351c3c;
background-image: -webkit-linear-gradient(-45deg, #112b3c, #351c3c 70%);
background-image: -moz-linear-gradient(-45deg, #112b3c, #351c3c 70%);
background-image: -ms-linear-gradient(-45deg, #112b3c, #351c3c 70%);
background-image: -o-linear-gradient(-45deg, #112b3c, #351c3c 70%);
box-shadow: 0 0 0 2rpx #111d29;
}
.lens-outer::after{
content: "";
display: block;
width: 48rpx;
height: 48rpx;
position: absolute;
top: 16rpx;
left: 16rpx;
border-radius: 50%;
background-color: #150619;
background-image: -webkit-linear-gradient(-45deg, #0a2035, #17081b 70%);
background-image: -moz-linear-gradient(-45deg, #0a2035, #17081b 70%);
background-image: -ms-linear-gradient(-45deg, #0a2035, #17081b 70%);
background-image: -o-linear-gradient(-45deg, #0a2035, #17081b 70%);
box-shadow: 0 0 0 2rpx #393745;
}
.lens-inner{
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background: #000;
position: absolute;
top: 76rpx;
left: 76rpx;
border: 2rpx solid #221f27;
}
.lens-inner::before{
content: "";
display: block;
width: 66rpx;
height: 66rpx;
border-radius: 50%;
position: absolute;
top: -20rpx;
left: -20rpx;
-webkit-transform: rotate(45deg);
-o-transform: rotate(45deg);
-moz-transform: rotate(45deg);
transform: rotate(45deg);
box-shadow: inset -6rpx 0 6rpx -4rpx #7c4c88;
}
.leaf {
width: 176rpx;
height: 176rpx;
position: absolute;
border-radius: 0 0 72rpx 104rpx/0 0 24rpx 40rpx;
}
.a{
bottom: 40rpx;
right: 10rpx;
box-shadow: 0 2rpx 0 #4c2f4d, -64rpx 24rpx 32rpx #442149, 0 80rpx 0 #7a21a3,inset 0 -2rpx 2rpx #200526;
}
.a::before {
content: "";
display: block;
width: 176rpx;
height: 176rpx;
position: absolute;
border-radius: 0 0 72rpx 104rpx/0 0 24rpx 40rpx;
bottom: -32rpx;
right: 32rpx;
box-shadow: 24rpx 16rpx 24rpx #5d206e, 0 80rpx 0 #9731c5,inset 0 -2rpx 2rpx #200526;
-webkit-transform: rotate(300deg);
-o-transform: rotate(300deg);
-moz-transform: rotate(300deg);
transform: rotate(300deg);
}
.a::after {
content: "";
display: block;
width: 176rpx;
height: 176rpx;
position: absolute;
border-radius: 0 0 72rpx 104rpx/0 0 24rpx 40rpx;
bottom: -72rpx;
right: 16rpx;
box-shadow: 64rpx 40rpx 40rpx #361d3b, 0 80rpx 0 #8829b7,inset 0 -2rpx 2rpx #200526;
-webkit-transform: rotate(240deg);
-o-transform: rotate(240deg);
-moz-transform: rotate(240deg);
transform: rotate(240deg);
}
.b{
bottom: -36rpx;
right: -6rpx;
-webkit-transform: rotate(180deg);
-o-transform: rotate(180deg);
-moz-transform: rotate(180deg);
transform: rotate(180deg);
box-shadow: 80rpx 56rpx 48rpx #1e618b, 0 80rpx 0 #522162,inset 0 -2rpx 2rpx #200526;
}
.b::before {
content: "";
display: block;
width: 176rpx;
height: 176rpx;
position: absolute;
border-radius: 0 0 72rpx 104rpx/0 0 24rpx 40rpx;
bottom: -32rpx;
right: 32rpx;
-webkit-transform: rotate(-60deg);
-o-transform: rotate(-60deg);
-moz-transform: rotate(-60deg);
transform: rotate(-60deg);
clip: rect(0em, 144rpx, 344.88rpx, 0em);
box-shadow: 44rpx 80rpx 32rpx #256186, 0 80rpx 0 #2f3241,inset 0 -2rpx 2rpx #200526;
}
.b::after{
content: "";
display: block;
width: 176rpx;
height: 176rpx;
position: absolute;
border-radius: 0 0 72rpx 104rpx/0 0 24rpx 40rpx;
bottom: -68rpx;
right: 18rpx;
-webkit-transform: rotate(-120deg);
-o-transform: rotate(-120deg);
-moz-transform: rotate(-120deg);
transform: rotate(-120deg);
clip: rect(0em, 144rpx, 344.88rpx, 0em);
box-shadow: -5rpx 0em 5.8rpx #4a7693,-88rpx 16rpx 40rpx #1e618b, 0 80rpx 0 #411e46,inset 0 -2rpx 2rpx #200526;
}
.c{
bottom: 40rpx;
right: 8rpx;
clip: rect(0em, 80rpx, 344.88rpx, 0em);
box-shadow: 0 2rpx 0 #4c2f4d, -24rpx 20rpx 32rpx #442149, 0 80rpx 0 #7a21a3,inset 0 -2rpx 2rpx #200526;
}

Binary file not shown.

502
miniprogram/test.html Normal file
View File

@@ -0,0 +1,502 @@
<html>
<main id="app">
<div class="card">
<span class="glow"></span>
</div>
</main>
</html>
<style>
:root {
/* vars */
--glow-sens: 30;
--card-bg: linear-gradient(8deg,var(--dark) 75%, color-mix(in hsl, var(--dark), white 2.5%) 75.5%);
--blend: soft-light;
--glow-blend: plus-lighter;
--glow-color: 40deg 80% 80%;
--glow-boost: 0%;
}
.light .card {
--card-bg: linear-gradient(8deg,color-mix(in hsl, hsl(260, 25%, 95%), var(--dark) 2.5%) 75%, hsl(260, 25%, 95%) 75.5%);
--blend: darken;
--glow-blend: luminosity;
--glow-color: 280deg 90% 95%;
--glow-boost: 15%;
--fg: black;
color: var(--fg);
}
body.light {
background-image:
linear-gradient(
180deg,
hsl(var(--h), 8%, 58%),
hsl(var(--h), 15%, 42%)
);
}
.card {
--pads: 40px;
--color-sens: calc(var(--glow-sens) + 20);
--pointer-°: 45deg;
position: relative;
width: clamp(320px, calc(100svw - calc(var(--pads) * 2)), 600px);
height: calc(100svh - calc(var(--pads) * 2));
max-height: 600px;
border-radius: 1.768em;
isolation: isolate;
transform: translate3d(0, 0, 0.01px);
display: grid;
border: 1px solid rgb(255 255 255 / 25%);
background: var(--card-bg);
background-repeat: no-repeat;
&::before,
&::after,
& > .glow {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
transition: opacity 0.25s ease-out;
z-index: -1;
}
&:not(:hover):not(.animating) {
&::before,
&::after,
& > .glow {
opacity: 0;
transition: opacity 0.75s ease-in-out;
}
}
&::before {
/* mesh gradient border */
border: 1px solid transparent;
background:
linear-gradient(var(--card-bg) 0 100%) padding-box,
linear-gradient(rgb(255 255 255 / 0%) 0% 100%) border-box,
radial-gradient(at 80% 55%, hsla(268,100%,76%,1) 0px, transparent 50%) border-box,
radial-gradient(at 69% 34%, hsla(349,100%,74%,1) 0px, transparent 50%) border-box,
radial-gradient(at 8% 6%, hsla(136,100%,78%,1) 0px, transparent 50%) border-box,
radial-gradient(at 41% 38%, hsla(192,100%,64%,1) 0px, transparent 50%) border-box,
radial-gradient(at 86% 85%, hsla(186,100%,74%,1) 0px, transparent 50%) border-box,
radial-gradient(at 82% 18%, hsla(52,100%,65%,1) 0px, transparent 50%) border-box,
radial-gradient(at 51% 4%, hsla(12,100%,72%,1) 0px, transparent 50%) border-box,
linear-gradient(#c299ff 0 100%) border-box;
/* opacity increases as pointer gets near edge */
opacity: calc((var(--pointer-d) - var(--color-sens)) / (100 - var(--color-sens)));
/* border is masked to a cone, originating from the center towards the pointer */
mask-image:
conic-gradient(
from var(--pointer-°) at center, black 25%, transparent 40%, transparent 60%, black 75%
);
}
&::after {
/* mesh gradient background */
border: 1px solid transparent;
background:
radial-gradient(at 80% 55%, hsla(268,100%,76%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 69% 34%, hsla(349,100%,74%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 8% 6%, hsla(136,100%,78%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 41% 38%, hsla(192,100%,64%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 86% 85%, hsla(186,100%,74%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 82% 18%, hsla(52,100%,65%,1) 0px, transparent 50%) padding-box,
radial-gradient(at 51% 4%, hsla(12,100%,72%,1) 0px, transparent 50%) padding-box,
linear-gradient(#c299ff 0 100%) padding-box;
/* 5 radial masks to create a squircle-cut-out, and then a cone-gradient
originating from the center towards the pointer to highlight the edges */
mask-image:
linear-gradient( to bottom, black, black ),
radial-gradient( ellipse at 50% 50%, black 40%, transparent 65% ),
radial-gradient( ellipse at 66% 66%, black 5%, transparent 40% ),
radial-gradient( ellipse at 33% 33%, black 5%, transparent 40% ),
radial-gradient( ellipse at 66% 33%, black 5%, transparent 40% ),
radial-gradient( ellipse at 33% 66%, black 5%, transparent 40% ),
conic-gradient( from var(--pointer-°) at center, transparent 5%, black 15%, black 85%, transparent 95% );
mask-composite: subtract,add,add,add,add,add;
/* opacity increases as pointer gets near edge */
opacity: calc((var(--pointer-d) - var(--color-sens)) / (100 - var(--color-sens)));
mix-blend-mode: var(--blend);
}
& > .glow {
/* glowing border edges */
--outset: var(--pads);
/* outer padding so the glow can overflow the element without being masked */
inset: calc(var(--outset) * -1);
pointer-events: none;
z-index: 1;
/* glow is masked to a cone, originating from the center towards the pointer */
mask-image:
conic-gradient(
from var(--pointer-°) at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%
);
/* opacity increases as pointer gets near edge */
opacity: calc((var(--pointer-d) - var(--glow-sens)) / (100 - var(--glow-sens)));
mix-blend-mode: var(--glow-blend);
&::before {
content: "";
position: absolute;
inset: var(--outset);
border-radius: inherit;
box-shadow:
inset 0 0 0 1px hsl( var(--glow-color) / 100%),
inset 0 0 1px 0 hsl( var(--glow-color) / calc(var(--glow-boost) + 60%)),
inset 0 0 3px 0 hsl( var(--glow-color) / calc(var(--glow-boost) + 50%)),
inset 0 0 6px 0 hsl( var(--glow-color) / calc(var(--glow-boost) + 40%)),
inset 0 0 15px 0 hsl( var(--glow-color) / calc(var(--glow-boost) + 30%)),
inset 0 0 25px 2px hsl( var(--glow-color) / calc(var(--glow-boost) + 20%)),
inset 0 0 50px 2px hsl( var(--glow-color) / calc(var(--glow-boost) + 10%)),
0 0 1px 0 hsl( var(--glow-color) / calc(var(--glow-boost) + 60%)),
0 0 3px 0 hsl( var(--glow-color) / calc(var(--glow-boost) + 50%)),
0 0 6px 0 hsl( var(--glow-color) / calc(var(--glow-boost) + 40%)),
0 0 15px 0 hsl( var(--glow-color) / calc(var(--glow-boost) + 30%)),
0 0 25px 2px hsl( var(--glow-color) / calc(var(--glow-boost) + 20%)),
0 0 50px 2px hsl( var(--glow-color) / calc(var(--glow-boost) + 10%));
;
}
}
}
.card {
box-shadow:
rgba(0, 0, 0, 0.1) 0px 1px 2px,
rgba(0, 0, 0, 0.1) 0px 2px 4px,
rgba(0, 0, 0, 0.1) 0px 4px 8px,
rgba(0, 0, 0, 0.1) 0px 8px 16px,
rgba(0, 0, 0, 0.1) 0px 16px 32px,
rgba(0, 0, 0, 0.1) 0px 32px 64px;
}
.inner {
text-align: center;
h2 {
color: inherit;
font-weight: 500;
font-size: 1.25em;
margin-block: 0.5em;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5em 1em;
}
svg {
height: 24px;
}
}
h2 {
}
.card .inner {
display: flex;
flex-direction: column;
justify-content: space-between;
container-type: inline-size;
position: relative;
overflow: auto;
z-index: 1;
}
.card .content {
padding: 1em;
font-weight: 300;
text-align: left;
line-height: 1.4;
color: color-mix(var(--fg), transparent 60%);
overflow: auto;
scrollbar-width: none;
mask-image: linear-gradient( to top, transparent 5px, black 2em);
& em,
& strong {
color: color-mix(var(--fg), transparent 40%);
}
& p {
opacity: 0;
animation: fadeContent 1.5s ease-in-out 2s both;
&:nth-child(2) {
animation-delay: 2.25s;
}
&:nth-child(3) {
animation-delay: 2.5s;
}
&:nth-child(4) {
animation-delay: 2.75s;
}
}
}
@keyframes fadeContent {
to {
opacity: 1;
}
}
body, html {
height: 100svh;
overflow: auto;
background: hsl(var(--h), 18%, 12%);
}
main {
justify-items: center;
align-content: center;
min-height: 100%;
}
.sun {
opacity: 0.25;
&:hover {
opacity: 1;
}
}
.moon {
opacty: 1;
}
.sun, .moon {
transition: all 0.2s ease;
}
.light {
--link: hsl(var(--canvas), 90%, 50%);
--linkh: hsl(150, 85%, 40%);
& .sun {
opacity: 1;
}
& .moon {
opacity: 0.25;
&:hover {
opacity: 1;
}
}
h2 {
text-shadow: 0 1px 1px lightslategray;
}
}
</style>
<script>
const $card = document.querySelector(".card");
const cardUpdate = (e) => {
const position = pointerPositionRelativeToElement( $card, e );
const [px,py] = position.pixels;
const [perx, pery] = position.percent;
const [dx,dy] = distanceFromCenter( $card, px, py );
const edge = closenessToEdge( $card, px, py );
const angle = angleFromPointerEvent( $card, dx, dy );
$card.style.setProperty('--pointer-x', `${round(perx)}%`);
$card.style.setProperty('--pointer-y', `${round(pery)}%`);
$card.style.setProperty('--pointer-°', `${round(angle)}deg`);
$card.style.setProperty('--pointer-d', `${round(edge * 100)}`);
$card.classList.remove('animating');
};
$card.addEventListener("pointermove", cardUpdate);
const centerOfElement = ($el) => {
const { left, top, width, height } = $el.getBoundingClientRect();
return [ width/2, height/2 ];
}
const pointerPositionRelativeToElement = ($el, e) => {
const pos = [e.clientX, e.clientY];
const { left, top, width, height } = $el.getBoundingClientRect();
const x = pos[0] - left;
const y = pos[1] - top;
const px = clamp((100 / width) * x);
const py = clamp((100 / height) * y);
return { pixels: [x,y], percent: [px,py] }
}
const angleFromPointerEvent = ($el, dx, dy ) => {
// in degrees
let angleRadians = 0;
let angleDegrees = 0;
if ( dx !== 0 || dy !== 0 ) {
angleRadians = Math.atan2(dy, dx);
angleDegrees = angleRadians * (180 / Math.PI) + 90;
if (angleDegrees < 0) {
angleDegrees += 360;
}
}
return angleDegrees;
}
const distanceFromCenter = ( $card, x, y ) => {
// in pixels
const [cx,cy] = centerOfElement( $card );
return [ x - cx, y - cy ];
}
const closenessToEdge = ( $card, x, y ) => {
// in fraction (0,1)
const [cx,cy] = centerOfElement( $card );
const [dx,dy] = distanceFromCenter( $card, x, y );
let k_x = Infinity;
let k_y = Infinity;
if (dx !== 0) {
k_x = cx / Math.abs(dx);
}
if (dy !== 0) {
k_y = cy / Math.abs(dy);
}
return clamp((1 / Math.min(k_x, k_y)), 0, 1);
}
const round = (value, precision = 3) => parseFloat(value.toFixed(precision));
const clamp = (value, min = 0, max = 100) =>
Math.min(Math.max(value, min), max);
/** code for the intro animation, not related to teh interaction */
const playAnimation = () => {
const angleStart = 110;
const angleEnd = 465;
$card.style.setProperty('--pointer-°', `${angleStart}deg`);
$card.classList.add('animating');
animateNumber({
ease: easeOutCubic,
duration: 500,
onUpdate: (v) => {
$card.style.setProperty('--pointer-d', v);
}
});
animateNumber({
ease: easeInCubic,
delay: 0,
duration: 1500,
endValue: 50,
onUpdate: (v) => {
const d = (angleEnd - angleStart) * (v / 100) + angleStart;
$card.style.setProperty('--pointer-°', `${d}deg`);
}
});
animateNumber({
ease: easeOutCubic,
delay: 1500,
duration: 2250,
startValue: 50,
endValue: 100,
onUpdate: (v) => {
const d = (angleEnd - angleStart) * (v / 100) + angleStart;
$card.style.setProperty('--pointer-°', `${d}deg`);
}
});
animateNumber({
ease: easeInCubic,
duration: 1500,
delay: 2500,
startValue: 100,
endValue: 0,
onUpdate: (v) => {
$card.style.setProperty('--pointer-d', v);
},
onEnd: () => {
$card.classList.remove('animating');
}
});
}
playAnimation();
function easeOutCubic(x) {
return 1 - Math.pow(1 - x, 3);
}
function easeInCubic(x) {
return x * x * x;
}
function animateNumber(options) {
const {
startValue = 0,
endValue = 100,
duration = 1000,
delay = 0,
onUpdate = () => {},
ease = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
onStart = () => {},
onEnd = () => {},
} = options;
const startTime = performance.now() + delay;
function update() {
const currentTime = performance.now();
const elapsed = currentTime - startTime;
const t = Math.min(elapsed / duration, 1); // Normalize to [0, 1]
const easedValue = startValue + (endValue - startValue) * ease(t); // Apply easing
onUpdate(easedValue);
if (t < 1) {
requestAnimationFrame(update); // Continue the animation
} else if (t >= 1) {
onEnd();
}
}
setTimeout(() => {
onStart();
requestAnimationFrame(update); // Start the animation after the delay
}, delay);
}
</script>

View File

@@ -2,6 +2,7 @@
// API 基础域名
export const BASE_URL = 'https://app.xhzone.cn'
// export const BASE_URL = 'https://prod-201510-4-1385696640.sh.run.tcloudbase.com'
// 文件服务基础路径
export const FILE_BASE_URL = `${BASE_URL}/api/v1/file`

View File

@@ -51,5 +51,7 @@
"ignore": [],
"include": []
},
"appid": "wxe739c0e6fb02eda8"
"ignoreDevUnusedFiles": false,
"ignoreUploadUnusedFiles": false,
"appid": "wxd6917f77eb2723fb"
}

View File

@@ -42,6 +42,13 @@
"query": "imageId=2089419112290844672",
"launchMode": "default",
"scene": null
},
{
"name": "",
"pathName": "pages/analyze/analyze",
"query": "",
"launchMode": "default",
"scene": null
}
]
}