feat(web): Add landing page

This commit is contained in:
Wanjohi
2025-07-21 17:55:55 +03:00
parent 43103e2914
commit 9cf3ad5397
7 changed files with 466 additions and 37 deletions

View File

@@ -0,0 +1,173 @@
---
import { Stack } from "../solidjs/index.ts";
---
<div
style={{
"--width":
"clamp(calc(250px - 1px),calc(100vw - 1px - (1rem * 2)),calc(1080px - 1px))",
"--height": "fit-content",
}}
class="overflow-hidden"
>
<!-- Hero Component on Mobile -->
<section
style={{
"--grid-rows": 2,
"--grid-cols": 1,
"--column-width": "calc(var(--width) / var(--grid-cols))",
"--row-height": "calc(var(--height) / var(--grid-rows))",
}}
class="max-mobile:grid hidden w-(--width) h-(--height) relative grid-template after:absolute after:block after:left-0 after:right-0 after:size-full after:bg-gradient-to-b after:from-background-200 after:from-70% after:to-transparent after:to-80% pointer-events-none"
>
<div class="overflow-hidden z-20 mb-px mt-px col-auto row-auto p-6">
<div
class="max-mobile:gap-6 max-w-5xl:gap-6 h-full flex flex-col justify-center items-center"
>
<div class="flex items-center gap-2 flex-col text-center">
<h1
class="max-[520px]:max-w-[360px] max-mobile:text-2xl max-mobile:leading-[133%] max-mobile:-tracking-[.96px] text-venter font-semibold text-3xl text-balance text-center mb-4"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</h1>
<p
style={{
"--xs-text-size": "1rem",
"--xs-text-line-height": "1.5rem",
"--xs-text-weight": "400",
"--xs-text-letter-spacing": "0px",
"--sm-text-size": "1rem",
"--sm-text-line-height": "1.5rem",
"--sm-text-weight": "400",
"--sm-text-letter-spacing": "0px",
"--smd-text-size": "1rem",
"--smd-text-line-height": "1.5rem",
"--smd-text-weight": "400",
"--smd-text-letter-spacing": "0px",
"--md-text-size": "1rem",
"--md-text-line-height": "1.5rem",
"--md-text-weight": "400",
"--md-text-letter-spacing": "0px",
"--lg-text-size": "1.25rem",
"--lg-text-line-height": "2.25rem",
"--lg-text-weight": "400",
"--lg-text-letter-spacing": "0px",
}}
class="text-lg text-center mb-6 text_wrapper max-mobile:max-w-4/5 max-[768px]:text-sm text-balance"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
</p>
</div>
<Stack client:load>
<a
href="/"
class="rounded-full leading-6 outline-offset-2 bg-gray-1000 text-gray-100 max-w-full justify-center items-center p-3 px-[14px] h-10 flex cursor-pointer select-none outline-none text-sm"
>
Start Playing
</a>
<a
href="/"
class="bg-background-100 hover:bg-background-200 shadow-shadow-border-small rounded-full leading-6 outline-offset-2 text-gray-1000 border border-gray-alpha-400 max-w-full justify-center items-center p-3 px-[14px] h-10 flex cursor-pointer select-none outline-none text-sm"
>
View Pricing
</a>
</Stack>
</div>
</div>
</section>
<section
style={{
"--sm-grid-rows": 4,
"--md-grid-rows": 8,
"--sm-grid-cols": 8,
"--md-grid-cols": 12,
"--column-width": "calc(var(--width) / var(--md-grid-cols))",
"--row-height": "calc(var(--height) / var(--md-grid-rows))",
"--sm-height": "calc(var(--width) / var(--grid-cols) * var(--grid-rows))",
}}
class="w-(--width) h-(--sm-height) grid relative grid-template overflow-hidden isolate border-b border-gray-200"
>
<!--TODO: Fix TailwindCSS dark-mode system to use the CSS class-->
<div
style={{
"--sm-grid-row": "1 / -1",
"--sm-grid-column": "1 / -1",
"--sm-cell-rows": -2,
"--sm-cell-columns": -2,
}}
] class="z-0 p-0 overflow-hidden mr-px mb-px col-span-full row-span-full select-none transform-[translateY(calc(var(--row-height)/1.2))] relative before:absolute before:inset-0 before:z-100 before:dark:[background:radial-gradient(150%_150%_at_50%_140%,transparent_0,transparent_50%,var(--color-background-200)_76%)] before:[background:radial-gradient(120%_70%_at_50%_100%,transparent_50%,rgba(255,255,255,.95)_88%,var(--color-background-200)_95%)]"
>
<div class="rainbow"></div>
</div>
</section>
<section>
<div
style={{
"--grid-cols": 12,
"--row-height": "calc(var(--width) / var(--grid-cols))",
"--column-width": "var(--width)"
"--grid-rows":1,
}}
class="border-gray-200 border-b grid-template grid relative"
>
<div class=" e"></div>
</div>
</section>
</div>
<style>
.grid-template {
grid-template-columns: repeat(var(--grid-cols), var(--column-width));
grid-template-rows: repeat(var(--grid-rows), var(--row-height));
}
@media screen and (min-width: 0px) {
.text_wrapper {
--text-size: var(--sm-text-size);
--text-weight: var(--sm-text-weight);
--text-line-height: var(--sm-text-line-height);
--text-letter-spacing: var(--sm-text-letter-spacing);
}
}
@media screen and (min-width: 601px) {
.text_wrapper {
--text-size: var(--md-text-size);
--text-weight: var(--md-text-weight);
--text-line-height: var(--md-text-line-height);
--text-letter-spacing: var(--md-text-letter-spacing);
}
}
@media screen and (min-width: 961px) {
.text_wrapper {
--text-size: var(--lg-text-size);
--text-weight: var(--lg-text-weight);
--text-line-height: var(--lg-text-line-height);
--text-letter-spacing: var(--lg-text-letter-spacing);
}
}
.rainbow {
--yellow: rgba(255, 206, 32, 0.8);
--red: rgba(197, 0, 0, 0.8);
--blue: rgba(0, 89, 171, 0.8);
--cyan: rgba(15, 255, 169, 0.8);
--angle: 180deg;
--offset: 70%;
mix-blend-mode: hard-light;
width: 100%;
inset: 0;
height: 100%;
position: absolute;
background: conic-gradient(
from var(--angle) at 50% var(--offset),
rgba(0, 0, 0, 0) 0deg,
var(--blue) 72.0000010728836deg,
var(--cyan) 144.0000021457672deg,
var(--yellow) 216.00000858306885deg,
var(--red) 288.0000042915344deg,
rgba(0, 0, 0, 0) 1turn
);
background-color: black;
}
</style>

