/* =========================================================================
 * sb-components.css — reusable UI: badges, stars, cards, tooltips, forms,
 * alerts, empty states, skeleton loaders, pills. All `.sb-` scoped.
 * ========================================================================= */

/* =========================================================================
 * Star rating (ENT-004: 1 decimal + half-step stars). Drawn with clip-path
 * so half stars are exact; no icon font, no image request.
 * ========================================================================= */
.sb-stars { display: inline-flex; align-items: center; gap: 2px; vertical-align: middle; }
.sb-star {
  --sb-star-size: 18px;
  width: var(--sb-star-size); height: var(--sb-star-size);
  display: inline-block; position: relative;
  background: var(--sb-surface-3);
  -webkit-mask: var(--sb-star-shape) center / contain no-repeat;
          mask: var(--sb-star-shape) center / contain no-repeat;
}
:root {
  --sb-star-shape: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2.5l2.9 6 6.6.9-4.8 4.6 1.2 6.5L12 18.9 6.1 20.5l1.2-6.5L2.5 9.4l6.6-.9z"/></svg>');
}
.sb-star--full { background: var(--sb-brass-500); }
.sb-star--half { background: linear-gradient(90deg, var(--sb-brass-500) 50%, var(--sb-surface-3) 50%); }
.sb-star--empty { background: var(--sb-surface-3); }
.sb-stars--sm .sb-star { --sb-star-size: 14px; }
.sb-stars--lg .sb-star { --sb-star-size: 26px; }

.sb-score-value { font-weight: var(--sb-fw-bold); color: var(--sb-ink-900); margin-left: var(--sb-sp-2); }
.sb-score-count { color: var(--sb-text-muted); font-size: var(--sb-fs-sm); }

/* =========================================================================
 * Score block (ENT-004) — the numeral leads, four category bars carry the rest.
 *
 * Five stars discard what the 36 questions collected: a stable at 4.7 on
 * facilities and 2.1 on tuition renders identically to a flat 3.4, and those are
 * different stables to a rider choosing between them. The numeral plus a
 * four-bar breakdown occupies the same footprint and carries four more signals.
 *
 * Bars render only where the per-category data exists. `/search/stables` returns
 * `rating: {count, avg}` alone, so search rows are numeral-only; the profile
 * derives categories from `/stables/{id}/ratings` details client-side. A
 * category rated n/a throughout draws HATCHED, never empty — an empty bar reads
 * as a score of zero, which is the opposite of "not applicable".
 * ========================================================================= */
.sb-score { display: flex; align-items: flex-start; gap: var(--sb-sp-4); }
.sb-score__num {
  font-family: var(--sb-font-head);
  font-weight: var(--sb-fw-semibold);
  font-size: 2.5rem;
  line-height: .95;
  letter-spacing: -.018em;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--sb-ink-900);
  margin: 0;
}
.sb-score__num sub {
  font-family: var(--sb-font-sans);
  font-size: var(--sb-fs-xs);
  font-weight: var(--sb-fw-semibold);
  vertical-align: baseline;
  color: var(--sb-ink-400);
  margin-left: 2px;
  letter-spacing: 0;
}
.sb-score__count {
  display: block;
  font-family: var(--sb-font-sans);
  font-size: var(--sb-fs-xs);
  font-weight: var(--sb-fw-normal);
  letter-spacing: 0;
  color: var(--sb-ink-400);
  margin-top: var(--sb-sp-1);
}
.sb-score__none {
  font-size: var(--sb-fs-sm);
  color: var(--sb-ink-400);
  max-width: 14ch;
  margin: 0;
}
/* One grid for all rows (the rows are display:contents), label column sized to the
 * longest label. It used to be a fixed 84/92px per row — enough for the English
 * "Horse"/"Rider", and it cut the German names off ("RUND UMS PF…", 24 Sep). */
.sb-score__bars {
  display: grid; grid-template-columns: max-content max-content max-content;
  column-gap: var(--sb-sp-2); row-gap: 6px; align-items: center; padding-top: 4px;
}
.sb-score__bar { display: contents; }
/* B11: stars + number per category (was a 4px bar). */
.sb-score__cstars { display: inline-flex; line-height: 1; }
.sb-score__cval { font-size: var(--sb-fs-xs); font-weight: var(--sb-fw-semibold); color: var(--sb-ink-900); font-variant-numeric: tabular-nums; }
.sb-score__cval--na { color: var(--sb-ink-400); font-weight: var(--sb-fw-normal); }
/* When the rows do not fit beside the numeral (phones, the longer Spanish labels), they go
 * under it instead of running off the screen. */
