History

Merge pull request #739 from RhysSullivan/claude/update-marketing-modal-6MTp4

Rhys Sullivan committed 6 months ago

Commit 8db007b2 parents d021499 cbe8f461 file · +218 −12Browse files

Unified · Split

apps/marketing/src/pages/index.astro+218−12
@@ -268,18 +268,156 @@ import { AnimatedBeamDemo } from "../components/animated-beam-demo"; · 267 unmodified lines
268268 <p
269269 class="text-[14.5px] leading-[1.55] text-ink-2 mb-6 text-pretty flex-1"
270270 >
271− We're looking for early adopters to help us develop the
272− self-hosted product. If you're a company interested in self
271+ We're looking for early adopters to give feedback and use the
272+ self hosted product. If you're a company interested in self
273273 hosting get in touch.
274274 </p>
275− <a href="mailto:rhys@executor.sh" class="btn-link self-start"
276− >Get in touch →</a
275+ <button
276+ type="button"
277+ class="btn-link self-start"
278+ data-open-modal="self-host-contact"
279+ >Get in touch →</button
277280 >
278281 </div>
279282 </div>
280283 </div>
281284 </section>
282285
286+ {/* ─── SELF-HOST CONTACT MODAL ─── */}
287+ <dialog
288+ id="self-host-contact"
289+ class="self-host-modal"
290+ aria-labelledby="self-host-contact-title"
291+ >
292+ <div class="surface-card p-7 max-w-[520px] w-full">
293+ <div class="flex items-start justify-between gap-4 mb-5">
294+ <h3
295+ id="self-host-contact-title"
296+ class="text-[20px] font-semibold tracking-[-0.01em] text-ink"
297+ >
298+ Get in touch
299+ </h3>
300+ <button
301+ type="button"
302+ class="text-ink-3 hover:text-ink transition-colors"
303+ data-close-modal
304+ aria-label="Close"
305+ >
306+ <svg
307+ width="18"
308+ height="18"
309+ viewBox="0 0 24 24"
310+ fill="none"
311+ stroke="currentColor"
312+ stroke-width="2"
313+ stroke-linecap="round"
314+ stroke-linejoin="round"
315+ ><line x1="18" y1="6" x2="6" y2="18"></line><line
316+ x1="6"
317+ y1="6"
318+ x2="18"
319+ y2="18"></line></svg
320+ >
321+ </button>
322+ </div>
323+
324+ <p class="text-[13.5px] leading-[1.55] text-ink-2 mb-2">
325+ Copy the template, fill in your company, and send it over.
326+ </p>
327+
328+ <div class="relative mb-5">
329+ <textarea
330+ id="self-host-template"
331+ class="w-full text-[14px] leading-[1.55] text-ink bg-surface-2 border border-rule rounded-md p-3 pr-12 font-mono resize-none"
332+ rows="3"
333+ readonly
334+ >Hi Rhys, I'm at {company} and interested in self hosting executor.</textarea
335+ >
336+ <button
337+ type="button"
338+ class="copy-btn absolute top-2 right-2 p-1.5 rounded text-ink-3 hover:text-ink hover:bg-surface transition-colors"
339+ data-copy-target="self-host-template"
340+ aria-label="Copy template to clipboard"
341+ >
342+ <span class="copy-icons" aria-hidden="true">
343+ <svg
344+ class="copy-icon-copy"
345+ width="14"
346+ height="14"
347+ viewBox="0 0 24 24"
348+ fill="none"
349+ stroke="currentColor"
350+ stroke-width="2"
351+ stroke-linecap="round"
352+ stroke-linejoin="round"
353+ ><rect x="9" y="9" width="13" height="13" rx="2" ry="2"
354+ ></rect><path
355+ d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
356+ ></path></svg
357+ >
358+ <svg
359+ class="copy-icon-check"
360+ width="14"
361+ height="14"
362+ viewBox="0 0 24 24"
363+ fill="none"
364+ stroke="currentColor"
365+ stroke-width="2.5"
366+ stroke-linecap="round"
367+ stroke-linejoin="round"
368+ ><polyline points="20 6 9 17 4 12"></polyline></svg
369+ >
370+ </span>
371+ </button>
372+ </div>
373+
374+ <div
375+ class="flex items-center justify-between gap-3 bg-surface-2 border border-rule rounded-md px-3 py-2"
376+ >
377+ <span class="font-mono text-[14px] text-ink truncate"
378+ >rhys@executor.sh</span
379+ >
380+ <button
381+ type="button"
382+ class="copy-btn flex items-center gap-1.5 text-[13px] text-ink-2 hover:text-ink transition-colors"
383+ data-copy="rhys@executor.sh"
384+ aria-label="Copy email to clipboard"
385+ >
386+ <span class="copy-label">Copy</span>
387+ <span class="copy-icons" aria-hidden="true">
388+ <svg
389+ class="copy-icon-copy"
390+ width="14"
391+ height="14"
392+ viewBox="0 0 24 24"
393+ fill="none"
394+ stroke="currentColor"
395+ stroke-width="2"
396+ stroke-linecap="round"
397+ stroke-linejoin="round"
398+ ><rect x="9" y="9" width="13" height="13" rx="2" ry="2"
399+ ></rect><path
400+ d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
401+ ></path></svg
402+ >
403+ <svg
404+ class="copy-icon-check"
405+ width="14"
406+ height="14"
407+ viewBox="0 0 24 24"
408+ fill="none"
409+ stroke="currentColor"
410+ stroke-width="2.5"
411+ stroke-linecap="round"
412+ stroke-linejoin="round"
413+ ><polyline points="20 6 9 17 4 12"></polyline></svg
414+ >
415+ </span>
416+ </button>
417+ </div>
418+ </div>
419+ </dialog>
420+
283421 {/* ─── FOOTER ─── */}
284422 <footer class="border-t border-rule py-10">
285423 <div
@@ -309,23 +447,91 @@ import { AnimatedBeamDemo } from "../components/animated-beam-demo"; · 23 unmodified lines
309447 </Layout>
310448
311449 <script>
450+ const flashCopied = (() => {
451+ const timers = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>();
452+ return (btn: HTMLElement, ok: boolean) => {
453+ btn.dataset.copied = ok ? "true" : "false";
454+ const existing = timers.get(btn);
455+ if (existing) clearTimeout(existing);
456+ timers.set(
457+ btn,
458+ setTimeout(() => {
459+ delete btn.dataset.copied;
460+ timers.delete(btn);
461+ }, 1500),
462+ );
463+ };
464+ })();
465+
312466 document
313467 .querySelectorAll<HTMLButtonElement>("button[data-copy]")
314468 .forEach((btn) => {
315− let timer: ReturnType<typeof setTimeout> | null = null;
316−
317469 btn.addEventListener("click", async () => {
318470 const text = btn.dataset.copy ?? "";
319471 try {
320472 await navigator.clipboard.writeText(text);
321− btn.dataset.copied = "true";
473+ flashCopied(btn, true);
322474 } catch {
323− btn.dataset.copied = "false";
475+ flashCopied(btn, false);
324476 }
325− if (timer) clearTimeout(timer);
326− timer = setTimeout(() => {
327− delete btn.dataset.copied;
328− }, 1500);
477+ });
478+ });
479+
480+ document
481+ .querySelectorAll<HTMLButtonElement>("button[data-copy-target]")
482+ .forEach((btn) => {
483+ btn.addEventListener("click", async () => {
484+ const id = btn.dataset.copyTarget ?? "";
485+ const target = document.getElementById(id) as
486+ | HTMLTextAreaElement
487+ | HTMLInputElement
488+ | null;
489+ const text = target?.value ?? "";
490+ try {
491+ await navigator.clipboard.writeText(text);
492+ flashCopied(btn, true);
493+ } catch {
494+ flashCopied(btn, false);
495+ }
496+ });
497+ });
498+
499+ document
500+ .querySelectorAll<HTMLButtonElement>("button[data-open-modal]")
501+ .forEach((btn) => {
502+ btn.addEventListener("click", () => {
503+ const id = btn.dataset.openModal ?? "";
504+ const dialog = document.getElementById(id) as HTMLDialogElement | null;
505+ dialog?.showModal();
506+ });
507+ });
508+
509+ document
510+ .querySelectorAll<HTMLDialogElement>("dialog.self-host-modal")
511+ .forEach((dialog) => {
512+ dialog
513+ .querySelectorAll<HTMLButtonElement>("button[data-close-modal]")
514+ .forEach((btn) => {
515+ btn.addEventListener("click", () => dialog.close());
516+ });
517+
518+ dialog.addEventListener("click", (event) => {
519+ if (event.target === dialog) dialog.close();
329520 });
330521 });
331522 </script>
523+
524+<style>
525+ dialog.self-host-modal {
526+ background: transparent;
527+ border: none;
528+ padding: 0;
529+ max-width: 92vw;
530+ width: 520px;
531+ color: inherit;
532+ }
533+ dialog.self-host-modal::backdrop {
534+ background: rgba(0, 0, 0, 0.5);
535+ backdrop-filter: blur(4px);
536+ }
537+</style>