Bisher wurde alles als reiner Text ausgegeben. Jetzt werden Ueberschriften, Listen, Tabellen, Codebloecke, Zitate und Links dargestellt, und aus "- [ ] Aufgabe" werden echte Checklisten. Sicherheitsrelevant: der Inhalt stammt vom Benutzer. Ohne rehype-sanitize waere das ein XSS-Loch, sobald jemand rohes HTML in eine Notiz schreibt. Das Standardschema erlaubt nur harmlose Elemente; ergaenzt wird nur, was fuer Aufgabenlisten und Sprachklassen an Codebloecken noetig ist. Links bekommen zusaetzlich target=_blank mit noopener und nofollow. Die Formatierung steht in components.css: Tailwinds Preflight setzt alle Abstaende und Schriftgroessen zurueck, Ueberschriften und Listen brauchen also wieder Form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
396 lines
10 KiB
CSS
396 lines
10 KiB
CSS
/* ─────────────────────────────────────────────────────────────
|
||
Nebula – wiederkehrende Bausteine.
|
||
|
||
Bewusst in @layer components: Tailwind sortiert die Layer als
|
||
theme → base → components → utilities. Ohne diese Zuordnung wuerden
|
||
die Kurzschreibweisen hier (padding, border-radius …) die Utilities
|
||
ueberschreiben, weil sie spaeter im Stylesheet stehen – aus
|
||
"field pl-9" wuerde dann wieder das linke Standardpadding und das
|
||
Suchsymbol laege auf dem Platzhaltertext.
|
||
───────────────────────────────────────────────────────────── */
|
||
|
||
@layer components {
|
||
/* ── Glasflaechen ─────────────────────────────────────────── */
|
||
|
||
.glass {
|
||
position: relative;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
backdrop-filter: blur(18px) saturate(140%);
|
||
-webkit-backdrop-filter: blur(18px) saturate(140%);
|
||
border-radius: var(--radius-card);
|
||
box-shadow:
|
||
0 1px 0 0 var(--glass-highlight) inset,
|
||
0 24px 60px -30px rgba(0, 0, 0, 0.9);
|
||
}
|
||
|
||
.glass-soft {
|
||
background: rgba(255, 255, 255, 0.025);
|
||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
border-radius: 0.9rem;
|
||
}
|
||
|
||
/* Karte mit Akzent-Glow. --accent-from/--accent-to setzt die Komponente. */
|
||
.card-glow {
|
||
transition:
|
||
transform 0.35s cubic-bezier(0.22, 1, 0.36, 1),
|
||
box-shadow 0.35s ease,
|
||
border-color 0.35s ease;
|
||
}
|
||
|
||
.card-glow::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: -1px;
|
||
border-radius: inherit;
|
||
padding: 1px;
|
||
background: linear-gradient(135deg, var(--accent-from, #8b5cf6), var(--accent-to, #d946ef));
|
||
-webkit-mask:
|
||
linear-gradient(#000 0 0) content-box,
|
||
linear-gradient(#000 0 0);
|
||
-webkit-mask-composite: xor;
|
||
mask-composite: exclude;
|
||
opacity: 0;
|
||
transition: opacity 0.35s ease;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.card-glow:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow:
|
||
0 1px 0 0 var(--glass-highlight) inset,
|
||
0 30px 70px -30px rgba(0, 0, 0, 0.95),
|
||
0 0 46px -12px var(--accent-from, #8b5cf6);
|
||
}
|
||
|
||
.card-glow:hover::before {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
/**
|
||
* Flaeche fuer alles, was ueber dem Inhalt schwebt: Menues, Dialoge.
|
||
*
|
||
* Bewusst nahezu deckend statt Glas. Ein Overlay muss lesbar sein, egal
|
||
* was darunter liegt - bei 3,5 % Deckkraft schien der Inhalt dahinter
|
||
* durch. backdrop-filter hilft hier nicht: liegt das Element in einem
|
||
* Vorfahren, der selbst backdrop-filter setzt (unsere Karten), bleibt die
|
||
* Weichzeichnung wirkungslos.
|
||
*/
|
||
.popover {
|
||
/* Vollstaendig deckend: bei 97 % schimmerte darunterliegender Text
|
||
(die Schaltflaechen "Bearbeiten" und "Loeschen") noch durch. */
|
||
background: #090b18;
|
||
border: 1px solid rgba(255, 255, 255, 0.11);
|
||
border-radius: 0.9rem;
|
||
box-shadow:
|
||
0 1px 0 0 rgba(255, 255, 255, 0.07) inset,
|
||
0 28px 70px -24px rgba(0, 0, 0, 0.95);
|
||
}
|
||
|
||
/**
|
||
* Markdown-Inhalte. Tailwind setzt in seinem Preflight alle Abstaende und
|
||
* Schriftgroessen zurueck, also muessen Ueberschriften, Listen und
|
||
* Codebloecke hier wieder Form bekommen.
|
||
*/
|
||
.md {
|
||
font-size: 0.875rem;
|
||
line-height: 1.65;
|
||
color: #cbd5e1;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
.md > *:first-child { margin-top: 0; }
|
||
.md > *:last-child { margin-bottom: 0; }
|
||
|
||
.md h1, .md h2, .md h3, .md h4 {
|
||
color: #f1f5f9;
|
||
font-weight: 600;
|
||
line-height: 1.3;
|
||
margin: 1.4em 0 0.5em;
|
||
}
|
||
.md h1 { font-size: 1.3rem; }
|
||
.md h2 { font-size: 1.15rem; }
|
||
.md h3 { font-size: 1rem; }
|
||
.md h4 { font-size: 0.9rem; }
|
||
|
||
.md p { margin: 0.75em 0; }
|
||
|
||
.md a {
|
||
color: #c4b5fd;
|
||
text-decoration: underline;
|
||
text-underline-offset: 2px;
|
||
}
|
||
.md a:hover { color: #ddd6fe; }
|
||
|
||
.md strong { color: #f1f5f9; font-weight: 600; }
|
||
|
||
.md ul, .md ol { margin: 0.75em 0; padding-left: 1.35em; }
|
||
.md ul { list-style: disc; }
|
||
.md ol { list-style: decimal; }
|
||
.md li { margin: 0.3em 0; }
|
||
.md li::marker { color: #64748b; }
|
||
|
||
/* Aufgabenlisten aus "- [ ] …" */
|
||
.md li:has(> input[type="checkbox"]) {
|
||
list-style: none;
|
||
margin-left: -1.35em;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 0.5rem;
|
||
}
|
||
.md input[type="checkbox"] {
|
||
margin-top: 0.35em;
|
||
accent-color: #8b5cf6;
|
||
width: 0.9rem;
|
||
height: 0.9rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.md code {
|
||
background: rgba(255, 255, 255, 0.07);
|
||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||
border-radius: 0.35rem;
|
||
padding: 0.1em 0.35em;
|
||
font-family: var(--font-mono);
|
||
font-size: 0.85em;
|
||
color: #e9d5ff;
|
||
}
|
||
|
||
.md pre {
|
||
background: rgba(4, 6, 16, 0.75);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 0.75rem;
|
||
padding: 0.85rem 1rem;
|
||
margin: 0.9em 0;
|
||
overflow-x: auto;
|
||
}
|
||
.md pre code {
|
||
background: none;
|
||
border: none;
|
||
padding: 0;
|
||
color: #cbd5e1;
|
||
font-size: 0.8rem;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.md blockquote {
|
||
border-left: 2px solid rgba(167, 139, 250, 0.5);
|
||
padding-left: 0.9rem;
|
||
margin: 0.9em 0;
|
||
color: #94a3b8;
|
||
font-style: italic;
|
||
}
|
||
|
||
.md hr {
|
||
border: 0;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.09);
|
||
margin: 1.4em 0;
|
||
}
|
||
|
||
.md table {
|
||
width: 100%;
|
||
margin: 0.9em 0;
|
||
border-collapse: collapse;
|
||
font-size: 0.8rem;
|
||
display: block;
|
||
overflow-x: auto;
|
||
}
|
||
.md th, .md td {
|
||
border: 1px solid rgba(255, 255, 255, 0.09);
|
||
padding: 0.4rem 0.6rem;
|
||
text-align: left;
|
||
}
|
||
.md th {
|
||
background: rgba(255, 255, 255, 0.04);
|
||
color: #e2e8f0;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.md img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
/* ── Text & Akzente ───────────────────────────────────────── */
|
||
|
||
.text-gradient {
|
||
background: linear-gradient(120deg, #c4b5fd 0%, #67e8f9 45%, #f0abfc 100%);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
}
|
||
|
||
.accent-bar {
|
||
background: linear-gradient(90deg, var(--accent-from, #8b5cf6), var(--accent-to, #d946ef));
|
||
}
|
||
|
||
.accent-dot {
|
||
box-shadow: 0 0 12px 1px currentColor;
|
||
}
|
||
|
||
/* ── Buttons ──────────────────────────────────────────────── */
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
border-radius: 0.75rem;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
padding: 0.55rem 1rem;
|
||
transition:
|
||
background-color 0.2s ease,
|
||
border-color 0.2s ease,
|
||
color 0.2s ease,
|
||
box-shadow 0.2s ease,
|
||
filter 0.2s ease;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
border: 1px solid transparent;
|
||
}
|
||
|
||
.btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn:focus-visible {
|
||
outline: 2px solid rgba(167, 139, 250, 0.8);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(120deg, #7c3aed, #c026d3);
|
||
color: #fff;
|
||
box-shadow: 0 10px 30px -12px rgba(168, 85, 247, 0.9);
|
||
}
|
||
|
||
.btn-primary:hover:not(:disabled) {
|
||
filter: brightness(1.12);
|
||
box-shadow: 0 14px 36px -12px rgba(168, 85, 247, 1);
|
||
}
|
||
|
||
.btn-ghost {
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
color: #cbd5f5;
|
||
}
|
||
|
||
.btn-ghost:hover:not(:disabled) {
|
||
background: rgba(255, 255, 255, 0.09);
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: rgba(244, 63, 94, 0.12);
|
||
border-color: rgba(244, 63, 94, 0.35);
|
||
color: #fda4af;
|
||
}
|
||
|
||
.btn-danger:hover:not(:disabled) {
|
||
background: rgba(244, 63, 94, 0.22);
|
||
color: #fff;
|
||
}
|
||
|
||
/* ── Formularfelder ───────────────────────────────────────── */
|
||
|
||
.field {
|
||
width: 100%;
|
||
border-radius: 0.75rem;
|
||
background: rgba(8, 10, 24, 0.6);
|
||
border: 1px solid rgba(255, 255, 255, 0.09);
|
||
padding: 0.6rem 0.85rem;
|
||
font-size: 0.875rem;
|
||
color: #e6e8f5;
|
||
transition:
|
||
border-color 0.2s ease,
|
||
box-shadow 0.2s ease;
|
||
}
|
||
|
||
.field::placeholder {
|
||
color: rgba(148, 163, 184, 0.6);
|
||
}
|
||
|
||
.field:focus {
|
||
outline: none;
|
||
border-color: rgba(167, 139, 250, 0.6);
|
||
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.18);
|
||
}
|
||
|
||
.field-label {
|
||
display: block;
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: rgba(148, 163, 184, 0.85);
|
||
margin-bottom: 0.4rem;
|
||
}
|
||
|
||
select.field option {
|
||
background: #0a0c1a;
|
||
color: #e6e8f5;
|
||
}
|
||
|
||
/**
|
||
* Schieberegler. Der native Regler zeichnet die Spur rechts vom Griff in
|
||
* einem hellen Standardton - im dunklen Layout ein grelles Band. Deshalb
|
||
* vollstaendig selbst gezeichnet: der gefuellte Anteil kommt aus der
|
||
* Variablen --fill, die die Komponente aus dem Wert setzt.
|
||
*/
|
||
.range {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 100%;
|
||
height: 6px;
|
||
border-radius: 9999px;
|
||
cursor: pointer;
|
||
background: linear-gradient(
|
||
90deg,
|
||
#8b5cf6 0%,
|
||
#d946ef var(--fill, 0%),
|
||
rgba(255, 255, 255, 0.1) var(--fill, 0%),
|
||
rgba(255, 255, 255, 0.1) 100%
|
||
);
|
||
}
|
||
|
||
.range:focus-visible {
|
||
outline: 2px solid rgba(167, 139, 250, 0.8);
|
||
outline-offset: 4px;
|
||
}
|
||
|
||
.range::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
height: 16px;
|
||
width: 16px;
|
||
border-radius: 9999px;
|
||
background: #f5f3ff;
|
||
border: 2px solid #7c3aed;
|
||
box-shadow: 0 0 14px -2px rgba(168, 85, 247, 0.9);
|
||
cursor: grab;
|
||
}
|
||
|
||
.range::-webkit-slider-thumb:active {
|
||
cursor: grabbing;
|
||
}
|
||
|
||
.range::-moz-range-thumb {
|
||
height: 14px;
|
||
width: 14px;
|
||
border-radius: 9999px;
|
||
background: #f5f3ff;
|
||
border: 2px solid #7c3aed;
|
||
box-shadow: 0 0 14px -2px rgba(168, 85, 247, 0.9);
|
||
cursor: grab;
|
||
}
|
||
|
||
.range::-moz-range-track {
|
||
background: transparent;
|
||
}
|
||
}
|