extract editorial components to markdown.tsx, simplify liveline to content-only
- New website/src/components/markdown.tsx with all reusable components: EditorialPage, TableOfContents, BackButton, P, Code, Caption, CodeBlock, ChartPlaceholder, Section, Divider, SectionHeading, List, Li, PropsTable - liveline.tsx reduced from ~988 to ~370 lines, now pure content - Renamed Paragraph->P, InlineCode->Code for brevity - TOC accepts items/logo props for reuse across pages - Centered image captions - Divider with equal top/bottom padding - Variable spacing CSS rules for code blocks, images, section dividers
This commit is contained in:
@@ -0,0 +1,578 @@
|
|||||||
|
/*
|
||||||
|
* Editorial markdown components for the liveline-style pages.
|
||||||
|
* Extracted from liveline.tsx so page files are content-only.
|
||||||
|
*
|
||||||
|
* All components use CSS variables from liveline.css (--ll-*).
|
||||||
|
* Import liveline.css and liveline-prism.css in the page file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import Prism from "prismjs";
|
||||||
|
import "prismjs/components/prism-jsx";
|
||||||
|
import "prismjs/components/prism-tsx";
|
||||||
|
import "prismjs/components/prism-bash";
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
TOC sidebar (fixed left)
|
||||||
|
========================================================================= */
|
||||||
|
|
||||||
|
function useActiveTocId() {
|
||||||
|
const [activeId, setActiveId] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const headings = document.querySelectorAll<HTMLElement>("h1[id]");
|
||||||
|
if (headings.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
const visible: string[] = [];
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting && entry.target.id) {
|
||||||
|
visible.push(entry.target.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (visible.length > 0) {
|
||||||
|
const sorted = visible.sort((a, b) => {
|
||||||
|
const elA = document.getElementById(a);
|
||||||
|
const elB = document.getElementById(b);
|
||||||
|
if (!elA || !elB) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return elA.getBoundingClientRect().top - elB.getBoundingClientRect().top;
|
||||||
|
});
|
||||||
|
setActiveId(sorted[sorted.length - 1]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rootMargin: "-80px 0px -75% 0px",
|
||||||
|
threshold: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
headings.forEach((heading) => {
|
||||||
|
observer.observe(heading);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return activeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TableOfContents({
|
||||||
|
items,
|
||||||
|
logo,
|
||||||
|
}: {
|
||||||
|
items: Array<{ label: string; href: string }>;
|
||||||
|
logo?: string;
|
||||||
|
}) {
|
||||||
|
const activeId = useActiveTocId();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside
|
||||||
|
className="fixed top-[80px] hidden lg:block"
|
||||||
|
style={{ left: "max(1rem, calc((100vw - 550px) / 2 - 200px))", width: "122px" }}
|
||||||
|
>
|
||||||
|
<nav>
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
className="no-underline transition-colors block"
|
||||||
|
style={{
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: "20px",
|
||||||
|
letterSpacing: "-0.09px",
|
||||||
|
padding: "4px 0",
|
||||||
|
color: "var(--ll-text-primary)",
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
marginBottom: "8px",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = "var(--ll-text-hover)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = "var(--ll-text-primary)";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{logo ?? "index"}
|
||||||
|
</a>
|
||||||
|
{items.map((item) => {
|
||||||
|
const isActive = `#${activeId}` === item.href;
|
||||||
|
const defaultColor = isActive ? "var(--ll-text-primary)" : "var(--ll-text-secondary)";
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className="block no-underline"
|
||||||
|
style={{
|
||||||
|
fontSize: "13px",
|
||||||
|
fontWeight: 475,
|
||||||
|
lineHeight: "15.6px",
|
||||||
|
letterSpacing: "-0.04px",
|
||||||
|
padding: "3px 0",
|
||||||
|
color: defaultColor,
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
transition: "color 0.15s ease",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (!isActive) {
|
||||||
|
e.currentTarget.style.color = "var(--ll-text-hover)";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = defaultColor;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
Back button (fixed top-right)
|
||||||
|
========================================================================= */
|
||||||
|
|
||||||
|
export function BackButton() {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
className="fixed top-5 right-5 z-[100000] flex items-center justify-center w-10 h-10 rounded-full no-underline"
|
||||||
|
style={{
|
||||||
|
background: "var(--ll-btn-bg)",
|
||||||
|
color: "var(--ll-text-secondary)",
|
||||||
|
boxShadow: "var(--ll-btn-shadow)",
|
||||||
|
transition: "color 0.15s, transform 0.15s",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = "var(--ll-text-hover)";
|
||||||
|
e.currentTarget.style.transform = "scale(1.05)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = "var(--ll-text-secondary)";
|
||||||
|
e.currentTarget.style.transform = "scale(1)";
|
||||||
|
}}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.currentTarget.style.transform = "scale(0.95)";
|
||||||
|
}}
|
||||||
|
onMouseUp={(e) => {
|
||||||
|
e.currentTarget.style.transform = "scale(1.05)";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||||
|
<path
|
||||||
|
d="M12.25 7H1.75M1.75 7L6.125 2.625M1.75 7L6.125 11.375"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
Typography
|
||||||
|
========================================================================= */
|
||||||
|
|
||||||
|
export function SectionHeading({ id, children }: { id: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<h1
|
||||||
|
id={id}
|
||||||
|
className="scroll-mt-[5.25rem]"
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 560,
|
||||||
|
lineHeight: "20px",
|
||||||
|
letterSpacing: "-0.09px",
|
||||||
|
color: "var(--ll-text-primary)",
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
transform: "translateY(-10px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function P({ children, className = "" }: { children: React.ReactNode; className?: string }) {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className={className}
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 475,
|
||||||
|
lineHeight: "20px",
|
||||||
|
letterSpacing: "-0.09px",
|
||||||
|
color: "var(--ll-text-primary)",
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Caption({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
fontSize: "12px",
|
||||||
|
fontWeight: 475,
|
||||||
|
textAlign: "center",
|
||||||
|
lineHeight: "20px",
|
||||||
|
letterSpacing: "-0.09px",
|
||||||
|
color: "var(--ll-text-secondary)",
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Code({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<code className="ll-inline-code">
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
Layout
|
||||||
|
========================================================================= */
|
||||||
|
|
||||||
|
export function Divider() {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "24px 0", display: "flex", alignItems: "center" }}>
|
||||||
|
<div style={{ height: "1px", background: "var(--ll-divider)", flex: 1 }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Section({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<SectionHeading id={id}>{title}</SectionHeading>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function List({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
className="m-0 pl-5"
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 475,
|
||||||
|
lineHeight: "20px",
|
||||||
|
letterSpacing: "-0.09px",
|
||||||
|
color: "var(--ll-text-primary)",
|
||||||
|
listStyleType: "disc",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Li({ children }: { children: React.ReactNode }) {
|
||||||
|
return <li style={{ padding: "0 0 8px 12px" }}>{children}</li>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
Code block with Prism syntax highlighting and line numbers
|
||||||
|
========================================================================= */
|
||||||
|
|
||||||
|
export function CodeBlock({ children, lang = "jsx" }: { children: string; lang?: string }) {
|
||||||
|
const codeRef = useRef<HTMLElement>(null);
|
||||||
|
const lines = children.split("\n");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (codeRef.current) {
|
||||||
|
Prism.highlightElement(codeRef.current);
|
||||||
|
}
|
||||||
|
}, [children]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<figure className="m-0 ll-bleed">
|
||||||
|
<div className="relative">
|
||||||
|
<pre
|
||||||
|
className="overflow-x-auto"
|
||||||
|
style={{
|
||||||
|
background: "var(--ll-code-bg)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="flex"
|
||||||
|
style={{
|
||||||
|
padding: "12px 8px 8px",
|
||||||
|
fontFamily: "var(--ll-font-code)",
|
||||||
|
fontSize: "12px",
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: "18px",
|
||||||
|
letterSpacing: "normal",
|
||||||
|
color: "var(--ll-text-primary)",
|
||||||
|
tabSize: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="select-none shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
color: "var(--ll-code-line-nr)",
|
||||||
|
textAlign: "right",
|
||||||
|
paddingRight: "20px",
|
||||||
|
minWidth: "28px",
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{lines.map((_, i) => {
|
||||||
|
return (
|
||||||
|
<span key={i} className="block">
|
||||||
|
{i + 1}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<code
|
||||||
|
ref={codeRef}
|
||||||
|
className={`language-${lang}`}
|
||||||
|
style={{ whiteSpace: "pre", background: "none", padding: 0 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</figure>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
Chart placeholder (dark box with animated line)
|
||||||
|
========================================================================= */
|
||||||
|
|
||||||
|
export function ChartPlaceholder({ height = 200, label }: { height?: number; label?: string }) {
|
||||||
|
return (
|
||||||
|
<div className="ll-bleed">
|
||||||
|
<div
|
||||||
|
className="w-full overflow-hidden relative"
|
||||||
|
style={{
|
||||||
|
height: `${height}px`,
|
||||||
|
background: "rgb(17, 17, 17)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 550 200"
|
||||||
|
className="absolute inset-0 w-full h-full"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="chartFill" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stopColor="#3b82f6" stopOpacity="0.3" />
|
||||||
|
<stop offset="100%" stopColor="#3b82f6" stopOpacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
d="M0,140 C30,135 60,120 90,125 C120,130 150,100 180,95 C210,90 240,110 270,105 C300,100 330,80 360,85 C390,90 420,70 450,65 C480,60 510,75 550,60"
|
||||||
|
fill="none"
|
||||||
|
stroke="#3b82f6"
|
||||||
|
strokeWidth="2"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0,140 C30,135 60,120 90,125 C120,130 150,100 180,95 C210,90 240,110 270,105 C300,100 330,80 360,85 C390,90 420,70 450,65 C480,60 510,75 550,60 L550,200 L0,200 Z"
|
||||||
|
fill="url(#chartFill)"
|
||||||
|
/>
|
||||||
|
<circle cx="550" cy="60" r="4" fill="#3b82f6">
|
||||||
|
<animate attributeName="r" values="4;6;4" dur="2s" repeatCount="indefinite" />
|
||||||
|
<animate attributeName="opacity" values="1;0.6;1" dur="2s" repeatCount="indefinite" />
|
||||||
|
</circle>
|
||||||
|
</svg>
|
||||||
|
{label && (
|
||||||
|
<div
|
||||||
|
className="absolute top-3 right-3 px-2 py-1 rounded text-xs"
|
||||||
|
style={{
|
||||||
|
background: "rgba(59, 130, 246, 0.15)",
|
||||||
|
color: "#3b82f6",
|
||||||
|
fontFamily: "var(--ll-font-code)",
|
||||||
|
fontWeight: 475,
|
||||||
|
fontSize: "11px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
Comparison table
|
||||||
|
========================================================================= */
|
||||||
|
|
||||||
|
export function PropsTable({
|
||||||
|
title,
|
||||||
|
rows,
|
||||||
|
}: {
|
||||||
|
title?: string;
|
||||||
|
rows: Array<[string, string, string]>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-full overflow-x-auto" style={{ padding: "8px 0" }}>
|
||||||
|
{title && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
fontSize: "11px",
|
||||||
|
fontWeight: 400,
|
||||||
|
color: "var(--ll-text-muted)",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
letterSpacing: "0.02em",
|
||||||
|
padding: "0 0 6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<table
|
||||||
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
borderSpacing: 0,
|
||||||
|
borderCollapse: "collapse",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{["Prop", "Type", "Default"].map((header) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header}
|
||||||
|
className="text-left"
|
||||||
|
style={{
|
||||||
|
padding: "4.8px 12px 4.8px 0",
|
||||||
|
fontSize: "11px",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
color: "var(--ll-text-muted)",
|
||||||
|
borderBottom: "1px solid var(--ll-border)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{header}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rows.map(([prop, type, def]) => {
|
||||||
|
return (
|
||||||
|
<tr key={prop}>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
padding: "4.8px 12px 4.8px 0",
|
||||||
|
fontSize: "11px",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontFamily: "var(--ll-font-code)",
|
||||||
|
color: "var(--ll-text-primary)",
|
||||||
|
borderBottom: "1px solid var(--ll-border)",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{prop}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
padding: "4.8px 12px 4.8px 0",
|
||||||
|
fontSize: "11px",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontFamily: "var(--ll-font-code)",
|
||||||
|
color: "var(--ll-text-muted)",
|
||||||
|
borderBottom: "1px solid var(--ll-border)",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{type}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
padding: "4.8px 12px 4.8px 0",
|
||||||
|
fontSize: "11px",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontFamily: "var(--ll-font-code)",
|
||||||
|
color: "var(--ll-text-muted)",
|
||||||
|
borderBottom: "1px solid var(--ll-border)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{def}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
Page shell — wraps content with layout, TOC, back button, animations
|
||||||
|
========================================================================= */
|
||||||
|
|
||||||
|
export function EditorialPage({
|
||||||
|
toc,
|
||||||
|
logo,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
toc: Array<{ label: string; href: string }>;
|
||||||
|
logo?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="liveline-page relative min-h-screen overflow-x-hidden"
|
||||||
|
style={{
|
||||||
|
background: "var(--ll-bg)",
|
||||||
|
color: "var(--ll-text-primary)",
|
||||||
|
fontFamily: "var(--ll-font-primary)",
|
||||||
|
WebkitFontSmoothing: "antialiased",
|
||||||
|
textRendering: "optimizeLegibility",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BackButton />
|
||||||
|
<TableOfContents items={toc} logo={logo} />
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="mx-auto"
|
||||||
|
style={{ width: "550px", maxWidth: "calc(100% - 2rem)", padding: "0 1rem 6rem" }}
|
||||||
|
>
|
||||||
|
<div style={{ height: "80px" }} />
|
||||||
|
|
||||||
|
<article className="liveline-article flex flex-col gap-[24px]">
|
||||||
|
{children}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+277
-829
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user