From 20f601636fe57f1fdad2695cff02db1def3a13c4 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 24 Sep 2026 18:00:14 -0700 Subject: [PATCH] Redesign the homepage for a more professional look, keeping the hand-drawn art moq.dev needs to read well to bigger companies without losing what makes it stand out. Keep the dark slate/green scheme and the existing hand-drawn art, and pair it with plain system-sans typography and a conventional layout. - Top nav replaces the sidebar, with a moq.pro button carrying the cat badge - New homepage: hero with brush underline and hand-drawn strike-throughs, standards strip, feature grid, use-case cards (icon slots for new art), self-host vs. moq.pro, and latest posts with cover art - Footer with project, community, and production links - Blog index and post styling cleaned up (no heavy heading underlines) - Hand-drawn strokes and notes copied from moq.pro into public/drawn/ - Drop the /try vs /anon note from the demo page Co-Authored-By: Claude Opus 5.5 --- public/drawn/how.svg | 52 ++++++++ public/drawn/icon-back.svg | 6 + public/drawn/icon-battery.svg | 6 + public/drawn/icon-globe.svg | 4 + public/drawn/icon-link.svg | 4 + public/drawn/icon-lock.svg | 5 + public/drawn/icon-puzzle.svg | 4 + public/drawn/icon-rocket.svg | 1 + public/drawn/icon-stonk.svg | 1 + public/drawn/open.svg | 81 ++++++++++++ public/drawn/stroke-1.svg | 17 +++ public/drawn/stroke-3.svg | 16 +++ public/drawn/stroke-5.svg | 17 +++ public/drawn/what.svg | 66 ++++++++++ src/components/feature.astro | 44 ------- src/layouts/global.astro | 133 ++++++++++++++------ src/layouts/global.css | 100 +++++++++++++-- src/pages/blog/index.astro | 72 ++++++----- src/pages/demo.mdx | 2 - src/pages/index.astro | 224 ++++++++++++++++++++++++++++++++++ src/pages/index.mdx | 64 ---------- src/stats.ts | 4 +- tailwind.config.cjs | 4 + 23 files changed, 732 insertions(+), 195 deletions(-) create mode 100644 public/drawn/how.svg create mode 100644 public/drawn/icon-back.svg create mode 100644 public/drawn/icon-battery.svg create mode 100644 public/drawn/icon-globe.svg create mode 100644 public/drawn/icon-link.svg create mode 100644 public/drawn/icon-lock.svg create mode 100644 public/drawn/icon-puzzle.svg create mode 100644 public/drawn/icon-rocket.svg create mode 100644 public/drawn/icon-stonk.svg create mode 100644 public/drawn/open.svg create mode 100644 public/drawn/stroke-1.svg create mode 100644 public/drawn/stroke-3.svg create mode 100644 public/drawn/stroke-5.svg create mode 100644 public/drawn/what.svg delete mode 100644 src/components/feature.astro create mode 100644 src/pages/index.astro delete mode 100644 src/pages/index.mdx diff --git a/public/drawn/how.svg b/public/drawn/how.svg new file mode 100644 index 0000000..cf0c9a0 --- /dev/null +++ b/public/drawn/how.svg @@ -0,0 +1,52 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + + + + + + + + diff --git a/public/drawn/icon-back.svg b/public/drawn/icon-back.svg new file mode 100644 index 0000000..f67368f --- /dev/null +++ b/public/drawn/icon-back.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/drawn/icon-battery.svg b/public/drawn/icon-battery.svg new file mode 100644 index 0000000..c44532e --- /dev/null +++ b/public/drawn/icon-battery.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/drawn/icon-globe.svg b/public/drawn/icon-globe.svg new file mode 100644 index 0000000..58ab38e --- /dev/null +++ b/public/drawn/icon-globe.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/drawn/icon-link.svg b/public/drawn/icon-link.svg new file mode 100644 index 0000000..2a3673d --- /dev/null +++ b/public/drawn/icon-link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/drawn/icon-lock.svg b/public/drawn/icon-lock.svg new file mode 100644 index 0000000..14930ee --- /dev/null +++ b/public/drawn/icon-lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/drawn/icon-puzzle.svg b/public/drawn/icon-puzzle.svg new file mode 100644 index 0000000..9f5c965 --- /dev/null +++ b/public/drawn/icon-puzzle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/drawn/icon-rocket.svg b/public/drawn/icon-rocket.svg new file mode 100644 index 0000000..fd12f47 --- /dev/null +++ b/public/drawn/icon-rocket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/drawn/icon-stonk.svg b/public/drawn/icon-stonk.svg new file mode 100644 index 0000000..2c8ba81 --- /dev/null +++ b/public/drawn/icon-stonk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/drawn/open.svg b/public/drawn/open.svg new file mode 100644 index 0000000..657688a --- /dev/null +++ b/public/drawn/open.svg @@ -0,0 +1,81 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + + + + + + + + + + + + + + diff --git a/public/drawn/stroke-1.svg b/public/drawn/stroke-1.svg new file mode 100644 index 0000000..e6f230c --- /dev/null +++ b/public/drawn/stroke-1.svg @@ -0,0 +1,17 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + diff --git a/public/drawn/stroke-3.svg b/public/drawn/stroke-3.svg new file mode 100644 index 0000000..10a6790 --- /dev/null +++ b/public/drawn/stroke-3.svg @@ -0,0 +1,16 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + diff --git a/public/drawn/stroke-5.svg b/public/drawn/stroke-5.svg new file mode 100644 index 0000000..ccb5db5 --- /dev/null +++ b/public/drawn/stroke-5.svg @@ -0,0 +1,17 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + diff --git a/public/drawn/what.svg b/public/drawn/what.svg new file mode 100644 index 0000000..0156dbb --- /dev/null +++ b/public/drawn/what.svg @@ -0,0 +1,66 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + + + + + + + + + + diff --git a/src/components/feature.astro b/src/components/feature.astro deleted file mode 100644 index 1fc6c61..0000000 --- a/src/components/feature.astro +++ /dev/null @@ -1,44 +0,0 @@ ---- -type Feature = - | "source" - | "web" - | "realtime" - | "scale" - | "native" - | "discover" - | "efficient" - | "distribution" - | "contribution" - | "robotics" - | "conferencing"; - -interface Props { - icon: Feature; -} - -const { icon } = Astro.props; - -// The first set are Kixel's original hand-drawn icons from moq.pro. The -// use-case icons in public/home/use-case/ are placeholders waiting on -// hand-drawn versions, same as public/social/. -const artwork: Record = { - source: "https://moq.pro/icons/box.svg", - web: "https://moq.pro/icons/globe.svg", - realtime: "https://moq.pro/icons/rocket.svg", - scale: "https://moq.pro/icons/stonk.svg", - native: "https://moq.pro/icons/puzzle.svg", - discover: "https://moq.pro/icons/link.svg", - efficient: "https://moq.pro/icons/battery.svg", - distribution: "/home/use-case/distribution.svg", - contribution: "/home/use-case/contribution.svg", - robotics: "/home/use-case/robotics.svg", - conferencing: "/home/use-case/conferencing.svg", -}; ---- - -
  • - - -
  • diff --git a/src/layouts/global.astro b/src/layouts/global.astro index 11e37ab..25a9bd8 100644 --- a/src/layouts/global.astro +++ b/src/layouts/global.astro @@ -3,22 +3,24 @@ import "./global.css"; // Imported after global.css so the theme's .hljs-* color rules land later in // the cascade and beat the prose plugin's .markdown :where(pre code) { color: inherit }. import "highlight.js/styles/atom-one-dark.css"; -import NavLink from "@/components/nav-link.astro"; // NOTE: This is magically used as the type for Astro.props interface Props { title: string; description: string; + // The page draws its own layout instead of sitting in the reading column. + bare?: boolean; frontmatter?: { title: string; date: string; description: string; + author?: string; cover?: string; }; } -let { title, frontmatter, description } = Astro.props; +let { title, frontmatter, description, bare } = Astro.props; if (frontmatter?.title) title = frontmatter.title; if (frontmatter?.description) description = frontmatter.description; @@ -32,10 +34,20 @@ const ogImage = new URL(frontmatter?.cover ?? "/layout/icon.png", siteUrl).toStr // moq.wtf for staging builds, moq.pro for live (and local dev). const proUrl = import.meta.env.MODE === "staging" ? "https://moq.wtf" : "https://moq.pro"; + +const path = Astro.url.pathname; +const nav = [ + { href: "/demo", word: "Demo" }, + { href: "/blog", word: "Blog" }, + { href: "https://doc.moq.dev", word: "Docs" }, +]; +const date = frontmatter?.date + ? new Date(frontmatter.date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", timeZone: "UTC" }) + : undefined; --- - + @@ -46,6 +58,9 @@ const proUrl = import.meta.env.MODE === "staging" ? "https://moq.wtf" : "https:/ {fullTitle} + + + @@ -68,41 +83,91 @@ const proUrl = import.meta.env.MODE === "staging" ? "https://moq.wtf" : "https:/ - -
    - +
    + - +
    + { + bare ? ( + + ) : ( +
    + {date && ( +

    + {date} + {frontmatter?.author && <> · {frontmatter.author}} +

    + )} + +
    + ) + } +
    - - - - - - + diff --git a/src/layouts/global.css b/src/layouts/global.css index b84d558..9e6bbc1 100644 --- a/src/layouts/global.css +++ b/src/layouts/global.css @@ -2,6 +2,31 @@ @tailwind components; @tailwind utilities; +@layer base { + html { + @apply bg-slate-900 text-slate-200; + color-scheme: dark; + font-size: 17px; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + } + + /* Plain system sans on purpose: the handwriting is the personality. */ + body { + @apply font-sans; + font-optical-sizing: auto; + } + + ::selection { + background: rgb(34 197 94 / 30%); + } + + :focus-visible { + outline: 2px solid #22c55e; + outline-offset: 3px; + } +} + /* highlight.js's atom-one-dark theme paints its own background on code.hljs, which double-stacks with the prose plugin's pre background. Strip it. */ code.hljs, @@ -11,25 +36,78 @@ pre code.hljs { } @layer components { + .display { + @apply font-sans font-bold tracking-[-0.025em] text-white; + line-height: 1.05; + } + + /* Small technical labels. Used sparingly so mono still means "this is code". */ + .label { + @apply font-mono text-[0.72rem] uppercase tracking-[0.08em] text-slate-400; + } + + .green-link { + @apply text-green-500 underline decoration-green-500/40 decoration-2 underline-offset-4 transition-colors hover:decoration-green-500; + } + + .btn { + @apply inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-[0.95rem] font-semibold leading-none transition-colors; + } + .btn-green { + @apply btn bg-green-500 text-slate-900 hover:bg-green-400; + } + .btn-line { + @apply btn border border-slate-700 text-slate-100 hover:border-slate-500 hover:bg-slate-800; + } + + /* A hand-drawn strike-through: crossed out by a person, not by CSS. */ + .struck { + @apply relative inline-block text-slate-400; + } + .struck::after { + content: ""; + position: absolute; + left: -6%; + right: -6%; + top: 40%; + height: 0.28em; + background: url("/drawn/stroke-3.svg") no-repeat; + background-size: 100% 100%; + transform: rotate(-3deg); + } + /* Different strokes and angles so a row of them doesn't look copy-pasted. */ + .struck:nth-of-type(3n + 2)::after { + background-image: url("/drawn/stroke-1.svg"); + transform: scaleX(-1) rotate(-2deg); + } + .struck:nth-of-type(3n + 3)::after { + background-image: url("/drawn/stroke-5.svg"); + transform: rotate(-1deg); + } + /* see https://tailwindcss.com/docs/typography-plugin */ .markdown { - @apply prose prose-lg lg:prose-xl max-w-none; - @apply prose-moq; - @apply prose-headings:underline prose-headings:decoration-green-500 prose-headings:decoration-4 prose-headings:underline-offset-4; - @apply prose-h3:text-2xl; - @apply prose-th:no-underline; - @apply prose-h1:mb-8 prose-h1:mt-12 prose-h1:decoration-8; - @apply prose-h2:decoration-4; - @apply prose-h3:decoration-2; + @apply prose prose-lg max-w-none prose-moq; + @apply prose-headings:font-bold prose-headings:tracking-[-0.02em]; + @apply prose-h1:text-[2.6rem] md:prose-h1:text-[3.2rem] prose-h1:leading-[1.05] prose-h1:mb-10 prose-h1:mt-4; + @apply prose-h2:text-[1.7rem] prose-h2:mt-14; + @apply prose-h3:text-[1.3rem]; + @apply prose-p:leading-[1.7]; @apply prose-li:my-1; - @apply prose-img:rounded-lg; + @apply prose-img:rounded-xl; @apply prose-figure:object-center prose-figure:text-center prose-img:m-auto; - @apply prose-a:font-bold prose-a:text-green-500 prose-a:underline prose-a:decoration-green-500 prose-a:decoration-2 prose-a:underline-offset-4; + @apply prose-a:font-semibold prose-a:text-green-500 prose-a:underline prose-a:decoration-green-500/40 prose-a:decoration-2 prose-a:underline-offset-4 hover:prose-a:decoration-green-500; + @apply prose-code:font-mono prose-code:text-[0.82em] prose-code:font-normal prose-code:before:content-none prose-code:after:content-none; + @apply prose-pre:rounded-xl prose-pre:border prose-pre:border-slate-800 prose-pre:text-[0.82rem]; /* Long URLs and inline `code` shouldn't push the page wider than the viewport. */ @apply break-words; } + .markdown :where(code):not(:where(pre *)) { + @apply rounded bg-slate-800 px-1.5 py-0.5; + } + /* Wide tables scroll on their own instead of widening the page. `display: block` turns the table into the scroll container; the rows still lay out as a table inside it. Specificity is .markdown table (0,1,1) so it beats the prose @@ -47,7 +125,7 @@ pre code.hljs { } .tagline { - @apply my-12 text-center text-2xl font-bold text-white underline decoration-green-500 decoration-4 underline-offset-4 md:text-3xl; + @apply my-12 text-center text-2xl font-bold text-white md:text-3xl; } table td:first-child { diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index 8fb0b71..5c340e5 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -1,7 +1,6 @@ --- import MainLayout from "../../layouts/global.astro"; -//TODO: add the interface for the frontmatter of the blog posts interface Frontmatter { title: string; cover: string; @@ -13,47 +12,44 @@ interface Frontmatter { const posts = Object.values( await import.meta.glob<{ frontmatter: Frontmatter; url: string }>("./*.mdx", { eager: true }), ); -posts.sort((a, b) => { - const dateA = Date.parse(a.frontmatter.date); - const dateB = Date.parse(b.frontmatter.date); - return dateB - dateA; -}); ---- - - -
    -
    -

    Blog Posts

    - - - - +posts.sort((a, b) => Date.parse(b.frontmatter.date) - Date.parse(a.frontmatter.date)); - - +const formatDate = (date: string) => + new Date(date).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", timeZone: "UTC" }); +--- - - - - RSS - + +
    +
    +
    +

    Blog

    +

    Hot takes on live media, QUIC, and building MoQ in the open.

    +
    + RSS
    - { - posts.map((post) => ( - - )) - } + + )) + } +
    diff --git a/src/pages/demo.mdx b/src/pages/demo.mdx index 137fc71..96f1934 100644 --- a/src/pages/demo.mdx +++ b/src/pages/demo.mdx @@ -89,8 +89,6 @@ Want something you can run from a terminal? These copy-paste examples use the sa
    -> Browser demos use private broadcasts in `/try`. The explicit `/anon` sandbox used by these terminal examples is public and ephemeral. Pick a unique broadcast name, do not publish anything private, and stop the command when you're done. - ## CLI The [`moq` CLI](https://doc.moq.dev/bin/cli) imports and exports common media containers over MoQ. diff --git a/src/pages/index.astro b/src/pages/index.astro new file mode 100644 index 0000000..c82d032 --- /dev/null +++ b/src/pages/index.astro @@ -0,0 +1,224 @@ +--- +import Layout from "@/layouts/global.astro"; + +interface Post { + frontmatter: { title: string; description: string; date: string; cover: string }; + url: string; +} + +const posts = Object.values(import.meta.glob("./blog/*.mdx", { eager: true })) + .sort((a, b) => Date.parse(b.frontmatter.date) - Date.parse(a.frontmatter.date)) + .slice(0, 3); + +const formatDate = (date: string) => + new Date(date).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", timeZone: "UTC" }); + +const proUrl = import.meta.env.MODE === "staging" ? "https://moq.wtf" : "https://moq.pro"; + +const features = [ + { icon: "rocket", title: "Adaptive latency", body: "Real-time, interactive, or lean-back from one stack. Less important data is skipped during congestion so viewers stay live." }, + { icon: "stonk", title: "Massive scale", body: "Everything can be cached and fanned out by a CDN, from a single self-hosted relay to millions of viewers." }, + { icon: "link", title: "Live discovery", body: "Relays gossip broadcasts and the shortest route between them. No central authority, no rooms to configure." }, + { icon: "battery", title: "Cheap and lazy", body: "Nothing is transmitted, or even encoded, until a viewer asks for it." }, + { icon: "globe", title: "Modern web", body: "WebTransport, WebCodecs, and WebAudio in the browser. No plugins, no hacks." }, + { icon: "puzzle", title: "Every platform", body: "Rust for native, TypeScript for the web, with bindings for C, Python, Kotlin, Swift, and Go." }, + { icon: "lock", title: "Secure", body: "Encrypted with TLS and authenticated with JWT. Self-host a private CDN or encrypt end to end." }, + { icon: "back", title: "Plays well with others", body: "Ingest from OBS, FFmpeg, GStreamer, RTMP, and SRT. Serve HLS and WebRTC to the players you already have." }, +]; + +// `icon` is the hand-drawn art in public/home/use-case/. The ones without it +// render an empty slot until they're drawn. +const useCases = [ + { title: "Distribution", icon: "distribution", body: "Fan out to millions of viewers on demand, and keep legacy players on HLS.", href: "/blog/replacing-hls-dash" }, + { title: "Contribution", icon: "contribution", body: "Ingest from OBS, FFmpeg, or SRT with dynamic origins and failover.", href: "https://doc.moq.dev/bin/obs" }, + { title: "Conferencing", icon: "conferencing", body: "Browser publish and watch with a live roster, plus WebRTC for WHIP and WHEP clients.", href: "/blog/first-app" }, + { title: "Robotics", icon: "robotics", body: "Real-time teleoperation, with telemetry and control on the same relays as the video.", href: "/blog/on-a-boat" }, + { title: "Voice and video AI", body: "Server-side media in Rust or Python, played back faster than real time in the browser.", href: "https://doc.moq.dev/concept/use-case/ai" }, + { title: "Interactive", body: "Media down, input up. MoQ Boy is a Game Boy played by everyone watching at once.", href: "/boy" }, +]; +--- + + + +
    + + Oct 8 + DEMOQED, the first MoQ conference, in San Francisco + + + +

    + Real-time media and + + massive scale + + . +

    + +

    + One open protocol to replace WebRTC, HLS/DASH, and + RTMP/SRT. +

    + +

    + Media over QUIC is a live media protocol built on QUIC, the transport behind HTTP/3. It's being standardized at the + IETF, and this is the open source implementation: Rust for native, TypeScript for the browser. +

    + + +
    + + +
    +
    +
    + IETF + QUIC +
    +

    + Build first, argue later. + MoQ is being standardized by the IETF + as a new RFC. We ship + moq-lite today: a forwards-compatible + subset of moq-transport + that works with any MoQ CDN. +

    +
    +
    + + +
    +
    + what you get +

    The latency of WebRTC with the scale of HLS.

    +
    +
    + { + features.map((feature) => ( +
    + +

    {feature.title}

    +

    {feature.body}

    +
    + )) + } +
    +
    + + +
    +

    Built for more than video.

    + +
    + + +
    +
    +
    +

    Ready for production.

    +

    + Every piece is open source under MIT or Apache 2.0. Run it yourself, or let us run it for you. +

    + +
    +
    +
    + Open source +

    Self-host

    +

    + Deploy moq-relay anywhere. Clusters discover each + other, route by the shortest path, and fail over without a central controller. +

    +
      +
    • ✓  No license fees, no lock-in
    • +
    • ✓  Compatible with any MoQ CDN
    • +
    • ✓  Community support on Discord
    • +
    + +
    +
    + Managed +

    + moq.pro + +

    +

    + A global MoQ CDN run by the people who wrote the relay. Every viewer lands on the nearest edge, with no regions + to pick. +

    +
      +
    • ✓  500 GB of delivery free every month
    • +
    • ✓  Up and running in minutes, nothing to operate
    • +
    • ✓  Direct support from the maintainers
    • +
    + +
    +
    +
    +
    + + +
    + +
    +
    diff --git a/src/pages/index.mdx b/src/pages/index.mdx deleted file mode 100644 index 952edbd..0000000 --- a/src/pages/index.mdx +++ /dev/null @@ -1,64 +0,0 @@ ---- -layout: "@/layouts/global.astro" ---- - -import Feature from "@/components/feature.astro"; -import Author from "@/components/author.astro"; - -
    - Media over QUIC Logo -
    - -**Media over QUIC** is a live media protocol powered by [QUIC](https://quicwg.org/): -a super-charged TCP/UDP replacement that powers [HTTP/3](https://en.wikipedia.org/wiki/HTTP/3). -It's being standardized by the [IETF](https://www.ietf.org/) and your _favorite_ big tech companies such as Google, Cloudflare, Cisco, Meta, Akamai, etc (and me ofc). - -Our goal is to replace [WebRTC](./blog/replacing-webrtc), [HLS/DASH](./blog/replacing-hls-dash), and [RTMP/SRT](./blog/on-a-boat) with one generic/extensible protocol. -Supports any **latency**, any **scale**, and any **content** (not just media!). - - - -### Features - -
      - **Real-time Latency**: Stay **live** by skipping less important data during congestion. - **Massive Scale**: Fan out via a managed [CDN](https://moq.pro) and/or self-hosted [moq-relay](https://doc.moq.dev/bin/relay/). - **Live Discovery**: Gossips broadcasts and the shortest route without a central authority. - **Cheap and Lazy**: Nothing is transmitted (or encoded!) until a viewer requests it. - **Web Support**: [Typescript](https://doc.moq.dev/lib/js/) utilizing [WebTransport](https://caniuse.com/webtransport), [WebCodecs](https://caniuse.com/webcodecs), and [WebAudio](https://caniuse.com/audio-api). - **Native Support**: [Rust](https://doc.moq.dev/lib/rs/), plus [C](https://doc.moq.dev/lib/c/), [Python](https://doc.moq.dev/lib/py/), [Kotlin](https://doc.moq.dev/lib/kt/), [Swift](https://doc.moq.dev/lib/swift/), and [Go](https://doc.moq.dev/lib/go/) bindings. - **Integrations**: [GStreamer](https://doc.moq.dev/bin/gstreamer), [OBS](https://doc.moq.dev/bin/obs), [FFmpeg](https://doc.moq.dev/bin/cli), [CLI](https://doc.moq.dev/bin/cli), and more! -
    - -### Use Cases - -
      - **[Distribution](/blog/replacing-hls-dash)**: Scalable and on-demand, plus [HLS compatibility](https://github.com/moq-dev/moq/tree/main/rs/moq-hls). Think HTTP but live. - **Contribution**: Dynamic origins and failover, plus [TS/SRT compatibility](https://github.com/moq-dev/moq/tree/main/rs/moq-srt). - **[Robotics](/blog/on-a-boat)**: Real-time teleoperation with custom telemetry, plus [WebRTC compatibility](https://github.com/moq-dev/moq/tree/main/rs/moq-rtc). - **[Conferencing](/blog/first-app)**: Web support and dynamic peer discovery. [P2P](https://www.iroh.computer/) for native apps. -
    - -Enough pondering, try the demos already. There's even a gameboy emulator. Don't pretend like you're not excited. - -### *Standards -This website uses [moq-lite](https://doc.moq.dev/concept/layer/moq-lite), a subset of the *official* [moq-transport](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/) draft. -moq-lite is forwards compatible with moq-transport (draft-14+) and will work with any MoQ CDN. -The principles behind MoQ are fantastic, but standards are **SLOW** and involve too much arguing/bloat. -My goal is to get MoQ in __production now__, even if it's not a standard yet. - - -### More Stuff -- The [blog](/blog) if you're a fan of hot takes. -- The [docs](https://doc.moq.dev) and [Github](https://github.com/moq-dev/moq) if you're a nerd. -- The [discord](https://discord.moq.dev) if you want to chat with me or other unhinged developers. -- The [demos](/demo) if you seriously got this far without clicking on a link. -- The [pro offering](https://moq.pro) if you want to pay me for hosting lul. - -Contributions are welcome, of course. -But also feel free to just yoink the code, fork it, and make it your own. -If you're using some flavor of MoQ in the wild, or would like some help/advice, I'd love to hear about it: `me@kixel.me` - - diff --git a/src/stats.ts b/src/stats.ts index 1a975a3..09d8f13 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -76,7 +76,7 @@ const round = (n: number) => compact.format(n).toLowerCase(); const TITLES: Record = { stars: "GitHub stars", chatters: "Discord members" }; // Fills every element with a `data-stat="stars"` / `data-stat="chatters"` -// attribute, e.g. "1.5k stars", and titles the surrounding link with the exact +// attribute, e.g. "1.5k stars" (or just "1.5k" with `data-short`), and titles the surrounding link with the exact // count. Elements stay empty if a fetch fails; the links still work. export async function renderStats() { const cached = readCache(); @@ -87,7 +87,7 @@ export async function renderStats() { const key = el.dataset.stat as keyof Stats; const value = stats[key]; if (typeof value !== "number") continue; - el.textContent = `${round(value)} ${key}`; + el.textContent = "short" in el.dataset ? round(value) : `${round(value)} ${key}`; const link = el.closest("a"); if (link) link.title = `${value.toLocaleString()} ${TITLES[key]}`; } diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 0f754e3..aba83c2 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -2,6 +2,10 @@ module.exports = { content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], theme: { extend: { + fontFamily: { + sans: ["system-ui", "-apple-system", '"Segoe UI"', "Roboto", "Helvetica", "Arial", "sans-serif"], + mono: ['"JetBrains Mono"', "ui-monospace", "monospace"], + }, typography: ({ theme }) => ({ moq: { css: {