:root {
  color-scheme: light dark;

  --bg: #f7f7fb;
  --surface: #ffffff;
  --border: #e2e2ea;
  --text: #22222c;
  --text-muted: #6b6b78;
  --accent: #4c4cff;
  --accent-contrast: #ffffff;
  --danger: #e0473e;
  --warn: #b8860b;
  --radius: 10px;
  --shadow: 0 2px 8px rgba(20, 20, 40, 0.06);
  --shadow-modal: 0 10px 40px rgba(0, 0, 0, 0.2);
  --modal-border: transparent;

  /* Regenbogenfarben (Pastelltöne für Header) — bewusst identisch in Hell
     und Dunkel: die Farbcodierung ist Inhalt, nicht Deko (siehe Punkt 5),
     und Tageskopf-Text bleibt aus demselben Grund immer dunkel (#1c1c26
     unten), unabhängig vom Design. */
  --day-1: #ff8a8a; /* Montag - Rot */
  --day-2: #ffb37a; /* Dienstag - Orange */
  --day-3: #ffe27a; /* Mittwoch - Gelb */
  --day-4: #8fe08f; /* Donnerstag - Grün */
  --day-5: #7ac9ff; /* Freitag - Blau */
  --day-6: #9d8cff; /* Samstag - Indigo */
  --day-7: #e39dff; /* Sonntag - Violett */

  /* "Jetzt"-Hervorhebung (aktuelle Zeile/Zelle) — eigene Amber-Töne, nicht
     von --accent ableitbar. --now-bg ist bewusst als Mix aus --now-accent
     und --surface definiert (statt eigenem Hell-/Dunkel-Wertpaar), damit
     sie automatisch mit dem Theme mitwandert. */
  --now-accent: #f0a83c;
  --now-bg: color-mix(in srgb, var(--now-accent) 18%, var(--surface));

  /* Abgeleitete Zwischentöne: aus den Basis-Tokens oben mit color-mix()
     berechnet, statt als eigene Hell-/Dunkel-Werte gepflegt zu werden — sie
     folgen automatisch jeder Theme-Änderung an --text/--surface/--accent/
     --danger weiter unten, ohne im @media/[data-theme]-Block wiederholt zu
     werden müssen. */
  --surface-2: color-mix(in srgb, var(--text) 8%, var(--surface));
  --tint-accent: color-mix(in srgb, var(--accent) 6%, var(--surface));
  --time-col-bg: color-mix(in srgb, var(--text) 5%, var(--surface));
  --time-cell-bg: color-mix(in srgb, var(--text) 2%, var(--surface));
  --entry-bg: color-mix(in srgb, var(--accent) 6%, var(--surface));
  --entry-bg-hover: color-mix(in srgb, var(--accent) 10%, var(--surface));
  --entry-bg-selecting: color-mix(in srgb, var(--accent) 18%, var(--surface));
  --placeholder-color: color-mix(in srgb, var(--text-muted) 55%, var(--surface));
  --danger-tint: color-mix(in srgb, var(--danger) 15%, var(--surface));
  --danger-outline: color-mix(in srgb, var(--danger) 45%, transparent);
  --danger-outline-hover: color-mix(in srgb, var(--danger) 10%, transparent);
  --resize-handle-hover: color-mix(in srgb, var(--text) 15%, transparent);
}

