diff --git a/src/components/islands/CopyPrompt.astro b/src/components/islands/CopyPrompt.astro new file mode 100644 index 0000000..5d49f74 --- /dev/null +++ b/src/components/islands/CopyPrompt.astro @@ -0,0 +1,205 @@ +--- +// CopyPrompt — copy-to-clipboard island for full-guide. One per button. +// +// Click reads `#${target}`'s textContent, copies it via +// `navigator.clipboard.writeText`, and falls back to a `document.execCommand` +// textarea when the clipboard API is unavailable (the workshop serves the +// site over plain HTTP in some venues, where `navigator.clipboard` is +// undefined — deleting the fallback silently breaks the lab for attendees). +// Both paths are kept on purpose; this is the contract the legacy app.js +// copyPrompt function uses for the three full-guide prompts. +// +// On success, writes a bilingual result string to the page-owned +// `#copy-status` (live region, `role="status"` `aria-live="polite"`) and +// swaps the button's to COPIED / COPIADO for 1800 ms before +// restoring it. +// +// Language comes from `document.documentElement.lang` — set today by +// app.js's `applyLanguage` and tomorrow by task 15c's language-toggle +// island. A `MutationObserver` on `` keeps the button label +// in step with whatever flips it; this island does not own the toggle +// and does not invent a second mechanism. Task 15c and 15d should align +// on this — see the task report. + +interface Props { + // The id (without `#`) of the
 element whose textContent
+  // is copied. Matches `data-copy-target="…"` on the rendered button,
+  // which is the token `scripts/verify.mjs` asserts in the legacy
+  // `full-guide/index.html`.
+  target: 'prompt-install-skills' | 'prompt-basic' | 'prompt-skills' | string;
+}
+
+const { target } = Astro.props;
+---
+
+
+
+
+
+
diff --git a/src/components/islands/ReadingProgress.astro b/src/components/islands/ReadingProgress.astro
new file mode 100644
index 0000000..ad6d2f1
--- /dev/null
+++ b/src/components/islands/ReadingProgress.astro
@@ -0,0 +1,57 @@
+---
+// ReadingProgress — scroll-position bar. One instance per page.
+//
+// Renders the same markup the legacy `full-guide/index.html` line 13
+// carried: ``.
+// The scroll listener is registered with `{ passive: true }`, matching
+// app.js line 406 and the rule in `.agents/rules/animation.md` that a
+// passive listener is required for any handler bound to a frequently
+// fired event like scroll. Animating `width` here is fine — the bar
+// is a 3px element at the very top of the viewport, so layout cost
+// is negligible compared to the alternative of subscribing to
+// IntersectionObserver and computing progress from a single sentinel
+// per section.
+//
+// Language: this island does not read language. It has no string
+// labels and the same progress semantics apply to EN and PT.
+
+interface Props {
+  // Optional override selector. Defaults to `.reading-progress span`
+  // to match the legacy app.js query, in case a page renders the bar
+  // with a different class name.
+  target?: string;
+}
+
+const { target = '.reading-progress span' } = Astro.props;
+---
+
+
+
+