feat(website): init dark mode

This commit is contained in:
Darius Cepulis
2025-10-22 10:32:35 -07:00
parent 99afc51e94
commit 4502fe43ad
31 changed files with 280 additions and 92 deletions
+2
View File
@@ -26,7 +26,9 @@ export default antfu(
'**/.vercel/',
'**/dist/',
'**/styles/vjs.css',
// some files that make eslint/prettier panic
'website/src/components/Posthog.astro',
'website/src/components/ThemeInit.astro',
],
plugins: {
'jsx-a11y': jsxA11y,
-1
View File
@@ -8,5 +8,4 @@ not comprehensive. just the things I'm worried about forgetting
- animations
- docs features
- copy code blocks
- dark mode
- search
+1
View File
@@ -36,6 +36,7 @@ export default defineConfig({
shikiConfig: {
themes: {
light: 'gruvbox-light-hard',
dark: 'gruvbox-dark-medium',
},
// TODO shiki transformers
},
+7 -3
View File
@@ -3,6 +3,7 @@ import clsx from 'clsx';
import { createHighlighter, hastToHtml } from 'shiki';
import html from 'shiki/langs/html.mjs';
import tsx from 'shiki/langs/tsx.mjs';
import gruvboxDarkSoft from 'shiki/themes/gruvbox-dark-soft.mjs';
import gruvboxLightHard from 'shiki/themes/gruvbox-light-hard.mjs';
// If you try importing more than one island with ClientCode in it,
@@ -12,7 +13,7 @@ import gruvboxLightHard from 'shiki/themes/gruvbox-light-hard.mjs';
// eslint-disable-next-line antfu/no-top-level-await
const highlighter: Highlighter = await createHighlighter({
themes: [gruvboxLightHard],
themes: [gruvboxLightHard, gruvboxDarkSoft],
langs: [html, tsx],
});
@@ -25,7 +26,10 @@ export interface ClientCodeProps {
export default function ClientCode({ code, lang, className }: ClientCodeProps) {
const hast = highlighter.codeToHast(code, {
lang,
theme: 'gruvbox-light-hard',
themes: {
light: 'gruvbox-light-hard',
dark: 'gruvbox-dark-soft',
},
});
// shiki gives us a root > pre > code > text structure
@@ -51,7 +55,7 @@ export default function ClientCode({ code, lang, className }: ClientCodeProps) {
return (
<pre
className={clsx('rounded-lg p-6 overflow-x-auto overflow-y-scroll max-h-96 border border-light-40 bg-light-100', preClassName, className)}
className={clsx('rounded-lg p-6 overflow-x-auto overflow-y-scroll max-h-96 border border-light-40 dark:border-dark-80 bg-light-100 dark:bg-dark-110', preClassName, className)}
>
<code
className={clsx('font-mono text-code', codeClassName)}
+43 -33
View File
@@ -3,6 +3,7 @@ import { DISCORD_INVITE_URL, GITHUB_REPO_URL } from '@/consts';
import { ArrowUpRight } from 'lucide-react';
import GitHub from './icons/GitHub';
import Discord from './icons/Discord';
import { ThemeToggle } from './ThemeToggle';
type NavLink = {
href: string;
@@ -22,40 +23,49 @@ type Props = {
const { class: className } = Astro.props;
---
<footer class:list={['border-t border-light-40 py-6 px-3 grid gap-3', className]}>
<nav class="flex flex-col sm:flex-row">
<div class="flex justify-center sm:justify-start">
{
navLinks.map((link) => (
<a href={link.href} class="intent:underline px-3 py-2 inline-flex gap-1 items-center">
{link.label} {link.external ? <ArrowUpRight size="1em" /> : null}
</a>
))
}
</div>
<div class="flex justify-center sm:justify-start">
<a
href={GITHUB_REPO_URL}
class="group px-3 py-2 inline-flex items-center"
aria-label="GitHub"
target="_blank"
rel="noopener noreferrer"
>
<GitHub size="1em" className="text-dark-80 group-intent:text-dark-100" />
</a>
<a
href={DISCORD_INVITE_URL}
class="group px-3 py-2 inline-flex items-center"
aria-label="Discord"
target="_blank"
rel="noopener noreferrer"
>
<Discord size="1em" className="text-dark-80 group-intent:text-dark-100" />
</a>
</div>
</nav>
<footer class:list={['border-t border-light-40 dark:border-dark-80 py-6 px-3 grid gap-3', className]}>
<div class="flex items-center justify-between flex-col sm:flex-row gap-3 mb-2 sm:mb-0">
<nav class="flex flex-col sm:flex-row gap-1">
<div class="flex justify-center sm:justify-start">
{
navLinks.map((link) => (
<a href={link.href} class="intent:underline px-3 py-2 inline-flex gap-1 items-center">
{link.label} {link.external ? <ArrowUpRight size="1em" /> : null}
</a>
))
}
</div>
<div class="flex justify-center sm:justify-start">
<a
href={GITHUB_REPO_URL}
class="group px-3 py-2 inline-flex items-center"
aria-label="GitHub"
target="_blank"
rel="noopener noreferrer"
>
<GitHub
size="1em"
className="text-dark-80 dark:text-light-80 group-intent:text-dark-100 dark:group-intent:text-light-40"
/>
</a>
<a
href={DISCORD_INVITE_URL}
class="group px-3 py-2 inline-flex items-center"
aria-label="Discord"
target="_blank"
rel="noopener noreferrer"
>
<Discord
size="1em"
className="text-dark-80 dark:text-light-80 group-intent:text-dark-100 dark:group-intent:text-light-40"
/>
</a>
</div>
</nav>
<ThemeToggle client:idle />
</div>
<p class="text-sm text-center sm:text-left text-dark-40 px-3">
<p class="text-sm text-center sm:text-left text-dark-40 dark:text-light-40 px-3">
© 2010&ndash;{new Date().getFullYear()}, Video.js contributors
<br />
Video.js is a free and open source HTML5 video player framework
+1 -1
View File
@@ -5,7 +5,7 @@
<div class="absolute h-1/2 bottom-0 left-0 right-0 bg-red"></div>
<div class="absolute h-1/4 bottom-0 left-0 right-0 bg-purple"></div>
<div
class="relative font-semibold tracking-tighter text-light-80 select-none"
class="relative font-semibold tracking-tighter text-light-80 dark:text-dark-100 select-none"
style="font-size:28cqi;line-height:0.72;"
>
VideoJS
+3 -1
View File
@@ -28,7 +28,9 @@ const { class: className, dark = false } = Astro.props;
<nav
class:list={[
'pl-3 flex justify-between sticky top-0 z-40 border-b',
dark ? 'bg-dark-100 text-light-80 border-dark-100' : 'bg-light-80 border-light-40',
dark
? 'bg-dark-100 text-light-80 border-dark-100 dark:border-dark-80'
: 'bg-light-80 border-light-40 dark:bg-dark-100 dark:border-dark-80',
className,
]}
style="height: var(--nav-h);"
+4 -4
View File
@@ -35,7 +35,7 @@ export function Select<T extends string = string>({
<BaseSelect.Trigger
className={twMerge(
clsx(
'inline-flex items-center gap-2 bg-light-60 border border-light-40 rounded-lg text-sm p-2',
'inline-flex items-center gap-2 bg-light-60 dark:bg-dark-90 dark:text-light-100 border border-light-40 dark:border-dark-80 rounded-lg text-sm p-2',
),
className,
)}
@@ -55,7 +55,7 @@ export function Select<T extends string = string>({
>
<BaseSelect.Popup
className={clsx(
'border border-light-40 rounded-lg bg-light-60 shadow-xl text-sm',
'border border-light-40 dark:border-dark-80 rounded-lg bg-light-60 dark:bg-dark-80 shadow-xl text-sm',
'overflow-hidden',
)}
style={{
@@ -77,9 +77,9 @@ export function Select<T extends string = string>({
disabled={option.disabled}
className={clsx(
'flex items-center gap-2 p-2',
'cursor-pointer intent:bg-light-80',
'cursor-pointer intent:bg-light-80 dark:intent:bg-dark-100',
option.disabled && 'opacity-50 cursor-not-allowed',
option.value === value && 'bg-light-80',
option.value === value && 'bg-light-80 dark:bg-dark-100',
)}
>
<BaseSelect.ItemText>{option.label}</BaseSelect.ItemText>
+26
View File
@@ -0,0 +1,26 @@
---
import { THEME_KEY } from '@/consts';
---
{/* Dark mode initialization script - runs before paint to prevent FOUC */}
<script is:inline define:vars={{ localStorageKey: THEME_KEY }}>
if (
typeof localStorage !== 'undefined' &&
typeof document !== 'undefined' &&
typeof document.documentElement !== 'undefined'
) {
let localStorageValue = localStorage[localStorageKey];
const isValidKey = localStorageValue === 'light' || localStorageValue === 'dark' || localStorageValue === 'system';
if (!localStorageValue || !isValidKey) {
localStorage[localStorageKey] = 'system';
localStorageValue = 'system';
}
if (localStorageValue === 'dark') {
document.documentElement.classList.add('dark');
} else if (localStorageValue === 'system') {
if (typeof window.matchMedia !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
}
}
</script>
+118
View File
@@ -0,0 +1,118 @@
import { Toggle } from '@base-ui-components/react/toggle';
import { ToggleGroup } from '@base-ui-components/react/toggle-group';
import clsx from 'clsx';
import { Monitor, Moon, Sun } from 'lucide-react';
import { useEffect, useState } from 'react';
import { THEME_KEY } from '@/consts';
type Preference = 'system' | 'light' | 'dark';
type Theme = 'light' | 'dark';
const themeOptions = [
{ value: 'system' as const, label: 'System', icon: Monitor },
{ value: 'light' as const, label: 'Light', icon: Sun },
{ value: 'dark' as const, label: 'Dark', icon: Moon },
];
function initPreference(): Preference {
if (typeof localStorage === 'undefined') return 'system';
if (localStorage[THEME_KEY] === 'light') return 'light';
if (localStorage[THEME_KEY] === 'dark') return 'dark';
if (localStorage[THEME_KEY] === 'system') return 'system';
// Shouldn't be possible after head script runs, but handle it
localStorage[THEME_KEY] = 'system';
return 'system';
}
function getThemeFromPreference(preference: Preference): Theme {
if (preference === 'light') return 'light';
if (preference === 'dark') return 'dark';
if (preference === 'system') {
if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
return 'light';
}
export function ThemeToggle() {
const [preference, _setPreference] = useState<Preference | null>(null);
const [theme, setTheme] = useState<Theme | null>(null);
const setPreference = (newPreference: Preference) => {
_setPreference(newPreference);
if (typeof localStorage !== 'undefined') localStorage[THEME_KEY] = newPreference;
setTheme(getThemeFromPreference(newPreference));
};
const handleValueChange = (values: Preference[]) => {
if (values.length > 0) setPreference(values[0]);
};
// Initialize preference and theme on mount
useEffect(() => {
const initialPreference = initPreference();
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
_setPreference(initialPreference);
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
setTheme(getThemeFromPreference(initialPreference));
}, []);
// Listen to media query changes when preference is 'system'
useEffect(() => {
if (preference !== 'system') return;
if (typeof window === 'undefined' || typeof window.matchMedia === 'undefined') return;
const onMediaChange = (e: MediaQueryListEvent) => setTheme(e.matches ? 'dark' : 'light');
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', onMediaChange);
return () => {
mediaQuery.removeEventListener('change', onMediaChange);
};
}, [preference]);
// Keep document.documentElement in sync with theme
useEffect(() => {
if (typeof document === 'undefined') return;
if (theme === 'light') document.documentElement.classList.remove('dark');
else if (theme === 'dark') document.documentElement.classList.add('dark');
return () => {
document.documentElement.classList.remove('dark');
};
}, [theme]);
return (
<ToggleGroup
disabled={preference === null}
value={[preference]}
onValueChange={handleValueChange}
multiple={false}
className={clsx(
'inline-flex items-center gap-1 bg-light-60 dark:bg-dark-90 dark:text-light-100 border border-light-40 dark:border-dark-80 rounded-lg p-1',
)}
>
{themeOptions.map((option) => {
const Icon = option.icon;
return (
<Toggle
key={option.value}
value={option.value}
className={clsx(
'relative',
'flex items-center gap-1.5 px-2.5 py-1.5 rounded text-sm',
preference === null ? 'cursor-wait' : 'cursor-pointer',
preference === option.value ? 'bg-light-80 dark:bg-dark-100' : preference !== null ? 'intent:bg-light-80/50 dark:intent:bg-dark-100/50' : '',
)}
aria-label={option.label}
>
<Icon size={14} />
</Toggle>
);
})}
</ToggleGroup>
);
}
+2 -2
View File
@@ -49,9 +49,9 @@ export default function ToggleGroup<T extends string = string>({
value={option.value}
disabled={option.disabled}
className={clsx(
'text-dark-40 data-[pressed]:text-current',
'text-dark-40 dark:text-light-40 data-[pressed]:text-current',
'py-3 border-b border-transparent',
option.disabled ? 'opacity-50 cursor-not-allowed' : 'intent:border-dark-40 data-[pressed]:border-current cursor-pointer',
option.disabled ? 'opacity-50 cursor-not-allowed' : 'intent:border-dark-40 dark:intent:border-light-40 data-[pressed]:border-current cursor-pointer',
)}
>
{option.label}
+2 -2
View File
@@ -38,13 +38,13 @@ export function Tooltip({
<BaseTooltip.Positioner side={side} sideOffset={sideOffset}>
<BaseTooltip.Popup
className={clsx(
'bg-dark-100 text-light-80 text-sm px-3 py-2 rounded-lg shadow-lg',
'bg-dark-100 dark:bg-dark-90 text-light-80 dark:text-light-100 text-sm px-3 py-2 rounded-lg shadow-lg',
'z-50 max-w-xs',
className,
)}
>
{content}
<BaseTooltip.Arrow className="fill-dark-100" />
<BaseTooltip.Arrow className="fill-dark-100 dark:fill-dark-90" />
</BaseTooltip.Popup>
</BaseTooltip.Positioner>
</BaseTooltip.Portal>
@@ -67,9 +67,9 @@ export function AuthorSocialLinks({ socialLinks, className }: AuthorSocialLinksP
target="_blank"
rel="noopener noreferrer"
aria-label={`${config.label} profile`}
className="inline-flex items-center justify-center p-2 rounded-lg intent:text-dark-40 transition-colors"
className="inline-flex items-center justify-center p-2 rounded-lg intent:text-dark-40 dark:text-light-40"
>
<Icon size={20} />
<Icon size={20} strokeWidth={1.5} />
</a>
</Tooltip>
</li>
@@ -14,16 +14,16 @@ const { post, authors, asLink } = Astro.props;
<article>
<a href={`/blog/${post.id}`} class="group grid max-w-3xl">
<time datetime={post.data.pubDate.toISOString()} class="text-dark-40 mb-2">
<time datetime={post.data.pubDate.toISOString()} class="text-dark-40 dark:text-light-40 mb-2">
<FormattedDate date={post.data.pubDate} />
</time>
<div class="mb-3">
<h2 class="text-md font-medium md:text-h4 group-intent:underline mb-1">{post.data.title}</h2>
<p class="font-medium md:text-md text-dark-40">{post.data.description}</p>
<p class="font-medium md:text-md text-dark-40 dark:text-light-40">{post.data.description}</p>
</div>
{
authors.length > 0 && (
<div class="text-dark-40">
<div class="text-dark-40 dark:text-light-40">
{new Intl.ListFormat('en', { style: 'long', type: 'conjunction' }).format(
authors.map((author) => author.data.name),
)}
@@ -22,10 +22,10 @@ function getGuideUrl<F extends SupportedFramework>(framework: F, style: Supporte
prev ? (
<a
href={getGuideUrl(framework, style, prev.slug)}
class="grid items-center gap-x-2 p-4 border border-light-40 rounded-lg intent:border-dark-40"
class="grid items-center gap-x-2 p-4 border border-light-40 dark:border-dark-80 rounded-lg intent:border-dark-40 dark:intent:border-dark-40"
style="grid-template-areas: 'empty direction' 'icon title'; grid-template-columns: auto minmax(0,1fr); grid-template-rows: auto minmax(0,1fr);"
>
<div class="text-sm text-dark-80" style="grid-area: direction;">
<div class="text-sm text-dark-80 dark:text-light-40" style="grid-area: direction;">
Previous
</div>
<div class="text-base font-medium text-balance" style="grid-area: title">
@@ -43,10 +43,10 @@ function getGuideUrl<F extends SupportedFramework>(framework: F, style: Supporte
next ? (
<a
href={getGuideUrl(framework, style, next.slug)}
class="grid items-center gap-x-2 p-4 border border-light-40 rounded-lg intent:border-dark-40 text-right"
class="grid items-center gap-x-2 p-4 border border-light-40 dark:border-dark-80 rounded-lg intent:border-dark-40 text-right dark:intent:border-dark-40"
style="grid-template-areas: 'direction empty' 'title icon';grid-template-columns: minmax(0,1fr) auto; grid-template-rows: auto minmax(0,1fr);"
>
<div class="text-sm text-dark-80" style="grid-area: direction;">
<div class="text-sm text-dark-80 dark:text-light-40" style="grid-area: direction;">
Next
</div>
<div class="text-base font-medium text-balance" style="grid-area: title">
@@ -2,7 +2,6 @@
import type { SupportedFramework, SupportedStyle } from '@/types/docs';
import { PreferenceUpdater } from '@/components/docs/PreferenceUpdater';
import { Selectors } from '@/components/docs/Selectors';
import SidebarItem from '@/components/docs/SidebarItem.astro';
import { filterSidebar } from '@/utils/docs/sidebar';
@@ -18,6 +17,6 @@ const filteredSidebar = filterSidebar(framework, style);
<slot />
<PreferenceUpdater client:idle currentFramework={framework} currentStyle={style} />
<nav class="p-6 text-dark-80">
<nav class="p-6 text-dark-80 dark:text-light-40">
{filteredSidebar.map((item) => <SidebarItem item={item} framework={framework} style={style} docTitles={docTitles} />)}
</nav>
+1 -1
View File
@@ -69,7 +69,7 @@ export function Selectors({
}));
return (
<div className="p-6 lg:py-2.5 xl:p-6 border-b border-light-40">
<div className="p-6 lg:py-2.5 xl:p-6 border-b border-light-40 dark:border-dark-80">
<div className="max-w-3xl mx-auto w-full grid gap-x-6 gap-y-2 items-center" style={{ gridTemplateColumns: 'auto minmax(0, 1fr)' }}>
<span>Framework</span>
<Select
@@ -33,10 +33,10 @@ const isActive = containsActivePath(item);
<summary
class:list={[
'py-2 flex items-center gap-4 ',
'intent:text-dark-100 cursor-pointer',
'intent:text-dark-100 dark:intent:text-light-80 cursor-pointer',
depth === 0 && 'font-medium',
depth > 0 && 'border-l',
isActive ? 'text-dark-100 border-dark-100 ' : 'border-light-40',
isActive ? 'text-dark-100 dark:text-light-80 border-dark-100' : 'border-light-40 dark:border-dark-40',
]}
>
<span class="flex-1">{item.sidebarLabel}</span>
@@ -52,10 +52,10 @@ const isActive = containsActivePath(item);
<a
class:list={[
'py-2 flex items-center gap-4 ',
'intent:text-dark-100 cursor-pointer',
'intent:text-dark-100 dark:intent:text-light-100 cursor-pointer',
depth === 0 && 'font-medium',
depth > 0 && 'border-l',
isActive ? 'text-dark-100 border-dark-100 ' : 'border-light-40',
isActive ? 'text-dark-100 dark:text-light-100 border-current ' : 'border-light-40 dark:border-dark-40',
]}
style={`padding-left: calc(var(--spacing) * ${depth * 4})`}
href={`/docs/framework/${framework}/style/${style}/${item.slug}`}
@@ -33,7 +33,7 @@ export function TableOfContentsDesktop({ headings, activeId, onNavigate, classNa
<a
href={`#${heading.slug}`}
onClick={e => handleClick(e, heading.slug)}
className={clsx('text-sm block', activeId === heading.slug ? 'text-dark-100' : 'text-dark-40 intent:text-dark-100',
className={clsx('text-sm block', activeId === heading.slug ? 'text-dark-100 dark:text-light-100' : 'text-dark-40 dark:text-light-40 intent:text-dark-100 dark:intent:text-light-100',
)}
style={{ paddingLeft: `calc(${(heading.depth - 2)} * var(--spacing) * 4)` }}
>
@@ -24,7 +24,7 @@ export function TableOfContentsMobile({ headings, activeId, onNavigate, classNam
return (
<div
className={clsx('border-b border-light-40 bg-light-80 px-6 lg:px-12 h-(--h) flex items-center', className)}
className={clsx('border-b border-light-40 dark:border-dark-80 bg-light-80 dark:bg-dark-100 px-6 lg:px-12 h-(--h) flex items-center', className)}
style={{ '--h': 'var(--mobile-toc-h)' } as React.CSSProperties}
>
<div className="w-full max-w-3xl mx-auto">
@@ -20,7 +20,13 @@ const isCodeBlock = codeBlock === 'true';
<slot />
</Tag>
) : (
<Tag class={twMerge('bg-light-60 border border-light-40 px-1 rounded font-mono text-code', className)} {...props}>
<Tag
class={twMerge(
'bg-light-60 dark:bg-dark-90 dark:text-light-100 border border-light-40 dark:border-dark-80 px-1 rounded font-mono text-code',
className,
)}
{...props}
>
<slot />
</Tag>
)
+5 -3
View File
@@ -8,13 +8,15 @@ type Props<Tag extends HTMLTag = 'pre'> = Polymorphic<{ as: Tag }> & {
class?: string;
};
const { as: Tag = 'pre', maxWidth = true, class: className, style, ...props } = Astro.props;
const { as: Tag = 'pre', maxWidth = true, class: className, style: _style, ...props } = Astro.props;
---
<div class={twMerge(clsx('relative my-6 group', maxWidth && 'max-w-3xl mx-auto'))}>
<Tag
class={twMerge('rounded-lg p-6 overflow-x-auto overflow-y-scroll max-h-96 border border-light-40', className)}
style={`${style};background-color:var(--color-light-100);`}
class={twMerge(
'rounded-lg p-6 overflow-x-auto overflow-y-scroll max-h-96 border border-light-40 dark:border-dark-80 bg-light-100 dark:bg-dark-110',
className,
)}
{...props}
>
<slot />
+1
View File
@@ -2,3 +2,4 @@ export const SITE_TITLE = 'Video.js 10';
export const SITE_DESCRIPTION = `For over 15 years, Video.js has been the world's web video player. Now rebuilt in v10 for modern development and performance.`;
export const GITHUB_REPO_URL = 'https://github.com/muxinc/vjs-10-monorepo/';
export const DISCORD_INVITE_URL = 'https://discord.gg/yyatwmQr';
export const THEME_KEY = 'vjs-site-theme';
+6 -1
View File
@@ -1,12 +1,15 @@
---
import type { ImageMetadata } from 'astro';
import { Font } from 'astro:assets';
import { SITE_TITLE } from '@/consts';
import '@/styles/globals.css';
import FooterEasterEgg from '@/components/FooterEasterEgg.astro';
import Posthog from '@/components/Posthog.astro';
import ThemeInit from '@/components/ThemeInit.astro';
interface Props {
title: string | string[];
@@ -69,11 +72,13 @@ const fullTitle = Array.isArray(title)
{/* Analytics */}
<Posthog />
<ThemeInit />
</head>
<body
class:list={[
'relative',
'bg-light-80 text-dark-100',
'bg-light-80 dark:bg-dark-100 text-dark-100 dark:text-light-80',
'font-sans antialiased text-pretty text-base',
'min-w-80',
'[--nav-h:calc(var(--spacing)*13)] sm:[--nav-h:calc(var(--spacing)*14)]',
+1 -1
View File
@@ -34,7 +34,7 @@ const docTitles = new Map(allDocs.map((d) => [d.id, d.data.title]));
<DocsSidebar slot="mobile-nav" framework={framework} style={style} docTitles={docTitles} />
</NavBar>
<aside
class="hidden lg:block lg:border-r border-light-40 sticky overflow-y-scroll"
class="hidden lg:block lg:border-r border-light-40 dark:border-dark-80 sticky overflow-y-scroll"
style="top: var(--nav-h); max-height: calc(100vh - var(--nav-h));"
>
<DocsSidebar framework={framework} style={style} docTitles={docTitles}>
+1 -1
View File
@@ -48,7 +48,7 @@ const jsonLdSchema = createBlogCollectionSchema({
<JsonLd slot="head" schema={jsonLdSchema} />
<header class="mb-10 md:mb-20 w-full max-w-3xl mx-auto">
<h1 class="text-h4 md:text-h2 mb-2">Video.js Blog</h1>
<p class="text-md font-medium md:text-h5 text-dark-40">
<p class="text-md font-medium md:text-h5 text-dark-40 dark:text-light-40">
{page.data.length === 0 ? 'Coming soon' : 'Articles, announcements, news, updates, and more'}
</p>
</header>
+6 -3
View File
@@ -39,14 +39,17 @@ const jsonLdSchema = createBlogPostingSchema({
<Blog title={[post.data.title, 'Blog']} description={post.data.description}>
<JsonLd slot="head" schema={jsonLdSchema} />
<article class="@container">
<header class="border-b border-light-40 max-w-3xl mx-auto pb-10 mb-10">
<time datetime={post.data.pubDate.toISOString()} class="font-medium md:text-md text-dark-40 flex gap-4 mb-1">
<header class="border-b border-light-40 dark:border-dark-80 max-w-3xl mx-auto pb-10 mb-10">
<time
datetime={post.data.pubDate.toISOString()}
class="font-medium md:text-md text-dark-40 dark:text-light-40 flex gap-4 mb-1"
>
<FormattedDate date={post.data.pubDate} />
<span>&bull;</span>
<span>{remarkPluginFrontmatter.minutesRead}</span>
</time>
<h1 class="text-h4 md:text-h2 mb-2">{post.data.title}</h1>
<div class="font-medium md:text-md text-dark-40">
<div class="font-medium md:text-md text-dark-40 dark:text-light-40">
By {
new Intl.ListFormat('en', { style: 'long', type: 'conjunction' })
.formatToParts(authors.map((author) => author.data.name))
@@ -56,12 +56,12 @@ const jsonLdSchema = createProfilePageSchema({
<img
src={author.data.avatar}
alt={author.data.name}
class="w-24 h-24 md:w-36 md:h-36 rounded-full border border-light-40 bg-light-100 row-span-3"
class="w-24 h-24 md:w-36 md:h-36 rounded-full border border-light-40 dark:border-dark-80 bg-light-100 dark:bg-dark-110 row-span-3"
/>
)
}
<h1 class="text-h2 mb-1">{author.data.name}</h1>
{author.data.bio && <p class="text-md text-dark-80 mb-4">{author.data.bio}</p>}
{author.data.bio && <p class="text-md text-dark-80 dark:text-light-80 mb-4">{author.data.bio}</p>}
{
author.data.socialLinks && (
<AuthorSocialLinks client:idle socialLinks={author.data.socialLinks} className="flex gap-2" />
@@ -119,7 +119,7 @@ const jsonLdSchema = createTechArticleSchema({
</div>
<div class="@container px-6 lg:px-12 flex flex-col" style="grid-area: article;">
<article class="mb-18 flex-1">
<header class="border-b border-light-40 max-w-3xl mx-auto py-8 mb-8">
<header class="border-b border-light-40 dark:border-dark-80 max-w-3xl mx-auto py-8 mb-8">
<H3 as="h1" class="mt-0 mb-2">{doc.data.title}</H3>
<p class="@lg:font-medium">{doc.data.description}</p>
</header>
+10 -6
View File
@@ -30,7 +30,11 @@ const poster = `https://image.mux.com/${PLAYBACK_ID}/thumbnail.webp?time=0`;
</p>
</div>
<div class="vjs relative z-10 w-full -mb-11 md:-mb-21 lg:-mb-32 grid grid-cols-1 grid-rows-1">
<HeroVideo client:load className="aspect-video rounded-3xl bg-dark-80 shadow-2xl" poster={poster} />
<HeroVideo
client:load
className="aspect-video rounded-3xl bg-dark-80 dark:bg-dark-40 shadow-2xl"
poster={poster}
/>
</div>
</div>
<div class="absolute bottom-0 left-0 right-0 h-28 md:h-67 grid grid-rows-4">
@@ -57,7 +61,7 @@ const poster = `https://image.mux.com/${PLAYBACK_ID}/thumbnail.webp?time=0`;
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
<section class="flex gap-3 rounded-xl border border-light-40 p-6">
<section class="flex gap-3 rounded-xl border border-light-40 dark:border-dark-80 p-6">
<CircleCheck size={24} className="flex-shrink-0" />
<div>
<h3 class="font-semibold text-md">Preview</h3>
@@ -65,17 +69,17 @@ const poster = `https://image.mux.com/${PLAYBACK_ID}/thumbnail.webp?time=0`;
<p class="max-w-3xl">First public look, with limited features. Not production ready.</p>
</div>
</section>
<section class="flex gap-3 rounded-xl border border-light-40 p-6">
<section class="flex gap-3 rounded-xl border border-light-40 dark:border-dark-80 p-6">
<div>
<Clock size={24} className="flex-shrink-0" />
</div>
<div>
<h3 class="font-semibold text-md">Beta</h3>
<p class="font-medium mb-3">December 2025</p>
<p class="font-medium mb-3">February 2025</p>
<p class="max-w-3xl">Core features, like basic controls and adaptive streaming. Bugs likely.</p>
</div>
</section>
<section class="flex gap-3 rounded-xl border border-light-40 p-6">
<section class="flex gap-3 rounded-xl border border-light-40 dark:border-dark-80 p-6">
<div>
<Clock size={24} className="flex-shrink-0" />
</div>
@@ -85,7 +89,7 @@ const poster = `https://image.mux.com/${PLAYBACK_ID}/thumbnail.webp?time=0`;
<p class="max-w-3xl">Stable release. Fundamental v8 feature parity.</p>
</div>
</section>
<section class="flex gap-3 rounded-xl border border-light-40 p-6">
<section class="flex gap-3 rounded-xl border border-light-40 dark:border-dark-80 p-6">
<div>
<Clock size={24} className="flex-shrink-0" />
</div>
+14 -8
View File
@@ -1,12 +1,6 @@
@import 'tailwindcss';
/* before we begin with tailwind, let's go over some global rules */
/* dark mode should use dark chrome */
@media (prefers-color-scheme: dark) {
* {
color-scheme: dark;
}
}
/* nested lists should have lower-alpha style */
.list-decimal .list-decimal {
list-style-type: lower-alpha;
@@ -37,6 +31,16 @@
@slot;
}
}
@custom-variant dark (&:where(.dark, .dark *));
.dark * {
color-scheme: dark;
}
.dark .shiki,
.dark .shiki span,
.dark .astro-code,
.dark .astro-code span {
color: var(--shiki-dark) !important;
}
/* theme inline tells tailwind to reference the value of these properties */
@theme inline {
@@ -52,7 +56,9 @@
--color-white: white;
--color-black: black;
--color-dark-110: #2a2928;
--color-dark-100: #393836;
--color-dark-90: #494741;
--color-dark-80: #5a5850;
--color-dark-40: #7d796a;
@@ -108,11 +114,11 @@
--text-sm--letter-spacing: -0.02em;
/* Notice the use of em, so that code scales with, say, headers */
--text-code: 0.875em;
--text-code: 0.9375em;
--text-code--line-height: calc(28 / 16);
--text-code--letter-spacing: -0.02em;
/* if you don't want this scaling, use text-base-code */
--text-base-code: 0.875rem;
--text-base-code: 0.9375rem;
--text-base-code--line-height: calc(28 / 16);
--text-base-code--letter-spacing: -0.02em;