diff --git a/.github/skills/doc-tester/SKILL.md b/.github/skills/doc-tester/SKILL.md index aacfead2..bf783f6b 100644 --- a/.github/skills/doc-tester/SKILL.md +++ b/.github/skills/doc-tester/SKILL.md @@ -658,33 +658,37 @@ mcp_playwright_browser_type # Type text mcp_playwright_browser_screenshot # Capture screenshot ``` -### Hex1b Terminal MCP Tools +### Hex1b CLI Tools -The Hex1b MCP server provides tools for interacting with terminal sessions and capturing terminal output. This is particularly useful for: +The Hex1b CLI tool provides terminal automation for interacting with terminal sessions and capturing terminal output. This is particularly useful for: - **Capturing terminal screenshots** for documentation evidence -- **Recording asciinema sessions** (`.cast` files) for documentation +- **Recording terminal sessions** for documentation -#### Available Tools +#### Installation -First, activate the terminal tools you need: +Install Hex1b.Tool if not already available: -``` -activate_terminal_session_creation_tools # Start bash/powershell sessions -activate_terminal_interaction_tools # Screenshots, text capture, input +```bash +dotnet tool install -g Hex1b.Tool ``` -Then use the terminal tools: +Refer to the **hex1b skill** for the full command reference. -``` -mcp_hex1b_list_terminals # List active terminal sessions -mcp_hex1b_start_bash_terminal # Start a new bash session -mcp_hex1b_start_pwsh_terminal # Start a new PowerShell session -mcp_hex1b_send_terminal_input # Send commands to terminal -mcp_hex1b_wait_for_terminal_text # Wait for specific output -mcp_hex1b_capture_terminal_screenshot # Capture terminal as SVG -mcp_hex1b_capture_terminal_text # Capture terminal text content -mcp_hex1b_record_asciinema # Record terminal session as .cast file +#### Common Commands + +```bash +dotnet hex1b terminal list # List active terminal sessions +dotnet hex1b terminal start -- bash # Start a new bash session +dotnet hex1b terminal start -- pwsh # Start a new PowerShell session +dotnet hex1b keys --text "command" # Send text to terminal +dotnet hex1b keys --key Enter # Send a keypress +dotnet hex1b assert --text-present "..." # Wait for specific output +dotnet hex1b capture screenshot --format svg # Capture terminal as SVG +dotnet hex1b capture screenshot --format text # Capture terminal text content +dotnet hex1b capture recording start --output .cast # Start asciinema recording +dotnet hex1b capture recording stop # Stop asciinema recording +dotnet hex1b terminal stop # Stop a terminal session ``` #### Asciinema Recordings diff --git a/.github/skills/doc-writer/SKILL.md b/.github/skills/doc-writer/SKILL.md index e070f6bb..74323161 100644 --- a/.github/skills/doc-writer/SKILL.md +++ b/.github/skills/doc-writer/SKILL.md @@ -524,28 +524,45 @@ import AsciinemaPlayer from '@components/AsciinemaPlayer.astro'; - `speed`: Playback speed multiplier (default: 1.5) - `idleTimeLimit`: Max idle time between commands (default: 1.5s) -#### Creating New Recordings with Hex1b MCP +#### Creating New Recordings with Hex1b CLI -Use the Hex1b MCP server to create new terminal recordings: +Use the Hex1b CLI tool to create new terminal recordings and screenshots. Install it first if needed: -1. Activate the terminal tools: - ``` - activate_terminal_session_creation_tools - activate_terminal_interaction_tools +```bash +dotnet tool install -g Hex1b.Tool +``` + +Refer to the **hex1b skill** for the full command reference. Common workflow: + +1. Start a terminal session: + ```bash + dotnet hex1b terminal start -- bash + # Note the terminal ID from the output ``` -2. Start a terminal session and record: +2. Send commands and wait for output: + ```bash + dotnet hex1b keys --text "aspire run" + dotnet hex1b keys --key Enter + dotnet hex1b assert --text-present "Ready" --timeout 30 ``` - mcp_hex1b_start_bash_terminal # Start a new session - mcp_hex1b_send_terminal_input # Send commands - mcp_hex1b_wait_for_terminal_text # Wait for output - mcp_hex1b_record_asciinema # Save as .cast file + +3. Record a session: + ```bash + dotnet hex1b capture recording start --output session.cast + # ... interact with the terminal ... + dotnet hex1b capture recording stop ``` -3. For screenshots instead of recordings: +4. Capture output as SVG (for screenshots) or text: + ```bash + dotnet hex1b capture screenshot --format svg --output screenshot.svg + dotnet hex1b capture screenshot --format text --output output.txt ``` - mcp_hex1b_capture_terminal_screenshot # Capture as SVG - mcp_hex1b_capture_terminal_text # Capture as text + +5. Stop the terminal when done: + ```bash + dotnet hex1b terminal stop ``` #### When to Use Recordings vs Code Blocks diff --git a/.github/skills/hex1b/SKILL.md b/.github/skills/hex1b/SKILL.md new file mode 100644 index 00000000..fcdf3f9e --- /dev/null +++ b/.github/skills/hex1b/SKILL.md @@ -0,0 +1,343 @@ +--- +name: hex1b +description: CLI tool for automating any terminal application — TUI apps, shells, CLI tools, REPLs, and more. Use when you need to launch a process in a virtual terminal, capture its screen output, inject keystrokes or mouse input, take screenshots, record sessions, or assert on what's visible. +--- + +# Hex1b CLI Skill + +The `dotnet hex1b` CLI tool lets you automate **any terminal application** — TUI apps, +interactive CLIs, shells, REPLs, curses programs, or anything else that runs in a terminal. +It wraps arbitrary processes in a headless virtual terminal, giving you programmatic control +over screen capture, input injection, and content assertions. + +## Installation + +```bash +# Install as a global tool +dotnet tool install -g Hex1b.Tool + +# Or as a local tool (requires a tool manifest) +dotnet new tool-manifest # if no manifest exists yet +dotnet tool install --local Hex1b.Tool +dotnet tool restore # to restore from an existing manifest +``` + +## Concepts + +A **terminal** is a headless virtual terminal managed by Hex1b. Any process that runs in a +terminal emulator can be launched inside one. Terminals are identified by a short numeric ID +(the process ID). Use a prefix if unambiguous. + +All commands support `--json` for machine-readable output. + +--- + +## How to Launch a Process in a Virtual Terminal + +Start any command in a headless terminal. This is the entry point for all automation. + +```bash +# Start a shell +dotnet hex1b terminal start -- bash + +# Start a specific program with custom terminal size +dotnet hex1b terminal start --width 120 --height 40 -- htop + +# Start with a working directory +dotnet hex1b terminal start --cwd /path/to/project -- vim myfile.txt + +# Start a .NET project +dotnet hex1b terminal start -- dotnet run --project src/MyApp + +# Start and immediately attach (interactive mirror) +dotnet hex1b terminal start --attach -- bash +``` + +To get the terminal ID for subsequent commands: + +```bash +# List all running terminals +dotnet hex1b terminal list + +# Get the ID as JSON (useful for scripting) +ID=$(dotnet hex1b terminal start --json -- dotnet run --project src/MyApp | jq -r .id) +``` + +## How to See What's on Screen + +Capture the terminal's visible content at any point. + +```bash +# Plain text (default) — good for reading content and assertions +dotnet hex1b capture screenshot + +# ANSI — preserves colors and formatting +dotnet hex1b capture screenshot --format ansi + +# SVG — rendered terminal screenshot as vector image +dotnet hex1b capture screenshot --format svg --output screenshot.svg + +# PNG — rendered terminal screenshot as raster image (requires --output) +dotnet hex1b capture screenshot --format png --output screenshot.png + +# HTML — rendered terminal screenshot as HTML +dotnet hex1b capture screenshot --format html --output screenshot.html + +# Include scrollback history +dotnet hex1b capture screenshot --scrollback 100 + +# Wait for specific content to appear before capturing +dotnet hex1b capture screenshot --format png --output ready.png --wait "Ready" --timeout 30 +``` + +The `--wait` option polls until the specified text is visible, then captures. Useful when +the application takes time to render its initial state. + +## How to Wait for Something to Appear + +Use assertions to block until content is visible (or confirm it's absent). This is essential +for reliable automation — never assume timing. + +```bash +# Wait up to 30 seconds for text to appear +dotnet hex1b assert --text-present "Welcome" --timeout 30 + +# Confirm error text is NOT showing (waits up to 10s to be sure) +dotnet hex1b assert --text-absent "Error" --timeout 10 +``` + +Exit code 0 means the assertion passed; non-zero means it failed (timed out). + +## How to Send Keyboard Input + +Type text or send individual keys to the terminal. + +```bash +# Type text (each character sent as a keystroke) +dotnet hex1b keys --text "hello world" + +# Send a named key +dotnet hex1b keys --key Enter +dotnet hex1b keys --key Tab +dotnet hex1b keys --key Escape +dotnet hex1b keys --key UpArrow + +# Send key with modifiers +dotnet hex1b keys --key C --ctrl # Ctrl+C +dotnet hex1b keys --key S --ctrl # Ctrl+S +dotnet hex1b keys --key Tab --shift # Shift+Tab +dotnet hex1b keys --key F --ctrl --shift # Ctrl+Shift+F +``` + +Available key names (from the `Hex1bKey` enum, case-insensitive): + +- **Letters:** `A`–`Z` +- **Digits:** `D0`–`D9` +- **Function keys:** `F1`–`F12` +- **Navigation:** `UpArrow`, `DownArrow`, `LeftArrow`, `RightArrow`, `Home`, `End`, `PageUp`, `PageDown` +- **Editing:** `Backspace`, `Delete`, `Insert` +- **Whitespace:** `Tab`, `Enter`, `Spacebar` +- **Other:** `Escape` +- **Punctuation:** `OemComma`, `OemPeriod`, `OemMinus`, `OemPlus`, `OemQuestion`, `Oem1`, `Oem4`, `Oem5`, `Oem6`, `Oem7`, `OemTilde` +- **Numpad:** `NumPad0`–`NumPad9`, `Multiply`, `Add`, `Subtract`, `Decimal`, `Divide` + +## How to Send Mouse Input + +Click or drag at specific terminal coordinates (0-based column, row). + +```bash +# Left click at column 10, row 5 +dotnet hex1b mouse click 10 5 + +# Right click +dotnet hex1b mouse click 10 5 --button right + +# Drag from (5,3) to (20,3) +dotnet hex1b mouse drag 5 3 20 3 +``` + +## How to Start a Recording After a Terminal Has Launched + +If the terminal is already running, start recording its session to an asciinema `.cast` file. + +```bash +# Start the terminal first +dotnet hex1b terminal start -- dotnet run --project src/MyApp +# ... get the from terminal list ... + +# Begin recording to a file +dotnet hex1b capture recording start --output session.cast + +# Optionally set a title and idle time limit +dotnet hex1b capture recording start --output session.cast --title "Demo session" --idle-limit 2.0 + +# Do your interactions... +dotnet hex1b keys --text "hello" +dotnet hex1b keys --key Enter + +# Stop recording when done +dotnet hex1b capture recording stop +``` + +## How to Record a Session from the Moment It Starts + +Use `--record` on `terminal start` to begin recording immediately when the process launches. + +```bash +# Start terminal with recording enabled from the start +dotnet hex1b terminal start --record session.cast -- dotnet run --project src/MyApp + +# The recording is already in progress — interact normally +dotnet hex1b assert --text-present "Ready" --timeout 15 +dotnet hex1b keys --key Enter +dotnet hex1b capture screenshot --format text + +# Stop recording when done +dotnet hex1b capture recording stop +``` + +## How to Stop a Recording + +```bash +# Stop the active recording +dotnet hex1b capture recording stop + +# Check if a terminal is currently recording +dotnet hex1b capture recording status +``` + +The `.cast` file is written incrementally, so the file will contain all events up to the +point you stop. + +## How to Play Back a Recording + +```bash +# Simple playback in the terminal +dotnet hex1b capture recording playback --file session.cast + +# Play at 2x speed +dotnet hex1b capture recording playback --file session.cast --speed 2.0 + +# Interactive TUI player with pause/seek controls +dotnet hex1b capture recording playback --file session.cast --player +``` + +## How to Inspect a Hex1b TUI App's Widget Tree + +If the terminal is running a Hex1b application with `.WithDiagnostics()` enabled, you can +inspect its internal widget/node tree. + +```bash +# Show the full widget tree with geometry +dotnet hex1b app tree + +# Include focus state +dotnet hex1b app tree --focus + +# Include popup stack +dotnet hex1b app tree --popups + +# Limit tree depth +dotnet hex1b app tree --depth 3 + +# Get as JSON for programmatic inspection +dotnet hex1b app tree --json +``` + +## How to Stop and Clean Up Terminals + +```bash +# Stop a specific terminal +dotnet hex1b terminal stop + +# Get terminal details (PID, dimensions, uptime) +dotnet hex1b terminal info + +# Resize a running terminal +dotnet hex1b terminal resize --width 160 --height 50 + +# Clean up stale sockets from exited processes +dotnet hex1b terminal clean +``` + +## How to Attach Interactively + +Attach to a terminal for interactive use — you see what the process sees and can type directly. + +```bash +# Attach to a terminal (Ctrl+] to detach) +dotnet hex1b terminal attach +``` + +## How to Set Up the Agent Skill File + +Generate this skill file for a repository so AI agents know how to use the CLI. + +```bash +# Write skill file to .github/skills/hex1b/SKILL.md +dotnet hex1b agent init + +# Specify a different repo root +dotnet hex1b agent init --path /path/to/repo + +# Overwrite an existing skill file +dotnet hex1b agent init --force + +# Print to stdout instead of writing to disk +dotnet hex1b agent init --stdout +``` + +## Common Workflow: End-to-End Scripted Test + +```bash +# Launch the app +ID=$(dotnet hex1b terminal start --json -- dotnet run --project src/MyApp | jq -r .id) + +# Wait for it to be ready +dotnet hex1b assert $ID --text-present "Main Menu" --timeout 15 + +# Navigate and interact +dotnet hex1b keys $ID --key Enter +dotnet hex1b assert $ID --text-present "Settings" +dotnet hex1b keys $ID --text "new value" +dotnet hex1b keys $ID --key Enter + +# Capture final state +dotnet hex1b capture screenshot $ID --format png --output result.png +dotnet hex1b capture screenshot $ID --format text + +# Clean up +dotnet hex1b terminal stop $ID +``` + +## Common Workflow: Record a Demo + +```bash +# Start with recording +ID=$(dotnet hex1b terminal start --json --record demo.cast -- dotnet run --project samples/MyApp | jq -r .id) + +# Wait and interact +dotnet hex1b assert $ID --text-present "Ready" --timeout 15 +dotnet hex1b keys $ID --key Tab +dotnet hex1b keys $ID --key Enter + +# Take a screenshot at a key moment +dotnet hex1b capture screenshot $ID --format png --output highlight.png + +# Stop recording and terminal +dotnet hex1b capture recording stop $ID +dotnet hex1b terminal stop $ID + +# Play it back +dotnet hex1b capture recording playback --file demo.cast --player +``` + +## Tips + +- Use `--json` with `jq` for scriptable output: `dotnet hex1b terminal list --json | jq '.[] | .id'` +- Terminal IDs are PIDs — use a unique prefix instead of the full number +- `terminal list` automatically cleans up stale sockets from exited processes +- `capture screenshot --wait` is useful for waiting for async rendering before capturing +- Always use `assert` before interacting — never assume the app has rendered +- For PNG screenshots, `--output` is required since PNG is a binary format +- Recordings use the asciinema v2 `.cast` format and can be played with any compatible player \ No newline at end of file diff --git a/.mcp.json b/.mcp.json index cbdb2015..6983627d 100644 --- a/.mcp.json +++ b/.mcp.json @@ -13,13 +13,6 @@ "-y", "@playwright/mcp@latest" ] - }, - "hex1b": { - "command": "dnx", - "args": [ - "Hex1b.McpServer@0.66.0", - "--yes" - ] } } } \ No newline at end of file diff --git a/.vscode/mcp.json b/.vscode/mcp.json index 69730796..17d6ad58 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -15,14 +15,6 @@ "-y", "@playwright/mcp@latest" ] - }, - "hex1b": { - "type": "stdio", - "command": "dnx", - "args": [ - "Hex1b.McpServer@0.66.0", - "--yes" - ] } } } \ No newline at end of file diff --git a/opencode.jsonc b/opencode.jsonc index 646329f9..2f81d714 100644 --- a/opencode.jsonc +++ b/opencode.jsonc @@ -18,15 +18,6 @@ "@playwright/mcp@latest" ], "enabled": true - }, - "hex1b": { - "type": "local", - "command": [ - "dnx", - "Hex1b.McpServer@0.66.0", - "--yes" - ], - "enabled": true } } } \ No newline at end of file diff --git a/src/frontend/src/content/docs/app-host/configuration.mdx b/src/frontend/src/content/docs/app-host/configuration.mdx index 5de80d2d..c7e00f34 100644 --- a/src/frontend/src/content/docs/app-host/configuration.mdx +++ b/src/frontend/src/content/docs/app-host/configuration.mdx @@ -114,3 +114,45 @@ Internal settings are used by the AppHost and integrations. Internal settings ar | `AppHost:BrowserToken` | Automatically generated 128-bit entropy token. | The browser token used to authenticate browsing to the dashboard when it is launched by the AppHost. The browser token can be set by `ASPIRE_DASHBOARD_FRONTEND_BROWSERTOKEN`. The value is present if needed: the AppHost is in run mode, the dashboard isn't disabled, and the dashboard isn't configured to allow anonymous access with `ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS`. | | `AppHost:ResourceService:AuthMode` | `ApiKey`. If `ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS` is true then the value is `Unsecured`. | The authentication mode used to access the resource service. The value is present if needed: the AppHost is in run mode and the dashboard isn't disabled. | | `AppHost:ResourceService:ApiKey` | Automatically generated 128-bit entropy token. | The API key used to authenticate requests made to the AppHost's resource service. The API key can be set by `ASPIRE_DASHBOARD_RESOURCESERVICE_APIKEY`. The value is present if needed: the AppHost is in run mode, the dashboard isn't disabled, and the dashboard isn't configured to allow anonymous access with `ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS`. | + +## Advanced orchestration properties + + + +### DcpCliPath + +The `DcpCliPath` property specifies the path to the **Developer Control Plane (DCP)** executable. The DCP is the core orchestration engine that Aspire uses to run and manage distributed application resources locally during development. + +#### How it works + +When you use the [Aspire SDK](/get-started/aspire-sdk/), the build system automatically: + +1. Imports the platform-specific `Aspire.Hosting.Orchestration` NuGet package (for example, `Aspire.Hosting.Orchestration.win-x64`). +2. Sets `DcpCliPath` to point to the `dcp` executable within that package. +3. Embeds this path as assembly metadata in your compiled AppHost. + +At runtime, the AppHost reads this metadata to locate and start the DCP process, which then orchestrates your application's resources. + +#### Override options + +In rare cases, you may need to override the default DCP path: + +| Method | Example | +| ------ | ------- | +| MSBuild property | `C:\path\to\dcp.exe` | +| Command-line argument | `--dcp-cli-path /path/to/dcp` | +| Configuration | `DcpPublisher:CliPath` | + +#### When to use + +You might override `DcpCliPath` in these scenarios: + +- **Aspire contributors**: Testing with a custom or debug build of DCP when developing Aspire itself. +- **CI/CD pipelines**: Non-standard SDK layouts where automatic discovery doesn't work. +- **Troubleshooting**: Temporarily pointing to a specific DCP version to diagnose issues. + + diff --git a/src/frontend/src/content/docs/app-host/eventing.mdx b/src/frontend/src/content/docs/app-host/eventing.mdx index c57087a4..7d47931d 100644 --- a/src/frontend/src/content/docs/app-host/eventing.mdx +++ b/src/frontend/src/content/docs/app-host/eventing.mdx @@ -255,6 +255,7 @@ In some cases, such as extension libraries, you may need to access lifecycle eve ```csharp title="AppHost.cs" using Aspire.Hosting.Eventing; +using Aspire.Hosting.Lifecycle; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/frontend/src/content/docs/index.mdx b/src/frontend/src/content/docs/index.mdx index 2fe5db4e..c8085910 100644 --- a/src/frontend/src/content/docs/index.mdx +++ b/src/frontend/src/content/docs/index.mdx @@ -12,7 +12,7 @@ banner: content: | 🚀 Aspire 13.2 has shipped!See what's new in Aspire 13.2. hero: - tagline: Your stack, streamlined.

