Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# dependencies
/node_modules
.pnpm-store
/.pnp
.pnp.js

Expand Down
30 changes: 30 additions & 0 deletions app/components/ambient-grain.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client";

type AmbientGrainProps = {
/** Marketing pages use a slightly stronger grain than the in-app shell (Orbit default). */
variant?: "marketing" | "app";
className?: string;
};

/** Orbit-style film grain / vignette wash (`@orbit/ui/ambient-grain`). */
export function AmbientGrain({ variant = "marketing", className = "" }: AmbientGrainProps) {
const density =
variant === "marketing"
? "opacity-[0.22] dark:opacity-[0.35]"
: "opacity-[0.14] dark:opacity-[0.28]";

return (
<div
aria-hidden
className={[
"pointer-events-none absolute inset-0 z-0 mix-blend-multiply dark:mix-blend-overlay",
density,
"[background-image:radial-gradient(1200px_600px_at_50%_-10%,rgba(0,0,0,0.07),transparent_60%),radial-gradient(800px_500px_at_80%_40%,rgba(0,0,0,0.035),transparent_60%)]",
"dark:[background-image:radial-gradient(1200px_600px_at_50%_-10%,rgba(255,255,255,0.08),transparent_60%),radial-gradient(800px_500px_at_80%_40%,rgba(255,255,255,0.04),transparent_60%)]",
className,
]
.filter(Boolean)
.join(" ")}
/>
);
}
22 changes: 22 additions & 0 deletions app/components/form-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { PropsWithChildren } from "react";

const labelClass =
"mb-1.5 block font-mono text-[11px] font-medium uppercase tracking-[0.2em] text-muted-foreground";

const shellClass =
"rounded-lg border border-border bg-background px-3 py-2.5 transition focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/40 focus-within:ring-offset-2 focus-within:ring-offset-background";

export function FormFieldLabel({
htmlFor,
children,
}: PropsWithChildren<{ htmlFor: string }>) {
return (
<label htmlFor={htmlFor} className={labelClass}>
{children}
</label>
);
}

export function FormFieldShell({ className = "", children }: PropsWithChildren<{ className?: string }>) {
return <div className={`${shellClass} ${className}`.trim()}>{children}</div>;
}
47 changes: 47 additions & 0 deletions app/components/github-stars-badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import Link from "next/link";
import { useEffect, useState } from "react";
import { headerChrome } from "@components/header-chrome";

function formatCompact(n: number) {
return Intl.NumberFormat("en-US", {
notation: "compact",
maximumFractionDigits: 1,
}).format(n);
}

export function GitHubStarsBadge() {
const [stars, setStars] = useState<number | null>(null);

useEffect(() => {
let cancelled = false;
fetch("https://api.github.com/repos/chronark/envshare")
.then((r) => r.json())
.then((json: { stargazers_count?: number }) => {
if (!cancelled && typeof json.stargazers_count === "number") {
setStars(json.stargazers_count);
}
})
.catch(() => {});
return () => {
cancelled = true;
};
}, []);

const label = stars != null ? formatCompact(stars) : "—";

return (
<Link
href="https://github.com/chronark/envshare"
target="_blank"
rel="noopener noreferrer"
className={headerChrome.surface}
>
<svg className="h-4 w-4 shrink-0" viewBox="0 0 16 16" fill="currentColor" aria-hidden>
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
</svg>
<span className="tabular-nums">{label}</span>
</Link>
);
}
17 changes: 17 additions & 0 deletions app/components/header-chrome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const headerChrome = {
ghost:
"inline-flex h-9 shrink-0 items-center justify-center rounded-lg border border-transparent bg-transparent px-3 text-sm font-medium text-muted-foreground transition hover:border-border hover:bg-muted/60 hover:text-foreground",
ghostActive:
"inline-flex h-9 shrink-0 items-center justify-center rounded-lg border border-border bg-muted px-3 text-sm font-medium text-foreground transition",
/** Primary app routes — matches ENVSHARE wordmark (mono, caps, tracking). */
navGhost:
"inline-flex h-9 shrink-0 items-center justify-center rounded-lg border border-transparent bg-transparent px-3 font-mono text-xs font-medium uppercase tracking-[0.2em] text-muted-foreground transition hover:border-border hover:bg-muted/60 hover:text-foreground",
navGhostActive:
"inline-flex h-9 shrink-0 items-center justify-center rounded-lg border border-border bg-muted px-3 font-mono text-xs font-medium uppercase tracking-[0.2em] text-foreground transition",
surface:
"inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-lg border border-border bg-background px-3 text-sm font-medium text-foreground shadow-sm transition hover:bg-muted/60",
surfaceIcon:
"inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border bg-background text-foreground shadow-sm transition hover:bg-muted/60",
primary:
"inline-flex h-9 shrink-0 items-center justify-center rounded-lg border border-primary bg-primary px-4 font-mono text-xs font-semibold uppercase tracking-[0.18em] text-primary-foreground shadow-sm transition hover:opacity-90",
} as const;
74 changes: 74 additions & 0 deletions app/components/landing-hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"use client";

