diff --git a/apps/shipyard/.gitignore b/apps/shipyard/.gitignore deleted file mode 100644 index cccfd3167..000000000 --- a/apps/shipyard/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# fumadocs -.source \ No newline at end of file diff --git a/apps/shipyard/README.md b/apps/shipyard/README.md deleted file mode 100644 index 723879805..000000000 --- a/apps/shipyard/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# shipyard - -Shipyard's website and docs at [coss.com/shipyard](https://coss.com/shipyard): -the landing page, and the docs at `/shipyard/docs` on the same Fumadocs setup -as coss ui's. - -- `app/page.tsx`, `app/landing.css`: the landing page, as it was at - berthd.app. The harbour at the top is dithered on a canvas and comes alive - at night (`public/assets/harbour.js`, `hero-loop.js`); the chapters' loops - and line scenes load once the page has painted (`clips.js`, - `scenes.css`/`scenes.json`). `components/landing/runtime.tsx` brings them - in. The page keeps its own type (Inter, JetBrains Mono, in `fonts/`) and - tokens; the header, the rails and the docs are coss's. -- `content/docs/`: the docs, moved here from the `cosscom/shipyard` - repository's `docs/` folder. `components/docs/` are its own pieces - (callouts, the stage, the path of cards, screenshots in `public/shots/`, - diagrams and the line scenes); `lib/rehype-reference.ts` makes the - reference tables linkable. -- The app's downloads, the live demo (`berthd.app/demo/`), the box's - install line (`berthd.app/install`), the plugin listing and the - **Review in Shipyard** bounce page stay on berthd.app: they are built and - published from the `cosscom/shipyard` repository. - -```sh -bun install -bun run dev --filter=shipyard # http://localhost:4100/shipyard -``` diff --git a/apps/shipyard/app/api/raw/[...slug]/route.ts b/apps/shipyard/app/api/raw/[...slug]/route.ts deleted file mode 100644 index b2eb6fbaf..000000000 --- a/apps/shipyard/app/api/raw/[...slug]/route.ts +++ /dev/null @@ -1,50 +0,0 @@ -import fs from "node:fs/promises"; -import path from "node:path"; -import { type NextRequest, NextResponse } from "next/server"; - -export async function GET( - _request: NextRequest, - { params }: { params: Promise<{ slug: string[] }> }, -) { - const { slug } = await params; - - // Construct the file path from the slug - // If we have ['docs', 'filename'], look in (root) directory - let filePath: string; - if (slug.length === 2 && slug[0] === "docs") { - filePath = path.join( - process.cwd(), - "content", - "docs", - "(root)", - slug[1] || "", - ); - } else { - filePath = path.join(process.cwd(), "content", ...slug); - } - - // Check for both .mdx and .md extensions - const mdxPath = filePath.endsWith(".mdx") ? filePath : `${filePath}.mdx`; - const mdPath = filePath.endsWith(".md") ? filePath : `${filePath}.md`; - - try { - let content: string; - - // Try .mdx first, then .md - try { - content = await fs.readFile(mdxPath, "utf-8"); - } catch { - content = await fs.readFile(mdPath, "utf-8"); - } - - // Return raw markdown with appropriate headers - return new NextResponse(content, { - headers: { - "Cache-Control": "public, max-age=3600", - "Content-Type": "text/plain; charset=utf-8", - }, - }); - } catch { - return NextResponse.json({ error: "File not found" }, { status: 404 }); - } -} diff --git a/apps/shipyard/app/apple-icon.png b/apps/shipyard/app/apple-icon.png deleted file mode 100644 index 4402aaa8e..000000000 Binary files a/apps/shipyard/app/apple-icon.png and /dev/null differ diff --git a/apps/shipyard/app/docs.css b/apps/shipyard/app/docs.css deleted file mode 100644 index 1489493c2..000000000 --- a/apps/shipyard/app/docs.css +++ /dev/null @@ -1,325 +0,0 @@ -/* Shipyard's own pieces in the docs (components/docs): callouts, the stage - on the introduction, the numbered path of cards, screenshots, and the - reference tables lib/rehype-reference.ts makes linkable. The brand's rules: - one outline all round, never a coloured stripe; amber only for what needs - you. Tokens are coss ui's. */ - -:root { - --berth-amber: #c27a12; - --berth-fg-2: color-mix( - in srgb, - var(--foreground) 78%, - var(--muted-foreground) - ); - --berth-code-bg: var(--muted); - --berth-border-soft: color-mix(in srgb, var(--border) 64%, transparent); -} -.dark { - --berth-amber: #e8a33d; -} - -.berth-callout { - display: flex; - gap: 12px; - margin: 1.25rem 0; - padding: 12px 16px; - border: 1px solid var(--border); - border-radius: 12px; - background: var(--card); - font-size: 0.9rem; - line-height: 1.6; - color: var(--berth-fg-2); -} -.berth-callout[data-type="warning"] { - border-color: color-mix(in srgb, var(--berth-amber) 55%, var(--border)); -} -.berth-callout[data-type="error"] { - border-color: color-mix(in srgb, var(--destructive) 50%, var(--border)); -} -.berth-callout-icon { - flex: none; - width: 18px; - height: 18px; - margin-top: 2px; - color: var(--muted-foreground); -} -.berth-callout[data-type="warning"] .berth-callout-icon { - color: var(--berth-amber); -} -.berth-callout[data-type="error"] .berth-callout-icon { - color: var(--destructive); -} -.berth-callout-body { - min-width: 0; - display: grid; - gap: 4px; -} -.berth-callout-title { - margin: 0; - font-weight: 600; - color: var(--foreground); -} -.berth-callout-text > * { - margin: 0; -} -.berth-callout-text > * + * { - margin-top: 0.5em; -} -.berth-callout-text a { - color: var(--foreground); - text-decoration: underline; - text-decoration-color: color-mix(in srgb, var(--foreground) 35%, transparent); - text-underline-offset: 3px; -} -.berth-callout-text code { - font-size: 0.86em; - padding: 0.1em 0.32em; - border-radius: 5px; - background: var(--berth-code-bg); - border: 1px solid var(--berth-border-soft); - color: var(--foreground); - overflow-wrap: anywhere; -} -.berth-stage { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: 8px 24px; - margin: 8px 0 36px; - padding: 20px 20px 18px 22px; - border: 1px solid var(--border); - border-radius: 14px; - background: var(--card); -} -.berth-stage-title { - margin: 0 0 10px; - font-size: 14px; - font-weight: 600; - color: var(--foreground); -} -.berth-stage-text { - font-size: 0.9375rem; - line-height: 1.6; -} -.berth-stage-text > :first-child { - margin-top: 0; -} -.berth-stage-text > :last-child { - margin-bottom: 0; -} -.berth-stage-text figure.shiki { - margin: 0 0 12px; - background: var(--background) !important; -} -.berth-stage-art { - align-self: end; - margin: 0 -6px -6px 0; -} -@media (max-width: 720px) { - .berth-stage { - grid-template-columns: minmax(0, 1fr); - padding: 16px; - } - .berth-stage-art { - order: -1; - justify-self: start; - margin: -4px 0 0 -10px; - } - .berth-stage-art svg { - width: 184px; - height: auto; - } -} -.berth-path { - counter-reset: berth-step; -} -.berth-path a[data-card] { - counter-increment: berth-step; - position: relative; - padding-left: 52px; -} -.berth-path a[data-card]::before { - content: counter(berth-step); - position: absolute; - left: 16px; - top: 15px; - display: grid; - place-items: center; - width: 22px; - height: 22px; - border-radius: 999px; - border: 1px solid var(--border); - background: var(--card); - font: 500 12px / 1 var(--font-mono); - color: var(--muted-foreground); -} -.berth-nw { - white-space: nowrap; -} -.berth-path a[data-card]::before { - top: 15px; -} -.berth-stage-text a { - color: var(--foreground); - font-weight: 500; - text-decoration: underline; - text-decoration-color: color-mix(in srgb, var(--foreground) 35%, transparent); - text-underline-offset: 3px; -} -.berth-stage-text :not(pre) > code { - font-size: 0.86em; - padding: 0.1em 0.34em; - border-radius: 5px; - background: var(--background); - border: 1px solid var(--berth-border-soft); - color: var(--foreground); -} -.berth-stage-text p { - color: var(--berth-fg-2); -} -.berth-stage-text figure.shiki { - --berth-code-ground: var(--background); -} -dl.berth-ref { - margin: 1.25rem 0 1.75rem; - border: 1px solid var(--border); - border-radius: 12px; -} -.berth-ref-item { - position: relative; - padding: 11px 40px 12px 16px; - scroll-margin-top: 7rem; - transition: background-color 0.15s; -} -.berth-ref-item + .berth-ref-item { - border-top: 1px solid var(--berth-border-soft); -} -.berth-ref-item:first-child { - border-radius: 11px 11px 0 0; -} -.berth-ref-item:last-child { - border-radius: 0 0 11px 11px; -} -.berth-ref-item:only-child { - border-radius: 11px; -} -.berth-ref-sig { - margin: 0; - font-weight: 400; - line-height: 1.55; -} -.berth-ref-sig code { - padding: 0; - border: 0; - background: none; - font-size: 13.5px; - font-weight: 500; - color: var(--foreground); -} -.berth-ref-desc { - margin: 3px 0 0; - padding: 0; - font-size: 0.875rem; - line-height: 1.6; - color: var(--berth-fg-2); -} -.berth-ref-desc > :first-child { - margin-top: 0; -} -a.berth-ref-anchor { - position: absolute; - top: 10px; - right: 10px; - display: grid; - place-items: center; - width: 24px; - height: 24px; - border-radius: 6px; - font: 500 13px / 1 var(--font-mono); - color: var(--muted-foreground); - text-decoration: none; - opacity: 0; - transition: - opacity 0.15s, - background-color 0.15s, - color 0.15s; -} -.berth-ref-item:hover a.berth-ref-anchor, -a.berth-ref-anchor:focus-visible, -.berth-ref-item:target a.berth-ref-anchor { - opacity: 1; -} -a.berth-ref-anchor:hover { - color: var(--foreground); - background: var(--accent); -} -@media (hover: none) { - a.berth-ref-anchor { - opacity: 0.6; - } -} -.berth-ref-item:target, -table.berth-fields tr:target > td { - background: var(--accent); -} -table.berth-fields tr { - scroll-margin-top: 7rem; -} -a.berth-field-link { - text-decoration: none; - font-weight: inherit; -} -a.berth-field-link:hover code { - text-decoration: underline; - text-decoration-color: color-mix(in srgb, var(--foreground) 40%, transparent); - text-underline-offset: 3px; -} -#nd-page .berth-stage-text figure.shiki pre { - width: auto; - min-width: 0; - padding-right: 0; - white-space: pre-wrap; -} -#nd-page .berth-stage-text figure.shiki .line { - white-space: pre-wrap; - overflow-wrap: anywhere; - padding-left: calc(var(--padding-left) + 2ch); - text-indent: -2ch; -} -#nd-page .berth-stage-text figure.shiki .line > span { - display: inline-block; - max-width: 100%; - text-indent: 0; -} -#nd-page .berth-stage-text figure.shiki .line > span:only-child { - display: inline; -} -.berth-shot { - margin: 1.5rem 0 1.75rem; -} -.berth-shot img { - display: block; - width: 100%; - height: auto; - border: 1px solid var(--border); - border-radius: 12px; - background: var(--berth-code-bg); -} -.berth-shot-light, -.berth-shot-dark { - display: block; -} -.berth-shot-dark, -.dark .berth-shot-light { - display: none; -} -.dark .berth-shot-dark { - display: block; -} -.berth-shot-caption { - margin: 10px 2px 0; - max-width: 68ch; - font-size: 13.5px; - line-height: 1.55; - color: var(--muted-foreground); -} diff --git a/apps/shipyard/app/docs/[[...slug]]/page.tsx b/apps/shipyard/app/docs/[[...slug]]/page.tsx deleted file mode 100644 index 677b35f55..000000000 --- a/apps/shipyard/app/docs/[[...slug]]/page.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { Card, CardFrame, CardPanel } from "@coss/ui/components/card"; -import { ScrollArea } from "@coss/ui/components/scroll-area"; -import { notFound } from "next/navigation"; -import { DocsCopyPage } from "@/components/docs-copy-page"; -import { DocsTableOfContents } from "@/components/docs-toc"; -import { SiteFooter } from "@/components/site-footer"; -import { source } from "@/lib/source"; -import { mdxComponents } from "@/mdx-components"; - -export const revalidate = false; -export const dynamic = "force-static"; -export const dynamicParams = false; - -export function generateStaticParams() { - return source.generateParams(); -} - -export async function generateMetadata(props: { - params: Promise<{ slug?: string[] }>; -}) { - const params = await props.params; - const page = source.getPage(params.slug); - - if (!page) { - notFound(); - } - - const doc = page.data; - - if (!doc.title || !doc.description) { - notFound(); - } - - return { - description: doc.description, - title: `${doc.title} - Shipyard docs`, - }; -} - -export default async function Page(props: { - params: Promise<{ slug?: string[] }>; -}) { - const params = await props.params; - const page = source.getPage(params.slug); - if (!page) { - notFound(); - } - - const doc = page.data; - const rawContent = await page.data.getText("raw"); - const MDX = doc.body; - - return ( -
-
- - - -
-
-
-
-

