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
41 changes: 38 additions & 3 deletions apps/web/src/components/download-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,45 @@ import { Icon } from "@iconify-icon/react";

import { cn } from "@hypr/utils";

import { usePlatform } from "@/hooks/use-platform";

export function DownloadButton() {
const platform = usePlatform();

const getPlatformData = () => {
switch (platform) {
case "mac":
return {
icon: "mdi:apple",
label: "Download for Mac",
href: "/download/apple-silicon",
};
case "windows":
return {
icon: "mdi:microsoft-windows",
label: "Download for Windows",
href: "/download/windows",
};
case "linux":
return {
icon: "mdi:linux",
label: "Download for Linux",
href: "/download/linux",
};
default:
return {
icon: "mdi:apple",
label: "Download for Mac",
href: "/download/apple-silicon",
};
}
};

const { icon, label, href } = getPlatformData();

return (
<a
href="/download/apple-silicon"
href={href}
download
className={cn([
"group px-6 h-12 flex items-center justify-center text-base sm:text-lg",
Expand All @@ -14,8 +49,8 @@ export function DownloadButton() {
"transition-all",
])}
>
<Icon icon="mdi:apple" className="text-xl mr-2" />
Download for Mac
<Icon icon={icon} className="text-xl mr-2" />
{label}
</a>
);
}
65 changes: 65 additions & 0 deletions apps/web/src/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,71 @@ export function useGitHubStats() {
});
}

export interface Stargazer {
username: string;
avatar: string;
}

export function useGitHubStargazers(count: number = 100) {
return useQuery({
queryKey: ["github-stargazers", count],
queryFn: async (): Promise<Stargazer[]> => {
try {
const repoResponse = await fetch(
`https://api.github.com/repos/${ORG_REPO}`,
{
headers: {
Accept: "application/vnd.github.v3+json",
},
},
);
if (!repoResponse.ok) {
console.error(
`Failed to fetch repo info: ${repoResponse.status} ${repoResponse.statusText}`,
);
return [];
}
const repoData = await repoResponse.json();
const totalStars = repoData.stargazers_count ?? LAST_SEEN_STARS;

if (totalStars === 0) {
return [];
}

const perPage = Math.min(count, 100);
const lastPage = Math.ceil(totalStars / perPage);

const response = await fetch(
`https://api.github.com/repos/${ORG_REPO}/stargazers?per_page=${perPage}&page=${lastPage}`,
{
headers: {
Accept: "application/vnd.github.v3+json",
},
},
);
if (!response.ok) {
console.error(
`Failed to fetch stargazers: ${response.status} ${response.statusText}`,
);
return [];
}
const data = await response.json();
const stargazers = data.map(
(user: { login: string; avatar_url: string }) => ({
username: user.login,
avatar: user.avatar_url,
}),
);
return stargazers.reverse();
} catch (error) {
console.error("Error fetching stargazers:", error);
return [];
}
},
staleTime: 1000 * 60 * 60,
});
}

