Created
August 21, 2026 02:41
-
-
Save brianmed/c87bd617c9d2bf9ad97d042c6064dd32 to your computer and use it in GitHub Desktop.
Paste5 is an HTML 5 C# Minimal API file app that copy and pastes between the browser and Wayland
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S dotnet -- | |
| // 1) Install dotnet sdk, e.g., sudo apt install dotnet-sdk-10.0 | |
| // 2) Copy gist to paste5.cs | |
| // 3) chmod a+x paste5.cs | |
| // 4) ./paste5.cs --urls='https://*:8443' | |
| #:sdk Microsoft.NET.Sdk.Web | |
| using System.Diagnostics; | |
| using System.Text.Json.Serialization; | |
| using Microsoft.AspNetCore.Mvc; | |
| WebApplicationBuilder builder = WebApplication.CreateBuilder(args); | |
| builder | |
| .Services | |
| .Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options => | |
| { | |
| options.SerializerOptions.PropertyNameCaseInsensitive = true; | |
| options.SerializerOptions.PropertyNamingPolicy = null; | |
| options.SerializerOptions.TypeInfoResolver = AppJsonSerializerContext.Default; | |
| }); | |
| WebApplication app = builder.Build(); | |
| ClipboardEndpoints.Map(app); | |
| StaticEndpoints.Map(app); | |
| app.Run(); | |
| public class ScratchPad | |
| { | |
| public string Text { get; set; } | |
| public string Message { get; set; } | |
| } | |
| [JsonSerializable(typeof(ScratchPad))] | |
| public partial class AppJsonSerializerContext : JsonSerializerContext | |
| { | |
| } | |
| public static class ClipboardEndpoints | |
| { | |
| public static void Map(WebApplication app) | |
| { | |
| app.MapPost("/clipboard/wayland/copy", async (ScratchPad scratchPad) => | |
| { | |
| ProcessStartInfo psi = new ProcessStartInfo(); | |
| psi.FileName = "wl-copy"; | |
| psi.UseShellExecute = false; | |
| psi.RedirectStandardInput = true; | |
| psi.RedirectStandardOutput = true; | |
| psi.CreateNoWindow = true; | |
| using Process p = Process.Start(psi); | |
| p.StandardInput.Write(scratchPad.Text); | |
| p.StandardInput.Close(); | |
| p.WaitForExit(); | |
| return Results.Ok("wl-copy was successful"); | |
| }); | |
| app.MapGet("/clipboard/wayland/paste", async () => | |
| { | |
| ProcessStartInfo psi = new ProcessStartInfo(); | |
| psi.FileName = "wl-paste"; | |
| psi.UseShellExecute = false; | |
| psi.RedirectStandardInput = true; | |
| psi.RedirectStandardOutput = true; | |
| psi.CreateNoWindow = true; | |
| using Process p = Process.Start(psi); | |
| string text = p.StandardOutput.ReadToEnd(); | |
| p.StandardInput.Close(); | |
| p.WaitForExit(); | |
| return Results.Ok(new ScratchPad() { Text = text, Message = "wl-paste was successful" }); | |
| }); | |
| } | |
| } | |
| public static class StaticEndpoints | |
| { | |
| public static void Map(WebApplication app) | |
| { | |
| app.MapGet("/", async () => | |
| { | |
| string htmlContent = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <link rel="stylesheet" href="/static/simple.css"> | |
| <title>Paste5</title> | |
| </head> | |
| <body> | |
| <div> | |
| <h1>Paste5</h1> | |
| </div> | |
| <div> | |
| <form> | |
| <p><strong>HTML5 Paste Tool</strong></p> | |
| <p> | |
| <label>Scratch Pad</label> | |
| <input type="text" id="scratch-pad" name="scratch_pad"> | |
| </p> | |
| <button id="from-browser">From Browser</button> | |
| <button id="to-browser">To Browser</button> | |
| <button id="from-wayland">From Wayland</button> | |
| <button id="to-wayland">To Wayland</button> | |
| </form> | |
| <div> | |
| <p id="response-text"></p> | |
| </div> | |
| </div> | |
| <script> | |
| document.querySelector('#from-browser').addEventListener('click', async (event) => | |
| { | |
| event.preventDefault(); | |
| const text = await navigator.clipboard.readText(); | |
| document.querySelector('#scratch-pad').value = text; | |
| document.querySelector('#response-text').innerHTML = "From Browser was successful"; | |
| }); | |
| document.querySelector('#to-browser').addEventListener('click', async (event) => | |
| { | |
| event.preventDefault(); | |
| const scratchPad = document.querySelector('#scratch-pad').value; | |
| const text = await navigator.clipboard.writeText(scratchPad); | |
| document.querySelector('#response-text').innerHTML = "To Browser was successful"; | |
| }); | |
| document.querySelector('#from-wayland').addEventListener('click', async (event) => | |
| { | |
| event.preventDefault(); | |
| const scratchPad = document.querySelector('#scratch-pad').value; | |
| const response = await fetch("/clipboard/wayland/paste"); | |
| const json = await response.json(); | |
| document.querySelector('#scratch-pad').value = json.Text; | |
| document.querySelector('#response-text').innerHTML = json.Message; | |
| }); | |
| document.querySelector('#to-wayland').addEventListener('click', async (event) => | |
| { | |
| event.preventDefault(); | |
| const scratchPad = document.querySelector('#scratch-pad').value; | |
| const response = await fetch("/clipboard/wayland/copy", | |
| { | |
| method: 'POST', | |
| headers: | |
| { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ Text: scratchPad }) | |
| }); | |
| document.querySelector('#response-text').innerHTML = (await response.text()).replace("\"", ""); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """; | |
| return Results.Content(htmlContent, contentType: "text/html"); | |
| }); | |
| app.MapGet("/static/simple.css", async () => | |
| { | |
| string cssContent = """ | |
| /* Global variables. */ | |
| :root { | |
| /* Set sans-serif & mono fonts */ | |
| --sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir, | |
| "Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica, | |
| "Helvetica Neue", sans-serif; | |
| --serif-font: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; | |
| --mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace; | |
| --standard-border-radius: 5px; | |
| --border-width: 1px; | |
| /* Default (light) theme */ | |
| --bg: #fff; | |
| --accent-bg: #f5f7ff; | |
| --text: #212121; | |
| --text-light: #585858; | |
| --border: #898EA4; | |
| --accent: #0d47a1; | |
| --accent-hover: #1266e2; | |
| --accent-text: var(--bg); | |
| --code: #d81b60; | |
| --preformatted: #444; | |
| --marked: #ffdd33; | |
| --disabled: #efefef; | |
| } | |
| /* Dark theme */ | |
| @media (prefers-color-scheme: dark) { | |
| :root { | |
| color-scheme: dark; | |
| --bg: #212121; | |
| --accent-bg: #2b2b2b; | |
| --text: #dcdcdc; | |
| --text-light: #ababab; | |
| --accent: #ffb300; | |
| --accent-hover: #ffe099; | |
| --accent-text: var(--bg); | |
| --code: #f06292; | |
| --preformatted: #ccc; | |
| --disabled: #111; | |
| } | |
| /* Add a bit of transparency so light media isn't so glaring in dark mode */ | |
| img, | |
| video { | |
| opacity: 0.8; | |
| } | |
| } | |
| /* Reset box-sizing */ | |
| *, *::before, *::after { | |
| box-sizing: border-box; | |
| } | |
| /* Reset default appearance */ | |
| textarea, | |
| select, | |
| input, | |
| progress { | |
| appearance: none; | |
| -webkit-appearance: none; | |
| -moz-appearance: none; | |
| } | |
| html { | |
| /* Set the font globally */ | |
| font-family: var(--sans-font); | |
| scroll-behavior: smooth; | |
| } | |
| /* Make the body a nice central block */ | |
| body { | |
| color: var(--text); | |
| background-color: var(--bg); | |
| font-size: 1.15rem; | |
| line-height: 1.5; | |
| /* display: grid; */ | |
| /* grid-template-columns: 1fr min(45rem, 90%) 1fr; */ | |
| margin: 16px; | |
| } | |
| body > * { | |
| grid-column: 2; | |
| } | |
| /* Make the header bg full width, but the content inline with body */ | |
| body > header { | |
| background-color: var(--accent-bg); | |
| border-bottom: var(--border-width) solid var(--border); | |
| text-align: center; | |
| padding: 0 0.5rem 2rem 0.5rem; | |
| grid-column: 1 / -1; | |
| } | |
| body > header > *:only-child { | |
| margin-block-start: 2rem; | |
| } | |
| body > header h1 { | |
| max-width: 1200px; | |
| margin: 1rem auto; | |
| } | |
| body > header p { | |
| max-width: 40rem; | |
| margin: 1rem auto; | |
| } | |
| /* Add a little padding to ensure spacing is correct between content and header nav */ | |
| main { | |
| padding-top: 1.5rem; | |
| } | |
| body > footer { | |
| margin-top: 4rem; | |
| padding: 2rem 1rem 1.5rem 1rem; | |
| color: var(--text-light); | |
| font-size: 0.9rem; | |
| text-align: center; | |
| border-top: var(--border-width) solid var(--border); | |
| } | |
| /* Format headers */ | |
| h1 { | |
| font-size: 3rem; | |
| } | |
| h2 { | |
| font-size: 2.6rem; | |
| margin-top: 3rem; | |
| } | |
| h3 { | |
| font-size: 2rem; | |
| margin-top: 3rem; | |
| } | |
| h4 { | |
| font-size: 1.44rem; | |
| } | |
| h5 { | |
| font-size: 1.15rem; | |
| } | |
| h6 { | |
| font-size: 0.96rem; | |
| } | |
| p { | |
| margin: 1.5rem 0; | |
| } | |
| /* Prevent long strings from overflowing container */ | |
| p, h1, h2, h3, h4, h5, h6 { | |
| overflow-wrap: break-word; | |
| } | |
| /* Fix line height when title wraps */ | |
| h1, | |
| h2, | |
| h3 { | |
| line-height: 1.1; | |
| } | |
| /* Reduce header size on mobile */ | |
| @media only screen and (max-width: 720px) { | |
| h1 { | |
| font-size: 2.5rem; | |
| } | |
| h2 { | |
| font-size: 2.1rem; | |
| } | |
| h3 { | |
| font-size: 1.75rem; | |
| } | |
| h4 { | |
| font-size: 1.25rem; | |
| } | |
| } | |
| /* Format links & buttons */ | |
| a, | |
| a:visited { | |
| color: var(--accent); | |
| } | |
| a:hover { | |
| text-decoration: none; | |
| } | |
| button, | |
| .button, | |
| a.button, /* extra specificity to override a */ | |
| input[type="submit"], | |
| input[type="reset"], | |
| input[type="button"] { | |
| border: var(--border-width) solid var(--accent); | |
| background-color: var(--accent); | |
| color: var(--accent-text); | |
| padding: 0.5em; | |
| text-decoration: none; | |
| line-height: normal; | |
| } | |
| .button[aria-disabled="true"], | |
| input:disabled, | |
| textarea:disabled, | |
| select:disabled, | |
| button[disabled] { | |
| cursor: not-allowed; | |
| background-color: var(--disabled); | |
| border-color: var(--disabled); | |
| color: var(--text-light); | |
| } | |
| input[type="range"] { | |
| padding: 0; | |
| } | |
| /* Set the cursor to '?' on an abbreviation and style the abbreviation to show that there is more information underneath */ | |
| abbr[title] { | |
| cursor: help; | |
| text-decoration-line: underline; | |
| text-decoration-style: dotted; | |
| } | |
| button:enabled:hover, | |
| .button:not([aria-disabled="true"]):hover, | |
| input[type="submit"]:enabled:hover, | |
| input[type="reset"]:enabled:hover, | |
| input[type="button"]:enabled:hover { | |
| background-color: var(--accent-hover); | |
| border-color: var(--accent-hover); | |
| cursor: pointer; | |
| } | |
| .button:focus-visible, | |
| button:focus-visible:where(:enabled), | |
| input:enabled:focus-visible:where( | |
| [type="submit"], | |
| [type="reset"], | |
| [type="button"] | |
| ) { | |
| outline: 2px solid var(--accent); | |
| outline-offset: 1px; | |
| } | |
| /* Format navigation */ | |
| header nav { | |
| font-size: 1rem; | |
| line-height: 2; | |
| padding: 1rem 0 0 0; | |
| } | |
| /* Use flexbox to allow items to wrap, as needed */ | |
| header nav ul, | |
| header nav ol { | |
| align-content: space-around; | |
| align-items: center; | |
| display: flex; | |
| flex-direction: row; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| list-style-type: none; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| /* List items are inline elements, make them behave more like blocks */ | |
| header nav ul li, | |
| header nav ol li { | |
| display: inline-block; | |
| } | |
| header nav a, | |
| header nav a:visited { | |
| margin: 0 0.5rem 1rem 0.5rem; | |
| border: var(--border-width) solid var(--border); | |
| border-radius: var(--standard-border-radius); | |
| color: var(--text); | |
| display: inline-block; | |
| padding: 0.1rem 1rem; | |
| text-decoration: none; | |
| } | |
| header nav a:hover, | |
| header nav a.current, | |
| header nav a[aria-current="page"], | |
| header nav a[aria-current="true"] { | |
| background: var(--bg); | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| cursor: pointer; | |
| } | |
| /* Reduce nav side on mobile */ | |
| @media only screen and (max-width: 720px) { | |
| header nav a { | |
| border: none; | |
| padding: 0; | |
| text-decoration: underline; | |
| line-height: 1; | |
| } | |
| header nav a.current { | |
| background: none; | |
| } | |
| } | |
| /* Consolidate box styling */ | |
| aside, details, pre, progress { | |
| background-color: var(--accent-bg); | |
| border: var(--border-width) solid var(--border); | |
| border-radius: var(--standard-border-radius); | |
| margin-bottom: 1rem; | |
| } | |
| aside { | |
| font-size: 1rem; | |
| width: 30%; | |
| padding: 0 15px; | |
| margin-inline-start: 15px; | |
| float: right; | |
| } | |
| *[dir="rtl"] aside { | |
| float: left; | |
| } | |
| /* Make aside full-width on mobile */ | |
| @media only screen and (max-width: 720px) { | |
| aside { | |
| width: 100%; | |
| float: none; | |
| margin-inline-start: 0; | |
| } | |
| } | |
| article, fieldset, dialog { | |
| border: var(--border-width) solid var(--border); | |
| padding: 1rem; | |
| border-radius: var(--standard-border-radius); | |
| margin-bottom: 1rem; | |
| } | |
| article h2:first-child, | |
| section h2:first-child, | |
| article h3:first-child, | |
| section h3:first-child { | |
| margin-top: 1rem; | |
| } | |
| section { | |
| border-top: var(--border-width) solid var(--border); | |
| border-bottom: var(--border-width) solid var(--border); | |
| padding: 2rem 1rem; | |
| margin: 3rem 0; | |
| } | |
| /* Don't double separators when chaining sections */ | |
| section + section, | |
| section:first-child { | |
| border-top: 0; | |
| padding-top: 0; | |
| } | |
| section + section { | |
| margin-top: 0; | |
| } | |
| section:last-child { | |
| border-bottom: 0; | |
| padding-bottom: 0; | |
| } | |
| details { | |
| padding: 0.7rem 1rem; | |
| } | |
| summary { | |
| cursor: pointer; | |
| font-weight: bold; | |
| padding: 0.7rem 1rem; | |
| margin: -0.7rem -1rem; | |
| word-break: break-word; | |
| } | |
| details[open] > summary + * { | |
| margin-top: 0; | |
| } | |
| details[open] > summary { | |
| margin-bottom: 0.5rem; | |
| } | |
| details[open] > :last-child { | |
| margin-bottom: 0; | |
| } | |
| details h2:first-of-type { | |
| margin-top: 0; | |
| } | |
| /* Format tables */ | |
| table { | |
| border-collapse: collapse; | |
| margin: 1.5rem 0; | |
| } | |
| figure > table { | |
| width: max-content; | |
| margin: 0; | |
| } | |
| td, | |
| th { | |
| border: var(--border-width) solid var(--border); | |
| text-align: start; | |
| padding: 0.5rem; | |
| } | |
| th { | |
| background-color: var(--accent-bg); | |
| font-weight: bold; | |
| } | |
| tr:nth-child(even) { | |
| /* Set every other cell slightly darker. Improves readability. */ | |
| background-color: var(--accent-bg); | |
| } | |
| table caption { | |
| font-weight: bold; | |
| margin-bottom: 0.5rem; | |
| } | |
| /* Format forms */ | |
| textarea, | |
| select, | |
| input, | |
| button, | |
| .button { | |
| font-size: inherit; | |
| font-family: inherit; | |
| padding: 0.5em; | |
| margin-bottom: 0.5rem; | |
| border-radius: var(--standard-border-radius); | |
| box-shadow: none; | |
| max-width: 100%; | |
| display: inline-block; | |
| } | |
| textarea, | |
| select, | |
| input { | |
| color: var(--text); | |
| background-color: var(--bg); | |
| border: var(--border-width) solid var(--border); | |
| } | |
| label { | |
| display: block; | |
| } | |
| textarea:not([cols]) { | |
| width: 100%; | |
| } | |
| /* Add arrow to drop-down */ | |
| select:not([multiple]) { | |
| background-image: linear-gradient(45deg, transparent 49%, var(--text) 51%), | |
| linear-gradient(135deg, var(--text) 51%, transparent 49%); | |
| background-position: calc(100% - 15px), calc(100% - 10px); | |
| background-size: 5px 5px, 5px 5px; | |
| background-repeat: no-repeat; | |
| padding-inline-end: 25px; | |
| } | |
| *[dir="rtl"] select:not([multiple]) { | |
| background-position: 10px, 15px; | |
| } | |
| /* checkbox and radio button style */ | |
| input[type="checkbox"], | |
| input[type="radio"] { | |
| vertical-align: middle; | |
| position: relative; | |
| width: min-content; | |
| } | |
| input[type="checkbox"] + label, | |
| input[type="radio"] + label { | |
| display: inline-block; | |
| } | |
| input[type="radio"] { | |
| border-radius: 100%; | |
| } | |
| input[type="checkbox"]:checked, | |
| input[type="radio"]:checked { | |
| background-color: var(--accent); | |
| } | |
| input[type="checkbox"]:checked::after { | |
| /* Creates a rectangle with colored right and bottom borders which is rotated to look like a check mark */ | |
| content: " "; | |
| width: 0.2em; | |
| height: 0.4em; | |
| border-radius: 0; | |
| position: absolute; | |
| top: 0.04em; | |
| left: 0.18em; | |
| background-color: transparent; | |
| border-right: solid var(--bg) 0.08em; | |
| border-bottom: solid var(--bg) 0.08em; | |
| font-size: 1.8em; | |
| transform: rotate(45deg); | |
| } | |
| input[type="radio"]:checked::after { | |
| /* creates a colored circle for the checked radio button */ | |
| content: " "; | |
| width: 0.3em; | |
| height: 0.3em; | |
| border-radius: 100%; | |
| position: absolute; | |
| top: 0.125em; | |
| background-color: var(--bg); | |
| left: 0.125em; | |
| font-size: 1.8em; | |
| } | |
| /* Makes input fields wider on smaller screens */ | |
| @media only screen and (max-width: 720px) { | |
| textarea, | |
| select, | |
| input { | |
| width: 100%; | |
| } | |
| } | |
| /* Set a height for color input */ | |
| input[type="color"] { | |
| height: 2.5rem; | |
| padding: 0.2rem; | |
| } | |
| /* do not show border around file selector button */ | |
| input[type="file"] { | |
| border: 0; | |
| } | |
| /* Misc body elements */ | |
| hr { | |
| border: none; | |
| height: var(--border-width); | |
| background: var(--border); | |
| margin: 1rem auto; | |
| } | |
| mark { | |
| padding: 2px 5px; | |
| border-radius: var(--standard-border-radius); | |
| background-color: var(--marked); | |
| color: black; | |
| } | |
| mark a { | |
| color: var(--accent); | |
| } | |
| img, | |
| video { | |
| max-width: 100%; | |
| height: auto; | |
| border-radius: var(--standard-border-radius); | |
| } | |
| figure { | |
| margin: 0; | |
| display: block; | |
| overflow-x: auto; | |
| } | |
| figure > img, | |
| figure > picture > img { | |
| display: block; | |
| margin-inline: auto; | |
| } | |
| figcaption { | |
| position: sticky; | |
| left: 0; | |
| text-align: center; | |
| font-size: 0.9rem; | |
| color: var(--text-light); | |
| margin-block: 1rem; | |
| } | |
| blockquote { | |
| margin-inline-start: 2rem; | |
| margin-inline-end: 0; | |
| margin-block: 2rem; | |
| padding: 0.4rem 0.8rem; | |
| border-inline-start: 0.35rem solid var(--accent); | |
| color: var(--text-light); | |
| font-style: italic; | |
| } | |
| cite { | |
| font-size: 0.9rem; | |
| color: var(--text-light); | |
| font-style: normal; | |
| } | |
| dt { | |
| color: var(--text-light); | |
| } | |
| /* Use mono font for code elements */ | |
| code, | |
| pre, | |
| pre span, | |
| kbd, | |
| samp { | |
| font-family: var(--mono-font); | |
| color: var(--code); | |
| } | |
| kbd { | |
| color: var(--preformatted); | |
| border: var(--border-width) solid var(--preformatted); | |
| border-bottom: 3px solid var(--preformatted); | |
| border-radius: var(--standard-border-radius); | |
| padding: 0.1rem 0.4rem; | |
| } | |
| pre { | |
| padding: 1rem 1.4rem; | |
| max-width: 100%; | |
| overflow: auto; | |
| color: var(--preformatted); | |
| } | |
| /* Fix embedded code within pre */ | |
| pre code { | |
| color: var(--preformatted); | |
| background: none; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| /* Progress bars */ | |
| /* Declarations are repeated because you */ | |
| /* cannot combine vendor-specific selectors */ | |
| progress { | |
| width: 100%; | |
| } | |
| progress:indeterminate { | |
| background-color: var(--accent-bg); | |
| } | |
| progress::-webkit-progress-bar { | |
| border-radius: var(--standard-border-radius); | |
| background-color: var(--accent-bg); | |
| } | |
| progress::-webkit-progress-value { | |
| border-radius: var(--standard-border-radius); | |
| background-color: var(--accent); | |
| } | |
| progress::-moz-progress-bar { | |
| border-radius: var(--standard-border-radius); | |
| background-color: var(--accent); | |
| transition-property: width; | |
| transition-duration: 0.3s; | |
| } | |
| progress:indeterminate::-moz-progress-bar { | |
| background-color: var(--accent-bg); | |
| } | |
| dialog { | |
| background-color: var(--bg); | |
| max-width: 40rem; | |
| margin: auto; | |
| } | |
| dialog::backdrop { | |
| background-color: var(--bg); | |
| opacity: 0.8; | |
| } | |
| dialog h2:first-of-type { | |
| margin-top: 0; | |
| } | |
| @media only screen and (max-width: 720px) { | |
| dialog { | |
| max-width: calc(100vw - 2rem); | |
| } | |
| } | |
| /* Superscript & Subscript */ | |
| /* Prevent scripts from affecting line-height. */ | |
| sup, sub { | |
| vertical-align: baseline; | |
| position: relative; | |
| } | |
| sup { | |
| top: -0.4em; | |
| } | |
| sub { | |
| top: 0.3em; | |
| } | |
| /* Classes for notices */ | |
| .notice { | |
| background: var(--accent-bg); | |
| border: var(--border-width) solid var(--border); | |
| border-radius: var(--standard-border-radius); | |
| padding: 1.5rem; | |
| margin: 2rem 0; | |
| } | |
| div.notice p:first-of-type { | |
| margin-top: 0; | |
| } | |
| div.notice p:last-of-type { | |
| margin-bottom: 0; | |
| } | |
| /* Print */ | |
| @media print { | |
| @page { | |
| margin: 1cm; | |
| } | |
| body { | |
| display: block; | |
| } | |
| body > header { | |
| background-color: unset; | |
| } | |
| body > header nav, | |
| body > footer { | |
| display: none; | |
| } | |
| article { | |
| border: none; | |
| padding: 0; | |
| } | |
| a[href^="http"]::after { | |
| content: " <" attr(href) ">"; | |
| } | |
| abbr[title]:after { | |
| content: " (" attr(title) ")"; | |
| } | |
| a { | |
| text-decoration: none; | |
| } | |
| p { | |
| widows: 3; | |
| orphans: 3; | |
| } | |
| hr { | |
| border-top: var(--border-width) solid var(--border); | |
| } | |
| mark { | |
| border: var(--border-width) solid var(--border); | |
| } | |
| figure { | |
| break-inside: avoid; | |
| } | |
| pre code { | |
| white-space: pre-wrap; | |
| } | |
| } | |
| """; | |
| return Results.Content(cssContent, contentType: "text/css"); | |
| }); | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment