|
10 | 10 | * A page opting in renders as a page of the Sim docs, not a third design |
11 | 11 | * system. |
12 | 12 | * |
13 | | - * Injected only into the preview document, never written into the stored |
14 | | - * file, so restyling every existing page is a change to this constant. The |
15 | | - * preview is also what the share page renders through, so shared links pick |
16 | | - * it up too; a raw download is the one path that sees unstyled markup. |
| 13 | + * Never written into the stored file: the preview injects it live and |
| 14 | + * page-document.ts bakes it into standalone/share/download documents, so |
| 15 | + * restyling every existing page is a change to this constant. |
17 | 16 | */ |
18 | 17 |
|
19 | 18 | /** Marker a page carries to request {@link SIM_ARTIFACT_STYLESHEET}. */ |
@@ -100,9 +99,11 @@ export function simTokenOverrides(): string { |
100 | 99 | * 2px color bar down the start edge) with the docs' shadow removal. |
101 | 100 | * - layout: global.css --spacing-fd-container 1400px, --fd-sidebar-width |
102 | 101 | * 300px, TOC column 268px, --content-gap 2.25rem. |
103 | | - * The docs' webfont (Inter under --font-geist-sans) cannot cross the sandbox |
104 | | - * (font CDNs and app-origin fetches are blocked), so the system stack stands |
105 | | - * in for it. |
| 102 | + * The docs' webfont (Inter under --font-geist-sans) leads --font-sans; each |
| 103 | + * surface injects the matching @font-face (see lib/workspace-files/page-font |
| 104 | + * — the app-served URL for standalone documents, a host-fetched data: URI |
| 105 | + * for the sandboxed preview), falling back to the system stack until it |
| 106 | + * loads. |
106 | 107 | */ |
107 | 108 | export const SIM_ARTIFACT_STYLESHEET = ` |
108 | 109 | :root { |
@@ -692,35 +693,39 @@ export const SIM_ARTIFACT_SHELL = `<script> |
692 | 693 | paintThemeButton() |
693 | 694 | bar.append(title, search, themeButton) |
694 | 695 |
|
695 | | - // Code blocks get the docs frame: a header with the language label and a |
696 | | - // copy button above each fenced block. |
| 696 | + // Code blocks get the docs frame: like the docs' prose fences, an |
| 697 | + // untitled block has no header row — just the floating copy control, |
| 698 | + // emcn's Duplicate icon flipping to a brand-accent Check while copied. |
| 699 | + const COPY_ICON = '<svg ' + ICON_ATTRS + '><path d="M14.25 0.75H2.75C1.64543 0.75 0.75 1.64543 0.75 2.75V14.25"/><rect x="5.25" y="5.25" width="14" height="14" rx="2"/></svg>' |
| 700 | + const COPIED_ICON = '<svg ' + ICON_ATTRS + '><path d="M18.25 2.75L7.25 15.75L1.75 10.25"/></svg>' |
697 | 701 | for (const pre of [...main.querySelectorAll('pre')]) { |
698 | 702 | if (pre.closest('.codeblock')) continue |
699 | 703 | const codeEl = pre.querySelector('code') |
700 | | - const langMatch = codeEl && codeEl.className.match(/language-([\\w-]+)/) |
701 | 704 | const frame = document.createElement('figure') |
702 | 705 | frame.className = 'codeblock' |
703 | | - const head = document.createElement('div') |
704 | | - head.className = 'codeblock-head' |
705 | | - const lang = document.createElement('span') |
706 | | - lang.textContent = langMatch ? langMatch[1] : 'code' |
707 | 706 | const copy = document.createElement('button') |
708 | 707 | copy.className = 'codeblock-copy' |
709 | 708 | copy.type = 'button' |
710 | | - copy.textContent = 'Copy' |
| 709 | + copy.setAttribute('aria-label', 'Copy Text') |
| 710 | + copy.innerHTML = COPY_ICON |
| 711 | + let copyResetTimer |
711 | 712 | copy.addEventListener('click', async () => { |
712 | 713 | const text = (codeEl || pre).textContent || '' |
713 | 714 | try { |
714 | 715 | await navigator.clipboard.writeText(text) |
715 | | - copy.textContent = 'Copied' |
716 | | - } catch { |
717 | | - copy.textContent = 'Select + copy' |
718 | | - } |
719 | | - setTimeout(() => { copy.textContent = 'Copy' }, 1500) |
| 716 | + copy.innerHTML = COPIED_ICON |
| 717 | + copy.classList.add('is-copied') |
| 718 | + copy.setAttribute('aria-label', 'Copied Text') |
| 719 | + clearTimeout(copyResetTimer) |
| 720 | + copyResetTimer = setTimeout(() => { |
| 721 | + copy.innerHTML = COPY_ICON |
| 722 | + copy.classList.remove('is-copied') |
| 723 | + copy.setAttribute('aria-label', 'Copy Text') |
| 724 | + }, 1500) |
| 725 | + } catch {} |
720 | 726 | }) |
721 | | - head.append(lang, copy) |
722 | 727 | pre.replaceWith(frame) |
723 | | - frame.append(head, pre) |
| 728 | + frame.append(copy, pre) |
724 | 729 | } |
725 | 730 |
|
726 | 731 | const cols = document.createElement('div') |
|
0 commit comments