- {doc.title} -

- {doc.description && ( -

- {doc.description} -

- )} -
-
- -
-
-
- -
-
-
-
-
-
- -
-
-
-
- -
-
- {doc.toc?.length ? : null} -
- -
-
- ); -} diff --git a/apps/shipyard/app/docs/layout.tsx b/apps/shipyard/app/docs/layout.tsx deleted file mode 100644 index 0da66e977..000000000 --- a/apps/shipyard/app/docs/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { SidebarProvider } from "@coss/ui/components/sidebar"; -import { DocsSidebar } from "@/components/docs-sidebar"; -import { source } from "@/lib/source"; - -export default function DocsLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
- - -
{children}
-
-
- ); -} diff --git a/apps/shipyard/app/globals.css b/apps/shipyard/app/globals.css deleted file mode 100644 index a2ca26cce..000000000 --- a/apps/shipyard/app/globals.css +++ /dev/null @@ -1,12 +0,0 @@ -@import "@coss/ui/globals.css"; -@import "./landing.css"; -@import "./docs.css"; -@import "../components/docs/diagram.css"; -@import "../components/docs/scenes.css"; - -@layer base { - a:active, - button:active { - @apply opacity-72 md:opacity-100; - } -} diff --git a/apps/shipyard/app/icon.svg b/apps/shipyard/app/icon.svg deleted file mode 100644 index df2be7ab9..000000000 --- a/apps/shipyard/app/icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/apps/shipyard/app/landing.css b/apps/shipyard/app/landing.css deleted file mode 100644 index 4d834ceeb..000000000 --- a/apps/shipyard/app/landing.css +++ /dev/null @@ -1,766 +0,0 @@ -/* Shipyard's landing page, as it was at berthd.app (the repository's - site/styles.css), scoped to .shipyard-landing under coss.com's layout and - header. Its own type (Inter, JetBrains Mono) and tokens (Shipyard Light - and Shipyard Dark, by the site's theme class) and coss-ui's buttons. The - harbour at the top is the only picture that isn't a screenshot. The few - url()s here are under the site's base path (next.config.ts). */ - -.shipyard-landing { - --bg: #ffffff; - --card: #ffffff; - --muted-bg: #f4f4f6; - --border: #e6e6ea; - --input: #dcdce1; - --fg: #26272b; - --fg-2: #45474e; - --muted: #62646d; - --primary: #26272b; - --primary-fg: #ffffff; - --amber: #c27a12; - --ring: #8b8d96; - --sea: #eef1f6; - --blue: #3b82f6; - --green: #16a34a; - --shadow: - 0 1px 2px rgb(20 22 30 / 0.04), 0 24px 60px -28px rgb(20 22 30 / 0.28); - --sans: - var(--font-inter), ui-sans-serif, system-ui, -apple-system, "Segoe UI", - sans-serif; - --mono: var(--font-jetbrains-mono), ui-monospace, "SF Mono", Menlo, monospace; - --band: clamp(340px, 58vh, 600px); - color-scheme: light; -} -/* Night: the system's dark mode, unless the page was put to day; or the - page put to night by "Close the laptop". */ -.dark .shipyard-landing { - /* coss.com's dark ground, so the page sits flush under its header. */ - --bg: #141414; - --card: #1f2024; - --muted-bg: #222327; - --border: #2c2d33; - --input: #34353c; - --fg: #e3e4e8; - --fg-2: #c0c2c9; - --muted: #9a9ca5; - --primary: #e3e4e8; - --primary-fg: #18191c; - --amber: #e8a33d; - --ring: #6b6d76; - --sea: #131824; - --green: #4ade80; - --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 30px 80px -30px rgb(0 0 0 / 0.75); - color-scheme: dark; -} -.shipyard-landing { - position: relative; - margin: 0; - background: var(--bg); - color: var(--fg); - font: 15px / 1.6 var(--sans); - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; -} -.shipyard-landing img, -.shipyard-landing svg, -.shipyard-landing canvas { - display: block; -} -.shipyard-landing a { - color: inherit; -} -.shipyard-landing :focus-visible { - outline: 2px solid var(--ring); - outline-offset: 2px; - border-radius: 6px; -} -.shipyard-landing h1, -.shipyard-landing h2 { - margin: 0; - font-weight: 600; - letter-spacing: -0.022em; - text-wrap: balance; -} -.shipyard-landing p { - margin: 0; -} -.shipyard-landing code { - font-family: var(--mono); -} -.shipyard-landing .wrap { - width: min(1040px, 100% - 32px); - margin-inline: auto; -} -/* coss-ui buttons: default (primary) and outline, size default. */ -.shipyard-landing .btn { - display: inline-flex; - align-items: center; - gap: 8px; - height: 36px; - padding: 0 14px; - border: 1px solid var(--input); - border-radius: 10px; - background: var(--card); - color: var(--fg); - font: 500 14px / 1 var(--sans); - text-decoration: none; - white-space: nowrap; - box-shadow: 0 1px 2px rgb(0 0 0 / 0.05); - transition: - background-color 0.15s, - border-color 0.15s; -} -.shipyard-landing .btn:hover { - background: var(--muted-bg); -} -.shipyard-landing .btn.primary { - border-color: var(--primary); - background: var(--primary); - color: var(--primary-fg); - box-shadow: - inset 0 1px rgb(255 255 255 / 0.16), - 0 1px 2px rgb(0 0 0 / 0.12); -} -.shipyard-landing .btn.primary:hover { - opacity: 0.9; -} -.shipyard-landing .btn svg { - width: 16px; - height: 16px; - margin-inline: -2px; -} -.shipyard-landing .actions { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 8px; -} -.shipyard-landing .site-header-inner { - display: flex; - align-items: center; - gap: 8px; - height: 100%; - max-width: 1416px; - margin: 0 auto; - padding: 0 16px; -} -/* coss-ui's ghost button, size sm. */ -/* A hairline between the page's links and the repository's. */ -/* The theme toggle is an icon, as on coss.com; its wording is for readers. */ -@media (min-width: 640px) { - .shipyard-landing .site-header-inner { - padding: 0 24px; - } -} -.shipyard-landing .rule { - pointer-events: none; - max-width: 1416px; - margin: 0 auto; -} -.shipyard-landing .rule { - position: relative; - z-index: 7; - height: 0; - --mark-top: -3.5px; -} -.shipyard-landing .marks::before, -.shipyard-landing .marks::after { - content: ""; - position: absolute; - top: var(--mark-top); - width: 8px; - height: 8px; - border: 1px solid var(--border); - border-radius: 2px; - background: var(--bg); - box-shadow: 0 1px 2px rgb(0 0 0 / 0.05); -} -.shipyard-landing .marks::before { - left: -15.5px; -} -.shipyard-landing .marks::after { - right: -15.5px; -} -/* Narrower than the header's width plus the rails, they'd sit at the window's - edge or past it. */ -@media (max-width: 1441px) { - .shipyard-landing .marks::before, - .shipyard-landing .marks::after { - display: none; - } -} -.shipyard-landing .chapter.ruled { - position: relative; -} -.shipyard-landing .chapter.ruled::before { - content: ""; - position: absolute; - inset: 0 0 auto; - height: 1px; - background: color-mix(in srgb, var(--border) 64%, transparent); -} -/* Hero: the harbour band, then the pitch at its foot, where it dissolves. */ -.shipyard-landing .hero { - position: relative; - /* The whole page: the harbour and what's under it fill the window. */ - min-height: 100svh; - padding-bottom: 48px; -} -.shipyard-landing .harbour { - position: relative; - height: var(--band); - overflow: hidden; -} -.shipyard-landing .harbour picture, -.shipyard-landing .harbour img { - width: 100%; - height: 100%; -} -.shipyard-landing .harbour img { - object-fit: cover; - object-position: 50% 42%; - -webkit-mask-image: linear-gradient(black 50%, transparent); - mask-image: linear-gradient(black 50%, transparent); - transition: opacity 0.4s; -} -.shipyard-landing .harbour canvas { - position: absolute; - inset: 0; - image-rendering: pixelated; - opacity: 0; - transition: opacity 0.4s; -} -.shipyard-landing .harbour.drawn canvas { - opacity: 1; -} -.shipyard-landing .harbour.drawn img { - opacity: 0; -} -/* The loop, at night: over the drawn harbour in the same box (so nothing - moves), dissolving through the same dots (a mask from assets/hero-loop.js). - It brings its own beam, so the CSS one rests. */ -.shipyard-landing .loop { - position: absolute; - inset: 0; - opacity: 0; - transition: opacity 0.8s; - -webkit-mask: - var(--mask, linear-gradient(black 50%, transparent)) 0 0 / 100% 100% - no-repeat; - mask: - var(--mask, linear-gradient(black 50%, transparent)) 0 0 / 100% 100% - no-repeat; -} -/* The video itself is a pixel, under the canvas its frames are drawn on. */ -.shipyard-landing .loop video { - position: absolute; - top: 0; - left: 0; - width: 1px; - height: 1px; -} -.shipyard-landing .loop canvas { - position: absolute; - inset: 0; - width: 100%; - height: 100%; -} -.shipyard-landing .harbour.live .loop { - opacity: 1; -} -.shipyard-landing .harbour.live > canvas { - transition-delay: 0.8s; -} -.shipyard-landing .harbour.live > canvas, -.shipyard-landing .harbour.live .beam { - opacity: 0 !important; -} -.shipyard-landing .harbour .loop canvas { - opacity: 1; - image-rendering: auto; - transition: none; -} -.shipyard-landing .hero-copy { - position: relative; - width: min(720px, 100% - 32px); - margin: calc(var(--band) * -0.3) auto 0; - text-align: center; -} - -.shipyard-landing .hero h1 { - font-size: clamp(32px, 5.2vw, 54px); - line-height: 1.06; - text-shadow: - 0 0 10px var(--bg), - 0 0 24px var(--bg); -} -.shipyard-landing .lede { - max-width: 560px; - margin: 16px auto 24px; - color: var(--fg-2); - font-size: 17px; - text-shadow: 0 0 8px var(--bg); - text-wrap: balance; -} -.shipyard-landing .install { - display: flex; - align-items: center; - gap: 10px; - width: fit-content; - max-width: 100%; - margin: 18px auto 0; - padding: 4px 4px 4px 12px; - border: 1px solid var(--border); - border-radius: 12px; - background: var(--muted-bg); - text-align: left; -} -.shipyard-landing .install-label { - flex: none; - color: var(--muted); - font-size: 12px; -} -.shipyard-landing .install code { - overflow-x: auto; - font-size: 13px; - white-space: nowrap; - scrollbar-width: none; -} -.shipyard-landing .copy { - flex: none; - height: 28px; - padding: 0 10px; - border: 1px solid var(--input); - border-radius: 8px; - background: var(--card); - color: var(--fg); - font: 500 12.5px var(--sans); - cursor: pointer; -} -.shipyard-landing .copy:hover { - background: var(--bg); -} -.shipyard-landing .note { - margin-top: 14px; - color: var(--muted); - font-size: 13px; -} -.shipyard-landing .note a { - display: inline-flex; - align-items: center; - gap: 2px; - color: var(--fg-2); - font-weight: 500; - text-decoration: none; -} -.shipyard-landing .note a:hover { - color: var(--fg); -} -.shipyard-landing .note svg { - width: 13px; - height: 13px; -} -/* The Linux app is an alpha: a quiet line under the Mac download. */ -.shipyard-landing .note.linux-alpha { - margin-top: 4px; - font-size: 12px; -} -.shipyard-landing .note.linux-alpha a { - text-decoration: underline; - text-underline-offset: 2px; - text-decoration-color: var(--muted); -} -.shipyard-landing .kicker { - display: flex; - align-items: center; - gap: 10px; - color: var(--muted); - font: 500 13px / 1 var(--mono); -} -/* A segmented switch over several views. */ -.shipyard-landing .seg { - display: flex; - width: fit-content; - margin: 0 auto 18px; - padding: 3px; - border: 1px solid var(--border); - border-radius: 11px; - background: var(--muted-bg); -} -.shipyard-landing .seg button { - height: 30px; - padding: 0 14px; - border: 0; - border-radius: 8px; - background: transparent; - color: var(--muted); - font: 500 13.5px var(--sans); - cursor: pointer; -} -.shipyard-landing .seg button:hover { - color: var(--fg); -} -.shipyard-landing .seg button[aria-pressed="true"] { - background: var(--card); - color: var(--fg); - box-shadow: - 0 1px 2px rgb(0 0 0 / 0.08), - 0 0 0 1px var(--border); -} -.shipyard-landing .stack { - display: grid; -} -.shipyard-landing .stack > [aria-hidden="true"] { - opacity: 0; - visibility: hidden; - transition: - opacity 0.3s, - visibility 0s 0.3s; -} -.shipyard-landing .sr { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - clip-path: inset(50%); - white-space: nowrap; -} -@media (max-width: 640px) { - .shipyard-landing { - --band: clamp(300px, 52vh, 420px); - } - .shipyard-landing .hero-copy { - margin-top: calc(var(--band) * -0.22); - } - .shipyard-landing .harbour img { - object-position: 8% 42%; - } - .shipyard-landing .lede { - font-size: 16px; - } - .shipyard-landing .install-label { - display: none; - } - /* The install section is a scroll away, and the width is for the brand. */ -} -@media (prefers-reduced-motion: reduce) { - .shipyard-landing .harbour canvas, - .shipyard-landing .harbour img { - transition: none; - } -} -/* The lighthouse's beam: a wedge of halftone dots from the lamp, sweeping - slowly. Only transform moves; it rests when the page is hidden and for - reduced motion. Brighter at night. */ -.shipyard-landing .beam { - position: absolute; - z-index: 1; - width: min(70vw, 900px); - height: 220px; - margin-top: -110px; - transform-origin: 0 50%; - background: linear-gradient( - to right, - rgb(255 236 190 / 0.9), - rgb(255 236 190 / 0.35) 40%, - transparent 85% - ); - clip-path: polygon(0 48%, 100% 0, 100% 100%, 0 52%); - -webkit-mask: - radial-gradient(circle at 1px 1px, #000 0.8px, transparent 1px) 0 0 / 3px - 3px; - mask: - radial-gradient(circle at 1px 1px, #000 0.8px, transparent 1px) 0 0 / 3px - 3px; - opacity: 0; - pointer-events: none; - animation: sweep 11s ease-in-out infinite alternate; - transition: opacity 0.8s; -} -.shipyard-landing .harbour.drawn .beam { - opacity: 0.32; -} -.dark .shipyard-landing .harbour.drawn .beam { - opacity: 0.85; -} -@keyframes sweep { - from { - transform: rotate(-14deg); - } - to { - transform: rotate(10deg); - } -} -.shipyard-landing[data-page-hidden] .beam { - animation-play-state: paused; -} -/* "Close the laptop": puts the page to night, and says what keeps going. */ -/* Until the script shows it, the switch keeps its place, so the header - never moves. */ -@keyframes pulse { - 50% { - opacity: 0.35; - } -} -@media (prefers-reduced-motion: reduce) { - .shipyard-landing .beam { - animation: none; - transform: rotate(-4deg); - } -} - -@keyframes film-in { - from { - opacity: 0; - transform: translateY(8px) scale(0.985); - } -} - -@media (prefers-reduced-motion: reduce) { - .shipyard-landing .loop, - .shipyard-landing .harbour.live > canvas { - transition: none; - } -} -/* Below the harbour: the film's story in six chapters. Each opens with one - of the app's line scenes (assets/scenes.css), its one amber light, then the - film's caption and a few words, and a short loop of the real app. */ -.shipyard-landing .chapter { - padding-top: clamp(72px, 10vw, 128px); -} -.shipyard-landing .chapter .wrap { - width: min(1120px, 100% - 32px); -} -/* The hero already fills the window; the first chapter follows closer. */ -.shipyard-landing #start { - padding-top: clamp(40px, 6vw, 72px); -} -.shipyard-landing .chapter-head { - display: grid; - grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); - gap: 14px 64px; - align-items: end; - margin-bottom: clamp(28px, 4vw, 40px); -} -.shipyard-landing .chapter-mark { - grid-column: 1 / -1; - display: flex; - align-items: center; - gap: 16px; - /* Room for what rises above the scene's box: a mast, a beam, a sun. */ - padding-top: 14px; - margin-bottom: 6px; -} -.shipyard-landing .chapter-mark.centered { - flex-direction: column; - gap: 10px; - margin-bottom: 18px; -} -/* A line scene's slot, its size kept until the drawing arrives. */ -.shipyard-landing .scene { - --muted-foreground: var(--muted); - --warning: var(--amber); - flex: none; - display: block; - width: 144px; - aspect-ratio: 160 / 52; -} -.shipyard-landing .scene.dawn { - width: 208px; -} -.shipyard-landing .scene svg { - width: 100%; - height: auto; -} -.shipyard-landing .chapter h2 { - font-size: clamp(30px, 4.2vw, 48px); - line-height: 1.05; -} -.shipyard-landing .chapter-head > p { - max-width: 470px; - color: var(--fg-2); - font-size: 16.5px; - text-wrap: pretty; -} -/* A loop: its poster sizes the frame; the video fades in over it once it - plays. The footage is the app in Shipyard Dark, so the frame is dark too. */ -.shipyard-landing .clip { - position: relative; - margin: 0; - overflow: hidden; - border: 1px solid var(--border); - border-radius: 14px; - background: #1d1e22; - box-shadow: var(--shadow); -} -.shipyard-landing .clip img { - width: 100%; - height: auto; -} -/* A poster not asked for yet keeps its frame, and shows no alt text. */ -.shipyard-landing .clip img[data-src] { - visibility: hidden; -} -.shipyard-landing .clip video { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - object-fit: cover; - opacity: 0; - transition: opacity 0.4s; -} -.shipyard-landing .clip.playing video { - opacity: 1; -} -.shipyard-landing .views .seg { - margin-bottom: 16px; -} -.shipyard-landing .stack > .clip { - grid-area: 1 / 1; - transition: opacity 0.3s; -} -/* It keeps going: the night comes in through the harbour's dots, whatever - the theme, and goes out the same way. */ -.shipyard-landing .chapter.after-dark { - padding-top: clamp(56px, 7vw, 96px); -} -.shipyard-landing .night-in, -.shipyard-landing .night-out { - height: 64px; - background: #18191c; - -webkit-mask: url("/shipyard/assets/seam.svg") 0 0 / 16px 64px repeat-x; - mask: url("/shipyard/assets/seam.svg") 0 0 / 16px 64px repeat-x; -} -.shipyard-landing .night-out { - transform: scaleY(-1); -} -.shipyard-landing .night-body { - --bg: #18191c; - --card: #1f2024; - --muted-bg: #222327; - --border: #2c2d33; - --input: #34353c; - --fg: #e3e4e8; - --fg-2: #c0c2c9; - --muted: #9a9ca5; - --amber: #e8a33d; - --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 30px 80px -30px rgb(0 0 0 / 0.75); - padding: clamp(40px, 6vw, 72px) 0 clamp(48px, 7vw, 88px); - background: var(--bg); - color: var(--fg); - color-scheme: dark; -} -.shipyard-landing .night-body .clip { - border-color: #2c2d33; -} -.shipyard-landing .boxes { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 8px 10px; - margin-top: 18px; - color: var(--fg-2); - font: 500 12.5px / 1 var(--mono); -} -.shipyard-landing .boxes span { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 7px 12px 7px 10px; - border: 1px solid var(--border); - border-radius: 999px; - background: var(--card); -} -.shipyard-landing .boxes i { - width: 7px; - height: 7px; - border-radius: 50%; - background: #4ade80; - box-shadow: 0 0 8px rgb(74 222 128 / 0.6); - animation: pulse 2.6s ease-in-out infinite; -} -.shipyard-landing .boxes span + span i { - animation-delay: -1.3s; -} -.shipyard-landing[data-page-hidden] .boxes i { - animation-play-state: paused; -} -/* Install: the end, at dawn. */ -.shipyard-landing .install-ch { - padding-bottom: clamp(88px, 11vw, 136px); - text-align: center; -} -.shipyard-landing .lede-2 { - max-width: 460px; - margin: 14px auto 26px; - color: var(--fg-2); - font-size: 17px; - text-wrap: balance; -} -.shipyard-landing .ends { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 8px 24px; - margin-top: 26px; - font-size: 14.5px; -} -.shipyard-landing .ends a { - display: inline-flex; - align-items: center; - gap: 2px; - color: var(--fg-2); - font-weight: 500; - text-decoration: none; -} -.shipyard-landing .ends a:hover { - color: var(--fg); -} -.shipyard-landing .ends svg { - width: 14px; - height: 14px; -} -@media (max-width: 760px) { - .shipyard-landing .chapter-head { - grid-template-columns: 1fr; - gap: 12px; - } - .shipyard-landing .scene { - width: 120px; - } - .shipyard-landing .scene.dawn { - width: 168px; - } - /* On a phone, the frame closes in on the app's main pane. */ - .shipyard-landing .clip img, - .shipyard-landing .clip video { - aspect-ratio: 4 / 3; - height: auto; - object-fit: cover; - object-position: 64% 40%; - } - .shipyard-landing .clip video { - height: 100%; - } - .shipyard-landing .views .seg button { - padding: 0 10px; - font-size: 13px; - } -} -@media (prefers-reduced-motion: reduce) { - .shipyard-landing .boxes i { - animation: none; - } - .shipyard-landing .clip video, - .shipyard-landing .stack > .clip { - transition: none; - } -} -@media (max-width: 760px) { - .shipyard-landing .night-body .clip img, - .shipyard-landing .night-body .clip video { - object-position: 12% 50%; - } -} -/* A phone's header: the brand a touch smaller, and the links as icons - (styles for 640px and under, above). */ diff --git a/apps/shipyard/app/layout.tsx b/apps/shipyard/app/layout.tsx deleted file mode 100644 index d5c721fd0..000000000 --- a/apps/shipyard/app/layout.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import "./globals.css"; - -import { fontMono, fontSans } from "@coss/ui/fonts"; -import { ThemeProvider } from "@coss/ui/shared/theme-provider"; -import type { Metadata } from "next"; -import { SiteHeader } from "@/components/site-header"; -import { appConfig } from "@/lib/config"; -import { inter, jetbrainsMono } from "@/lib/fonts"; - -export const metadata: Metadata = { - description: appConfig.description, - metadataBase: new URL("https://coss.com"), - openGraph: { - description: appConfig.description, - siteName: "Shipyard", - title: "Shipyard - coding agents on your own boxes", - type: "website", - url: appConfig.url, - }, - title: { - default: "Shipyard - coding agents on your own boxes", - template: "%s - Shipyard", - }, -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - -
-