/* ===== Typeface ==============================================================
   Libre Franklin — the closest open relative of Whitney, BU's licensed brand
   sans (both descend from Franklin Gothic). One family only; hierarchy is
   carried by weight and size, never by a second face. Self-hosted woff2,
   swap so text renders immediately in the fallback stack. */
@font-face {
    font-family: 'Libre Franklin';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/libre-franklin-v20-latin-regular.woff2') format('woff2');
}
@font-face {
    font-family: 'Libre Franklin';
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/libre-franklin-v20-latin-italic.woff2') format('woff2');
}
@font-face {
    font-family: 'Libre Franklin';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/libre-franklin-v20-latin-500.woff2') format('woff2');
}
@font-face {
    font-family: 'Libre Franklin';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/libre-franklin-v20-latin-600.woff2') format('woff2');
}
@font-face {
    font-family: 'Libre Franklin';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/libre-franklin-v20-latin-700.woff2') format('woff2');
}

/* ===== Design tokens =========================================================
   BU's palette is deliberately minimal: BU Red #CC0000 + BU Black #2D2926.
   BU Black is a *warm* black, so the whole neutral ramp is derived from it —
   one warm gray family, no Bootstrap cool-grays mixed in. BU Red is the only
   accent anywhere in the app.

   Shape lock (applied everywhere, no exceptions except the skip-link pill):
     cards & inputs 8px · buttons 6px · tags 4px · modals 10px.

   Motion policy: hover/active states only — no keyframes, no reveals. */
:root,
[data-bs-theme="light"] {
    --bu-red: #cc0000;
    --bu-red-hover: #990000;
    /* BU Red can't clear contrast minimums on a dark surface (~2.6:1), so dark
       mode uses this lighter tint of the same red wherever red must be
       perceivable (text, focus rings, the featured keel). */
    --bu-red-bright: #ff5a5a;
    --bu-black: #2d2926;

    --paper: #fbfaf9;      /* page background — near-white with BU Black's warmth */
    --surface: #ffffff;    /* cards, header, inputs */
    --ink: #2d2926;        /* body text — BU Black itself */
    --ink-muted: #5f5a55;  /* muted text — 6.5:1 on --paper, 6.8:1 on --surface (AA) */
    --hairline: #e5e2de;   /* borders, dividers */
    --hairline-strong: #cfcac4; /* hovered borders, input borders */

    --bs-body-bg: var(--paper);
    --bs-body-color: var(--ink);
    --bs-border-color: var(--hairline);
    --bs-secondary-color: var(--ink-muted);
    --bs-tertiary-bg: var(--surface);
    --bs-body-font-family: 'Libre Franklin', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    --bs-body-font-size: .95rem;
    --bs-body-line-height: 1.6;

    --bs-primary: var(--bu-red);
    --bs-primary-rgb: 204, 0, 0;
    --bs-link-color: var(--bu-red);
    --bs-link-color-rgb: 204, 0, 0;
    --bs-link-hover-color: var(--bu-red-hover);
    --bs-link-hover-color-rgb: 153, 0, 0;
}

/* Dark theme: the same warm ramp continued past black — derived from BU Black,
   not Bootstrap's cool near-blacks. Declared after bootstrap.css so it wins
   over Bootstrap's own [data-bs-theme="dark"] block at equal specificity. */
[data-bs-theme="dark"] {
    --paper: #181614;
    --surface: #211f1c;
    --ink: #ece9e6;        /* 13.6:1 on --surface */
    --ink-muted: #a8a29b;  /* 6.5:1 on --surface, 7.1:1 on --paper (AA) */
    --hairline: #3a3733;
    --hairline-strong: #4c4842;

    --bs-body-bg: var(--paper);
    --bs-body-color: var(--ink);
    --bs-border-color: var(--hairline);
    --bs-secondary-color: var(--ink-muted);
    --bs-tertiary-bg: var(--surface);

    --bs-link-color: var(--bu-red-bright);
    --bs-link-color-rgb: 255, 90, 90;
    --bs-link-hover-color: #ff7d7d;
    --bs-link-hover-color-rgb: 255, 125, 125;
}

html, body {
    background: var(--paper);
}

