Skip to content

Commit cf305e5

Browse files
committed
feat(luxe): inject generative luxe art backgrounds to journal and archive cards
1 parent e1a946a commit cf305e5

5 files changed

Lines changed: 152 additions & 1 deletion

File tree

src/pages/apps/github-thumbnail/GithubThumbnailGeneratorPage.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const THEME_OPTIONS = [
7474
{ value: 'missionControl', label: 'MISSION_CONTROL' },
7575
{ value: 'etherealGlow', label: 'ETHEREAL_GLOW' },
7676
{ value: 'boldMinimal', label: 'CLASSIFIED_DOC' },
77+
{ value: 'luxe', label: 'LUXE_ART' },
7778
];
7879

7980
const GithubThumbnailGeneratorPage = () => {

src/pages/apps/github-thumbnail/themes.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import { missionControl } from './themes/missionControl';
5555
import { etherealGlow } from './themes/etherealGlow';
5656
import { boldMinimal } from './themes/boldMinimal';
5757

58+
import { luxe } from './themes/luxe';
59+
5860
export const themeRenderers = {
5961
modern,
6062
brutalist,
@@ -112,4 +114,5 @@ export const themeRenderers = {
112114
missionControl,
113115
etherealGlow,
114116
boldMinimal,
117+
luxe,
115118
};
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { wrapText } from '../utils';
2+
3+
export const luxe = (ctx, width, height, scale, data) => {
4+
const { repoOwner, repoName, description, language, stars, primaryColor, secondaryColor, bgColor } = data;
5+
6+
// LCG Random Generator (Ported from LuxeArt.jsx)
7+
const seed = repoName + repoOwner;
8+
let h = 0xdeadbeef;
9+
for (let i = 0; i < seed.length; i++) {
10+
h = Math.imul(h ^ seed.charCodeAt(i), 2654435761);
11+
}
12+
const rng = () => {
13+
h = Math.imul(h ^ (h >>> 16), 2246822507);
14+
h = Math.imul(h ^ (h >>> 13), 3266489909);
15+
return ((h ^= h >>> 16) >>> 0) / 4294967296;
16+
};
17+
18+
// Background
19+
ctx.fillStyle = '#EBEBEB'; // Light grey/white as in LuxeArt
20+
ctx.fillRect(0, 0, width, height);
21+
22+
// Organic Curves (Ported from LuxeArt.jsx)
23+
const curveCount = 8 + Math.floor(rng() * 5);
24+
const baseHue = Math.floor(rng() * 360);
25+
26+
ctx.save();
27+
// We'll use multiply blend mode for the curves as in LuxeArt
28+
ctx.globalCompositeOperation = 'multiply';
29+
30+
for (let i = 0; i < curveCount; i++) {
31+
const points = [];
32+
const segments = 4;
33+
const startY = rng() * height;
34+
35+
points.push({ x: 0, y: startY });
36+
37+
for (let j = 1; j <= segments; j++) {
38+
points.push({
39+
x: (j / segments) * width,
40+
y: startY + (rng() - 0.5) * (height * 0.5),
41+
});
42+
}
43+
44+
// Draw Smooth Curve
45+
ctx.beginPath();
46+
ctx.moveTo(points[0].x, points[0].y);
47+
48+
for (let j = 0; j < points.length - 1; j++) {
49+
const p0 = points[j];
50+
const p1 = points[j + 1];
51+
const cp1x = p0.x + (p1.x - p0.x) / 2;
52+
const cp1y = p0.y;
53+
const cp2x = p0.x + (p1.x - p0.x) / 2;
54+
const cp2y = p1.y;
55+
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p1.x, p1.y);
56+
}
57+
58+
// Close shape to bottom
59+
ctx.lineTo(width, height);
60+
ctx.lineTo(0, height);
61+
ctx.closePath();
62+
63+
// Color generation (Ported from LuxeArt.jsx)
64+
const isGold = rng() > 0.8;
65+
const hue = isGold ? 45 : baseHue + (rng() - 0.5) * 30;
66+
const sat = isGold ? 60 : 0;
67+
const lit = isGold ? 60 : 90 - i * 5;
68+
const opacity = 0.05 + rng() * 0.15;
69+
70+
ctx.fillStyle = `hsla(${hue}, ${sat}%, ${lit}%, ${opacity})`;
71+
ctx.fill();
72+
73+
ctx.strokeStyle = `hsla(${hue}, ${sat}%, ${lit - 20}%, ${opacity * 2})`;
74+
ctx.lineWidth = 1 * scale;
75+
ctx.stroke();
76+
}
77+
ctx.restore();
78+
79+
// Noise specks
80+
ctx.save();
81+
for (let k = 0; k < 100; k++) {
82+
const cx = rng() * width;
83+
const cy = rng() * height;
84+
const r = rng() * 3 * scale;
85+
ctx.beginPath();
86+
ctx.arc(cx, cy, r, 0, Math.PI * 2);
87+
ctx.fillStyle = 'rgba(0,0,0,0.1)';
88+
ctx.fill();
89+
}
90+
ctx.restore();
91+
92+
// Typography & Content
93+
const serifFont = '"Playfair Display", "Times New Roman", serif';
94+
const monoFont = '"JetBrains Mono", "Courier New", monospace';
95+
96+
// Decorative Line
97+
ctx.strokeStyle = '#000';
98+
ctx.lineWidth = 1 * scale;
99+
ctx.beginPath();
100+
ctx.moveTo(width * 0.1, height * 0.2);
101+
ctx.lineTo(width * 0.9, height * 0.2);
102+
ctx.stroke();
103+
104+
// Repo Owner (Brand)
105+
ctx.fillStyle = '#000';
106+
ctx.font = `italic 300 ${24 * scale}px ${serifFont}`;
107+
ctx.textAlign = 'left';
108+
ctx.letterSpacing = '10px';
109+
ctx.fillText(repoOwner.toUpperCase(), width * 0.1, height * 0.15);
110+
111+
// Repo Name
112+
ctx.font = `normal 900 ${120 * scale}px ${serifFont}`;
113+
ctx.letterSpacing = 'normal';
114+
ctx.fillText(repoName, width * 0.1, height * 0.45);
115+
116+
// Description
117+
ctx.font = `italic 300 ${32 * scale}px ${serifFont}`;
118+
ctx.fillStyle = '#333';
119+
wrapText(ctx, description, width * 0.1, height * 0.6, width * 0.6, 45 * scale);
120+
121+
// Language & Stats (Bottom Bar)
122+
ctx.font = `bold ${20 * scale}px ${monoFont}`;
123+
ctx.fillStyle = '#000';
124+
ctx.textAlign = 'right';
125+
ctx.fillText(language.toUpperCase(), width * 0.9, height * 0.85);
126+
127+
ctx.font = `normal ${20 * scale}px ${monoFont}`;
128+
ctx.fillText(`${stars} STARS // ${repoOwner}`, width * 0.9, height * 0.9);
129+
130+
// Accent Circle
131+
ctx.beginPath();
132+
ctx.arc(width * 0.85, height * 0.4, 40 * scale, 0, Math.PI * 2);
133+
ctx.strokeStyle = '#000';
134+
ctx.lineWidth = 0.5 * scale;
135+
ctx.stroke();
136+
};

src/pages/luxe-views/LuxeBlogPage.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '@phosphor-icons/react';
1010
import { fetchAllBlogPosts } from '../../utils/dataUtils';
1111
import Seo from '../../components/Seo';
12+
import LuxeArt from '../../components/LuxeArt';
1213

1314
const FILTERS = [
1415
{ id: 'all', label: 'All' },
@@ -202,7 +203,10 @@ const LuxeBlogPage = () => {
202203
backgroundSize: '20px 20px',
203204
}}
204205
/>
205-
<h2 className="font-playfairDisplay text-[#1A1A1A] leading-tight group-hover:scale-105 transition-transform duration-700 ease-out text-3xl md:text-4xl">
206+
<div className="absolute inset-0 opacity-0 group-hover:opacity-[0.04] transition-opacity duration-1000 pointer-events-none">
207+
<LuxeArt seed={item.title} colorful={false} className="w-full h-full mix-blend-multiply" />
208+
</div>
209+
<h2 className="font-playfairDisplay text-[#1A1A1A] leading-tight group-hover:scale-105 transition-transform duration-700 ease-out text-3xl md:text-4xl relative z-10">
206210
{item.title}
207211
</h2>
208212
<p className="font-outfit text-xs md:text-sm text-[#1A1A1A]/60 line-clamp-2 mt-4 max-w-xs mx-auto opacity-0 group-hover:opacity-100 transition-opacity duration-500">
@@ -237,6 +241,9 @@ const LuxeBlogPage = () => {
237241
<div className="flex flex-col md:flex-row items-stretch">
238242
<div className="hidden md:flex w-48 bg-[#FAFAF8] border-r border-[#1A1A1A]/5 items-center justify-center p-6 relative overflow-hidden shrink-0">
239243
<div className="absolute inset-0 opacity-[0.03] pointer-events-none" style={{ backgroundImage: 'radial-gradient(#1A1A1A 1px, transparent 1px)', backgroundSize: '10px 10px' }} />
244+
<div className="absolute inset-0 opacity-[0.02] group-hover:opacity-[0.06] transition-opacity duration-700 pointer-events-none">
245+
<LuxeArt seed={item.title} colorful={false} className="w-full h-full mix-blend-multiply transition-transform duration-1000 group-hover:scale-110" />
246+
</div>
240247
<div className="text-center relative z-10">
241248
<div className="font-playfairDisplay text-4xl text-[#1A1A1A] group-hover:italic transition-all">
242249
{new Date(item.date).getDate().toString().padStart(2, '0')}

src/pages/luxe-views/LuxeLogsPage.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import Seo from '../../components/Seo';
1111
import piml from 'piml';
1212
import colors from '../../config/colors';
13+
import LuxeArt from '../../components/LuxeArt';
1314

1415
const categories = [
1516
'Book',
@@ -180,6 +181,9 @@ const LuxeLogsPage = () => {
180181
className="group relative block aspect-square bg-white rounded-xl overflow-hidden border border-[#1A1A1A]/5 shadow-sm hover:shadow-xl transition-all duration-500"
181182
>
182183
<div className="absolute inset-0 opacity-[0.02] pointer-events-none" style={{ backgroundImage: 'radial-gradient(#1A1A1A 1px, transparent 1px)', backgroundSize: '16px 16px' }} />
184+
<div className="absolute inset-0 opacity-[0.02] group-hover:opacity-[0.06] transition-opacity duration-700 pointer-events-none">
185+
<LuxeArt seed={log.title} colorful={true} className="w-full h-full mix-blend-multiply transition-transform duration-1000 group-hover:scale-110" />
186+
</div>
183187
<div className="absolute inset-0 p-6 md:p-8 flex flex-col justify-between">
184188
<div className="flex justify-between items-start">
185189
<span className="font-outfit text-[10px] uppercase tracking-widest text-[#1A1A1A]/40">

0 commit comments

Comments
 (0)