View File

@@ -0,0 +1,136 @@
import { type JSX, splitProps } from "solid-js";
import styles from "./index.module.css";
export interface StackProps {
stackFlex?: string;
stackDirection?: "column" | "row" | "row-reverse" | "column-reverse";
stackAlign?: "start" | "end" | "center" | "stretch" | "baseline";
stackJustify?: "start" | "end" | "center" | "space-between" | "space-around" | "space-evenly";
stackPadding?: string;
smStackGap?: string;
mdStackGap?: string;
lgStackGap?: string;
xlStackGap?: string;
smStackDirection?: string;
smStackAlign?: string;
smStackJustify?: string;
smStackPadding?: string;
mdStackDirection?: string;
mdStackAlign?: string;
mdStackJustify?: string;
mdStackPadding?: string;
lgStackDirection?: string;
lgStackAlign?: string;
lgStackJustify?: string;
lgStackPadding?: string;
xlStackDirection?: string;
xlStackAlign?: string;
xlStackJustify?: string;
xlStackPadding?: string;
children?: JSX.Element | JSX.Element[];
class?: string;
}
const defaultProps: Omit<StackProps, "children" | "class"> = {
stackFlex: "initial",
stackDirection: "row",
stackAlign: "stretch",
stackJustify: "center",
stackPadding: "0px",
smStackGap: "16px",
mdStackGap: "24px",
lgStackGap: "24px",
xlStackGap: "24px",
smStackDirection: undefined,
smStackAlign: undefined,
smStackJustify: undefined,
smStackPadding: undefined,
mdStackDirection: undefined,
mdStackAlign: undefined,
mdStackJustify: undefined,
mdStackPadding: undefined,
lgStackDirection: undefined,
lgStackAlign: undefined,
lgStackJustify: undefined,
lgStackPadding: undefined,
xlStackDirection: undefined,
xlStackAlign: undefined,
xlStackJustify: undefined,
xlStackPadding: undefined,
};
export function Stack(props: StackProps) {
const [local, others] = splitProps({ ...defaultProps, ...props }, [
"stackFlex",
"stackDirection",
"stackAlign",
"stackJustify",
"stackPadding",
"smStackGap",
"mdStackGap",
"lgStackGap",
"xlStackGap",
"smStackDirection",
"smStackAlign",
"smStackJustify",
"smStackPadding",
"mdStackDirection",
"mdStackAlign",
"mdStackJustify",
"mdStackPadding",
"lgStackDirection",
"lgStackAlign",
"lgStackJustify",
"lgStackPadding",
"xlStackDirection",
"xlStackAlign",
"xlStackJustify",
"xlStackPadding",
"children",
"class"
]);
const styleVars: Record<string, string | undefined> = {
"--stack-flex": local.stackFlex,
"--stack-direction": local.stackDirection,
"--stack-align": local.stackAlign,
"--stack-justify": local.stackJustify,
"--stack-padding": local.stackPadding,
"--sm-stack-gap": local.smStackGap,
"--md-stack-gap": local.mdStackGap,
"--lg-stack-gap": local.lgStackGap,
"--xl-stack-gap": local.xlStackGap,
...(local.smStackDirection && { "--sm-stack-direction": local.smStackDirection }),
...(local.smStackAlign && { "--sm-stack-align": local.smStackAlign }),
...(local.smStackJustify && { "--sm-stack-justify": local.smStackJustify }),
...(local.smStackPadding && { "--sm-stack-padding": local.smStackPadding }),
...(local.mdStackDirection && { "--md-stack-direction": local.mdStackDirection }),
...(local.mdStackAlign && { "--md-stack-align": local.mdStackAlign }),
...(local.mdStackJustify && { "--md-stack-justify": local.mdStackJustify }),
...(local.mdStackPadding && { "--md-stack-padding": local.mdStackPadding }),
...(local.lgStackDirection && { "--lg-stack-direction": local.lgStackDirection }),
...(local.lgStackAlign && { "--lg-stack-align": local.lgStackAlign }),
...(local.lgStackJustify && { "--lg-stack-justify": local.lgStackJustify }),
...(local.lgStackPadding && { "--lg-stack-padding": local.lgStackPadding }),
...(local.xlStackDirection && { "--xl-stack-direction": local.xlStackDirection }),
...(local.xlStackAlign && { "--xl-stack-align": local.xlStackAlign }),
...(local.xlStackJustify && { "--xl-stack-justify": local.xlStackJustify }),
...(local.xlStackPadding && { "--xl-stack-padding": local.xlStackPadding }),
};
const styleStr = Object.entries(styleVars)
.filter(([, v]) => v !== undefined)
.map(([k, v]) => `${k}: ${v}`)
.join("; ");
return (
<div
class={`${styles.stack} ${local.stackPadding !== "0px" ? styles.stackPadding : ""} ${local.class ? ` ${local.class}` : ""}`}
style={styleStr}
{...others}
>
{local.children}
</div>
);
}

