PageSourceSearch

https://clojure.stream/auth/sign-in?redirect=%2Fsettings%2Faccount

html clojure.stream collected 2026-10-02 05:35:36 UTC 27,883 bytes, 234 lines download raw bytes

1<!DOCTYPE html><html lang="en" class="h-full"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><title>Sign In - ClojureStream</title><meta name="description" content="Sign in to your ClojureStream account to access courses, workshops, and community."><meta name="author" content="ClojureStream"><meta name="theme-color" content="#111827"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="apple-touch-icon" href="/apple-touch-icon.png"><link rel="manifest" href="/site.webmanifest"><meta name="msapplication-TileColor" content="#00aba9"><meta name="application-name" content="Sign In - ClojureStream"><meta property="og:image" content="https://clojure.stream/img/og-1200x630.png"><meta property="og:image:secure_url" content="https://clojure.stream/img/og-1200x630.png"><meta property="og:image:type" content="image/png"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="og:locale" content="en_US"><meta property="og:title" content="Sign In - ClojureStream"><meta property="og:type" content="website"><meta property="og:url" content="https://clojure.stream/auth/sign-in"><meta property="og:site_name" content="Sign In - ClojureStream"><meta property="og:description" content="Sign in to your ClojureStream account to access courses, workshops, and community."><meta name="twitter:card" content="summary_large_image"><meta name="twitter:site" content="@clojurestream"><meta name="twitter:creator" content="@clojurestream"><meta name="twitter:title" content="Sign In - ClojureStream"><meta name="twitter:description" content="Sign in to your ClojureStream account to access courses, workshops, and community."><meta name="twitter:image" content="https://clojure.stream/img/og-1200x630.png"><meta name="twitter:url" content="https://clojure.stream/auth/sign-in"><link rel="stylesheet" href="/fonts/inter/inter.css"><link rel="stylesheet" href="/css/main.css?v=1789434938916">
1<script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>
1<script type="text/javascript" nonce="ETH3yFHi3E+WqkhaL6Si5Q==">document.addEventListener('click',function(e){
2        var toggle=e.target.closest('[data-twe-dropdown-toggle-ref]');
3        if(toggle){
4          e.stopPropagation();
5          var dd=toggle.closest('[data-twe-dropdown-ref]');
6          if(!dd)return;
7          var menu=dd.querySelector('[data-twe-dropdown-menu-ref]');
8          if(!menu)return;
9          var open=menu.hasAttribute('data-twe-dropdown-show');
10          document.querySelectorAll('[data-twe-dropdown-menu-ref][data-twe-dropdown-show]').forEach(function(m){
11            m.removeAttribute('data-twe-dropdown-show');m.classList.add('hidden');
12          });
13          if(!open){menu.setAttribute('data-twe-dropdown-show','');menu.classList.remove('hidden');}
14          return;
15        }
16        document.querySelectorAll('[data-twe-dropdown-menu-ref][data-twe-dropdown-show]').forEach(function(m){
17          m.removeAttribute('data-twe-dropdown-show');m.classList.add('hidden');
18        });
19        var btn=e.target.closest('[data-action]');
20        if(btn){
21          var action=btn.dataset.action;
22          if(action==='reload'){window.location.reload();}
23          else if(action==='show-modal'){var t=btn.dataset.target;if(t)document.getElementById(t).showModal();}
24        }
25      });
26      document.addEventListener('submit',function(e){
27        var f=e.target;
28        if(f.dataset.disableOnSubmit!=='true')return;
29        if(f.dataset.submitted){e.preventDefault();return;}
30        f.dataset.submitted='true';
31        var b=f.querySelector('button[type=submit]');
32        if(b){b.disabled=true;b.dataset.originalText=b.textContent;b.textContent='Saving...';}
33      });
34      document.addEventListener('DOMContentLoaded',function(){
35        document.querySelectorAll('[data-auto-dismiss]').forEach(function(el){
36          var ms=parseInt(el.dataset.autoDismiss)||5000;
37          setTimeout(function(){
38            if(typeof el.hide==='function')el.hide();
39            else el.removeAttribute('open');
40          },ms);
41        });
42        try{
43          var userTz=Intl.DateTimeFormat().resolvedOptions().timeZone;
44          document.querySelectorAll('[data-event-utc]').forEach(function(el){
45            if(el.dataset.localTimeApplied)return;
46            var iso=el.getAttribute('data-event-utc');
47            var eventTz=el.getAttribute('data-event-timezone');
48            if(!iso||eventTz===userTz)return;
49            var d=new Date(iso);
50            if(isNaN(d.getTime()))return;
51            var fmt=new Intl.DateTimeFormat([],{hour:'2-digit',minute:'2-digit',hour12:false,timeZoneName:'short'});
52            var local=document.createElement('div');
53            local.textContent='('+fmt.format(d)+' your time)';
54            el.parentNode.insertBefore(local,el.nextSibling);
55            el.dataset.localTimeApplied='1';
56          });
57        }catch(_err){/* leave canonical time untouched */}
58      });</script>
58</head><body class="body bg-gray-900 h-full overflow-y-scroll scroll-smooth"><a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-white focus:text-black focus:rounded">Skip to main content</a><div class="fixed inset-0 w-full h-full -z-10 pointer-events-none"><div class="absolute inset-x-0 top-0 transform-gpu overflow-hidden blur-3xl" aria-hidden="true"><div class="relative left-[calc(50%-11rem)] aspect-1155/678 w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-linear-to-tr from-lime-400 to-blue-500 opacity-30 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]" style="clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)"></div></div><div class="absolute inset-x-0 bottom-0 transform-gpu overflow-hidden blur-3xl" aria-hidden="true"><div class="relative left-[calc(50%+3rem)] aspect-1155/678 w-[36.125rem] -translate-x-1/2 bg-linear-to-tr from-green-300 to-sky-500 opacity-30 sm:left-[calc(50%+36rem)] sm:w-[72.1875rem]" style="clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)"></div></div></div><div class="relative z-10 flex flex-col min-h-full"><header><div class="fixed z-10 right-0 left-0 bottom-0 pb-[max(0.5rem,env(safe-area-inset-bottom))] sm:bottom-auto sm:top-0  sm:bg-linear-to-b sm:from-gray-900 sm:from-10% sm:via-gray-to-gray-900/0"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"><div class="flex items-center justify-center h-16"><nav class="flex sm:hidden gap-1 backdrop-blur-sm bg-white/5 ring-1 ring-white/10 rounded-full p-1" aria-label="Main Navigation"><a href="/" class="group flex items-center justify-center rounded-full transition px-1 text-gray-400 hover:text-white hover:bg-white/5"><img class="size-6 rounded-full" src="/img/clojurestream-logo.svg" alt="ClojureStream"></a><a href="/docs" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M7.25 3.68772C6.25693 3.24568 5.15715 3 4 3C3.44413 3 2.9015 3.05669 2.37757 3.16462C2.15412 3.21064 2 3.4115 2 3.63964V11.6344C2 11.9794 2.34235 12.2215 2.67903 12.1459C3.10405 12.0504 3.54614 12 4 12C5.1977 12 6.31344 12.3509 7.25 12.9556V3.68772Z"></path><path d="M8.75 12.9556C9.68656 12.3509 10.8023 12 12 12C12.4539 12 12.8959 12.0504 13.321 12.1459C13.6577 12.2215 14 11.9794 14 11.6344V3.63964C14 3.4115 13.8459 3.21064 13.6224 3.16462C13.0985 3.05669 12.5559 3 12 3C10.8428 3 9.74308 3.24568 8.75 3.68772V12.9556Z"></path></svg></a><a href="/courses" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M3 3.73241C3 2.54878 4.30673 1.83146 5.30531 2.46692L12.0114 6.73441C12.9376 7.32384 12.9376 8.67597 12.0114 9.2654L5.30532 13.5329C4.30673 14.1684 3 13.451 3 12.2674V3.73241Z"></path></svg></a><a href="/workshops" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M2 4C2 2.89543 2.89543 2 4 2H12C13.1046 2 14 2.89543 14 4V12C14 13.1046 13.1046 14 12 14H4C2.89543 14 2 13.1046 2 12V4ZM4.21967 5.96967C3.92678 6.26256 3.92678 6.73744 4.21967 7.03033L5.18934 8L4.21967 8.96967C3.92678 9.26256 3.92678 9.73744 4.21967 10.0303C4.51256 10.3232 4.98744 10.3232 5.28033 10.0303L6.78033 8.53033C7.07322 8.23744 7.07322 7.76256 6.78033 7.46967L5.28033 5.96967C4.98744 5.67678 4.51256 5.67678 4.21967 5.96967ZM8.75 8.5C8.33579 8.5 8 8.83579 8 9.25C8 9.66421 8.33579 10 8.75 10H11.25C11.6642 10 12 9.66421 12 9.25C12 8.83579 11.6642 8.5 11.25 8.5H8.75Z" clip-rule="evenodd" fill-rule="evenodd"></path></svg></a><a href="/jobs" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M11 4V3C11 1.89543 10.1046 1 9 1H7C5.89543 1 5 1.89543 5 3V4H4C2.89543 4 2 4.89543 2 6V9C2 10.1046 2.89543 11 4 11H12C13.1046 11 14 10.1046 14 9V6C14 4.89543 13.1046 4 12 4H11ZM9 2.5H7C6.72386 2.5 6.5 2.72386 6.5 3V4H9.5V3C9.5 2.72386 9.27614 2.5 9 2.5ZM9 9C9 9.55228 8.55228 10 8 10C7.44772 10 7 9.55228 7 9C7 8.44772 7.44772 8 8 8
58C8.55228 8 9 8.44772 9 9Z" clip-rule="evenodd" fill-rule="evenodd"></path><path d="M3 11.8291V11.9998C3 13.1044 3.89543 13.9998 5 13.9998H11C12.1046 13.9998 13 13.1044 13 11.9998V11.8291C12.6872 11.9397 12.3506 11.9998 12 11.9998H4C3.64936 11.9998 3.31278 11.9397 3 11.8291Z"></path></svg></a><a href="/podcast" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M8 1C6.89543 1 6 1.89543 6 3V7C6 8.10457 6.89543 9 8 9C9.10457 9 10 8.10457 10 7V3C10 1.89543 9.10457 1 8 1Z"></path><path d="M4.5 7C4.5 6.58579 4.16421 6.25 3.75 6.25C3.33579 6.25 3 6.58579 3 7C3 9.50652 4.84437 11.5823 7.25 11.9441V13.5H5.75C5.33579 13.5 5 13.8358 5 14.25C5 14.6642 5.33579 15 5.75 15H10.25C10.6642 15 11 14.6642 11 14.25C11 13.8358 10.6642 13.5 10.25 13.5H8.75V11.9441C11.1556 11.5823 13 9.50652 13 7C13 6.58579 12.6642 6.25 12.25 6.25C11.8358 6.25 11.5 6.58579 11.5 7C11.5 8.933 9.933 10.5 8 10.5C6.067 10.5 4.5 8.933 4.5 7Z"></path></svg></a><a href="/settings/account" class="group flex items-center rounded-full transition opacity-80 hover:opacity-100"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M8 8C9.65685 8 11 6.65685 11 5C11 3.34315 9.65685 2 8 2C6.34315 2 5 3.34315 5 5C5 6.65685 6.34315 8 8 8Z"></path><path d="M12.7347 14C13.3531 14 13.8275 13.439 13.607 12.8613C12.7455 10.6036 10.5597 9 7.99942 9C5.43913 9 3.25338 10.6036 2.39182 12.8613C2.17134 13.439 2.64575 14 3.26412 14H12.7347Z"></path></svg></a></nav><nav class="hidden sm:flex gap-1 backdrop-blur-sm bg-white/5 ring-1 ring-white/10 rounded-full p-1" aria-label="Main Navigation"><a href="/" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Home"><img class="size-6 rounded-full mr-2 -ml-2" src="/img/clojurestream-logo.svg" alt="ClojureStream">Home</a><a href="/docs" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Docs">Docs</a><a href="/courses" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Courses">Courses</a><a href="/workshops" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Workshops">Workshops</a><a href="/jobs" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Jobs">Jobs</a><a href="/podcast" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Podcast">Podcast</a><a href="/auth/sign-in" class="group flex items-center rounded-full px-3 py-0.5 ring-1 ring-teal-400 ring-inset text-sm/7 font-medium bg-blur-md hover:text-gray-900 hover:bg-teal-400 text-gray-400">Sign in</a></nav></div></div></div></header><main id="main-content" class="relative isolate container mx-auto max-w-7xl mt-8 sm:mt-24 lg:mt-32 px-4 sm:px-6 lg:px-8 flex-grow"><div class="max-w-3xl mx-auto overflow-hidden"><div class="py-5 space-y-6"><h1 class="text-2xl text-center font-bold leading-6 text-white">Sign In</h1></div><div class="mt-10 sm:mx-auto sm:w-full sm:max-w-md"><form method="POST" action="/api/actions" data-disable-on-submit="true"><input type="hidden" name="action" value="auth/login"><input type="hidden" name="__csrf-token" value="kxDQIy4UILMcieN6X6yBmNevdmprdSspkCswVlxk3uk="><div class="space-y-6"><div class="[&amp;&gt;[data-slot=label]+[data-slot=control]]:mt-3 [&amp;&gt;[data-slot=label]+[data-slot=description]]:mt-1 [&amp;&gt;[data-slot=description]+[data-slot=control]]:mt-3 [&amp;&gt;[data-slot=control]+[data-slot=description]]:mt-3 [&amp;&gt;[data-slot=control]+[data-slot=error]]:mt-3 *:data-[slot=label]:font-medium"><label for="email" data-slot="label" class="text-base/6 select-none sm:text-sm/6 text-white">Email</label><span data-slot="control" class="relative block w-full after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset focus-within:after:ring-2 has-[:disabled]:opacity-50 focus-within:after:ring-teal-500"><input placeholder="[email protected]" name="email" type="email" id="email" class="relative block w-full appearance-none rounded-lg px-[13px] py-[9px] sm:px-[11px] sm:py-[5px] text-base/6 placeholder:text-zinc-500 sm:text-sm/6 text-white border border-white/10 hover:border-white/20 bg-transparent bg-white/5 focus:outline-none user-invalid:border-red-600 user-invalid:hover:border-red-600 disabled:border-white/15 disabled:bg-white/2.5 hover:disabled:border-white/15" required></span></div><input type="hidden" name="redirect" value="/settings/account"><div class="space-y-3"><button type="submit" class="w-full relative isolate inline-flex items-baseline justify-center gap-x-2 border font-semibold focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 disabled:opacity-50 [&amp;&gt;[data-slot=icon]]:-mx-0.5 [&amp;&gt;[data-slot=icon]]:shrink-0 [&amp;&gt;[data-slot=icon]]:self-center [&amp;&gt;[data-slot=icon]]:text-(--btn-icon) rounded-lg text-base/6 px-[13px] py-[9px] sm:px-[11px] sm:py-[5px] sm:text-sm/6 [&amp;&gt;[data-slot=icon]]:my-0.5 [&amp;&gt;[data-slot=icon]]:size-5 sm:[&amp;&gt;[data-slot=icon]]:my-1 sm:[&amp;&gt;[data-slot=icon]]:size-4 border-transparent bg-(--btn-bg) border-white/5 before:hidden after:absolute after:-inset-px after:-z-10 after:rounded-lg after:shadow-[inset_0_1px_rgba(255,255,255,0.15)] hover:after:bg-(--btn-hover-overlay) disabled:before:shadow-none disabled:after:shadow-none text-white [--btn-hover-overlay:rgba(255,255,255,0.1)] [--btn-bg:#0d9488] [--btn-border:rgba(15,118,110,0.9)] [--btn-icon:rgba(255,255,255,0.6)] hover:[--btn-icon:rgba(255,255,255,0.8)] cursor-default"><span class="absolute left-1/2 top-1/2 size-[max(100%,2.75rem)] -translate-x-1/2 -translate-y-1/2 [@media(pointer:fine)]:hidden" aria-hidden="true"></span>Send one-time password</button><button type="button" id="passkey-login-btn" style="display:none" class="flex w-full justify-center items-center gap-2 rounded-md bg-white/10 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-white/20 ring-1 ring-inset ring-white/10"><svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"></path></svg>Sign in with passkey</button></div><p class="mt-4 text-center text-xs text-zinc-500">By signing in, you agree to our <a href="/legal/privacy-policy" class="text-zinc-400 hover:text-white">Privacy Policy</a> and <a href="/legal/terms-and-conditions" class="text-zinc-400 hover:text-white">Terms and Conditions</a>.</p></div></form></div><div style="display:none">
58<input type="hidden" name="__csrf-token" value="kxDQIy4UILMcieN6X6yBmNevdmprdSspkCswVlxk3uk="></div>
58<script type="text/javascript" nonce="ETH3yFHi3E+WqkhaL6Si5Q==">
59  (function() {
60    // Base64URL encoding/decoding utilities
61    function base64UrlToBytes(base64url) {
62      const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
63      const padding = '='.repeat((4 - base64.length % 4) % 4);
64      const binary = atob(base64 + padding);
65      return Uint8Array.from(binary, c => c.charCodeAt(0));
66    }
67
68    function bytesToBase64Url(bytes) {
69      const binary = String.fromCharCode(...new Uint8Array(bytes));
70      const base64 = btoa(binary);
71      return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
72    }
73
74    // Convert assertion response to JSON for server
75    function assertionToJSON(assertion) {
76      return JSON.stringify({
77        id: assertion.id,
78        rawId: bytesToBase64Url(assertion.rawId),
79        type: assertion.type,
80        response: {
81          clientDataJSON: bytesToBase64Url(assertion.response.clientDataJSON),
82          authenticatorData: bytesToBase64Url(assertion.response.authenticatorData),
83          signature: bytesToBase64Url(assertion.response.signature),
84          userHandle: assertion.response.userHandle ? bytesToBase64Url(assertion.response.userHandle) : null
85        }
86      });
87    }
88
89    let hasPasskeys = false;
90    let debounceTimer = null;
91
92    async function checkPasskeys(email) {
93      if (!email || !email.includes('@')) {
94        document.getElementById('passkey-login-btn').style.display = 'none';
95        hasPasskeys = false;
96        return;
97      }
98
99      try {
100        const res = await fetch('/api/passkey/check?email=' + encodeURIComponent(email));
101        const data = await res.json();
102        hasPasskeys = data.hasPasskeys;
103
104        const btn = document.getElementById('passkey-login-btn');
105        btn.style.display = hasPasskeys ? 'flex' : 'none';
106      } catch (e) {
107        console.error('Passkey check failed:', e);
108        document.getElementById('passkey-login-btn').style.display = 'none';
109        hasPasskeys = false;
110      }
111    }
112
113    async function loginWithPasskey() {
114      const emailInput = document.getElementById('email');
115      const email = emailInput.value.trim().toLowerCase();
116      const btn = document.getElementById('passkey-login-btn');
117      const originalText = btn.textContent;
118
119      if (!email) {
120        alert('Please enter your email address first.');
121        return;
122      }
123
124      try {
125        if (!window.PublicKeyCredential) {
126          alert('Your browser does not support passkeys.');
127          return;
128        }
129
130        btn.textContent = 'Authenticating...';
131        btn.disabled = true;
132
133        // 1. Fetch authentication options from server
134        const optionsRes = await fetch('/api/passkey/login/options?email=' + encodeURIComponent(email));
135        if (!optionsRes.ok) {
136          const error = await optionsRes.json();
137          throw new Error(error.message || 'Failed to get authentication options');
138        }
139        const options = await optionsRes.json();
140
141        // 2. Convert base64url fields to ArrayBuffer
142        const publicKeyOptions = {
143          ...options,
144          challenge: base64UrlToBytes(options.challenge)
145        };
146
147        if (options.allowCredentials) {
148          publicKeyOptions.allowCredentials = options.allowCredentials.map(cred => ({
149            ...cred,
150            id: base64UrlToBytes(cred.id)
151          }));
152        }
153
154        // 3. Get assertion via WebAuthn API
155        const assertion = await navigator.credentials.get({ publicKey: publicKeyOptions });
156
157        // 4. Submit to server via form action
158        const form = document.createElement('form');
159        form.method = 'POST';
160        form.action = '/api/actions';
161        form.style.display = 'none';
162
163        const actionInput = document.createElement('input');
164        actionInput.name = 'action';
165        actionInput.value = 'passkey/login';
166        form.appendChild(actionInput);
167
168        const emailInput2 = document.createElement('input');
169        emailInput2.name = 'email';
170        emailInput2.value = email;
171        form.appendChild(emailInput2);
172
173        const credentialInput = document.createElement('input');
174        credentialInput.name = 'credential';
175        credentialInput.value = assertionToJSON(assertion);
176        form.appendChild(credentialInput);
177
178        // Preserve redirect param if present
179        const redirectInput = document.querySelector('input[name="redirect"]');
180        if (redirectInput && redirectInput.value) {
181          const ri = document.createElement('input');
182          ri.name = 'redirect';
183          ri.value = redirectInput.value;
184          form.appendChild(ri);
185        }
186
187        // Include CSRF token
188        const csrfToken = document.querySelector('input[name="__csrf-token"]');
189        if (csrfToken) {
190          const csrfInput = document.createElement('input');
191          csrfInput.name = '__csrf-token';
192          csrfInput.value = csrfToken.value;
193          form.appendChild(csrfInput);
194        }
195
196        document.body.appendChild(form);
197        form.submit();
198
199      } catch (error) {
200        console.error('Passkey login error:', error);
201        btn.textContent = originalText;
202        btn.disabled = false;
203
204        if (error.name === 'NotAllowedError') {
205          alert('Authentication was cancelled or timed out.');
206        } else {
207          alert('Failed to authenticate with passkey: ' + error.message);
208        }
209      }
210    }
211
212    document.addEventListener('DOMContentLoaded', function() {
213      const emailInput = document.getElementById('email');
214      const passkeyBtn = document.getElementById('passkey-login-btn');
215
216      if (emailInput && passkeyBtn) {
217        // Check on page load if email is pre-filled
218        if (emailInput.value) {
219          checkPasskeys(emailInput.value);
220        }
221
222        // Check when email changes (debounced)
223        emailInput.addEventListener('input', function() {
224          clearTimeout(debounceTimer);
225          debounceTimer = setTimeout(() => checkPasskeys(this.value), 500);
226        });
227
228        // Handle passkey login button click
229        passkeyBtn.addEventListener('click', loginWithPasskey);
230      }
231    });
232  })();
233  </script>
233</div></main><footer class="footer mt-12 mb-20 sm:mb-0"><div class="max-w-7xl mx-auto py-8 px-4 sm:px-6 md:flex md:items-center md:justify-between lg:px-8"><div class="flex justify-center space-x-6 md:order-2"><a href="https://x.com/clojurestream" class="text-gray-400 hover:text-gray-200 rounded scale-80" target="_blank" rel="noopener"><span class="sr-only">X</span><svg fill="currentColor" class="h-6 w-6" viewBox="0 0 1200 1227" aria-hidden="true" fill-rule="evenodd" clip-rule="evenodd"><path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill-rule="evenodd" clip-rule="evenodd"></path></svg></a><a href="https://github.com/clojurestream" class="text-gray-400 hover:text-gray-200 rounded-full" target="_blank" rel="noopener"><span class="sr-only">GitHub</span><svg fill="currentColor" class="h-6 w-6" viewBox="0 0 24 24" aria-hidden="true" fill-rule="evenodd" clip-rule="evenodd"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" fill-rule="evenodd" clip-rule="evenodd"></path></svg></a></div><div class="mt-8 md:mt-0 md:order-1"><p class="text-center text-xs text-gray-500">© 2022 ClojureStream (by Muneo GmbH). All rights reserved · <a href="/changelog" class="text-gray-500 hover:text-gray-200 rounded hover:underline">Changelog</a> · <a href="/legal/privacy-policy" class="text-gray-500 hover:text-gray-200 rounded hover:underline">Privacy</a> · <a href="/legal/terms-and-conditions" class="text-gray-500 hover:text-gray-200 rounded hover:underline">Terms</a> · <a href="/climate" class="text-gray-500 hover:text-gray-200 rounded hover:underline">Climate</a></p></div></div></footer></div>
233<script nonce="ETH3yFHi3E+WqkhaL6Si5Q==">(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.nonce='ETH3yFHi3E+WqkhaL6Si5Q==';d.innerHTML="window.__CF$cv$params={r:'a44158396cea10c2',t:'MTc5MDkxOTMzNQ=='};var a=document.createElement('script');a.nonce='ETH3yFHi3E+WqkhaL6Si5Q==';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
233<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" nonce="ETH3yFHi3E+WqkhaL6Si5Q==" data-cf-beacon='{"version":"2024.11.0","token":"d4eeed365483480ea46adb43c3c36c3a","r":1,"spa":2}' crossorigin="anonymous"></script>
233
234</body></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.