Skip to content

Commit d3ad365

Browse files
committed
feat: interactive 3D logo, OKLCH palette system, syntax highlighting
PlatonicLogo: drag-to-spin with quaternion physics, click-face actions (shape/play/spin/see-through), shared rotation singleton across instances, morph-scale transitions, touch support, pre-hydration CSS tilt. Palette: 20-step OKLCH hue ring with CVD-optimized light/dark tiers via qlab separate+harmonize pipeline. All semantic colors (accent, blog, error) derive from palette entries. withAlpha helper for alpha variants. Syntax: dual-mode chromatic tokens (blue/purple/green/red), tagged template and import binding detection, ampersand neutralization, rainbow inline code. Favicon: cube SVG with soft-light blend, light/dark palette CSS switching. Generator script at scripts/gen-favicon.mjs.
1 parent d2a07d7 commit d3ad365

28 files changed

Lines changed: 4733 additions & 718 deletions

app/homepage.css

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
.hero-header {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
color: var(--sc-color-text);
6+
background: none;
7+
position: relative;
8+
z-index: 20;
9+
10+
box-sizing: border-box;
11+
padding-bottom: 3rem;
12+
}
13+
14+
.hero-tagline {
15+
font-family: var(--font-display);
16+
font-weight: var(--sc-fontWeight-display);
17+
font-size: 2.5rem;
18+
line-height: 1.15;
19+
margin: 1.25em 0 0.5em;
20+
text-align: left;
21+
}
22+
23+
.hero-subtitle {
24+
font-size: 1.125rem;
25+
font-weight: 400;
26+
margin: 0;
27+
max-inline-size: 36ch;
28+
text-wrap: balance;
29+
text-align: left;
30+
opacity: 0.85;
31+
}
32+
33+
.hero-used-by {
34+
color: var(--sc-color-textMuted);
35+
font-size: var(--sc-text-sm, 0.875rem);
36+
font-weight: 600;
37+
margin: 2.5rem 0 0.5rem;
38+
max-width: none;
39+
opacity: 0.8;
40+
text-align: center;
41+
text-transform: uppercase;
42+
}
43+
44+
@media (max-width: 62.5em) {
45+
.hero-tagline,
46+
.hero-subtitle {
47+
text-align: center;
48+
max-inline-size: 44ch;
49+
}
50+
}

app/layout.tsx

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,111 @@
11
import { Metadata, Viewport } from 'next';
2-
import { Karla, JetBrains_Mono } from 'next/font/google';
2+
import { Figtree, Inter, Google_Sans_Code } from 'next/font/google';
3+
import './theme-base.css';
34
import StyledComponentsRegistry from '../lib/registry';
45
import ClientLayout from '../components/ClientLayout';
6+
import { BASE_URL } from './url';
7+
import { getPosts } from '@/utils/blog.server';
58