.sb-score { flex-wrap: wrap; }
.sb-score__label {
  font-size: 10.5px; font-weight: var(--sb-fw-semibold);
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--sb-ink-400); text-align: right;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sb-score__track { height: 4px; background: var(--sb-surface-3); overflow: hidden; }
.sb-score__fill { height: 100%; background: var(--sb-brass-500); display: block; }
.sb-score__fill--na {
  width: 100% !important;
  background: repeating-linear-gradient(90deg, var(--sb-line-strong) 0 2px, transparent 2px 4px);
}
.sb-score--lg .sb-score__num { font-size: clamp(3rem, 6vw, 4.25rem); }
/* Stars beside the numeral (F4/F8, review 09.10.2026). */
/* Inside the numeral's <p>: reset its display-size font, or the line is as tall as the numeral. */
.sb-score__stars { display: block; margin: .5rem 0 0; font-size: 1rem; line-height: 1; font-family: var(--sb-font-sans); letter-spacing: 0; }
.sb-score__num .sb-score__count { white-space: nowrap; }
.sb-score__bars { min-width: 0; }

/* =========================================================================
 * The register — search results as ruled rows, not a card grid.
 *
 * Cards with drop shadows are the genre default and they spend the horizontal
 * space a comparison needs. Rows put every score on one axis so the eye can run
 * down the column, which is the product's whole job.
 * ========================================================================= */
.sb-register { border-top: 1.5px solid var(--sb-ink-900); margin: 0; padding: 0; list-style: none; }
.sb-row { border-bottom: 1px solid var(--sb-border); }
a.sb-row__link {
  display: grid;
  grid-template-columns: 104px 1fr auto;
  gap: var(--sb-sp-5);
  align-items: center;
  padding: var(--sb-sp-5) var(--sb-sp-2);
  text-decoration: none;
  color: inherit;
  transition: background-color var(--sb-transition);
}
a.sb-row__link:hover { background: var(--sb-surface-2); }
.sb-row__media {
  width: 104px; height: 76px;
  border: 1px solid var(--sb-border);
  background: var(--sb-surface-3);
  overflow: hidden;
  display: block;
}
.sb-row__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sb-row__body { min-width: 0; }
.sb-row__title {
  font-family: var(--sb-font-head);
  font-weight: var(--sb-fw-semibold);
  font-size: var(--sb-fs-lg);
  letter-spacing: -.006em;
  line-height: 1.2;
  color: var(--sb-ink-900);
  margin: 0 0 var(--sb-sp-1);
}
.sb-row__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sb-sp-2);
  font-size: var(--sb-fs-sm); color: var(--sb-ink-400); margin: 0;
}
.sb-row__sep { width: 3px; height: 3px; border-radius: 50%; background: var(--sb-line-strong); flex: none; }

/* Below the tablet break the three columns cannot share one line without
 * crushing the name, so the score drops to its own row under the thumbnail and
 * body. The shared-axis comparison is a wide-screen affordance; on a phone the
 * reader is scrolling one stable at a time anyway. */
@media (max-width: 720px) {
  a.sb-row__link {
    grid-template-columns: 76px 1fr;
    grid-template-areas: "media body" "score score";
    gap: var(--sb-sp-3) var(--sb-sp-4);
    align-items: start;
  }
  .sb-row__media { grid-area: media; width: 76px; height: 58px; }
  .sb-row__body { grid-area: body; }
  .sb-row .sb-score { grid-area: score; align-items: center; }
  .sb-row .sb-score__num { font-size: 2rem; }
}

/* Interactive star input (rating form) */
.sb-starinput { display: inline-flex; flex-direction: row-reverse; gap: 4px; }
.sb-starinput input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.sb-starinput label {
  width: 32px; height: 32px; cursor: pointer;
  background: var(--sb-surface-3);
  -webkit-mask: var(--sb-star-shape) center / contain no-repeat;
          mask: var(--sb-star-shape) center / contain no-repeat;
  transition: background var(--sb-transition);
}
.sb-starinput label:hover,
.sb-starinput label:hover ~ label,
.sb-starinput input:checked ~ label { background: var(--sb-brass-500); }
.sb-starinput input:focus-visible ~ label:first-of-type,
.sb-starinput label:focus-within { box-shadow: var(--sb-focus); border-radius: 4px; }

