小台灯登录页源码
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″ />
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″ />
<title>Cute Lamp 💡 with Login Form</title>
<style>
*,
*:after,
*:before {
box-sizing: border-box;
}
:root {
–cord: hsl(210, 0%, calc((40 + (var(–on, 0) * 50)) * 1%));
–opening: hsl(
50,
calc((10 + (var(–on, 0) * 80)) * 1%),
calc((20 + (var(–on, 0) * 70)) * 1%)
);
–feature: #0a0a0a;
–accent: 210;
–tongue: #e06952;
–base-top: hsl(
var(–accent),
0%,
calc((40 + (var(–on, 0) * 40)) * 1%)
);
–base-side: hsl(
var(–accent),
0%,
calc((20 + (var(–on, 0) * 40)) * 1%)
);
–post: hsl(
var(–accent),
0%,
calc((20 + (var(–on, 0) * 40)) * 1%)
);
–b-1: hsla(
45,
calc((0 + (var(–on, 0) * 0)) * 1%),
calc((50 + (var(–on, 0) * 50)) * 1%),
0.85
);
–b-2: hsla(
45,
calc((0 + (var(–on, 0) * 0)) * 1%),
calc((20 + (var(–on, 0) * 30)) * 1%),
0.25
);
–b-3: hsla(
45,
calc((0 + (var(–on, 0) * 0)) * 1%),
calc((20 + (var(–on, 0) * 30)) * 1%),
0.5
);
–b-4: hsla(
45,
calc((0 + (var(–on, 0) * 0)) * 1%),
calc((20 + (var(–on, 0) * 30)) * 1%),
0.25
);
–l-1: hsla(
45,
calc((0 + (var(–on, 0) * 20)) * 1%),
calc((50 + (var(–on, 0) * 50)) * 1%),
0.85
);
–l-2: hsla(
45,
calc((0 + (var(–on, 0) * 20)) * 1%),
calc((50 + (var(–on, 0) * 50)) * 1%),
0.85
);
–shade-hue: 320;
–t-1: hsl(
var(–shade-hue),
calc((0 + (var(–on, 0) * 20)) * 1%),
calc((30 + (var(–on, 0) * 60)) * 1%)
);
–t-2: hsl(
var(–shade-hue),
calc((0 + (var(–on, 0) * 20)) * 1%),
calc((20 + (var(–on, 0) * 35)) * 1%)
);
–t-3: hsl(
var(–shade-hue),
calc((0 + (var(–on, 0) * 20)) * 1%),
calc((10 + (var(–on, 0) * 20)) * 1%)
);
–glow-color: hsl(320, 40%, 45%);
–glow-color-dark: hsl(320, 40%, 35%);
}
body {
min-height: 100vh;
display: grid;
place-items: center;
background: #121921;
margin: 0;
font-family: system-ui, -apple-system, sans-serif;
}
.container {
display: flex;
align-items: center;
justify-content: center;
gap: 8vmin;
flex-wrap: wrap;
padding: 2rem;
}
.login-form {
background: rgba(18, 25, 33, 0.9);
padding: 3rem 2.5rem;
border-radius: 20px;
min-width: 320px;
opacity: 0;
transform: scale(0.8) translateY(20px);
pointer-events: none;
transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
border: 2px solid transparent;
box-shadow: 0 0 0px rgba(255, 255, 255, 0);
}
.login-form.active {
opacity: 1;
transform: scale(1) translateY(0);
pointer-events: all;
border-color: var(–glow-color);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.1),
0 0 30px var(–glow-color),
inset 0 0 15px rgba(255, 255, 255, 0.05);
}
.login-form h2 {
color: #fff;
font-size: 2rem;
margin: 0 0 2rem 0;
text-align: center;
text-shadow: 0 0 8px var(–glow-color);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
color: #aaa;
font-size: 0.9rem;
margin-bottom: 0.5rem;
text-shadow: 0 0 5px var(–glow-color);
}
.form-group input {
width: 100%;
padding: 0.75rem 1rem;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: #fff;
font-size: 1rem;
transition: all 0.3s ease;
}
.form-group input:focus {
outline: none;
border-color: var(–glow-color);
box-shadow: 0 0 10px var(–glow-color);
background: rgba(255, 255, 255, 0.08);
}
.form-group input::placeholder {
color: #666;
}
.login-btn {
width: 100%;
padding: 0.875rem;
background: linear-gradient(
135deg,
var(–glow-color),
var(–glow-color-dark)
);
border: none;
border-radius: 10px;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
margin-top: 0.5rem;
}
.login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3),
0 0 20px var(–glow-color);
}
.login-btn:active {
transform: translateY(0);
}
.form-footer {
margin-top: 1.5rem;
text-align: center;
}
.forgot-link {
color: #888;
font-size: 0.9rem;
text-decoration: none;
transition: all 0.3s ease;
}
.forgot-link:hover {
color: var(–glow-color);
text-shadow: 0 0 10px var(–glow-color);
}
.radio-controls {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.lamp {
display: none;
height: 40vmin;
overflow: visible !important;
}
.cord {
stroke: var(–cord);
}
.cord–rig {
display: none;
}
.lamp__tongue {
fill: var(–tongue);
}
.lamp__hit {
cursor: pointer;
opacity: 0;
}
.lamp__feature {
fill: var(–feature);
}
.lamp__stroke {
stroke: var(–feature);
}
.lamp__mouth,
.lamp__light {
opacity: var(–on, 0);
}
.shade__opening {
fill: var(–opening);
}
.shade__opening-shade {
opacity: calc(1 – var(–on, 0));
}
.post__body {
fill: var(–post);
}
.base__top {
fill: var(–base-top);
}
.base__side {
fill: var(–base-side);
}
.top__body {
fill: var(–t-3);
}
</style>
</head>
<body>
<form class=”radio-controls”>
<input type=”radio” id=”on” name=”status” value=”on” />
<label for=”on”>On</label>
<input type=”radio” id=”off” name=”status” value=”off” />
<label for=”off”>Off</label>
</form>
<div class=”container”>
<svg
class=”lamp”
viewBox=”0 0 333 484″
fill=”none”
xmlns=”http://www.w3.org/2000/svg”
>
<g class=”lamp__shade shade”>
<ellipse
class=”shade__opening”
cx=”165″
cy=”220″
rx=”130″
ry=”20″
/>
<ellipse
class=”shade__opening-shade”
cx=”165″
cy=”220″
rx=”130″
ry=”20″
fill=”url(#opening-shade)”
/>
</g>
<g class=”lamp__base base”>
<path
class=”base__side”
d=”M165 464c44.183 0 80-8.954 80-20v-14h-22.869c-14.519-3.703-34.752-6-57.131-6-22.379 0-42.612 2.297-57.131 6H85v14c0 11.046 35.817 20 80 20z”
/>
<path
d=”M165 464c44.183 0 80-8.954 80-20v-14h-22.869c-14.519-3.703-34.752-6-57.131-6-22.379 0-42.612 2.297-57.131 6H85v14c0 11.046 35.817 20 80 20z”
fill=”url(#side-shading)”
/>
<ellipse
class=”base__top”
cx=”165″
cy=”430″
rx=”80″
ry=”20″
/>
<ellipse
cx=”165″
cy=”430″
rx=”80″
ry=”20″
fill=”url(#base-shading)”
/>
</g>
<g class=”lamp__post post”>
<path
class=”post__body”
d=”M180 142h-30v286c0 3.866 6.716 7 15 7 8.284 0 15-3.134 15-7V142z”
/>
<path
d=”M180 142h-30v286c0 3.866 6.716 7 15 7 8.284 0 15-3.134 15-7V142z”
fill=”url(#post-shading)”
/>
</g>
<g class=”lamp__cords cords”>
<path
class=”cord cord–rig”
d=”M124 187.033V347″
stroke-width=”6″
stroke-linecap=”round”
/>
<path
class=”cord cord–rig”
d=”M124 187.023s17.007 21.921 17.007 34.846c0 12.925-11.338 23.231-17.007 34.846-5.669 11.615-17.007 21.921-17.007 34.846 0 12.925 17.007 34.846 17.007 34.846″
stroke-width=”6″
stroke-linecap=”round”
/>
<path
class=”cord cord–rig”
d=”M124 187.017s-21.259 17.932-21.259 30.26c0 12.327 14.173 20.173 21.259 30.26 7.086 10.086 21.259 17.933 21.259 30.26 0 12.327-21.259 30.26-21.259 30.26″
stroke-width=”6″
stroke-linecap=”round”
/>
<path
class=”cord cord–rig”
d=”M124 187s29.763 8.644 29.763 20.735-19.842 13.823-29.763 20.734c-9.921 6.912-29.763 8.644-29.763 20.735S124 269.939 124 269.939″
stroke-width=”6″
stroke-linecap=”round”
/>
<path
class=”cord cord–rig”
d=”M124 187.029s-10.63 26.199-10.63 39.992c0 13.794 7.087 26.661 10.63 39.992 3.543 13.331 10.63 26.198 10.63 39.992 0 13.793-10.63 39.992-10.63 39.992″
stroke-width=”6″
stroke-linecap=”round”
/>
<path
class=”cord cord–rig”
d=”M124 187.033V347″
stroke-width=”6″
stroke-linecap=”round”
/>
<line
class=”cord cord–dummy”
x1=”124″
y2=”348″
x2=”124″
y1=”190″
stroke-width=”6″
stroke-linecap=”round”
/>
</g>
<path
class=”lamp__light”
d=”M290.5 193H39L0 463.5c0 11.046 75.478 20 165.5 20s167-11.954 167-23l-42-267.5z”
fill=”url(#light)”
/>
<g class=”lamp__top top”>
<path
class=”top__body”
fill-rule=”evenodd”
clip-rule=”evenodd”
d=”M164.859 0c55.229 0 100 8.954 100 20l29.859 199.06C291.529 208.451 234.609 200 164.859 200S38.189 208.451 35 219.06L64.859 20c0-11.046 44.772-20 100-20z”
/>
<path
class=”top__shading”
fill-rule=”evenodd”
clip-rule=”evenodd”
d=”M164.859 0c55.229 0 100 8.954 100 20l29.859 199.06C291.529 208.451 234.609 200 164.859 200S38.189 208.451 35 219.06L64.859 20c0-11.046 44.772-20 100-20z”
fill=”url(#top-shading)”
/>
</g>
<g class=”lamp__face face”>
<g class=”lamp__mouth”>
<path
d=”M165 178c19.882 0 36-16.118 36-36h-72c0 19.882 16.118 36 36 36z”
fill=”#141414“
/>
<clipPath
class=”lamp__feature”
id=”mouth”
x=”129″
y=”142″
width=”72″
height=”36″
>
<path
d=”M165 178c19.882 0 36-16.118 36-36h-72c0 19.882 16.118 36 36 36z”
fill=”#141414“
/>
</clipPath>
<g clip-path=”url(#mouth)”>
<circle
class=”lamp__tongue”
cx=”179.4″
cy=”172.6″
r=”18″
/>
</g>
</g>
<g class=”lamp__eyes”>
<path
class=”lamp__eye lamp__stroke”
d=”M115 135c0-5.523-5.82-10-13-10s-13 4.477-13 10″
stroke-width=”4″
stroke-linecap=”round”
stroke-linejoin=”round”
/>
<path
class=”lamp__eye lamp__stroke”
d=”M241 135c0-5.523-5.82-10-13-10s-13 4.477-13 10″
stroke-width=”4″
stroke-linecap=”round”
stroke-linejoin=”round”
/>
</g>
</g>
<defs>
<linearGradient
id=”opening-shade”
x1=”35″
y1=”220″
x2=”295″
y2=”220″
gradientUnits=”userSpaceOnUse”
>
<stop />
<stop
offset=”1″
stop-color=”var(–shade)”
stop-opacity=”0″
/>
</linearGradient>
<linearGradient
id=”base-shading”
x1=”85″
y1=”444″
x2=”245″
y2=”444″
gradientUnits=”userSpaceOnUse”
>
<stop stop-color=”var(–b-1)” />
<stop
offset=”0.8″
stop-color=”var(–b-2)”
stop-opacity=”0″
/>
</linearGradient>
<linearGradient
id=”side-shading”
x1=”119″
y1=”430″
x2=”245″
y2=”430″
gradientUnits=”userSpaceOnUse”
>
<stop stop-color=”var(–b-3)” />
<stop
offset=”1″
stop-color=”var(–b-4)”
stop-opacity=”0″
/>
</linearGradient>
<linearGradient
id=”post-shading”
x1=”150″
y1=”288″
x2=”180″
y2=”288″
gradientUnits=”userSpaceOnUse”
>
<stop stop-color=”var(–b-1)” />
<stop
offset=”1″
stop-color=”var(–b-2)”
stop-opacity=”0″
/>
</linearGradient>
<linearGradient
id=”light”
x1=”165.5″
y1=”218.5″
x2=”165.5″
y2=”483.5″
gradientUnits=”userSpaceOnUse”
>
<stop stop-color=”var(–l-1)” stop-opacity=”.2″ />
<stop
offset=”1″
stop-color=”var(–l-2)”
stop-opacity=”0″
/>
</linearGradient>
<linearGradient
id=”top-shading”
x1=”56″
y1=”110″
x2=”295″
y2=”110″
gradientUnits=”userSpaceOnUse”
>
<stop stop-color=”var(–t-1)” stop-opacity=”.8″ />
<stop
offset=”1″
stop-color=”var(–t-2)”
stop-opacity=”0″
/>
</linearGradient>
</defs>
<circle
class=”lamp__hit”
cx=”124″
cy=”347″
r=”66″
fill=”#C4C4C4“
fill-opacity=”.1″
/>
</svg>
<div class=”login-form”>
<h2>欢迎回来</h2>
<form onsubmit=”return false;”>
<div class=”form-group”>
<label for=”username”>账号</label>
<input
type=”text”
id=”username”
placeholder=”请输入账号”
required
/>
</div>
<div class=”form-group”>
<label for=”password”>密码</label>
<input
type=”password”
id=”password”
placeholder=”请输入密码”
required
/>
</div>
<button type=”submit” class=”login-btn”>登录</button>
<div class=”form-footer”>
<a href=”#” class=”forgot-link”>忘记密码?</a>
</div>
</form>
</div>
</div>
<script src=”https://unpkg.co/gsap@3/dist/gsap.min.js”></script>
<script src=”https://unpkg.com/gsap@3/dist/Draggable.min.js”></script>
<script src=”https://assets.codepen.io/16327/MorphSVGPlugin3.min.js”></script>
<script>
const {
gsap,
gsap: { registerPlugin, set, to, timeline },
MorphSVGPlugin,
Draggable,
} = window;
registerPlugin(MorphSVGPlugin);
const AUDIO = {
CLICK: new Audio(“https://assets.codepen.io/605876/click.mp3”),
};
const ON = document.querySelector(“#on“);
const OFF = document.querySelector(“#off“);
const LOGIN_FORM = document.querySelector(“.login-form”);
let startX;
let startY;
const PROXY = document.createElement(“div”);
const CORDS = gsap.utils.toArray(“.cords path”);
const CORD_DURATION = 0.1;
const HIT = document.querySelector(“.lamp__hit”);
const DUMMY_CORD = document.querySelector(“.cord–dummy”);
const ENDX = DUMMY_CORD.getAttribute(“x2”);
const ENDY = DUMMY_CORD.getAttribute(“y2”);
const RESET = () => {
set(PROXY, {
x: ENDX,
y: ENDY,
});
};
RESET();
const STATE = {
ON: false,
};
gsap.set([“.cords”, HIT], {
x: -10,
});
gsap.set(“.lamp__eye”, {
rotate: 180,
transformOrigin: “50% 50%”,
yPercent: 50,
});
const CORD_TL = timeline({
paused: true,
onStart: () => {
STATE.ON = !STATE.ON;
set(document.documentElement, { “–on”: STATE.ON ? 1 : 0 });
const hue = gsap.utils.random(0, 359);
set(document.documentElement, { “–shade-hue”: hue });
const glowColor = `hsl(${hue}, 40%, 45%)`;
const glowColorDark = `hsl(${hue}, 40%, 35%)`;
set(document.documentElement, {
“–glow-color”: glowColor,
});
set(document.documentElement, {
“–glow-color-dark”: glowColorDark,
});
set(“.lamp__eye”, {
rotate: STATE.ON ? 0 : 180,
});
set([DUMMY_CORD, HIT], { display: “none” });
set(CORDS[0], { display: “block” });
AUDIO.CLICK.play();
if (STATE.ON) {
ON.setAttribute(“checked”, true);
OFF.removeAttribute(“checked”);
LOGIN_FORM.classList.add(“active”);
} else {
ON.removeAttribute(“checked”);
OFF.setAttribute(“checked”, true);
LOGIN_FORM.classList.remove(“active”);
}
},
onComplete: () => {
set([DUMMY_CORD, HIT], { display: “block” });
set(CORDS[0], { display: “none” });
RESET();
},
});
for (let i = 1; i < CORDS.length; i++) {
CORD_TL.add(
to(CORDS[0], {
morphSVG: CORDS[i],
duration: CORD_DURATION,
repeat: 1,
yoyo: true,
})
);
}
Draggable.create(PROXY, {
trigger: HIT,
type: “x,y”,
onPress: (e) => {
startX = e.x;
startY = e.y;
},
onDrag: function () {
set(DUMMY_CORD, {
attr: {
x2: this.x,
y2: Math.max(400, this.y),
},
});
},
onRelease: function (e) {
const DISTX = Math.abs(e.x – startX);
const DISTY = Math.abs(e.y – startY);
const TRAVELLED = Math.sqrt(DISTX * DISTX + DISTY * DISTY);
to(DUMMY_CORD, {
attr: { x2: ENDX, y2: ENDY },
duration: CORD_DURATION,
onComplete: () => {
if (TRAVELLED > 50) {
CORD_TL.restart();
} else {
RESET();
}
},
});
},
});
gsap.set(“.lamp”, { display: “block” });
</script>
<div style=”
color: #fff;
text-align: center;
margin-top: 40px;
font-size: 14px;
opacity: 0.7;
“>
2026/3/30 <strong>—— Mr.L</strong>
</div>
</body>
</html>
夜雨聆风