/* Dark mode: only the primitive tokens above are redefined here — every
   derived color-mix() token recomputes on its own from these, so nothing
   else in this file needs a dark-specific rule. Guarded per the usual
   three-state pattern: an explicit [data-theme] always wins over the OS
   preference, "system" (no attribute) follows prefers-color-scheme. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #17171f;
    --surface: #1f1f29;
    --border: #34343f;
    --text: #eeeef3;
    --text-muted: #9a9aa8;
    --accent: #7a7aff;
    --danger: #ff6b61;
    --warn: #e0a940;
    --shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
    --shadow-modal: 0 10px 40px rgba(0, 0, 0, 0.5);
    --modal-border: var(--border);
  }
}

:root[data-theme="dark"] {
  --bg: #17171f;
  --surface: #1f1f29;
  --border: #34343f;
  --text: #eeeef3;
  --text-muted: #9a9aa8;
  --accent: #7a7aff;
  --danger: #ff6b61;
  --warn: #e0a940;
  --shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  --shadow-modal: 0 10px 40px rgba(0, 0, 0, 0.5);
  --modal-border: var(--border);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
}

.app-header {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px 24px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.header-top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
}

.plan-controls {
  display: flex;
  align-items: center;
  gap: 6px;
}

.plan-switcher {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  font-size: 0.85rem;
  background: var(--surface);
  color: var(--text);
  max-width: 200px;
}

.icon-btn {
  border: none;
  background: var(--surface-2);
  color: var(--text);
  width: 32px;
  height: 32px;
  border-radius: 8px;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.icon-btn:hover {
  opacity: 0.85;
}

.icon-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Groups the title with the lock button so they stay on one line even
   when .header-top itself switches to a column layout below (narrow
   screens, see media query further down) — otherwise the lock button
   would fall onto its own row there, adding header height for no reason. */
.title-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
}

.app-header h1 {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 1.4rem;
  margin: 0;
  padding: 2px 6px;
  border-radius: 6px;
  border: 1px dashed transparent;
  outline: none;
  cursor: text;
}

/* Shared by the language and theme <select>s — both per-browser, app-wide
   display preferences, styled identically in the header. */
.header-select {
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  font-size: 0.85rem;
  background: var(--surface);
  color: var(--text);
}

.app-header h1:hover {
  border-color: var(--border);
}

.app-header h1:focus {
  border-color: var(--accent);
  background: var(--tint-accent);
}

.app-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 20px;
}

/* Groups the toolbar by what an action does (data, grid setup, view) instead
   of one flat row of equally-weighted buttons. The border sits on the group
   itself rather than a standalone divider element, so a flex-wrap break
   never strands a lone separator line on its own row. */
.action-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.action-group:not(:last-of-type) {
  padding-right: 20px;
  border-right: 1px solid var(--border);
}

.action-group-label {
  width: 100%;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Pushed away from the rest of the toolbar and visually distinct (outline,
   not a filled button) so "Reset" doesn't read as just another secondary
   action next to Export/Print. */
.reset-group {
  margin-left: auto;
}

.hint-text {
  margin: 0 0 12px;
  font-size: 0.82rem;
  color: var(--text-muted);
}

.btn {
  border: none;
  background: var(--accent);
  color: var(--accent-contrast);
  padding: 9px 16px;
  border-radius: var(--radius);
  font-size: 0.9rem;
  cursor: pointer;
  transition: opacity 0.15s ease, transform 0.1s ease;
}

.btn:hover {
  opacity: 0.9;
}

.btn:active {
  transform: scale(0.98);
}

.btn-secondary {
  background: var(--surface-2);
  color: var(--text);
}

.btn-danger {
  background: var(--danger);
  color: white;
}

.btn-danger-outline {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger-outline);
}

.btn-danger-outline:hover {
  background: var(--danger-outline-hover);
}

/* Editing controls are only visible while editing is unlocked. */
body.edit-locked .row-remove-btn,
body.edit-locked .col-remove-btn,
body.edit-locked .add-day-col,
body.edit-locked .cell-placeholder,
body.edit-locked .col-resize-handle,
body.edit-locked .entry-copy-btn {
  display: none !important;
}

/* Collapses the header down to just the title (plus the plan switcher and
   theme/language selects, which stay reachable down to tablet width) for a
   "look at and use the schedule, don't edit it" mode — and actually locks
   editing (app.js): no new entries, no renaming the plan title or day
   columns. Switching plans, theme, language, and printing stay available
   since they don't mutate the current plan's content. */
body.edit-locked .app-actions,
body.edit-locked .hint-text,
body.edit-locked #newPlanBtn,
body.edit-locked #deletePlanBtn,
body.edit-locked #aboutBtn {
  display: none !important;
}

