From 1209d8e8718ddf337b8b5b8a703ecd565d1269d0 Mon Sep 17 00:00:00 2001 From: LennarX Date: Sat, 18 Jul 2026 03:32:34 +0200 Subject: [PATCH 1/2] feat: mark external links with an arrow and open them in a new tab All links leaving the site now share EXT_ATTRS (target="_blank" rel="external noopener"); site.css appends a small text-presentation arrow to every such link except icon-only ones like the topbar GitHub button. Covers the site chrome, landing, license, impressum and all external links in rendered docs. Co-Authored-By: Claude Fable 5 --- build.mjs | 4 ++-- src/impressum.mjs | 4 ++-- src/landing.mjs | 6 +++--- src/layout.mjs | 12 ++++++++---- src/license.mjs | 4 ++-- src/styles/site.css | 8 ++++++++ 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/build.mjs b/build.mjs index 5fed98a..ed897f0 100644 --- a/build.mjs +++ b/build.mjs @@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url'; import { Marked } from 'marked'; -import { docShell, esc, GITHUB_URL, highlightTokens, SITE_URL } from './src/layout.mjs'; +import { docShell, esc, EXT_ATTRS, GITHUB_URL, highlightTokens, SITE_URL } from './src/layout.mjs'; import { renderLanding } from './src/landing.mjs'; import { renderImpressum } from './src/impressum.mjs'; import { renderLicense } from './src/license.mjs'; @@ -128,7 +128,7 @@ const marked = new Marked({ const text = this.parser.parseInline(tokens); const t = title ? ` title="${esc(title)}"` : ''; const url = rewriteHref(href); - const ext = /^https?:/.test(url) ? ' rel="external"' : ''; + const ext = /^https?:/.test(url) ? EXT_ATTRS : ''; return `${text}`; }, code({ text, lang }) { diff --git a/src/impressum.mjs b/src/impressum.mjs index d40d5ee..e1f00e6 100644 --- a/src/impressum.mjs +++ b/src/impressum.mjs @@ -1,6 +1,6 @@ // Legal notice (Impressum) - a fully static, hand-written page. Required to be // reachable from every page under German law (§5 DDG); linked from the footer. -import { esc, pageShell } from './layout.mjs'; +import { esc, EXT_ATTRS, pageShell } from './layout.mjs'; export function renderImpressum({ version }) { const body = `
@@ -22,7 +22,7 @@ Email: mentorfilou@gmail.com

Editorial responsibility

-Lennard Solterbeck and the flashtrace team +Lennard Solterbeck and the flashtrace team
`; diff --git a/src/landing.mjs b/src/landing.mjs index 12ccb6f..9797a1b 100644 --- a/src/landing.mjs +++ b/src/landing.mjs @@ -1,7 +1,7 @@ // The landing page: hero with a CSS-built IDE mock, how-it-works, features, // curated interactive examples, install snippets. All static HTML. import { colorizeReport, examples, heroTerminal } from './examples.mjs'; -import { esc, GITHUB_URL, highlightTokens, pageShell } from './layout.mjs'; +import { esc, EXT_ATTRS, GITHUB_URL, highlightTokens, pageShell } from './layout.mjs'; // --- hero IDE mock ----------------------------------------------------------- @@ -215,7 +215,7 @@ npx flashtrace" aria-label="Copy install commands">Copy

Vendored, zero install

-

Download dist/flashtrace.mjs into your repository and run it directly.

+

Download dist/flashtrace.mjs into your repository and run it directly.

node flashtrace.mjs
@@ -233,7 +233,7 @@ export function renderLanding({ version, gitRef }) {

flashtrace verifies that every requirement in your Markdown specs is covered by the code and tests it demands - with zero runtime dependencies, anywhere Node.js runs.

${ideMock()} diff --git a/src/layout.mjs b/src/layout.mjs index b4937ce..9f90e83 100644 --- a/src/layout.mjs +++ b/src/layout.mjs @@ -4,6 +4,10 @@ export const SITE_URL = 'https://flashtrace.github.io'; export const GITHUB_URL = 'https://github.com/flashtrace/flashtrace'; export const DISCUSSIONS_URL = 'https://github.com/flashtrace/flashtrace/discussions'; +// Attributes for links leaving the site: open a new tab, and rel="external" +// lets site.css append the "↗" marker (icon-only links are excluded there). +export const EXT_ATTRS = ' target="_blank" rel="external noopener"'; + export function esc(s) { return String(s) .replaceAll('&', '&') @@ -49,13 +53,13 @@ function topBar({ version, active, withSidebar }) { flashtrace - ${esc(version)} + ${esc(version)}
- ${githubIcon} + ${githubIcon} @@ -73,8 +77,8 @@ function footer({ version }) {
diff --git a/src/license.mjs b/src/license.mjs index 6fd569f..ffc75df 100644 --- a/src/license.mjs +++ b/src/license.mjs @@ -3,7 +3,7 @@ // recognized as Apache 2.0, a GitHub-style summary panel is shown above it; // an unrecognized license falls back to the raw text alone rather than // risking a wrong summary. -import { esc, pageShell } from './layout.mjs'; +import { esc, EXT_ATTRS, pageShell } from './layout.mjs'; // Octicons (MIT): law, check, x, info. const lawIcon = ``; @@ -31,7 +31,7 @@ ${col('Permissions', 'lic-ok', checkIcon, ['Commercial use', 'Modification', 'Di ${col('Limitations', 'lic-no', xIcon, ['Trademark use', 'Liability', 'Warranty'])} ${col('Conditions', 'lic-info', infoIcon, ['License and copyright notice', 'State changes'])} -

This is not legal advice. Learn more about the Apache License 2.0.

+

This is not legal advice. Learn more about the Apache License 2.0.

`; } diff --git a/src/styles/site.css b/src/styles/site.css index c42c66c..e0f0483 100644 --- a/src/styles/site.css +++ b/src/styles/site.css @@ -118,6 +118,14 @@ a:hover { text-decoration: underline; } +/* External links carry rel="external" and open in a new tab; mark all but + icon-only ones with "↗" (\FE0E forces text over emoji presentation). */ +a[rel~='external']:not(.icon-btn)::after { + content: '\2197\FE0E'; + font-size: 0.75em; + margin-left: 0.15em; +} + img { max-width: 100%; } From 8644757379ff2f6dc9f835b0ad93a66be83aa725 Mon Sep 17 00:00:00 2001 From: LennarX Date: Sat, 18 Jul 2026 04:07:27 +0200 Subject: [PATCH 2/2] refactor: make the external-link arrow opt-in via ext-mark class target="_blank" stays on every external link, but the arrow marker now only appears where it earns its keep: the footer links, the impressum team link, the raw dist/flashtrace.mjs download and the license learn-more link. The version badge, the "View on GitHub" button and docs-rendered links stay unmarked. Co-Authored-By: Claude Fable 5 --- src/impressum.mjs | 2 +- src/landing.mjs | 2 +- src/layout.mjs | 8 ++++---- src/license.mjs | 2 +- src/styles/site.css | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/impressum.mjs b/src/impressum.mjs index e1f00e6..db812de 100644 --- a/src/impressum.mjs +++ b/src/impressum.mjs @@ -22,7 +22,7 @@ Email: mentorfilou@gmail.com

