Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 218 additions & 12 deletions apps/marketing/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -268,18 +268,156 @@ import { AnimatedBeamDemo } from "../components/animated-beam-demo";
<p
class="text-[14.5px] leading-[1.55] text-ink-2 mb-6 text-pretty flex-1"
>
We're looking for early adopters to help us develop the
self-hosted product. If you're a company interested in self
We're looking for early adopters to give feedback and use the
self hosted product. If you're a company interested in self
hosting get in touch.
</p>
<a href="mailto:rhys@executor.sh" class="btn-link self-start"
>Get in touch →</a
<button
type="button"
class="btn-link self-start"
data-open-modal="self-host-contact"
>Get in touch →</button
>
</div>
</div>
</div>
</section>

{/* ─── SELF-HOST CONTACT MODAL ─── */}
<dialog
id="self-host-contact"
class="self-host-modal"
aria-labelledby="self-host-contact-title"
>
<div class="surface-card p-7 max-w-[520px] w-full">
<div class="flex items-start justify-between gap-4 mb-5">
<h3
id="self-host-contact-title"
class="text-[20px] font-semibold tracking-[-0.01em] text-ink"
>
Get in touch
</h3>
<button
type="button"
class="text-ink-3 hover:text-ink transition-colors"
data-close-modal
aria-label="Close"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><line x1="18" y1="6" x2="6" y2="18"></line><line
x1="6"
y1="6"
x2="18"
y2="18"></line></svg
>
</button>
</div>

<p class="text-[13.5px] leading-[1.55] text-ink-2 mb-2">
Copy the template, fill in your company, and send it over.
</p>

<div class="relative mb-5">
<textarea
id="self-host-template"
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"
rows="3"
readonly
>Hi Rhys, I'm at {company} and interested in self hosting executor.</textarea
>
<button
type="button"
class="copy-btn absolute top-2 right-2 p-1.5 rounded text-ink-3 hover:text-ink hover:bg-surface transition-colors"
data-copy-target="self-host-template"
aria-label="Copy template to clipboard"
>
<span class="copy-icons" aria-hidden="true">
<svg
class="copy-icon-copy"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><rect x="9" y="9" width="13" height="13" rx="2" ry="2"
></rect><path
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
></path></svg
>
<svg
class="copy-icon-check"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
><polyline points="20 6 9 17 4 12"></polyline></svg
>
</span>
</button>
</div>

<div
class="flex items-center justify-between gap-3 bg-surface-2 border border-rule rounded-md px-3 py-2"
>
<span class="font-mono text-[14px] text-ink truncate"
>rhys@executor.sh</span
>
<button
type="button"
class="copy-btn flex items-center gap-1.5 text-[13px] text-ink-2 hover:text-ink transition-colors"
data-copy="rhys@executor.sh"
aria-label="Copy email to clipboard"
>
<span class="copy-label">Copy</span>
<span class="copy-icons" aria-hidden="true">
<svg
class="copy-icon-copy"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><rect x="9" y="9" width="13" height="13" rx="2" ry="2"
></rect><path
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
></path></svg
>
<svg
class="copy-icon-check"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
><polyline points="20 6 9 17 4 12"></polyline></svg
>
</span>
</button>
</div>
</div>
</dialog>

{/* ─── FOOTER ─── */}
<footer class="border-t border-rule py-10">
<div
Expand Down Expand Up @@ -309,23 +447,91 @@ import { AnimatedBeamDemo } from "../components/animated-beam-demo";
</Layout>

<script>
const flashCopied = (() => {
const timers = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>();
return (btn: HTMLElement, ok: boolean) => {
btn.dataset.copied = ok ? "true" : "false";
const existing = timers.get(btn);
if (existing) clearTimeout(existing);
timers.set(
btn,
setTimeout(() => {
delete btn.dataset.copied;
timers.delete(btn);
}, 1500),
);
};
})();

document
.querySelectorAll<HTMLButtonElement>("button[data-copy]")
.forEach((btn) => {
let timer: ReturnType<typeof setTimeout> | null = null;

btn.addEventListener("click", async () => {
const text = btn.dataset.copy ?? "";
try {
await navigator.clipboard.writeText(text);
btn.dataset.copied = "true";
flashCopied(btn, true);
} catch {
btn.dataset.copied = "false";
flashCopied(btn, false);
}
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
delete btn.dataset.copied;
}, 1500);
});
});

document
.querySelectorAll<HTMLButtonElement>("button[data-copy-target]")
.forEach((btn) => {
btn.addEventListener("click", async () => {
const id = btn.dataset.copyTarget ?? "";
const target = document.getElementById(id) as
| HTMLTextAreaElement
| HTMLInputElement
| null;
const text = target?.value ?? "";
try {
await navigator.clipboard.writeText(text);
flashCopied(btn, true);
} catch {
flashCopied(btn, false);
}
});
});

document
.querySelectorAll<HTMLButtonElement>("button[data-open-modal]")
.forEach((btn) => {
btn.addEventListener("click", () => {
const id = btn.dataset.openModal ?? "";
const dialog = document.getElementById(id) as HTMLDialogElement | null;
dialog?.showModal();
});
});

document
.querySelectorAll<HTMLDialogElement>("dialog.self-host-modal")
.forEach((dialog) => {
dialog
.querySelectorAll<HTMLButtonElement>("button[data-close-modal]")
.forEach((btn) => {
btn.addEventListener("click", () => dialog.close());
});

dialog.addEventListener("click", (event) => {
if (event.target === dialog) dialog.close();
});
});
</script>

<style>
dialog.self-host-modal {
background: transparent;
border: none;
padding: 0;
max-width: 92vw;
width: 520px;
color: inherit;
}
dialog.self-host-modal::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
</style>
Loading