/* Locked content stays visually inert — no hand cursor implying it's
   clickable, no text caret implying it's editable. */
body.edit-locked .data-cell,
body.edit-locked .day-name,
body.edit-locked .day-name-short,
body.edit-locked #planTitle {
  cursor: default;
}

main {
  padding: 20px 24px 60px;
}

.table-wrap {
  overflow-x: auto;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

table {
  border-collapse: collapse;
  width: 100%;
  /* Fixed layout (not auto): with dynamic-width form controls in the time
     column, auto layout sizes columns from their natural/intrinsic content
     width and mostly ignores small min-width hints, which made the time
     column render far wider than declared. Fixed layout instead honors the
     declared widths below directly, and columns without an explicit width
     (the day columns) share the remainder equally — so the initial 7-day
     layout actually fits without horizontal scroll down to tablet widths.
     min-width below is the floor under which it scrolls instead of
     squeezing columns further (unavoidable on narrow phones). */
  table-layout: fixed;
  min-width: 600px;
}

thead th {
  position: sticky;
  top: 0;
  padding: 14px 10px;
  font-weight: 600;
  /* Not a theme token: day-column headers always sit on a light rainbow
     pastel (--day-1..7, unchanged across themes), so their text stays this
     dark regardless of light/dark mode. .time-col overrides both below. */
  color: #1c1c26;
  text-align: center;
  border-bottom: 2px solid var(--border);
  /* Sits above the frozen .time-cell body column (below) when scrolled
     right on a narrow screen, so the sticky header row still covers it. */
  z-index: 3;
}

.time-col {
  position: relative;
  width: 92px;
  background: var(--time-col-bg) !important;
  color: var(--text) !important;
}

.day-col {
  position: relative;
  min-width: 88px;
}

/* Draggable strip on the right edge of a header cell for manually widening
   a column (time or day) when its content — raster times in particular —
   would otherwise be clipped. Purely a display/editing affordance, so it's
   hidden alongside the other edit icons and in print. */
.col-resize-handle {
  position: absolute;
  top: 0;
  right: -4px;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  z-index: 1;
}

.col-resize-handle:hover,
.col-resize-handle:active {
  background: var(--resize-handle-hover);
}

/* Column colors are assigned inline (cycled through --day-1..7) since the
   number of day columns is user-controlled, not fixed at 7. */

.day-col-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

.day-name {
  outline: none;
  border-radius: 4px;
  padding: 1px 3px;
  cursor: text;
  white-space: normal;
  overflow-wrap: anywhere;
}

.day-name:hover {
  background: rgba(255, 255, 255, 0.45);
}

.day-name:focus {
  background: white;
  color: var(--text);
}

/* Shown instead of .day-name only below the breakpoint in the media query
   further down — a short label for a default weekday name (see
   shortDayLabel in app.js) so it doesn't wrap into a stack of single
   words on a narrow screen. Not itself contenteditable; tapping it
   reveals + focuses the real .day-name for renaming (app.js toggles
   .day-col-inner's "editing" class). */
.day-name-short {
  display: none;
  border-radius: 4px;
  padding: 1px 3px;
  cursor: pointer;
}

.day-name-short:hover {
  background: rgba(255, 255, 255, 0.45);
}

.col-remove-btn {
  flex: 0 0 auto;
  border: none;
  background: rgba(255, 255, 255, 0.4);
  color: #1c1c26;
  width: 18px;
  height: 18px;
  line-height: 1;
  border-radius: 50%;
  font-size: 0.75rem;
  cursor: pointer;
  opacity: 0.7;
}

.col-remove-btn:hover:not(:disabled) {
  opacity: 1;
  background: rgba(255, 255, 255, 0.85);
}

.col-remove-btn:disabled {
  opacity: 0.25;
  cursor: not-allowed;
}

.add-day-col {
  width: 44px;
  background: transparent !important;
}

.add-day-col .icon-btn {
  width: 28px;
  height: 28px;
}

th.day-col.current-day-col {
  box-shadow: inset 0 -3px 0 0 rgba(28, 28, 38, 0.45);
}

tbody td {
  border: 1px solid var(--border);
  padding: 0;
  vertical-align: top;
  height: 70px;
}

.time-cell {
  padding: 8px;
  background: var(--time-cell-bg);
  /* Overrides tbody td's vertical-align: top — previously moot since
     .time-cell's own display: flex took over alignment entirely; now that
     it's a plain table cell again (see .time-cell-inner below), this is
     needed to keep its content vertically centered like before. */
  vertical-align: middle;
}

/* The flex layout for the label + remove button lives here rather than
   directly on .time-cell, keeping .time-cell itself a plain table cell
   like .time-col — an earlier (insufficient on its own) attempt at fixing
   a Firefox sticky-column bug; see item 25 in REQUIREMENTS.md and
   timeTd.style.width in app.js for the actual fix (an explicit width, not
   the display type, turned out to be what Firefox needed). Kept since it's
   harmless either way. */
.time-cell-inner {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* A contenteditable span rather than an <input>: inputs never wrap their
   text, so a narrow time column just clipped raster labels like
   "16:40–18:10". This wraps normally, and at the en dash first since that's
   a natural break point in the label. */
.time-label {
  flex: 1 1 auto;
  min-width: 0;
  outline: none;
  background: transparent;
  font-size: 0.85rem;
  color: var(--text);
  text-align: center;
  padding: 4px;
  border-radius: 6px;
  cursor: text;
  white-space: normal;
  overflow-wrap: anywhere;
}

.time-label:focus {
  outline: 2px solid var(--accent);
  background: var(--surface);
}

.time-label:empty::before {
  content: attr(data-placeholder);
  color: var(--text-muted);
}

.row-remove-btn {
  flex: 0 0 auto;
  border: none;
  background: transparent;
  color: var(--text-muted);
  width: 22px;
  height: 22px;
  line-height: 1;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.95rem;
  opacity: 0.5;
}

.row-remove-btn:hover {
  opacity: 1;
  background: var(--danger-tint);
  color: var(--danger);
}

.time-cell.current-row {
  background: var(--now-bg);
  box-shadow: inset 3px 0 0 0 var(--now-accent);
}

.data-cell {
  position: relative;
  cursor: pointer;
  padding: 0;
  min-width: 88px;
}

/* The visible "box" for a cell's content, separate from the <td> itself:
   a multi-row entry with a custom start/end time insets this box within
   the covered rows via top/bottom (see computeSubRangeInset in app.js), so
   the box's top and bottom edges track where the entry actually starts and
   ends instead of just padding text inside a box that always fills the
   full rowspan. */
.entry-box {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  padding: 8px 10px;
  /* Entries sit on their own tinted surface. Set their foreground explicitly
     rather than relying on inheritance from the table/body: that keeps both
     the title and description readable after a dark-theme token change. */
  color: var(--text);
  overflow: hidden;
  transition: background 0.12s ease;
}

/* Duplicates the entry into an in-memory clipboard (see copyEntry/
   pasteEntryAt in app.js) — hidden while editing is locked, like the
   row/column remove icons, and
   stops the click from also starting a drag-to-move on the cell beneath
   it, same as .entry-link already does. */
.entry-copy-btn {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 6px;
  /* Not a hardcoded white overlay (that read as an invisible near-white
     blob against a dark entry background in dark mode) — --surface-2 is
     already the theme-aware "faint tint over the surface" token every
     other icon button (.icon-btn) uses, so it stays legible in both
     themes automatically. */
  background: var(--surface-2);
  color: var(--text-muted);
  line-height: 1;
  font-size: 0.75rem;
  cursor: pointer;
  opacity: 0.6;
}

.entry-copy-btn:hover {
  opacity: 1;
  background: var(--tint-accent);
  color: var(--accent);
}

/* A copied entry is "armed" (Ctrl+V) waiting for a click to place it —
   the copy cursor previews that the next click pastes rather than opens/
   drags the cell it lands on. */
body.paste-armed .data-cell {
  cursor: copy;
}

.data-cell:hover .entry-box {
  background: var(--tint-accent);
}

.data-cell.filled .entry-box {
  background: var(--entry-bg);
}

.data-cell.filled:hover .entry-box {
  background: var(--entry-bg-hover);
}

/* Per-entry color override: --entry-hue is set inline (renderCellContent
   in app.js) to one of the rainbow hues. A visibly stronger mix than the
   default 6%/10% above — it needs to actually read as "this entry's
   color", not just a faint tint — but still computed from --surface the
   same way, so it still adapts automatically between light and dark mode
   instead of needing its own light/dark pair. */
.data-cell.filled.has-color .entry-box {
  background: color-mix(in srgb, var(--entry-hue) 22%, var(--surface));
}

.data-cell.filled.has-color:hover .entry-box {
  background: color-mix(in srgb, var(--entry-hue) 30%, var(--surface));
}

/* A filled cell is grabbable (drag it to move the entry, see item 26) —
   .selecting/.drop-target below cover the drag itself, this is just the
   affordance before one starts. Locked mode's own cursor: default override
   (body.edit-locked .data-cell) wins by specificity over this. */
.data-cell.filled {
  cursor: grab;
}

.data-cell.filled:active {
  cursor: grabbing;
}

.data-cell.selecting .entry-box {
  background: var(--entry-bg-selecting);
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Previews where a dragged entry would land — dashed rather than
   .selecting's solid outline, so "here's where it'll drop" doesn't look
   identical to "this is the new range you're actively drawing". */
.data-cell.drop-target .entry-box {
  background: var(--entry-bg-hover);
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
}

.data-cell.current-cell .entry-box {
  outline: 2px solid var(--now-accent);
  outline-offset: -2px;
}

body.no-select {
  user-select: none;
}

.entry-time-badge {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--accent);
  margin: 0 0 2px;
  letter-spacing: 0.02em;
}

.entry-title {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--text);
  margin: 0 0 4px;
  word-break: break-word;
}

.entry-description {
  font-size: 0.78rem;
  color: var(--text);
  margin: 0 0 4px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.entry-link {
  font-size: 0.75rem;
  color: var(--accent);
  text-decoration: none;
  word-break: break-all;
}

.entry-link:hover {
  text-decoration: underline;
}

.cell-placeholder {
  color: var(--placeholder-color);
  font-size: 1.1rem;
  text-align: center;
  display: block;
}

/* Brief, non-blocking feedback (undo confirmation, drag-overwrite notice,
   ...) — shown/hidden via app.js's showToast(), auto-dismisses on its own.
   Above the modal overlay's z-index too, so it's never hidden behind one
   left open. */
.toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  background: var(--text);
  color: var(--bg);
  padding: 10px 18px;
  border-radius: var(--radius);
  font-size: 0.85rem;
  box-shadow: var(--shadow-modal);
  z-index: 110;
  max-width: calc(100vw - 32px);
  text-align: center;
}

.toast.hidden {
  display: none;
}

/* Modal */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 30, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 100;
}

