/* ==========================================================================
   Resolve — application layer on top of the design system.

   Everything here is additive: resolve.css stays a verbatim copy of the supplied
   design so it can be re-synced if the design changes, and anything the app needs
   beyond it (htmx loading states, logo tiles, skeletons) lives in this file.
   ========================================================================== */

/* --------------------------------------------------------------- Brand tile */
/*  The design used an icon font inside an indigo square. The real logo is a
    full-colour mark, so the tile becomes a white plate instead — an indigo
    background behind an indigo-and-green mark would lose the R entirely.

    The tile owns the padding; the PNG is cropped tight to the mark. Padding in
    both left the logo small and adrift in whitespace. */
.logo--image {
  background: #fff !important;
  /*  2px, not 5px. The artwork already carries its own optical margin, so a generous
      tile padding stacked white on white and shrank the mark to roughly half the tile.
      Just enough inset here to keep the mark off the rounded corners. */
  padding: 2px;
  overflow: hidden;
  /*  A soft ring plus a lifted shadow: on the near-black sidebar a plain white
      square reads as a hole punched in the panel, while a ring makes it sit on
      the surface. */
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, .10),
    0 6px 16px -6px rgba(2, 6, 23, .75) !important;
}
.logo--image img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Sidebar brand — larger than the design's 34px icon tile so the mark is legible. */
.sidebar__brand .logo--image {
  width: 40px;
  height: 40px;
  border-radius: 12px;
}
.sidebar__brand b { font-size: 1.12rem; letter-spacing: -0.02em; }
.sidebar__brand span { font-size: .735rem; letter-spacing: .01em; }

/* Auth pages sit on a gradient, so the mark gets a little more presence. */
.auth__brand .logo--image {
  width: 46px;
  height: 46px;
  border-radius: 13px;
}

/*  Light backgrounds (the mobile brand row on the auth cards) need the ring
    darkened instead — a white ring on white is invisible. */
.auth__form .logo--image,
.logo--image.logo--on-light {
  box-shadow:
    0 0 0 1px rgba(15, 23, 42, .08),
    0 4px 12px -4px rgba(15, 23, 42, .25) !important;
}

/* ------------------------------------------------- Route progress indicator */
/*  A 3px bar pinned to the top of the viewport during an htmx navigation.
    Deliberately not a blocking overlay: navigation stays interruptible, and a
    fast response never flashes a spinner (the bar animates in over 120ms). */
.route-progress {
  position: fixed;
  inset: 0 auto auto 0;
  height: 3px;
  width: 0;
  background: linear-gradient(90deg, var(--c-primary), var(--c-info));
  z-index: 2000;
  opacity: 0;
  transition: width .2s ease, opacity .2s ease;
  pointer-events: none;
}
.route-progress.is-loading { opacity: 1; }
.route-progress.is-done    { width: 100%; opacity: 0; transition: width .12s ease, opacity .3s ease .1s; }

/* ------------------------------------------------------- htmx indicator base */
/*  htmx toggles .htmx-request on the indicator element. The default is hidden;
    only the requesting element's indicator shows, so a slow table refresh does
    not spin every icon on the page. */
.htmx-indicator { opacity: 0; visibility: hidden; transition: opacity .15s ease; }
.htmx-request .htmx-indicator,
.htmx-indicator.htmx-request { opacity: 1; visibility: visible; }

/*  Content being replaced dims slightly rather than disappearing, so the page
    does not collapse to zero height and bounce the scroll position. */
#app-content.htmx-swapping { opacity: .55; transition: opacity .12s ease; }
#app-content.htmx-settling { opacity: 1; }

/*  Any element mid-request stops accepting clicks — prevents double submits
    without needing to disable each button by hand. */
.htmx-request[hx-post], .htmx-request[hx-get],
.htmx-request[hx-put], .htmx-request[hx-delete] {
  pointer-events: none;
  opacity: .75;
}

