/* ============================================================================
   @timeslider/styles - components.css
   ----------------------------------------------------------------------------
   Layer 3 of three: btn, card, row, badge, tbl, tag, conn, ...

   Every rule here references the SEMANTIC layer only, never a primitive. That
   is what makes the dark theme a single block in tokens.css.

   The short, generic class names are nested under .ts-scope so the file can be
   loaded on an MVC view that still carries its legacy stylesheets without the
   two sets of rules colliding.
   ============================================================================ */

/* ---------- Page head ---------- */
.ts-scope .pagehead {
  margin-bottom: 24px;
}

.ts-scope .pagehead h1 {
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -.015em;
  margin: 0 0 5px;
}

.ts-scope .pagehead p {
  margin: 0;
  color: var(--text-3);
  font-size: 13.5px;
  max-width: 56ch;
}

/* ---------- Cards and rows ---------- */
.ts-scope .card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  margin-bottom: 18px;
  overflow: hidden;
}

.ts-scope .card > header {
  padding: 15px 20px 13px;
  border-bottom: 1px solid var(--border-soft);
}

.ts-scope .card > header h3 {
  margin: 0;
  font-size: 14.5px;
  font-weight: 600;
}

.ts-scope .card > header p {
  margin: 3px 0 0;
  color: var(--text-3);
  font-size: 12.5px;
}

/* A card header that carries an action on the right (+ Gruppe, + Vorlage). */
.ts-scope .card > header.with-action {
  display: flex;
  align-items: center;
  gap: 12px;
}

.ts-scope .card > header.with-action > .head-text {
  flex: 1;
  min-width: 0;
}

.ts-scope .card > .body {
  padding: 16px 20px;
}

/* A label on the left, its value on the right, both starting at the TOP of the row.
 *
 * The label used to carry `padding-top: 8px` on top of this, and that was a compensation for
 * something the row does not always contain: a field's text starts 9px below its box (8px padding
 * plus 1px border), so the label was pushed down to meet it. Where the value was a line of text
 * instead of a field - most of the subscription page - there was nothing to compensate and the
 * label sat 8px below its own value.
 *
 * The two goals are mutually exclusive: a label cannot be flush with the top of the row AND sit on
 * the same line as the text inside a padded field. This picks the top edge, because it is the one
 * that holds for every kind of value - a field, a line of text, a button, a picture - and it needs
 * no exception and no magic number. The 9px between the label's text and a field's text is the
 * price, and it reads as aligned because the label meets the field's frame.
 */
.ts-scope .row {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 20px;
  align-items: start;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-soft);
}

/* A FRAMED CONTROL as the value - a single-line text box, or a button: there the label goes back
 * down by 8px.
 *
 * Those are the cases where the top edge is not what the eye wants: both are a frame around one
 * line of text, and a label level with the frame reads as sitting above the value it belongs to.
 * 8px puts it on the line inside - a text box adds 8px padding plus a 1px border, a button 7px
 * plus 1px and a slightly smaller font, which lands on the same line.
 *
 * The slider is NOT in the list, and that was measured rather than assumed: its cell is one line
 * tall - the height comes from the "100 %" beside the track - so the value already sits on the
 * label's line and the track's middle already meets the label's middle. The offset would push the
 * label 8px below both (measured: label text 974 against a value at 966, label middle 985 against
 * a track middle at 977). A field or a button earns the 8px because its FRAME holds a line of text
 * away from the top; the slider has no frame to hold anything.
 *
 * Which is why this is a rule about the VALUE, not a padding on every label - the mistake the
 * original had. A line of text, a picture, radio buttons, colour swatches and image thumbnails keep
 * the top edge; a control asks for the offset.
 *
 * `input` without the three type exclusions would catch radio buttons, checkboxes and the native
 * range; `textarea` is deliberately absent, because a multi-line box is read from its top like any
 * other block. The descendant combinator after `.ctl` is on purpose too: the field sits one level
 * deeper wherever two of them share a line (the visible day range, the invite row).
 *
 * `.btn` and not `button`: the image thumbnails and the colour swatches are buttons as well, and
 * they are pictures rather than lines of text - they keep the top edge. The class is what the
 * design system means by "a button", and it covers the links styled as one.
 *
 * And not when the label carries a hint under it: `:not(:has(small))`. Two lines of label are a
 * BLOCK, and a block hangs from the top of the row - pushing the pair down by 8px to put the first
 * line on the field's text leaves the whole thing looking dropped. Only a bare label follows the
 * line inside the box. */
