Style editor Markdown with .markdown-body and add ::: design blocks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ca0b3c311c
commit
5eb36ad3e9
12 files changed
+683
-17
No files matched your search
@@ -37,6 +37,14 @@
|
||||
not --color-accent, so white text stays readable across the whole sweep. */
|
||||
--gradient-brand: linear-gradient(135deg, var(--color-header) 0%, #3a7a3e 100%);
|
||||
|
||||
/* Buttons in editor content (a lone mail link, links in an "aufruf" box): a
|
||||
filled blue with white text. Own tokens, not --color-brand, because in dark
|
||||
mode --color-brand turns light for link text, and white on that light blue
|
||||
would fail WCAG AA. Both shades carry white at >= 5.5:1. */
|
||||
--color-button: #2b6f9c;
|
||||
--color-button-hover: #1f5477;
|
||||
--color-on-button: #fff;
|
||||
|
||||
/* Headings. Its own token (not --color-brand) so headings and links can differ:
|
||||
in dark mode headings become a soft white while links keep the brand blue. */
|
||||
--color-heading: #2b6f9c;
|
||||
@@ -123,6 +131,10 @@
|
||||
|
||||
--gradient-brand: linear-gradient(135deg, var(--color-header) 0%, #2f6b33 100%);
|
||||
|
||||
--color-button: #266f9c; /* the header blue: deep enough for white
|
||||
text, calm on the dark page */
|
||||
--color-button-hover: #1f5477;
|
||||
|
||||
--color-heading: #c9ced6; /* soft white, kept a step brighter than the
|
||||
body text so headings still lead the page */
|
||||
|
||||
@@ -461,3 +473,351 @@ h1:focus {
|
||||
padding-top: var(--space-2);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* --- Editor content (.markdown-body) -------------------------------------------
|
||||
Everything the editors write in PocketBase (page bodies, posts, FAQ answers, the
|
||||
intros on /posts and /events) is Markdown rendered by Shared/Markdown.cs and
|
||||
wrapped in .markdown-body. Editors cannot add classes or HTML, so the look of
|
||||
their text comes entirely from these rules. Every selector is scoped to
|
||||
.markdown-body so nothing here leaks into the site's own components, and the
|
||||
rules must hold for any Markdown an editor might type, including none at all. */
|
||||
|
||||
/* The line icons the content styles need, as data URIs: no request, no foreign
|
||||
host. Same Lucide paths as Shared/IconSet.cs (license notice there); CSS cannot
|
||||
reach that C# set, so these are copies. Drawn in black and used as a mask over
|
||||
background-color: currentColor, so each icon takes the colour of its text --
|
||||
dark mode included -- just like the inline-SVG <Icon> component. */
|
||||
.markdown-body {
|
||||
--icon-external-link: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 3h6v6'/%3E%3Cpath d='M10 14 21 3'/%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3C/svg%3E");
|
||||
--icon-file-text: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z'/%3E%3Cpath d='M14 2v5a1 1 0 0 0 1 1h5'/%3E%3Cpath d='M10 9H8'/%3E%3Cpath d='M16 13H8'/%3E%3Cpath d='M16 17H8'/%3E%3C/svg%3E");
|
||||
--icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
|
||||
--icon-mail: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7'/%3E%3Crect x='2' y='4' width='20' height='16' rx='2'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* A block's last element needs no gap below: the surrounding card or page already
|
||||
spaces it, and a trailing margin makes boxes look bottom-heavy. */
|
||||
.markdown-body > :last-child,
|
||||
.markdown-body div > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Section headings get a short green bar above them. The bodies are mostly a run
|
||||
of ## sections, and the bar makes each start easy to spot when scrolling on a
|
||||
phone. Decoration only: the heading text carries the meaning. */
|
||||
.markdown-body h2::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 2.5rem;
|
||||
height: 4px;
|
||||
margin-bottom: var(--space-2);
|
||||
border-radius: 2px;
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Lists: the reading width of paragraphs, a little air between items, and the
|
||||
bullets and numbers in the green accent so lists tie into the palette. */
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
margin: 0 0 var(--space-3);
|
||||
padding-left: 1.5rem;
|
||||
max-width: 42rem;
|
||||
}
|
||||
|
||||
.markdown-body li + li {
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
.markdown-body li::marker {
|
||||
color: var(--color-accent-dark);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Quotes (e.g. a passage from the rules of procedure): an accent bar and a faint
|
||||
green wash set them apart from the editor's own words. */
|
||||
.markdown-body blockquote {
|
||||
margin: 0 0 var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
max-width: 42rem;
|
||||
border-left: 4px solid var(--color-accent);
|
||||
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
||||
background: var(--color-accent-tint);
|
||||
}
|
||||
|
||||
/* Links: a soft underline that firms up on hover. Underlined at all (not colour
|
||||
alone) so links stay recognisable for colour-blind visitors. */
|
||||
.markdown-body a {
|
||||
text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 3px;
|
||||
transition: color var(--transition), text-decoration-color var(--transition);
|
||||
}
|
||||
|
||||
.markdown-body a:hover {
|
||||
text-decoration-color: currentColor;
|
||||
}
|
||||
|
||||
/* Links that leave the site get a small external-link icon, so visitors know
|
||||
before clicking. Editors write links to our own pages as relative paths
|
||||
(/contact), so "absolute http(s) URL" means "another site". PDFs are left out:
|
||||
they carry their own file icon below. */
|
||||
.markdown-body a:is([href^="http://"], [href^="https://"]):not([href$=".pdf" i])::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
margin-left: 0.2em;
|
||||
vertical-align: -0.05em;
|
||||
background-color: currentColor;
|
||||
mask: var(--icon-external-link) center / contain no-repeat;
|
||||
}
|
||||
|
||||
/* PDF links inside running text: a file icon in front and a small "PDF" tag
|
||||
behind, so visitors know a download (not a web page) opens. */
|
||||
.markdown-body a[href$=".pdf" i]::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-right: 0.25em;
|
||||
vertical-align: -0.15em;
|
||||
background-color: currentColor;
|
||||
mask: var(--icon-file-text) center / contain no-repeat;
|
||||
}
|
||||
|
||||
.markdown-body a[href$=".pdf" i]::after {
|
||||
content: "PDF";
|
||||
margin-left: 0.35em;
|
||||
padding: 0 0.35em;
|
||||
border-radius: 4px;
|
||||
background: var(--color-brand-tint);
|
||||
color: var(--color-text-soft);
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* A PDF link standing alone in its paragraph or list item becomes a file card;
|
||||
this is what turns a plain list of documents on /downloads into a download
|
||||
area. Markdown.cs marks such paragraphs/items .lone-link, because CSS cannot see
|
||||
the text around a link. Two child paths: in a tight list the link sits directly
|
||||
in the <li>, in a loose list inside a <p>. */
|
||||
.markdown-body .lone-link > a[href$=".pdf" i],
|
||||
.markdown-body .lone-link > p > a[href$=".pdf" i] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
max-width: 42rem;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--color-bg);
|
||||
box-shadow: var(--shadow);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: box-shadow var(--transition), border-color var(--transition);
|
||||
}
|
||||
|
||||
.markdown-body .lone-link > a[href$=".pdf" i]:hover,
|
||||
.markdown-body .lone-link > p > a[href$=".pdf" i]:hover {
|
||||
border-color: var(--color-brand);
|
||||
box-shadow: var(--shadow-hover);
|
||||
}
|
||||
|
||||
.markdown-body .lone-link > a[href$=".pdf" i]::before,
|
||||
.markdown-body .lone-link > p > a[href$=".pdf" i]::before {
|
||||
flex-shrink: 0;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin: 0;
|
||||
color: var(--color-accent-dark);
|
||||
background-color: var(--color-accent-dark);
|
||||
}
|
||||
|
||||
/* The "PDF" tag moves to the card's right edge. */
|
||||
.markdown-body .lone-link > a[href$=".pdf" i]::after,
|
||||
.markdown-body .lone-link > p > a[href$=".pdf" i]::after {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* A list of file cards needs no bullets and no indent: the cards are the
|
||||
structure. */
|
||||
.markdown-body li.lone-link:has(> a[href$=".pdf" i], > p > a[href$=".pdf" i]) {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.markdown-body ul:has(> li.lone-link > a[href$=".pdf" i], > li.lone-link > p > a[href$=".pdf" i]) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* Buttons: a mail link alone in its paragraph (the contact page's main action),
|
||||
and every link inside an "aufruf" box. Filled with the button blue (see
|
||||
--color-button for why not --color-brand); hover darkens it and adds a shadow,
|
||||
nothing moves. */
|
||||
.markdown-body .lone-link > a[href^="mailto:"],
|
||||
.markdown-body .aufruf a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-button);
|
||||
color: var(--color-on-button);
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transition: background-color var(--transition), box-shadow var(--transition);
|
||||
}
|
||||
|
||||
.markdown-body .lone-link > a[href^="mailto:"]:hover,
|
||||
.markdown-body .aufruf a:hover {
|
||||
background: var(--color-button-hover);
|
||||
color: var(--color-on-button);
|
||||
box-shadow: var(--shadow-hover);
|
||||
}
|
||||
|
||||
/* The mail button leads with an envelope, so it reads as "write to us" at a
|
||||
glance. */
|
||||
.markdown-body .lone-link > a[href^="mailto:"]::before {
|
||||
content: "";
|
||||
width: 1.1em;
|
||||
height: 1.1em;
|
||||
background-color: currentColor;
|
||||
mask: var(--icon-mail) center / contain no-repeat;
|
||||
}
|
||||
|
||||
/* --- Design blocks (::: name ... :::) ------------------------------------------
|
||||
Markdown.cs turns "::: name" into <div class="name">. The names are German
|
||||
because editors type them (CLAUDE.md: values German). An unknown name has no
|
||||
rule here and so simply reads as normal text -- a typo never breaks a page. */
|
||||
|
||||
/* ::: aufruf -- a highlighted call to action: a tinted box with a brand-blue edge.
|
||||
Its links become buttons (rule above). */
|
||||
.markdown-body .aufruf {
|
||||
margin: 0 0 var(--space-4);
|
||||
padding: var(--space-4);
|
||||
max-width: 42rem;
|
||||
border-left: 4px solid var(--color-brand);
|
||||
border-radius: var(--radius);
|
||||
background: var(--color-brand-tint);
|
||||
}
|
||||
|
||||
/* ::: hinweis -- a quiet info box with an info icon in front. Green wash, not the
|
||||
yellow highlight: it informs, it does not warn. The icon is decoration; the text
|
||||
says what matters. */
|
||||
.markdown-body .hinweis {
|
||||
position: relative;
|
||||
margin: 0 0 var(--space-4);
|
||||
padding: var(--space-3) var(--space-4) var(--space-3) 3.25rem;
|
||||
max-width: 42rem;
|
||||
border-radius: var(--radius);
|
||||
background: var(--color-accent-tint);
|
||||
}
|
||||
|
||||
.markdown-body .hinweis::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: calc(var(--space-3) + 0.2rem);
|
||||
left: var(--space-4);
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
background-color: var(--color-accent-dark);
|
||||
mask: var(--icon-info) center / contain no-repeat;
|
||||
}
|
||||
|
||||
/* ::: kacheln and ::: kennzahlen -- a list laid out as a grid of cards. auto-fill
|
||||
with a minimum width gives as many columns as fit (one on a phone) without any
|
||||
breakpoint. */
|
||||
.markdown-body :is(.kacheln, .kennzahlen) > ul {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
max-width: none;
|
||||
margin: 0 0 var(--space-4);
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.markdown-body .kacheln > ul {
|
||||
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
|
||||
}
|
||||
|
||||
.markdown-body .kennzahlen > ul {
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
}
|
||||
|
||||
/* The grid gap does the spacing; the list's own item spacing would only push the
|
||||
later cards of the first row down. */
|
||||
.markdown-body :is(.kacheln, .kennzahlen) li + li {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Kacheln: each item a white card; a bold start ("- **Mensa** ...") becomes the
|
||||
card's title on its own line. */
|
||||
.markdown-body .kacheln li {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-radius: var(--radius);
|
||||
background: var(--color-bg);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.markdown-body .kacheln li > strong:first-child {
|
||||
display: block;
|
||||
margin-bottom: var(--space-1);
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
/* Kennzahlen: "- **seit 1975** Gründung" -- the bold part becomes a big figure,
|
||||
the rest a small caption beneath. Brand blue for the figure, not the green: the
|
||||
green does not reach 3:1 on its own tint, the blue does. */
|
||||
.markdown-body .kennzahlen li {
|
||||
padding: var(--space-4) var(--space-3);
|
||||
border-radius: var(--radius);
|
||||
background: var(--color-accent-tint);
|
||||
color: var(--color-text-soft);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.markdown-body .kennzahlen li > strong:first-child {
|
||||
display: block;
|
||||
color: var(--color-brand);
|
||||
font-size: 2rem;
|
||||
font-weight: var(--font-weight-heading);
|
||||
letter-spacing: var(--letter-spacing-heading);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
/* Tables (pipe tables): not in the content yet, styled ahead of time. As a block
|
||||
with overflow they scroll sideways on a phone instead of stretching the page. */
|
||||
.markdown-body table {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
margin: 0 0 var(--space-4);
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.markdown-body th,
|
||||
.markdown-body td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-heading);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Images never overflow the column and get the site's rounded corners. */
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* A horizontal rule as a calm thin line with room around it. */
|
||||
.markdown-body hr {
|
||||
margin: var(--space-5) 0;
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
Reference in new issue
Block a user