/* Inline button spinner: markup stays put, visibility flips on request. */
.btn .btn-spinner {
  display: none;
  width: .9rem; height: .9rem;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: resolve-spin .6s linear infinite;
  margin-right: .45rem;
  vertical-align: -2px;
}
.htmx-request .btn-spinner, .btn.htmx-request .btn-spinner { display: inline-block; }
@keyframes resolve-spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------- Skeletons */
/*  Used as hx-placeholder content for slow regions so the layout keeps its
    shape while loading instead of jumping when data lands. */
.skeleton {
  background: linear-gradient(90deg, #EEF2F7 25%, #F8FAFC 37%, #EEF2F7 63%);
  background-size: 400% 100%;
  animation: resolve-shimmer 1.3s ease-in-out infinite;
  border-radius: var(--radius-sm);
}
.skeleton-line { height: 12px; margin-bottom: .6rem; }
.skeleton-line:last-child { width: 65%; margin-bottom: 0; }
@keyframes resolve-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; }
  .btn .btn-spinner { animation-duration: 1.5s; }
}

/* ------------------------------------------------------------------- Forms */
/*  Server-side validation summary rendered by ASP.NET; matches .alert-danger. */
.validation-summary-errors ul { margin: 0; padding-left: 1.1rem; }
.field-validation-error { color: var(--c-danger); font-size: .82rem; display: block; margin-top: .3rem; }
.input-validation-error { border-color: var(--c-danger) !important; }

/* Password rules checklist on the change-password and signup screens. */
/*  Bottom margin matters: without it the next field's label crowds the last rule
    and the two blocks read as one. */
.pw-rules { list-style: none; padding: 0; margin: .6rem 0 1rem; font-size: .82rem; }
.pw-rules li { color: var(--c-text-muted); display: flex; align-items: center; gap: .45rem; margin-bottom: .25rem; }
.pw-rules li::before { content: "\F28A"; font-family: "bootstrap-icons"; font-size: .9rem; }
.pw-rules li.ok { color: var(--c-success); }
.pw-rules li.ok::before { content: "\F26E"; }

/* ----------------------------------------------------------- Inline search */
/*  An icon-prefixed search input for use inside cards and toolbars.

    Deliberately NOT .topbar__search: that class carries `display:none` below
    576px, because the design hides the *global* search on phones. Reusing it for
    a filter box silently removed the only way to search a table on mobile. */
.field-search { position: relative; }
.field-search i {
  position: absolute;
  left: .9rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--c-text-muted);
  font-size: 1rem;
  pointer-events: none;
}
.field-search .form-control {
  padding-left: 2.5rem;
  background: var(--c-bg);
  border-color: transparent;
  height: 42px;
  border-radius: var(--radius-pill);
}
.field-search .form-control:focus {
  background: #fff;
  border-color: var(--c-primary);
  box-shadow: var(--ring);
}

/* --------------------------------------------------------------- Badges */
/*  badge-neutral's background is --c-bg, the page tint. That reads fine on the
    page itself but disappears against a white card, where most badges actually
    sit — "Final", "Comment", "12 total" all rendered as bare floating text. A
    hairline border restores the badge shape without changing the palette. */
.badge-neutral {
  border: 1px solid var(--c-border);
  /* Keep the same visual height as borderless badges. */
  padding: calc(.38em - 1px) calc(.62em - 1px);
}

/* -------------------------------------------------------------- Avatars */
/*  Avatars live inside flex rows (sidebar footer, topbar, table cells) next to
    a name. Flex items default to flex-shrink:1, so a long name compresses the
    avatar horizontally while its height is untouched — a 34x34 circle renders
    as a ~28x34 oval. The design sets width/height but not the shrink
    behaviour, so it is fixed here rather than by editing resolve.css.

    aspect-ratio holds the shape even if only one dimension is ever set. */
.avatar {
  flex: 0 0 auto;
  aspect-ratio: 1 / 1;
}