.modal-overlay.hidden {
  display: none;
}

.modal {
  background: var(--surface);
  border: 1px solid var(--modal-border);
  border-radius: 14px;
  padding: 22px;
  width: 100%;
  max-width: 420px;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: var(--shadow-modal);
}

.modal h2 {
  margin: 0 0 16px;
  font-size: 1.1rem;
}

.field {
  display: block;
  margin-bottom: 14px;
}

.field span {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  margin-bottom: 5px;
  color: var(--text-muted);
}

.field input,
.field textarea {
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.9rem;
  font-family: inherit;
  resize: vertical;
}

.field input:focus,
.field textarea:focus {
  outline: 2px solid var(--accent);
  border-color: transparent;
}

/* A checkbox field reads better as one row (box, then label text) than
   .field's default block-stacked "label above input" layout. */
.field-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.field-checkbox input {
  /* Overrides .field input's width: 100%/padding/border (meant for text
     inputs) — a checkbox needs its own natural, unstretched size. */
  width: auto;
  padding: 0;
  border: none;
  cursor: pointer;
}

.field-checkbox span {
  margin: 0;
  font-weight: 400;
  color: var(--text);
}

/* Entry color picker: reuses the same rainbow hues as the day headers
   (--day-1..7, see rainbowPalette() in app.js) instead of a separate
   palette or a native <input type="color">, so the choices stay visually
   consistent with the rest of the app and there's no browser-inconsistent
   color-picker popup. */