import Link from "next/link";
import { ParticleField } from "@components/particle-field";

export function LandingHero() {
return (
<section className="relative z-10 mx-auto grid max-w-7xl grid-cols-1 items-center gap-10 px-6 pt-8 pb-16 md:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)] md:gap-16 md:px-12 md:pt-16 md:pb-24">
<div className="relative order-2 max-w-xl md:order-1">
<Link
href="https://github.com/chronark/envshare"
className="inline-flex w-fit items-center gap-2 rounded-full border border-border bg-muted px-3 py-1.5 font-mono text-[11px] font-medium uppercase leading-none tracking-[0.2em] text-muted-foreground transition hover:bg-accent"
>
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-emerald-500" aria-hidden />
Open source on GitHub
</Link>
<h1 className="mt-6 font-sans text-4xl font-medium leading-[1.05] tracking-tight text-foreground md:text-5xl lg:text-[56px]">
Share environment variables{" "}
<em className="not-italic text-muted-foreground">securely</em>
</h1>
<p className="mt-6 max-w-lg text-base leading-relaxed text-muted-foreground md:text-lg">
Your document is encrypted in your browser before being stored for a limited period of time and read
operations. Unencrypted data never leaves your browser.
</p>
<div className="mt-10 flex flex-wrap items-center gap-3">
<Link
href="/deploy"
className="inline-flex h-10 items-center justify-center rounded-lg border border-primary bg-primary px-5 text-sm font-semibold text-primary-foreground shadow-sm transition hover:opacity-90 md:h-11 md:px-6"
>
Deploy
</Link>
<Link
href="/share"
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-input bg-popover px-5 text-sm font-semibold text-foreground shadow-sm transition hover:bg-accent md:h-11 md:px-6"
>
Share
<span aria-hidden>→</span>
</Link>
</div>
<div className="mt-10 font-mono text-[11px] text-muted-foreground uppercase tracking-[0.2em]">
Simple · Secure · Private by design
</div>
</div>

<div className="relative order-1 h-[320px] md:order-2 md:h-[520px]">
<div className="absolute inset-0">
<ParticleField
src="/particle-padlock.png"
className="rounded-2xl"
sampleStep={2}
threshold={42}
dotSize={1.05}
mouseForce={75}
mouseRadius={120}
spring={0.032}
damping={0.88}
renderScale={0.92}
denseParticles
adaptToTheme
/>
</div>
{/* Vignette into page bg — in dark mode this layer sits on top of the canvas and would wash out light particles, so skip it. */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 rounded-2xl dark:hidden"
style={{
background:
"radial-gradient(65% 55% at 50% 50%, transparent 55%, hsl(var(--background) / 0.88) 100%)",
}}
/>
</div>
</section>
);
}
Loading