/* Headings: weight does the work; slight negative tracking at display sizes,
   and balanced wrapping so two-line headings don't orphan a word. */
h1, h2, h3, .h1, .h2, .h3 {
    letter-spacing: -0.01em;
    text-wrap: balance;
}

/* Page title: the one place the scarlet rule repeats — a short red tick under
   the h1, echoing the masthead bar. One brand gesture, used twice, nowhere else. */
.page-title::after {
    content: "";
    display: block;
    width: 2rem;
    height: 3px;
    margin-top: .55rem;
    border-radius: 2px;
    background: var(--bu-red);
}

/* Tabular figures for counts, analytics tables, and the chart. */
.tnum { font-variant-numeric: tabular-nums; }

/* Content column — single source of truth for the page width. */
.content-max { max-width: 1140px; }

/* Narrow column for single-purpose forms/pages (e.g. catalog import). */
.content-narrow { max-width: 640px; }

/* ===== Shape lock ============================================================ */
.card {
    --bs-card-border-radius: .5rem;      /* 8px */
    --bs-card-inner-border-radius: calc(.5rem - 1px);
    --bs-card-border-color: var(--hairline);
    --bs-card-bg: var(--surface);
}
.form-control, .form-select {
    border-radius: .5rem;                 /* 8px */
    border-color: var(--hairline-strong);
    background-color: var(--surface);
}
.btn { --bs-btn-border-radius: .375rem; } /* 6px */
.btn-sm { --bs-btn-border-radius: .375rem; }
.modal-content { border-radius: .625rem; } /* 10px */

/* Press feedback: buttons acknowledge the click physically. */
.btn:active { transform: translateY(1px); }

/* ===== Buttons ===============================================================
   Bootstrap's btn-primary derives its background from component-scoped vars,
   not --bs-primary, so set those explicitly to BU Red. */
.btn-primary {
    --bs-btn-bg: var(--bu-red);
    --bs-btn-border-color: var(--bu-red);
    --bs-btn-hover-bg: var(--bu-red-hover);
    --bs-btn-hover-border-color: var(--bu-red-hover);
    --bs-btn-active-bg: var(--bu-red-hover);
    --bs-btn-active-border-color: var(--bu-red-hover);
    --bs-btn-disabled-bg: var(--bu-red);
    --bs-btn-disabled-border-color: var(--bu-red);
}
.btn-outline-primary {
    --bs-btn-color: var(--bu-red);
    --bs-btn-border-color: var(--bu-red);
    --bs-btn-hover-bg: var(--bu-red);
    --bs-btn-hover-border-color: var(--bu-red);
    --bs-btn-active-bg: var(--bu-red);
    --bs-btn-active-border-color: var(--bu-red);
}

/* outline-secondary re-anchored on the warm ramp. A single gray can't pass AA
   on both a near-white and a near-black surface, so it's split by theme —
   ink-muted label in light, a light warm gray in dark — clearing WCAG 1.4.3
   (text) and 1.4.11 (the icon-only theme toggle). */
.btn-outline-secondary {
    --bs-btn-color: var(--ink-muted);
    --bs-btn-border-color: var(--hairline-strong);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--ink-muted);
    --bs-btn-hover-border-color: var(--ink-muted);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--ink-muted);
    --bs-btn-active-border-color: var(--ink-muted);
}
[data-bs-theme="dark"] .btn-outline-secondary {
    --bs-btn-hover-color: #181614;
    --bs-btn-hover-bg: var(--ink-muted);
    --bs-btn-hover-border-color: var(--ink-muted);
    --bs-btn-active-color: #181614;
    --bs-btn-active-bg: var(--ink-muted);
    --bs-btn-active-border-color: var(--ink-muted);
}

/* outline-danger stays on brand red — stock Bootstrap #dc3545 is off-brand and
   fails AA on dark cards. Dark mode uses the brighter brand red with a
   near-black hover label. */