.ts-scope
  .row:has(> .ctl input:not([type='radio']):not([type='checkbox']):not([type='range']))
  > .lbl:not(:has(small)),
.ts-scope .row:has(> .ctl select) > .lbl:not(:has(small)),
.ts-scope .row:has(> .ctl .btn) > .lbl:not(:has(small)) {
  padding-top: 8px;
}

/* A button as the value has to stop HANGING first, or the offset above overshoots it.
 *
 * `.ctl` is a block, a `.btn` is inline-block, and an inline-block sits on the baseline of the line
 * box it is in - so a 34px button in a 20px line reaches 7px ABOVE the top of its own cell.
 * Measured: the cell starts at 308, the button's frame at 301. The label, pushed down by 8px to
 * meet the text inside the button, then ended up 7px below it.
 *
 * As a flex item the button starts where its cell starts, and the 8px is right again for the same
 * reason it is right for a text box. Only for rows that hold a button directly, and `.inline` is
 * left alone: it is a flex container already, with its own 9px gap and centred items, and a cell
 * holding text and paragraphs must stay a block. */
.ts-scope .row:has(> .ctl > .btn) > .ctl:not(.inline) {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px;
}

.ts-scope .row:last-child {
  border-bottom: 0;
}

/* No padding-top any more: the row aligns by baseline, see above. */

.ts-scope .row .lbl label {
  font-size: 13.5px;
  font-weight: 500;
  display: block;
}

.ts-scope .row .lbl small {
  display: block;
  color: var(--text-3);
  font-size: 12px;
  margin-top: 2px;
  line-height: 1.4;
}

.ts-scope .ctl {
  min-width: 0;
}

/* ---------- Fields ---------- */
/* Formularfelder der Einstellungen.

   `:not(.ts-legacy *)` haelt diese Regeln aus den nicht migrierten Komponenten heraus, die in
   einer Einstellungsseite gehostet werden. Ohne den Ausschluss wurde jedes Textfeld darin zu
   einem Kasten ueber die volle Breite mit Rahmen und Radius - das Feld "Schlagwort hinzufuegen"
   im Vorlagen-Panel etwa, das dort ein unauffaelliges Inline-Element sein soll.

   Der Ausschluss steht hier und nicht als Rueckstellung in .ts-legacy, weil er sonst auch die
   Legacy-Regeln mitnehmen wuerde: `body input {...}` in app.component.scss setzt Farbe und Flaeche
   aus dem --ts-* Theme, und die soll gelten. */
.ts-scope input[type=text]:not(.ts-legacy *),
.ts-scope input[type=email]:not(.ts-legacy *),
.ts-scope input[type=password]:not(.ts-legacy *),
.ts-scope input[type=number]:not(.ts-legacy *),
.ts-scope select:not(.ts-legacy *),
.ts-scope textarea:not(.ts-legacy *) {
  width: 100%;
  padding: 8px 11px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--surface);
  outline: none;
}

.ts-scope input[type=text]:focus:not(.ts-legacy *),
.ts-scope input[type=email]:focus:not(.ts-legacy *),
.ts-scope input[type=password]:focus:not(.ts-legacy *),
.ts-scope input[type=number]:focus:not(.ts-legacy *),
.ts-scope select:focus:not(.ts-legacy *),
.ts-scope textarea:focus:not(.ts-legacy *) {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus);
  outline: none;
}