/* =========================================================================
 * Badges (UX-003) — Certified / Top 10·50·100 / Stall available.
 * Multilingual: label text comes from PHP/JS, chip styling is language-agnostic.
 * ========================================================================= */
.sb-badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--sb-fs-xs); font-weight: var(--sb-fw-semibold);
  padding: 4px 10px; border-radius: var(--sb-radius-pill);
  border: 1px solid transparent; line-height: 1.4; white-space: nowrap;
}
.sb-badge .sb-icon { width: 14px; height: 14px; }
.sb-badge--md { font-size: var(--sb-fs-sm); padding: 5px 12px; }
.sb-badge--certified { background: var(--sb-claret-100); color: var(--sb-claret-700); border-color: var(--sb-claret-400); }
.sb-badge--top { background: var(--sb-brass-100); color: var(--sb-warn-ink); border-color: var(--sb-brass-400); }
.sb-badge--stable-free { background: var(--sb-surface-3); color: var(--sb-ink-500); border-color: var(--sb-line-strong); }
/* A badge that links out (FREE-001: "Stall frei" → the stable's own website).
   It must still read as a badge, not as body-copy link: no underline, the chip's
   own colour. The hover/focus affordance is what tells a mouse user it is
   clickable at all, and the focus ring is the same one every other control uses
   (NFR-001 — the badge is in the tab order the moment it becomes a link). */
.sb-badge--link { text-decoration: none; cursor: pointer; transition: filter .15s ease, box-shadow .15s ease; }
.sb-badge--link:hover { filter: brightness(0.96); }
/* `--sb-focus` is a box-shadow value in this theme (sb-base.css), not a colour —
   so it is applied as one here too, like .sb-starinput does. */
.sb-badge--link:focus-visible { box-shadow: var(--sb-focus); }
.sb-badge-row { display: flex; flex-wrap: wrap; gap: var(--sb-sp-2); }

/* Pills / meta chips */
.sb-pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--sb-fs-xs); color: var(--sb-text-muted);
  background: var(--sb-surface-2); border: 1px solid var(--sb-border);
  padding: 3px 10px; border-radius: var(--sb-radius-pill);
}

/* =========================================================================
 * Cards
 * ========================================================================= */
.sb-card {
  background: var(--sb-surface); border: 1px solid var(--sb-border);
  border-radius: var(--sb-radius-lg); overflow: hidden;
  transition: box-shadow var(--sb-transition), transform var(--sb-transition), border-color var(--sb-transition);
}
.sb-card--link:hover { box-shadow: var(--sb-shadow); transform: translateY(-2px); border-color: var(--sb-claret-400); }
.sb-card__media { position: relative; aspect-ratio: 16 / 10; background: var(--sb-surface-3); overflow: hidden; }
.sb-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sb-card__badges { position: absolute; top: var(--sb-sp-2); left: var(--sb-sp-2); display: flex; gap: 4px; flex-wrap: wrap; }
.sb-card__body { padding: var(--sb-sp-4); }
.sb-card__title { font-size: var(--sb-fs-md); margin: 0 0 4px; }
.sb-card__title a { text-decoration: none; color: var(--sb-ink-900); }
.sb-card__meta { display: flex; align-items: center; gap: 6px; color: var(--sb-text-muted); font-size: var(--sb-fs-sm); margin-bottom: var(--sb-sp-3); }
.sb-card__score { display: flex; align-items: center; gap: var(--sb-sp-2); }

/* =========================================================================
 * Forms
 * ========================================================================= */
.sb-field { margin-bottom: var(--sb-sp-5); }
.sb-label { display: block; font-weight: var(--sb-fw-semibold); margin-bottom: var(--sb-sp-2); color: var(--sb-ink-900); }
.sb-label .sb-req { color: var(--sb-error); margin-left: 2px; }
.sb-hint { display: block; font-size: var(--sb-fs-sm); color: var(--sb-text-muted); margin-top: var(--sb-sp-2); }
/* Scoped `.sb-app …` (0,2,0) rather than the bare class (0,1,0) to outrank the
 * PARENT theme's reset, which styles typed inputs as
 * `input[type="text"], …, select, textarea` — (0,1,1) — with
 * `border:1px solid #666; border-radius:3px; padding:.5rem 1rem`.
 *
 * That reset was winning on <input> and losing on <select>, because `select` in
 * the same rule is only (0,0,1). So one form drew its text fields with a grey
 * #666 border, 3px corners and 8/16 padding and the select beside them with the
 * design's warm border, 12px corners and 12/16 padding. It went unseen while
 * `--sb-radius` was 3px — the reset's value and ours agreed by accident, and
 * only the border and padding differed. */
