make the landing page look beautiful 😍😍😍
This commit is contained in:
parent
a72ebe853f
commit
d36c98cb49
22 changed files with 481 additions and 29 deletions
15
bun.lock
15
bun.lock
|
@ -4,6 +4,9 @@
|
|||
"": {
|
||||
"name": "my-qwik-empty-starter",
|
||||
"dependencies": {
|
||||
"@cloudgakkai/qwik-aos": "github:CloudGakkai/qwik-aos",
|
||||
"@types/aos": "^3.0.7",
|
||||
"aos": "^3.0.0-beta.6",
|
||||
"ky": "^1.8.1",
|
||||
"nanostores": "^1.0.1",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
|
@ -35,6 +38,8 @@
|
|||
|
||||
"@builder.io/qwik-city": ["@builder.io/qwik-city@1.14.1", "", { "dependencies": { "@mdx-js/mdx": "^3", "@types/mdx": "^2", "source-map": "^0.7.4", "svgo": "^3.3", "undici": "*", "valibot": ">=0.36.0 <2", "vfile": "6.0.2", "vite": "^5", "vite-imagetools": "^7", "zod": "3.22.4" } }, "sha512-VsAvk7u2HyyTnL9GhpT+h10t2XAIlxtv6LFL3Xt9/1QZ6lMfGWMcMEAMuZB1Ib+D/oTfu7QRqZngRg3FsrIKyg=="],
|
||||
|
||||
"@cloudgakkai/qwik-aos": ["@cloudgakkai/qwik-aos@github:CloudGakkai/qwik-aos#affd212", {}, "CloudGakkai-qwik-aos-affd212"],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
|
@ -245,6 +250,8 @@
|
|||
|
||||
"@trysound/sax": ["@trysound/sax@0.2.0", "", {}, "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="],
|
||||
|
||||
"@types/aos": ["@types/aos@3.0.7", "", {}, "sha512-sEhyFqvKauUJZDbvAB3Pggynrq6g+2PS4XB3tmUr+mDL1gfDJnwslUC4QQ7/l8UD+LWpr3RxZVR/rHoZrLqZVg=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
@ -297,6 +304,8 @@
|
|||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"aos": ["aos@3.0.0-beta.6", "", { "dependencies": { "classlist-polyfill": "^1.2.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1" } }, "sha512-VLWrpq8bfAWcetynVHMMrqdC+89Qq/Ym6UBJbHB4crIwp3RR8uq1dNGgsFzoDl03S43rlVMK+na3r5+oUCZsYw=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
||||
|
@ -345,6 +354,8 @@
|
|||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"classlist-polyfill": ["classlist-polyfill@1.2.0", "", {}, "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="],
|
||||
|
@ -655,8 +666,12 @@
|
|||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
|
||||
|
||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||
|
|
|
@ -42,6 +42,8 @@
|
|||
"vite-tsconfig-paths": "^4.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/aos": "^3.0.7",
|
||||
"aos": "^3.0.0-beta.6",
|
||||
"ky": "^1.8.1",
|
||||
"nanostores": "^1.0.1",
|
||||
"tailwind-scrollbar": "^4.0.2"
|
||||
|
|
BIN
public/dashboard 1.png
Normal file
BIN
public/dashboard 1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 MiB |
BIN
public/dashboard 2.png
Normal file
BIN
public/dashboard 2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 588 KiB |
|
@ -3,8 +3,8 @@ import { useNanostore$ } from "~/hooks/nanostores";
|
|||
import { api } from "~/lib/api";
|
||||
import { areFilesLoaded, dashboardFiles } from "~/lib/stores";
|
||||
import { StereoFile } from "~/lib/types";
|
||||
import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "./Icons";
|
||||
import StereoLogo from "./StereoLogo";
|
||||
import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "../misc/Icons";
|
||||
import StereoLogo from "../misc/StereoLogo";
|
||||
|
||||
export default component$(() => {
|
||||
const loaded = useNanostore$<boolean>(areFilesLoaded);
|
|
@ -3,7 +3,7 @@ import { useNanostore$ } from "~/hooks/nanostores";
|
|||
import { api } from "~/lib/api";
|
||||
import { dashboardFiles } from "~/lib/stores";
|
||||
import { StereoFile } from "~/lib/types";
|
||||
import { SolarClipboardAddBold, SolarDownloadMinimalisticBold, SolarTrashBin2Bold } from "./Icons";
|
||||
import { SolarClipboardAddBold, SolarDownloadMinimalisticBold, SolarTrashBin2Bold } from "../misc/Icons";
|
||||
|
||||
type FileProps = {
|
||||
file: StereoFile;
|
23
src/components/landing/CallToAction.tsx
Normal file
23
src/components/landing/CallToAction.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { component$ } from "@builder.io/qwik";
|
||||
import { OAUTH_LINK } from "~/lib/constants";
|
||||
|
||||
export default component$(() => (
|
||||
<div
|
||||
data-aos="fade-up"
|
||||
data-aos-duration="1000"
|
||||
class="flex flex-col gap-1.5 items-center justify-center w-4/5 bg-gradient-to-b from-stereo/30 to-transparent rounded-2xl h-96 py-4"
|
||||
>
|
||||
<p class="text-4xl text-center">
|
||||
ready to try the <span class="text-stereo">stereo</span> experience?
|
||||
</p>
|
||||
<p class="text-xl text-white/80 text-center">
|
||||
join over <span class="text-stereo">100k</span> other people hosting their files with <span class="text-stereo">stereo</span>!
|
||||
</p>
|
||||
<a
|
||||
href={OAUTH_LINK}
|
||||
class="px-12 py-1.5 mt-1.5 text-lg font-medium text-white bg-stereo rounded-full hover:text-stereo hover:bg-white transition duration-300"
|
||||
>
|
||||
get started
|
||||
</a>
|
||||
</div>
|
||||
));
|
29
src/components/landing/Footer.tsx
Normal file
29
src/components/landing/Footer.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { component$ } from "@builder.io/qwik";
|
||||
import StereoLogo from "../misc/StereoLogo";
|
||||
|
||||
export default component$(() => {
|
||||
return (
|
||||
<div class="w-screen flex py-16 px-16">
|
||||
<div class="flex flex-col flex-shrink h-full justify-start items-start gap-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="flex gap-[1ch]">
|
||||
<StereoLogo class="w-8 h-8 text-stereo" />
|
||||
<span class="text-white font-medium text-2xl">stereo<span class="text-stereo font-bold">.</span>cat</span>
|
||||
</span>
|
||||
|
||||
<span class="text-white/80 font-light text-lg">
|
||||
store all your precious moments with <span class="text-stereo font-medium">stereo</span>.
|
||||
</span>
|
||||
|
||||
<span class="text-white/80 font-light text-md">
|
||||
copyright © {new Date().getFullYear()} stereo.cat - all rights reserved.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-grow justify-end items-start gap-12">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
83
src/components/landing/Hero.tsx
Normal file
83
src/components/landing/Hero.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { component$, useSignal } from "@builder.io/qwik";
|
||||
import { useRelativeMouse } from "~/hooks/mouse";
|
||||
import { OAUTH_LINK } from "~/lib/constants";
|
||||
import GradientBorder from "../misc/GradientBorder";
|
||||
|
||||
export default component$(() => {
|
||||
const ref1 = useSignal<HTMLElement>();
|
||||
const mouse1 = useRelativeMouse(ref1);
|
||||
|
||||
const ref2 = useSignal<HTMLElement>();
|
||||
const mouse2 = useRelativeMouse(ref2);
|
||||
|
||||
return (
|
||||
<div class="flex flex-col w-full bg-gradient-to-b from-stereo/30 to-transparent overflow-x-clip select-none">
|
||||
<div class="mt-62 flex flex-col justify-center w-full h-full text-center">
|
||||
<div class="flex flex-col items-center justify-center gap-2 font-light">
|
||||
<p class="text-6xl">
|
||||
you bring the files, we'll bring the <span class="text-stereo">magic</span>.
|
||||
</p>
|
||||
<p class="text-2xl text-white/80">
|
||||
stereo is no-bs file-host inspired by Tixte, that you'll love to use
|
||||
</p>
|
||||
<div class="flex gap-4 mt-1">
|
||||
<a
|
||||
href={OAUTH_LINK}
|
||||
class="px-6 py-1 text-lg font-medium text-white bg-stereo rounded-full hover:text-stereo hover:bg-white transition duration-300"
|
||||
>
|
||||
get started
|
||||
</a>
|
||||
<a
|
||||
href="discord.gg"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="px-6 py-1 text-lg font-medium text-stereo rounded-full border border-stereo hover:bg-stereo hover:text-white transition duration-300"
|
||||
>
|
||||
learn more
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-10 flex items-center" style={{ perspective: "1000px" }}>
|
||||
<GradientBorder
|
||||
ref={ref1}
|
||||
size="3px"
|
||||
from="#ff264e"
|
||||
to="transparent"
|
||||
direction="to bottom"
|
||||
style={{
|
||||
transformStyle: "preserve-3d",
|
||||
transformOrigin: "center center",
|
||||
transform: `
|
||||
rotateX(${(mouse1.y / 120) + 30}deg)
|
||||
rotateY(${(-mouse1.x / 120) + 25}deg)
|
||||
rotateZ(-10deg)
|
||||
`,
|
||||
}}
|
||||
class="rounded-2xl -mr-12"
|
||||
>
|
||||
<img src="dashboard 1.png" class="h-[32rem] rounded-2xl shadow-2xl shadow-stereo/10" />
|
||||
</GradientBorder>
|
||||
<GradientBorder
|
||||
ref={ref2}
|
||||
size="3px"
|
||||
from="#ff264e"
|
||||
to="transparent"
|
||||
direction="to bottom"
|
||||
style={{
|
||||
transformStyle: "preserve-3d",
|
||||
transformOrigin: "center center",
|
||||
transform: `
|
||||
rotateX(${(mouse2.y / 120) + 30}deg)
|
||||
rotateY(${(-mouse2.x / 60) - 25}deg)
|
||||
rotateZ(10deg)
|
||||
`,
|
||||
}}
|
||||
class="rounded-2xl -ml-12 shadow-2xl shadow-stereo/10"
|
||||
>
|
||||
<img src="dashboard 2.png" class="h-[36rem] rounded-2xl" />
|
||||
</GradientBorder>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
41
src/components/landing/Navbar.tsx
Normal file
41
src/components/landing/Navbar.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { component$ } from "@builder.io/qwik";
|
||||
import StereoLogo from "../misc/StereoLogo";
|
||||
|
||||
export default component$(() => {
|
||||
const items = [
|
||||
{ text: "Synopsis", href: "#" },
|
||||
{ text: "Discord", href: "#" },
|
||||
{ text: "Dashboard", href: "/dashboard", highlighted: true },
|
||||
{ text: "Pricing", href: "#" },
|
||||
{ text: "Terms", href: "#" }
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
data-aos="fade-down"
|
||||
data-aos-anchor-placement="top-center"
|
||||
data-aos-duration="1000"
|
||||
class="fixed flex items-center justify-start top-6 left-1/2 transform -translate-x-1/2 bg-neutral-950 p-8 h-10 rounded-full lg:w-2/3 md:w-4/5 w-4/5 z-[9999999] shadow-lg">
|
||||
<div class="flex flex-1/3">
|
||||
<StereoLogo class="w-10 h-10 text-stereo hover:text-white transition-all duration-300 hover:cursor-pointer" />
|
||||
</div>
|
||||
<div class="w-full hidden md:flex flex-grow gap-4 items-center justify-center">
|
||||
{items.map(({ text, href, highlighted: h }) => (
|
||||
<a
|
||||
key={text}
|
||||
href={href}
|
||||
class={
|
||||
h ? "px-4 py-0.5 hover:text-stereo hover:bg-white rounded-full font-medium bg-stereo text-white transition duration-300 text-lg"
|
||||
: "text-white font-light hover:text-stereo transition duration-300 text-lg"
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div class="flex flex-1/3 items-center justify-end">
|
||||
<span class="text-white group hover:text-stereo transition duration-300 font-medium text-2xl">stereo<span class="text-stereo group-hover:text-white transition duration-300 font-bold">.</span>cat</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
});
|
40
src/components/landing/Stats.tsx
Normal file
40
src/components/landing/Stats.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { component$ } from "@builder.io/qwik";
|
||||
|
||||
export default component$(() => {
|
||||
type StatProps = {
|
||||
stat: string,
|
||||
description: string;
|
||||
}
|
||||
|
||||
const Stat = component$(({ stat, description }: StatProps) => (
|
||||
<div class="group hover:scale-105 transition-all duration-300 bg-gradient-to-t from-stereo/30 to-transparent p-8 px-12 rounded-2xl h-96 flex flex-col items-center text-center shadow-2xl shadow-stereo/15">
|
||||
<div class="flex-grow flex items-center justify-center">
|
||||
<p class="text-7xl font-bold text-stereo group-hover:text-white transition-colours duration-300">
|
||||
{stat.includes("+") ? stat.substring(0, stat.length - 1) : stat}
|
||||
{stat.endsWith("+") ? <span class="text-stereo/80 text-4xl align-super">+</span> : ""}
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-xl w-3/4 text-wrap text-white/30 group-hover:text-white/60 transition-colours duration-300">{description}</p>
|
||||
</div>
|
||||
) );
|
||||
|
||||
return (
|
||||
<div
|
||||
data-aos="fade-up"
|
||||
data-aos-duration="1000"
|
||||
class="flex flex-col gap-12 items-center justify-center w-full"
|
||||
>
|
||||
<div class="flex flex-col gap-1 items-center justify-center">
|
||||
<p class="text-lg text-stereo font-bold uppercase">statistics</p>
|
||||
<p class="text-2xl text-white/80">
|
||||
we know what you're thinking, and we have the numbers to prove it.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Stat stat="1.5M+" description="files hosted on stereo" />
|
||||
<Stat stat="100k+" description="active users on stereo" />
|
||||
<Stat stat="99.9%" description="uptime guarantee for all files" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
});
|
112
src/components/landing/Testimonials.tsx
Normal file
112
src/components/landing/Testimonials.tsx
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { $, component$, useOnDocument, useSignal } from "@builder.io/qwik";
|
||||
import ky from "ky";
|
||||
|
||||
export default component$(() => {
|
||||
type TestimonialProps = {
|
||||
nickname: string;
|
||||
pfp?: string;
|
||||
id?: string;
|
||||
quote: string;
|
||||
};
|
||||
|
||||
type LanyardResponse = {
|
||||
data: {
|
||||
discord_status: "online" | "idle" | "dnd";
|
||||
discord_user: {
|
||||
avatar: string;
|
||||
}
|
||||
},
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
const Testimonial = component$(({ nickname, id, quote, pfp }: TestimonialProps) => {
|
||||
const lanyard = useSignal<LanyardResponse>();
|
||||
|
||||
useOnDocument("DOMContentLoaded", $(async () => {
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const response = await ky.get(`https://api.lanyard.rest/v1/users/${id}`).json<LanyardResponse>();
|
||||
lanyard.value = response;
|
||||
console.log("Lanyard data:", lanyard.value);
|
||||
} catch (error) {
|
||||
console.error("Error fetching lanyard data:", error);
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<div
|
||||
data-aos="fade-up"
|
||||
data-aos-duration="1000"
|
||||
class="flex gap-6 items-center bg-gradient-to-t from-stereo/15 to-stereo/5 rounded-2xl p-6 max-w-2xl shadow-2xl shadow-stereo/15"
|
||||
>
|
||||
<div class="relative h-30 aspect-square flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
border: lanyard.value ? `3px solid ${
|
||||
lanyard.value.data.discord_status === "online"
|
||||
? "#43b581"
|
||||
: lanyard.value.data.discord_status === "idle"
|
||||
? "#faa61a"
|
||||
: lanyard.value.data.discord_status === "dnd"
|
||||
? "#f04747"
|
||||
: "#7289da"
|
||||
}` : "",
|
||||
padding: lanyard.value ? `3px` : "0px",
|
||||
}}>
|
||||
<img
|
||||
src={pfp ? pfp : (
|
||||
lanyard.value
|
||||
? `https://cdn.discordapp.com/avatars/${id}/${lanyard.value.data.discord_user.avatar}.png`
|
||||
: `https://api.dicebear.com/9.x/shapes/svg?seed=${Math.random().toString(36).substring(2, 15)}.png`
|
||||
)}
|
||||
class="rounded-full h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-stereo text-9xl h-min -mb-18">“</p>
|
||||
<p class="text-lg text-white/90">
|
||||
{quote}
|
||||
</p>
|
||||
<p class="text-white/60 font-light italic">— {nickname}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
data-aos="fade-up"
|
||||
data-aos-duration="1000"
|
||||
class="flex flex-col gap-6 items-center justify-center w-4/5"
|
||||
>
|
||||
<div class="flex flex-col gap-1 items-center justify-center">
|
||||
<p class="text-lg text-stereo font-bold uppercase">testimonials</p>
|
||||
<p class="text-2xl text-white/80">
|
||||
don't just take our word for it, hear it from our users.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-6">
|
||||
<Testimonial
|
||||
nickname="grng"
|
||||
id="829372486780715018"
|
||||
quote="stereo is the best file host I've ever used, it's fast, reliable, and the interface is so clean and easy to use. I love it!"
|
||||
/>
|
||||
|
||||
<Testimonial
|
||||
nickname="hexlocation"
|
||||
id="1325924978805440522"
|
||||
quote="I've been using stereo for a while now, and I can't imagine going back to any other file host. It's just that good!"
|
||||
/>
|
||||
|
||||
<Testimonial
|
||||
nickname="an anonymous user"
|
||||
quote="stereo has changed the way I share files, it's so easy to use and the performance is top-notch. Highly recommend!"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xl text-white/50">
|
||||
and many, many more...
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
});
|
40
src/components/misc/GradientBorder.tsx
Normal file
40
src/components/misc/GradientBorder.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { component$, CSSProperties, QwikIntrinsicElements, Slot } from "@builder.io/qwik";
|
||||
|
||||
type GradientProps = {
|
||||
size: string;
|
||||
from: string;
|
||||
to: string;
|
||||
direction?: string;
|
||||
};
|
||||
|
||||
export default component$((props: GradientProps & QwikIntrinsicElements["div"]) => {
|
||||
const {
|
||||
size,
|
||||
from,
|
||||
to,
|
||||
direction,
|
||||
style: userStyle,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const borderStyle: CSSProperties = {
|
||||
padding: size,
|
||||
background: `linear-gradient(${direction || "to bottom"}, ${from}, ${to})`,
|
||||
display: "inline-block",
|
||||
};
|
||||
|
||||
// Only spread userStyle if it's an object
|
||||
const mergedStyle =
|
||||
userStyle && typeof userStyle === "object"
|
||||
? { ...borderStyle, ...userStyle }
|
||||
: borderStyle;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={mergedStyle}
|
||||
{...rest}
|
||||
>
|
||||
<Slot />
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -3,6 +3,5 @@
|
|||
|
||||
@theme {
|
||||
--font-sans: 'DM Sans', sans-serif;
|
||||
}
|
||||
|
||||
@plugin 'tailwind-scrollbar';
|
||||
--color-stereo: #ff264e;
|
||||
}
|
27
src/hooks/mouse.ts
Normal file
27
src/hooks/mouse.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { $, Signal, useOnDocument, useStore } from "@builder.io/qwik";
|
||||
|
||||
export const useMouse = () => {
|
||||
const pos = useStore({ x: 0, y: 0 });
|
||||
|
||||
useOnDocument("mousemove", $((event: MouseEvent) => {
|
||||
const { clientX, clientY } = event;
|
||||
pos.x = clientX;
|
||||
pos.y = clientY;
|
||||
}));
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
export const useRelativeMouse = (ref: Signal<HTMLElement | undefined>) => {
|
||||
const pos = useStore({ x: 0, y: 0 });
|
||||
|
||||
useOnDocument("mousemove", $((event: MouseEvent) => {
|
||||
const el = ref.value;
|
||||
if (!el) return;
|
||||
const rect = el.getBoundingClientRect();
|
||||
pos.x = event.clientX - rect.left;
|
||||
pos.y = event.clientY - rect.top;
|
||||
}));
|
||||
|
||||
return pos;
|
||||
};
|
|
@ -1,9 +1,9 @@
|
|||
import { component$, useVisibleTask$ } from "@builder.io/qwik";
|
||||
import { routeLoader$, type DocumentHead } from "@builder.io/qwik-city";
|
||||
import Controlbar from "~/components/Controlbar";
|
||||
import Controlbar from "~/components/dashboard/Controlbar";
|
||||
// import Dropzone from "~/components/Dropzone";
|
||||
import File from "~/components/File";
|
||||
import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "~/components/Icons";
|
||||
import File from "~/components/dashboard/File";
|
||||
import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "~/components/misc/Icons";
|
||||
import { useNanostore$ } from "~/hooks/nanostores";
|
||||
import { api } from "~/lib/api";
|
||||
import { areFilesLoaded, dashboardFiles } from "~/lib/stores";
|
||||
|
@ -12,7 +12,6 @@ import { StereoFile } from "~/lib/types";
|
|||
export const useAuthCheck = routeLoader$(({ cookie, redirect: r }) => {
|
||||
const jwt = cookie.get("jwt");
|
||||
if (jwt) return {};
|
||||
|
||||
throw r(302, "/");
|
||||
});
|
||||
|
||||
|
|
|
@ -1,18 +1,44 @@
|
|||
/* eslint-disable qwik/jsx-img */
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
import { routeLoader$ } from "@builder.io/qwik-city";
|
||||
import { OAUTH_LINK } from "~/lib/constants";
|
||||
import { DocumentHead, routeLoader$ } from "@builder.io/qwik-city";
|
||||
import CallToAction from "~/components/landing/CallToAction";
|
||||
import Footer from "~/components/landing/Footer";
|
||||
import Hero from "~/components/landing/Hero";
|
||||
import Navbar from "~/components/landing/Navbar";
|
||||
import Stats from "~/components/landing/Stats";
|
||||
import Testimonials from "~/components/landing/Testimonials";
|
||||
|
||||
export const useAuthCheck = routeLoader$(({ cookie, redirect: r }) => {
|
||||
const jwt = cookie.get("jwt");
|
||||
if (!jwt) return {};
|
||||
export const useAuthCheck = routeLoader$(({ cookie, redirect: r, query }) => {
|
||||
const jwt = cookie.get("jwt");
|
||||
const set = Boolean(query.get("jwt_set"));
|
||||
|
||||
throw r(302, "/dashboard");
|
||||
if (jwt && set) {
|
||||
throw r(302, "/dashboard");
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
export default component$(() => {
|
||||
return (
|
||||
<div class="flex flex-col items-center justify-center flex-grow">
|
||||
<a href={OAUTH_LINK} class="bg-white text-black p-2 rounded-lg">clik heir 2 auth!!!!!!</a>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<div class="flex flex-col flex-grow w-screen items-center mb-24 gap-56">
|
||||
<Hero />
|
||||
<Stats />
|
||||
<Testimonials />
|
||||
<CallToAction />
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const head: DocumentHead = {
|
||||
title: "Welcome to Qwik",
|
||||
meta: [
|
||||
{
|
||||
name: "description",
|
||||
content: "Qwik site description",
|
||||
},
|
||||
],
|
||||
};
|
|
@ -1,9 +1,25 @@
|
|||
import { component$, Slot } from '@builder.io/qwik';
|
||||
|
||||
import { $, component$, Slot, useOnDocument } from '@builder.io/qwik';
|
||||
import AOS from 'aos';
|
||||
import 'aos/dist/aos.css';
|
||||
|
||||
export default component$(() => {
|
||||
return (
|
||||
<div class="flex flex-col min-h-screen bg-neutral-950 text-white scrollbar-thin overflow-scroll">
|
||||
<Slot />
|
||||
</div>
|
||||
);
|
||||
useOnDocument("DOMContentLoaded", $(() => {
|
||||
AOS.init({
|
||||
once: true,
|
||||
duration: 1000,
|
||||
offset: 100,
|
||||
easing: 'ease-in-out',
|
||||
});
|
||||
}))
|
||||
|
||||
return (
|
||||
<div
|
||||
class="
|
||||
flex flex-col
|
||||
min-h-screen w-full overflow-x-clip
|
||||
bg-neutral-950 text-white
|
||||
">
|
||||
<Slot />
|
||||
</div>
|
||||
);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue