@@ -362,6 +365,18 @@ PYTHON">
inset 0 0 20px oklch(0.831 0.142 84.4 / 0.1);
}
+ .hero-cta-after {
+ font-family: var(--font-heading, "Roboto", sans-serif);
+ font-size: 1.3rem;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ color: var(--color-text-secondary);
+ border: 3px solid var(--color-border);
+ border-radius: 2px;
+ padding: 1rem 2.5rem;
+ display: inline-block;
+ }
+
@media (max-width: 768px) {
.hero-main {
padding: 5rem 1.2rem 3rem;
diff --git a/src/components/ui/DateGate.astro b/src/components/ui/DateGate.astro
new file mode 100644
index 000000000..6bcb84d8b
--- /dev/null
+++ b/src/components/ui/DateGate.astro
@@ -0,0 +1,79 @@
+---
+/**
+ * DateGate — conditionally render content based on build-time date.
+ *
+ * Defines a date window via `after` and/or `before` props. At build time,
+ * the component determines which time period "now" falls into and renders
+ * exactly one of three named slots: "before", "during", or "after".
+ *
+ * Any omitted slot simply renders nothing — use 2 slots for a simple
+ * on/off gate or all 3 for a full before/during/after split.
+ *
+ * Date strings are parsed as UTC midnight when no time portion is given
+ * (e.g. "2026-07-14" = 2026-07-14T00:00:00.000Z). For precise time-of-day
+ * gating in a specific timezone, include the offset:
+ * after="2026-07-14T00:00:00+02:00" (midnight CEST)
+ *
+ * @example
+ * // 3-state: before CfP / open / closed
+ *
+ * CfP opens June 1st!
+ * Submit your talk
+ * CfP closed. See you in Kraków!
+ *
+ *
+ * @example
+ * // 2-state: teaser → live (omit "after" slot)
+ *
+ * Tickets coming soon!
+ * Buy Tickets
+ *
+ */
+
+interface Props {
+ /**
+ * Window start — ISO 8601 date string, inclusive.
+ * Date-only strings (e.g. "2026-07-14") are parsed as UTC midnight.
+ * Include offset for timezone-aware gating: "2026-07-14T00:00:00+02:00"
+ */
+ after?: string;
+ /**
+ * Window end — ISO 8601 date string, inclusive.
+ * Same parsing rules as `after`.
+ */
+ before?: string;
+}
+
+const { after, before } = Astro.props;
+
+// --- Parse dates ---
+// Date-only strings (no "T") get interpreted as UTC midnight by new Date().
+// DateTime strings with offset (e.g. "...T00:00:00+02:00") parse correctly.
+const parseDate = (s: string): Date => {
+ const iso = s.includes("T") ? s : `${s}T00:00:00.000Z`;
+ return new Date(iso);
+};
+
+const now = new Date();
+const afterDate = after ? parseDate(after) : null;
+const beforeDate = before ? parseDate(before) : null;
+
+// --- Determine which period "now" falls into ---
+type Period = "before" | "during" | "after";
+
+let period: Period = "during";
+
+if (afterDate && beforeDate) {
+ if (now < afterDate) period = "before";
+ else if (now > beforeDate) period = "after";
+ else period = "during";
+} else if (afterDate) {
+ period = now < afterDate ? "before" : "during";
+} else if (beforeDate) {
+ period = now > beforeDate ? "after" : "during";
+}
+---
+
+{period === "before" ?
: null}
+{period === "during" ?
: null}
+{period === "after" ?
: null}