.sb-app .sb-input, .sb-app .sb-select, .sb-app .sb-textarea {
  width: 100%; font: inherit; color: var(--sb-ink-900);
  background: #fff; border: 1px solid var(--sb-border);
  border-radius: var(--sb-radius); padding: var(--sb-sp-3) var(--sb-sp-4);
  min-height: 46px; transition: border-color var(--sb-transition), box-shadow var(--sb-transition);
}
/* One height for text fields and selects. Text inputs grew to ~52px from the
 * inherited 1.6 line height while selects (which ignore line-height) stopped at
 * the 46px minimum, so "Bundesland" and "Land" sat 5px apart in the same row
 * (DEL-002 pass, 1 Oct). 48px keeps the touch target (NFR-001). */
.sb-app .sb-input, .sb-app .sb-select { height: 48px; padding-block: 0; line-height: 1.4; }
.sb-textarea { min-height: 120px; resize: vertical; }
.sb-input:focus, .sb-select:focus, .sb-textarea:focus { outline: none; border-color: var(--sb-claret-500); box-shadow: var(--sb-focus); }
.sb-field.has-error .sb-input,
.sb-field.has-error .sb-select,
.sb-field.has-error .sb-textarea { border-color: var(--sb-error); }
.sb-field__error { display: none; color: var(--sb-error); font-size: var(--sb-fs-sm); margin-top: var(--sb-sp-2); }
.sb-field.has-error .sb-field__error { display: block; }
.sb-inline-group { display: flex; gap: var(--sb-sp-3); flex-wrap: wrap; }
.sb-inline-group > * { flex: 1 1 200px; }

.sb-checkbox { display: flex; align-items: flex-start; gap: var(--sb-sp-3); font-size: var(--sb-fs-sm); }
/* flex: none — as a flex item the box shrank below 18px whenever its label wrapped
 * (registration consent vs "Angemeldet bleiben" on the same auth card). */
.sb-checkbox input { flex: none; margin-top: 3px; width: 18px; height: 18px; accent-color: var(--sb-claret-700); }

/* =========================================================================
 * Tooltips / explainer (UX-004) — hover, focus, keyboard AND touch operable.
 * Implemented as a <button> that toggles a popover; hover/focus reveal it too.
 * ========================================================================= */
.sb-tip { position: relative; display: inline-flex; }
.sb-tip__trigger {
  display: inline-flex; align-items: center; justify-content: center;
  /* 24px is the WCAG 2.2 AA 2.5.8 minimum target size; this was 20px. There are
   * 36 of them on the rating form, so it is the most-repeated control in the app. */
  width: 24px; height: 24px; border-radius: 50%;
  border: 1px solid var(--sb-claret-400); background: var(--sb-claret-050);
  color: var(--sb-claret-700); font-size: 12px; font-weight: 700; cursor: pointer;
  line-height: 1; padding: 0;
}
button.sb-tip__trigger:hover, button.sb-tip__trigger:focus {
  background: var(--sb-claret-100); color: var(--sb-claret-700);
}
.sb-tip__bubble {
  position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  width: max-content; max-width: 280px; z-index: 50;
  background: var(--sb-ink-900); color: #fff;
  font-size: var(--sb-fs-sm); font-weight: var(--sb-fw-normal); line-height: 1.45;
  padding: var(--sb-sp-3) var(--sb-sp-4); border-radius: var(--sb-radius);
  box-shadow: var(--sb-shadow-lg);
  /* `display: none` rather than visibility alone.
   *
   * A `visibility: hidden` element still contributes to scroll width, and there
   * are 36 of these on the rating form — one per criterion. Anchored `left: 0`
   * against their trigger with a width of up to 280px, the ones on the right of
   * the row extended well past a 360px viewport, so the page scrolled sideways
   * on a phone before the user touched anything (NFR-001 is mobile-first).
   *
   * The cost is the fade: `display` is not transitionable, so the bubble now
   * appears instantly. On a tooltip that is a fair trade for a page that does
   * not scroll horizontally, and it is the only fix here that does not need JS
   * or a positioning refactor. */
  opacity: 0; visibility: hidden; display: none;
  transition: opacity var(--sb-transition), visibility var(--sb-transition);
}
.sb-tip__bubble::after {
  content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  border: 6px solid transparent; border-top-color: var(--sb-ink-900);
}
.sb-tip:hover .sb-tip__bubble,
.sb-tip__trigger:focus-visible + .sb-tip__bubble,
.sb-tip.is-open .sb-tip__bubble { opacity: 1; visibility: visible; display: block; }
/* Escape (sb-ui.js): hidden until focus or the pointer leaves — beats :hover/:focus-visible. */
.sb-tip.sb-tip.is-dismissed .sb-tip__bubble { opacity: 0; visibility: hidden; }
@media (max-width: 480px) {
  /* Anchor the bubble's RIGHT edge to the trigger and let it grow leftwards.
   *
   * Anchoring `left: 0` grew it rightwards from a trigger that sits at the end
   * of a long criterion label — i.e. near the right edge — so an opened tooltip
   * ran ~75px past a 360px viewport and scrolled the page sideways. Growing
   * leftwards keeps it inside, because the space to the trigger's left is
   * exactly the space the label already occupies.
   *
   * The width cap is viewport-relative rather than `80vw` so it accounts for the
   * page gutter instead of assuming one. */
  .sb-tip__bubble {
    left: auto; right: 0; transform: none;
    max-width: min(calc(100vw - 32px), 280px);
  }
  .sb-tip__bubble::after { left: auto; right: 8px; transform: none; }
}

