Files
miniprogram-1/miniprogram/test.html
2025-11-25 20:40:05 +08:00

502 lines
14 KiB
HTML

<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>