View File

@@ -0,0 +1,92 @@
.stack {
display: flex;
flex-direction: var(--stack-direction, column);
align-items: var(--stack-align, stretch);
justify-content: var(--stack-justify, flex-start);
flex: var(--stack-flex, initial);
gap: var(--stack-gap, 0);
}
.stack_padding {
padding: var(--stack-padding, 0);
}
@media screen and (max-width: 600px) {
.stack {
--stack-direction: var(--sm-stack-direction);
--stack-align: var(--sm-stack-align);
--stack-justify: var(--sm-stack-justify);
--stack-padding: var(--sm-stack-padding);
--stack-gap: var(--sm-stack-gap);
}
}
@media screen and (min-width: 601px) and (max-width: 960px) {
.stack {
--stack-direction: var(--md-stack-direction, var(--sm-stack-direction));
--stack-align: var(--md-stack-align, var(--sm-stack-align));
--stack-justify: var(--md-stack-justify, var(--sm-stack-justify));
--stack-padding: var(--md-stack-padding, var(--sm-stack-padding));
--stack-gap: var(--md-stack-gap, var(--sm-stack-gap));
}
}
@media screen and (min-width: 961px) and (max-width: 1200px) {
.stack {
--stack-direction: var(
--lg-stack-direction,
var(--md-stack-direction, var(--sm-stack-direction))
);
--stack-align: var(
--lg-stack-align,
var(--md-stack-align, var(--sm-stack-align))
);
--stack-justify: var(
--lg-stack-justify,
var(--md-stack-justify, var(--sm-stack-justify))
);
--stack-padding: var(
--lg-stack-padding,
var(--md-stack-padding, var(--sm-stack-padding))
);
--stack-gap: var(
--lg-stack-gap,
var(--md-stack-gap, var(--sm-stack-gap))
);
}
}
@media screen and (min-width: 1201px) {
.stack {
--stack-direction: var(
--xl-stack-direction,
var(
--lg-stack-direction,
var(--md-stack-direction, var(--sm-stack-direction))
)
);
--stack-align: var(
--xl-stack-align,
var(--lg-stack-align, var(--md-stack-align, var(--sm-stack-align)))
);
--stack-justify: var(
--xl-stack-justify,
var(
--lg-stack-justify,
var(--md-stack-justify, var(--sm-stack-justify))
)
);
--stack-padding: var(
--xl-stack-padding,
var(
--lg-stack-padding,
var(--md-stack-padding, var(--sm-stack-padding))
)
);
--stack-gap: var(
--xl-stack-gap,
var(--lg-stack-gap, var(--md-stack-gap, var(--sm-stack-gap)))
);
}
}

View File

@@ -0,0 +1 @@
export * from "./Stack"

