diff --git a/astro.config.ts b/astro.config.ts index bafbd5b77a2..1578962e7fb 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -14,6 +14,8 @@ import { ccipRedirects } from "./src/config/redirects/ccip" import trailingSlashMiddleware from "./src/integrations/trailing-slash-middleware" import redirectsJson from "./src/features/redirects/redirects.json" import { extractCanonicalUrlsWithLanguageVariants } from "./src/utils/sidebar" +import remarkCodeFenceFilename from "./src/lib/markdown/remarkCodeFenceFilename" +import rehypeCodeSampleFences from "./src/lib/markdown/rehypeCodeSampleFences" config() // Load .env file @@ -103,9 +105,13 @@ export default defineConfig({ return item }, }), - mdx(), + // Ensure our fence-meta parser runs for `.mdx` pages (in addition to `markdown.remarkPlugins`). + mdx({ + remarkPlugins: [remarkCodeFenceFilename], + }), ], markdown: { + remarkPlugins: [remarkCodeFenceFilename], rehypePlugins: [ rehypeSlug, // Required for autolink to work properly [ @@ -116,6 +122,7 @@ export default defineConfig({ ], // Wrap tables in div with overflow supported [rehypeWrapAll, { selector: "table", wrapper: "div.overflow-wrapper" }], + rehypeCodeSampleFences, ] as RehypePlugins, syntaxHighlight: "prism", smartypants: false, diff --git a/public/images/language-icons/go.svg b/public/images/language-icons/go.svg new file mode 100644 index 00000000000..9bb5f643118 --- /dev/null +++ b/public/images/language-icons/go.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/public/images/language-icons/json.svg b/public/images/language-icons/json.svg new file mode 100644 index 00000000000..b5c5ada53de --- /dev/null +++ b/public/images/language-icons/json.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/public/images/language-icons/python.svg b/public/images/language-icons/python.svg new file mode 100644 index 00000000000..5bd72165186 --- /dev/null +++ b/public/images/language-icons/python.svg @@ -0,0 +1,7 @@ + + + + + + python [#ffffff] Created with Sketch. + \ No newline at end of file diff --git a/public/images/language-icons/rust.svg b/public/images/language-icons/rust.svg new file mode 100644 index 00000000000..630595afb26 --- /dev/null +++ b/public/images/language-icons/rust.svg @@ -0,0 +1,7 @@ + + + + + + rust + \ No newline at end of file diff --git a/public/images/language-icons/solidity.svg b/public/images/language-icons/solidity.svg new file mode 100644 index 00000000000..86b9f4995b2 --- /dev/null +++ b/public/images/language-icons/solidity.svg @@ -0,0 +1,27 @@ + + + + +Vector 1 +Created with Sketch. + + + + + + + + + + + + + diff --git a/public/images/language-icons/terminal.svg b/public/images/language-icons/terminal.svg new file mode 100644 index 00000000000..4920153ef58 --- /dev/null +++ b/public/images/language-icons/terminal.svg @@ -0,0 +1,13 @@ + + + diff --git a/public/images/language-icons/toml.svg b/public/images/language-icons/toml.svg new file mode 100644 index 00000000000..de69f153f63 --- /dev/null +++ b/public/images/language-icons/toml.svg @@ -0,0 +1,12 @@ + + + + + + + file_type_toml + + + + + \ No newline at end of file diff --git a/public/images/language-icons/typescript.svg b/public/images/language-icons/typescript.svg new file mode 100644 index 00000000000..025b352d841 --- /dev/null +++ b/public/images/language-icons/typescript.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/CodeSample/CodeSample.astro b/src/components/CodeSample/CodeSample.astro index 73cef4f4cb2..6f4eb4d4153 100644 --- a/src/components/CodeSample/CodeSample.astro +++ b/src/components/CodeSample/CodeSample.astro @@ -1,20 +1,30 @@ --- -import { Prism } from "@astrojs/prism" +import { runHighlighterWithAstro } from "@astrojs/prism/dist/highlighter" import fs from "node:fs/promises" import path from "node:path" +import { getLanguageIconSrc, languageBadge } from "../../lib/codeSample/language.js" + export type Props = { src: string lang?: string + filename?: string showButtonOnly?: boolean optimize?: boolean runs?: number } -const { src, lang, showButtonOnly, optimize, runs } = Astro.props as Props +const { src, lang, filename, showButtonOnly, optimize, runs } = Astro.props as Props const data = (await fs.readFile(path.join(process.cwd(), "public", src), "utf-8")).toString() +const prismLang = lang ?? "solidity" +const headerFilename = filename?.trim() || undefined +const { classLanguage, html } = runHighlighterWithAstro(prismLang, data) +const languageKey = prismLang.toLowerCase() +const languageIconSrc = getLanguageIconSrc(languageKey) +const preInnerHtml = `${html}` + const isSolidityFile = src.match(/\.sol/) const isSample = isSolidityFile && (src.indexOf("samples/") === 0 || src.indexOf("/samples/") === 0) @@ -29,7 +39,38 @@ const remixUrl = `https://remix.ethereum.org/#url=https://docs.chain.link/${clea }` --- -{!showButtonOnly && } +{ + !showButtonOnly && + (headerFilename ? ( +
+
+
+ + + {headerFilename} + +
+ +
+
+      
+ ) : ( +
+    ))
+}
+
 {
   isSample && (
     
diff --git a/src/components/HeadCommon.astro b/src/components/HeadCommon.astro index c9434d0daa4..ecd9c543eee 100644 --- a/src/components/HeadCommon.astro +++ b/src/components/HeadCommon.astro @@ -1,6 +1,7 @@ --- import "../styles/theme.css" import "../styles/index.css" +import "../styles/code-blocks.css" import "../styles/migrated.css" import "../styles/prism-darcula.css" import "@chainlink/design-system/global-styles.css" diff --git a/src/content/ccip/getting-started/evm.mdx b/src/content/ccip/getting-started/evm.mdx index 96d0822ddee..4aef5a8681e 100644 --- a/src/content/ccip/getting-started/evm.mdx +++ b/src/content/ccip/getting-started/evm.mdx @@ -169,7 +169,7 @@ Congratulations! You just sent your first cross-chain data using CCIP. Next, exa The smart contract in this tutorial is designed to interact with CCIP to send data. The contract code includes comments to clarify the various functions, events, and underlying logic. However, this section explains the key elements. You can see the full contract code below. - + #### Initializing the contract @@ -202,7 +202,7 @@ The `sendMessage` function completes several operations: The smart contract in this tutorial is designed to interact with CCIP to receive data. The contract code includes comments to clarify the various functions, events, and underlying logic. However, this section explains the key elements. You can see the full contract code below. - + #### Initializing the contract diff --git a/src/content/ccip/tutorials/evm/manual-execution.mdx b/src/content/ccip/tutorials/evm/manual-execution.mdx index 8b7055a533b..517cd94526c 100644 --- a/src/content/ccip/tutorials/evm/manual-execution.mdx +++ b/src/content/ccip/tutorials/evm/manual-execution.mdx @@ -50,7 +50,10 @@ In this tutorial, you'll send a text _string_ and CCIP-BnM tokens between smart 1. Manually retry the execution. 1. Observe successful execution after the gas limit adjustment. - + ### Deploy your contracts diff --git a/src/content/ccip/tutorials/evm/programmable-token-transfers-defensive.mdx b/src/content/ccip/tutorials/evm/programmable-token-transfers-defensive.mdx index 0b5d6141d56..972dd9696b3 100644 --- a/src/content/ccip/tutorials/evm/programmable-token-transfers-defensive.mdx +++ b/src/content/ccip/tutorials/evm/programmable-token-transfers-defensive.mdx @@ -50,7 +50,10 @@ In this guide, you'll initiate a transaction from a smart contract on _Avalanche that for successful scenarios. - + ### Deploy your contracts diff --git a/src/lib/codeSample/language.ts b/src/lib/codeSample/language.ts new file mode 100644 index 00000000000..24e4c3b3313 --- /dev/null +++ b/src/lib/codeSample/language.ts @@ -0,0 +1,25 @@ +export function languageBadge(language: string): string { + const l = language.toLowerCase() + if (l === "solidity" || l === "sol") return "SOL" + if (["javascript", "js", "mjs", "cjs"].includes(l)) return "JS" + if (["typescript", "ts", "mts", "cts"].includes(l)) return "TS" + if (["bash", "sh", "shell"].includes(l)) return "SH" + if (l === "go" || l === "golang") return "GO" + if (l === "json" || l === "jsonc") return "JSON" + if (l === "yaml" || l === "yml") return "YAML" + return l.slice(0, 4).toUpperCase() +} + +export function getLanguageIconSrc(language: string): string | undefined { + const l = language.toLowerCase() + // Map language names/aliases to icons in `public/images/language-icons/`. + if (l === "solidity" || l === "sol") return "/images/language-icons/solidity.svg" + if (["typescript", "ts", "mts", "cts"].includes(l)) return "/images/language-icons/typescript.svg" + if (["go", "golang"].includes(l)) return "/images/language-icons/go.svg" + if (["json", "jsonc"].includes(l)) return "/images/language-icons/json.svg" + if (l === "toml") return "/images/language-icons/toml.svg" + if (["python", "py"].includes(l)) return "/images/language-icons/python.svg" + if (["rust", "rs"].includes(l)) return "/images/language-icons/rust.svg" + if (["bash", "sh", "shell", "zsh", "terminal"].includes(l)) return "/images/language-icons/terminal.svg" + return undefined +} diff --git a/src/lib/markdown/rehypeCodeSampleFences.ts b/src/lib/markdown/rehypeCodeSampleFences.ts new file mode 100644 index 00000000000..4d2cc70fa69 --- /dev/null +++ b/src/lib/markdown/rehypeCodeSampleFences.ts @@ -0,0 +1,141 @@ +import { visit } from "unist-util-visit" + +import { getLanguageIconSrc, languageBadge } from "../codeSample/language.js" + +function toCamelCaseDataAttr(attr: string): string { + // "data-filename" -> "dataFilename" + return attr.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()) +} + +function getProp(node: any, attr: string): unknown { + if (!node?.properties) return undefined + return node.properties[attr] ?? node.properties[toCamelCaseDataAttr(attr)] +} + +function deleteProp(node: any, attr: string): void { + if (!node?.properties) return + delete node.properties[attr] + delete node.properties[toCamelCaseDataAttr(attr)] +} + +function normalizeClassName(className: unknown): string[] { + if (!className) return [] + if (Array.isArray(className)) return className.map(String) + return String(className) + .split(/\s+/) + .map((c) => c.trim()) + .filter(Boolean) +} + +/** + * Convert `.code-sample[data-filename]` wrappers (created in remark) into full + * CodeSample-style blocks by inserting the header markup and adjusting the + * contained `
` for shared styling + copy-button behavior.
+ */
+export default function rehypeCodeSampleFences() {
+  return (tree: unknown) => {
+    visit(tree as any, "element", (node: any, index: number | undefined, parent: any) => {
+      if (!node || node.tagName !== "div") return
+
+      const classes = normalizeClassName(node.properties?.className)
+      if (!classes.includes("code-sample")) return
+
+      const filenameRaw = getProp(node, "data-filename")
+      const filename = typeof filenameRaw === "string" ? filenameRaw.trim() : ""
+      if (!filename) return
+
+      // Avoid double-inserting if this already has a header.
+      const hasHeader =
+        Array.isArray(node.children) &&
+        node.children.some(
+          (c: any) =>
+            c?.type === "element" &&
+            c?.tagName === "div" &&
+            normalizeClassName(c?.properties?.className).includes("code-sample__header")
+        )
+      if (hasHeader) return
+
+      const preEl =
+        Array.isArray(node.children) && node.children.find((c: any) => c?.type === "element" && c?.tagName === "pre")
+      if (!preEl) return
+
+      const languageRaw = getProp(node, "data-language") ?? getProp(preEl, "data-language")
+      const language = typeof languageRaw === "string" && languageRaw.trim() ? languageRaw.trim() : "text"
+      const languageKey = language.toLowerCase()
+      const iconSrc = getLanguageIconSrc(languageKey)
+
+      // Update the 
 node to match CodeSample behavior
+      const existing = normalizeClassName(preEl.properties?.className)
+      preEl.properties = preEl.properties || {}
+      preEl.properties.className = Array.from(
+        new Set([...existing, "code-sample__pre", "code-sample__pre--with-header"])
+      )
+      preEl.properties["data-no-copy-button"] = ""
+
+      const langSpan: any = {
+        type: "element",
+        tagName: "span",
+        properties: {
+          className: ["code-sample__lang", ...(iconSrc ? ["code-sample__lang--icon"] : [])],
+          "aria-hidden": "true",
+        },
+        children: iconSrc
+          ? [
+              {
+                type: "element",
+                tagName: "img",
+                properties: { className: ["code-sample__lang-icon"], src: iconSrc, alt: "" },
+                children: [],
+              },
+            ]
+          : [{ type: "text", value: languageBadge(languageKey) }],
+      }
+
+      const filenameSpan: any = {
+        type: "element",
+        tagName: "span",
+        properties: { className: ["code-sample__filename"], title: filename },
+        children: [{ type: "text", value: filename }],
+      }
+
+      const headerLeft: any = {
+        type: "element",
+        tagName: "div",
+        properties: { className: ["code-sample__header-left"] },
+        children: [langSpan, filenameSpan],
+      }
+
+      const copyButton: any = {
+        type: "element",
+        tagName: "button",
+        properties: { type: "button", className: ["code-sample__copy-button"], "aria-label": "Copy code" },
+        children: [
+          {
+            type: "element",
+            tagName: "img",
+            properties: { src: "/assets/icons/copyIcon.svg", alt: "Copy code", width: "16", height: "16" },
+            children: [],
+          },
+        ],
+      }
+
+      const header: any = {
+        type: "element",
+        tagName: "div",
+        properties: { className: ["code-sample__header"] },
+        children: [headerLeft, copyButton],
+      }
+
+      const wrapper: any = {
+        type: "element",
+        tagName: "div",
+        properties: { className: ["code-sample"], "data-language": languageKey },
+        children: [header, preEl],
+      }
+
+      // Replace this placeholder wrapper with the full wrapper (keeping only the pre).
+      // (We intentionally don't preserve `data-filename` in output markup.)
+      if (parent && typeof index === "number") parent.children[index] = wrapper
+    })
+  }
+}
diff --git a/src/lib/markdown/remarkCodeFenceFilename.ts b/src/lib/markdown/remarkCodeFenceFilename.ts
new file mode 100644
index 00000000000..e1a26b40e93
--- /dev/null
+++ b/src/lib/markdown/remarkCodeFenceFilename.ts
@@ -0,0 +1,117 @@
+import { visit } from "unist-util-visit"
+
+import { getLanguageIconSrc, languageBadge } from "../codeSample/language.js"
+
+const FILENAME_RE = /(?:^|\s)filename\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s]+))/i
+
+/**
+ * Wrap fenced code blocks that include `filename="..."` meta in a lightweight
+ * `.code-sample` container so we can add a CodeSample-like header in rehype
+ * (after syntax highlighting), while keeping the filename metadata intact.
+ */
+export default function remarkCodeFenceFilename() {
+  return (tree: unknown) => {
+    visit(tree as any, "code", (node: any, index: number | undefined, parent: any) => {
+      if (!parent || typeof index !== "number") return
+
+      const meta = typeof node?.meta === "string" ? node.meta : ""
+      if (!meta) return
+
+      const match = meta.match(FILENAME_RE)
+      if (!match) return
+
+      const filename = String(match[1] || match[2] || match[3] || "").trim()
+      if (!filename) return
+
+      const language = typeof node.lang === "string" && node.lang.trim() ? node.lang.trim().toLowerCase() : "text"
+
+      const iconSrc = getLanguageIconSrc(language)
+      const badge = languageBadge(language)
+
+      const langChildren = iconSrc
+        ? [
+            {
+              type: "mdxJsxFlowElement",
+              name: "img",
+              attributes: [
+                { type: "mdxJsxAttribute", name: "class", value: "code-sample__lang-icon" },
+                { type: "mdxJsxAttribute", name: "src", value: iconSrc },
+                { type: "mdxJsxAttribute", name: "alt", value: "" },
+              ],
+              children: [],
+            },
+          ]
+        : [{ type: "text", value: badge }]
+
+      const header = {
+        type: "mdxJsxFlowElement",
+        name: "div",
+        attributes: [{ type: "mdxJsxAttribute", name: "class", value: "code-sample__header" }],
+        children: [
+          {
+            type: "mdxJsxFlowElement",
+            name: "div",
+            attributes: [{ type: "mdxJsxAttribute", name: "class", value: "code-sample__header-left" }],
+            children: [
+              {
+                type: "mdxJsxFlowElement",
+                name: "span",
+                attributes: [
+                  {
+                    type: "mdxJsxAttribute",
+                    name: "class",
+                    value: `code-sample__lang${iconSrc ? " code-sample__lang--icon" : ""}`,
+                  },
+                  { type: "mdxJsxAttribute", name: "aria-hidden", value: "true" },
+                ],
+                children: langChildren,
+              },
+              {
+                type: "mdxJsxFlowElement",
+                name: "span",
+                attributes: [
+                  { type: "mdxJsxAttribute", name: "class", value: "code-sample__filename" },
+                  { type: "mdxJsxAttribute", name: "title", value: filename },
+                ],
+                children: [{ type: "text", value: filename }],
+              },
+            ],
+          },
+          {
+            type: "mdxJsxFlowElement",
+            name: "button",
+            attributes: [
+              { type: "mdxJsxAttribute", name: "type", value: "button" },
+              { type: "mdxJsxAttribute", name: "class", value: "code-sample__copy-button" },
+              { type: "mdxJsxAttribute", name: "aria-label", value: "Copy code" },
+            ],
+            children: [
+              {
+                type: "mdxJsxFlowElement",
+                name: "img",
+                attributes: [
+                  { type: "mdxJsxAttribute", name: "src", value: "/assets/icons/copyIcon.svg" },
+                  { type: "mdxJsxAttribute", name: "alt", value: "Copy code" },
+                  { type: "mdxJsxAttribute", name: "width", value: "16" },
+                  { type: "mdxJsxAttribute", name: "height", value: "16" },
+                ],
+                children: [],
+              },
+            ],
+          },
+        ],
+      }
+
+      parent.children[index] = {
+        type: "mdxJsxFlowElement",
+        name: "div",
+        attributes: [
+          { type: "mdxJsxAttribute", name: "class", value: "code-sample" },
+          { type: "mdxJsxAttribute", name: "data-language", value: language },
+          { type: "mdxJsxAttribute", name: "data-filename", value: filename },
+        ],
+        children: [header, node],
+      }
+    })
+  }
+}
diff --git a/src/scripts/codeSampleCopy.ts b/src/scripts/codeSampleCopy.ts
new file mode 100644
index 00000000000..0f82e113132
--- /dev/null
+++ b/src/scripts/codeSampleCopy.ts
@@ -0,0 +1,39 @@
+function copyText(text: string): Promise {
+  if (navigator.clipboard && window.isSecureContext) {
+    return navigator.clipboard.writeText(text)
+  }
+
+  // Fallback for non-secure contexts (e.g. http://localhost)
+  const textarea = document.createElement("textarea")
+  textarea.value = text
+  textarea.setAttribute("readonly", "")
+  textarea.style.position = "absolute"
+  textarea.style.left = "-9999px"
+  document.body.appendChild(textarea)
+  textarea.select()
+  const ok = document.execCommand("copy")
+  document.body.removeChild(textarea)
+  return ok ? Promise.resolve() : Promise.reject(new Error("Copy failed"))
+}
+
+document.addEventListener("click", async (e) => {
+  const target = e.target as HTMLElement | null
+  const button = (target?.closest?.(".code-sample__copy-button") as HTMLButtonElement | null) ?? null
+  if (!button) return
+
+  const root = button.closest(".code-sample")
+  const codeEl = (root?.querySelector("pre code") as HTMLElement | null) ?? null
+  const text = codeEl ? codeEl.innerText : ""
+  if (!text) return
+
+  const oldHtml = button.innerHTML
+  try {
+    await copyText(text)
+    button.innerHTML = 'Copied'
+    window.setTimeout(() => {
+      button.innerHTML = oldHtml
+    }, 1500)
+  } catch (_err) {
+    // no-op; keep original UI
+  }
+})
diff --git a/src/scripts/copyToClipboard/copyToClipboard.ts b/src/scripts/copyToClipboard/copyToClipboard.ts
index da7ffee25af..6a640eae21c 100644
--- a/src/scripts/copyToClipboard/copyToClipboard.ts
+++ b/src/scripts/copyToClipboard/copyToClipboard.ts
@@ -52,6 +52,11 @@ document.addEventListener("DOMContentLoaded", () => {
         return
       }
 
+      // Skip CodeSample-style blocks (they have their own header copy button)
+      if (codeBlock.closest(".code-sample")) {
+        return
+      }
+
       const copyButtonContainer = document.createElement("div")
       copyButtonContainer.className = styles.copyCodeButtonWrapper
 
diff --git a/src/scripts/index.ts b/src/scripts/index.ts
index 2b912568f04..7bb969536c7 100644
--- a/src/scripts/index.ts
+++ b/src/scripts/index.ts
@@ -1,4 +1,5 @@
 import "./fix-remix-urls"
 import "./fix-external-links"
+import "./codeSampleCopy"
 import "./copyToClipboard/copyToClipboard"
 import "./scroll-to-search"
diff --git a/src/styles/code-blocks.css b/src/styles/code-blocks.css
new file mode 100644
index 00000000000..652650faeee
--- /dev/null
+++ b/src/styles/code-blocks.css
@@ -0,0 +1,119 @@
+/* Shared styles for custom code blocks (CodeSample + future fenced blocks) */
+
+.code-sample {
+  margin: 1rem 0;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.code-sample__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 0.75rem;
+  padding: 10px 14px 11px 14px;
+  background: #3a3a3a;
+  color: #e0e0e0;
+  font-family: var(
+    --font-mono,
+    ui-monospace,
+    SFMono-Regular,
+    Menlo,
+    Monaco,
+    Consolas,
+    "Liberation Mono",
+    "Courier New",
+    monospace
+  );
+  font-size: 0.9rem;
+  line-height: 1;
+}
+
+.code-sample__header-left {
+  display: flex;
+  align-items: center;
+  gap: 0.75rem;
+  min-width: 0;
+}
+
+.code-sample__lang {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 30px;
+  height: 24px;
+  padding: 0 6px;
+  border-radius: 6px;
+  background: rgba(255, 255, 255, 0.1);
+  font-weight: 600;
+  letter-spacing: 0.02em;
+}
+
+.code-sample__lang-icon {
+  width: 24px;
+  height: 24px;
+  display: block;
+  object-fit: contain;
+}
+
+.code-sample__lang-icon,
+.code-sample__copy-button > img {
+  filter: brightness(0) invert(1);
+  opacity: 0.95;
+}
+
+/* When we render an icon, drop the "badge" background block. */
+.code-sample__lang--icon {
+  background: transparent;
+  padding: 0;
+  min-width: 24px;
+  border-radius: 0;
+}
+
+.code-sample__filename {
+  font-size: 0.95rem;
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.code-sample__copy-button {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 34px;
+  height: 34px;
+  padding: 0;
+  border-radius: 8px;
+  border: 1px solid rgba(255, 255, 255, 0.15);
+  background: rgba(255, 255, 255, 0.08);
+  cursor: pointer;
+  flex: 0 0 auto;
+}
+
+.code-sample__copy-button:hover {
+  background: rgba(255, 255, 255, 0.12);
+}
+
+.code-sample__copy-button:focus-visible {
+  outline: 2px solid rgba(255, 255, 255, 0.35);
+  outline-offset: 2px;
+}
+
+.code-sample__copy-button > img {
+  display: block;
+}
+
+.code-sample__pre--with-header {
+  margin: 0;
+  border-top-left-radius: 0 !important;
+  border-top-right-radius: 0 !important;
+}
+
+/* Fenced blocks wrapped via remark won't have CodeSample's pre classes. */
+.code-sample > pre {
+  margin: 0;
+  border-top-left-radius: 0 !important;
+  border-top-right-radius: 0 !important;
+}