feat(site): film grain (#150)

This commit is contained in:
Darius Cepulis
2025-10-31 14:32:49 -05:00
committed by GitHub
parent 8402b33ad7
commit 52b522da29
8 changed files with 77 additions and 16 deletions
@@ -0,0 +1,29 @@
import clsx from 'clsx';
import { twMerge } from 'tailwind-merge';
import texture from './texture.svg?raw';
const textureUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(texture)}`;
interface Props {
currentPath: string;
className?: string;
}
export default function FilmGrain({ currentPath, className }: Props) {
return (
<div
className={twMerge(
clsx(
'absolute w-full h-full top-0 left-0 pointer-events-none mix-blend-overlay z-10',
currentPath.startsWith('/docs') ? 'opacity-70 dark:opacity-50' : 'opacity-70 dark:opacity-60',
),
className,
)}
style={{
backgroundImage: `url("${textureUrl}")`,
backgroundPosition: `0% 0%`,
backgroundRepeat: `repeat`,
}}
/>
);
}
+1
View File
@@ -0,0 +1 @@
export { default } from './FilmGrain';
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600">
<filter id="a" x="0%" y="0%" width="100%" height="100%">
<feTurbulence baseFrequency=".5" stitchTiles="stitch" result="noise" />
<feColorMatrix in="noise" type="saturate" values="0" result="grayscaleNoise" />
</filter>
<rect width="100%" height="100%" filter="url(#a)" />
</svg>

After

Width:  |  Height:  |  Size: 359 B

+7 -3
View File
@@ -2,6 +2,7 @@ import { Dialog } from '@base-ui-components/react/dialog';
import clsx from 'clsx';
import { ArrowUpRight, Menu, X } from 'lucide-react';
import { DISCORD_INVITE_URL, GITHUB_REPO_URL } from '@/consts';
import FilmGrain from '../FilmGrain';
interface NavLink {
href: string;
@@ -32,6 +33,7 @@ export default function MobileNav({ navLinks, currentPath, dark = false, childre
<Dialog.Portal>
{/* Popup container */}
<Dialog.Popup className={clsx('fixed inset-0 z-50 flex flex-col', dark ? 'bg-dark-100 text-light-100' : 'bg-light-80 dark:bg-dark-100 text-dark-100 dark:text-light-100')}>
<FilmGrain currentPath={currentPath} />
{/* Header with close button */}
<div
className={clsx(
@@ -41,14 +43,16 @@ export default function MobileNav({ navLinks, currentPath, dark = false, childre
style={{ height: 'var(--nav-h)' }}
>
<Dialog.Title className="sr-only">Navigation</Dialog.Title>
<p className="text-h5 px-3">Video.js</p>
<p className="text-h5 px-3">Video.js v10</p>
<Dialog.Close className={clsx('flex items-center justify-center p-3 h-full aspect-square', dark ? 'bg-light-80 text-dark-100 intent:bg-light-40' : 'bg-dark-100 text-light-80 intent:bg-dark-80 dark:bg-light-80 dark:text-dark-100 dark:intent:bg-light-40')} aria-label="Close navigation menu">
<X size={24} />
</Dialog.Close>
</div>
{/* Children */}
{children ? <div className={clsx('border-b', dark ? 'border-dark-80' : 'border-light-40 dark:border-dark-80')}>{children}</div> : null}
{/* Astro makes it hard to know if we have children, so, we use -mt-px to hide the border-b if we don't */}
<div className={clsx('-mt-px border-b', dark ? 'border-dark-80' : 'border-light-40 dark:border-dark-80')}>
{children}
</div>
{/* Navigation links */}
<nav className="flex flex-col py-3">
+6 -3
View File
@@ -4,6 +4,7 @@ import MobileNav from './MobileNav';
import { ArrowUpRight } from 'lucide-react';
import Discord from '@/components/icons/Discord';
import GitHub from '@/components/icons/GitHub';
import FilmGrain from '../FilmGrain';
export interface Props {
dark?: boolean;
@@ -35,10 +36,12 @@ const { class: className, dark = false } = Astro.props;
]}
style="height: var(--nav-h);"
>
<!-- Logo -->
{/* Grain */}
<FilmGrain currentPath={Astro.url.pathname} />
{/* Logo */}
<a href="/" class="flex items-center px-3 text-h5">Video.js v10</a>
<!-- Desktop nav links -->
{/* Desktop nav links */}
<div class="hidden sm:flex -mb-px pr-3">
{
navLinks.map((link) => (
@@ -76,7 +79,7 @@ const { class: className, dark = false } = Astro.props;
</a>
</div>
<!-- Mobile nav -->
{/* Mobile nav */}
<MobileNav client:load navLinks={navLinks} currentPath={currentPath} dark={dark}>
<slot name="mobile-nav" />
</MobileNav>
+12 -5
View File
@@ -4,6 +4,7 @@ import type { SupportedFramework, SupportedStyle } from '@/types/docs';
import { PreferenceUpdater } from '@/components/docs/PreferenceUpdater';
import SidebarItem from '@/components/docs/SidebarItem.astro';
import { filterSidebar } from '@/utils/docs/sidebar';
import FilmGrain from '../FilmGrain';
type Props<F extends SupportedFramework = SupportedFramework> = {
framework: F;
@@ -15,8 +16,14 @@ const { framework, style, docTitles } = Astro.props;
const filteredSidebar = filterSidebar(framework, style);
---
<slot />
<PreferenceUpdater client:idle currentFramework={framework} currentStyle={style} />
<nav class="p-6 text-dark-80 dark:text-light-40">
{filteredSidebar.map((item) => <SidebarItem item={item} framework={framework} style={style} docTitles={docTitles} />)}
</nav>
<div class="bg-light-80 dark:bg-dark-100">
<slot />
<PreferenceUpdater client:idle currentFramework={framework} currentStyle={style} />
<nav class="p-6">
{
filteredSidebar.map((item) => (
<SidebarItem item={item} framework={framework} style={style} docTitles={docTitles} />
))
}
</nav>
</div>
+12 -4
View File
@@ -7,6 +7,7 @@ import { SITE_TITLE } from '@/consts';
import '@/styles/globals.css';
import FilmGrain from '@/components/FilmGrain';
import Posthog from '@/components/Posthog.astro';
import ThemeInit from '@/components/ThemeInit.astro';
@@ -18,6 +19,7 @@ interface Props {
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image } = Astro.props;
const fullTitle = Array.isArray(title)
? title.filter((t) => t !== SITE_TITLE).join(' | ') + ` | ${SITE_TITLE}`
: title === SITE_TITLE
@@ -78,15 +80,21 @@ const fullTitle = Array.isArray(title)
<body
class:list={[
'relative',
'bg-light-80 dark:bg-dark-100 text-dark-100 dark:text-light-100',
'font-sans antialiased text-pretty text-base',
'font-sans antialiased text-pretty text-base text-dark-100 dark:text-light-100',
'min-w-80',
'[--nav-h:calc(var(--spacing)*13)] sm:[--nav-h:calc(var(--spacing)*14)]',
'selection:bg-yellow selection:text-dark-100',
]}
>
{/* BaseUI requires body.relative and div.isolate */}
<div class="isolate">
<div
class:list={[
// BaseUI requires body.relative and div.isolate
'isolate',
// background colors are applied here for a more consistent FilmGrain blending
'bg-light-80 dark:bg-dark-100',
]}
>
<FilmGrain currentPath={Astro.url.pathname} />
<slot />
</div>
</body>
+3 -1
View File
@@ -11,6 +11,7 @@ import Base from './Base.astro';
import { Selectors } from '@/components/docs/Selectors';
import { getDocTitle } from '@/utils/docs/title';
import FooterEasterEgg from '@/components/FooterEasterEgg.astro';
import FilmGrain from '@/components/FilmGrain';
type Props<F extends SupportedFramework = SupportedFramework> = {
doc: CollectionEntry<'docs'>;
@@ -36,9 +37,10 @@ const docTitles = new Map(allDocs.map((d) => [d.id, getDocTitle(d, framework)]))
<DocsSidebar slot="mobile-nav" framework={framework} style={style} docTitles={docTitles} />
</NavBar>
<aside
class="hidden lg:block lg:border-r border-light-40 dark:border-dark-80 sticky overflow-y-scroll"
class="hidden lg:block lg:border-r border-light-40 dark:border-dark-80 z-10 sticky overflow-y-scroll"
style="top: var(--nav-h); max-height: calc(100vh - var(--nav-h));"
>
<FilmGrain currentPath={Astro.url.pathname} />
<DocsSidebar framework={framework} style={style} docTitles={docTitles}>
<Selectors client:load currentFramework={framework} currentStyle={style} currentSlug={slug} />
</DocsSidebar>