/* =========================================================================
 * Alerts (form/API feedback — unified error format, api-contract §4)
 * ========================================================================= */
.sb-alert {
  display: flex; gap: var(--sb-sp-3); align-items: flex-start;
  padding: var(--sb-sp-4); border-radius: var(--sb-radius);
  border: 1px solid transparent; margin-bottom: var(--sb-sp-5); font-size: var(--sb-fs-sm);
}
.sb-alert--error { background: var(--sb-error-bg); border-color: #eab8b1; color: #7a241c; }
.sb-alert--success { background: var(--sb-success-bg); border-color: #b7d8c4; color: #1d5637; }
.sb-alert--info { background: var(--sb-info-bg); border-color: var(--sb-claret-400); color: var(--sb-claret-800); }

/* =========================================================================
 * Empty / error / loading states (UX-001, DEL-002 — states designed in)
 * ========================================================================= */
.sb-state {
  text-align: center; padding: var(--sb-sp-8) var(--sb-sp-4);
  color: var(--sb-text-muted); max-width: 460px; margin-inline: auto;
}
.sb-state__icon {
  width: 56px; height: 56px; margin: 0 auto var(--sb-sp-4);
  color: var(--sb-claret-400);
}
/* Two classes: the title is an h1/h2 depending on the page (renderState level),
   and the Elementor kit's `.elementor-kit-N h2` (0,1,1) would size it otherwise. */
.sb-state .sb-state__title { color: var(--sb-ink-900); font-size: var(--sb-fs-lg); margin-top: 0; margin-bottom: var(--sb-sp-2); }
.sb-state__actions { margin-top: var(--sb-sp-5); display: flex; gap: var(--sb-sp-3); justify-content: center; flex-wrap: wrap; }

/* Skeleton loaders */
.sb-skeleton { position: relative; overflow: hidden; background: var(--sb-surface-3); border-radius: var(--sb-radius-sm); }
.sb-skeleton::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.6), transparent);
  transform: translateX(-100%); animation: sb-shimmer 1.4s infinite;
}
@keyframes sb-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .sb-skeleton::after { animation: none; } }
.sb-skeleton--text { height: 14px; margin-bottom: 8px; }
.sb-skeleton--title { height: 22px; width: 60%; margin-bottom: 12px; }

/* Spinner */
.sb-spinner { width: 22px; height: 22px; border: 3px solid var(--sb-claret-100); border-top-color: var(--sb-claret-600); border-radius: 50%; animation: sb-spin .7s linear infinite; display: inline-block; }
@keyframes sb-spin { 100% { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .sb-spinner { animation-duration: 2s; } }

/* Visually-hidden live region for status announcements (a11y) */
.sb-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* B8: the searchable field behind "Weitere Länder …" (sb-country.js). */
.sb-country-more { margin-top: 6px; }
.sb-country-more .sb-control { width: 100%; }
