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
6 changes: 2 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,5 @@ JWT_SECRET=""
# Free tier: use a key ending in :fx | Paid tier: standard key
DEEPL_API_KEY=""

# status.nodebyte.host public status API — admin token unlocks hidden monitors
# and bypasses the shared CDN cache for fresher data.
STATUS_API_URL="https://status.nodebyte.host"
STATUS_TOKEN=""
# nodebytestat.us public status API — no auth required.
STATUS_API_URL="https://nodebytestat.us"
208 changes: 140 additions & 68 deletions app/_og/image-generator.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ReactNode } from 'react'
import { ImageResponse } from 'next/og'

export const OG_SIZE = { width: 1200, height: 630 } as const
Expand Down Expand Up @@ -25,6 +26,11 @@ export const OG_CONFIGS = {
description: 'Minecraft, Rust, Hytale and more — one-click deployment with mod support included.',
features: ['Minecraft', 'Rust', 'Hytale', 'Mod Support', 'DDoS Protected'],
},
brand: {
headline: ['Brand &', 'Press Kit.'] as const,
description: 'Logo files, color palettes, and usage guidelines for partners and press.',
features: ['Logo Assets', '47 Themes', 'Templates', 'Usage Guide'],
},
} satisfies Record<string, OGConfig>

// ─── Logo path data ────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -53,74 +59,98 @@ function LogoMark({ size, clipId }: { size: number; clipId: string }) {
)
}

export function makeOGImage(config: OGConfig): ImageResponse {
return new ImageResponse(
(
/** The atmospheric layer shared by every generated image — bg color, radial glows, top accent bar, ghost logo watermark, bottom URL. */
function OGBackdrop({ clipId, children }: { clipId: string; children?: ReactNode }) {
return (
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
position: 'relative',
background: '#040d1a',
overflow: 'hidden',
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
}}
>
{/* Blue radial glow — top right */}
<div
style={{
width: '100%',
height: '100%',
position: 'absolute',
top: -180,
right: -80,
width: 600,
height: 600,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(59,130,246,0.14) 0%, transparent 65%)',
display: 'flex',
position: 'relative',
background: '#040d1a',
overflow: 'hidden',
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
}}
>
{/* Blue radial glow — top right */}
<div
style={{
position: 'absolute',
top: -180,
right: -80,
width: 600,
height: 600,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(59,130,246,0.14) 0%, transparent 65%)',
display: 'flex',
}}
/>
/>

{/* Purple radial glow — bottom left */}
<div
style={{
position: 'absolute',
bottom: -180,
left: -60,
width: 500,
height: 500,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(91,0,239,0.08) 0%, transparent 65%)',
display: 'flex',
}}
/>
{/* Purple radial glow — bottom left */}
<div
style={{
position: 'absolute',
bottom: -180,
left: -60,
width: 500,
height: 500,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(91,0,239,0.08) 0%, transparent 65%)',
display: 'flex',
}}
/>

{/* Top accent bar */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: 'linear-gradient(90deg, #3b82f6 0%, #5b00ef 50%, #803cee 100%)',
display: 'flex',
}}
/>
{/* Top accent bar */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: 'linear-gradient(90deg, #3b82f6 0%, #5b00ef 50%, #803cee 100%)',
display: 'flex',
}}
/>

{/* Ghost logo watermark — right side */}
<div
style={{
position: 'absolute',
right: -90,
top: 75,
display: 'flex',
opacity: 0.055,
}}
>
<LogoMark size={480} clipId="og-lg-clip" />
</div>
{/* Ghost logo watermark — right side */}
<div
style={{
position: 'absolute',
right: -90,
top: 75,
display: 'flex',
opacity: 0.055,
}}
>
<LogoMark size={480} clipId={clipId} />
</div>

{/* Bottom — URL */}
<div
style={{
position: 'absolute',
bottom: 32,
left: 80,
color: 'rgba(58,90,120,0.38)',
fontSize: 13,
letterSpacing: 1.5,
display: 'flex',
}}
>
nodebyte.host
</div>

{children}
</div>
)
}

export function makeOGImage(config: OGConfig): ImageResponse {
return new ImageResponse(
(
<OGBackdrop clipId="og-lg-clip">
{/* Main content */}
<div
style={{
Expand Down Expand Up @@ -248,22 +278,64 @@ export function makeOGImage(config: OGConfig): ImageResponse {
))}
</div>
</div>
</OGBackdrop>
),
{
width: OG_SIZE.width,
height: OG_SIZE.height,
},
)
}