.ts-scope input:read-only:not(.ts-legacy *) {
  background: var(--surface-sunken);
  color: var(--text-2);
}

.ts-scope .hint {
  color: var(--text-3);
  font-size: 12px;
  margin-top: 6px;
}

.ts-scope .err {
  color: var(--danger);
  font-size: 12px;
  margin-top: 6px;
  display: none;
}

/* The hint under a field and its error occupy the same slot: the error REPLACES
   the hint rather than pushing it around, so a validation message does not
   reflow the form. Driven by .bad, which a component sets only once the field
   is both touched and invalid. */
.ts-scope .field.bad input,
.ts-scope .field.bad select {
  border-color: var(--danger);
}

.ts-scope .field.bad .hint {
  display: none;
}

.ts-scope .field.bad .err {
  display: block;
}

.ts-scope .inline {
  display: flex;
  gap: 9px;
  align-items: center;
  flex-wrap: wrap;
}

.ts-scope .inline .sep {
  color: var(--text-3);
}

.ts-scope .w-sm {
  max-width: 96px;
}

.ts-scope .w-md {
  max-width: 260px;
}

.ts-scope .grow {
  flex: 1;
  min-width: 190px;
}

/* ---------- Buttons ---------- */
.ts-scope .btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  padding: 7px 14px;
  border-radius: 7px;
  font-size: 13.5px;
  font-weight: 500;
  white-space: nowrap;
}

.ts-scope .btn:hover:not(:disabled) {
  background: var(--surface-3);
}

.ts-scope .btn:disabled {
  opacity: .55;
  cursor: default;
}

.ts-scope .btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.ts-scope .btn.primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.ts-scope .btn.quiet {
  border-color: transparent;
  background: transparent;
  color: var(--text-2);
}

.ts-scope .btn.quiet:hover:not(:disabled) {
  background: var(--surface-3);
}

.ts-scope .btn.danger {
  border-color: var(--danger-border);
  color: var(--danger);
  background: var(--surface);
}

.ts-scope .btn.danger:hover:not(:disabled) {
  background: var(--danger-soft);
}

/* ---------- States ---------- */
.ts-scope .badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 20px;
}

.ts-scope .badge.ok {
  background: var(--ok-soft);
  color: var(--ok);
}

.ts-scope .badge.off {
  background: var(--surface-3);
  color: var(--text-3);
}

.ts-scope .badge.pend {
  background: var(--warn-soft);
  color: var(--warn);
}

/* ---------- Connection / list rows ---------- */
.ts-scope .conn {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border-soft);
}

.ts-scope .conn:last-child {
  border-bottom: 0;
}

.ts-scope .conn .ic {
  width: 34px;
  height: 34px;
  border-radius: var(--r);
  flex: none;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
}

.ts-scope .conn .ic.neutral {
  background: var(--surface-3);
  color: var(--text-2);
}

.ts-scope .conn .meta {
  min-width: 0;
  flex: 1;
}

.ts-scope .conn .meta b {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
}

.ts-scope .conn .meta span {
  display: block;
  color: var(--text-3);
  font-size: 12.5px;
  margin-top: 1px;
}

/* ---------- Radio / choice group ---------- */
.ts-scope .choice {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.ts-scope .choice label {
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 9px 13px;
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 13.5px;
  background: var(--surface);
}

.ts-scope .choice input {
  accent-color: var(--accent);
  margin: 0;
  width: auto;
}

.ts-scope .choice label:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  font-weight: 600;
}

/* ---------- Thumbnail / slider ---------- */
.ts-scope .thumb {
  width: 104px;
  height: 64px;
  padding: 0;
  border-radius: var(--r);
  flex: none;
  border: 1px solid var(--border);
  background: var(--surface-3) center / cover no-repeat;
}

/* The selected background image. Two rings rather than one thick border, so the
   selection reads at a glance without the tile changing size and shifting the row. */
.ts-scope .thumb.picked {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--focus);
}