.btn-outline-danger {
    --bs-btn-color: var(--bu-red);
    --bs-btn-border-color: var(--bu-red);
    --bs-btn-hover-bg: var(--bu-red);
    --bs-btn-hover-border-color: var(--bu-red);
    --bs-btn-active-bg: var(--bu-red-hover);
    --bs-btn-active-border-color: var(--bu-red-hover);
}
[data-bs-theme="dark"] .btn-outline-danger {
    --bs-btn-color: var(--bu-red-bright);
    --bs-btn-border-color: var(--bu-red-bright);
    --bs-btn-hover-color: #181614;
    --bs-btn-hover-bg: var(--bu-red-bright);
    --bs-btn-hover-border-color: var(--bu-red-bright);
    --bs-btn-active-color: #181614;
    --bs-btn-active-bg: var(--bu-red-bright);
    --bs-btn-active-border-color: var(--bu-red-bright);
}

/* btn-dark blends into the dark page background; lift it to a visible warm gray */
[data-bs-theme="dark"] .btn-dark {
    --bs-btn-bg: #403c37;
    --bs-btn-border-color: #524d47;
    --bs-btn-hover-bg: #4c4842;
    --bs-btn-hover-border-color: #5e594f;
    --bs-btn-active-bg: #57524b;
    --bs-btn-active-border-color: #6a6459;
}

/* ===== Muted-text + badge fixes ==============================================
   Bootstrap's .text-secondary is a fixed cool gray that fails AA on tinted
   surfaces. Point it at the warm muted token, which is AA-verified per theme
   (see token comments above). */
.text-secondary { color: var(--ink-muted) !important; }

/* Fixed light badge backgrounds (BlazorBootstrap BadgeColor.Light) */
[data-bs-theme="dark"] .bg-light,
[data-bs-theme="dark"] .text-bg-light {
    background-color: var(--bs-secondary-bg) !important;
}
[data-bs-theme="dark"] .text-bg-light {
    color: var(--ink-muted) !important;
}

/* ===== Tags ==================================================================
   Quiet washed-tint tags replace solid/pill badges. Text-on-tint pairs are
   AA-verified: light theme uses dark text on a pale ground; dark theme uses a
   light tint of the hue on a low-alpha ground.
     (light theme · dark theme, dark measured against the alpha ground blended
      over the card surface)
     new      6.43:1 · 7.82:1
     beta     6.05:1 · 7.23:1
     hidden   6.58:1 · 6.09:1
   Featured is the exception: solid BU Red with white text (5.89:1 both themes)
   — the catalog's single filled tag, a deliberate brand moment for the apps
   the university is putting forward. */
