Skip to content

feat: add custom ghost logo branding throughout the site #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 7, 2025
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
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# 👻 GhostPaste
<div align="center">
<img src="assets/logo.svg" alt="GhostPaste Logo" width="128" height="128">

# GhostPaste

### Zero-knowledge encrypted code sharing platform where your secrets stay secret

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Built with Next.js](https://img.shields.io/badge/Built%20with-Next.js%2015-black)](https://nextjs.org)
[![Deployed on Cloudflare](https://img.shields.io/badge/Deployed%20on-Cloudflare-orange)](https://cloudflare.com)

[Live Demo](https://ghostpaste.dev) • [Report Bug](https://github.com/nullcoder/ghostpaste/issues) • [Request Feature](https://github.com/nullcoder/ghostpaste/issues)
</div>

> Zero-knowledge encrypted code sharing platform where your secrets stay secret

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
---

## 🔐 What is GhostPaste?

Expand Down Expand Up @@ -253,3 +263,10 @@ This project demonstrates that AI can be more than just a coding assistant - it
<a href="https://github.com/nullcoder/ghostpaste/issues">Report Bug</a> •
<a href="https://github.com/nullcoder/ghostpaste/issues">Request Feature</a>
</p>

<div align="center">
<br>
<img src="assets/logo.svg" alt="GhostPaste" width="64" height="64">
<br>
<sub>Share code. Keep secrets. Stay ghosted. 👻</sub>
</div>
Binary file added app/apple-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions app/apple-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/favicon.ico
Binary file not shown.
2 changes: 2 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@

:root {
--radius: 0.625rem;
--ghost-brand: #6366f1;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
Expand Down Expand Up @@ -157,6 +158,7 @@
}

.dark {
--ghost-brand: #818cf8;
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
Expand Down
Binary file added app/icon-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions app/icon-16.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/icon-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions app/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Ghost, Home, Plus } from "lucide-react";
import { Home, Plus } from "lucide-react";
import { GhostLogo } from "@/components/ghost-logo";

export default function NotFound() {
return (
<div className="container mx-auto flex min-h-[80vh] flex-col items-center justify-center px-4 text-center">
{/* Floating Ghost */}
<div className="animate-float mb-8">
<Ghost className="text-muted-foreground/50 h-32 w-32" />
<GhostLogo size="2xl" className="text-indigo-500/50" />
</div>

{/* 404 Text with Glitch Effect */}
Expand Down
14 changes: 14 additions & 0 deletions assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from "react";
import Link from "next/link";
import { Container } from "@/components/ui/container";
import { cn } from "@/lib/utils";
import { Ghost } from "lucide-react";
import { GhostLogo } from "@/components/ghost-logo";

export interface FooterProps {
/**
Expand Down Expand Up @@ -31,7 +31,7 @@ export function Footer({ className, buildId }: FooterProps) {
{/* Left section - Branding */}
<div className="space-y-2 text-center md:text-left">
<div className="flex items-center justify-center gap-2 md:justify-start">
<Ghost className="h-6 w-6" aria-hidden="true" />
<GhostLogo className="text-indigo-600/80 dark:text-indigo-400/80" />
<span className="text-lg font-semibold">GhostPaste</span>
</div>
<p className="text-muted-foreground text-sm">
Expand Down
65 changes: 65 additions & 0 deletions components/ghost-logo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import { GhostLogo } from "./ghost-logo";

describe("GhostLogo", () => {
it("renders correctly with default props", () => {
const { container } = render(<GhostLogo />);
const svg = container.querySelector("svg");

expect(svg).toBeInTheDocument();
expect(svg).toHaveAttribute("viewBox", "0 0 32 32");
expect(svg).toHaveClass("h-6", "w-6");
});

it("applies custom className", () => {
const { container } = render(<GhostLogo className="text-red-500" />);
const svg = container.querySelector("svg");

expect(svg).toHaveClass("text-red-500");
});

it("applies correct size classes", () => {
const { container: smallContainer } = render(<GhostLogo size="sm" />);
const { container: mediumContainer } = render(<GhostLogo size="md" />);
const { container: largeContainer } = render(<GhostLogo size="lg" />);

expect(smallContainer.querySelector("svg")).toHaveClass("h-4", "w-4");
expect(mediumContainer.querySelector("svg")).toHaveClass("h-6", "w-6");
expect(largeContainer.querySelector("svg")).toHaveClass("h-8", "w-8");
});

it("contains ghost body path", () => {
const { container } = render(<GhostLogo />);
const path = container.querySelector("path");

expect(path).toBeInTheDocument();
expect(path).toHaveAttribute("fill", "currentColor");
});

it("contains bracket eyes", () => {
const { container } = render(<GhostLogo />);
const textElements = container.querySelectorAll("text");

expect(textElements).toHaveLength(2);
expect(textElements[0]).toHaveTextContent("<");
expect(textElements[1]).toHaveTextContent(">");
});

it("contains binary dots", () => {
const { container } = render(<GhostLogo />);
const circles = container.querySelectorAll("circle");

expect(circles).toHaveLength(3);
circles.forEach((circle) => {
expect(circle).toHaveAttribute("fill", "white");
});
});

it("has proper accessibility attributes", () => {
const { container } = render(<GhostLogo />);
const svg = container.querySelector("svg");

expect(svg).toHaveAttribute("aria-hidden", "true");
});
});
59 changes: 59 additions & 0 deletions components/ghost-logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { cn } from "@/lib/utils";

interface GhostLogoProps {
className?: string;
size?: "sm" | "md" | "lg" | "xl" | "2xl";
}

export function GhostLogo({ className, size = "md" }: GhostLogoProps) {
const sizeClasses = {
sm: "h-4 w-4",
md: "h-6 w-6",
lg: "h-8 w-8",
xl: "h-16 w-16",
"2xl": "h-32 w-32",
};

return (
<svg
className={cn(sizeClasses[size], className)}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
{/* Main ghost body */}
<path
d="M16 6c-4.4 0-8 3.6-8 8v9l1.5 2.5c0.3 0.5 1 0.3 1.3-0.2L12 23.8c0.2-0.3 0.6-0.3 0.8 0L14 25.3c0.2 0.3 0.6 0.3 0.8 0L16 23.8c0.2-0.3 0.6-0.3 0.8 0L18 25.3c0.2 0.3 0.6 0.3 0.8 0L20 23.8c0.2-0.3 0.6-0.2 1.2 0.2L22.5 25.5c0.3 0.5 1 0.3 1.3-0.2L24 23V14c0-4.4-3.6-8-8-8Z"
fill="currentColor"
/>

{/* Code bracket eyes */}
<text
x="12"
y="18"
fontFamily="monospace"
fontSize="8"
fill="white"
textAnchor="middle"
>
&lt;
</text>
<text
x="20"
y="18"
fontFamily="monospace"
fontSize="8"
fill="white"
textAnchor="middle"
>
&gt;
</text>

{/* Binary dots for tech aesthetic */}
<circle cx="14" cy="20" r="0.8" fill="white" opacity="0.8" />
<circle cx="16" cy="20" r="0.8" fill="white" opacity="0.6" />
<circle cx="18" cy="20" r="0.8" fill="white" opacity="0.8" />
</svg>
);
}
13 changes: 10 additions & 3 deletions components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import { useState } from "react";
import Link from "next/link";
import { Menu, Ghost, Keyboard } from "lucide-react";
import { Menu, Keyboard } from "lucide-react";
import { GithubIcon } from "@/components/icons/github-icon";
import { GhostLogo } from "@/components/ghost-logo";
import {
NavigationMenu,
NavigationMenuList,
Expand Down Expand Up @@ -56,7 +57,10 @@ export function Header() {
href="/"
className="flex items-center gap-2 text-lg font-semibold transition-opacity hover:opacity-80"
>
<Ghost className="h-6 w-6" aria-hidden="true" />
<GhostLogo
size="lg"
className="text-indigo-600 dark:text-indigo-400"
/>
<span>GhostPaste</span>
</Link>

Expand Down Expand Up @@ -129,7 +133,10 @@ export function Header() {
>
<SheetHeader className="p-6 pb-0">
<SheetTitle className="flex items-center gap-2 text-lg">
<Ghost className="h-5 w-5" aria-hidden="true" />
<GhostLogo
size="sm"
className="text-indigo-600 dark:text-indigo-400"
/>
<span>GhostPaste</span>
</SheetTitle>
</SheetHeader>
Expand Down