diff --git a/.vitepress/config.mts b/.vitepress/config.mts
index 8a3b685c..cabef645 100644
--- a/.vitepress/config.mts
+++ b/.vitepress/config.mts
@@ -2,7 +2,7 @@
import { defineConfig } from 'vitepress'
import container from 'markdown-it-container'
import { renderSandbox } from 'vitepress-plugin-sandpack'
-import { withMermaid } from 'vitepress-plugin-mermaid'
+import { MermaidMarkdown, MermaidPlugin } from 'vitepress-plugin-mermaid'
import { head, nav, sidebar, transformHead } from './configs/index.mts'
const LIVE_SANDBOX_CONTAINER_PATTERN = /:{3,}\s*textmode-(?:api-)?sandbox\b[^\n]*\n[\s\S]*?\n:{3,}/g
@@ -61,7 +61,7 @@ const themeConfig = {
}
// https://vitepress.dev/reference/site-config
-export default withMermaid(defineConfig({
+export default defineConfig({
lang: 'en-US',
appearance: 'dark',
lastUpdated: true,
@@ -84,8 +84,20 @@ export default withMermaid(defineConfig({
hostname: 'https://code.textmode.art'
},
transformHead,
+ transformHtml: (html, _id, { pageData }) => pageData.frontmatter.layout === 'home'
+ ? html.replace(/(
]*id="VPContent")/, '$1 role="main"') : html,
vite: {
+ // Retain the plugin's configuration module; the theme loads its renderer lazily.
+ plugins: [{ ...MermaidPlugin(), transform: undefined }],
+ resolve: {
+ alias: {
+ 'dayjs/plugin/advancedFormat.js': 'dayjs/esm/plugin/advancedFormat',
+ 'dayjs/plugin/customParseFormat.js': 'dayjs/esm/plugin/customParseFormat',
+ 'dayjs/plugin/isoWeek.js': 'dayjs/esm/plugin/isoWeek',
+ 'cytoscape/dist/cytoscape.umd.js': 'cytoscape/dist/cytoscape.esm.js',
+ },
+ },
optimizeDeps: {
// Mermaid is only reached through a lazily-loaded component, so Vite's
// dependency scanner misses it. Pre-bundle it (and its CommonJS
@@ -96,6 +108,8 @@ export default withMermaid(defineConfig({
markdown: {
config(md) {
+ // Keep the plugin's Markdown syntax without its eager browser runtime.
+ MermaidMarkdown(md, {})
md
.use(container, 'textmode-sandbox', {
render(tokens: any[], idx: number) {
@@ -121,4 +135,4 @@ export default withMermaid(defineConfig({
},
themeConfig,
-}))
+})
diff --git a/.vitepress/configs/head.mts b/.vitepress/configs/head.mts
index c6072df2..c819343f 100644
--- a/.vitepress/configs/head.mts
+++ b/.vitepress/configs/head.mts
@@ -9,9 +9,6 @@ const baseHeaders: HeadConfig[] = [
['link', { rel: 'icon', type: 'image/png', sizes: '64x64', href: '/icon-dark-64.png', media: '(prefers-color-scheme: dark)' }],
['link', { rel: 'manifest', href: '/site.webmanifest' }],
['script', {}, "(()=>{const m=window.matchMedia('(prefers-color-scheme: dark)');const u=()=>{const mode=m.matches?'dark':'light';document.querySelectorAll('link[rel*=\"icon\"][sizes]').forEach(l=>{l.href=l.href.replace(/-(light|dark)-/,`-${mode}-`)});};m.addEventListener('change',u);})();"],
- ['link', { rel: 'preconnect', href: 'https://fonts.googleapis.com' }],
- ['link', { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }],
- ['link', { href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap', rel: 'stylesheet' }]
]
export const head: HeadConfig[] = [...baseHeaders]
@@ -181,6 +178,7 @@ export const transformHead = ({ pageData }: TransformContext): HeadConfig[] => {
// Schema: Home page
if (isHomePage) {
+ head.push(['link', { rel: 'preload', href: '/fonts/Bescii-Mono.woff', as: 'font', type: 'font/woff', crossorigin: '' }])
head.push(['script', { type: 'application/ld+json' }, JSON.stringify({
"@context": "https://schema.org",
"@graph": [
diff --git a/.vitepress/theme/components/TextmodeHero.vue b/.vitepress/theme/components/TextmodeHero.vue
index 4f3a24c9..e680df07 100644
--- a/.vitepress/theme/components/TextmodeHero.vue
+++ b/.vitepress/theme/components/TextmodeHero.vue
@@ -1,17 +1,24 @@
-
+
+
+
{{ frontmatter.hero.name }}
+
{{ frontmatter.hero.tagline }}
+
+
+
+
diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css
index 6305f113..cd6cae78 100644
--- a/.vitepress/theme/custom.css
+++ b/.vitepress/theme/custom.css
@@ -1,5 +1,66 @@
-/* Import a good monospace font for ASCII aesthetics */
-@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap');
+@font-face {
+ font-family: 'Bescii Mono';
+ src: url('/fonts/Bescii-Mono.woff') format('woff');
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+/* Same JetBrains Mono face, served locally; browsers load only needed subsets. */
+/* cyrillic-ext */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400 700;
+ font-display: swap;
+ src: url(/fonts/JetBrainsMono-cyrillic-ext.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400 700;
+ font-display: swap;
+ src: url(/fonts/JetBrainsMono-cyrillic.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400 700;
+ font-display: swap;
+ src: url(/fonts/JetBrainsMono-greek.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400 700;
+ font-display: swap;
+ src: url(/fonts/JetBrainsMono-vietnamese.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400 700;
+ font-display: swap;
+ src: url(/fonts/JetBrainsMono-latin-ext.woff2) format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400 700;
+ font-display: swap;
+ src: url(/fonts/JetBrainsMono-latin.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
/* Custom CSS variables for textmode.js theme */
:root {
@@ -73,13 +134,6 @@ body::-webkit-scrollbar-thumb:hover {
display: none !important;
}
-/* Hide default hero heading since we have a custom UI */
-.VPHome .VPHero .heading,
-.VPHome .VPHero h1.text,
-.VPHome .VPHero .main .name {
- display: none !important;
-}
-
/* Enhanced hero section styles - only apply to home page */
.VPHome .VPHero {
position: relative;
@@ -97,15 +151,12 @@ body::-webkit-scrollbar-thumb:hover {
.textmode-hero-container {
position: relative;
width: 100%;
- height: 400px;
+ min-height: 400px;
margin: 1.5rem 0;
border-radius: 8px;
overflow: hidden;
- background: linear-gradient(135deg, var(--vp-c-bg-soft) 0%, var(--vp-c-bg) 100%);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
+ background: #1b1b1f;
+ isolation: isolate;
}
.textmode-hero-canvas {
@@ -126,7 +177,7 @@ body::-webkit-scrollbar-thumb:hover {
/* TextmodeHero mobile styles */
.textmode-hero-container {
- height: 300px;
+ min-height: 300px;
margin: 1rem 0;
}
}
@@ -171,14 +222,6 @@ body::-webkit-scrollbar-thumb:hover {
text-underline-offset: 2px;
}
-@media (max-width: 480px) {
-
- /* TextmodeHero mobile styles */
- .textmode-hero-container {
- height: 250px;
- }
-}
-
/* Textmode Sandbox Overlay Styling */
.textmode-sandbox-wrapper {
position: relative;
diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts
index 0b9acd65..5ba33b04 100644
--- a/.vitepress/theme/index.ts
+++ b/.vitepress/theme/index.ts
@@ -3,7 +3,7 @@ import DefaultTheme from 'vitepress/theme'
import type { EnhanceAppContext, Theme } from 'vitepress'
import { useData, useRoute } from 'vitepress'
import './custom.css'
-import { defineComponent, h } from 'vue'
+import { defineAsyncComponent, defineComponent, h } from 'vue'
import codeblocksFold from 'vitepress-plugin-codeblocks-fold'
import 'vitepress-plugin-codeblocks-fold/style/index.css'
import { useCodeSandbox } from 'vitepress-plugin-codesandbox'
@@ -53,7 +53,7 @@ const HeroLayout = defineComponent({
setup(_, { slots }) {
return () => h(DefaultTheme.Layout, null, {
...slots,
- 'home-hero-info-after': () => h(TextmodeHero),
+ 'home-hero-info': () => h(TextmodeHero),
'aside-bottom': () => h(AsideLinks),
'doc-after': () => h(DocFooter),
'layout-bottom': () => h('div', [
@@ -71,6 +71,7 @@ const baseTheme: Theme = {
const { app } = ctx
// Register components
+ app.component('Mermaid', defineAsyncComponent(() => import('vitepress-plugin-mermaid/Mermaid.vue')))
app.component('TextmodeLiveSandbox', TextmodeLiveSandbox)
app.component('TextmodeSandbox', TextmodeSandbox)
app.component('TextmodeApiSandbox', TextmodeApiSandbox)
diff --git a/.vitepress/theme/sketches/hero-wave/index.ts b/.vitepress/theme/sketches/hero-wave/index.ts
index 79c52d37..9693db4d 100644
--- a/.vitepress/theme/sketches/hero-wave/index.ts
+++ b/.vitepress/theme/sketches/hero-wave/index.ts
@@ -65,7 +65,8 @@ export function createHeroWaveSketch(
tm.draw(() => {
if (!isReady || isDestroyed) return
- if (!hasRenderedFrame) {
+ const firstFrame = !hasRenderedFrame
+ if (firstFrame) {
hasRenderedFrame = true
schedulePendingResize()
}
@@ -81,6 +82,7 @@ export function createHeroWaveSketch(
}
renderBrandingOverlay(tm, time, subheaderLines)
}
+ if (firstFrame) options.onReady?.()
})
const applyResize = (width: number, height: number) => {
diff --git a/.vitepress/theme/sketches/hero-wave/types.ts b/.vitepress/theme/sketches/hero-wave/types.ts
index 3546bc0e..947f48bb 100644
--- a/.vitepress/theme/sketches/hero-wave/types.ts
+++ b/.vitepress/theme/sketches/hero-wave/types.ts
@@ -9,4 +9,6 @@ export interface HeroWaveOptions {
* "hero" shows the logo + tagline overlay, "background" only renders the waves.
*/
mode?: 'hero' | 'background'
+ /** Called after the first complete frame, before playback may be paused. */
+ onReady?: () => void
}
diff --git a/index.md b/index.md
index 179323c9..9302bb4f 100644
--- a/index.md
+++ b/index.md
@@ -6,7 +6,8 @@ layout: home
footer: false
hero:
- text: textmode.js
+ name: textmode.js
+ tagline: A creative coding library for real-time ASCII art and textmode graphics on the web.
actions:
- theme: brand
text: Get started
@@ -43,11 +44,6 @@ features:
---
-
textmode.js - Real-time ASCII art for the web
-
- Lightweight creative coding library for real-time textmode graphics in the browser.
-
-
## What is `textmode.js`?
diff --git a/public/fonts/JetBrainsMono-OFL.txt b/public/fonts/JetBrainsMono-OFL.txt
new file mode 100644
index 00000000..675d198b
--- /dev/null
+++ b/public/fonts/JetBrainsMono-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/public/fonts/JetBrainsMono-cyrillic-ext.woff2 b/public/fonts/JetBrainsMono-cyrillic-ext.woff2
new file mode 100644
index 00000000..289de931
Binary files /dev/null and b/public/fonts/JetBrainsMono-cyrillic-ext.woff2 differ
diff --git a/public/fonts/JetBrainsMono-cyrillic.woff2 b/public/fonts/JetBrainsMono-cyrillic.woff2
new file mode 100644
index 00000000..de39acbc
Binary files /dev/null and b/public/fonts/JetBrainsMono-cyrillic.woff2 differ
diff --git a/public/fonts/JetBrainsMono-greek.woff2 b/public/fonts/JetBrainsMono-greek.woff2
new file mode 100644
index 00000000..a53d52d6
Binary files /dev/null and b/public/fonts/JetBrainsMono-greek.woff2 differ
diff --git a/public/fonts/JetBrainsMono-latin-ext.woff2 b/public/fonts/JetBrainsMono-latin-ext.woff2
new file mode 100644
index 00000000..82f96681
Binary files /dev/null and b/public/fonts/JetBrainsMono-latin-ext.woff2 differ
diff --git a/public/fonts/JetBrainsMono-latin.woff2 b/public/fonts/JetBrainsMono-latin.woff2
new file mode 100644
index 00000000..4d09cda4
Binary files /dev/null and b/public/fonts/JetBrainsMono-latin.woff2 differ
diff --git a/public/fonts/JetBrainsMono-vietnamese.woff2 b/public/fonts/JetBrainsMono-vietnamese.woff2
new file mode 100644
index 00000000..e38f5538
Binary files /dev/null and b/public/fonts/JetBrainsMono-vietnamese.woff2 differ