Editorial responsibility

-Lennard Solterbeck and the flashtrace team +Lennard Solterbeck and the flashtrace team
`; diff --git a/src/landing.mjs b/src/landing.mjs index 9797a1b..358dc4d 100644 --- a/src/landing.mjs +++ b/src/landing.mjs @@ -215,7 +215,7 @@ npx flashtrace" aria-label="Copy install commands">Copy

Vendored, zero install

-

Download dist/flashtrace.mjs into your repository and run it directly.

+

Download dist/flashtrace.mjs into your repository and run it directly.

node flashtrace.mjs
diff --git a/src/layout.mjs b/src/layout.mjs index 9f90e83..72a790f 100644 --- a/src/layout.mjs +++ b/src/layout.mjs @@ -4,8 +4,8 @@ export const SITE_URL = 'https://flashtrace.github.io'; export const GITHUB_URL = 'https://github.com/flashtrace/flashtrace'; export const DISCUSSIONS_URL = 'https://github.com/flashtrace/flashtrace/discussions'; -// Attributes for links leaving the site: open a new tab, and rel="external" -// lets site.css append the "↗" marker (icon-only links are excluded there). +// Attributes for links leaving the site: open a new tab. Links that should +// also show the "↗" marker add class="ext-mark" (styled in site.css). export const EXT_ATTRS = ' target="_blank" rel="external noopener"'; export function esc(s) { @@ -77,8 +77,8 @@ function footer({ version }) { diff --git a/src/license.mjs b/src/license.mjs index ffc75df..aa92455 100644 --- a/src/license.mjs +++ b/src/license.mjs @@ -31,7 +31,7 @@ ${col('Permissions', 'lic-ok', checkIcon, ['Commercial use', 'Modification', 'Di ${col('Limitations', 'lic-no', xIcon, ['Trademark use', 'Liability', 'Warranty'])} ${col('Conditions', 'lic-info', infoIcon, ['License and copyright notice', 'State changes'])} -

This is not legal advice. Learn more about the Apache License 2.0.

+

This is not legal advice. Learn more about the Apache License 2.0.

`; } diff --git a/src/styles/site.css b/src/styles/site.css index e0f0483..ddadce0 100644 --- a/src/styles/site.css +++ b/src/styles/site.css @@ -118,9 +118,9 @@ a:hover { text-decoration: underline; } -/* External links carry rel="external" and open in a new tab; mark all but - icon-only ones with "↗" (\FE0E forces text over emoji presentation). */ -a[rel~='external']:not(.icon-btn)::after { +/* Opt-in "↗" marker for external links; the arrow-worthy ones add this class + next to EXT_ATTRS (\FE0E forces text over emoji presentation). */ +.ext-mark::after { content: '\2197\FE0E'; font-size: 0.75em; margin-left: 0.15em;