/* The building blocks every later package copies instead of inventing its own.
 *
 * Naming: block, then block__part, then block--variant. One class does one
 * job, no nesting of selectors deeper than a part, no element selectors (those
 * belong to base.css). If a page needs something that is not here, it gets
 * added here and to the pattern page -- not hidden in a page stylesheet.
 *
 * State names in the markup stay English (status--pending, not
 * status--wartet), matching the database enums; the German word the user reads
 * is text inside the element and comes from views/. */

/* --- subject tint --------------------------------------------------------
   Every element that carries a subject hue derives its three colours here,
   from the lightness/chroma pairs in tokens.css and its own --hue.
 *
 * This block must sit on the tinted element itself, never on :root: a custom
 * property is substituted where it is declared, so a finished oklch() on :root
 * would freeze the fallback hue and ignore the --hue on the element.
 *
 * Later packages tint a new element by adding it to this list, or by putting
 * .tinted on it. Both need style="--hue: <subjects.hue>" in the markup -- the
 * one place where a database value enters the stylesheet. */

.tinted,
.subject,
.card--subject {
  --subject-surface: oklch(var(--subject-surface-lc) var(--hue));
  --subject-border: oklch(var(--subject-border-lc) var(--hue));
  --subject-text: oklch(var(--subject-text-lc) var(--hue));
  --subject-mark: oklch(var(--subject-mark-lc) var(--hue));
}

/* --- layout helpers ------------------------------------------------------ */

/* Vertical rhythm without margins on the children themselves. */
.stack > * + * {
  margin-top: var(--space-4);
}
.stack--tight > * + * {
  margin-top: var(--space-2);
}
.stack--loose > * + * {
  margin-top: var(--space-6);
}

/* A row of things that wraps instead of overflowing: tags, buttons, chips. */
.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.cluster--between {
  justify-content: space-between;
}

/* Cards, thumbnails, subject tiles. Columns come from the available width,
   never from a media query. */
.grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr));
}
.grid--narrow {
  grid-template-columns: repeat(auto-fill, minmax(min(12rem, 100%), 1fr));
}

/* Wide content -- tables, code, later the media grid -- scrolls inside its own
   box. The page itself must never scroll sideways: on a phone that turns every
   vertical swipe into a fight. Every <table> gets this wrapper. */
.table-scroll {
  margin-bottom: var(--space-4);
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

.table-scroll > table {
  margin-bottom: 0;
}

/* --- card ---------------------------------------------------------------- */

.card {
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  box-shadow: var(--shadow-sm);
}

.card__title {
  margin-bottom: var(--space-1);
  font-size: var(--text-lg);
}

.card__meta {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* The user's own note, shown as typed. Markdown is not rendered yet -- that is
   lib/markdown.js in package 11 -- so line breaks are all this can honour. */
.card__note {
  white-space: pre-wrap;
}

.card > :last-child {
  margin-bottom: 0;
}

/* Whole card is a link: the title carries the link, the card gets the hover.
   Keeps one tab stop per card instead of one per line of text. */
.card--link {
  position: relative;
  transition: border-color 120ms, box-shadow 120ms, transform 120ms;
}

.card--link:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
}

.card--link:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.card--link .card__title a {
  color: inherit;
  text-decoration: none;
}

.card--link .card__title a::after {
  content: '';
  position: absolute;
  inset: 0;
}

.card--link .card__title a:focus-visible {
  outline: none;
}

/* Coloured spine: the subject of an entry, readable at a glance in a list. */
.card--subject {
  border-left: 4px solid var(--subject-mark);
}

/* --- button --------------------------------------------------------------
   <button> and <a class="button"> must be indistinguishable: forms use the
   first, navigation the second. */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  color: var(--text);
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 120ms, border-color 120ms;
}

.button:hover {
  background: var(--surface-hover);
  color: var(--text);
}

.button--primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--text-on-accent);
}

.button--primary:hover {
  border-color: var(--accent-hover);
  background: var(--accent-hover);
  color: var(--text-on-accent);
}

.button--quiet {
  border-color: transparent;
  background: none;
  color: var(--text-muted);
  font-weight: 500;
}

.button--quiet:hover {
  background: var(--surface-hover);
}

.button--danger {
  border-color: var(--danger-border);
  background: var(--danger-surface);
  color: var(--danger);
}

.button--danger:hover {
  border-color: var(--danger);
  background: var(--danger-surface);
  color: var(--danger-hover);
}

.button--small {
  min-height: 2rem;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
}

.button:disabled,
.button[aria-disabled='true'] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* --- subject chip --------------------------------------------------------
   Carries style="--hue: 205" from the database. Nothing here knows which
   subject it is, and nothing needs a stylesheet regenerated when the user adds
   one in the admin area. */

.subject {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--subject-border);
  border-radius: var(--radius-pill);
  background: var(--subject-surface);
  color: var(--subject-text);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
}

.subject__dot {
  width: 0.6em;
  height: 0.6em;
  border-radius: 50%;
  background: var(--subject-mark);
  flex: none;
}

a.subject:hover {
  border-color: var(--subject-mark);
  color: var(--subject-text);
}

/* --- tag -----------------------------------------------------------------
   Free keywords, subject-independent, therefore neutral. Suggested tags from
   Claude are unconfirmed until the user accepts them (project rule 8). */

.tag {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  color: var(--text-muted);
  font-size: var(--text-xs);
  text-decoration: none;
}

a.tag:hover {
  border-color: var(--border-strong);
  color: var(--text);
}

.tag--suggested {
  border-style: dashed;
  border-color: var(--accent-border);
  background: none;
  color: var(--accent);
}

