Files
nebula/src/app/components.css
T
florian.wildeandClaude Opus 5 04e8437f29 feat(ui): Markdown in Beschreibung und Notizen
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>
2026-07-26 01:03:55 +02:00

396 lines
10 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ─────────────────────────────────────────────────────────────
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;
}
}