{/* Bottom — URL */}
/**
* A blank branded canvas — same backdrop (glows, top accent bar, ghost logo
* watermark) as the real OG images, but with no headline/description/feature
* copy baked in. Downloadable from /brand as a starting template partners
* and press can drop their own text onto.
*/
export function makeOGBackgroundTemplate(): ImageResponse {
return new ImageResponse(
(
<OGBackdrop clipId="og-template-clip">
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 16,
position: 'absolute',
bottom: 32,
top: 56,
left: 80,
color: 'rgba(58,90,120,0.38)',
fontSize: 13,
letterSpacing: 1.5,
display: 'flex',
}}
>
nodebyte.host
<LogoMark size={50} clipId="og-template-sm-clip" />
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<span
style={{
color: '#e4f0ff',
fontSize: 15,
fontWeight: 700,
letterSpacing: 4,
textTransform: 'uppercase',
display: 'flex',
}}
>
NodeByte Hosting
</span>
<span
style={{
color: '#2a4460',
fontSize: 13,
fontWeight: 400,
fontStyle: 'italic',
letterSpacing: 0.2,
display: 'flex',
}}
>
Built for Humans. Powered by Bytes.
</span>
</div>
</div>
</div>
</OGBackdrop>
),
{
width: OG_SIZE.width,
Expand Down
6 changes: 1 addition & 5 deletions app/api/status/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { NextResponse } from "next/server"
import { computeLatencyStats, fetchStatusSnapshot, type MonitorStatus, type MonitorType } from "@/packages/core/lib/status"
import { computeLatencyStats, fetchStatusSnapshot, type MonitorStatus } from "@/packages/core/lib/status"

export const revalidate = 30

export interface StatusApiMonitor {
name: string
type: MonitorType
groupName: string | null
subgroupName: string | null
status: MonitorStatus
uptime30dPct: number | null
latency: { fast: number; avg: number; slow: number } | null
Expand All @@ -32,9 +30,7 @@ export async function GET() {

const monitors: StatusApiMonitor[] = snapshot.monitors.map((m) => ({
name: m.name,
type: m.type,
groupName: m.group_name,
subgroupName: m.subgroup_name,
status: m.status,
uptime30dPct: m.uptime_30d_pct,
latency: computeLatencyStats(m),
Expand Down
5 changes: 5 additions & 0 deletions app/brand/background/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { makeOGBackgroundTemplate } from '../../_og/image-generator'

export async function GET() {
return makeOGBackgroundTemplate()
}
9 changes: 9 additions & 0 deletions app/brand/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { OG_CONFIGS, OG_CONTENT_TYPE, OG_SIZE, makeOGImage } from '../_og/image-generator'

export const alt = 'NodeByte Hosting — Brand & Press Kit'
export const size = OG_SIZE
export const contentType = OG_CONTENT_TYPE

export default function Image() {
return makeOGImage(OG_CONFIGS.brand)
}
11 changes: 11 additions & 0 deletions app/brand/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BrandPage } from "@/packages/ui/components/Layouts/Brand/brand-page"
import type { Metadata } from "next"

export const metadata: Metadata = {
title: "Brand & Press Kit",
description: "Logo files, color palettes, and usage guidelines for partners, press, and anyone writing about NodeByte Hosting.",
}

export default function Brand() {
return <BrandPage />
}
9 changes: 9 additions & 0 deletions app/brand/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { OG_CONFIGS, OG_CONTENT_TYPE, OG_SIZE, makeOGImage } from '../_og/image-generator'

export const alt = 'NodeByte Hosting — Brand & Press Kit'
export const size = OG_SIZE
export const contentType = OG_CONTENT_TYPE

export default function Image() {
return makeOGImage(OG_CONFIGS.brand)
}
4 changes: 2 additions & 2 deletions app/dedicated/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export const metadata: Metadata = {

export default async function DedicatedPage() {
const hub = await getCategoryHub(DEDICATED_HUB_SLUGS)
const children = hub?.children ?? []
const categorySlugs = hub?.children.map((c) => c.slug) ?? []

const plansByCategory = await Promise.all(children.map((c) => getDedicatedPlans(c.slug)))
const plansByCategory = await Promise.all(categorySlugs.map((slug) => getDedicatedPlans(slug)))
const plans = plansByCategory.flat()

return <DedicatedHub plans={plans} />
Expand Down
21 changes: 21 additions & 0 deletions app/object-storage/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Metadata } from "next"
import { ObjectStorageHub } from "@/packages/ui/components/Layouts/ObjectStorage/object-storage-hub"
import { getObjectStoragePlans } from "@/packages/core/products/billing-service"
import { getCategoryHub } from "@/packages/core/lib/bytepay"
import { OBJECT_STORAGE_HUB_SLUGS } from "@/packages/core/constants/catalog-hubs"

export const metadata: Metadata = {
title: "Object Storage",
description:
"S3-compatible object storage with generous free egress, self-service access keys, and 99.99% enterprise-grade reliability.",
}

export default async function ObjectStoragePage() {
const hub = await getCategoryHub(OBJECT_STORAGE_HUB_SLUGS)
const categorySlugs = hub?.children.map((c) => c.slug) ?? []

const plansByCategory = await Promise.all(categorySlugs.map((slug) => getObjectStoragePlans(slug)))
const plans = plansByCategory.flat()

return <ObjectStorageHub plans={plans} />
}
4 changes: 2 additions & 2 deletions app/vps/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export const metadata: Metadata = {

export default async function VpsPage() {
const hub = await getCategoryHub(VPS_HUB_SLUGS)
const children = hub?.children ?? []
const categorySlugs = hub?.children.map((c) => c.slug) ?? []

const plansByCategory = await Promise.all(children.map((c) => getVpsPlans(c.slug)))
const plansByCategory = await Promise.all(categorySlugs.map((slug) => getVpsPlans(slug)))
const plans = plansByCategory.flat()

return <VpsHub plans={plans} />
Expand Down
1 change: 1 addition & 0 deletions packages/core/constants/catalog-hubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
export const GAME_HUB_SLUGS = ["game-servers", "games"]
export const VPS_HUB_SLUGS = ["vps-hosting", "vps", "vps-servers"]
export const DEDICATED_HUB_SLUGS = ["dedicated-servers", "dedicated", "dedi"]
export const OBJECT_STORAGE_HUB_SLUGS = ["object-storage", "storage"]
2 changes: 1 addition & 1 deletion packages/core/constants/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const LINKS = {
githubDiscussions: "https://github.com/orgs/NodeByteHosting/discussions",
twitter: "https://twitter.com/NodeByteHosting",
trustpilot: "https://uk.trustpilot.com/review/nodebyte.host",
status: "https://status.nodebyte.host",
status: "https://nodebytestat.us",
network: "https://lg.nodebyte.host",
contact: "/contact",
billing: {
Expand Down
Loading