/* ts-slider, NICHT .slider: der kurze Name kollidierte mit der Zeitachse der Timer-Komponente,
   die ihren Container ebenfalls .slider nennt. Innerhalb von .ts-scope traf diese Regel damit auch
   den Zeitstrahl einer eingebetteten Altkomponente und machte ihn zu einer 320px breiten Flexbox.

   Ein Name wie "slider" ist fuer ein Design-System, das fremde Komponenten umschliesst, zu
   allgemein - jede neue Klasse hier braucht ein Praefix. */
.ts-scope .ts-slider {
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: 320px;
}

/* Two shapes, on purpose: the settings pages use optimus-ui's p-slider (the house control
   library, themed through TimesliderPreset), while a server-rendered view that opts into
   .ts-scope has no Angular and stays on the native input. */
.ts-scope .ts-slider p-slider {
  flex: 1;
}

.ts-scope .ts-slider input[type=range] {
  flex: 1;
  accent-color: var(--accent);
}

.ts-scope .ts-slider output {
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  width: 44px;
  text-align: right;
}

/* ---------- Colour picker ---------- */
.ts-scope .swatches {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

.ts-scope .swatch {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid transparent;
  background: var(--surface);
  padding: 0;
  position: relative;
  box-shadow: 0 0 0 1px var(--border) inset;
}

.ts-scope .swatch i {
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--c);
}

.ts-scope .swatch[aria-pressed="true"] {
  border-color: var(--accent);
}

.ts-scope .swatch.custom i {
  background: conic-gradient(#F0625B, #F5B301, #4BBE8A, #3B82D6, #8B5CF6, #F0625B);
}

.ts-scope .swatch.custom.picked i {
  background: var(--c);
}

.ts-scope .custom-color {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface-2);
}

.ts-scope .custom-color input[type=color] {
  width: 44px;
  height: 36px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  cursor: pointer;
}

.ts-scope .custom-color input[type=text] {
  max-width: 120px;
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
}

.ts-scope .contrast {
  font-size: 12px;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ---------- Tables ---------- */
.ts-scope .tbl-wrap {
  overflow-x: auto;
}

.ts-scope .tbl {
  width: 100%;
  border-collapse: collapse;
}

.ts-scope .tbl th {
  text-align: left;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--text-3);
  font-weight: 600;
  padding: 10px 20px;
  border-bottom: 1px solid var(--border-soft);
}

.ts-scope .tbl td {
  padding: 11px 20px;
  border-bottom: 1px solid var(--border-soft);
  vertical-align: middle;
}

.ts-scope .tbl tr:last-child td {
  border-bottom: 0;
}

.ts-scope .tbl .mail {
  font-size: 13.5px;
}

.ts-scope .tbl .mail small {
  display: block;
  color: var(--text-3);
  font-size: 12px;
}

.ts-scope .tbl select {
  max-width: 150px;
}

.ts-scope .tbl td.act {
  text-align: right;
  width: 1%;
}

.ts-scope .avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-2);
  display: grid;
  place-items: center;
  font-size: 11.5px;
  font-weight: 600;
  flex: none;
  overflow: hidden;
}

.ts-scope .avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ts-scope .avatar.lg {
  width: 44px;
  height: 44px;
  font-size: 14px;
}

.ts-scope .cell {
  display: flex;
  align-items: center;
  gap: 10px;
}

.ts-scope .counter {
  font-size: 12.5px;
  color: var(--text-3);
}

/* ---------- Tags ----------
   Gone, on purpose, and this note is here so it does not come back.

   The mockup drew tag chips itself - amber pills, `.tag` inside `.tags` inside `.grp`. The tags and
   templates pages now HOST the real components instead (tagGroupTree, app-timer-templates), which
   colour a tag by the team it belongs to, so no settings page ever used these rules.

   What they did do is paint the hosted components: `.tag` is the class the legacy ts-tag component
   puts on its own chip, and an ancestor selector like `.ts-scope .tag` reaches straight through
   view encapsulation. A private template's tag came out amber - the design system's colour, on a
   tag that has no team colour of its own - and amber is a colour an ORGANISATION can have, so it
   read as "the wrong organisation's tag". Same class of collision as .slider before it.

   If a settings page ever needs its own chip again: give it a ts- prefixed name. */