.color-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.color-swatch {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid transparent;
  padding: 0;
  cursor: pointer;
  box-shadow: inset 0 0 0 1px var(--border);
}

.color-swatch.selected {
  border-color: var(--accent);
}

.color-swatch-none {
  background: var(--surface);
  position: relative;
}

.color-swatch-none::after {
  content: "";
  position: absolute;
  inset: 5px;
  background: var(--danger);
  clip-path: polygon(0 45%, 100% 45%, 100% 55%, 0 55%);
  transform: rotate(45deg);
}

.modal-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 18px;
  gap: 10px;
}

.modal-actions-right {
  display: flex;
  gap: 10px;
}

.modal-hint {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin: -6px 0 16px;
}

.time-hint {
  margin: -8px 0 14px;
  color: var(--warn);
}

.time-hint:empty {
  display: none;
}

.preset-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

.preset-btn {
  flex: 1 1 auto;
}

.modal-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 0 0 16px;
}

.field-group-label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  margin-bottom: 8px;
  color: var(--text-muted);
}

.about-description {
  margin-bottom: 18px;
}

.about-source {
  margin-bottom: 18px;
}

.about-link {
  color: var(--accent);
  font-size: 0.9rem;
  text-decoration: none;
  font-weight: 600;
}

.about-link:hover {
  text-decoration: underline;
}