export const GITHUB_ORG_REPO = ORG_REPO;
export const GITHUB_LAST_SEEN_STARS = LAST_SEEN_STARS;
export const GITHUB_LAST_SEEN_FORKS = LAST_SEEN_FORKS;
42 changes: 42 additions & 0 deletions apps/web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ import { Route as ViewProductAiNotetakingRouteImport } from './routes/_view/prod
import { Route as ViewProductAiAssistantRouteImport } from './routes/_view/product/ai-assistant'
import { Route as ViewPressKitAppRouteImport } from './routes/_view/press-kit.app'
import { Route as ViewLegalSlugRouteImport } from './routes/_view/legal/$slug'
import { Route as ViewDownloadWindowsRouteImport } from './routes/_view/download/windows'
import { Route as ViewDownloadLinuxRouteImport } from './routes/_view/download/linux'
import { Route as ViewDownloadAppleSiliconRouteImport } from './routes/_view/download/apple-silicon'
import { Route as ViewDownloadAppleIntelRouteImport } from './routes/_view/download/apple-intel'
import { Route as ViewDocsSplatRouteImport } from './routes/_view/docs/$'
Expand Down Expand Up @@ -434,6 +436,16 @@ const ViewLegalSlugRoute = ViewLegalSlugRouteImport.update({
path: '/legal/$slug',
getParentRoute: () => ViewRouteRoute,
} as any)
const ViewDownloadWindowsRoute = ViewDownloadWindowsRouteImport.update({
id: '/download/windows',
path: '/download/windows',
getParentRoute: () => ViewRouteRoute,
} as any)
const ViewDownloadLinuxRoute = ViewDownloadLinuxRouteImport.update({
id: '/download/linux',
path: '/download/linux',
getParentRoute: () => ViewRouteRoute,
} as any)
const ViewDownloadAppleSiliconRoute =
ViewDownloadAppleSiliconRouteImport.update({
id: '/download/apple-silicon',
Expand Down Expand Up @@ -538,6 +550,8 @@ export interface FileRoutesByFullPath {
'/docs/$': typeof ViewDocsSplatRoute
'/download/apple-intel': typeof ViewDownloadAppleIntelRoute
'/download/apple-silicon': typeof ViewDownloadAppleSiliconRoute
'/download/linux': typeof ViewDownloadLinuxRoute
'/download/windows': typeof ViewDownloadWindowsRoute
'/legal/$slug': typeof ViewLegalSlugRoute
'/press-kit/app': typeof ViewPressKitAppRoute
'/product/ai-assistant': typeof ViewProductAiAssistantRoute
Expand Down Expand Up @@ -616,6 +630,8 @@ export interface FileRoutesByTo {
'/docs/$': typeof ViewDocsSplatRoute
'/download/apple-intel': typeof ViewDownloadAppleIntelRoute
'/download/apple-silicon': typeof ViewDownloadAppleSiliconRoute
'/download/linux': typeof ViewDownloadLinuxRoute
'/download/windows': typeof ViewDownloadWindowsRoute
'/legal/$slug': typeof ViewLegalSlugRoute
'/press-kit/app': typeof ViewPressKitAppRoute
'/product/ai-assistant': typeof ViewProductAiAssistantRoute
Expand Down Expand Up @@ -699,6 +715,8 @@ export interface FileRoutesById {
'/_view/docs/$': typeof ViewDocsSplatRoute
'/_view/download/apple-intel': typeof ViewDownloadAppleIntelRoute
'/_view/download/apple-silicon': typeof ViewDownloadAppleSiliconRoute
'/_view/download/linux': typeof ViewDownloadLinuxRoute
'/_view/download/windows': typeof ViewDownloadWindowsRoute
'/_view/legal/$slug': typeof ViewLegalSlugRoute
'/_view/press-kit/app': typeof ViewPressKitAppRoute
'/_view/product/ai-assistant': typeof ViewProductAiAssistantRoute
Expand Down Expand Up @@ -782,6 +800,8 @@ export interface FileRouteTypes {
| '/docs/$'
| '/download/apple-intel'
| '/download/apple-silicon'
| '/download/linux'
| '/download/windows'
| '/legal/$slug'
| '/press-kit/app'
| '/product/ai-assistant'
Expand Down Expand Up @@ -860,6 +880,8 @@ export interface FileRouteTypes {
| '/docs/$'
| '/download/apple-intel'
| '/download/apple-silicon'
| '/download/linux'
| '/download/windows'
| '/legal/$slug'
| '/press-kit/app'
| '/product/ai-assistant'
Expand Down Expand Up @@ -942,6 +964,8 @@ export interface FileRouteTypes {
| '/_view/docs/$'
| '/_view/download/apple-intel'
| '/_view/download/apple-silicon'
| '/_view/download/linux'
| '/_view/download/windows'
| '/_view/legal/$slug'
| '/_view/press-kit/app'
| '/_view/product/ai-assistant'
Expand Down Expand Up @@ -1480,6 +1504,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ViewLegalSlugRouteImport
parentRoute: typeof ViewRouteRoute
}
'/_view/download/windows': {
id: '/_view/download/windows'
path: '/download/windows'
fullPath: '/download/windows'
preLoaderRoute: typeof ViewDownloadWindowsRouteImport
parentRoute: typeof ViewRouteRoute
}
'/_view/download/linux': {
id: '/_view/download/linux'
path: '/download/linux'
fullPath: '/download/linux'
preLoaderRoute: typeof ViewDownloadLinuxRouteImport
parentRoute: typeof ViewRouteRoute
}
'/_view/download/apple-silicon': {
id: '/_view/download/apple-silicon'
path: '/download/apple-silicon'
Expand Down Expand Up @@ -1651,6 +1689,8 @@ interface ViewRouteRouteChildren {
ViewChangelogSlugRoute: typeof ViewChangelogSlugRoute
ViewDownloadAppleIntelRoute: typeof ViewDownloadAppleIntelRoute
ViewDownloadAppleSiliconRoute: typeof ViewDownloadAppleSiliconRoute
ViewDownloadLinuxRoute: typeof ViewDownloadLinuxRoute
ViewDownloadWindowsRoute: typeof ViewDownloadWindowsRoute
ViewLegalSlugRoute: typeof ViewLegalSlugRoute
ViewProductAiAssistantRoute: typeof ViewProductAiAssistantRoute
ViewProductAiNotetakingRoute: typeof ViewProductAiNotetakingRoute
Expand Down Expand Up @@ -1709,6 +1749,8 @@ const ViewRouteRouteChildren: ViewRouteRouteChildren = {
ViewChangelogSlugRoute: ViewChangelogSlugRoute,
ViewDownloadAppleIntelRoute: ViewDownloadAppleIntelRoute,
ViewDownloadAppleSiliconRoute: ViewDownloadAppleSiliconRoute,
ViewDownloadLinuxRoute: ViewDownloadLinuxRoute,
ViewDownloadWindowsRoute: ViewDownloadWindowsRoute,
ViewLegalSlugRoute: ViewLegalSlugRoute,
ViewProductAiAssistantRoute: ViewProductAiAssistantRoute,
ViewProductAiNotetakingRoute: ViewProductAiNotetakingRoute,
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/routes/_view/download/linux.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createFileRoute, redirect } from "@tanstack/react-router";

export const Route = createFileRoute("/_view/download/linux")({
beforeLoad: async () => {
throw redirect({
// TODO: needs to be fixed
href: "https://desktop2.hyprnote.com/download/latest/appimage?channel=nightly",
});
},
});
10 changes: 10 additions & 0 deletions apps/web/src/routes/_view/download/windows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createFileRoute, redirect } from "@tanstack/react-router";

export const Route = createFileRoute("/_view/download/windows")({
beforeLoad: async () => {
throw redirect({
// TODO: needs to be fixed
href: "https://desktop2.hyprnote.com/download/latest/msi?channel=nightly",
});
},
});
86 changes: 63 additions & 23 deletions apps/web/src/routes/_view/opensource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { createFileRoute, Link } from "@tanstack/react-router";

import { cn } from "@hypr/utils";

import { DownloadButton } from "@/components/download-button";
import { GitHubOpenSource } from "@/components/github-open-source";
import { SlashSeparator } from "@/components/slash-separator";
import { Stargazer, useGitHubStargazers } from "@/queries";

export const Route = createFileRoute("/_view/opensource")({
component: Component,
Expand Down Expand Up @@ -68,11 +70,67 @@ function Component() {
);
}

function StargazerAvatar({ stargazer }: { stargazer: Stargazer }) {
return (
<a
href={`https://github.com/${stargazer.username}`}
target="_blank"
rel="noopener noreferrer"
className="block size-8 rounded-sm overflow-hidden border border-neutral-200/50 bg-neutral-100 shrink-0 hover:scale-110 hover:border-neutral-400 hover:opacity-100 transition-all"
>
<img
src={stargazer.avatar}
alt={`${stargazer.username}'s avatar`}
className="w-full h-full object-cover"
loading="lazy"
/>
</a>
);
}

function StargazersGrid({ stargazers }: { stargazers: Stargazer[] }) {
const rows = 16;
const cols = 32;

return (
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute inset-0 flex flex-col justify-center gap-1 opacity-40 px-4">
{Array.from({ length: rows }).map((_, rowIndex) => (
<div key={rowIndex} className="flex gap-1 justify-center">
{Array.from({ length: cols }).map((_, colIndex) => {
const index = (rowIndex * cols + colIndex) % stargazers.length;
const stargazer = stargazers[index];
const delay = (rowIndex * cols + colIndex) * 0.05;

return (
<div
key={`${rowIndex}-${colIndex}`}
className="pointer-events-auto animate-fade-in-out"
style={{
animationDelay: `${delay}s`,
animationDuration: "3s",
}}
>
<StargazerAvatar stargazer={stargazer} />
</div>
);
})}
</div>
))}
</div>
</div>
);
}

function HeroSection() {
const { data: stargazers = [] } = useGitHubStargazers(500);

return (
<div className="bg-linear-to-b from-stone-50/30 to-stone-100/30">
<div className="px-6 py-12 lg:py-20">
<header className="mb-12 text-center max-w-4xl mx-auto">
<div className="bg-linear-to-b from-stone-50/30 to-stone-100/30 relative overflow-hidden">
{stargazers.length > 0 && <StargazersGrid stargazers={stargazers} />}
<div className="px-6 py-12 lg:py-20 relative z-10">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_800px_400px_at_50%_50%,white_0%,rgba(255,255,255,0.8)_40%,transparent_70%)] pointer-events-none" />
<header className="mb-12 text-center max-w-4xl mx-auto relative">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-serif text-stone-600 mb-6">
Built in the open,
<br />
Expand All @@ -97,16 +155,7 @@ function HeroSection() {
<Icon icon="mdi:github" className="text-lg" />
View on GitHub
</a>
<a
href="https://hyprnote.com/download"
className={cn([
"inline-flex items-center justify-center px-8 py-3 text-base font-medium rounded-full",
"bg-linear-to-t from-neutral-200 to-neutral-100 text-neutral-900 shadow-sm",
"hover:shadow-md hover:scale-[102%] active:scale-[98%] transition-all",
])}
>
Download for free
</a>
<DownloadButton />
</div>
</header>
</div>
Expand Down Expand Up @@ -464,16 +513,7 @@ function CTASection() {
today.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="https://hyprnote.com/download"
className={cn([
"px-8 py-3 text-base font-medium rounded-full",
"bg-linear-to-t from-stone-600 to-stone-500 text-white",
"hover:scale-105 active:scale-95 transition-transform",
])}
>
Download for free
</a>
<DownloadButton />
<Link
to="/product/local-ai"
className={cn([
Expand Down
Loading