/* ---------- Empty state ---------- */
.ts-scope .empty {
  padding: 44px 22px;
  text-align: center;
}

.ts-scope .empty h4 {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 600;
}

.ts-scope .empty p {
  margin: 0 auto 16px;
  color: var(--text-3);
  font-size: 13px;
  max-width: 44ch;
}

/* ---------- Loading state ----------
   For a region whose content is only known after a server round trip: the page
   says it is fetching instead of standing there empty.

   The spinner is the container's ::before, so a page needs one class and no
   markup of its own for it. And it is NOT called .loading: that name is already
   used inside ts-spinner, and an ancestor selector like this one reaches
   through view encapsulation into every component the settings area hosts. */
.ts-scope .loader {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 0;
}

.ts-scope .loader::before {
  content: "";
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: ts-spin 0.8s linear infinite;
}

.ts-scope .loader b {
  display: block;
  font-size: 14px;
  font-weight: 600;
}

.ts-scope .loader p {
  margin: 3px 0 0;
  color: var(--text-3);
  font-size: 13px;
  max-width: 60ch;
}

@keyframes ts-spin {
  to {
    transform: rotate(360deg);
  }
}

/* A reader who asked for less motion gets the text on its own. base.css cuts
   every animation here down to 0.001ms, which would leave the ring standing
   still - and a frozen spinner reads as a hung page, not as a working one. */
@media (prefers-reduced-motion: reduce) {
  .ts-scope .loader::before {
    display: none;
  }
}

/* ---------- Danger zone ----------
   Destructive actions get their own card at the end of a page, in a red frame.
   The confirmation - typing the name or the e-mail address - lives in the
   dialog, not here. */
.ts-scope .card.danger-zone {
  border-color: var(--danger-border);
}

.ts-scope .card.danger-zone > header {
  background: var(--danger-soft);
  border-bottom-color: var(--danger-border);
}

.ts-scope .card.danger-zone > header h3 {
  color: var(--danger);
}

/* ---------- Note / bridge ---------- */
.ts-scope .note {
  font-size: 12.5px;
  color: var(--text-3);
  border-left: 2px solid var(--border);
  padding-left: 12px;
  margin: 0 0 18px;
}

.ts-scope .bridge {
  border: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: var(--r-lg);
  padding: 18px 20px;
  display: flex;
  gap: 16px;
  align-items: flex-start;
  margin-bottom: 18px;
}

.ts-scope .bridge .ic {
  width: 36px;
  height: 36px;
  border-radius: 9px;
  background: var(--host-pill-bg);
  color: var(--host-pill-text);
  display: grid;
  place-items: center;
  flex: none;
  font-size: 16px;
}

/* ---------- Dialog host ----------
   Angular Material paints its own surface and padding inside
   .mat-mdc-dialog-surface. A settings dialog draws its own header, body and
   footer, so the container has to get out of the way - opened with
   panelClass: 'ts-settings-dialog'. These two rules are NOT under .ts-scope
   because the CDK overlay is attached to the body, outside the shell. */
.ts-settings-dialog .mat-mdc-dialog-surface {
  padding: 0;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}

.ts-settings-dialog .mat-mdc-dialog-container {
  --mdc-dialog-container-color: var(--surface);
  --mdc-dialog-container-shape: var(--r-lg);
}

/* ---------- Narrow displays ---------- */
@media (max-width: 640px) {
  .ts-scope .row {
    grid-template-columns: 1fr;
    gap: 8px;
  }

  /* one column: the label stands above its field, so there is no line to meet */
  .ts-scope .row > .lbl {
    padding-top: 0;
  }

}