/* --------------------------------------------------------- Table columns */
/*  Floor widths for text-heavy columns.

    Without these the browser shrinks a column until its text wraps one word per
    line rather than letting .table-responsive scroll — technically not overflow,
    but unreadable on a phone. A min-width makes the table scroll instead, which
    is the behaviour the wrapper exists for. */
.cell-wide   { min-width: 240px; }
.cell-medium { min-width: 160px; }
.cell-narrow { min-width: 110px; }

/* ------------------------------------------------------------- Tick lists */
/*  The design styles .list-tick items with a green check ::before but never
    suppresses the native marker, so each row renders a bullet AND a tick. The
    design's own demo pages hid it with Bootstrap's .list-unstyled; fixing it here
    means every tick list is correct without remembering that extra class. */
.list-tick { list-style: none; padding-left: 0; margin-bottom: 0; }

/* ------------------------------------------------------- Clickable rows */
/*  Whole-row navigation without Bootstrap's .stretched-link. That helper
    positions its ::after against the nearest *positioned* ancestor; a <tr> is
    not one, so the overlay escapes the row and steals hover and clicks from the
    rest of the table. Here the row carries data-row-href and resolve.js handles
    the click, so nothing overlaps anything. */
.table-clickable tbody tr { cursor: pointer; }

/*  The title stays a real link for keyboard and middle-click, but its underline
    should follow the row, not just the text. */
.table-clickable tbody tr .row-title { text-decoration: none; }
.table-clickable tbody tr:hover .row-title { text-decoration: underline; }

/*  The chevron is the standing signal that a row opens something; it firms up on
    hover rather than appearing from nothing, so the row does not shift. */