6-
const karla = Karla({
9+
const inter = Inter({
710
subsets: ['latin'],
8-
weight: ['200', '400', '700'],
911
display: 'swap',
10-
variable: '--font-body',
12+
variable: '--font-sans',
1113
});
1214

13-
const jetbrainsMono = JetBrains_Mono({
15+
const figtree = Figtree({
16+
subsets: ['latin'],
17+
display: 'swap',
18+
variable: '--font-display',
19+
});
20+
21+
const googleSansCode = Google_Sans_Code({
1422
subsets: ['latin'],
15-
weight: ['400', '700'],
1623
display: 'swap',
1724
variable: '--font-mono',
25+
adjustFontFallback: false,
1826
});
1927

2028
export const metadata: Metadata = {
2129
title: {
2230
default: 'styled-components',
2331
template: '%s | styled-components',
2432
},
25-
description:
26-
'Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress',
33+
description: 'CSS for the <Component> Age',
2734
icons: {
28-
icon: '/favicon.png',
35+
icon: [
36+
{ url: '/favicon.svg', type: 'image/svg+xml' },
37+
{ url: '/favicon.png', type: 'image/png' },
38+
],
39+
shortcut: '/atom.png',
2940
},
3041
manifest: '/manifest.json',
3142
authors: [{ name: 'styled-components' }],
43+
metadataBase: new URL(BASE_URL),
44+
openGraph: {
45+
type: 'website',
46+
locale: 'en_US',
47+
siteName: 'styled-components',
48+
images: [{ url: '/atom.png', width: 652, height: 652 }],
49+
},
50+
twitter: {
51+
card: 'summary_large_image',
52+
site: '@mxstbr',
53+
creator: '@mxstbr',
54+
images: ['/meta.png'],
55+
},
56+
verification: {
57+
google: 'lWntYW6AWVMcShSIWLmOzKr8Wyek2TR-zuQn6_XGu_c',
58+
},
3259
};
3360

3461
export const viewport: Viewport = {
3562
width: 'device-width',
3663
initialScale: 1.0,
3764
userScalable: true,
38-
themeColor: '#da936a',
65+
// Brand accent (oklch(0.50 0.16 290)) in sRGB hex. Mobile browser chrome
66+
// can't resolve oklch() or CSS vars here, so this is the one place the
67+
// brand purple has to live as a literal.
68+
themeColor: '#654DB6',
3969
};
4070

41-
export default function RootLayout({ children }: { children: React.ReactNode }) {
71+
/**
72+
* Inline script to set the theme before first paint, preventing FOUC.
73+
* Reads localStorage, falls back to system preference, defaults to light.
74+
*/
75+
const themeScript = `
76+
(function() {
77+
try {
78+
var d = document.documentElement;
79+
var stored = localStorage.getItem('theme');
80+
if (stored === 'dark' || stored === 'light') {
81+
d.classList.add(stored);
82+
if (stored === 'dark') d.dataset.theme = 'dark';
83+
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
84+
d.classList.add('dark');
85+
d.dataset.theme = 'dark';
86+
}
87+
} catch (e) {}
88+
if (location.pathname === '/') d.setAttribute('data-hero-logo-visible', '');
89+
})();
90+
`;
91+
92+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
93+
const posts = await getPosts();
94+
const latestPost = posts[0] ? { title: posts[0].title, slug: posts[0].slug } : null;
95+
4296
return (
43-
<html data-theme="dark" lang="en" className={`${karla.variable} ${jetbrainsMono.variable}`}>
97+
<html
98+
lang="en"
99+
suppressHydrationWarning
100+
className={`${inter.variable} ${figtree.variable} ${googleSansCode.variable}`}
101+
>
44102
<head>
103+
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
45104
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3" />
46105
</head>
47106
<body>
48107
<StyledComponentsRegistry>
49-
<ClientLayout>
108+
<ClientLayout latestPost={latestPost}>
50109
<main id="main-content">{children}</main>
51110
</ClientLayout>
52111
</StyledComponentsRegistry>

app/not-found.tsx

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
'use client';
2+
3+
import styled, { keyframes } from 'styled-components';
4+
import Link from '@/components/Link';
5+
import docsJson from './docs.json';
6+
import { theme, font } from '@/utils/theme';
7+
import titleToDash from '@/utils/titleToDash';
8+
9+
const faqPage = docsJson.pages.find(p => p.pathname === 'faqs');
10+
const faqs = faqPage?.sections ?? [];
11+
12+
export default function NotFound() {
13+
return (
14+
<Wrapper>
15+
<CodeBlock>
16+
<Comment>{"// This component doesn't exist yet"}</Comment>
17+
<Line>
18+
<Keyword>const</Keyword> <ComponentName>MissingPage</ComponentName> = <FnName>styled</FnName>
19+
<Punct>.</Punct>
20+
<FnName>div</FnName>
21+
<Tick>`</Tick>
22+
</Line>
23+
<CSSLine>
24+
<Prop>status</Prop>
25+
<Punct>:</Punct> <Value>404</Value>
26+
<Punct>;</Punct>
27+
</CSSLine>
28+
<CSSLine>
29+
<Prop>display</Prop>
30+
<Punct>:</Punct> <Value>none</Value>
31+
<Punct>;</Punct>
32+
</CSSLine>
33+
<Line>
34+
<Tick>`</Tick>
35+
<Punct>;</Punct>
36+
</Line>
37+
</CodeBlock>
38+
39+
<Message>
40+
This page doesn't exist.{' '}
41+
<Link href="/" variant="inline">
42+
Go home
43+
</Link>{' '}
44+
or{' '}
45+
<Link href="/docs" variant="inline">
46+
browse the docs
47+
</Link>
48+
.
49+
</Message>
50+
51+
<FAQSection>
52+
<FAQHeading>Frequently Asked Questions</FAQHeading>
53+
<FAQGrid>
54+
{faqs.map(faq => (
55+
<FAQLink key={faq.title} href={`/docs/faqs#${titleToDash(faq.title)}`} variant="inline">
56+
{faq.title}
57+
</FAQLink>
58+
))}
59+
</FAQGrid>
60+
</FAQSection>
61+
</Wrapper>
62+
);
63+
}
64+
65+
const blink = keyframes`
66+
0%, 100% { opacity: 1; }
67+
50% { opacity: 0; }
68+
`;
69+
70+
const Wrapper = styled.div`
71+
display: flex;
72+
flex-direction: column;
73+
align-items: center;
74+
justify-content: center;
75+
min-height: 60vh;
76+
padding: 96px ${theme.space[8]};
77+
font-family: ${font.sans};
78+
color: ${theme.color.text};
79+
`;
80+
81+
const CodeBlock = styled.pre`
82+
font-family: ${font.mono};
83+
font-size: clamp(${theme.text.sm}, 2.5vw, ${theme.text.lg});
84+
line-height: 1.7;
85+
margin: 0 0 ${theme.space[6]};
86+
padding: ${theme.space[6]} ${theme.space[8]};
87+
background: ${theme.color.codeBg};
88+
color: ${theme.color.codeText};
89+
border-radius: ${theme.radius.lg};
90+
border: 1px solid ${theme.color.border};
91+
box-shadow: 0 1px 3px ${theme.color.shadow};
92+
max-width: 100%;
93+
overflow-x: auto;
94+
95+
&::after {
96+
content: '|';
97+
animation: ${blink} 1s step-end infinite;
98+
color: ${theme.color.accent};
99+
font-weight: ${theme.fontWeight.bold};
100+
}
101+
`;
102+
103+
const Line = styled.span`
104+
display: block;
105+
`;
106+
107+
const CSSLine = styled(Line)`
108+
padding-left: 2ch;
109+
`;
110+
111+
const Comment = styled(Line)`
112+
color: ${theme.color.codeComment};
113+
font-style: italic;
114+
`;
115+
116+
const Keyword = styled.span`
117+
color: ${theme.color.codeComment};
118+
`;
119+
120+
const ComponentName = styled.span`
121+
color: ${theme.color.codeText};
122+
`;
123+
124+
const FnName = styled.span`
125+
color: ${theme.color.codeFunction};
126+
`;
127+
128+
const Punct = styled.span`
129+
color: ${theme.color.codeComment};
130+
`;
131+
132+
const Tick = styled.span`
133+
color: ${theme.color.codeString};
134+
`;
135+
136+
const Prop = styled.span`
137+
color: ${theme.color.codeDeclaration};
138+
`;
139+
140+
const Value = styled.span`
141+
color: ${theme.color.codeText};
142+
`;
143+
144+
const Message = styled.p`
145+
font-size: ${theme.text.base};
146+
color: ${theme.color.textSecondary};
147+
margin: 0 0 ${theme.space[10]};
148+
text-align: center;
149+
`;
150+
151+
const FAQSection = styled.section`
152+
width: 100%;
153+
max-width: 64ch;
154+
`;
155+
156+
const FAQHeading = styled.h2`
157+
font-size: ${theme.text.sm};
158+
font-weight: ${theme.fontWeight.semibold};
159+
text-transform: uppercase;
160+
letter-spacing: 0.05em;
161+
color: ${theme.color.textMuted};
162+
margin: 0 0 ${theme.space[4]};
163+
text-align: center;
164+
`;
165+
166+
const FAQGrid = styled.div`
167+
display: flex;
168+
flex-wrap: wrap;
169+
gap: ${theme.space[2]};
170+
justify-content: center;
171+
`;
172+
173+
const FAQLink = styled(Link)`
174+
font-size: ${theme.text.xs};
175+
padding: ${theme.space[1]} ${theme.space[3]};
176+
border-radius: ${theme.radius.md};
177+
border: 1px solid ${theme.color.border};
178+
background: ${theme.color.surface};
179+
transition:
180+
background ${theme.duration.normal},
181+
border-color ${theme.duration.normal};
182+
183+
&:hover {
184+
background: ${theme.color.accentSubtle};
185+
border-color: ${theme.color.accent};
186+
}
187+
`;

0 commit comments

Comments
 (0)