Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions website/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,12 @@ export default defineConfig({
const path = new URL(page).pathname.replace(/\/$/, "") || "/";
const isTeamIndex =
path === "/team" || /\/[a-z]{2}-[A-Z]{2}\/team$/.test(path);
// Squeeze page: indexable and shareable, but kept out of the sitemap.
const isLaunch =
path === "/launch" || /\/[a-z]{2}-[A-Z]{2}\/launch$/.test(path);
return (
!isTeamIndex &&
!isLaunch &&
!page.includes("/tags/") &&
!page.includes("/account/") &&
!page.includes("/oauth/") &&
Expand Down
9 changes: 9 additions & 0 deletions website/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ interface ContactData {

interface SubscribeData {
email: string;
firstName: string;
lastName: string;
/** Which list this signup belongs to. Omitted by the site newsletter. */
source?: string;
}

export interface Entrant {
Expand Down Expand Up @@ -106,11 +110,15 @@ const createApiClient = () => {

/**
* Subscribe to waitlist
*
* `source` identifies which list the signup belongs to. Omitting it
* produces the original payload, so existing callers are unaffected.
*/
subscribe: (
email: string,
firstName: string,
lastName: string,
source?: string,
): ApiResponse => {
return fetch(`${env.API_URL}/waitlist`, {
headers: {
Expand All @@ -121,6 +129,7 @@ const createApiClient = () => {
email,
firstName,
lastName,
...(source ? { source } : {}),
} as SubscribeData),
});
},
Expand Down
95 changes: 95 additions & 0 deletions website/src/components/features/launch/AsciiFrame.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
/**
* ASCII backdrop for /launch.
*
* This is Home's hero field, not a copy of it: the element carries
* id="ascii-hero" and the script drives it through the very same
* `calcHeroDims` / `heroLoop` exports in scripts/home/hero.ts, which render
* via renderASCIIRect over chladniRect with the shared RAMP.
*
* hero.ts resolves #ascii-hero at module scope and gates its loop behind
* `heroActive`, which on Home is flipped by the terminal loader. There is no
* loader here, so we flip it ourselves. `updateMousePosition` is deliberately
* not wired — it is the one export that writes to Home's #fval readout and
* would throw on this page.
*
* Framing is done with a radial mask rather than by slicing the field, so the
* pattern stays whole and simply fades out behind the content.
*/
---

<div id="ascii-hero" class="launch-ascii" aria-hidden="true"></div>

<style>
/* Same type treatment as Home's #ascii-hero. */
.launch-ascii {
position: absolute;
inset: 0;
font-family: "Geist Mono", monospace;
font-size: 11px;
line-height: 1.22;
letter-spacing: 0.05em;
white-space: pre;
color: rgba(255, 107, 53, 0.38);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
pointer-events: none;
user-select: none;
z-index: 0;

/* Transparent where the content sits, opaque toward the edges. */
-webkit-mask-image: radial-gradient(
ellipse 62% 56% at 50% 50%,
transparent 0%,
transparent 42%,
rgba(0, 0, 0, 0.5) 68%,
#000 92%
);
mask-image: radial-gradient(
ellipse 62% 56% at 50% 50%,
transparent 0%,
transparent 42%,
rgba(0, 0, 0, 0.5) 68%,
#000 92%
);
}

@media (max-width: 768px) {
.launch-ascii {
font-size: 6px;
line-height: 1.15;

-webkit-mask-image: radial-gradient(
ellipse 78% 46% at 50% 50%,
transparent 0%,
transparent 46%,
rgba(0, 0, 0, 0.5) 70%,
#000 94%
);
mask-image: radial-gradient(
ellipse 78% 46% at 50% 50%,
transparent 0%,
transparent 46%,
rgba(0, 0, 0, 0.5) 70%,
#000 94%
);
}
}
</style>

<script>
import {
calcHeroDims,
heroLoop,
updateHeroActive,
} from "@/scripts/home/hero";

calcHeroDims();
// Home flips this from its terminal loader; /launch has none.
updateHeroActive(true);
heroLoop();

window.addEventListener("resize", calcHeroDims);
</script>
93 changes: 93 additions & 0 deletions website/src/components/features/launch/FactsSection.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
import Button from "@/components/ui/Button.astro";
import { createTranslator, getLocaleFromUrl } from "@/utils/i18n";

const locale = getLocaleFromUrl(Astro.url.pathname);
const t = await createTranslator(locale);
---

<div class="launch-facts">
<div class="launch-stat">
<span class="launch-stat-label">{t("launch.facts.supply.label")}</span>
<span class="launch-stat-value">{t("launch.facts.supply.value")}</span>
</div>

<div class="launch-consensus">
<span class="launch-stat-label">{t("launch.facts.consensus.label")}</span>

<Button
href="https://docs.quantus.com/guides/mining/"
variant="secondary"
size="medium"
target="_blank"
rel="noopener"
class="launch-mining-btn border-white hover:border-flare hover:text-flare"
data-track="mining_docs_click"
data-track-cta-location="launch"
>
{t("launch.mining_cta")}
</Button>
</div>
</div>

<style>
.launch-facts {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--launch-space-group);
}

/* Both blocks share one label/value treatment so they read as a pair. */
.launch-stat {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--launch-space-tight);
}

.launch-stat-label {
font-family: "Geist Mono", monospace;
font-size: 11px;
font-weight: 400;
color: rgba(232, 230, 224, 0.4);
letter-spacing: 0.14em;
text-transform: uppercase;
text-align: center;
line-height: 1.4;
}

.launch-stat-value {
font-family: "Geist Mono", monospace;
font-size: 36px;
font-weight: 400;
color: var(--color-flare);
letter-spacing: -0.01em;
text-align: center;
line-height: 1.1;
}

/* Match the NOTIFY ME button's padding so the two CTAs carry equal weight. */
.launch-mining-btn {
padding: 13px 26px;
}

/* Tighter than the stack gap so the eyebrow binds to its button. */
.launch-consensus {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--launch-space-tight);
}

@media (max-width: 768px) {
.launch-stat-label {
font-size: 10px;
letter-spacing: 0.12em;
}

.launch-stat-value {
font-size: 28px;
}
}
</style>
49 changes: 49 additions & 0 deletions website/src/components/features/launch/HeroSection.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
import LogoLong from "@/assets/brand/logo-long.svg";
import { createTranslator, getLocaleFromUrl } from "@/utils/i18n";

const locale = getLocaleFromUrl(Astro.url.pathname);
const t = await createTranslator(locale);
---

<div class="launch-hero">
<LogoLong class="launch-logo" />
<span class="launch-eyebrow">{t("launch.hero.eyebrow")}</span>
</div>

<style>
.launch-hero {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--launch-space-tight);
}

.launch-logo {
display: block;
height: 28px;
width: auto;
}

.launch-eyebrow {
font-family: "Geist Mono", monospace;
font-size: 12px;
font-weight: 400;
color: rgba(232, 230, 224, 0.45);
letter-spacing: 0.14em;
text-transform: uppercase;
text-align: center;
line-height: 1.5;
}

@media (max-width: 768px) {
.launch-logo {
height: 22px;
}

.launch-eyebrow {
font-size: 11px;
letter-spacing: 0.12em;
}
}
</style>
Loading