One self-contained HTML file, no libraries. Your AI gets it from Flow's get_pattern tool.
Show the code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Password garden sign-up | Flow pattern</title>
<!--
Flow login pattern: Password garden (https://flow.donta.io/screens/login/password-garden)
Sign-up where every password rule is a seed in a raised garden bed. Meet a rule
and its plant sprouts, grows and blooms into its own flower; the sun rises from
dawn to bright day as the password gets stronger. The button is never silently
disabled: submitting with something missing says exactly what, and the seeds
still waiting give a little wiggle. Success brings every flower to full bloom
and a butterfly drifts in. The garden is decorative (aria-hidden); every rule's
state is in the form as text. Under reduced motion the garden simply shows
each state without animating.
Demo: try "tidepool7harbor" for a strong password; "ada123" shows what's missing.
-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,700&display=swap" rel="stylesheet">
<style>
:root {
--ink: #1b2a21;
--ink-soft: #4d5e53;
--line: #d9e3da;
--card: #ffffff;
--green: #2e7a4b;
--green-deep: #215c38;
--sun: #ffc23d;
--coral: #ff6b57;
--violet: #7468f0;
--err: #b3261e;
--serif: "Fraunces", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
--sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body { min-height: 100vh; font-family: var(--sans); color: var(--ink); background: #e9f3ea; overflow-x: hidden; }
.scene { position: fixed; inset: 0; }
.scene svg { width: 100%; height: 100%; display: block; }
.layout { position: relative; min-height: 100vh; display: grid; grid-template-columns: 1fr minmax(360px, 440px); align-items: center; gap: 40px; padding: 40px clamp(20px, 6vw, 96px); }
.brand { position: absolute; top: 28px; left: clamp(20px, 6vw, 96px); display: flex; align-items: center; gap: 10px; font: 700 21px/1 var(--serif); color: #fff; text-shadow: 0 1px 8px rgba(20,30,60,.35); transition: color .8s; }
.brand svg { width: 26px; height: 26px; }
.day .brand { color: var(--ink); text-shadow: none; }
/* Card */
.card {
grid-column: 2;
position: relative;
background: rgba(255,255,255,.96);
border-radius: 24px;
padding: 32px 32px 26px;
box-shadow: 0 30px 70px -28px rgba(25,45,35,.55), 0 2px 0 rgba(255,255,255,.6) inset, 0 0 0 1px rgba(30,60,40,.06);
}
h1 { margin: 0 0 8px; font: 700 clamp(30px, 3.4vw, 38px)/1.02 var(--serif); letter-spacing: -.02em; }
.sub { margin: 0 0 22px; color: var(--ink-soft); font-size: 15.5px; line-height: 1.5; }
form { display: grid; gap: 16px; }
label { display: grid; gap: 7px; font-size: 14px; font-weight: 650; }
.field { position: relative; }
input {
width: 100%; min-height: 50px; padding: 0 16px; border-radius: 14px; border: 1.5px solid var(--line);
background: #fbfdfb; color: var(--ink); font: 16px var(--sans); transition: border-color .15s, box-shadow .15s;
}
input:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 4px rgba(46,122,75,.18); background: #fff; }
input[aria-invalid="true"] { border-color: var(--err); }
.pw input { padding-right: 84px; }
.show { position: absolute; right: 5px; top: 5px; bottom: 5px; min-width: 70px; border: 0; border-radius: 10px; background: #eef5ef; color: var(--green-deep); font: 600 14px var(--sans); cursor: pointer; }
.show:hover { background: #e2eee4; }
.show:focus-visible, .btn:focus-visible, a:focus-visible { outline: 3px solid var(--green); outline-offset: 2px; }
.strength { display: flex; align-items: center; gap: 10px; margin: -4px 0 0; font-size: 13px; color: var(--ink-soft); min-height: 18px; }
.meter { flex: 1; display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.meter i { height: 6px; border-radius: 99px; background: #e5ece6; transition: background-color .35s; }
.lv1 .meter i:nth-child(1) { background: #f08a5d; }
.lv2 .meter i:nth-child(-n+2) { background: #f2b63d; }
.lv3 .meter i { background: var(--green); }
.strength b { font-weight: 650; color: var(--ink); min-width: 92px; text-align: right; }
.rules { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; font-size: 14.5px; }
.rules li { display: flex; align-items: center; gap: 11px; color: var(--ink-soft); transition: color .2s; }
.rules li .bud { flex: none; width: 22px; height: 22px; border-radius: 50%; border: 2px dashed #c7d3c9; display: grid; place-items: center; transition: background-color .25s, border-color .25s, transform .4s cubic-bezier(.3,1.8,.5,1); }
.rules li .bud svg { width: 12px; height: 12px; opacity: 0; transition: opacity .2s; }
.rules li.met { color: var(--ink); }
.rules li.met .bud { border-style: solid; transform: scale(1.1); }
.rules li.met .bud svg { opacity: 1; }
.rules li[data-rule="len"].met .bud { background: var(--sun); border-color: #e3a51f; }
.rules li[data-rule="mix"].met .bud { background: var(--coral); border-color: #e2503d; }
.rules li[data-rule="name"].met .bud { background: var(--violet); border-color: #5b50d8; }
.rules li.missing { color: var(--err); }
.rules li.missing .bud { border-color: var(--err); }
.btn {
min-height: 52px; border: 0; border-radius: 14px; cursor: pointer; margin-top: 4px;
background: var(--green); color: #fff; font: 650 16px/1 var(--sans);
box-shadow: 0 2px 0 var(--green-deep), 0 12px 24px -14px rgba(33,92,56,.8);
transition: background-color .2s, transform .1s;
}
.btn:hover { background: #2a6f44; }
.btn:active { transform: translateY(1px); }
.btn[aria-busy="true"] { opacity: .8; cursor: progress; }
.msg { margin: 0; min-height: 20px; font-size: 14px; line-height: 1.45; }
.msg.err { color: var(--err); }
.foot { margin: 2px 0 0; font-size: 14px; color: var(--ink-soft); }
.foot a { color: var(--green-deep); font-weight: 650; }
.hint { margin: 0; font-size: 12px; color: #7d8b81; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.done { display: none; text-align: left; }
.card.is-done form, .card.is-done .intro { display: none; }
.card.is-done .done { display: grid; gap: 14px; animation: rise .6s cubic-bezier(.2,.9,.3,1.2) both; }
.done h2 { margin: 0; font: 700 36px/1.02 var(--serif); letter-spacing: -.02em; }
.done h2:focus { outline: none; }
.done p { margin: 0; color: var(--ink-soft); line-height: 1.5; }
.done b { color: var(--ink); overflow-wrap: anywhere; }
.done .btn { display: grid; place-items: center; text-decoration: none; }
.badge { width: 54px; height: 54px; border-radius: 16px; background: #eaf6ee; display: grid; place-items: center; }
@keyframes rise { from { opacity: 0; translate: 0 14px; } }
/* Garden art */
.stem { fill: none; stroke: #3f8f4f; stroke-width: 5; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .7s cubic-bezier(.4,0,.2,1); }
.leaf { transform-box: fill-box; transform: scale(0); transition: transform .45s cubic-bezier(.3,1.6,.5,1) .4s; }
.leaf.l { transform-origin: right center; }
.leaf.r { transform-origin: left center; }
.head { transform-box: fill-box; transform-origin: center; transform: scale(0) rotate(-40deg); transition: transform .6s cubic-bezier(.3,1.6,.5,1) .55s; }
.seed { transition: opacity .3s .1s, transform .3s; transform-box: fill-box; transform-origin: center bottom; }
.stem { opacity: 0; }
.plant.sprout .stem, .plant.grown .stem { opacity: 1; }
.plant.sprout .stem { stroke-dashoffset: .86; }
.plant.grown .stem { stroke-dashoffset: 0; }
.plant.grown .leaf { transform: scale(1); }
.plant.grown .head { transform: scale(1) rotate(0); }
.plant.grown .seed { opacity: 0; }
.plant.nudge .seed { animation: nudge .5s ease 2; }
@keyframes nudge { 25% { transform: rotate(-12deg) translateY(-2px); } 75% { transform: rotate(12deg) translateY(-2px); } }
.bloom .plant .head { transform: scale(1.14) rotate(0); transition-delay: 0s; }
.bloom .plant .sway { animation: sway 3.2s ease-in-out infinite alternate; transform-box: fill-box; transform-origin: center bottom; }
.bloom .plant:nth-child(2) .sway { animation-delay: -1.1s; }
.bloom .plant:nth-child(3) .sway { animation-delay: -2.2s; }
@keyframes sway { from { rotate: -2.2deg; } to { rotate: 2.2deg; } }
.sun { transition: transform 1.1s cubic-bezier(.3,1.2,.4,1); }
.sun-rays { transform-box: fill-box; transform-origin: center; animation: spin 40s linear infinite; }
@keyframes spin { to { rotate: 360deg; } }
.cloud { animation: drift 60s linear infinite; }
.cloud.c2 { animation-duration: 85s; animation-delay: -40s; }
@keyframes drift { from { translate: -260px 0; } to { translate: 1060px 0; } }
.butterfly { opacity: 0; }
.bloom .butterfly { opacity: 1; animation: flutter-path 5.5s cubic-bezier(.45,.05,.4,1) forwards; }
.wing { transform-box: fill-box; animation: flap .18s ease-in-out infinite alternate; }
.wing.l { transform-origin: right center; } .wing.r { transform-origin: left center; }
@keyframes flap { to { scale: .25 1; } }
@keyframes flutter-path {
0% { transform: translate(-200px, 40px); }
30% { transform: translate(30px, -50px); }
55% { transform: translate(140px, 30px); }
80% { transform: translate(200px, -30px); }
100% { transform: translate(236px, 44px); }
}
.sparkle { opacity: 0; transform-box: fill-box; transform-origin: center; }
.bloom .sparkle { animation: twinkle 1.6s ease-in-out infinite; }
.bloom .sparkle:nth-of-type(2) { animation-delay: .5s; } .bloom .sparkle:nth-of-type(3) { animation-delay: 1s; }
@keyframes twinkle { 0%, 100% { opacity: 0; scale: .4; } 50% { opacity: 1; scale: 1; } }
@media (max-width: 860px) {
.scene { position: absolute; inset: 0 0 auto 0; height: 360px; }
.layout { grid-template-columns: 1fr; padding: 300px 16px 32px; }
.card { grid-column: 1; padding: 26px 22px 22px; }
body { background: linear-gradient(#e9f3ea, #dcecdf); }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { transition: none !important; animation: none !important; }
.bloom .butterfly { transform: translate(236px, 44px); }
}
</style>
</head>
<body>
<div class="scene" aria-hidden="true">
<svg id="art" viewBox="0 0 800 600" preserveAspectRatio="xMidYMax slice">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop id="skyTop" offset="0" stop-color="#33416e"/>
<stop id="skyMid" offset=".55" stop-color="#b98aa0"/>
<stop id="skyLow" offset="1" stop-color="#f4b393"/>
</linearGradient>
<radialGradient id="sunGlow">
<stop offset="0" stop-color="#fff3c4" stop-opacity=".95"/>
<stop offset=".35" stop-color="#ffd76a" stop-opacity=".45"/>
<stop offset="1" stop-color="#ffd76a" stop-opacity="0"/>
</radialGradient>
<linearGradient id="wood" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#b47a4c"/><stop offset="1" stop-color="#8d5a35"/>
</linearGradient>
<linearGradient id="soil" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#7a5038"/><stop offset="1" stop-color="#5c3a28"/>
</linearGradient>
</defs>
<rect width="800" height="600" fill="url(#sky)"/>
<g class="sun" id="sun" style="transform: translateY(300px)">
<circle cx="300" cy="190" r="120" fill="url(#sunGlow)"/>
<g class="sun-rays" opacity=".55">
<g stroke="#ffd76a" stroke-width="4" stroke-linecap="round">
<line x1="300" y1="122" x2="300" y2="138"/><line x1="300" y1="242" x2="300" y2="258"/>
<line x1="232" y1="190" x2="248" y2="190"/><line x1="352" y1="190" x2="368" y2="190"/>
<line x1="252" y1="142" x2="263" y2="153"/><line x1="337" y1="227" x2="348" y2="238"/>
<line x1="348" y1="142" x2="337" y2="153"/><line x1="263" y1="227" x2="252" y2="238"/>
</g>
</g>
<circle cx="300" cy="190" r="38" fill="#ffcf4a"/>
<circle cx="292" cy="182" r="30" fill="#ffdf7a" opacity=".6"/>
</g>
<g class="cloud" opacity=".85"><ellipse cx="120" cy="170" rx="58" ry="17" fill="#fff"/><ellipse cx="150" cy="158" rx="32" ry="19" fill="#fff"/><ellipse cx="102" cy="160" rx="24" ry="14" fill="#fff"/></g>
<g class="cloud c2" opacity=".7"><ellipse cx="80" cy="235" rx="44" ry="12" fill="#fff"/><ellipse cx="104" cy="226" rx="24" ry="14" fill="#fff"/></g>
<path id="hillBack" d="M0 420 C 120 360, 230 372, 340 404 S 560 350, 800 392 V600 H0Z" fill="#7aa58a"/>
<path id="hillFront" d="M0 462 C 140 420, 300 440, 430 458 S 660 430, 800 452 V600 H0Z" fill="#8fbf7a"/>
<g fill="#6f9c63">
<circle cx="600" cy="440" r="22"/><circle cx="626" cy="446" r="16"/><circle cx="700" cy="436" r="26"/><circle cx="732" cy="444" r="18"/>
</g>
<!-- picket fence -->
<g fill="#f3efe4" stroke="#d9d2bf" stroke-width="1">
<rect x="40" y="452" width="430" height="6" rx="2"/>
<rect x="40" y="470" width="430" height="6" rx="2"/>
<g id="pickets"></g>
</g>
<!-- Raised bed -->
<path d="M60 476 Q250 462 440 476 L440 492 Q250 480 60 492Z" fill="url(#soil)"/>
<g id="plants">
<!-- Plant 1: sunflower (length) -->
<g class="plant" id="p-len" transform="translate(140 484)">
<g class="sway">
<path class="stem" pathLength="1" d="M0 0 C -8 -60, 10 -120, 0 -196"/>
<path class="leaf l" d="M-2 -78 C -20 -96, -44 -94, -56 -80 C -42 -70, -20 -66, -2 -78Z" fill="#4fa35d"/>
<path class="leaf r" d="M2 -122 C 20 -138, 42 -136, 54 -122 C 40 -112, 20 -110, 2 -122Z" fill="#4fa35d"/>
<g transform="translate(0 -196)"><g class="head">
<g fill="#ffc23d" stroke="#e9a91c" stroke-width="1">
<ellipse cx="0" cy="-24" rx="9" ry="20"/><ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(30)"/><ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(60)"/>
<ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(90)"/><ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(120)"/><ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(150)"/>
<ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(180)"/><ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(210)"/><ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(240)"/>
<ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(270)"/><ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(300)"/><ellipse cx="0" cy="-24" rx="9" ry="20" transform="rotate(330)"/>
</g>
<circle r="17" fill="#6b3e1f"/>
<g fill="#8a5428"><circle cx="-6" cy="-5" r="2"/><circle cx="5" cy="-7" r="2"/><circle cx="7" cy="4" r="2"/><circle cx="-4" cy="7" r="2"/><circle cx="0" cy="0" r="2"/></g>
</g></g>
</g>
<g class="seed"><ellipse cx="0" cy="2" rx="14" ry="6" fill="#6a4430"/><ellipse cx="0" cy="-2" rx="4" ry="5.5" fill="#efe1c3" transform="rotate(20)"/></g>
</g>
<!-- Plant 2: tulip (letters + number) -->
<g class="plant" id="p-mix" transform="translate(250 482)">
<g class="sway">
<path class="stem" pathLength="1" d="M0 0 C 6 -50, -6 -96, 0 -146"/>
<path class="leaf l" d="M-1 -10 C -20 -40, -26 -78, -16 -104 C -8 -76, -4 -44, -1 -10Z" fill="#58ad66"/>
<path class="leaf r" d="M1 -6 C 22 -30, 30 -60, 24 -86 C 14 -60, 8 -32, 1 -6Z" fill="#4c9d5a"/>
<g transform="translate(0 -150)"><g class="head">
<path d="M-20 -6 C -22 -26, -14 -40, -8 -44 L0 -26 L8 -44 C 14 -40, 22 -26, 20 -6 C 14 8, -14 8, -20 -6Z" fill="#ff6b57"/>
<path d="M0 -26 L-8 -44 C -3 -48, 3 -48, 8 -44Z" fill="#e2503d"/>
<path d="M-12 -2 C -14 -16, -10 -28, -6 -32" stroke="#ff9a7c" stroke-width="3" fill="none" stroke-linecap="round"/>
</g></g>
</g>
<g class="seed"><ellipse cx="0" cy="2" rx="14" ry="6" fill="#6a4430"/><ellipse cx="0" cy="-2" rx="4" ry="5.5" fill="#efe1c3" transform="rotate(-15)"/></g>
</g>
<!-- Plant 3: cosmos (not your email name) -->
<g class="plant" id="p-name" transform="translate(360 484)">
<g class="sway">
<path class="stem" pathLength="1" d="M0 0 C -10 -50, 8 -110, -2 -170"/>
<path class="leaf l" d="M-4 -60 C -18 -74, -34 -76, -46 -68 C -34 -58, -18 -54, -4 -60Z" fill="#4fa35d"/>
<path class="leaf r" d="M2 -98 C 16 -112, 32 -114, 44 -106 C 32 -96, 16 -92, 2 -98Z" fill="#4fa35d"/>
<g transform="translate(-2 -172)"><g class="head">
<g fill="#7468f0" stroke="#5b50d8" stroke-width="1">
<path d="M-7 -6 L-9 -30 L-3 -26 L0 -33 L3 -26 L9 -30 L7 -6Z"/>
<path d="M-7 -6 L-9 -30 L-3 -26 L0 -33 L3 -26 L9 -30 L7 -6Z" transform="rotate(45)"/>
<path d="M-7 -6 L-9 -30 L-3 -26 L0 -33 L3 -26 L9 -30 L7 -6Z" transform="rotate(90)"/>
<path d="M-7 -6 L-9 -30 L-3 -26 L0 -33 L3 -26 L9 -30 L7 -6Z" transform="rotate(135)"/>
<path d="M-7 -6 L-9 -30 L-3 -26 L0 -33 L3 -26 L9 -30 L7 -6Z" transform="rotate(180)"/>
<path d="M-7 -6 L-9 -30 L-3 -26 L0 -33 L3 -26 L9 -30 L7 -6Z" transform="rotate(225)"/>
<path d="M-7 -6 L-9 -30 L-3 -26 L0 -33 L3 -26 L9 -30 L7 -6Z" transform="rotate(270)"/>
<path d="M-7 -6 L-9 -30 L-3 -26 L0 -33 L3 -26 L9 -30 L7 -6Z" transform="rotate(315)"/>
</g>
<circle r="9" fill="#ffd23f"/><circle r="4" fill="#f2a516"/>
</g></g>
</g>
<g class="seed"><ellipse cx="0" cy="2" rx="14" ry="6" fill="#6a4430"/><ellipse cx="0" cy="-2" rx="4" ry="5.5" fill="#efe1c3" transform="rotate(10)"/></g>
</g>
</g>
<!-- bed front planks -->
<rect x="52" y="488" width="396" height="58" rx="5" fill="url(#wood)"/>
<g stroke="#7a4a2a" stroke-width="2" opacity=".55">
<line x1="52" y1="517" x2="448" y2="517"/>
<path d="M90 500 q30 -4 60 0 M220 528 q40 -5 80 0 M330 500 q25 -3 50 0 M120 536 q20 -3 40 0" fill="none"/>
</g>
<rect x="48" y="484" width="10" height="66" rx="3" fill="#9a6640"/><rect x="442" y="484" width="10" height="66" rx="3" fill="#9a6640"/>
<!-- plant markers -->
<g font-family="ui-monospace, Menlo, monospace" font-size="12" font-weight="700" text-anchor="middle" fill="#5a3a22">
<g transform="translate(172 498) rotate(4)"><rect x="-2" y="-4" width="4" height="34" fill="#c99a6a"/><rect x="-22" y="-26" width="44" height="22" rx="4" fill="#f4e6cc" stroke="#c99a6a"/><text y="-11">10+</text></g>
<g transform="translate(282 498) rotate(-3)"><rect x="-2" y="-4" width="4" height="34" fill="#c99a6a"/><rect x="-22" y="-26" width="44" height="22" rx="4" fill="#f4e6cc" stroke="#c99a6a"/><text y="-11">a·1</text></g>
<g transform="translate(392 498) rotate(5)"><rect x="-2" y="-4" width="4" height="34" fill="#c99a6a"/><rect x="-26" y="-26" width="52" height="22" rx="4" fill="#f4e6cc" stroke="#c99a6a"/><text y="-11">≠ name</text></g>
</g>
<!-- foreground grass -->
<path d="M0 548 C 150 536, 320 552, 500 542 S 700 538, 800 546 V600 H0Z" fill="#6fae5f"/>
<g id="blades" stroke="#5c9a4f" stroke-width="2.5" stroke-linecap="round" fill="none"></g>
<!-- butterfly -->
<g class="butterfly" id="butterfly">
<g transform="translate(120 250)">
<path class="wing l" d="M0 0 C -14 -18, -30 -16, -26 -2 C -30 8, -14 14, 0 2Z" fill="#ff8fb0" stroke="#d9587f" stroke-width="1"/>
<path class="wing r" d="M0 0 C 14 -18, 30 -16, 26 -2 C 30 8, 14 14, 0 2Z" fill="#ffb0c8" stroke="#d9587f" stroke-width="1"/>
<rect x="-1.6" y="-10" width="3.2" height="16" rx="1.6" fill="#3b2a3a"/>
</g>
</g>
<g fill="#fff6c7">
<path class="sparkle" d="M120 270 l3 9 9 3 -9 3 -3 9 -3 -9 -9 -3 9 -3z"/>
<path class="sparkle" d="M300 300 l2.5 7 7 2.5 -7 2.5 -2.5 7 -2.5 -7 -7 -2.5 7 -2.5z"/>
<path class="sparkle" d="M410 280 l3 8 8 3 -8 3 -3 8 -3 -8 -8 -3 8 -3z"/>
</g>
</svg>
</div>
<div class="layout" id="layout">
<div class="brand" aria-hidden="true">
<svg viewBox="0 0 32 32"><path d="M16 30 V14" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/><path d="M16 18 C 8 18, 5 12, 5 6 C 12 6, 16 10, 16 18Z M16 15 C 22 15, 27 11, 27 5 C 20 5, 16 8, 16 15Z" fill="currentColor"/></svg>
Greenhouse
</div>
<main class="card" id="card">
<div class="intro">
<h1>Grow your account</h1>
<p class="sub">Make a strong password and watch the garden come up. Every rule you meet is a flower.</p>
</div>
<form id="form" novalidate>
<label>Email
<input id="email" name="email" type="email" autocomplete="username" inputmode="email" required>
</label>
<label>Password
<span class="field pw">
<input id="password" name="password" type="password" autocomplete="new-password" required aria-describedby="rules strength">
<button class="show" id="show" type="button" aria-pressed="false" aria-controls="password">Show</button>
</span>
</label>
<p class="strength" id="strength"><span class="meter" aria-hidden="true"><i></i><i></i><i></i></span><b id="strengthText">Not started</b></p>
<ul class="rules" id="rules">
<li data-rule="len"><span class="bud" aria-hidden="true"><svg viewBox="0 0 12 12"><path d="M2 6.5 5 9 10 3" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="txt">At least 10 characters</span><span class="sr"> (not yet)</span></li>
<li data-rule="mix"><span class="bud" aria-hidden="true"><svg viewBox="0 0 12 12"><path d="M2 6.5 5 9 10 3" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="txt">Letters and at least one number</span><span class="sr"> (not yet)</span></li>
<li data-rule="name"><span class="bud" aria-hidden="true"><svg viewBox="0 0 12 12"><path d="M2 6.5 5 9 10 3" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="txt">Doesn't contain your email name</span><span class="sr"> (not yet)</span></li>
</ul>
<button class="btn" id="submit" type="submit">Create account</button>
<p class="msg" id="msg" role="status" aria-live="polite"></p>
<p class="foot">Already have an account? <a href="#">Sign in</a></p>
<p class="hint">Demo: "tidepool7harbor" is strong; "ada123" shows what's missing.</p>
</form>
<section class="done" aria-live="polite">
<div class="badge" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 32 32"><path d="M16 30 V14" stroke="#2e7a4b" stroke-width="2.6" stroke-linecap="round"/><path d="M16 18 C 8 18, 5 12, 5 6 C 12 6, 16 10, 16 18Z M16 15 C 22 15, 27 11, 27 5 C 20 5, 16 8, 16 15Z" fill="#2e7a4b"/></svg></div>
<h2 id="doneTitle" tabindex="-1">Account created</h2>
<p>Your garden is in full bloom. We sent a confirmation link to <b id="doneEmail"></b>.</p>
<a class="btn" href="#">Continue</a>
</section>
</main>
</div>
<script>
(() => {
const $ = (id) => document.getElementById(id);
const email = $("email"), password = $("password"), show = $("show"), form = $("form");
const submit = $("submit"), msg = $("msg"), strength = $("strength"), strengthText = $("strengthText");
const items = [...document.querySelectorAll("#rules li")];
const art = $("art"), sun = $("sun"), layout = $("layout");
const plants = { len: $("p-len"), mix: $("p-mix"), name: $("p-name") };
const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
// Decorative details generated once: fence pickets and grass blades.
const ns = "http://www.w3.org/2000/svg";
for (let x = 46; x < 470; x += 22) {
const p = document.createElementNS(ns, "path");
p.setAttribute("d", `M${x} 486 V444 l6 -8 6 8 V486Z`);
$("pickets").append(p);
}
for (let x = 6; x < 800; x += 9) {
const h = 8 + ((x * 37) % 13), lean = ((x * 13) % 9) - 4;
const p = document.createElementNS(ns, "path");
p.setAttribute("d", `M${x} 556 q${lean / 2} ${-h / 2} ${lean} ${-h}`);
$("blades").append(p);
}
// Frame the garden bed on narrow screens.
const frame = () => art.setAttribute("viewBox", innerWidth < 860 ? "20 170 470 420" : "0 0 800 600");
frame(); addEventListener("resize", frame);
const RULES = {
len: (p) => p.length >= 10,
mix: (p) => /[a-z]/i.test(p) && /\d/.test(p),
name: (p) => {
const n = email.value.split("@")[0].toLowerCase().replace(/[^a-z0-9]/g, "");
return p.length > 0 && (n.length < 3 || !p.toLowerCase().includes(n));
},
};
// Sky goes from dawn to bright day as rules are met.
const DAWN = { top: [51, 65, 110], mid: [185, 138, 160], low: [244, 179, 147], back: [96, 128, 120], front: [118, 156, 112] };
const DAY = { top: [124, 196, 232], mid: [190, 228, 238], low: [234, 246, 233], back: [122, 165, 138], front: [143, 191, 122] };
const mix = (a, b, t) => `rgb(${a.map((v, i) => Math.round(v + (b[i] - v) * t)).join(",")})`;
let skyT = 0, skyTarget = 0, raf = 0;
function paintSky(t) {
$("skyTop").setAttribute("stop-color", mix(DAWN.top, DAY.top, t));
$("skyMid").setAttribute("stop-color", mix(DAWN.mid, DAY.mid, t));
$("skyLow").setAttribute("stop-color", mix(DAWN.low, DAY.low, t));
$("hillBack").setAttribute("fill", mix(DAWN.back, DAY.back, t));
$("hillFront").setAttribute("fill", mix(DAWN.front, DAY.front, t));
document.body.classList.toggle("day", t > .55);
}
function animateSky() {
cancelAnimationFrame(raf);
if (reduced) { skyT = skyTarget; paintSky(skyT); return; }
const step = () => {
skyT += (skyTarget - skyT) * .06;
paintSky(skyT);
if (Math.abs(skyTarget - skyT) > .002) raf = requestAnimationFrame(step);
};
raf = requestAnimationFrame(step);
}
const LEVELS = ["Not started", "Weak", "Getting there", "Strong"];
function check() {
const p = password.value;
let met = 0;
for (const li of items) {
const key = li.dataset.rule, ok = RULES[key](p);
li.classList.toggle("met", ok);
li.querySelector(".sr").textContent = ok ? " (done)" : " (not yet)";
plants[key].classList.toggle("grown", ok);
plants[key].classList.toggle("sprout", !ok && p.length > 0);
if (ok) met++;
}
const lvl = p ? Math.max(1, met) : 0;
strength.className = "strength lv" + lvl;
strengthText.textContent = LEVELS[lvl];
sun.style.transform = `translateY(${p ? 210 - met * 70 : 300}px)`;
skyTarget = p ? .12 + met * .29 : 0;
animateSky();
return met === items.length;
}
password.addEventListener("input", () => {
const allMet = check();
items.forEach((li) => li.classList.remove("missing"));
if (allMet && msg.classList.contains("err")) { msg.className = "msg"; msg.textContent = ""; password.removeAttribute("aria-invalid"); }
});
email.addEventListener("input", () => { if (password.value) check(); email.removeAttribute("aria-invalid"); });
show.addEventListener("click", () => {
const visible = password.type === "password";
password.type = visible ? "text" : "password";
show.textContent = visible ? "Hide" : "Show";
show.setAttribute("aria-pressed", String(visible));
password.focus();
});
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (submit.getAttribute("aria-busy") === "true") return;
msg.className = "msg"; msg.textContent = "";
email.removeAttribute("aria-invalid"); password.removeAttribute("aria-invalid");
if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email.value.trim())) {
email.setAttribute("aria-invalid", "true"); msg.className = "msg err";
msg.textContent = "Enter your email address, like name@example.com.";
email.focus(); return;
}
if (!check()) {
const missing = items.filter((li) => !li.classList.contains("met"));
missing.forEach((li) => {
li.classList.add("missing");
const pl = plants[li.dataset.rule];
pl.classList.remove("nudge"); void pl.getBoundingClientRect(); pl.classList.add("nudge");
});
password.setAttribute("aria-invalid", "true"); msg.className = "msg err";
msg.textContent = "Your password still needs: " + missing.map((li) => li.querySelector(".txt").textContent.toLowerCase()).join("; ") + ".";
password.focus(); return;
}
submit.setAttribute("aria-busy", "true"); submit.disabled = true; submit.textContent = "Planting…";
await new Promise((r) => setTimeout(r, 750)); // your real request goes here
submit.removeAttribute("aria-busy"); submit.disabled = false; submit.textContent = "Create account";
$("doneEmail").textContent = email.value.trim();
art.classList.add("bloom");
sun.style.transform = "translateY(-10px)";
skyTarget = 1; animateSky();
$("card").classList.add("is-done");
$("doneTitle").focus({ preventScroll: true });
});
check();
})();
</script>
</body>
</html>