.raster-inputs {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
}

.field-inline {
  flex: 1 1 0;
  margin-bottom: 0;
}

.field-inline input {
  text-align: center;
}

@media (max-width: 600px) {
  .header-top {
    flex-direction: column;
    align-items: flex-start;
  }

  .header-select,
  #aboutBtn {
    align-self: flex-end;
  }

  /* Stretches .title-row to the header's full width so the title (flex: 1
     1 auto) grows to fill it, pushing the lock button flush to the right
     edge — same visual spot .header-select/#aboutBtn above land in —
     instead of the button sitting right up against the title text. */
  .title-row {
    align-self: stretch;
  }

  /* Locked + narrow: even the plan switcher and theme/language selects go,
     leaving just the title (and the toggle to unlock again). */
  body.edit-locked .plan-controls,
  body.edit-locked #themeSwitcher,
  body.edit-locked #languageSwitcher {
    display: none !important;
  }

  /* Below this width the table itself is already down to its min-width
     floor and scrolling (see table's own min-width: 600px above), so day
     columns are as narrow as they get — swap in the short label instead
     of letting a full weekday name wrap into a stack of single words. */
  .day-name {
    display: none;
  }

  .day-name-short {
    display: inline;
  }

  .day-col-inner.editing .day-name {
    display: inline;
  }

  .day-col-inner.editing .day-name-short {
    display: none;
  }

  /* Below this width the table scrolls horizontally (see table's own
     min-width above), so freeze the time column on the left the same way
     the header row is already frozen on top — otherwise it scrolls out of
     view exactly when it's most needed, while scrolled right to a later
     day column. .time-col is the header/corner cell (sticky both ways;
     highest z-index so it stays above both the frozen header row and the
     frozen body column), .time-cell the body cells (sticky left only). */
  .time-col {
    position: sticky;
    left: 0;
    z-index: 4;
  }

  .time-cell {
    position: sticky;
    left: 0;
    z-index: 2;
    /* Belt-and-braces: forces its own compositor layer, a common
       workaround for sticky-table-cell bugs in engines other than the one
       this project can actually test against (Chromium — see
       e2e/sticky-time-column.spec.js). Not verified against real
       Safari/WebKit (unavailable in this environment). The main fix for
       the bug actually reported (Firefox Mobile) is .time-cell no longer
       being a flex container itself, above. */
    transform: translateZ(0);
  }
}

