mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 22:37:29 +00:00
feat(web): site chrome on /blog + distinct content link color
- Add SiteShell (rail nav + workspace + footer) and wrap /blog and /blog/[slug] in it so they share the site's dark chrome instead of rendering as bare standalone pages. - Style rendered post HTML (.blog-content) for the dark workspace. - Links were `color: inherit` everywhere, so content-area links matched body text and were invisible. Give links a distinct accent (#5ac8a6); keep the rail nav and buttons on their own colors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cf99e93c53
commit
7e11130467
4 changed files with 189 additions and 55 deletions
|
|
@ -3,6 +3,7 @@ import { notFound } from "next/navigation";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { publicClient } from "@/lib/supabase";
|
import { publicClient } from "@/lib/supabase";
|
||||||
|
import { SiteShell } from "@/components/site-shell";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
|
@ -65,17 +66,15 @@ export default async function BlogPostPage({
|
||||||
if (!post) notFound();
|
if (!post) notFound();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main style={{ maxWidth: 760, margin: "0 auto", padding: "64px 24px" }}>
|
<SiteShell active="Blog">
|
||||||
<p style={{ marginBottom: 32 }}>
|
<article className="band" style={{ maxWidth: "48rem" }}>
|
||||||
<Link href="/blog" style={{ color: "#5b6b7a", textDecoration: "none" }}>
|
<p style={{ marginBottom: "1.5rem" }}>
|
||||||
← Blog
|
<Link href="/blog" style={{ color: "#b5beb2", textDecoration: "none" }}>
|
||||||
</Link>
|
← Blog
|
||||||
</p>
|
</Link>
|
||||||
<article>
|
</p>
|
||||||
<h1 style={{ fontSize: 38, fontWeight: 800, margin: "0 0 8px" }}>
|
<h1 style={{ fontSize: "2.2rem", margin: "0 0 0.5rem" }}>{post.title}</h1>
|
||||||
{post.title}
|
<div style={{ color: "#8a95a0", fontSize: "0.85rem", marginBottom: "2rem" }}>
|
||||||
</h1>
|
|
||||||
<div style={{ color: "#8a95a0", fontSize: 14, marginBottom: 32 }}>
|
|
||||||
{formatDate(post.published_at)}
|
{formatDate(post.published_at)}
|
||||||
</div>
|
</div>
|
||||||
{post.featured_image?.url ? (
|
{post.featured_image?.url ? (
|
||||||
|
|
@ -83,14 +82,15 @@ export default async function BlogPostPage({
|
||||||
<img
|
<img
|
||||||
src={post.featured_image.url}
|
src={post.featured_image.url}
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
style={{ width: "100%", borderRadius: 12, margin: "0 0 32px" }}
|
style={{ width: "100%", borderRadius: "0.75rem", margin: "0 0 2rem" }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div
|
<div
|
||||||
className="blog-content"
|
className="blog-content"
|
||||||
|
style={{ lineHeight: 1.7 }}
|
||||||
dangerouslySetInnerHTML={{ __html: post.html }}
|
dangerouslySetInnerHTML={{ __html: post.html }}
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</SiteShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import Link from "next/link";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { publicClient } from "@/lib/supabase";
|
import { publicClient } from "@/lib/supabase";
|
||||||
|
import { SiteShell } from "@/components/site-shell";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
|
@ -37,48 +38,50 @@ export default async function BlogIndex(): Promise<ReactNode> {
|
||||||
const posts = (data ?? []) as PostRow[];
|
const posts = (data ?? []) as PostRow[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main style={{ maxWidth: 760, margin: "0 auto", padding: "64px 24px" }}>
|
<SiteShell active="Blog">
|
||||||
<p style={{ marginBottom: 32 }}>
|
<div className="band">
|
||||||
<Link href="/" style={{ color: "#5b6b7a", textDecoration: "none" }}>
|
<div className="section-head">
|
||||||
← LogicSRC
|
<h2>Blog</h2>
|
||||||
</Link>
|
<p>
|
||||||
</p>
|
Project notes for LogicSRC OpenSpec standards, AgentSwarm, AgentByte,
|
||||||
<h1 style={{ fontSize: 40, fontWeight: 800, margin: "0 0 8px" }}>Blog</h1>
|
SDKs, MCP, and reference implementations.{" "}
|
||||||
<p style={{ color: "#5b6b7a", margin: "0 0 40px" }}>
|
<a href="/blog/rss.xml">RSS</a>
|
||||||
Project notes for LogicSRC OpenSpec standards, AgentSwarm, AgentByte,
|
</p>
|
||||||
SDKs, MCP, and reference implementations.{" "}
|
</div>
|
||||||
<a href="/blog/rss.xml" style={{ color: "#5b6b7a" }}>
|
|
||||||
RSS
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{posts.length === 0 ? (
|
{posts.length === 0 ? (
|
||||||
<p style={{ color: "#5b6b7a" }}>No posts yet.</p>
|
<p style={{ color: "#b5beb2" }}>No posts yet.</p>
|
||||||
) : (
|
) : (
|
||||||
<ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
|
<ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
<li
|
<li
|
||||||
key={post.slug}
|
key={post.slug}
|
||||||
style={{ padding: "20px 0", borderTop: "1px solid #e3e6e0" }}
|
style={{
|
||||||
>
|
padding: "1.25rem 0",
|
||||||
<Link
|
borderTop: "1px solid rgba(245, 247, 244, 0.12)",
|
||||||
href={`/blog/${post.slug}`}
|
}}
|
||||||
style={{ color: "#101418", textDecoration: "none" }}
|
|
||||||
>
|
>
|
||||||
<h2 style={{ fontSize: 22, fontWeight: 700, margin: "0 0 6px" }}>
|
<Link
|
||||||
{post.title}
|
href={`/blog/${post.slug}`}
|
||||||
</h2>
|
style={{ color: "inherit", textDecoration: "none" }}
|
||||||
</Link>
|
>
|
||||||
<div style={{ color: "#8a95a0", fontSize: 14, marginBottom: 8 }}>
|
<h3 style={{ margin: "0 0 0.35rem", fontSize: "1.25rem" }}>
|
||||||
{formatDate(post.published_at)}
|
{post.title}
|
||||||
</div>
|
</h3>
|
||||||
{post.excerpt ? (
|
</Link>
|
||||||
<p style={{ color: "#41505d", margin: 0 }}>{post.excerpt}</p>
|
<div
|
||||||
) : null}
|
style={{ color: "#8a95a0", fontSize: "0.85rem", marginBottom: "0.5rem" }}
|
||||||
</li>
|
>
|
||||||
))}
|
{formatDate(post.published_at)}
|
||||||
</ul>
|
</div>
|
||||||
)}
|
{post.excerpt ? (
|
||||||
</main>
|
<p style={{ color: "#b5beb2", margin: 0 }}>{post.excerpt}</p>
|
||||||
|
) : null}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SiteShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
79
apps/logicsrc-web/src/components/site-shell.tsx
Normal file
79
apps/logicsrc-web/src/components/site-shell.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
// Mirrors the rail/nav from page-markup.ts so standalone routes (e.g. /blog)
|
||||||
|
// share the site chrome. Anchor links point at the homepage sections.
|
||||||
|
const NAV: Array<{ href: string; label: string }> = [
|
||||||
|
{ href: "/#overview", label: "Overview" },
|
||||||
|
{ href: "/#schemas", label: "Schemas" },
|
||||||
|
{ href: "/agent-swarm", label: "Soon" },
|
||||||
|
{ href: "/agentbyte", label: "AgentByte" },
|
||||||
|
{ href: "/credential-sharing", label: "Credentials" },
|
||||||
|
{ href: "/#cli", label: "CLI" },
|
||||||
|
{ href: "/docs", label: "Docs" },
|
||||||
|
{ href: "/blog", label: "Blog" },
|
||||||
|
{ href: "/openspec", label: "OpenSpec" },
|
||||||
|
{ href: "/hire-us", label: "Hire Us" },
|
||||||
|
{ href: "/about", label: "About" },
|
||||||
|
{ href: "/terms", label: "Terms" },
|
||||||
|
{ href: "/privacy", label: "Privacy" },
|
||||||
|
{ href: "/#reference", label: "Reference" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SiteShell({
|
||||||
|
children,
|
||||||
|
active,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
active?: string;
|
||||||
|
}): ReactNode {
|
||||||
|
return (
|
||||||
|
<main className="shell">
|
||||||
|
<aside className="rail">
|
||||||
|
<a className="brand" href="/" style={{ textDecoration: "none", color: "inherit" }}>
|
||||||
|
<span className="mark">LS</span>
|
||||||
|
<div>
|
||||||
|
<strong>LogicSRC</strong>
|
||||||
|
<small>Open coordination standards</small>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<nav aria-label="LogicSRC sections">
|
||||||
|
{NAV.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className={item.label === active ? "active" : undefined}
|
||||||
|
aria-current={item.label === active ? "page" : undefined}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
<section className="workspace">
|
||||||
|
{children}
|
||||||
|
<footer
|
||||||
|
style={{
|
||||||
|
maxWidth: "72rem",
|
||||||
|
margin: "2rem auto 0",
|
||||||
|
padding: "1.25rem 0",
|
||||||
|
borderTop: "1px solid rgba(245, 247, 244, 0.12)",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: "0.75rem",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
fontSize: "0.85rem",
|
||||||
|
color: "#b5beb2",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>© {new Date().getFullYear()} Profullstack, Inc. · LogicSRC</span>
|
||||||
|
<span style={{ display: "flex", gap: "0.75rem" }}>
|
||||||
|
<a href="/docs" style={{ color: "inherit" }}>Docs</a>
|
||||||
|
<a href="/blog/rss.xml" style={{ color: "inherit" }}>RSS</a>
|
||||||
|
<a href="/terms" style={{ color: "inherit" }}>Terms</a>
|
||||||
|
<a href="/privacy" style={{ color: "inherit" }}>Privacy</a>
|
||||||
|
</span>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -15,10 +15,14 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: #5ac8a6;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border: 0;
|
border: 0;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
|
@ -75,6 +79,7 @@ nav {
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a {
|
nav a {
|
||||||
|
color: inherit;
|
||||||
min-height: 2.35rem;
|
min-height: 2.35rem;
|
||||||
padding: 0.55rem 0.75rem;
|
padding: 0.55rem 0.75rem;
|
||||||
border: 1px solid #263039;
|
border: 1px solid #263039;
|
||||||
|
|
@ -85,6 +90,7 @@ nav a.active,
|
||||||
nav a:hover {
|
nav a:hover {
|
||||||
border-color: #3f5049;
|
border-color: #3f5049;
|
||||||
background: #1b2329;
|
background: #1b2329;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace {
|
.workspace {
|
||||||
|
|
@ -696,3 +702,49 @@ pre {
|
||||||
font-size: 2.4rem;
|
font-size: 2.4rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Rendered blog post HTML (autoblog payload) on the dark workspace. */
|
||||||
|
.blog-content {
|
||||||
|
color: #e7ebe4;
|
||||||
|
}
|
||||||
|
.blog-content a {
|
||||||
|
color: #5ac8a6;
|
||||||
|
}
|
||||||
|
.blog-content h2,
|
||||||
|
.blog-content h3,
|
||||||
|
.blog-content h4 {
|
||||||
|
margin: 1.75rem 0 0.5rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
.blog-content p,
|
||||||
|
.blog-content ul,
|
||||||
|
.blog-content ol,
|
||||||
|
.blog-content blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
.blog-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.blog-content pre {
|
||||||
|
background: rgba(0, 0, 0, 0.32);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.blog-content code {
|
||||||
|
background: rgba(0, 0, 0, 0.32);
|
||||||
|
padding: 0.1rem 0.35rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.blog-content pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.blog-content blockquote {
|
||||||
|
border-left: 3px solid rgba(245, 247, 244, 0.2);
|
||||||
|
padding-left: 1rem;
|
||||||
|
color: #b5beb2;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue