mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 02:53:51 +00:00
Add darkbox palette themes
This commit is contained in:
@@ -117,7 +117,14 @@ const Stats = () => {
|
|||||||
|
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
.bg-gradient-text {
|
.bg-gradient-text {
|
||||||
background: linear-gradient(90deg, #fbbf24, #f59e0b, #d97706, #b45309, #f59e0b, #fbbf24);
|
background: linear-gradient(90deg,
|
||||||
|
rgb(var(--color-yellow-bright)),
|
||||||
|
rgb(var(--color-orange-bright)),
|
||||||
|
rgb(var(--color-orange)),
|
||||||
|
rgb(var(--color-yellow)),
|
||||||
|
rgb(var(--color-orange-bright)),
|
||||||
|
rgb(var(--color-yellow-bright))
|
||||||
|
);
|
||||||
background-size: 200% auto;
|
background-size: 200% auto;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
|
||||||
interface Cell {
|
interface Cell {
|
||||||
alive: boolean;
|
alive: boolean;
|
||||||
next: boolean;
|
next: boolean;
|
||||||
@@ -58,6 +59,43 @@ const RIPPLE_SPEED = 0.02; // Speed of ripple propagation
|
|||||||
const RIPPLE_ELEVATION_FACTOR = 4; // Height of ripple wave
|
const RIPPLE_ELEVATION_FACTOR = 4; // Height of ripple wave
|
||||||
const ELEVATION_FACTOR = 8; // Max height for 3D effect - reduced for more subtle effect
|
const ELEVATION_FACTOR = 8; // Max height for 3D effect - reduced for more subtle effect
|
||||||
|
|
||||||
|
const FALLBACK_PALETTE: [number, number, number][] = [
|
||||||
|
[204, 36, 29], [152, 151, 26], [215, 153, 33],
|
||||||
|
[69, 133, 136], [177, 98, 134], [104, 157, 106]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Read palette from current CSS variables
|
||||||
|
function readPaletteFromCSS(): [number, number, number][] {
|
||||||
|
try {
|
||||||
|
const style = getComputedStyle(document.documentElement);
|
||||||
|
const keys = ["--color-red", "--color-green", "--color-yellow", "--color-blue", "--color-purple", "--color-aqua"];
|
||||||
|
const palette: [number, number, number][] = [];
|
||||||
|
for (const key of keys) {
|
||||||
|
const val = style.getPropertyValue(key).trim();
|
||||||
|
if (val) {
|
||||||
|
const parts = val.split(" ").map(Number);
|
||||||
|
if (parts.length === 3 && parts.every((n) => !isNaN(n))) {
|
||||||
|
palette.push([parts[0], parts[1], parts[2]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return palette.length > 0 ? palette : FALLBACK_PALETTE;
|
||||||
|
} catch {
|
||||||
|
return FALLBACK_PALETTE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readBgFromCSS(): string {
|
||||||
|
try {
|
||||||
|
const val = getComputedStyle(document.documentElement).getPropertyValue("--color-background").trim();
|
||||||
|
if (val) {
|
||||||
|
const [r, g, b] = val.split(" ");
|
||||||
|
return `rgb(${r}, ${g}, ${b})`;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return "rgb(0, 0, 0)";
|
||||||
|
}
|
||||||
|
|
||||||
const Background: React.FC<BackgroundProps> = ({
|
const Background: React.FC<BackgroundProps> = ({
|
||||||
layout = 'index',
|
layout = 'index',
|
||||||
position = 'left'
|
position = 'left'
|
||||||
@@ -68,6 +106,8 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
const lastUpdateTimeRef = useRef<number>(0);
|
const lastUpdateTimeRef = useRef<number>(0);
|
||||||
const lastCycleTimeRef = useRef<number>(0);
|
const lastCycleTimeRef = useRef<number>(0);
|
||||||
const resizeTimeoutRef = useRef<NodeJS.Timeout>();
|
const resizeTimeoutRef = useRef<NodeJS.Timeout>();
|
||||||
|
const paletteRef = useRef<[number, number, number][]>(FALLBACK_PALETTE);
|
||||||
|
const bgColorRef = useRef<string>("rgb(0, 0, 0)");
|
||||||
const mouseRef = useRef<MousePosition>({
|
const mouseRef = useRef<MousePosition>({
|
||||||
x: -1000,
|
x: -1000,
|
||||||
y: -1000,
|
y: -1000,
|
||||||
@@ -78,15 +118,8 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const randomColor = (): [number, number, number] => {
|
const randomColor = (): [number, number, number] => {
|
||||||
const colors = [
|
const palette = paletteRef.current;
|
||||||
[204, 36, 29], // red
|
return palette[Math.floor(Math.random() * palette.length)];
|
||||||
[152, 151, 26], // green
|
|
||||||
[215, 153, 33], // yellow
|
|
||||||
[69, 133, 136], // blue
|
|
||||||
[177, 98, 134], // purple
|
|
||||||
[104, 157, 106] // aqua
|
|
||||||
];
|
|
||||||
return colors[Math.floor(Math.random() * colors.length)];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCellSize = () => {
|
const getCellSize = () => {
|
||||||
@@ -535,6 +568,31 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
window.addEventListener('mousemove', handleMouseMove, { signal });
|
window.addEventListener('mousemove', handleMouseMove, { signal });
|
||||||
window.addEventListener('mouseup', handleMouseUp, { signal });
|
window.addEventListener('mouseup', handleMouseUp, { signal });
|
||||||
|
|
||||||
|
// Read theme colors from CSS variables
|
||||||
|
paletteRef.current = readPaletteFromCSS();
|
||||||
|
bgColorRef.current = readBgFromCSS();
|
||||||
|
|
||||||
|
// Listen for theme changes and update colors
|
||||||
|
const handleThemeChanged = () => {
|
||||||
|
paletteRef.current = readPaletteFromCSS();
|
||||||
|
bgColorRef.current = readBgFromCSS();
|
||||||
|
|
||||||
|
if (gridRef.current) {
|
||||||
|
const grid = gridRef.current;
|
||||||
|
const palette = paletteRef.current;
|
||||||
|
for (let i = 0; i < grid.cols; i++) {
|
||||||
|
for (let j = 0; j < grid.rows; j++) {
|
||||||
|
const cell = grid.cells[i][j];
|
||||||
|
if (cell.alive && cell.opacity > 0.01) {
|
||||||
|
cell.baseColor = palette[Math.floor(Math.random() * palette.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("theme-changed", handleThemeChanged, { signal });
|
||||||
|
|
||||||
const handleVisibilityChange = () => {
|
const handleVisibilityChange = () => {
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
// Tab is hidden
|
// Tab is hidden
|
||||||
@@ -584,7 +642,7 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw frame
|
// Draw frame
|
||||||
ctx.fillStyle = '#000000';
|
ctx.fillStyle = bgColorRef.current;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
if (gridRef.current) {
|
if (gridRef.current) {
|
||||||
@@ -701,10 +759,10 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
<div className={getContainerClasses()}>
|
<div className={getContainerClasses()}>
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
className="w-full h-full bg-black"
|
className="w-full h-full bg-background"
|
||||||
style={{ cursor: 'default' }} // Changed from cursor-pointer to default
|
style={{ cursor: 'default' }} // Changed from cursor-pointer to default
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-black/30 backdrop-blur-sm pointer-events-none" />
|
<div className="absolute inset-0 bg-background/30 backdrop-blur-sm pointer-events-none" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Links } from "@/components/header/links";
|
import { Links } from "@/components/header/links";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header({ transparent = false }: { transparent?: boolean }) {
|
||||||
const [isClient, setIsClient] = useState(false);
|
const [isClient, setIsClient] = useState(false);
|
||||||
const [visible, setVisible] = useState(true);
|
const [visible, setVisible] = useState(true);
|
||||||
const [lastScrollY, setLastScrollY] = useState(0);
|
const [lastScrollY, setLastScrollY] = useState(0);
|
||||||
@@ -34,7 +34,7 @@ export default function Header() {
|
|||||||
return linkHref !== "/" && path.startsWith(linkHref);
|
return linkHref !== "/" && path.startsWith(linkHref);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isIndexPage = checkIsActive("/");
|
const isIndexPage = transparent || checkIsActive("/");
|
||||||
const headerLinks = Links.map((link) => {
|
const headerLinks = Links.map((link) => {
|
||||||
const isActive = checkIsActive(link.href);
|
const isActive = checkIsActive(link.href);
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export default function Header() {
|
|||||||
className={`
|
className={`
|
||||||
relative inline-block
|
relative inline-block
|
||||||
${link.color}
|
${link.color}
|
||||||
${!isIndexPage ? 'bg-black' : ''}
|
${!isIndexPage ? 'bg-background' : ''}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -94,13 +94,13 @@ export default function Header() {
|
|||||||
<div className={`
|
<div className={`
|
||||||
w-full flex flex-row items-center justify-center
|
w-full flex flex-row items-center justify-center
|
||||||
pointer-events-none
|
pointer-events-none
|
||||||
${!isIndexPage ? 'bg-black md:bg-transparent' : ''}
|
${!isIndexPage ? 'bg-background md:bg-transparent' : ''}
|
||||||
`}>
|
`}>
|
||||||
<div className={`
|
<div className={`
|
||||||
w-full md:w-auto flex flex-row pt-1 px-2 text-lg lg:text-3xl md:text-2xl
|
w-full md:w-auto flex flex-row pt-1 px-2 text-lg lg:text-3xl md:text-2xl
|
||||||
items-center justify-between md:justify-center space-x-2 md:space-x-10 lg:space-x-20 md:py-2
|
items-center justify-between md:justify-center space-x-2 md:space-x-10 lg:space-x-20 md:py-2
|
||||||
pointer-events-none [&_a]:pointer-events-auto
|
pointer-events-none [&_a]:pointer-events-auto
|
||||||
${!isIndexPage ? 'bg-black md:px-20' : ''}
|
${!isIndexPage ? 'bg-background md:px-20' : ''}
|
||||||
`}>
|
`}>
|
||||||
{headerLinks}
|
{headerLinks}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
98
src/src/components/theme-switcher/index.tsx
Normal file
98
src/src/components/theme-switcher/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { useRef, useState, useEffect } from "react";
|
||||||
|
import { getStoredThemeId, getNextTheme, applyTheme } from "@/lib/themes/engine";
|
||||||
|
|
||||||
|
const FADE_DURATION = 300;
|
||||||
|
|
||||||
|
const LABELS: Record<string, string> = {
|
||||||
|
darkbox: "classic",
|
||||||
|
"darkbox-retro": "retro",
|
||||||
|
"darkbox-dim": "dim",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ThemeSwitcher() {
|
||||||
|
const [hovering, setHovering] = useState(false);
|
||||||
|
const [nextLabel, setNextLabel] = useState("");
|
||||||
|
|
||||||
|
const maskRef = useRef<HTMLDivElement>(null);
|
||||||
|
const animatingRef = useRef(false);
|
||||||
|
const committedRef = useRef("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
committedRef.current = getStoredThemeId();
|
||||||
|
setNextLabel(LABELS[getNextTheme(committedRef.current).id] ?? "");
|
||||||
|
|
||||||
|
const handleSwap = () => {
|
||||||
|
const id = getStoredThemeId();
|
||||||
|
applyTheme(id);
|
||||||
|
committedRef.current = id;
|
||||||
|
setNextLabel(LABELS[getNextTheme(id).id] ?? "");
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("astro:after-swap", handleSwap);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("astro:after-swap", handleSwap);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (animatingRef.current) return;
|
||||||
|
animatingRef.current = true;
|
||||||
|
|
||||||
|
const mask = maskRef.current;
|
||||||
|
if (!mask) return;
|
||||||
|
|
||||||
|
const v = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue("--color-background")
|
||||||
|
.trim();
|
||||||
|
const [r, g, b] = v.split(" ").map(Number);
|
||||||
|
|
||||||
|
mask.style.backgroundColor = `rgb(${r},${g},${b})`;
|
||||||
|
mask.style.opacity = "1";
|
||||||
|
mask.style.visibility = "visible";
|
||||||
|
mask.style.transition = "none";
|
||||||
|
|
||||||
|
const next = getNextTheme(committedRef.current);
|
||||||
|
applyTheme(next.id);
|
||||||
|
committedRef.current = next.id;
|
||||||
|
setNextLabel(LABELS[getNextTheme(next.id).id] ?? "");
|
||||||
|
|
||||||
|
mask.offsetHeight;
|
||||||
|
|
||||||
|
mask.style.transition = `opacity ${FADE_DURATION}ms ease-out`;
|
||||||
|
mask.style.opacity = "0";
|
||||||
|
|
||||||
|
const onEnd = () => {
|
||||||
|
mask.removeEventListener("transitionend", onEnd);
|
||||||
|
mask.style.visibility = "hidden";
|
||||||
|
mask.style.transition = "none";
|
||||||
|
animatingRef.current = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
mask.addEventListener("transitionend", onEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="fixed bottom-4 right-4 z-[101] pointer-events-auto hidden md:block"
|
||||||
|
onMouseEnter={() => setHovering(true)}
|
||||||
|
onMouseLeave={() => setHovering(false)}
|
||||||
|
onClick={handleClick}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="text-foreground font-bold text-sm select-none transition-opacity duration-200"
|
||||||
|
style={{ opacity: hovering ? 0.8 : 0.15 }}
|
||||||
|
>
|
||||||
|
{nextLabel}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref={maskRef}
|
||||||
|
className="fixed inset-0 z-[100] pointer-events-none"
|
||||||
|
style={{ visibility: "hidden", opacity: 0 }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import { ClientRouter } from "astro:transitions";
|
|||||||
import Header from "@/components/header";
|
import Header from "@/components/header";
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import Background from "@/components/background";
|
import Background from "@/components/background";
|
||||||
|
import ThemeSwitcher from "@/components/theme-switcher";
|
||||||
|
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -20,17 +22,13 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<!-- OpenGraph -->
|
|
||||||
<meta property="og:image" content={ogImage} />
|
<meta property="og:image" content={ogImage} />
|
||||||
<meta property="og:image:width" content="1200" />
|
<meta property="og:image:width" content="1200" />
|
||||||
<meta property="og:image:height" content="630" />
|
<meta property="og:image:height" content="630" />
|
||||||
<!-- Twitter -->
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:image" content={ogImage} />
|
<meta name="twitter:image" content={ogImage} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
<!-- Basic meta description for search engines -->
|
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<!-- Also used in OpenGraph for social media sharing -->
|
|
||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
<link rel="icon" type="image/jpeg" href="/me.jpeg" />
|
<link rel="icon" type="image/jpeg" href="/me.jpeg" />
|
||||||
<ClientRouter
|
<ClientRouter
|
||||||
@@ -41,7 +39,6 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
::view-transition-new(:root) {
|
::view-transition-new(:root) {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
::view-transition-old(:root) {
|
::view-transition-old(:root) {
|
||||||
animation: 90ms ease-out both fade-out;
|
animation: 90ms ease-out both fade-out;
|
||||||
}
|
}
|
||||||
@@ -50,6 +47,7 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
to { opacity: 0; }
|
to { opacity: 0; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script is:inline set:html={THEME_LOADER_SCRIPT} />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background text-foreground min-h-screen flex flex-col">
|
<body class="bg-background text-foreground min-h-screen flex flex-col">
|
||||||
<Header client:load />
|
<Header client:load />
|
||||||
@@ -65,10 +63,7 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
<div class="mt-auto">
|
<div class="mt-auto">
|
||||||
<Footer client:load transition:persist />
|
<Footer client:load transition:persist />
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<ThemeSwitcher client:only="react" transition:persist />
|
||||||
document.addEventListener("astro:after-navigation", () => {
|
<script is:inline set:html={`window.scrollTo(0,0);` + THEME_NAV_SCRIPT} />
|
||||||
window.scrollTo(0, 0);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { ClientRouter } from "astro:transitions";
|
|||||||
import Header from "@/components/header";
|
import Header from "@/components/header";
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import Background from "@/components/background";
|
import Background from "@/components/background";
|
||||||
|
import ThemeSwitcher from "@/components/theme-switcher";
|
||||||
|
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -23,28 +25,27 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<!-- OpenGraph -->
|
|
||||||
<meta property="og:image" content={ogImage} />
|
<meta property="og:image" content={ogImage} />
|
||||||
<meta property="og:image:width" content="1200" />
|
<meta property="og:image:width" content="1200" />
|
||||||
<meta property="og:image:height" content="630" />
|
<meta property="og:image:height" content="630" />
|
||||||
<!-- Twitter -->
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:image" content={ogImage} />
|
<meta name="twitter:image" content={ogImage} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
<!-- Basic meta description for search engines -->
|
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<!-- Also used in OpenGraph for social media sharing -->
|
|
||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
<link rel="icon" type="image/jpeg" href="/me.jpeg" />
|
<link rel="icon" type="image/jpeg" href="/me.jpeg" />
|
||||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||||
<ClientRouter />
|
<ClientRouter />
|
||||||
|
<script is:inline set:html={THEME_LOADER_SCRIPT} />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background text-foreground">
|
<body class="bg-background text-foreground">
|
||||||
<Header client:load />
|
<Header client:load transparent />
|
||||||
<main transition:animate="fade">
|
<main transition:animate="fade">
|
||||||
<Background layout="index" client:only="react" transition:persist />
|
<Background layout="index" client:only="react" transition:persist />
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<Footer client:load transition:persist fixed=true />
|
<Footer client:load transition:persist fixed=true />
|
||||||
|
<ThemeSwitcher client:only="react" transition:persist />
|
||||||
|
<script is:inline set:html={THEME_NAV_SCRIPT} />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { ClientRouter } from "astro:transitions";
|
|||||||
import Header from "@/components/header";
|
import Header from "@/components/header";
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import Background from "@/components/background";
|
import Background from "@/components/background";
|
||||||
|
import ThemeSwitcher from "@/components/theme-switcher";
|
||||||
|
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -20,17 +22,13 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<!-- OpenGraph -->
|
|
||||||
<meta property="og:image" content={ogImage} />
|
<meta property="og:image" content={ogImage} />
|
||||||
<meta property="og:image:width" content="1200" />
|
<meta property="og:image:width" content="1200" />
|
||||||
<meta property="og:image:height" content="630" />
|
<meta property="og:image:height" content="630" />
|
||||||
<!-- Twitter -->
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:image" content={ogImage} />
|
<meta name="twitter:image" content={ogImage} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
<!-- Basic meta description for search engines -->
|
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<!-- Also used in OpenGraph for social media sharing -->
|
|
||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
<link rel="icon" type="image/jpeg" href="/me.jpeg" />
|
<link rel="icon" type="image/jpeg" href="/me.jpeg" />
|
||||||
<ClientRouter
|
<ClientRouter
|
||||||
@@ -41,7 +39,6 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
::view-transition-new(:root) {
|
::view-transition-new(:root) {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
::view-transition-old(:root) {
|
::view-transition-old(:root) {
|
||||||
animation: 90ms ease-out both fade-out;
|
animation: 90ms ease-out both fade-out;
|
||||||
}
|
}
|
||||||
@@ -50,6 +47,7 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
to { opacity: 0; }
|
to { opacity: 0; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script is:inline set:html={THEME_LOADER_SCRIPT} />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background text-foreground min-h-screen flex flex-col">
|
<body class="bg-background text-foreground min-h-screen flex flex-col">
|
||||||
<main class="flex-1 flex flex-col">
|
<main class="flex-1 flex flex-col">
|
||||||
@@ -61,10 +59,7 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
<Background layout="content" position="left" client:only="react" transition:persist />
|
<Background layout="content" position="left" client:only="react" transition:persist />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<ThemeSwitcher client:only="react" transition:persist />
|
||||||
document.addEventListener("astro:after-navigation", () => {
|
<script is:inline set:html={`window.scrollTo(0,0);` + THEME_NAV_SCRIPT} />
|
||||||
window.scrollTo(0, 0);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
59
src/src/lib/themes/engine.ts
Normal file
59
src/src/lib/themes/engine.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { THEMES, DEFAULT_THEME_ID } from "./index";
|
||||||
|
import { CSS_PROPS } from "./props";
|
||||||
|
import type { Theme } from "./types";
|
||||||
|
|
||||||
|
export function getStoredThemeId(): string {
|
||||||
|
if (typeof window === "undefined") return DEFAULT_THEME_ID;
|
||||||
|
return localStorage.getItem("theme") || DEFAULT_THEME_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveTheme(id: string): void {
|
||||||
|
localStorage.setItem("theme", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextTheme(currentId: string): Theme {
|
||||||
|
const list = Object.values(THEMES);
|
||||||
|
const idx = list.findIndex((t) => t.id === currentId);
|
||||||
|
return list[(idx + 1) % list.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets CSS vars and notifies canvas, but does NOT persist to localStorage. */
|
||||||
|
export function previewTheme(id: string): void {
|
||||||
|
const theme = THEMES[id];
|
||||||
|
if (!theme) return;
|
||||||
|
|
||||||
|
const root = document.documentElement;
|
||||||
|
for (const [key, prop] of CSS_PROPS) {
|
||||||
|
root.style.setProperty(prop, theme.colors[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent("theme-changed", { detail: { id } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyTheme(id: string): void {
|
||||||
|
const theme = THEMES[id];
|
||||||
|
if (!theme) return;
|
||||||
|
|
||||||
|
// Set CSS vars on :root for immediate visual update
|
||||||
|
const root = document.documentElement;
|
||||||
|
for (const [key, prop] of CSS_PROPS) {
|
||||||
|
root.style.setProperty(prop, theme.colors[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update <style id="theme-vars"> so Astro view transitions don't revert
|
||||||
|
let el = document.getElementById("theme-vars") as HTMLStyleElement | null;
|
||||||
|
if (!el) {
|
||||||
|
el = document.createElement("style");
|
||||||
|
el.id = "theme-vars";
|
||||||
|
document.head.appendChild(el);
|
||||||
|
}
|
||||||
|
let css = ":root{";
|
||||||
|
for (const [key, prop] of CSS_PROPS) {
|
||||||
|
css += `${prop}:${theme.colors[key]};`;
|
||||||
|
}
|
||||||
|
css += "}";
|
||||||
|
el.textContent = css;
|
||||||
|
|
||||||
|
saveTheme(id);
|
||||||
|
document.dispatchEvent(new CustomEvent("theme-changed", { detail: { id } }));
|
||||||
|
}
|
||||||
58
src/src/lib/themes/index.ts
Normal file
58
src/src/lib/themes/index.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import type { Theme } from "./types";
|
||||||
|
|
||||||
|
export const DEFAULT_THEME_ID = "darkbox";
|
||||||
|
|
||||||
|
function theme(
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
type: "dark" | "light",
|
||||||
|
colors: Theme["colors"],
|
||||||
|
palette: [number, number, number][]
|
||||||
|
): Theme {
|
||||||
|
return { id, name, type, colors, canvasPalette: palette };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Three darkbox variants from darkbox.nvim
|
||||||
|
// Classic (vivid) → Retro (muted) → Dim (deep)
|
||||||
|
// Each variant's "bright" is the next level up's base.
|
||||||
|
|
||||||
|
export const THEMES: Record<string, Theme> = {
|
||||||
|
darkbox: theme("darkbox", "Darkbox Classic", "dark", {
|
||||||
|
background: "0 0 0",
|
||||||
|
foreground: "235 219 178",
|
||||||
|
red: "251 73 52", redBright: "255 110 85",
|
||||||
|
orange: "254 128 25", orangeBright: "255 165 65",
|
||||||
|
green: "184 187 38", greenBright: "210 215 70",
|
||||||
|
yellow: "250 189 47", yellowBright: "255 215 85",
|
||||||
|
blue: "131 165 152", blueBright: "165 195 180",
|
||||||
|
purple: "211 134 155", purpleBright: "235 165 180",
|
||||||
|
aqua: "142 192 124", aquaBright: "175 220 160",
|
||||||
|
surface: "60 56 54",
|
||||||
|
}, [[251,73,52],[184,187,38],[250,189,47],[131,165,152],[211,134,155],[142,192,124]]),
|
||||||
|
|
||||||
|
"darkbox-retro": theme("darkbox-retro", "Darkbox Retro", "dark", {
|
||||||
|
background: "0 0 0",
|
||||||
|
foreground: "189 174 147",
|
||||||
|
red: "204 36 29", redBright: "251 73 52",
|
||||||
|
orange: "214 93 14", orangeBright: "254 128 25",
|
||||||
|
green: "152 151 26", greenBright: "184 187 38",
|
||||||
|
yellow: "215 153 33", yellowBright: "250 189 47",
|
||||||
|
blue: "69 133 136", blueBright: "131 165 152",
|
||||||
|
purple: "177 98 134", purpleBright: "211 134 155",
|
||||||
|
aqua: "104 157 106", aquaBright: "142 192 124",
|
||||||
|
surface: "60 56 54",
|
||||||
|
}, [[204,36,29],[152,151,26],[215,153,33],[69,133,136],[177,98,134],[104,157,106]]),
|
||||||
|
|
||||||
|
"darkbox-dim": theme("darkbox-dim", "Darkbox Dim", "dark", {
|
||||||
|
background: "0 0 0",
|
||||||
|
foreground: "168 153 132",
|
||||||
|
red: "157 0 6", redBright: "204 36 29",
|
||||||
|
orange: "175 58 3", orangeBright: "214 93 14",
|
||||||
|
green: "121 116 14", greenBright: "152 151 26",
|
||||||
|
yellow: "181 118 20", yellowBright: "215 153 33",
|
||||||
|
blue: "7 102 120", blueBright: "69 133 136",
|
||||||
|
purple: "143 63 113", purpleBright: "177 98 134",
|
||||||
|
aqua: "66 123 88", aquaBright: "104 157 106",
|
||||||
|
surface: "60 56 54",
|
||||||
|
}, [[157,0,6],[121,116,14],[181,118,20],[7,102,120],[143,63,113],[66,123,88]]),
|
||||||
|
};
|
||||||
25
src/src/lib/themes/loader.ts
Normal file
25
src/src/lib/themes/loader.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Generates the inline <script> content for theme loading.
|
||||||
|
* Called at build time in Astro frontmatter.
|
||||||
|
* The script reads "theme" from localStorage, looks up colors, injects a <style> tag.
|
||||||
|
*/
|
||||||
|
import { THEMES } from "./index";
|
||||||
|
import { CSS_PROPS } from "./props";
|
||||||
|
|
||||||
|
// Pre-build a { prop: value } map for each theme at build time
|
||||||
|
const themeVars: Record<string, Record<string, string>> = {};
|
||||||
|
for (const [id, theme] of Object.entries(THEMES)) {
|
||||||
|
const vars: Record<string, string> = {};
|
||||||
|
for (const [key, prop] of CSS_PROPS) {
|
||||||
|
vars[prop] = theme.colors[key];
|
||||||
|
}
|
||||||
|
themeVars[id] = vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets inline styles on <html> — highest specificity, beats any stylesheet
|
||||||
|
const APPLY = `var v=t[id];if(!v)return;var s=document.documentElement.style;for(var k in v)s.setProperty(k,v[k])`;
|
||||||
|
const LOOKUP = `var id=localStorage.getItem("theme");if(!id)return;var t=${JSON.stringify(themeVars)};`;
|
||||||
|
|
||||||
|
export const THEME_LOADER_SCRIPT = `(function(){${LOOKUP}${APPLY}})();`;
|
||||||
|
|
||||||
|
export const THEME_NAV_SCRIPT = `document.addEventListener("astro:after-navigation",function(){${LOOKUP}${APPLY}});`;
|
||||||
21
src/src/lib/themes/props.ts
Normal file
21
src/src/lib/themes/props.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { ThemeColors } from "./types";
|
||||||
|
|
||||||
|
export const CSS_PROPS: [keyof ThemeColors, string][] = [
|
||||||
|
["background", "--color-background"],
|
||||||
|
["foreground", "--color-foreground"],
|
||||||
|
["red", "--color-red"],
|
||||||
|
["redBright", "--color-red-bright"],
|
||||||
|
["orange", "--color-orange"],
|
||||||
|
["orangeBright", "--color-orange-bright"],
|
||||||
|
["green", "--color-green"],
|
||||||
|
["greenBright", "--color-green-bright"],
|
||||||
|
["yellow", "--color-yellow"],
|
||||||
|
["yellowBright", "--color-yellow-bright"],
|
||||||
|
["blue", "--color-blue"],
|
||||||
|
["blueBright", "--color-blue-bright"],
|
||||||
|
["purple", "--color-purple"],
|
||||||
|
["purpleBright", "--color-purple-bright"],
|
||||||
|
["aqua", "--color-aqua"],
|
||||||
|
["aquaBright", "--color-aqua-bright"],
|
||||||
|
["surface", "--color-surface"],
|
||||||
|
];
|
||||||
27
src/src/lib/themes/types.ts
Normal file
27
src/src/lib/themes/types.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export interface ThemeColors {
|
||||||
|
background: string;
|
||||||
|
foreground: string;
|
||||||
|
red: string;
|
||||||
|
redBright: string;
|
||||||
|
orange: string;
|
||||||
|
orangeBright: string;
|
||||||
|
green: string;
|
||||||
|
greenBright: string;
|
||||||
|
yellow: string;
|
||||||
|
yellowBright: string;
|
||||||
|
blue: string;
|
||||||
|
blueBright: string;
|
||||||
|
purple: string;
|
||||||
|
purpleBright: string;
|
||||||
|
aqua: string;
|
||||||
|
aquaBright: string;
|
||||||
|
surface: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Theme {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: "dark" | "light";
|
||||||
|
colors: ThemeColors;
|
||||||
|
canvasPalette: [number, number, number][];
|
||||||
|
}
|
||||||
@@ -1,3 +1,23 @@
|
|||||||
|
:root {
|
||||||
|
--color-background: 0 0 0;
|
||||||
|
--color-foreground: 235 219 178;
|
||||||
|
--color-red: 251 73 52;
|
||||||
|
--color-red-bright: 255 110 85;
|
||||||
|
--color-orange: 254 128 25;
|
||||||
|
--color-orange-bright: 255 165 65;
|
||||||
|
--color-green: 184 187 38;
|
||||||
|
--color-green-bright: 210 215 70;
|
||||||
|
--color-yellow: 250 189 47;
|
||||||
|
--color-yellow-bright: 255 215 85;
|
||||||
|
--color-blue: 131 165 152;
|
||||||
|
--color-blue-bright: 165 195 180;
|
||||||
|
--color-purple: 211 134 155;
|
||||||
|
--color-purple-bright: 235 165 180;
|
||||||
|
--color-aqua: 142 192 124;
|
||||||
|
--color-aqua-bright: 175 220 160;
|
||||||
|
--color-surface: 60 56 54;
|
||||||
|
}
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|||||||
@@ -6,35 +6,35 @@ module.exports = {
|
|||||||
"comic-code": ["Comic Code", "monospace"],
|
"comic-code": ["Comic Code", "monospace"],
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
background: "#000000",
|
background: "rgb(var(--color-background) / <alpha-value>)",
|
||||||
foreground: "#ebdbb2",
|
foreground: "rgb(var(--color-foreground) / <alpha-value>)",
|
||||||
red: {
|
red: {
|
||||||
DEFAULT: "#cc241d",
|
DEFAULT: "rgb(var(--color-red) / <alpha-value>)",
|
||||||
bright: "#fb4934"
|
bright: "rgb(var(--color-red-bright) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
orange: {
|
orange: {
|
||||||
DEFAULT: "#d65d0e",
|
DEFAULT: "rgb(var(--color-orange) / <alpha-value>)",
|
||||||
bright: "#fe8019"
|
bright: "rgb(var(--color-orange-bright) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
green: {
|
green: {
|
||||||
DEFAULT: "#98971a",
|
DEFAULT: "rgb(var(--color-green) / <alpha-value>)",
|
||||||
bright: "#b8bb26"
|
bright: "rgb(var(--color-green-bright) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
yellow: {
|
yellow: {
|
||||||
DEFAULT: "#d79921",
|
DEFAULT: "rgb(var(--color-yellow) / <alpha-value>)",
|
||||||
bright: "#fabd2f"
|
bright: "rgb(var(--color-yellow-bright) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
blue: {
|
blue: {
|
||||||
DEFAULT: "#458588",
|
DEFAULT: "rgb(var(--color-blue) / <alpha-value>)",
|
||||||
bright: "#83a598"
|
bright: "rgb(var(--color-blue-bright) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
purple: {
|
purple: {
|
||||||
DEFAULT: "#b16286",
|
DEFAULT: "rgb(var(--color-purple) / <alpha-value>)",
|
||||||
bright: "#d3869b"
|
bright: "rgb(var(--color-purple-bright) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
aqua: {
|
aqua: {
|
||||||
DEFAULT: "#689d6a",
|
DEFAULT: "rgb(var(--color-aqua) / <alpha-value>)",
|
||||||
bright: "#8ec07c"
|
bright: "rgb(var(--color-aqua-bright) / <alpha-value>)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
@@ -51,86 +51,82 @@ module.exports = {
|
|||||||
"draw-line": "draw-line 0.6s ease-out forwards",
|
"draw-line": "draw-line 0.6s ease-out forwards",
|
||||||
"fade-in": "fade-in 0.3s ease-in-out forwards"
|
"fade-in": "fade-in 0.3s ease-in-out forwards"
|
||||||
},
|
},
|
||||||
typography: (theme) => ({
|
typography: () => ({
|
||||||
DEFAULT: {
|
DEFAULT: {
|
||||||
css: {
|
css: {
|
||||||
color: theme("colors.foreground"),
|
color: "rgb(var(--color-foreground))",
|
||||||
"--tw-prose-body": theme("colors.foreground"),
|
"--tw-prose-body": "rgb(var(--color-foreground))",
|
||||||
"--tw-prose-headings": theme("colors.yellow.bright"),
|
"--tw-prose-headings": "rgb(var(--color-yellow-bright))",
|
||||||
"--tw-prose-links": theme("colors.blue.bright"),
|
"--tw-prose-links": "rgb(var(--color-blue-bright))",
|
||||||
"--tw-prose-bold": theme("colors.orange.bright"),
|
"--tw-prose-bold": "rgb(var(--color-orange-bright))",
|
||||||
"--tw-prose-quotes": theme("colors.green.bright"),
|
"--tw-prose-quotes": "rgb(var(--color-green-bright))",
|
||||||
"--tw-prose-code": theme("colors.purple.bright"),
|
"--tw-prose-code": "rgb(var(--color-purple-bright))",
|
||||||
"--tw-prose-hr": theme("colors.foreground"),
|
"--tw-prose-hr": "rgb(var(--color-foreground))",
|
||||||
"--tw-prose-bullets": theme("colors.foreground"),
|
"--tw-prose-bullets": "rgb(var(--color-foreground))",
|
||||||
|
|
||||||
// Base text color
|
|
||||||
color: theme("colors.foreground"),
|
|
||||||
|
|
||||||
// Headings
|
// Headings
|
||||||
h1: {
|
h1: {
|
||||||
color: theme("colors.yellow.bright"),
|
color: "rgb(var(--color-yellow-bright))",
|
||||||
fontWeight: "700",
|
fontWeight: "700",
|
||||||
},
|
},
|
||||||
h2: {
|
h2: {
|
||||||
color: theme("colors.yellow.bright"),
|
color: "rgb(var(--color-yellow-bright))",
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
},
|
},
|
||||||
h3: {
|
h3: {
|
||||||
color: theme("colors.yellow.bright"),
|
color: "rgb(var(--color-yellow-bright))",
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
},
|
},
|
||||||
h4: {
|
h4: {
|
||||||
color: theme("colors.yellow.bright"),
|
color: "rgb(var(--color-yellow-bright))",
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Links
|
// Links
|
||||||
a: {
|
a: {
|
||||||
color: theme("colors.blue.bright"),
|
color: "rgb(var(--color-blue-bright))",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
color: theme("colors.blue.DEFAULT"),
|
color: "rgb(var(--color-blue))",
|
||||||
},
|
},
|
||||||
textDecoration: "none",
|
textDecoration: "none",
|
||||||
borderBottom: `1px solid ${theme("colors.blue.bright")}`,
|
borderBottom: "1px solid rgb(var(--color-blue-bright))",
|
||||||
transition: "all 0.2s ease-in-out",
|
transition: "all 0.2s ease-in-out",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Code
|
// Code
|
||||||
'code:not([data-language])': {
|
'code:not([data-language])': {
|
||||||
color: theme('colors.purple.bright'),
|
color: "rgb(var(--color-purple-bright))",
|
||||||
backgroundColor: '#282828',
|
backgroundColor: "rgb(var(--color-surface))",
|
||||||
padding: '0',
|
padding: '0',
|
||||||
borderRadius: '0.25rem',
|
borderRadius: '0.25rem',
|
||||||
fontFamily: 'Comic Code, monospace',
|
fontFamily: 'Comic Code, monospace',
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
fontSize: 'inherit', // Match the parent text size
|
fontSize: 'inherit',
|
||||||
'&::before': { content: 'none' },
|
'&::before': { content: 'none' },
|
||||||
'&::after': { content: 'none' },
|
'&::after': { content: 'none' },
|
||||||
},
|
},
|
||||||
|
|
||||||
'pre': {
|
'pre': {
|
||||||
backgroundColor: '#282828',
|
backgroundColor: "rgb(var(--color-surface))",
|
||||||
color: theme("colors.foreground"),
|
color: "rgb(var(--color-foreground))",
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
overflow: 'visible', // This allows the copy button to be positioned outside
|
overflow: 'visible',
|
||||||
position: 'relative', // For the copy button positioning
|
position: 'relative',
|
||||||
marginTop: '1.5rem', // Space for the copy button and language label
|
marginTop: '1.5rem',
|
||||||
fontSize: 'inherit', // Match the parent font size
|
fontSize: 'inherit',
|
||||||
},
|
},
|
||||||
|
|
||||||
'pre code': {
|
'pre code': {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
fontFamily: 'Comic Code, monospace',
|
fontFamily: 'Comic Code, monospace',
|
||||||
fontSize: '1em', // This will inherit from the prose-lg setting
|
fontSize: '1em',
|
||||||
padding: '0',
|
padding: '0',
|
||||||
overflow: 'auto', // Enable horizontal scrolling
|
overflow: 'auto',
|
||||||
whiteSpace: 'pre',
|
whiteSpace: 'pre',
|
||||||
'&::before': { content: 'none' },
|
'&::before': { content: 'none' },
|
||||||
'&::after': { content: 'none' },
|
'&::after': { content: 'none' },
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
'[data-rehype-pretty-code-fragment]:nth-of-type(2) pre': {
|
'[data-rehype-pretty-code-fragment]:nth-of-type(2) pre': {
|
||||||
'[data-line]::before': {
|
'[data-line]::before': {
|
||||||
content: 'counter(line)',
|
content: 'counter(line)',
|
||||||
@@ -148,7 +144,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Bold
|
// Bold
|
||||||
strong: {
|
strong: {
|
||||||
color: theme("colors.orange.bright"),
|
color: "rgb(var(--color-orange-bright))",
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -156,15 +152,15 @@ module.exports = {
|
|||||||
ul: {
|
ul: {
|
||||||
li: {
|
li: {
|
||||||
"&::before": {
|
"&::before": {
|
||||||
backgroundColor: theme("colors.foreground"),
|
backgroundColor: "rgb(var(--color-foreground))",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Blockquotes
|
// Blockquotes
|
||||||
blockquote: {
|
blockquote: {
|
||||||
borderLeftColor: theme("colors.green.bright"),
|
borderLeftColor: "rgb(var(--color-green-bright))",
|
||||||
color: theme("colors.green.bright"),
|
color: "rgb(var(--color-green-bright))",
|
||||||
fontStyle: "italic",
|
fontStyle: "italic",
|
||||||
quotes: "\"\\201C\"\"\\201D\"\"\\2018\"\"\\2019\"",
|
quotes: "\"\\201C\"\"\\201D\"\"\\2018\"\"\\2019\"",
|
||||||
p: {
|
p: {
|
||||||
@@ -175,21 +171,21 @@ module.exports = {
|
|||||||
|
|
||||||
// Horizontal rules
|
// Horizontal rules
|
||||||
hr: {
|
hr: {
|
||||||
borderColor: theme("colors.foreground"),
|
borderColor: "rgb(var(--color-foreground))",
|
||||||
opacity: "0.2",
|
opacity: "0.2",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
table: {
|
table: {
|
||||||
thead: {
|
thead: {
|
||||||
borderBottomColor: theme("colors.foreground"),
|
borderBottomColor: "rgb(var(--color-foreground))",
|
||||||
th: {
|
th: {
|
||||||
color: theme("colors.yellow.bright"),
|
color: "rgb(var(--color-yellow-bright))",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tbody: {
|
tbody: {
|
||||||
tr: {
|
tr: {
|
||||||
borderBottomColor: theme("colors.foreground"),
|
borderBottomColor: "rgb(var(--color-foreground))",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -201,7 +197,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Figures
|
// Figures
|
||||||
figcaption: {
|
figcaption: {
|
||||||
color: theme("colors.foreground"),
|
color: "rgb(var(--color-foreground))",
|
||||||
opacity: "0.8",
|
opacity: "0.8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user