View File

@@ -1,12 +1,12 @@
--- ---
import '../styles/global.css'; import "../styles/global.css";
import "@fontsource/geist-sans/400.css"; import "@fontsource/geist-sans/400.css";
import "@fontsource/geist-sans/500.css"; import "@fontsource/geist-sans/500.css";
import "@fontsource/geist-sans/600.css"; import "@fontsource/geist-sans/600.css";
import "@fontsource/geist-sans/700.css"; import "@fontsource/geist-sans/700.css";
import "@fontsource/geist-sans/800.css"; import "@fontsource/geist-sans/800.css";
import "@fontsource/geist-sans/900.css"; import "@fontsource/geist-sans/900.css";
import '@fontsource-variable/geist-mono'; import "@fontsource-variable/geist-mono";
--- ---
<!doctype html> <!doctype html>
@@ -24,35 +24,26 @@ import '@fontsource-variable/geist-mono';
media="(prefers-color-scheme: dark)" media="(prefers-color-scheme: dark)"
content="hsl(0 0% 4%);" content="hsl(0 0% 4%);"
/> />
<link <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
rel="icon" <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>Nestri</title> <title>Nestri - Stream apps and games from the cloud</title>
</head> </head>
<body class="bg-background-200 text-gray-1000" > <body class="bg-background-200 text-gray-1000">
<noscript>You need to enable JavaScript to see this website.</noscript>
<slot /> <slot />
</body> </body>
</html> </html>
<style> <style>
*,*:before,*:after { *,
*:before,
*:after {
box-sizing: border-box; box-sizing: border-box;
} }
html, html,
body { body {
padding:0; padding: 0;
margin: 0; margin: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -62,4 +53,3 @@ import '@fontsource-variable/geist-mono';
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
</style> </style>

View File

@@ -1,10 +1,35 @@
--- ---
import Layout from "../layouts/Layout.astro"; import Layout from "../layouts/Layout.astro";
import HeroComponent from "../components/astro/HeroComponent.astro";
--- ---
<Layout> <Layout>
<main class="container mx-auto p-4"> <main class="container py-20">
<h1 class="text-3xl font-bold mb-4">Welcome to My Astro Site</h1> <div class="flex flex-col items-center justify-center">
<p class="mb-4 font-mono">This is a subtitle</p> <div
</main> class="flex relative max-w-[1080px] min-w-3xs mt-xs mr-xs before:absolute before:inset-0 before:-left-xs before:-top-xs border border-gray-200 pointer-events-none"
>
<!-- Cross -->
<!-- <div
style={{
"--cross-size": "21px",
"--cross-half-size":
"calc(var(--cross-size) / 2) + var(--guide-width, 1px) - 0.5px",
}}
class="w-fit h-fit pointer-events-none z-20 col-start-1 row-start-1 -inset-(--cross-half-size)"
>
<div
class="w-(--cross-half-size) h-(--cross-size) border-r absolute border-gray-600"
>
</div>
<div
class="h-(--cross-half-size) w-(--cross-size) border-b absolute border-gray-600"
>
</div>
</div>
<!-- Hero -->
<HeroComponent />
</div>
</div>
</main>
</Layout> </Layout>

View File

@@ -102,6 +102,9 @@
--color-background-100: hsl(0 0% 100%); --color-background-100: hsl(0 0% 100%);
--color-background-200: hsl(0 0% 98%); --color-background-200: hsl(0 0% 98%);
--color-shadow-border: 0 0 0 1px rgba(0, 0, 0, 0.08);
--color-shadow-border-small: var(--color-shadow-border), 0px 2px 2px rgba(0, 0, 0, 0.04);
} }
.dark { .dark {
@@ -206,6 +209,9 @@
--color-background-100: hsl(0 0% 4%); --color-background-100: hsl(0 0% 4%);
--color-background-200: hsl(0 0% 0%); --color-background-200: hsl(0 0% 0%);
--color-shadow-border: 0 0 0 1px rgba(255, 255, 255, 0.145);
--color-shadow-border-small: var(--color-shadow-border), 0px 1px 2px rgba(0, 0, 0, 0.16)
} }
@theme { @theme {
@@ -311,6 +317,12 @@
--color-background-100: var(--color-background-100); --color-background-100: var(--color-background-100);
--color-background-200: var(--color-background-200); --color-background-200: var(--color-background-200);
--color-brand: rgb(255, 79, 1);
--color-brand-alpha: rgba(255, 79, 1, 0.1);
--color-shadow-border-small: var(--color-shadow-border-small);
--font-sans: "Geist Sans", sans-serif; --font-sans: "Geist Sans", sans-serif;
--font-mono: "Geist Mono Variable", monospace; --font-mono: "Geist Mono Variable", monospace;
--breakpoint-mobile: 600px;
} }