.row-chevron { color: #CBD5E1; transition: color .15s, transform .15s; }
.table-clickable tbody tr:hover .row-chevron { color: var(--c-primary); }
.table-clickable tbody tr:hover .row-chevron i { transform: translateX(2px); }
.row-chevron i { display: inline-block; transition: transform .15s; }

/*  Keyboard parity: focusing the title highlights its row the same way hovering
    does, so tabbing through the table is legible. */
.table-clickable tbody tr:focus-within { background: var(--c-primary-050); }

/* ---------------------------------------------------------- Attachments */
.attach-list { display: flex; flex-direction: column; gap: .5rem; }

.attach-item {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .6rem .75rem;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  color: var(--c-text);
  background: #fff;
  transition: background .15s, border-color .15s;
}
.attach-item:hover { background: var(--c-bg); border-color: #CBD5E1; color: var(--c-text); }

.attach-icon { font-size: 1.35rem; color: var(--c-primary); flex: 0 0 auto; }
.attach-meta { flex: 1 1 auto; min-width: 0; }
/*  File names run long and must not stretch the card; ellipsis rather than wrap
    keeps every row the same height. */
.attach-name { display: block; font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attach-dl { color: var(--c-text-muted); flex: 0 0 auto; }
.attach-item:hover .attach-dl { color: var(--c-primary); }

/* Same amber treatment as an internal note, so the distinction is consistent. */
.attach-internal { background: var(--c-warning-050); border-color: #FDE68A; }
.attach-internal:hover { background: #FEF3C7; border-color: #FCD34D; }

/* ------------------------------------------------------- Ticket timeline */
/*  The conversation (spec §19) and the history trail share one vertical rhythm:
    real messages are cards, system entries are quiet single lines, so the eye
    skims the automated noise and lands on what people actually wrote. */
.timeline { display: flex; flex-direction: column; gap: .9rem; }

.tl-msg { display: flex; gap: .75rem; align-items: flex-start; }
.tl-msg .tl-body {
  flex: 1 1 auto;
  min-width: 0;               /* lets long words wrap instead of stretching the row */
  background: var(--c-bg);
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  padding: .75rem .9rem;
}

/*  Internal notes are visually unmistakable. An agent must never be in doubt about
    whether the requester can read what they are typing (spec §20). */
.tl-internal .tl-body {
  background: var(--c-warning-050);
  border-color: #FDE68A;
}

.tl-system {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: .82rem;
  color: var(--c-text-muted);
  padding-left: .15rem;
}
.tl-system i { font-size: 1rem; flex: 0 0 auto; }
.tl-system small { margin-left: auto; flex: 0 0 auto; white-space: nowrap; }

/* --------------------------------------------------------------- Empty state */
.empty-state { text-align: center; padding: 3rem 1rem; color: var(--c-text-muted); }
.empty-state .bi { font-size: 2.5rem; color: #CBD5E1; display: block; margin-bottom: .75rem; }


/* ------------------------------------------------------------- Notifications */
/*  An unread row is tinted with --c-primary-050, which is exactly the background a
    soft-primary icon tile uses — on an unread assignment notification the tile
    disappears into the row and only the glyph is left floating. White keeps the tile
    readable in both states without introducing a colour the design does not have. */
.notif-item.unread .ic.soft-primary { background: #fff; }

/*  The design's .notif-item is a flex row with no alignment set, so a two-line body
    leaves the icon tile stretched. Top-aligned keeps it square beside the first line. */
.notif-item { align-items: flex-start; }
.notif-item .ic { margin-top: .1rem; }
/*  Bootstrap's switch leaves the label hard against the control, which reads as one
    word running into the next when the label is a sentence rather than a word. */
.pref-switch .form-check-label { padding-left: .5rem; }
.pref-switch .form-check-input { margin-top: .15rem; }
/* ------------------------------------------------------------- Touch targets */
/*  Bootstrap's checkbox is 15x15 and its switch 36x20. Both are comfortable with a
    mouse and fiddly with a thumb, and WCAG 2.2 SC 2.5.8 asks for at least 24x24 CSS
    pixels. The notification preferences screen is sixteen switches whose whole purpose
    is being tapped, so this is not a theoretical concern.

    Scoped to coarse pointers and phone widths, so the density the supplied design
    specifies for the desktop is left exactly as drawn. Sizes are set in rem with
    matching negative margins because Bootstrap lays these out in em against the
    control's own font-size, which is not what we are scaling here. */
@media (pointer: coarse), (max-width: 575.98px) {
  .form-check { padding-left: 2.25rem; min-height: 1.75rem; }
  .form-check .form-check-input { width: 1.5rem; height: 1.5rem; margin-left: -2.25rem; }

  .form-switch { padding-left: 3.5rem; }
  .form-switch .form-check-input { width: 2.75rem; height: 1.5rem; margin-left: -3.5rem; }

  /* The label must clear the taller control rather than sitting against its top edge. */
  .form-check-label { padding-top: .1rem; }
}

/* ------------------------------------------------------ Scrollable table hint */
/*  A wide table scrolls inside .table-responsive on a phone, which is right — but
    Bootstrap gives no sign that it does, overlay scrollbars are invisible until
    touched, and the SLA targets table at 360px shows two of its eight columns and
    simply looks truncated.

    The usual trick — scroll-shadow gradients on the scroll container itself, attached
    local and scroll so a shadow shows only on the side that still has content —
    does NOT work here, and that was measured rather than assumed: the design paints
    table cells an opaque #F8FAFC, which covers anything painted behind them.

    So the fade goes on top instead, as a pseudo-element on the card. It cannot know
    the scroll position, so it is always present at these widths; on these tables, on a
    phone, there is always more to the right, and a standing hint is a smaller lie than
    no hint at all.  */
@media (max-width: 991.98px) {
  .card:has(> .table-responsive) { position: relative; }

  .card:has(> .table-responsive)::after {
    content: "";
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: 28px;
    pointer-events: none;                    /* never intercepts a tap or a swipe */
    border-radius: 0 var(--radius) var(--radius) 0;
    background: linear-gradient(to left, rgba(15, 23, 42, .13), rgba(15, 23, 42, 0));
  }
}
/* ---------------------------------------------------------------------------
   Ticket templates (spec §40)
   The picker on the new-ticket page. Ordinary links, so a template is an
   address — styled as cards because a grid of eight is scanned, not read.
   --------------------------------------------------------------------------- */
.template-card {
    display: flex;
    align-items: flex-start;
    gap: .625rem;
    height: 100%;
    padding: .75rem;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-surface);
    color: var(--c-text);
    text-decoration: none;
    transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}

.template-card:hover {
    border-color: var(--c-primary);
    box-shadow: var(--shadow-sm);
    color: var(--c-text);
}

/* Focus is drawn on the card itself: these are the first controls on the page
   and a keyboard user lands here before anything else. */
.template-card:focus-visible {
    outline: none;
    border-color: var(--c-primary);
    box-shadow: var(--ring);
}

.template-card.is-selected {
    border-color: var(--c-primary);
    background: var(--c-primary-050);
}

.template-card > .bi:first-child {
    font-size: 1.125rem;
    color: var(--c-primary);
    line-height: 1.4;
}

.template-card__name {
    font-weight: 600;
    display: block;
}

/* ---------------------------------------------------------------------------
   Satisfaction stars (spec §43)
   Radio inputs in reverse DOM order, so a CSS sibling selector can light the
   hovered star and every one before it. No JavaScript, so it also survives the
   printed view and needs no CSP exception.
   --------------------------------------------------------------------------- */
.star-rating {
    display: inline-flex;
    flex-direction: row-reverse;   /* 5..1 in the DOM reads 1..5 on screen */
    justify-content: flex-end;
    gap: .25rem;
    font-size: 1.5rem;
    line-height: 1;
}

.star-rating input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.star-rating label {
    color: var(--c-border);
    cursor: pointer;
    transition: color .12s ease, transform .12s ease;
}

/* The hovered star, and every one rendered before it (i.e. to its left). */
.star-rating label:hover,
.star-rating label:hover ~ label {
    color: var(--c-warning);
}

.star-rating label:hover {
    transform: scale(1.1);
}

/* The chosen star and everything below it stay lit. */
.star-rating input:checked ~ label {
    color: var(--c-warning);
}

/* Keyboard users must be able to see where they are. */
.star-rating input:focus-visible + label {
    outline: none;
    border-radius: 4px;
    box-shadow: var(--ring);
}

/* ---------------------------------------------------------------------------
   Transition-matrix toggles (spec §11)
   Bare checkboxes in table cells: no .form-check wrapper, so the touch-target
   rule below does not reach them, and no <label>, so they get none of the extra
   hit area a labelled control has. A grid of adjacent 15x15 targets is the
   hardest thing here to tap, so these are sized up at every width rather than
   only on coarse pointers — WCAG 2.5.8 asks for 24x24, and the table already
   scrolls, so the extra width costs nothing.
   --------------------------------------------------------------------------- */
.matrix-toggle {
    width: 1.5rem;
    height: 1.5rem;
    margin: 0;
    cursor: pointer;
}

/* ---------------------------------------------------------------------------
   Target size, the last 4px (WCAG 2.5.8)
   Two patterns fell just short of 24px and neither is covered by the standard's
   inline exception, so both are corrected rather than argued about.
   --------------------------------------------------------------------------- */

/*  A breadcrumb link is 19px tall, set by its font. It sits in a nav rather than
    a sentence, so "constrained by the line-height of non-target text" does not
    really apply. Vertical padding grows the hit box of an inline element without
    moving anything around it — the breadcrumb row already has the room. */
.breadcrumb a {
    padding-block: .3rem;
}

/*  A checkbox is hit by clicking its label too, so the label's line box is what
    decides the target's height — 20px, four short. Raising the line-height to
    24px fixes every .form-check at once and is visually all but invisible; on the
    two-line labels in the custom-field and skill dialogs it reads slightly
    better, not worse. The box itself is untouched. */
.form-check-label {
    line-height: 1.5rem;
}