.tag {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .16rem .5rem;
    border-radius: 4px;
    font-size: .72rem;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
}
.tag-muted { color: var(--ink-muted); border: 1px solid var(--hairline); background: transparent; font-weight: 500; }
.tag-new      { color: #2c5f33; background: #e6f0e4; }
.tag-beta     { color: #7a5300; background: #faf0d9; }
.tag-hidden   { color: #9f2222; background: #fbe9e9; }
.tag-featured { color: #fff; background: var(--bu-red); }
[data-bs-theme="dark"] .tag-new      { color: #9fd8a8; background: rgba(110, 200, 130, .13); }
[data-bs-theme="dark"] .tag-beta     { color: #e3c072; background: rgba(230, 180, 80, .13); }
[data-bs-theme="dark"] .tag-hidden   { color: #ff8a8a; background: rgba(255, 90, 90, .13); }

/* Eyebrow: small uppercase group label. Used ONLY as a functional label for
   the link groups and details panel on the app-detail page — not a decoration
   to put above every heading. */
.eyebrow {
    font-size: .72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--ink-muted);
}

/* ===== Focus visibility (WCAG 2.4.7) ======================================== */
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-select:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.1rem var(--surface), 0 0 0 0.25rem var(--bu-red);
}
/* Bootstrap's focus border is its stock blue — keep the border on the warm
   ramp so the red ring is the only focus color. */
.form-control:focus, .form-select:focus { border-color: var(--hairline-strong); }

/* The app-name link in each card uses .stretched-link, whose absolutely
   positioned ::after overlay covers the whole card — so drawing the ring on
   that overlay makes the ENTIRE card show focus, not just the inline text. */
.stretched-link:focus-visible::after {
    outline: 3px solid var(--bu-red);
    outline-offset: 2px;
    border-radius: .5rem;
}

/* Plain links (chips, header nav, resource links) still need a visible ring. */
a:focus-visible {
    outline: 3px solid var(--bu-red);
    outline-offset: 2px;
    border-radius: 3px;
}

/* BU Red is only ~2.6:1 against the dark page, so the focus ring switches to
   the brighter red in dark mode (WCAG 2.4.7). */
[data-bs-theme="dark"] a:focus-visible,
[data-bs-theme="dark"] .stretched-link:focus-visible::after {
    outline-color: var(--bu-red-bright);
}
[data-bs-theme="dark"] .btn:focus, [data-bs-theme="dark"] .btn:active:focus,
[data-bs-theme="dark"] .form-control:focus, [data-bs-theme="dark"] .form-select:focus,
[data-bs-theme="dark"] .form-check-input:focus {
    box-shadow: 0 0 0 0.1rem var(--paper), 0 0 0 0.25rem var(--bu-red-bright);
}

/* Favorite toggle: colors keyed off data-fav so the JS only flips the
   attribute and both themes stay correct (dark needs the brighter red to
   clear the 3:1 graphics minimum, WCAG 1.4.11). */
.fav-btn { color: var(--ink-muted); font-size: 1.35rem; }
.fav-btn[data-fav="true"] { color: var(--bu-red); }
[data-bs-theme="dark"] .fav-btn[data-fav="true"] { color: var(--bu-red-bright); }

h1:focus {
    outline: none;
}

/* Blazor form validation + error UI (standard template styles, still emitted by EditForm) */
.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* MyApps styles */

/* The scarlet rule: a 4px BU-Red masthead bar across the very top of the
   viewport — the app's single brand gesture, echoing bu.edu's red masthead.
   Purely decorative (aria-hidden in markup). */
.scarlet-rule {
    height: 4px;
    background: var(--bu-red);
    flex: 0 0 auto;
}

/* Skip-to-content link: hidden until focused (Bootstrap .visually-hidden-focusable),
   then shown as a high-contrast pill at the top-left for keyboard users (WCAG 2.4.1). */
.skip-link:focus, .skip-link:focus-within {
    position: fixed;
    top: .5rem;
    left: .5rem;
    z-index: 1080;
    padding: .5rem .9rem;
    background: #cc0000;
    color: #fff;
    border-radius: 5px;
    font-weight: 600;
    text-decoration: none;
}

/* Official BU MyApps header logo. The logo may not be manipulated, so we only
   size it — no recoloring or distortion. Two artwork variants (black wordmark for
   light, white wordmark for dark) are swapped by theme rather than recolored. */
.brand-logo {
    height: 36px; width: auto; display: block;
}
.brand-logo-dark { display: none; }
[data-bs-theme="dark"] .brand-logo-light { display: none; }
[data-bs-theme="dark"] .brand-logo-dark { display: block; }

/* Header / footer: flat surfaces separated by hairlines, not tinted bands. */
.app-header { background: var(--surface); border-bottom: 1px solid var(--hairline); }
.header-row { height: 60px; }
.app-footer { background: transparent; border-top: 1px solid var(--hairline); }

/* Header nav links: quiet by default, ink on hover, red underline when the
   link points at the current page (aria-current set in markup). */
.nav-quiet {
    color: var(--ink-muted);
    text-decoration: none;
    font-weight: 500;
    font-size: .9rem;
    padding: .25rem 0;
    border-bottom: 2px solid transparent;
    transition: color .15s ease;
}
.nav-quiet:hover { color: var(--ink); }
.nav-quiet[aria-current="page"] { color: var(--ink); border-bottom-color: var(--bu-red); }
[data-bs-theme="dark"] .nav-quiet[aria-current="page"] { border-bottom-color: var(--bu-red-bright); }

/* Back navigation: a quiet text link, not a boxed button. */
.back-link {
    color: var(--ink-muted);
    text-decoration: none;
    font-weight: 500;
    font-size: .9rem;
}
.back-link:hover { color: var(--ink); }

/* Icon-only header button (theme toggle): borderless but AA-visible —
   ink-muted clears 3:1 on the header surface in both themes (WCAG 1.4.11). */
.nav-icon-btn { color: var(--ink-muted); border: 0; font-size: 1rem; }
.nav-icon-btn:hover { color: var(--ink); }

/* Logo thumbnails: fixed square wells so cards align whether or not an app
   has artwork; transparent-friendly on either theme. */
.app-logo { height: 40px; width: 40px; flex: 0 0 auto; object-fit: contain; background: transparent; }
.detail-logo { height: 60px; width: 60px; flex: 0 0 auto; object-fit: contain; background: transparent; }

/* Card hover: the hairline darkens and an ultra-diffuse warm shadow appears.
   No lift, no glow — motion is reserved for state feedback. */
.app-card-hover { transition: box-shadow .15s ease, border-color .15s ease; }
.app-card-hover:hover {
    border-color: var(--hairline-strong);
    box-shadow: 0 2px 8px rgba(45, 41, 38, .07);
}
[data-bs-theme="dark"] .app-card-hover:hover {
    border-color: var(--hairline-strong);
    box-shadow: 0 2px 8px rgba(0, 0, 0, .4);
}

/* Featured apps carry no card decoration — they sort first and wear the solid
   red .tag-featured (a text label, so the cue is perceivable without color or
   motion; AppCard also renders a visually-hidden "(Featured)" in the link). */

@media (prefers-reduced-motion: reduce) {
    .app-card-hover, .nav-quiet, .btn { transition: none; }
    .btn:active { transform: none; }
}

/* Category filter: full-width when the search form stacks (mobile), shrink to
   content width once it sits inline with the search box and button (≥576px). */
@media (min-width: 576px) {
    .search-category { width: auto; }
}

/* Theme toggle icon: moon in light mode, sun in dark */
.theme-icon-dark { display: none; }
[data-bs-theme="dark"] .theme-icon-light { display: none; }
[data-bs-theme="dark"] .theme-icon-dark { display: inline; }

/* Card metadata line (vendor) */
.card-vendor { font-size: .78rem; }

/* Let flex children shrink below their content width (truncation guard) */
.min-w-0 { min-width: 0; }

/* Clamp the listing description to three lines */
.app-desc {
    font-size: .88rem;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}

/* Preserve author line breaks in the detail description */
.detail-desc { white-space: pre-line; line-height: 1.65; }
.text-pre-line { white-space: pre-line; }

/* Soft modal backdrop for the hand-rolled list-edit modals */
.modal-backdrop-soft { background: rgba(24, 22, 20, .5); }

/* Scrollable app list inside the edit-list modal */
.list-scroll { max-height: 200px; overflow-y: auto; }

/* ===== Help / documentation page ============================================
   Offset anchor jumps so headings clear the 60px sticky header, and pin the
   "On this page" table of contents alongside the content on large screens. */
.help-section { scroll-margin-top: 76px; }

@media (min-width: 992px) {
    .help-toc { position: sticky; top: 76px; }
}

/* ===== Card / list view toggle ==============================================
   The active view is chosen by <html data-apps-view> (set before paint by the
   head script). Card view is the default, so .view-list is hidden unless the
   attribute is "list". */
.view-list { display: none; }
[data-apps-view="list"] .view-cards { display: none; }
[data-apps-view="list"] .view-list { display: block; }

/* Segmented toggle: the active button reads as pressed (BU Black fill). */
.view-toggle-btn.active {
    background-color: var(--ink);
    border-color: var(--ink);
    color: var(--surface);
}

/* ===== List (table) view ==================================================== */
.app-logo-sm { height: 24px; width: 24px; flex: 0 0 auto; object-fit: contain; background: transparent; }
.app-table th { white-space: nowrap; border-bottom-color: var(--hairline-strong); }
.app-table-star { width: 1%; white-space: nowrap; }

/* Sortable column headers look like header text, not buttons. */
.app-sort {
    background: none; border: 0; padding: 0; font: inherit; color: inherit;
    cursor: pointer; display: inline-flex; align-items: center; gap: .25rem;
}
.app-sort:hover { color: var(--bu-red); }
/* Direction caret: ↕ unsorted, ↑ asc, ↓ desc. */
.app-sort-ind::after { content: "\2195"; opacity: .35; }
.app-sort.sort-asc .app-sort-ind::after { content: "\2191"; opacity: 1; }
.app-sort.sort-desc .app-sort-ind::after { content: "\2193"; opacity: 1; }