@media (max-width: 420px) {
  main {
    padding: 16px 12px 40px;
  }

  .entry-box {
    padding: 6px 8px;
  }

  .data-cell {
    min-width: 76px;
  }

  .time-col,
  .time-cell {
    min-width: 78px;
  }
}

/* Print view: just the plan title and the table. Everything interactive
   (plan/language controls, action buttons, edit icons, modals) is hidden;
   day-header and filled-cell background colors are forced to print since
   the rainbow coding is the point, not decoration. */
@media print {
  .plan-controls,
  #themeSwitcher,
  #languageSwitcher,
  #aboutBtn,
  #editLockBtn,
  #jumpToNowBtn,
  .app-actions,
  .hint-text,
  .row-remove-btn,
  .col-remove-btn,
  .add-day-col,
  .cell-placeholder,
  .col-resize-handle,
  .entry-copy-btn,
  .modal-overlay {
    display: none !important;
  }

  /* Printed output stays on the light palette regardless of the active
     on-screen theme — paper is white either way, and this keeps every
     color-mix()-derived token (entry backgrounds, time column, ...)
     resolving to its light value too instead of a dark-mode result baked
     into the printout. !important on each: a plain `:root` selector here
     is otherwise outranked by `:root[data-theme="dark"]`'s higher
     specificity (attribute selector), regardless of being inside
     @media print — a media query doesn't add specificity of its own. */
  :root {
    --bg: #f7f7fb !important;
    --surface: #ffffff !important;
    --border: #e2e2ea !important;
    --text: #22222c !important;
    --text-muted: #6b6b78 !important;
    --accent: #4c4cff !important;
    --danger: #e0473e !important;
    --warn: #b8860b !important;
  }

  body {
    background: white;
  }

  .app-header {
    border-bottom: none;
    padding: 0 0 12px;
  }

  .app-header h1 {
    border-color: transparent !important;
    background: none !important;
    cursor: default;
    padding: 0;
    text-align: center;
    /* Slightly larger than the on-screen 1.4rem — a printout's title reads
       more like a document heading than an app-header label. */
    font-size: 1.8rem;
  }

  main {
    padding: 0;
  }

  .table-wrap {
    box-shadow: none;
    border-radius: 0;
    overflow: visible;
  }

  table {
    width: 100%;
  }

  /* A plan with enough rows already spans multiple printed pages (expected —
     paper is finite, not itself a bug). Makes explicit what Chromium already
     does by default when a page break falls in the middle of a multi-row
     entry (item 4, drag-select across several time slots, rendered as one
     rowspan-merged cell): the whole row moves to the next page together
     instead of its content getting split across the two. Declared rather
     than relied on, since that default isn't guaranteed across browsers/
     engines. page-break-inside is the older/wider-supported alias for the
     same thing. */
  tr {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .time-col,
  .time-cell {
    /* !important: overrides a manual on-screen column resize (inline
       style), so print always gets the width the raster labels need. */
    width: 130px !important;
  }

  .time-label:empty::before {
    content: none;
  }

  thead th,
  .data-cell.filled .entry-box {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  .data-cell {
    cursor: default;
  }

  /* The "now" highlight (current row/day-column/cell, item 11) is live UI
     state, not part of the plan's printable content — a printout made at
     14:32 shouldn't have that exact moment baked into it. */
  .time-cell.current-row {
    background: var(--time-cell-bg) !important;
    box-shadow: none !important;
  }

  .data-cell.current-cell .entry-box {
    outline: none !important;
  }

  th.day-col.current-day-col {
    box-shadow: none !important;
  }

  @page {
    size: landscape;
    margin: 1.2cm;
  }
}