/* --- processing state ----------------------------------------------------
   The four values of media.status. Colour alone never carries the meaning:
   the German word is always in the element. */

.status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}

.status::before {
  content: '';
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: currentcolor;
  flex: none;
}

.status--pending {
  background: var(--state-pending-surface);
  color: var(--state-pending);
}
.status--running {
  background: var(--state-running-surface);
  color: var(--state-running);
}
.status--running::before {
  animation: pulse 1.4s ease-in-out infinite;
}
.status--done {
  background: var(--state-done-surface);
  color: var(--state-done);
}
.status--failed {
  background: var(--state-failed-surface);
  color: var(--state-failed);
}

@keyframes pulse {
  50% {
    opacity: 0.25;
  }
}

/* --- notice --------------------------------------------------------------
   Form errors, "upload received", budget warning at 80 %. */

.notice {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: var(--radius-md);
  background: var(--surface-raised);
}

.notice > :last-child {
  margin-bottom: 0;
}

.notice--info {
  border-color: var(--accent-border);
  border-left-color: var(--accent);
  background: var(--accent-surface);
}
.notice--success {
  border-color: var(--state-done);
  border-left-color: var(--state-done);
  background: var(--state-done-surface);
}
.notice--warning {
  border-color: var(--border-strong);
  border-left-color: var(--state-pending);
  background: var(--state-pending-surface);
}
.notice--error {
  border-color: var(--danger-border);
  border-left-color: var(--danger);
  background: var(--danger-surface);
}

/* --- empty state ---------------------------------------------------------
   A day without material, a search without hits. Never an empty page. */

.empty {
  padding: var(--space-6);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  color: var(--text-muted);
  text-align: center;
}

.empty > :last-child {
  margin-bottom: 0;
}

/* --- form field ---------------------------------------------------------- */

.field {
  margin-bottom: var(--space-4);
}

.field__hint {
  margin: var(--space-1) 0 0;
  color: var(--text-faint);
  font-size: var(--text-sm);
}

.field__error {
  margin: var(--space-1) 0 0;
  color: var(--danger);
  font-size: var(--text-sm);
  font-weight: 600;
}

.field--invalid input,
.field--invalid textarea,
.field--invalid select {
  border-color: var(--danger);
}

/* --- meta line -----------------------------------------------------------
   Timestamp, file size, page count: everything secondary, one line, muted. */

.meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
  list-style: none;
  padding: 0;
}

.meta > li + li {
  margin-top: 0;
}

/* --- theme switch --------------------------------------------------------
   Hidden until public/js/theme.js unhides it: without JavaScript the choice
   cannot be stored, and a control that does nothing is worse than none. */

.theme-switch[hidden] {
  display: none;
}

.theme-switch {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
}

.theme-switch button {
  min-height: 2rem;
  padding: var(--space-1) var(--space-3);
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--text-muted);
  font-size: var(--text-sm);
  cursor: pointer;
}

.theme-switch button[aria-pressed='true'] {
  background: var(--surface-raised);
  color: var(--text);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}

/* --- drop zone -----------------------------------------------------------
   The file input, made big enough to hit with a thumb. The <input> itself is
   hidden inside the <label>, not display:none -- a hidden input cannot be
   focused, and the keyboard path has to keep working. */

.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: 8rem;
  padding: var(--space-5) var(--space-4);
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface-sunken);
  text-align: center;
  cursor: pointer;
  transition: border-color 120ms, background-color 120ms;
}

.dropzone:hover {
  border-color: var(--accent-border);
}

/* Set by public/js/upload.js while something hovers over it. */
.dropzone--over {
  border-color: var(--accent);
  background: var(--accent-surface);
}

.dropzone:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.dropzone__title {
  font-weight: 600;
}

.dropzone__hint {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.dropzone input[type='file'] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* A file input that looks like a button: the camera, next to the drop zone. */
.button--camera {
  position: relative;
  margin-top: var(--space-2);
}

.button--camera input[type='file'] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.button--camera:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- chosen files --------------------------------------------------------
   Filled by public/js/upload.js before sending. Without JavaScript it stays
   hidden and the browser's own "3 Dateien" label does the job. */

.file-list {
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
}

.file-list > li {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--border);
}

.file-list__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-list__size {
  flex: none;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* --- progress ------------------------------------------------------------
   Bytes on the wire, nothing else. The processing progress in package 7 gets
   its own component fed by server-sent events. */

.progress {
  height: 0.5rem;
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
}

.progress__bar {
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 200ms linear;
}

/* --- media tile ----------------------------------------------------------
   One uploaded file in a list. Until package 7 produces thumbnails the browser
   scales the original, which is why the box has a fixed height and the image
   is cropped into it -- otherwise a portrait photo and a landscape one would
   tear the grid apart. */

.media-tile {
  display: flex;
  flex-direction: column;
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
}

.media-tile__preview {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 9rem;
  background: var(--surface-sunken);
}

.media-tile__preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.media-tile__preview--symbol {
  color: var(--text-muted);
  font-size: var(--text-lg);
  font-weight: 600;
  text-decoration: none;
}

.media-tile__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-3);
  font-size: var(--text-sm);
}

.media-tile__name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.media-tile__meta {
  color: var(--text-muted);
  font-size: var(--text-xs);
}

.media-tile__error {
  color: var(--danger);
  font-size: var(--text-xs);
}

/* Just uploaded: marks the entry the redirect points at, so a submission that
   landed among fifty others is still findable at a glance. */
.card--fresh {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- visually hidden ----------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
