/* ========================================================================== GeoGraSim Design-System V2 ========================================================================== Verbindlich für alle V2-Module und V2-Plattform-UIs. Module includen dieses File und nutzen die Tokens als CSS-Variablen. Tokens für: Farben (Standard + High-Contrast), Spacing, Radien, Shadows, Typografie. Plus A11y-Patterns: Focus-Indikatoren, Reduced-Motion, Visually-Hidden, Sticky-Modal-Pattern. Bild-Palette ≠ UI-Palette. Generierte Bilder folgen Flat-Scandinavian (siehe v2-modules/_spec/platform-standards.md § 5). ========================================================================== */ /* ── 1. Tokens (Standard-Modus) ────────────────────────────────────────── */ :root { /* Farben */ --ggs-fjord: #4a7c8a; --ggs-fjord-dark: #1f4e5a; --ggs-fjord-light: #dae8ec; --ggs-moss: #5a8a5e; --ggs-moss-dark: #3a6b3e; --ggs-moss-light: #dcead0; --ggs-sand: #e8d5b5; --ggs-sand-dark: #c4a97a; --ggs-coral: #c85c4a; --ggs-coral-light: #f0d0ca; --ggs-orange: #e8833a; --ggs-bg: #f5f2eb; --ggs-bg-dark: #e8e4d8; --ggs-white: #ffffff; --ggs-text: #2a2a2a; --ggs-text-muted: #8a8a8a; --ggs-text-light: #b0b0b0; --ggs-border: #e0ddd4; --ggs-success: var(--ggs-moss); --ggs-warning: var(--ggs-orange); --ggs-danger: var(--ggs-coral); --ggs-info: var(--ggs-fjord); /* Spacing */ --ggs-gap-xs: 4px; --ggs-gap-sm: 8px; --ggs-gap-md: 16px; --ggs-gap-lg: 24px; --ggs-gap-xl: 32px; /* Radien */ --ggs-radius-sm: 6px; --ggs-radius-md: 12px; --ggs-radius-lg: 16px; --ggs-radius-pill: 999px; /* Schatten */ --ggs-shadow-sm: 0 1px 3px rgba(0,0,0,.08); --ggs-shadow-md: 0 2px 8px rgba(0,0,0,.12); --ggs-shadow-lg: 0 4px 16px rgba(0,0,0,.16); /* Typografie */ --ggs-font: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --ggs-font-mono: 'JetBrains Mono', 'Fira Code', Consolas, monospace; /* Schrift-Skalierung — wird durch [data-text-scale] überschrieben */ --ggs-font-base: 1rem; /* ~16px */ /* Touch-Targets */ --ggs-touch-min: 36px; --ggs-touch-major: 44px; /* Schließen, Bestätigen, Abbrechen */ /* Animations-Dauern */ --ggs-anim-fast: 0.15s; --ggs-anim-med: 0.3s; --ggs-anim-slow: 0.6s; } /* ── 2. High-Contrast-Modus — hell (schwarz auf weiß) ─────────────────── */ /* Default-Variante: visuell ruhig, hohe Lesbarkeit, dunkel-saturierte Farben */ body[data-high-contrast="light"] { --ggs-fjord: #003366; --ggs-fjord-dark: #001a4d; --ggs-fjord-light: #cfd8e6; --ggs-moss: #1a4d1a; --ggs-moss-dark: #003300; --ggs-moss-light: #d0e6d0; --ggs-sand: #5a3a00; --ggs-sand-dark: #3a2400; --ggs-coral: #800000; --ggs-coral-light: #e6cccc; --ggs-orange: #b35900; --ggs-bg: #ffffff; --ggs-bg-dark: #f0f0f0; --ggs-white: #ffffff; --ggs-text: #000000; --ggs-text-muted: #000000; --ggs-text-light: #1a1a1a; --ggs-border: #000000; } /* ── 2b. High-Contrast-Modus — dunkel (gelb/grün auf schwarz) ─────────── */ /* Für Schüler*innen, die ein dunkles Schema bevorzugen */ body[data-high-contrast="dark"] { --ggs-fjord: #ffff00; --ggs-fjord-dark: #ffff00; --ggs-fjord-light: #404000; --ggs-moss: #00ff00; --ggs-moss-dark: #00ff00; --ggs-moss-light: #003300; --ggs-sand: #ffaa00; --ggs-sand-dark: #ffaa00; --ggs-coral: #ff6060; --ggs-coral-light: #330000; --ggs-orange: #ffaa00; --ggs-bg: #000000; --ggs-bg-dark: #000000; --ggs-white: #000000; --ggs-text: #ffffff; --ggs-text-muted: #e0e0e0; --ggs-text-light: #b0b0b0; --ggs-border: #ffffff; } /* Legacy-Kompatibilität: data-high-contrast="true" wird auf hell gemappt */ body[data-high-contrast="true"] { --ggs-fjord: #003366; --ggs-fjord-dark: #001a4d; --ggs-moss: #1a4d1a; --ggs-moss-dark: #003300; --ggs-coral: #800000; --ggs-bg: #ffffff; --ggs-white: #ffffff; --ggs-text: #000000; --ggs-text-muted: #000000; --ggs-border: #000000; } /* ── 3. Schrift-Skalierung (für textScale-Profil) ───────────────────────── */ body[data-text-scale="large"] { --ggs-font-base: 1.125rem; } /* ~18px */ body[data-text-scale="xlarge"] { --ggs-font-base: 1.25rem; } /* ~20px */ /* ── 4. Base Reset + Box ───────────────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; } * { margin: 0; padding: 0; } html, body { font-family: var(--ggs-font); font-size: var(--ggs-font-base); background: var(--ggs-bg); color: var(--ggs-text); line-height: 1.5; touch-action: manipulation; -webkit-tap-highlight-color: transparent; } input, textarea, select { font-size: max(16px, var(--ggs-font-base)); } /* iOS Zoom verhindern */ /* ── 5. Reduced-Motion: alles fast aus ────────────────────────────────── */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } /* Plus: Profil-Setting per data-Attribut (für Module die JS-Animationen haben) */ body[data-reduced-motion="true"] *, body[data-reduced-motion="true"] *::before, body[data-reduced-motion="true"] *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } /* ── 6. Focus-Indikatoren (Pflicht für A11y) ─────────────────────────── */ :focus { outline: none; } /* default mouse-focus weg, aber NUR weil :focus-visible drunter setzt */ :focus-visible { outline: 3px solid var(--ggs-fjord-dark); outline-offset: 2px; border-radius: 2px; } body[data-high-contrast="light"] :focus-visible { outline: 3px solid #000000; outline-offset: 2px; } body[data-high-contrast="dark"] :focus-visible { outline: 3px solid #ffff00; outline-offset: 2px; } body[data-high-contrast="true"] :focus-visible { outline: 3px solid #000000; outline-offset: 2px; } /* ── 7. Visually-Hidden (Screen-Reader-Only Text) ─────────────────────── */ .ggs-visually-hidden, .visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; } /* Skip-Link (taucht bei Tab-Fokus auf) */ .ggs-skip-link { position: absolute; top: -100px; left: var(--ggs-gap-md); background: var(--ggs-fjord-dark); color: var(--ggs-white); padding: var(--ggs-gap-sm) var(--ggs-gap-md); border-radius: var(--ggs-radius-sm); text-decoration: none; z-index: 9999; transition: top var(--ggs-anim-fast); } .ggs-skip-link:focus { top: var(--ggs-gap-md); } /* ── 8. Buttons ───────────────────────────────────────────────────────── */ .ggs-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--ggs-gap-sm); font-family: inherit; font-size: inherit; font-weight: 600; min-height: var(--ggs-touch-min); padding: 0 var(--ggs-gap-md); border-radius: var(--ggs-radius-md); border: 2px solid transparent; background: transparent; color: var(--ggs-text); cursor: pointer; transition: background var(--ggs-anim-fast), border-color var(--ggs-anim-fast); } .ggs-btn-primary { background: var(--ggs-fjord-dark); color: var(--ggs-white); border-color: var(--ggs-fjord-dark); } @media (hover: hover) { .ggs-btn-primary:hover { background: var(--ggs-fjord); border-color: var(--ggs-fjord); } } .ggs-btn-primary:active { transform: translateY(1px); } .ggs-btn-secondary { background: var(--ggs-white); color: var(--ggs-fjord-dark); border-color: var(--ggs-fjord-dark); } .ggs-btn-ghost { background: transparent; color: var(--ggs-text-muted); border-color: transparent; } @media (hover: hover) { .ggs-btn-ghost:hover { background: var(--ggs-bg-dark); color: var(--ggs-text); } } .ggs-btn-danger { background: var(--ggs-danger); color: var(--ggs-white); border-color: var(--ggs-danger); } .ggs-btn-major { min-height: var(--ggs-touch-major); padding: 0 var(--ggs-gap-lg); font-size: 1.05em; } .ggs-btn[disabled] { opacity: 0.5; cursor: not-allowed; } /* Icon-Button (kreisrund) */ .ggs-icon-btn { width: var(--ggs-touch-min); height: var(--ggs-touch-min); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; background: var(--ggs-white); border: 2px solid transparent; color: var(--ggs-text); cursor: pointer; font-size: 1.1em; } .ggs-icon-btn[aria-pressed="true"] { background: var(--ggs-fjord); color: var(--ggs-white); } /* ── 9. Karten ─────────────────────────────────────────────────────────── */ .ggs-card { background: var(--ggs-white); border: 1px solid var(--ggs-border); border-radius: var(--ggs-radius-lg); padding: var(--ggs-gap-lg); box-shadow: var(--ggs-shadow-sm); } .ggs-card-header { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ggs-text-muted); margin-bottom: var(--ggs-gap-sm); } /* ── 10. Modal- und Overlay-Pattern (iPad-Pflicht) ────────────────────── */ .ggs-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.45); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); } .ggs-modal { background: var(--ggs-white); border-radius: var(--ggs-radius-lg); max-width: 720px; width: 100%; max-height: 94dvh; /* dvh statt vh — Pflicht für iPad */ overflow: hidden; display: flex; flex-direction: column; box-shadow: var(--ggs-shadow-lg); position: relative; } .ggs-modal-content { padding: var(--ggs-gap-lg); overflow-y: auto; -webkit-overflow-scrolling: touch; } .ggs-modal-actions { position: sticky; /* sticky am unteren Rand — Pflicht für iPad */ bottom: 0; background: linear-gradient(to top, var(--ggs-white) 80%, transparent); padding: var(--ggs-gap-md); padding-bottom: max(var(--ggs-gap-md), env(safe-area-inset-bottom)); display: flex; justify-content: flex-end; gap: var(--ggs-gap-md); border-top: 1px solid var(--ggs-border); } .ggs-modal-close { position: absolute; top: var(--ggs-gap-sm); right: var(--ggs-gap-sm); width: var(--ggs-touch-major); height: var(--ggs-touch-major); border-radius: 50%; border: none; background: var(--ggs-bg-dark); color: var(--ggs-text); cursor: pointer; font-size: 1.2em; display: inline-flex; align-items: center; justify-content: center; } /* ── 11. Form-Elemente ────────────────────────────────────────────────── */ .ggs-input, .ggs-select, .ggs-textarea { width: 100%; padding: var(--ggs-gap-sm) var(--ggs-gap-md); border: 2px solid var(--ggs-border); border-radius: var(--ggs-radius-md); background: var(--ggs-white); color: var(--ggs-text); font-family: inherit; font-size: max(16px, var(--ggs-font-base)); min-height: var(--ggs-touch-min); } .ggs-input:focus, .ggs-select:focus, .ggs-textarea:focus { border-color: var(--ggs-fjord-dark); } .ggs-label { display: block; font-size: 0.85em; color: var(--ggs-text-muted); margin-bottom: var(--ggs-gap-xs); font-weight: 600; } /* ── 12. Status-Pills (Erfolg/Warnung/Danger) ─────────────────────────── */ .ggs-pill { display: inline-flex; align-items: center; gap: var(--ggs-gap-xs); padding: 2px var(--ggs-gap-sm); border-radius: var(--ggs-radius-pill); font-size: 0.78em; font-weight: 700; } .ggs-pill-success { background: var(--ggs-moss-light); color: var(--ggs-moss-dark); } .ggs-pill-warning { background: #fff3cd; color: #8a5a00; } .ggs-pill-danger { background: var(--ggs-coral-light); color: #862d20; } .ggs-pill-info { background: var(--ggs-fjord-light); color: var(--ggs-fjord-dark); } /* Information mit Icon (nicht nur Farbe — A11y-Pflicht) */ .ggs-pill::before { content: attr(data-icon); font-weight: 900; } /* ── 13. Live-Region für Score-/Phase-Updates (A11y-Stufe-2-Vorbereitung) ── */ .ggs-live-region { /* Visuell: nichts. Aber Screen-Reader liest aria-live="polite" mit. */ } /* ── 14. Stuck-Banner-Pattern ─────────────────────────────────────────── */ .ggs-stuck-banner { background: #fff3cd; border-left: 4px solid var(--ggs-orange); padding: var(--ggs-gap-md); border-radius: var(--ggs-radius-md); font-size: 0.9em; color: #8a5a00; } body[data-high-contrast="light"] .ggs-stuck-banner { background: #ffffff; color: #5a3a00; border: 2px solid #5a3a00; } body[data-high-contrast="dark"] .ggs-stuck-banner { background: #ffaa00; color: #000; border-left-color: #ffff00; } /* ── 15. Avatare ──────────────────────────────────────────────────────── */ .ggs-avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--ggs-fjord-light); object-fit: cover; border: 2px solid var(--ggs-white); box-shadow: var(--ggs-shadow-sm); } .ggs-avatar-lg { width: 64px; height: 64px; } .ggs-avatar-xl { width: 96px; height: 96px; } /* ── 16. Layout-Helper ────────────────────────────────────────────────── */ .ggs-stack { display: flex; flex-direction: column; gap: var(--ggs-gap-md); } .ggs-row { display: flex; flex-direction: row; gap: var(--ggs-gap-md); align-items: center; } .ggs-row-end { justify-content: flex-end; } .ggs-row-between { justify-content: space-between; } .ggs-spacer { flex: 1; } /* ── 17. Responsive Layout-Breakpoints ────────────────────────────────── */ /* Desktop ≥ 1200px: dreispaltig iPad Landscape 900–1199px: dreispaltig kompakter iPad Portrait / Mobile < 900px: einspaltig */ .ggs-layout-3col { display: grid; grid-template-columns: 240px 1fr 280px; gap: var(--ggs-gap-lg); } @media (max-width: 1199px) { .ggs-layout-3col { grid-template-columns: 210px 1fr 250px; gap: var(--ggs-gap-md); } } @media (max-width: 899px) { .ggs-layout-3col { grid-template-columns: 1fr; } } /* ── 18. Notes für Modul-Entwickler*innen ────────────────────────────── */ /* - Hex-Werte NICHT hardcoden — immer var(--ggs-...) nutzen. - Modul-eigene Farben (z.B. Wettersymbole) als ---* lokal definieren, aber GGS-Tokens als Fallback einbinden. - Eigene Modale: bitte .ggs-modal/.ggs-modal-actions/.ggs-modal-close nutzen, nicht eigene Modal-Klassen — sonst geht das iPad-Pattern verloren. - Information NIE nur durch Farbe vermitteln — immer Icon + Farbe. - Bei Auto-Play-Animationen: prefers-reduced-motion respektieren UND im UI einen Stop-Button anbieten. */