Orchestrate front ends, APIs, containers, and databases effortlessly—no rewrites, no limits. Extend Aspire to power any project.

+ tagline: Your stack, streamlined.

Orchestrate front ends, APIs, containers, and databases effortlessly—no rewrites, no limits. Extend Aspire to power any project. Free, open-source, and agent ready.

image: alt: Aspire logo file: ../../assets/aspire-hero.png @@ -39,8 +39,6 @@ import { LinkCard, } from '@astrojs/starlight/components'; - - ## Why developers choose Aspire @@ -76,7 +74,23 @@ import { Deploy anywhere—Kubernetes, cloud, or on-prem. Aspire adapts to your environment for consistent deployments without rewrites. - + + Aspire is the control plane for agentic dev. AI agents use your app model + to understand, build, and operate your entire stack. + + + Install the Aspire CLI, run `aspire init` in your repo, and fuel your agents with `aspire agent init`. + + + ## Define your stack in code @@ -86,6 +100,8 @@ import AppHostBuilder from '@components/AppHostBuilder.astro'; + + ## Polyglot, multilanguage support **Built for your stack**, Aspire orchestrates apps in C#, Java, Python, JavaScript, TypeScript, Go, and more—use the languages you prefer. [Explore how to model your apps](/get-started/resources/). diff --git a/src/frontend/src/content/docs/integrations/devtools/dev-tunnels.mdx b/src/frontend/src/content/docs/integrations/devtools/dev-tunnels.mdx index 0f6ca57a..b9defbe0 100644 --- a/src/frontend/src/content/docs/integrations/devtools/dev-tunnels.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/dev-tunnels.mdx @@ -38,7 +38,7 @@ Dev tunnels are useful for: ## Prerequisites -Before you create a dev tunnel, you first need to download and install the devtunnel CLI (Command Line Interface) tool that corresponds to your operating system. See the [devtunnel CLI installation documentation](https://learn.microsoft.com/developer/dev-tunnels/get-started#install) for more details. +Before you create a dev tunnel, you first need to download and install the devtunnel CLI (Command Line Interface) tool that corresponds to your operating system. See the [devtunnel CLI installation documentation](https://learn.microsoft.com/azure/developer/dev-tunnels/get-started#install) for more details. ## Hosting integration diff --git a/src/frontend/src/content/docs/testing/accessing-resources.mdx b/src/frontend/src/content/docs/testing/accessing-resources.mdx index 0c839114..e2034b23 100644 --- a/src/frontend/src/content/docs/testing/accessing-resources.mdx +++ b/src/frontend/src/content/docs/testing/accessing-resources.mdx @@ -44,3 +44,25 @@ await app.ResourceNotifications.WaitForResourceHealthyAsync( ``` This resource-notification pattern ensures that the resources are available before running the tests, avoiding potential issues with the tests failing due to the resources not being ready. + +## Start & Stop + +In some cases, you may want to manually start a resource when it is not initially started, for when the resource was created `WithExplicitStart` for example. + +```csharp +using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); +await app.ResourceCommands.ExecuteCommandAsync( + "webfrontend", + KnownResourceCommands.StartCommand, + cts.Token); +``` + +You could also want to stop a resource manually to see if your application is resilient when a particular resource shuts down. + +```csharp +using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); +await app.ResourceCommands.ExecuteCommandAsync( + "webfrontend", + KnownResourceCommands.StopCommand, + cts.Token); +``` diff --git a/src/frontend/src/styles/site.css b/src/frontend/src/styles/site.css index 7bec278c..2b7fe7f1 100644 --- a/src/frontend/src/styles/site.css +++ b/src/frontend/src/styles/site.css @@ -747,11 +747,19 @@ site-search svg { /* Hero page specific styling */ :root[data-has-hero] { + .hero { + padding-bottom: 1rem !important; + } + .sl-markdown-content>.sl-heading-wrapper { - margin-top: 5rem; + margin-top: 2rem; scroll-margin-top: 6rem; } + .sl-markdown-content>.sl-heading-wrapper:first-child { + margin-top: 0; + } + .sl-markdown-content>.languages-supported+p, .sl-markdown-content>.sl-heading-wrapper+p { max-width: 80%;