mirror of
https://github.com/abue-ammar/tinycast.git
synced 2026-10-02 08:14:38 +08:00
fix web design
This commit is contained in:
+12
-6
@@ -9,7 +9,11 @@
|
||||
<title>Tinycast — a tiny, native macOS launcher</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Tinycast is a tiny, fully native macOS launcher: fuzzy app search, inline calculator, clipboard history, and global hotkeys. ~3 MB, no Electron, no account, no telemetry. Free and open source under AGPL-3.0."
|
||||
content="Tinycast is a tiny, fully native macOS launcher and a free, open-source Raycast alternative: fuzzy app search, inline calculator, clipboard history, and global hotkeys. A lighter take on Raycast, Alfred, Spotlight, and Supermac — ~3 MB, no Electron, no account, no telemetry. Free and open source under AGPL-3.0."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="Raycast alternative, Alfred alternative, Spotlight replacement, Supermac, macOS launcher, Mac app launcher, clipboard manager, fuzzy app search, global hotkeys, native SwiftUI launcher, open source launcher"
|
||||
/>
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="canonical" href="https://abue-ammar.github.io/tinycast/" />
|
||||
@@ -21,11 +25,11 @@
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Tinycast — a tiny, native macOS launcher"
|
||||
content="Tinycast — a tiny, native macOS launcher "
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Fuzzy app search, calculator, clipboard history, and global hotkeys in ~3 MB. Native, local, no telemetry. Free and open source."
|
||||
content="A free, open-source Raycast alternative for macOS: fuzzy app search, calculator, clipboard history, and global hotkeys in ~3 MB. A lighter take on Raycast, Alfred, and Spotlight. Native, local, no telemetry."
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
@@ -43,11 +47,11 @@
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Tinycast — a tiny, native macOS launcher"
|
||||
content="Tinycast — a tiny, native macOS launcher "
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Fuzzy app search, calculator, clipboard history, and global hotkeys in ~3 MB. Native, local, no telemetry. Free and open source."
|
||||
content="A free, open-source Raycast alternative for macOS: fuzzy app search, calculator, clipboard history, and global hotkeys in ~3 MB. A lighter take on Raycast, Alfred, and Spotlight. Native, local, no telemetry."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
@@ -65,7 +69,9 @@
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Tinycast",
|
||||
"description": "A tiny, fully native macOS launcher: fuzzy app search, inline calculator, clipboard history, and global hotkeys. ~3 MB, no Electron, no account, no telemetry.",
|
||||
"alternativeHeadline": "A free, open-source Raycast alternative for macOS",
|
||||
"keywords": "Raycast alternative, Alfred alternative, Spotlight replacement, Supermac, macOS launcher, clipboard manager, global hotkeys",
|
||||
"description": "A tiny, fully native macOS launcher and free Raycast alternative: fuzzy app search, inline calculator, clipboard history, and global hotkeys. ~3 MB, no Electron, no account, no telemetry.",
|
||||
"url": "https://abue-ammar.github.io/tinycast/",
|
||||
"image": "https://abue-ammar.github.io/tinycast/og.png",
|
||||
"applicationCategory": "UtilitiesApplication",
|
||||
|
||||
Generated
+33
-4
@@ -12,7 +12,8 @@
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"tailwind-merge": "^3.6.0"
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"yet-another-react-lightbox": "^3.32.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.7",
|
||||
@@ -1368,7 +1369,7 @@
|
||||
"version": "19.2.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
|
||||
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -1379,8 +1380,9 @@
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -1511,7 +1513,7 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
@@ -2105,6 +2107,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
||||
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -2366,6 +2369,32 @@
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yet-another-react-lightbox": {
|
||||
"version": "3.32.1",
|
||||
"resolved": "https://registry.npmjs.org/yet-another-react-lightbox/-/yet-another-react-lightbox-3.32.1.tgz",
|
||||
"integrity": "sha512-/OK5U79uTD2kGv3m+w22IDCuVtgC0l9J2SwJRYE+88XqfkWwVkAVNbE8AdWozAlPFTjGiIyPHxukoIINH9mpiw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/igordanchenko"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16 || ^17 || ^18 || ^19",
|
||||
"@types/react-dom": "^16 || ^17 || ^18 || ^19",
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19",
|
||||
"react-dom": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^3.6.0"
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"yet-another-react-lightbox": "^3.32.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.7",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Nav } from "./components/nav";
|
||||
import { Hero } from "./components/hero";
|
||||
import { Gallery } from "./components/gallery";
|
||||
import { Features } from "./components/features";
|
||||
import { Compare } from "./components/compare";
|
||||
import { Switch } from "./components/switch";
|
||||
@@ -14,6 +15,7 @@ function App() {
|
||||
<Nav />
|
||||
<main>
|
||||
<Hero />
|
||||
<Gallery />
|
||||
<Features />
|
||||
<Compare />
|
||||
<Switch />
|
||||
|
||||
@@ -51,7 +51,8 @@ export function Compare() {
|
||||
intro="Everything you actually reach for is here — then Tinycast wins on the things that don't show up in a feature list: size, price, and who owns your data."
|
||||
>
|
||||
<Reveal className="mx-auto max-w-4xl">
|
||||
<div className="overflow-x-auto rounded-2xl border border-border shadow-key">
|
||||
{/* Desktop: the aligned three-column table. */}
|
||||
<div className="hidden overflow-x-auto rounded-2xl border border-border shadow-key md:block">
|
||||
<div className="relative min-w-152">
|
||||
{/* The "winner column" — one continuous panel behind Tinycast. */}
|
||||
<div
|
||||
@@ -103,6 +104,39 @@ export function Compare() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile: one card per row, so nothing scrolls sideways. */}
|
||||
<div className="space-y-3 md:hidden">
|
||||
{compareRows.map((row) => (
|
||||
<div
|
||||
key={row.label}
|
||||
className="rounded-xl border border-border/50 p-4"
|
||||
>
|
||||
<div className="mb-3 flex items-center text-small text-ash">
|
||||
{row.label}
|
||||
{row.sourced && (
|
||||
<sup className="ml-0.5 text-violet-bright">†</sup>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex flex-col items-center gap-2 rounded-lg bg-violet/[0.07] px-3 py-3 text-center ring-1 ring-inset ring-violet/20">
|
||||
<span className="flex items-center gap-1.5 font-mono text-eyebrow uppercase text-smoke">
|
||||
<Logo size={13} />
|
||||
Tinycast
|
||||
</span>
|
||||
<Value value={row.tinycast} own />
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2 px-3 py-3 text-center">
|
||||
<span className="flex items-center gap-1.5 font-mono text-eyebrow uppercase text-smoke">
|
||||
<RaycastLogo size={12} />
|
||||
Raycast
|
||||
</span>
|
||||
<Value value={row.raycast} own={false} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* The one external claim, cited so it holds up. */}
|
||||
<p className="mt-4 text-center font-mono text-caption text-smoke">
|
||||
<span className="text-violet-bright">†</span> Stack and memory figures
|
||||
|
||||
@@ -10,7 +10,7 @@ export function Ethos() {
|
||||
id="why"
|
||||
eyebrow="Why it's tiny"
|
||||
title="Built like a Mac app should be."
|
||||
intro="SwiftUI and AppKit, zero dependencies, no Electron runtime hiding underneath. It's fast because there's barely anything to it."
|
||||
intro="Native SwiftUI and AppKit. No Electron, no bloat — fast because there's barely anything to it."
|
||||
>
|
||||
{/* Stat strip — the one place numbers get loud. */}
|
||||
<Reveal className="mb-10 md:mb-14">
|
||||
@@ -36,21 +36,19 @@ export function Ethos() {
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{values.map((value, i) => (
|
||||
<Reveal key={value.title} delay={i * 60}>
|
||||
<article className="flex h-full gap-4 rounded-2xl border border-border p-4 sm:p-6">
|
||||
<span className="mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full bg-violet/15 text-violet-bright">
|
||||
<Check size={15} strokeWidth={2} />
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="text-body-lg font-medium">{value.title}</h3>
|
||||
<p className="mt-1.5 text-body text-ash">{value.body}</p>
|
||||
</div>
|
||||
</article>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
<Reveal>
|
||||
<ul className="flex flex-wrap justify-center gap-2">
|
||||
{values.map((value) => (
|
||||
<li
|
||||
key={value}
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-white/5 px-3 py-2 text-small text-ash shadow-keycap"
|
||||
>
|
||||
<Check size={13} strokeWidth={2.4} className="text-violet-bright" />
|
||||
{value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Reveal>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { site } from "../data/site";
|
||||
import { GitHubLogo, Logo } from "./ui/icon";
|
||||
import { DiscordLogo, GitHubLogo, Logo } from "./ui/icon";
|
||||
import { MetaStrip } from "./ui/meta-strip";
|
||||
|
||||
export function Footer() {
|
||||
const repoPath = site.repo.replace("https://", "");
|
||||
const hasDiscord = site.community.discord !== "#";
|
||||
|
||||
return (
|
||||
<footer className="border-t border-border/50">
|
||||
@@ -23,15 +24,26 @@ export function Footer() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={site.repo}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center gap-2 text-small text-ash transition-colors hover:text-white"
|
||||
>
|
||||
<GitHubLogo size={16} />
|
||||
{repoPath}
|
||||
</a>
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-6 gap-y-3">
|
||||
<a
|
||||
href={site.repo}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center gap-2 text-small text-ash transition-colors hover:text-white"
|
||||
>
|
||||
<GitHubLogo size={16} />
|
||||
{repoPath}
|
||||
</a>
|
||||
<a
|
||||
href={site.community.discord}
|
||||
target={hasDiscord ? "_blank" : undefined}
|
||||
rel={hasDiscord ? "noreferrer" : undefined}
|
||||
className="flex items-center gap-2 text-small text-ash transition-colors hover:text-white"
|
||||
>
|
||||
<DiscordLogo size={16} />
|
||||
Join the Discord
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Play } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import Lightbox, { type Slide } from "yet-another-react-lightbox";
|
||||
import Captions from "yet-another-react-lightbox/plugins/captions";
|
||||
import Video from "yet-another-react-lightbox/plugins/video";
|
||||
import Zoom from "yet-another-react-lightbox/plugins/zoom";
|
||||
import "yet-another-react-lightbox/styles.css";
|
||||
import "yet-another-react-lightbox/plugins/captions.css";
|
||||
import { galleryItems, type GalleryItem } from "../data/gallery";
|
||||
import { Reveal } from "./ui/reveal";
|
||||
import { Section } from "./ui/section";
|
||||
|
||||
// Everything in `public/` is served under the Vite base path.
|
||||
const asset = (name: string) => `${import.meta.env.BASE_URL}${name}`;
|
||||
|
||||
// The grid thumbnail: an explicit thumb, else a video's poster, else the image.
|
||||
const tileImage = (item: GalleryItem) =>
|
||||
asset(item.thumb ?? (item.type === "video" ? (item.poster ?? item.src) : item.src));
|
||||
|
||||
// One gallery item → one lightbox slide. Images carry title/description for the
|
||||
// Captions plugin; videos use the Video plugin's `sources` shape.
|
||||
function toSlide(item: GalleryItem): Slide {
|
||||
if (item.type === "video") {
|
||||
return {
|
||||
type: "video",
|
||||
poster: item.poster ? asset(item.poster) : undefined,
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
title: item.title,
|
||||
description: item.caption,
|
||||
sources: [{ src: asset(item.src), type: "video/mp4" }],
|
||||
};
|
||||
}
|
||||
return {
|
||||
src: asset(item.src),
|
||||
title: item.title,
|
||||
description: item.caption,
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
};
|
||||
}
|
||||
|
||||
export function Gallery() {
|
||||
const [index, setIndex] = useState(-1);
|
||||
const slides = galleryItems.map(toSlide);
|
||||
|
||||
return (
|
||||
<Section
|
||||
id="gallery"
|
||||
eyebrow="Tinycast in action"
|
||||
title="See it in motion."
|
||||
intro="A palette that stays out of your way — until you need it."
|
||||
>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{galleryItems.map((item, i) => (
|
||||
<Reveal key={`${item.title}-${i}`} delay={i * 60}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIndex(i)}
|
||||
className="group flex h-full w-full flex-col gap-3 rounded-2xl bg-ink/40 p-3 text-left shadow-key transition-shadow duration-200 hover:shadow-key-hover"
|
||||
>
|
||||
<figure className="relative aspect-video w-full overflow-hidden rounded-xl">
|
||||
<img
|
||||
src={tileImage(item)}
|
||||
alt={item.title}
|
||||
className="block size-full object-cover transition-transform duration-300 group-hover:scale-[1.02]"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{item.type === "video" && (
|
||||
<span className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="flex size-12 items-center justify-center rounded-full bg-black/40 text-white shadow-highlight backdrop-blur-sm">
|
||||
<Play size={20} fill="currentColor" />
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</figure>
|
||||
<div className="px-1 pb-1">
|
||||
<h3 className="text-body-lg font-medium text-white">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-1 text-body text-ash">{item.caption}</p>
|
||||
</div>
|
||||
</button>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Lightbox
|
||||
open={index >= 0}
|
||||
index={index}
|
||||
close={() => setIndex(-1)}
|
||||
slides={slides}
|
||||
plugins={[Video, Captions, Zoom]}
|
||||
controller={{ closeOnBackdropClick: true }}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { site } from "../data/site";
|
||||
import { hero, site } from "../data/site";
|
||||
import { AppShot } from "./app-shot";
|
||||
import { Button } from "./ui/button";
|
||||
import { AppleLogo, GitHubLogo } from "./ui/icon";
|
||||
@@ -30,25 +30,21 @@ export function Hero() {
|
||||
className="rise font-mono text-eyebrow uppercase text-ash"
|
||||
style={{ animationDelay: "0ms" }}
|
||||
>
|
||||
Native macOS launcher
|
||||
{hero.eyebrow}
|
||||
</p>
|
||||
|
||||
<h1
|
||||
className="rise mt-6 max-w-3xl text-display"
|
||||
style={{ animationDelay: "80ms" }}
|
||||
>
|
||||
Your shortcut to
|
||||
<br className="hidden sm:block" /> everything on your Mac.
|
||||
{hero.headline}
|
||||
</h1>
|
||||
|
||||
<p
|
||||
className="rise mt-6 max-w-xl text-sm text-ash"
|
||||
className="rise mt-6 max-w-lg text-body-lg text-ash"
|
||||
style={{ animationDelay: "160ms" }}
|
||||
>
|
||||
{site.name} is a tiny, fully native launcher — fuzzy app search, a
|
||||
calculator, clipboard history, and global hotkeys. A few megabytes on
|
||||
disk, and it brings your Raycast setup along. No Electron, no account,
|
||||
no telemetry.
|
||||
{hero.sub}
|
||||
</p>
|
||||
|
||||
<div
|
||||
|
||||
@@ -18,8 +18,8 @@ export function Nav() {
|
||||
return (
|
||||
<header className="fixed inset-x-0 top-4 z-50">
|
||||
<div className="container-page">
|
||||
<nav className="rounded-xl border border-border bg-void-black/60 backdrop-blur-2xl">
|
||||
<div className="flex items-center justify-between gap-4 px-3 py-2">
|
||||
<nav className="rounded-2xl border border-white/8 bg-void-black/60 backdrop-blur-2xl">
|
||||
<div className="flex items-center justify-between gap-4 px-4 py-3">
|
||||
<a
|
||||
href="#top"
|
||||
onClick={close}
|
||||
|
||||
@@ -71,6 +71,21 @@ export function RaycastLogo({ size = 18, ...props }: BrandIconProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function DiscordLogo({ size = 16, ...props }: BrandIconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M20.317 4.369a19.79 19.79 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.211.375-.445.865-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.6 12.6 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.74 19.74 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.1 13.1 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.291a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.009c.12.099.246.198.373.292a.077.077 0 0 1-.006.127 12.3 12.3 0 0 1-1.873.891.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.84 19.84 0 0 0 6.002-3.03.077.077 0 0 0 .032-.055c.5-5.177-.838-9.674-3.548-13.66a.06.06 0 0 0-.031-.028ZM8.02 15.331c-1.182 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418Zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function GitHubLogo({ size = 16, ...props }: BrandIconProps) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
import { site } from "./site";
|
||||
|
||||
// The values that make Tinycast Tinycast. Each is a plain promise, not a pitch.
|
||||
// The values that make Tinycast Tinycast — each a plain promise, one line each.
|
||||
export const values = [
|
||||
{
|
||||
title: "Free and open source",
|
||||
body: `Every line is public under the ${site.license} license. Read it, fork it, build it yourself.`,
|
||||
},
|
||||
{
|
||||
title: "Local by design",
|
||||
body: "Your apps, clipboard, and shortcuts stay on your Mac. Nothing is uploaded, ever.",
|
||||
},
|
||||
{
|
||||
title: "No account, no sign-in",
|
||||
body: "Install it and it works. There's no login, no paywall, and no pro tier.",
|
||||
},
|
||||
{
|
||||
title: "Zero telemetry",
|
||||
body: "No analytics, no crash pings, no background phone-home. It does nothing you didn't ask for.",
|
||||
},
|
||||
"Free & open source",
|
||||
"Local by design",
|
||||
"No account, no sign-in",
|
||||
"Zero telemetry",
|
||||
] as const;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Drives the "Tinycast in action" gallery + lightbox. Each item is a tile in
|
||||
// the grid and a slide in the lightbox. `src`/`thumb`/`poster` are resolved
|
||||
// against import.meta.env.BASE_URL in the component, so give plain filenames
|
||||
// that live in `public/` (e.g. "screenshot.png").
|
||||
//
|
||||
// DUMMY CONTENT: these reuse the two screenshots we already ship so the grid
|
||||
// looks full. Swap in real captures/clips later. To add a clip, use the video
|
||||
// shape shown in the commented template at the bottom — the lightbox already
|
||||
// loads the video plugin.
|
||||
|
||||
export type GalleryItem = {
|
||||
type: "image" | "video";
|
||||
// Full-size media shown in the lightbox (image src, or video file for clips).
|
||||
src: string;
|
||||
// Grid thumbnail; falls back to `src`/`poster` when omitted.
|
||||
thumb?: string;
|
||||
// Poster frame for video tiles/slides.
|
||||
poster?: string;
|
||||
title: string;
|
||||
caption: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export const galleryItems: GalleryItem[] = [
|
||||
{
|
||||
type: "image",
|
||||
src: "screenshot.png",
|
||||
title: "App launcher",
|
||||
caption: "Fuzzy-search every app and open it with a keystroke.",
|
||||
width: 2451,
|
||||
height: 1510,
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
src: "import.png",
|
||||
title: "Import from Raycast",
|
||||
caption: "Bring your whole setup across in one click.",
|
||||
width: 1800,
|
||||
height: 1192,
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
src: "screenshot.png",
|
||||
title: "Clipboard history",
|
||||
caption: "Text and images, full-text searchable, always local.",
|
||||
width: 2451,
|
||||
height: 1510,
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
src: "import.png",
|
||||
title: "Inline calculator",
|
||||
caption: "Math and unit conversions right in the palette.",
|
||||
width: 1800,
|
||||
height: 1192,
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
src: "screenshot.png",
|
||||
title: "Emoji & symbols",
|
||||
caption: "Search the full set; your most-used float to the top.",
|
||||
width: 2451,
|
||||
height: 1510,
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
src: "import.png",
|
||||
title: "Hyper key",
|
||||
caption: "Turn Caps Lock into a whole extra shortcut layer.",
|
||||
width: 1800,
|
||||
height: 1192,
|
||||
},
|
||||
];
|
||||
|
||||
// Video template — drop the .mp4 (and a poster frame) into `public/`, then copy
|
||||
// an entry like this into the array above:
|
||||
//
|
||||
// {
|
||||
// type: "video",
|
||||
// src: "demo.mp4",
|
||||
// poster: "demo-poster.png",
|
||||
// title: "Live demo",
|
||||
// caption: "Watch the launcher in motion.",
|
||||
// width: 1920,
|
||||
// height: 1080,
|
||||
// },
|
||||
@@ -9,9 +9,21 @@ export const site = {
|
||||
platform: "macOS 26+",
|
||||
license: "AGPL-3.0",
|
||||
licenseUrl: "https://github.com/abue-ammar/tinycast/blob/main/LICENSE",
|
||||
// Community. Replace the placeholder with the real Discord invite when ready.
|
||||
community: {
|
||||
discord: "#",
|
||||
},
|
||||
} as const;
|
||||
|
||||
// The hero, in as few words as possible — headline plus one punchy line.
|
||||
export const hero = {
|
||||
eyebrow: "Native macOS launcher",
|
||||
headline: "Everything on your Mac. One keystroke away.",
|
||||
sub: "A tiny, native launcher. No Electron. No account. No telemetry. No bullshit.",
|
||||
} as const;
|
||||
|
||||
export const nav = [
|
||||
{ label: "Gallery", href: "#gallery" },
|
||||
{ label: "Features", href: "#features" },
|
||||
{ label: "Compare", href: "#compare" },
|
||||
{ label: "Why tiny", href: "#why" },
|
||||
|
||||
+10
-1
@@ -140,10 +140,19 @@
|
||||
|
||||
/* Anchor targets land below the floating nav instead of underneath it. */
|
||||
section[id] {
|
||||
scroll-margin-top: 5.5rem;
|
||||
scroll-margin-top: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lightbox (yet-another-react-lightbox) — tint its dark chrome to match the
|
||||
page. YARL's own classes are namespaced (yarl__), so nothing here leaks. */
|
||||
.yarl__root {
|
||||
--yarl__color_backdrop: rgb(4 5 6 / 0.94);
|
||||
--yarl__color_button: var(--color-ash);
|
||||
--yarl__color_button_active: var(--color-white);
|
||||
--yarl__slide_captions_container_background: rgb(4 5 6 / 0.6);
|
||||
}
|
||||
|
||||
/* The page gutter: 16px on mobile, 24px from the sm breakpoint up. */
|
||||
@utility container-page {
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user