1<!doctype html> 2 3 4 5 6 7 8 9 10 11 12<html lang="en-us" data-speech-lang="en-US"> 13<head> 14 15 16 17 18 19 20 21 22 23 24
24<script>window.dataLayer=window.dataLayer||[]; 25 window.gtag=window.gtag||function(){window.dataLayer.push(arguments);}; 26 gtag('consent','default',{ 27 'ad_storage':'denied', 28 'ad_user_data':'denied', 29 'ad_personalization':'denied', 30 'analytics_storage':'denied', 31 'functionality_storage':'granted','security_storage':'granted','wait_for_update':500});</script>
31 32 <!-- Google Tag Manager --> 33
33<script>
vendor: 335 bytes, lines 33-37
33(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 34 new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 35 j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 36 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 37 })(window,document,'script','dataLayer','
37GTM-5H8ZTXWZ
vendor: 3 bytes, line 37
37');
37</script>
37 38 <!-- End Google Tag Manager --> 39 40 41
41<script src="/static/js/gtm-consent.6795f39845fa.js" defer></script>
41 42 43 44 45
45<script src="/static/js/gtm-spa.e02fb714e6a0.js" defer></script>
45 46 47 48 49 50 51 52 53 54 55 56 57 58
58<script>(function(c,l,a,r,i,t,y){ 59 c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; 60 t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; 61 y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); 62 })(window,document,"clarity","script","y7dgl4ctew"); 63 window.clarity('consentv2',{ 64 ad_Storage:'denied', 65 analytics_Storage:'denied'});</script>
65 66
66<script src="/static/js/clarity-consent.c3846f3c6590.js" defer></script>
66 67 <meta charset="utf-8"> 68 <meta name="viewport" content="width=device-width,initial-scale=1"> 69 <title>Create your account · Lexlegis</title> 70 <meta name="description" content="Lexlegis â a modern legal operating system for research, drafting and firm knowledge."> 71 <link rel="apple-touch-icon" sizes="57x57" href="/static/img/favicon/apple-icon-57x57.6d8158c2addb.png"> 72 <link rel="apple-touch-icon" sizes="60x60" href="/static/img/favicon/apple-icon-60x60.874d8dcfc4b9.png"> 73 <link rel="apple-touch-icon" sizes="72x72" href="/static/img/favicon/apple-icon-72x72.2079d44ae4db.png"> 74 <link rel="apple-touch-icon" sizes="76x76" href="/static/img/favicon/apple-icon-76x76.e8599f589e1f.png"> 75 <link rel="apple-touch-icon" sizes="114x114" href="/static/img/favicon/apple-icon-114x114.ec558de863fb.png"> 76 <link rel="apple-touch-icon" sizes="120x120" href="/static/img/favicon/apple-icon-120x120.d9e9abe3a007.png"> 77 <link rel="apple-touch-icon" sizes="144x144" href="/static/img/favicon/apple-icon-144x144.fc7eae34ac4b.png"> 78 <link rel="apple-touch-icon" sizes="152x152" href="/static/img/favicon/apple-icon-152x152.051db5c310be.png"> 79 <link rel="apple-touch-icon" sizes="180x180" href="/static/img/favicon/apple-icon-180x180.d7ba26897a56.png"> 80 <link rel="icon" type="image/png" sizes="192x192" href="/static/img/favicon/android-icon-192x192.af1cbe70b852.png"> 81 <link rel="icon" type="image/png" sizes="32x32" href="/static/img/favicon/favicon-32x32.3dd55b5c2402.png"> 82 <link rel="icon" type="image/png" sizes="96x96" href="/static/img/favicon/favicon-96x96.c04af51ecdff.png"> 83 <link rel="icon" type="image/png" sizes="16x16" href="/static/img/favicon/favicon-16x16.1f638be79134.png"> 84 <link rel="shortcut icon" href="/static/img/favicon/favicon.433caeb4cbbd.ico"> 85 <link rel="manifest" href="/static/img/favicon/manifest.2f20c51ac3db.json"> 86 <meta name="msapplication-TileColor" content="#ffffff"> 87 <meta name="msapplication-TileImage" content="/static/img/favicon/ms-icon-144x144.fc7eae34ac4b.png"> 88 <meta name="theme-color" content="#ffffff"> 89 90 91 92 93 94 95 <meta name="htmx-config" content='{"globalViewTransitions": true, "historyCacheSize": 0}'> 96 97 98 99 100 101 102 103 104 105 106
106<script> 107 (function () { 108 var waiting = {}; 109 // Run `cb(module)` as soon as window[name] exists â now, or the moment 110 // the module's script lands. 111 window.lexModule = function (name, cb) { 112 if (window[name]) { cb(window[name]); return; } 113 (waiting[name] = waiting[name] || []).push(cb); 114 }; 115 // Called by the module itself instead of assigning window[name] directly. 116 window.lexModuleReady = function (name, mod) { 117 window[name] = mod; 118 var queue = waiting[name] || []; 119 waiting[name] = []; 120 queue.forEach(function (cb) { try { cb(mod); } catch (e) { console.error(e); } }); 121 }; 122 })(); 123 </script>
123 124 125 126 127 <style> 128 .lex-progress { 129 position: fixed; top: 0; left: 0; height: 2px; width: 0; 130 background: linear-gradient(90deg, var(--brand, #3b3aa6), var(--brand-600, #2f2e90)); 131 box-shadow: 0 0 8px var(--brand-100, rgba(59, 58, 166, .4)); 132 z-index: 2147483646; opacity: 0; pointer-events: none; 133 transition: width .2s ease, opacity .2s ease; 134 } 135 .lex-progress.is-active { opacity: 1; } 136 .lex-progress.is-done { transition: width .15s ease, opacity .3s ease .12s; } 137 @media (prefers-reduced-motion: reduce) { 138 ::view-transition-group(*) { animation-duration: .001ms !important; } 139 } 140 141 /* Hanging environment pill (DEV / UAT / â¦) â top-centre, drops from the 142 very top edge. Hidden on production (server renders nothing). Driven by 143 the APP_ENVIRONMENT .env constant. Kept inline + in <head> so it paints 144 pre-script and survives hx-boost body swaps. */ 145 .lex-env-pill { 146 position: fixed; top: 0; left: 50%; transform: translateX(-50%); 147 z-index: 2147483645; pointer-events: none; 148 padding: 3px 14px 4px; border-radius: 0 0 8px 8px; 149 font: 600 11px/1 "JetBrains Mono", ui-monospace, monospace; 150 letter-spacing: .12em; text-transform: uppercase; color: #fff; 151 background: linear-gradient(180deg, #d97706, #b45309); 152 box-shadow: 0 2px 8px rgba(0,0,0,.25); 153 } 154 </style> 155 156
156<script src="/static/js/zappy-nav.a26b676f3901.js" defer></script>
156 157 158
158<script src="/static/js/upload-progress.ae6a52940d6e.js" defer></script>
158 159 160 <link rel="preconnect" href="https://fonts.googleapis.com"> 161 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 162 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Fraunces:opsz,[email protected],400;9..144,500;9..144,600&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> 163 164 <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> 165 <link href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css" rel="stylesheet"> 166 167 168 169 170 171 172 173
173<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" defer></script>
173 174 <link href="/static/css/app.9add88fdfba5.css" rel="stylesheet"> 175 176 177 <link href="/static/css/responsive.54c6270bff19.css" rel="stylesheet"> 178 179 180 181 182 <link href="/static/css/library.d4ea5e51a3d5.css" rel="stylesheet"> 183 <link href="/static/css/feedback.7ac75c83b311.css" rel="stylesheet"> 184 <link href="/static/css/referrals.609010930fd5.css" rel="stylesheet"> 185 <link href="/static/css/shortcuts.210e391d1940.css" rel="stylesheet"> 186 <link href="/static/css/tour.2458602bf409.css" rel="stylesheet"> 187 188 <link href="/static/css/reg-monitor.672d4c9dd13a.css" rel="stylesheet"> 189 <link href="/static/css/clm.826935c00dd2.css" rel="stylesheet"> 190 <link href="/static/css/compliance.a5b4c0c72cbb.css" rel="stylesheet"> 191 192 <link href="/static/css/partners.f04aa2330cd3.css" rel="stylesheet"> 193 194</head> 195<body class=""> 196 <!-- Google Tag Manager (noscript) -->
vendor: 70 bytes, lines 196-197
196 197 <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
197GTM-5H8ZTXWZ
vendor: 87 bytes, lines 197-199
197" 198 height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> 199
199<!-- End Google Tag Manager (noscript) --> 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
218<script>(function(){var t=localStorage.getItem("lex.theme")||"light",d=document.documentElement; 219 if(t==="dark"||t==="gray-dark"||(t==="system"&&matchMedia("(prefers-color-scheme: dark)").matches)){ 220 d.setAttribute("data-theme","dark"); 221 } 222 if(t==="gray-light"||t==="gray-dark"){ d.classList.add("lex-grayscale"); } 223 })();</script>
223 224 225 226 227 228 229 230
230<script>window.LEX_STREAM_WORDS_URL = "/static/data/stream_words.1c6050122742.json";</script>
230 231
231<script src="/static/js/stream-shimmer.5a7071f4cb60.js"></script>
231 232 233 234 235
235<script src="/static/js/thread-rail.05cc19a1c600.js" defer></script>
235 236 237 238<style> 239 /* v2 badge next to the brand on the auth left panel. */ 240 .auth-shell__v2-pill { 241 font-size: .62rem; 242 font-weight: 700; 243 letter-spacing: .06em; 244 text-transform: uppercase; 245 padding: .18rem .5rem; 246 border-radius: 999px; 247 color: #c7d2fe; 248 background: rgba(129,140,248,.18); 249 border: 1px solid rgba(129,140,248,.35); 250 line-height: 1; 251 margin-left: .1rem; 252 } 253 /* "Continue to v1" note pinned to the bottom of the left panel. */ 254 .auth-shell__v1-note { 255 margin-top: 2rem; 256 padding-top: 1.25rem; 257 border-top: 1px solid rgba(255,255,255,.10); 258 } 259 .auth-shell__v1-note .text-subtle { display: block; } 260 .auth-shell__v1-link { 261 display: inline-block; 262 margin-top: .35rem; 263 font-weight: 600; 264 color: #c7d2fe; 265 text-decoration: none; 266 } 267 .auth-shell__v1-link:hover { color: #fff; text-decoration: underline; } 268 269 /* Compact v1 fallback shown inside the form column (visible on all sizes). */ 270 .auth-v1-fallback { 271 margin-top: 1.5rem; 272 padding: .85rem 1rem; 273 border: 1px solid var(--border, rgba(15,18,32,.10)); 274 border-radius: 12px; 275 background: var(--bg-tint, rgba(15,18,32,.02)); 276 font-size: .85rem; 277 text-align: center; 278 } 279 .auth-v1-fallback a { font-weight: 600; text-decoration: none; } 280 .auth-v1-fallback a:hover { text-decoration: underline; } 281</style> 282<div class="auth-shell"> 283 <aside class="auth-shell__art"> 284 <div> 285 <a href="/" class="auth-card__brand text-white" aria-label="Lexlegis"> 286 <img class="auth-shell__logo" 287 src="/static/img/logo/new-logo-dark.735d749f2293.svg" 288 alt="Lexlegis" width="160" height="32"> 289 <span class="auth-shell__v2-pill">v2 · New</span> 290 </a> 291 <h2 class="mt-5">Where legal work gets done.</h2> 292 <p class="mt-3">Lexlegis v2 is rebuilt from the ground up â your research, drafting, documents, matters, and billing on one surface, with AI that cites its sources and shows its work. One home for solo practitioners and global firms alike.</p> 293 294 <p class="mt-3"> 295 <span class="text-subtle">Coming from the classic version?</span> 296 <a href="https://v1.lexlegis.ai" class="auth-shell__v1-link">Continue to Lexlegis v1 →</a> 297 </p> 298 299 <div class="auth-shell__art-features"> 300 <div class="auth-shell__feature"> 301 <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v4"/><path d="M12 17v4"/><path d="M3 12h4"/><path d="M17 12h4"/><path d="M5.6 5.6 8.5 8.5"/><path d="M15.5 15.5 18.4 18.4"/><path d="M5.6 18.4 8.5 15.5"/><path d="M15.5 8.5 18.4 5.6"/></svg> 302 303 <div><strong>MIRA agents</strong><div class="text-subtle">Hand off multi-step work â MIRA drafts, reviews, and acts across your firm's tools, and shows you every step before it commits.</div></div> 304 </div> 305 <div class="auth-shell__feature"> 306 <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg> 307 308 <div><strong>Ask, Interact & Draft</strong><div class="text-subtle">Answers grounded in law and your files, a conversation with any case document, and drafts you can trace clause by clause.</div></div> 309 </div> 310 <div class="auth-shell__feature"> 311 <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg> 312 313 <div><strong>Run the whole matter</strong><div class="text-subtle">Matters, contract lifecycles, tasks, and timesheets in one place â every document searchable by AI the moment it lands.</div></div> 314 </div> 315 <div class="auth-shell__feature"> 316 <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M9 2v6"/><path d="M15 2v6"/>
316<path d="M6 8h12v3a6 6 0 0 1-6 6 6 6 0 0 1-6-6z"/><path d="M12 17v5"/></svg> 317 318 <div><strong>Wherever you work</strong><div class="text-subtle">In Microsoft Word, over WhatsApp, or on your phone â your firm's knowledge is a message away.</div></div> 319 </div> 320 </div> 321 </div> 322 323 <div class="auth-shell__v1-note"> 324 <div class="text-subtle small">© Lexlegis â Built for counsel, everywhere.</div> 325 </div> 326 </aside> 327 <section class="auth-shell__form"> 328 <div class="auth-card"> 329 330 <a href="/" class="auth-card__brand d-lg-none"> 331 332 333<img class="sidebar__logo sidebar__logo--full auth-card__logo" 334 src="/static/img/logo/new-logo-light.a12ad8255ff7.svg" 335 alt="Lexlegis" width="150" height="30"> 336 337 </a> 338 339 <h1 class="h2">Create your account.</h1> 340 <p class="lead mb-4">Start with a free organization. No credit card required.</p> 341 342 343 344 345 <div class="sso-buttons" role="group" aria-label="Sign in with a single sign-on provider"> 346 347 348 <form method="post" action="/accounts/apple/login/?process=signup" class="sso-form" data-provider="apple"> 349 <input type="hidden" name="csrfmiddlewaretoken" value="hlg9j41VgXFR3A2gUP8PidYjHpK5kYLVmEDXkBKEGUzT12nOyCHO9P35hCquRkwK"> 350 351 <button type="submit" class="sso-btn" aria-label="Continue with Apple"> 352 <span class="sso-btn__icon" aria-hidden="true"> 353 <svg viewBox="0 0 384 512" focusable="false"> 354 <path fill="currentColor" d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/> 355 </svg> 356 </span> 357 <span class="sso-btn__label">Continue with <strong>Apple</strong></span> 358 359 </button> 360 </form> 361 362 363 364 <form method="post" action="/accounts/google/login/?process=signup" class="sso-form" data-provider="google"> 365 <input type="hidden" name="csrfmiddlewaretoken" value="hlg9j41VgXFR3A2gUP8PidYjHpK5kYLVmEDXkBKEGUzT12nOyCHO9P35hCquRkwK"> 366 367 <button type="submit" class="sso-btn" aria-label="Continue with Google"> 368 <span class="sso-btn__icon" aria-hidden="true"> 369 <svg viewBox="0 0 18 18" focusable="false"> 370 <path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.17-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62z"/> 371 <path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.81.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 0 0 9 18z"/> 372 <path fill="#FBBC05" d="M3.97 10.72A5.4 5.4 0 0 1 3.68 9c0-.6.1-1.18.29-1.72V4.95H.96A9 9 0 0 0 0 9c0 1.45.35 2.83.96 4.05l3.01-2.33z"/> 373 <path fill="#EA4335" d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.46.89 11.43 0 9 0A9 9 0 0 0 .96 4.95L3.97 7.28C4.68 5.16 6.66 3.58 9 3.58z"/> 374 </svg> 375 </span> 376 <span class="sso-btn__label">Continue with <strong>Google</strong></span> 377 378 </button> 379 </form> 380 381 382 383 <form method="post" action="/accounts/microsoft/login/?process=signup" class="sso-form" data-provider="microsoft"> 384 <input type="hidden" name="csrfmiddlewaretoken" value="hlg9j41VgXFR3A2gUP8PidYjHpK5kYLVmEDXkBKEGUzT12nOyCHO9P35hCquRkwK"> 385 386 <button type="submit" class="sso-btn" aria-label="Continue with Microsoft"> 387 <span class="sso-btn__icon" aria-hidden="true"> 388 <svg viewBox="0 0 21 21" focusable="false"> 389 <rect x="1" y="1" width="9" height="9" fill="#F25022"/> 390 <rect x="11" y="1" width="9" height="9" fill="#7FBA00"/> 391 <rect x="1" y="11" width="9" height="9" fill="#00A4EF"/> 392 <rect x="11" y="11" width="9" height="9" fill="#FFB900"/> 393 </svg> 394 </span>
395 <span class="sso-btn__label">Continue with <strong>Microsoft</strong></span> 396 397 </button> 398 </form> 399 400 401 402 403 </div> 404 <div class="auth-divider">or continue with email</div> 405 406 407 408 <form class="account-form" 409 method="post" 410 action="/register/" 411 novalidate 412 autocomplete="off" 413 data-form-type="other" 414 spellcheck="false"> 415 <input type="hidden" name="csrfmiddlewaretoken" value="hlg9j41VgXFR3A2gUP8PidYjHpK5kYLVmEDXkBKEGUzT12nOyCHO9P35hCquRkwK"> 416 417 <div aria-hidden="true" style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;"> 418 <input type="text" name="fake-username" tabindex="-1" autocomplete="username"> 419 <input type="password" name="fake-password" tabindex="-1" autocomplete="current-password"> 420 </div> 421 422 423 <div class="mb-3"> 424 <label class="form-label" for="id_email">Work email</label> 425 <input type="email" name="email" placeholder="Email address" autocomplete="off" maxlength="320" class="form-control" data-lpignore="true" data-1p-ignore="" data-form-type="other" readonly="readonly" required id="id_email"> 426 427 </div> 428 429 <div class="mb-2"> 430 <label class="form-label" for="id_password1">Password</label> 431 <div class="pw-input"> 432 <input type="password" name="password1" placeholder="Password" autocomplete="new-password" class="form-control" id="id_password1" data-lpignore="true" data-1p-ignore="" data-bwignore="" data-form-type="other" readonly="readonly" required aria-describedby="id_password1_helptext"> 433 <button type="button" 434 class="pw-toggle" 435 id="pw-toggle" 436 data-no-loader 437 aria-label="Show password" 438 aria-pressed="false" 439 aria-controls="id_password1" 440 tabindex="-1"> 441 <svg class="pw-toggle__eye" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 442 <path d="M1.5 12s4-7 10.5-7 10.5 7 10.5 7-4 7-10.5 7S1.5 12 1.5 12z"/> 443 <circle cx="12" cy="12" r="3"/> 444 </svg> 445 <svg class="pw-toggle__eye-off" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 446 <path d="M3 3l18 18"/> 447 <path d="M10.6 6.2A10.9 10.9 0 0 1 12 6c6.5 0 10.5 6 10.5 6a17.9 17.9 0 0 1-3.2 3.9"/> 448 <path d="M6.7 7.7A17.9 17.9 0 0 0 1.5 12s4 7 10.5 7a10.9 10.9 0 0 0 4.6-1"/> 449 <path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/> 450 </svg> 451 </button> 452 </div> 453 454 455 456 <div class="pw-meter" id="pw-meter" hidden aria-live="polite"> 457 <div class="pw-meter__head"> 458 <div class="pw-meter__segments" data-level="0" id="pw-segments"> 459 <span></span><span></span><span></span><span></span><span></span> 460 </div> 461 <div class="pw-meter__label" id="pw-strength-label"> 462 <span class="pw-meter__verdict">Strength</span> 463 <span class="pw-meter__crack text-subtle" id="pw-crack"></span> 464 </div> 465 </div> 466 467 <ul class="pw-rules" id="pw-rules"> 468 <li data-rule="length"><span class="pw-rules__dot"></span>At least 8 characters</li> 469 <li data-rule="upper"><span class="pw-rules__dot"></span>An uppercase letter (AâZ)</li> 470 <li data-rule="lower"><span class="pw-rules__dot"></span>A lowercase letter (aâz)</li> 471 <li data-rule="digit"><span class="pw-rules__dot"></span>A number (0â9)</li> 472 <li data-rule="symbol"><span class="pw-rules__dot"></span>A symbol (e.g. ! @ # $ %)</li> 473 </ul> 474 </div> 475 </div> 476 477 <div class="mb-3"> 478 <label class="form-label" for="id_country">Country</label> 479 <select name="country" class="form-select" autocomplete="on" required id="id_country"> 480 <option value="">Select your country</option> 481 482 <option value="AF">Afghanistan (+93)</option> 483 484 <option value="AL">Albania (+355)</option> 485 486 <option value="DZ">Algeria (+213)</option> 487 488 <option value="AR">Argentina (+54)</option> 489 490 <option value="AM">Armenia (+374)</option> 491 492 <option value="AU">Australia (+61)</option> 493 494 <option value="AT">Austria (+43)</option> 495 496 <option value="AZ">Azerbaijan (+994)</option> 497 498 <option value="BH">Bahrain (+973)</option> 499 500 <option value="BD">Bangladesh (+880)</option> 501 502 <option value="BY">Belarus (+375)</option> 503 504 <option value="BE">Belgium (+32)</option> 505 506 <option value="BO">Bolivia (+591)</option> 507 508 <option value="BA">Bosnia and Herzegovina (+387)</option> 509 510 <option value="BR">Brazil (+55)</option> 511 512 <option value="BG">Bulgaria (+359)</option> 513 514 <option value="KH">Cambodia (+855)</option> 515 516 <option value="CM">Cameroon (+237)</option> 517 518 <option value="CA">Canada (+1)</option> 519 520 <option value="CL">Chile (+56)</option> 521 522 <option value="CN">China (+86)</option> 523 524 <option value="CO">Colombia (+57)</option> 525 526 <option value="CR">Costa Rica (+506)</option> 527 528 <option value="HR">Croatia (+385)</option> 529 530 <option value="CU">Cuba (+53)</option> 531 532 <option value="CY">
532Cyprus (+357)</option> 533 534 <option value="CZ">Czechia (+420)</option> 535 536 <option value="DK">Denmark (+45)</option> 537 538 <option value="DO">Dominican Republic (+1)</option> 539 540 <option value="EC">Ecuador (+593)</option> 541 542 <option value="EG">Egypt (+20)</option> 543 544 <option value="EE">Estonia (+372)</option> 545 546 <option value="ET">Ethiopia (+251)</option> 547 548 <option value="FI">Finland (+358)</option> 549 550 <option value="FR">France (+33)</option> 551 552 <option value="GE">Georgia (+995)</option> 553 554 <option value="DE">Germany (+49)</option> 555 556 <option value="GH">Ghana (+233)</option> 557 558 <option value="GR">Greece (+30)</option> 559 560 <option value="GT">Guatemala (+502)</option> 561 562 <option value="HK">Hong Kong (+852)</option> 563 564 <option value="HU">Hungary (+36)</option> 565 566 <option value="IS">Iceland (+354)</option> 567 568 <option value="IN" selected>India (+91)</option> 569 570 <option value="ID">Indonesia (+62)</option> 571 572 <option value="IR">Iran (+98)</option> 573 574 <option value="IQ">Iraq (+964)</option> 575 576 <option value="IE">Ireland (+353)</option> 577 578 <option value="IL">Israel (+972)</option> 579 580 <option value="IT">Italy (+39)</option> 581 582 <option value="JM">Jamaica (+1)</option> 583 584 <option value="JP">Japan (+81)</option> 585 586 <option value="JO">Jordan (+962)</option> 587 588 <option value="KZ">Kazakhstan (+7)</option> 589 590 <option value="KE">Kenya (+254)</option> 591 592 <option value="KW">Kuwait (+965)</option> 593 594 <option value="KG">Kyrgyzstan (+996)</option> 595 596 <option value="LA">Laos (+856)</option> 597 598 <option value="LV">Latvia (+371)</option> 599 600 <option value="LB">Lebanon (+961)</option> 601 602 <option value="LY">Libya (+218)</option> 603 604 <option value="LT">Lithuania (+370)</option> 605 606 <option value="LU">Luxembourg (+352)</option> 607 608 <option value="MY">Malaysia (+60)</option> 609 610 <option value="MV">Maldives (+960)</option> 611 612 <option value="MT">Malta (+356)</option> 613 614 <option value="MX">Mexico (+52)</option> 615 616 <option value="MD">Moldova (+373)</option> 617 618 <option value="MN">Mongolia (+976)</option> 619 620 <option value="ME">Montenegro (+382)</option> 621 622 <option value="MA">Morocco (+212)</option> 623 624 <option value="MM">Myanmar (+95)</option> 625 626 <option value="NP">Nepal (+977)</option> 627 628 <option value="NL">Netherlands (+31)</option> 629 630 <option value="NZ">New Zealand (+64)</option> 631 632 <option value="NG">Nigeria (+234)</option> 633 634 <option value="NO">Norway (+47)</option> 635 636 <option value="OM">Oman (+968)</option> 637 638 <option value="PK">Pakistan (+92)</option> 639 640 <option value="PA">Panama (+507)</option> 641 642 <option value="PY">Paraguay (+595)</option> 643 644 <option value="PE">Peru (+51)</option> 645 646 <option value="PH">Philippines (+63)</option> 647 648 <option value="PL">Poland (+48)</option> 649 650 <option value="PT">Portugal (+351)</option> 651 652 <option value="QA">Qatar (+974)</option> 653 654 <option value="RO">Romania (+40)</option> 655 656 <option value="RU">Russia (+7)</option> 657 658 <option value="SA">Saudi Arabia (+966)</option> 659 660 <option value="RS">Serbia (+381)</option> 661 662 <option value="SG">Singapore (+65)</option> 663 664 <option value="SK">Slovakia (+421)</option> 665 666 <option value="SI">Slovenia (+386)</option> 667 668 <option value="ZA">South Africa (+27)</option> 669 670 <option value="KR">South Korea (+82)</option> 671 672 <option value="ES">Spain (+34)</option> 673 674 <option value="LK">Sri Lanka (+94)</option> 675 676 <option value="SE">Sweden (+46)</option> 677 678 <option value="CH">Switzerland (+41)</option> 679 680 <option value="TW">Taiwan (+886)</option> 681 682 <option value="TJ">Tajikistan (+992)</option> 683 684 <option value="TZ">Tanzania (+255)</option> 685 686 <option value="TH">Thailand (+66)</option> 687 688 <option value="TN">Tunisia (+216)</option> 689 690 <option value="TR">Turkey (+90)</option> 691 692 <option value="TM">Turkmenistan (+993)</option> 693 694 <option value="UG">Uganda (+256)</option> 695 696 <option value="UA">Ukraine (+380)</option> 697 698 <option value="AE">United Arab Emirates (+971)</option> 699 700 <option value="GB">United Kingdom (+44)</option> 701 702 <option value="US">United States (+1)</option> 703 704 <option value="UY">Uruguay (+598)</option> 705 706 <option value="UZ">Uzbekistan (+998)</option> 707 708 <option value="VE">Venezuela (+58)</option> 709 710 <option value="VN">Vietnam (+84)</option> 711 712 <option value="YE">Yemen (+967)</option> 713 714 <option value="ZM">Zambia (+260)</option> 715 716 <option value="ZW">Zimbabwe (+263)</option> 717 718 <option value="OT">Other / not listed</option> 719 720</select> 721 722 </div> 723 724 <div class="mb-3"> 725 <label class="form-label" for="id_phone">Phone number</label> 726
726<script id="phone-maxlengths" type="application/json">{"IN": 10, "US": 10, "CA": 10, "GB": 10, "AE": 9, "SA": 9, "QA": 8, "KW": 8, "OM": 8, "BH": 8, "SG": 8, "HK": 8, "PK": 10, "BD": 10, "NP": 10, "LK": 9, "CN": 11, "JP": 10, "PH": 10, "TH": 9, "VN": 9, "FR": 9, "ES": 9, "NL": 9, "RU": 10, "TR": 10, "ZA": 9, "NG": 10, "EG": 10, "DE": 11, "IT": 10, "KR": 10, "MY": 10, "ID": 12, "BR": 11, "AU": 10, "NZ": 10}</script>
726 727 <div class="input-group"> 728 <span class="input-group-text" id="phone-dial-prefix">+â</span> 729 <input type="text" name="phone" maxlength="10" class="form-control" autocomplete="tel" inputmode="tel" data-fallback-max="13" required id="id_phone"> 730 </div> 731 732 <div class="form-text small"> 733 We'll send a WhatsApp verification code to this number. 734 <a href="#" 735 role="button" 736 tabindex="0" 737 class="text-decoration-underline ms-1" 738 data-bs-toggle="popover" 739 data-bs-trigger="focus" 740 data-bs-placement="top" 741 data-bs-custom-class="lex-pop" 742 data-bs-title="Why we ask for your phone" 743 data-bs-content="Bots and spammers churn out throwaway email accounts to create bogus logins. To keep those out, we verify a mobile + email combination â a real phone number is much harder to fake at scale." 744 onclick="event.preventDefault();">why?</a> 745 </div> 746 </div> 747 748 <div class="form-check mb-3 mt-3"> 749 <input type="checkbox" name="terms_accepted" class="form-check-input" required id="id_terms_accepted" checked> 750 <label class="form-check-label small" for="id_terms_accepted"> 751 I agree to the 752 <a href="https://lexlegis.ai/legal/terms" target="_blank" rel="noopener">Terms of Service</a> 753 and 754 <a href="https://lexlegis.ai/legal/privacy" target="_blank" rel="noopener">Privacy Policy</a>. 755 </label> 756 757 </div> 758 759 760 761 <button type="submit" class="btn btn-primary w-100 mt-2" id="signup-submit">Create account</button> 762 </form> 763 764 <p class="text-center text-muted mt-4 mb-0"> 765 Already have an account? 766 <a href="/accounts/login/">Sign in</a> 767 </p> 768 769 <p class="text-center text-muted mt-2 mb-0"> 770 <a href="/auth/sso/">Sign in with SSO</a> 771 </p> 772
773 <div class="auth-v1-fallback"> 774 Already have a Lexlegis v1 account? 775 <a href="https://v1.lexlegis.ai">Continue to v1 →</a> 776 <div class="text-subtle small mt-1">You're creating an account on the new v2 â your v1 workspace stays where it is.</div> 777 </div> 778 779 <style> 780 /* Password field with inline eye toggle. */ 781 .pw-input { position: relative; } 782 .pw-input > input.form-control { padding-right: 2.6rem; } 783 .pw-toggle { 784 position: absolute; 785 top: 50%; 786 right: 6px; 787 transform: translateY(-50%); 788 display: inline-flex; 789 align-items: center; 790 justify-content: center; 791 width: 32px; 792 height: 32px; 793 padding: 0; 794 border: 0; 795 background: transparent; 796 color: var(--text-muted, #4a4f63); 797 border-radius: 8px; 798 cursor: pointer; 799 transition: color .15s ease, background .15s ease; 800 } 801 .pw-toggle:hover { color: var(--text, #0f1220); background: var(--bg-tint, rgba(15,18,32,.05)); } 802 .pw-toggle:focus-visible { 803 outline: 2px solid var(--focus, #3b6ef6); 804 outline-offset: 1px; 805 } 806 .pw-toggle__eye-off { display: none; } 807 .pw-toggle[aria-pressed="true"] .pw-toggle__eye { display: none; } 808 .pw-toggle[aria-pressed="true"] .pw-toggle__eye-off { display: inline-block; } 809 [data-theme="dark"] .pw-toggle { color: rgba(255,255,255,.7); } 810 [data-theme="dark"] .pw-toggle:hover { color: #fff; background: rgba(255,255,255,.08); } 811 812 /* ============================================================ 813 Password meter â editorial, segmented, animated on focus. 814 Hidden until the user engages with the password field, then 815 slides in with a measured cadence. 816 ============================================================ */ 817 .pw-meter { 818 margin-top: 10px; 819 padding: 14px 14px 12px; 820 border: 1px solid var(--border, rgba(15,18,32,.08)); 821 border-radius: 12px; 822 background: var(--bg-elev-2, #fafaf7); 823 box-shadow: 0 1px 1px rgba(15,18,32,.03); 824 opacity: 0; 825 transform: translateY(-6px); 826 transition: opacity .22s ease, transform .22s ease; 827 } 828 .pw-meter:not([hidden]) { 829 opacity: 1; 830 transform: translateY(0); 831 } 832 .pw-meter__head { 833 display: flex; 834 flex-direction: column; 835 gap: 8px; 836 } 837 .pw-meter__segments { 838 display: grid; 839 grid-template-columns: repeat(5, 1fr); 840 gap: 6px; 841 } 842 .pw-meter__segments > span { 843 display: block; 844 height: 6px; 845 border-radius: 999px; 846 background: var(--bg-tint, #efefea); 847 box-shadow: inset 0 0 0 1px var(--border-soft, rgba(15,18,32,.04)); 848 transition: background .25s ease, box-shadow .25s ease, transform .25s ease; 849 } 850 .pw-meter__segments[data-level="1"] > span:nth-child(-n+1), 851 .pw-meter__segments[data-level="2"] > span:nth-child(-n+2), 852 .pw-meter__segments[data-level="3"] > span:nth-child(-n+3), 853 .pw-meter__segments[data-level="4"] > span:nth-child(-n+4), 854 .pw-meter__segments[data-level="5"] > span:nth-child(-n+5) { 855 transform: scaleY(1.1); 856 } 857 .pw-meter__segments[data-level="1"] > span:nth-child(-n+1) { background: #c43d3d; box-shadow: none; } 858 .pw-meter__segments[data-level="2"] > span:nth-child(-n+2) { background: #d97a1b; box-shadow: none; } 859 .pw-meter__segments[data-level="3"] > span:nth-child(-n+3) { background: #d4ad2a; box-shadow: none; } 860 .pw-meter__segments[data-level="4"] > span:nth-child(-n+4) { background: #2e9c63; box-shadow: none; } 861 .pw-meter__segments[data-level="5"] > span:nth-child(-n+5) { 862 background: linear-gradient(90deg, #2e9c63 0%, #1a7f4d 100%); 863 box-shadow: 0 0 8px rgba(46,156,99,.35); 864 } 865 866 .pw-meter__label { 867 display: flex; 868 justify-content: space-between; 869 align-items: baseline; 870 gap: 12px; 871 font-size: .875rem; 872 } 873 .pw-meter__verdict { 874 font-weight: 600; 875 letter-spacing: -0.005em; 876 color: var(--text-muted, #4a4f63); 877 transition: color .2s ease; 878 } 879 .pw-meter[data-level="1"] .pw-meter__verdict { color: #c43d3d; } 880 .pw-meter[data-level="2"] .pw-meter__verdict { color: #d97a1b; } 881 .pw-meter[data-level="3"] .pw-meter__verdict { color: #b8961f; } 882 .pw-meter[data-level="4"] .pw-meter__verdict { color: #1a7f4d; } 883 .pw-meter[data-level="5"] .pw-meter__verdict { color: #1a7f4d; } 884 .pw-meter__crack { 885 font-size: .75rem; 886 font-variant-numeric: tabular-nums; 887 color: var(--text-subtle, #7f8497); 888 } 889 890 /* Rule checklist */ 891 .pw-rules { 892 list-style: none; 893 padding: 10px 0 0 0; 894 margin: 10px 0 0 0; 895 border-top: 1px dashed var(--border-soft, rgba(15,18,32,.06)); 896 display: grid; 897 grid-template-columns: 1fr 1fr; 898 gap: 4px 16px; 899 font-size: .75rem; 900 color: var(--text-subtle, #7f8497); 901 } 902 .pw-rules li { 903 display: flex; 904 align-items: center; 905 gap: 7px; 906 transition: color .2s ease; 907 } 908 .pw-rules__dot { 909 width: 14px; 910 height: 14px; 911 border-radius: 999px; 912 border: 1.5px solid var(--text-faint, #a9adbd); 913 background: transparent; 914 position: relative; 915 flex: 0 0 14px; 916 transition: background .2s ease, border-color .2s ease, transform .2s ease; 917 } 918 .pw-rules__dot::after { 919 content: ""; 920 position: absolute; 921 inset: 0; 922 background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 6.3l2.4 2.4 4.6-5'/></svg>") center/9px 9px no-repeat; 923 opacity: 0; 924 transform: scale(.6); 925 transition: opacity .15s ease, transform .15s ease; 926 } 927 .pw-rules li.pw-rule-ok { 928 color: #1a7f4d; 929 } 930 .pw-rules li.pw-rule-ok .pw-rules__dot { 931 background: #1a7f4d; 932 border-color: #1a7f4d; 933 transform: scale(1.05); 934 } 935 .pw-rules li.pw-rule-ok .pw-rules__dot::after { 936 opacity: 1; 937 transform: scale(1); 938 } 939 940 /* Dark-mode tweaks */ 941 [data-theme="dark"] .pw-meter { 942 background: rgba(255,255,255,.03); 943 border-color: var(--border, rgba(255,255,255,.08)); 944 } 945 946 /* Respect reduced motion */ 947 @media (prefers-reduced-motion: reduce) { 948 .pw-meter, 949 .pw-meter__segments > span, 950 .pw-rules__dot, 951 .pw-rules__dot::after { 952 transition: none !important; 953 } 954 } 955 </style> 956 957
957<script> 958 // Drop the readonly attribute the moment the user shows real intent on 959 // either field. The attribute is set server-side so the saved-password 960 // dropdown never opens on initial paint. 961 (function () { 962 ["id_email", "id_password1"].forEach(function (id) { 963 const el = document.getElementById(id); 964 if (!el) return; 965 const unlock = function () { el.removeAttribute("readonly"); }; 966 el.addEventListener("focus", unlock, { once: true }); 967 el.addEventListener("pointerdown", unlock, { once: true }); 968 }); 969 })(); 970 </script>
970 971 972
972<script> 973 // Catch obvious field-level problems client-side so the page never reloads 974 // and the password field keeps what the user typed. Server-side validation 975 // still runs as defense in depth (and for things we can't check here, like 976 // email uniqueness). app.js re-enables the submit button whenever any 977 // listener calls preventDefault, so no manual button reset needed. 978 (function () { 979 const form = document.querySelector("form.account-form"); 980 const email = document.getElementById("id_email"); 981 const pw1 = document.getElementById("id_password1"); 982 const terms = document.getElementById("id_terms_accepted"); 983 const country = document.getElementById("id_country"); 984 const phone = document.getElementById("id_phone"); 985 if (!form || !email || !pw1 || !terms) return; 986 987 // Loose enough to catch typos; server does the strict check. 988 const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 989 990 // Mirrors the visible rule list in the strength meter. 991 const PW_RULES = { 992 length: p => p.length >= 8, 993 upper: p => /[A-Z]/.test(p), 994 lower: p => /[a-z]/.test(p), 995 digit: p => /[0-9]/.test(p), 996 symbol: p => /[^A-Za-z0-9]/.test(p), 997 }; 998 999 function fieldHost(field) { 1000 // Sit the error inside the same wrapper Django's error div uses, so 1001 // spacing matches and our message replaces any prior client message. 1002 return field.closest(".mb-3, .mb-2, .form-check") || field.parentNode; 1003 } 1004 function setError(field, msg) { 1005 const host = fieldHost(field); 1006 let el = host.querySelector("[data-client-error]"); 1007 if (!el) { 1008 el = document.createElement("div"); 1009 el.setAttribute("data-client-error", ""); 1010 el.className = "text-danger small mt-1"; 1011 host.appendChild(el); 1012 } 1013 el.textContent = msg; 1014 } 1015 function clearError(field) { 1016 const el = fieldHost(field).querySelector("[data-client-error]"); 1017 if (el) el.remove(); 1018 } 1019 1020 function validate() { 1021 let firstInvalid = null; 1022 1023 const emailVal = email.value.trim(); 1024 if (!emailVal) { 1025 setError(email, "Please enter your work email."); 1026 firstInvalid = firstInvalid || email; 1027 } else if (!EMAIL_RE.test(emailVal)) { 1028 setError(email, "Please enter a valid email address."); 1029 firstInvalid = firstInvalid || email; 1030 } else { 1031 clearError(email); 1032 } 1033 1034 const missing = Object.keys(PW_RULES).filter(k => !PW_RULES[k](pw1.value)); 1035 if (missing.length) { 1036 setError(pw1, pw1.value 1037 ? "Password doesn't yet meet all the requirements below." 1038 : "Please choose a password."); 1039 firstInvalid = firstInvalid || pw1; 1040 } else { 1041 clearError(pw1); 1042 } 1043 1044 if (country && !country.value) { 1045 setError(country, "Please pick your country."); 1046 firstInvalid = firstInvalid || country; 1047 } else if (country) { 1048 clearError(country); 1049 } 1050 1051 if (phone) { 1052 const digits = (phone.value || "").replace(/\D/g, ""); 1053 if (digits.length < 6) { 1054 setError(phone, "Enter your phone number (at least 6 digits)."); 1055 firstInvalid = firstInvalid || phone; 1056 } else { 1057 clearError(phone); 1058 } 1059 } 1060 1061 if (!terms.checked) { 1062 setError(terms, "Please accept the Terms of Service and Privacy Policy to continue."); 1063 firstInvalid = firstInvalid || terms; 1064 } else { 1065 clearError(terms); 1066 } 1067 1068 return firstInvalid; 1069 } 1070 1071 form.addEventListener("submit", function (e) { 1072 const invalid = validate(); 1073 if (invalid) { 1074 e.preventDefault(); 1075 invalid.focus(); 1076 } 1077 }); 1078 1079 // Clear each field's error as soon as the user fixes it â keeps the 1080 // form feeling responsive instead of waiting for the next submit. 1081 email.addEventListener("input", function () { 1082 const v = email.value.trim(); 1083 if (v && EMAIL_RE.test(v)) clearError(email); 1084 }); 1085 pw1.addEventListener("input", function () { 1086 if (Object.keys(PW_RULES).every(k => PW_RULES[k](pw1.value))) clearError(pw1); 1087 }); 1088 terms.addEventListener("change", function () { 1089 if (terms.checked) clearError(terms); 1090 }); 1091 if (country) { 1092 country.addEventListener("change", function () { 1093 if (country.value) clearError(country); 1094 }); 1095 } 1096 if (phone) { 1097 phone.addEventListener("input", function () { 1098 // Digits only â the maxlength is a national-number digit count, so 1099 // spaces/other characters would wrongly eat into the per-country cap. 1100 const cleaned = (phone.value || "").replace(/\D/g, ""); 1101 if (cleaned !== phone.value) phone.value = cleaned; 1102 if (cleaned.length >= 6) clearError(phone); 1103 }); 1104 } 1105 })(); 1106 </script>
1106 1107 1108
1108<script> 1109 // Keep the +dial-code prefix in sync with the selected country. The country 1110 // dropdown label already includes "(+91)" etc; we extract it via regex so 1111 // we don't ship a separate JSON map. 1112 (function () { 1113 const country = document.getElementById("id_country"); 1114 const prefix = document.getElementById("phone-dial-prefix"); 1115 const phone = document.getElementById("id_phone"); 1116 if (!country || !prefix) return; 1117 1118 // ISO2 â max national-number length (from COUNTRY_PHONE_LENGTHS). Any 1119 // country missing here falls back to data-fallback-max on the input. 1120 let maxByCountry = {}; 1121 try { 1122 maxByCountry = JSON.parse( 1123 document.getElementById("phone-maxlengths").textContent 1124 ); 1125 } catch (_) {} 1126 const fallbackMax = phone 1127 ? parseInt(phone.getAttribute("data-fallback-max"), 10) || 0 1128 : 0; 1129 1130 function sync() { 1131 const opt = country.options[country.selectedIndex]; 1132 if (!opt) { prefix.textContent = "+â"; return; } 1133 const m = opt.text.match(/\(\+(\d+)\)\s*$/); 1134 prefix.textContent = m ? "+" + m[1] : "+â"; 1135 1136 if (phone) { 1137 const max = maxByCountry[country.value] || fallbackMax; 1138 if (max) { 1139 phone.maxLength = max; 1140 // Trim anything already over the new limit (e.g. switching from a 1141 // longer country to India after typing 11+ digits). 1142 if (phone.value.length > max) phone.value = phone.value.slice(0, max); 1143 } 1144 } 1145 } 1146 country.addEventListener("change", sync); 1147 sync(); 1148 1149 // On blur, if the user typed the dial code (with the + sign) as the 1150 // first characters of the phone number, strip it â the prefix is 1151 // already shown in the input-group addon. 1152 if (phone) { 1153 phone.addEventListener("blur", function () { 1154 const dial = prefix.textContent.trim(); 1155 if (!dial || !dial.startsWith("+") || dial === "+â") return; 1156 const v = (phone.value || "").trimStart(); 1157 if (v.startsWith(dial)) { 1158 phone.value = v.slice(dial.length).trimStart(); 1159 } 1160 }); 1161 } 1162 })(); 1163 </script>
1163 1164 1165
1165<script> 1166 (function () { 1167 const pw1 = document.getElementById("id_password1"); 1168 const meter = document.getElementById("pw-meter"); 1169 const segs = document.getElementById("pw-segments"); 1170 const verdict = meter.querySelector(".pw-meter__verdict"); 1171 const crack = document.getElementById("pw-crack"); 1172 1173 const ruleFns = { 1174 length: p => p.length >= 8, 1175 upper: p => /[A-Z]/.test(p), 1176 lower: p => /[a-z]/.test(p), 1177 digit: p => /[0-9]/.test(p), 1178 symbol: p => /[^A-Za-z0-9]/.test(p), 1179 }; 1180 const ruleEls = {}; 1181 meter.querySelectorAll(".pw-rules li[data-rule]").forEach(li => { 1182 ruleEls[li.dataset.rule] = li; 1183 }); 1184 1185 const VERDICT = { 1186 0: "Strength", 1187 1: "Very weak", 1188 2: "Weak", 1189 3: "Fair", 1190 4: "Strong", 1191 5: "Excellent", 1192 }; 1193 1194 // Rough entropy estimate (bits) â character-pool size à log2, with a 1195 // gentle penalty for runs and repeats. Not zxcvbn-grade but enough 1196 // to give a "could be cracked inâ¦" hint that feels alive. 1197 function entropyBits(p) { 1198 if (!p) return 0; 1199 let pool = 0; 1200 if (/[a-z]/.test(p)) pool += 26; 1201 if (/[A-Z]/.test(p)) pool += 26; 1202 if (/[0-9]/.test(p)) pool += 10; 1203 if (/[^A-Za-z0-9]/.test(p)) pool += 33; 1204 let bits = p.length * Math.log2(Math.max(2, pool)); 1205 if (/(.)\1{2,}/.test(p)) bits *= 0.78; // repeats: aaaa 1206 if (/0123|1234|2345|3456|4567|5678|6789|abcd|bcde|cdef|qwer|asdf|zxcv/i.test(p)) bits *= 0.75; 1207 return Math.max(0, bits); 1208 } 1209 1210 // 10 billion guesses/sec (offline GPU). Threshold the result into a 1211 // human band â anything astronomical collapses to "centuries". 1212 function crackTime(bits) { 1213 if (bits <= 0) return ""; 1214 const guesses = Math.pow(2, bits) / 2; 1215 const seconds = guesses / 1e10; 1216 if (seconds < 1) return "cracked instantly"; 1217 if (seconds < 60) return "cracked in seconds"; 1218 if (seconds < 3600) return "cracked in minutes"; 1219 if (seconds < 86400) return "cracked in hours"; 1220 if (seconds < 2592000) return "cracked in days"; 1221 if (seconds < 31536000) return "cracked in months"; 1222 if (seconds < 31536000 * 100) return "would take years"; 1223 if (seconds < 31536000 * 10000) return "would take centuries"; 1224 return "essentially uncrackable"; 1225 } 1226 1227 function score(p) { 1228 if (!p) return 0; 1229 let rulesMet = 0; 1230 Object.keys(ruleFns).forEach(k => { 1231 const ok = ruleFns[k](p); 1232 ruleEls[k].classList.toggle("pw-rule-ok", ok); 1233 if (ok) rulesMet += 1; 1234 }); 1235 const bits = entropyBits(p); 1236 // Combine ruleset coverage with entropy. Both must be high for a 5. 1237 let lvl; 1238 if (bits < 28) lvl = 1; 1239 else if (bits < 40) lvl = 2; 1240 else if (bits < 60) lvl = 3; 1241 else if (bits < 80) lvl = 4; 1242 else lvl = 5; 1243 // Don't promote past 3 without at least 4 rules satisfied. 1244 if (rulesMet < 4) lvl = Math.min(lvl, 3); 1245 if (rulesMet < 3) lvl = Math.min(lvl, 2); 1246 if (p.length < 8) lvl = Math.min(lvl, 2); 1247 return { lvl, bits }; 1248 } 1249
1250 function renderStrength() { 1251 const v = pw1.value || ""; 1252 const { lvl, bits } = score(v); 1253 segs.setAttribute("data-level", String(lvl)); 1254 meter.setAttribute("data-level", String(lvl)); 1255 verdict.textContent = VERDICT[lvl]; 1256 crack.textContent = v ? "· " + crackTime(bits) : ""; 1257 } 1258 1259 function showMeter() { 1260 if (meter.hasAttribute("hidden")) { 1261 meter.removeAttribute("hidden"); 1262 } 1263 } 1264 function maybeHideMeter() { 1265 // Hide only if both empty AND the password field has lost focus. 1266 if (!pw1.value && document.activeElement !== pw1) { 1267 meter.setAttribute("hidden", ""); 1268 } 1269 } 1270 1271 pw1.addEventListener("focus", () => { showMeter(); renderStrength(); }); 1272 pw1.addEventListener("input", () => { showMeter(); renderStrength(); }); 1273 pw1.addEventListener("blur", () => { maybeHideMeter(); }); 1274 if (pw1.value) { showMeter(); renderStrength(); } 1275 })(); 1276 </script>
1276 1277 1278
1278<script> 1279 // Eye toggle for the password field. Preserves caret position so toggling 1280 // mid-edit doesn't kick the cursor to the end. 1281 (function () { 1282 const pw = document.getElementById("id_password1"); 1283 const btn = document.getElementById("pw-toggle"); 1284 if (!pw || !btn) return; 1285 btn.addEventListener("click", function () { 1286 const showing = pw.type === "text"; 1287 const start = pw.selectionStart; 1288 const end = pw.selectionEnd; 1289 pw.type = showing ? "password" : "text"; 1290 btn.setAttribute("aria-pressed", showing ? "false" : "true"); 1291 btn.setAttribute("aria-label", showing ? "Show password" : "Hide password"); 1292 try { pw.setSelectionRange(start, end); } catch (_) {} 1293 pw.focus(); 1294 }); 1295 })(); 1296 </script>
1296 1297 1298 </div> 1299 </section> 1300</div> 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324<div id="cookie-consent" 1325 class="cc-root" 1326 hidden 1327 data-save-url="/cookie-consent/" 1328 data-policy-version="2026-06-01" 1329 role="dialog" 1330 aria-modal="false" 1331 aria-label="Cookie settings" 1332 aria-describedby="cc-banner-desc"> 1333 <input type="hidden" name="csrfmiddlewaretoken" value="hlg9j41VgXFR3A2gUP8PidYjHpK5kYLVmEDXkBKEGUzT12nOyCHO9P35hCquRkwK"> 1334 1335 1336 <div class="cc-card cc-banner" data-cc-step="banner"> 1337 <h2 class="cc-title">Cookie settings</h2> 1338 <p class="cc-text" id="cc-banner-desc"> 1339 We use cookies to deliver and improve our services, analyse site usage, 1340 and â with your agreement â to personalise your experience and market our 1341 services to you. You can read our 1342 <a href="/legal/cookies/" class="cc-link" 1343 target="_blank" rel="noopener noreferrer">Cookie Policy</a>. 1344 </p> 1345 <div class="cc-actions"> 1346 <button type="button" class="cc-btn cc-btn--ghost" data-cc-action="customize"> 1347 Customize Cookie Settings 1348 </button> 1349 <div class="cc-actions-row"> 1350 <button type="button" class="cc-btn cc-btn--outline" data-cc-action="reject"> 1351 Reject All Cookies 1352 </button> 1353 <button type="button" class="cc-btn cc-btn--primary" data-cc-action="accept"> 1354 Accept All Cookies 1355 </button> 1356 </div> 1357 </div> 1358 </div> 1359 1360 1361 <div class="cc-card cc-panel" data-cc-step="panel" hidden> 1362 <h2 class="cc-title">Cookie settings</h2> 1363 <p class="cc-text"> 1364 Choose which cookies you allow. Necessary cookies keep the site secure 1365 and working and can't be turned off. See our 1366 <a href="/legal/cookies/" class="cc-link" 1367 target="_blank" rel="noopener noreferrer">Cookie Policy</a>. 1368 </p> 1369 1370 <ul class="cc-cats"> 1371 <li class="cc-cat"> 1372 <div class="cc-cat-meta"> 1373 <span class="cc-cat-name">Necessary</span> 1374 <span class="cc-cat-desc">Enables security and basic functionality.</span> 1375 </div> 1376 <div class="cc-cat-state"> 1377 <span class="cc-required">Required</span> 1378 <span class="cc-switch cc-switch--on cc-switch--locked" aria-hidden="true"><span class="cc-knob"></span></span> 1379 </div> 1380 </li> 1381 1382 <li class="cc-cat"> 1383 <div class="cc-cat-meta"> 1384 <span class="cc-cat-name">Analytics</span> 1385 <span class="cc-cat-desc">Enables tracking of site performance.</span> 1386 </div> 1387 <label class="cc-cat-state"> 1388 <span class="cc-state-label" data-cc-label="analytics">Off</span> 1389 <input type="checkbox" class="cc-toggle" data-cc-cat="analytics" aria-label="Analytics cookies"> 1390 <span class="cc-switch" aria-hidden="true"><span class="cc-knob"></span></span> 1391 </label> 1392 </li> 1393 1394 <li class="cc-cat"> 1395 <div class="cc-cat-meta"> 1396 <span class="cc-cat-name">Marketing</span> 1397 <span class="cc-cat-desc">Enables ads personalisation and tracking.</span> 1398 </div> 1399 <label class="cc-cat-state"> 1400 <span class="cc-state-label" data-cc-label="marketing">Off</span> 1401 <input type="checkbox" class="cc-toggle" data-cc-cat="marketing" aria-label="Marketing cookies"> 1402 <span class="cc-switch" aria-hidden="true"><span class="cc-knob"></span></span> 1403 </label> 1404 </li> 1405 </ul> 1406 1407 <div class="cc-actions-row"> 1408 <button type="button" class="cc-btn cc-btn--outline" data-cc-action="back"> 1409 Back 1410 </button> 1411 <button type="button" class="cc-btn cc-btn--primary" data-cc-action="save"> 1412 Save preferences 1413 </button> 1414 </div> 1415 </div> 1416</div> 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439
1439<script src="https://unpkg.com/[email protected]" defer></script>
1439 1440 1441 1442 1443
1443<script src="https://unpkg.com/[email protected]/preload.js" defer></script>
1443 1444
1444<script src="/static/js/app.07742fd456f5.js" defer></script>
1444 1445 1446 1447
1447<script src="/static/js/settings-back.b1b0af35d605.js" defer></script>
1447 1448
1448<script src="/static/js/settings-nav.0195da7001b9.js" defer></script>
1448 1449 1450 1451
1451<script src="/static/js/quota-guard.3f62c54f3d7c.js" defer></script>
1451 1452 1453
1453<script src="/static/js/lex-sound.538ce90d3981.js" defer></script>
1453 1454 1455 1456
1456<script src="/static/js/output-actions.5c8ff1392774.js" defer></script>
1456 1457 1458 1459 1460
1460<script src="/static/js/md-table-copy.78492ffdb83c.js" defer></script>
1460 1461 1462
1462<script src="/static/js/compose-files.dd1b62c76182.js" defer></script>
1462 1463 1464
1464<script src="/static/js/attach-menu.84719a664cf1.js" defer></script>
1464 1465 1466
1466<script src="/static/js/matter-files-panel.9b3827725089.js" defer></script>
1466 1467 1468
1468<script src="/static/js/compose-improve.f1c27ef990b5.js" defer></script>
1468 1469 1470
1470<script src="/static/js/compose-tip.4063488c4cf8.js" defer></script>
1470 1471 1472
1472<script src="/static/js/ask-on-this.d814c40394f8.js" defer></script>
1472 1473 1474
1474<script src="/static/js/compose-progress.34009430d8d8.js" defer></script>
1474 1475 1476
1476<script src="/static/js/compose-charcount.2c48fb7b37a1.js" defer></script>
1476 1477
1477<script src="/static/js/token-meter.1e53d62fd4e8.js" defer></script>
1477 1478 1479
1479<script src="/static/js/dictation.5da007e633ee.js" defer></script>
1479 1480
1480<script src="/static/js/cookie-consent.a3fa9484bec7.js" defer></script>
1480 1481 1482 1483
1483<script src="/static/js/feedback.23023337adff.js" defer></script>
1483 1484
1484<script src="/static/js/feature-request.ce7e8129f424.js" defer></script>
1484 1485
1485<script src="/static/js/referrals.f586d0c0853f.js" defer></script>
1485 1486
1486<script src="/static/js/nps.af3a19d7a75d.js" defer></script>
1486 1487 1488
1488<script src="/static/js/shortcuts.89ea7186515b.js" defer></script>
1488 1489 1490
1490<script src="/static/js/tour.c7856152b8f8.js" defer></script>
1490 1491 1492
1492<script src="/static/js/library-editor.de607e99d311.js" defer></script>
1492 1493 1494
1494<script src="/static/js/dock-overflow.3459f6b43659.js" defer></script>
1494 1495 1496
1496<script> 1497 (function initSignupPopups() { 1498 function boot() { 1499 if (!window.bootstrap || !bootstrap.Popover) return setTimeout(boot, 40); 1500 document 1501 .querySelectorAll('.account-form [data-bs-toggle="popover"]') 1502 .forEach((el) => { 1503 if (el._lexPop) return; 1504 el._lexPop = new bootstrap.Popover(el, { container: "body", html: false }); 1505 }); 1506 document 1507 .querySelectorAll('.account-form [data-bs-toggle="tooltip"]') 1508 .forEach((el) => { 1509 if (el._lexTip) return; 1510 el._lexTip = new bootstrap.Tooltip(el, { container: "body" }); 1511 }); 1512 } 1513 if (document.readyState === "loading") { 1514 document.addEventListener("DOMContentLoaded", boot); 1515 } else { 1516 boot(); 1517 } 1518 })(); 1519 </script>
1519 1520 1521</body> 1522</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.