/* ===== 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, dividers */
    --control-border: #8a857e; /* input & outline-button borders — ≥3:1 on paper and surface (WCAG 1.4.11) */

    --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;
    --control-border: #7a746c; /* input & outline-button borders — ≥3:1 on paper and surface (WCAG 1.4.11) */

    --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(--control-border);
    --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;
    /* .26rem block padding puts every tag at ~24px tall: the category chips are links (see
       .tag-link) and 24×24 is the WCAG 2.2 AA target-size minimum (2.5.8). Applied to .tag rather
       than only the links so an interactive chip and a static one are never different heights in
       the same row. */
    padding: .26rem .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); }
.tag-paid     { color: #1c5f7a; background: #e1f0f6; }
/* Feedback inbox tags. The kind is neutral (it's a category, not a state); the three statuses
   borrow the same palette the app-status tags use, so "new" reads the same way in both places. */
.tag-kind            { color: var(--ink-muted); border: 1px solid var(--hairline); background: transparent; font-weight: 500; }
.tag-status-new      { color: #2c5f33; background: #e6f0e4; }
.tag-status-read     { color: #5f5a55; background: #eeecea; }
.tag-status-archived { color: #5f5a55; border: 1px solid var(--hairline); background: transparent; }
/* bi-currency-dollar renders thin at the tag's .72rem text size, so it gets its own bump: a larger
   glyph plus a hairline text-stroke to add weight an icon font's font-weight can't (bootstrap-icons
   ships one weight per glyph). */
.tag-paid i { font-size: 1.05em; -webkit-text-stroke: .25px currentColor; }
[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); }
[data-bs-theme="dark"] .tag-paid     { color: #7fc4e0; background: rgba(80, 170, 210, .13); }
[data-bs-theme="dark"] .tag-status-new      { color: #9fd8a8; background: rgba(110, 200, 130, .13); }
[data-bs-theme="dark"] .tag-status-read     { color: #b9b3ac; background: rgba(255, 255, 255, .07); }
[data-bs-theme="dark"] .tag-status-archived { color: #b9b3ac; border: 1px solid var(--hairline); background: transparent; }

/* A tag that is also a link: the category chips on cards, list rows and the detail page all filter
   the catalog. Kept visually identical to a static .tag-muted — the hairline darkening on hover is
   the only affordance, so a row of chips doesn't turn into a row of blue links. Deliberately not
   underlined: these read as chips, and the hover/focus states carry the interactivity (the
   surrounding text is never link-colored, so there's no adjacent-text ambiguity for WCAG 1.4.1). */
.tag-link, .tag-toggle { text-decoration: none; transition: color .12s ease, border-color .12s ease; }
.tag-link:hover, .tag-toggle:hover { color: var(--ink); border-color: var(--hairline-strong); }

/* The "+N" category overflow chip is a <button>, so it needs the pointer affordance a link gets for
   free. It inherits the rest of its look from .tag/.tag-muted (Bootstrap Reboot already makes
   buttons inherit font-family, so the chip type scale applies). */
.tag-toggle { cursor: pointer; }

/* 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);
}
/* Input/select outline at ≥3:1 on both surface and paper (WCAG 1.4.11). On focus
   the red ring is the indicator; keep the border on the warm ramp, not stock blue. */
.form-control, .form-select { border-color: var(--control-border); }
.form-control:focus, .form-select:focus { border-color: var(--control-border); }

/* 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. .tag-toggle rides along:
   it's a <button> but carries no .btn class, so neither the .btn ring above nor this link rule would
   otherwise reach it, and it would fall back to the UA default outline. */
a:focus-visible, .tag-toggle: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"] .tag-toggle: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); }

/* "Add to list" toggle (AddToListControl), beside the favorite bookmark on the app
   detail page. Same size and the same muted → red state change, keyed off
   data-inlist: this app is in at least one of your lists. Rendered server-side per
   request rather than flipped by JS, but the attribute selector keeps the two
   controls' styling parallel. */
.list-btn { color: var(--ink-muted); font-size: 1.35rem; line-height: 1; }
.list-btn[data-inlist="true"] { color: var(--bu-red); }
[data-bs-theme="dark"] .list-btn[data-inlist="true"] { color: var(--bu-red-bright); }

/* Fixed icon column in the menu, so list names align down the menu whether or not
   each row is checked. */
.list-btn-check { width: 1rem; flex: 0 0 auto; text-align: center; }

/* The menu can't grow past a phone's width on a long list name — the name truncates
   (.text-truncate on the span) inside this cap instead. */
.list-btn + .dropdown-menu { max-width: min(20rem, calc(100vw - 2rem)); }

/* ===== Toast confirmations ====================================================
   Feedback for actions that complete without a page reload — the favorite star
   and the "Add to list" menu.
   Bottom-center rather than a corner: the trigger can be any of ~70 cards, so
   there's no "near the button" to aim for, and the top is occupied by the sticky
   search bar. Fixed, above the sticky search (z 1020) and Bootstrap's dropdowns
   (1000) but below modals (1055), so it can't swallow a dialog's buttons.

   No animation, per the app's motion policy — no keyframes, no reveals. */
.toast-region {
    position: fixed;
    bottom: 1.25rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1040;
    display: flex;
    justify-content: center;
    /* Empty most of the time: never intercept clicks on the page underneath. The
       box carries no controls, so nothing here needs pointer events at all. */
    pointer-events: none;
    max-width: calc(100vw - 2rem);
}

.toast-msg {
    background: var(--ink);
    color: var(--paper);
    border-radius: .5rem;              /* card radius — it's a small surface, not a button */
    padding: .6rem 1rem;
    font-size: .9rem;
    font-weight: 500;
    box-shadow: 0 .5rem 1.25rem rgba(0, 0, 0, .28);
    /* Inverted against the page, so it reads as an overlay rather than another card.
       Wraps rather than truncates: an app name can be long and the message has to
       stay readable. */
    text-align: center;
}

/* Failure: BU Red, using the theme-appropriate tint so the text clears AA on it.
   Red is otherwise reserved for launch actions, but this is the one case where the
   message is that something didn't happen. */
.toast-msg-error {
    background: var(--bu-red-hover);
    color: #fff;
}
[data-bs-theme="dark"] .toast-msg-error {
    background: var(--bu-red);
    color: #fff;
}

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; }

/* The nav sits on the logo wordmark's baseline rather than on the row's midline.
   Everything in this row is centred by its box, and that isn't the same thing as
   looking aligned: the 36px logo art puts the "MyApps" cap band exactly on the
   row's centre, and the nav's own text box centres there too, but the wordmark's
   caps are 15.26px against the nav's 10.68px. Equal centring therefore leaves the
   smaller text's baseline 2.65px above the wordmark's (38.98px vs 41.63px from the
   header top, both measured at 60px row height), which reads as the nav floating
   high — most visibly on a phone, where the logo and the nav are the only two
   things in the row and sit close enough to compare directly.

   A relative offset, not a margin: with align-items:center the centring splits a
   margin-top between both sides, so it would move the nav only half as far. The
   whole nav shifts, not just .nav-quiet, so the link text and the "Sign out"
   button's label keep a shared baseline (they need 2.65px and 2.63px). Both the
   logo and the row are fixed px at every breakpoint, so one value holds. */
.header-row nav { position: relative; top: 2.65px; }
.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;
    /* Symmetric transparent borders so the text centers with the header buttons;
       the bottom one turns red as the active-page underline. */
    border-top: 2px solid transparent;
    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 links are a plain `.btn.btn-outline-secondary` "← Label" everywhere. */

/* 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; }

/* Monogram fallback (launcher tiles with no artwork): the app's initial in a neutral square, so
   every Favorites tile is a consistent, tappable square whether or not the app has a logo. */
.app-logo-monogram {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--hairline);
    color: var(--ink-muted);
    font-weight: 600;
    font-size: 1.15rem;
    line-height: 1;
}

/* Favorites dock: a wrap-around row of launcher tiles. Each tile is the app icon (or monogram),
   and the tile itself is the launch button — the .5rem padding around the 40px logo makes a ~58px
   tap target (well past WCAG 2.5.5's 44px). No badge: an icon in a labelled "Favorites" row reads
   as launch-on-click (the OS/Okta/Google convention); hover/focus give the usual feedback and the
   accessible name still says "Open <app> (opens in new tab)". */
/* Tiles pack tightly, dock-style. The visible distance between two tiles is the row gap plus the
   slack left over inside each .fav-item (its width minus the 58px tile), so both are kept small:
   4px + 7px either side lands ~18px apart, about half the ~34px it started at. The floor here is
   set by the caption, not the tile — see .fav-item. */
.fav-dock { align-items: flex-start; gap: .25rem; }

/* Each tile is captioned with the app name: the logo alone carries an occasional visitor only so
   far, and apps with no artwork fall back to a first-initial monogram, so two favorites starting
   with the same letter were indistinguishable. Fixed width keeps the dock on a tidy grid and the
   two-line clamp stops a long name from setting the row height. The caption is aria-hidden in the
   markup — the tile's aria-label already names the app.

   4.5rem (72px) is the width, and it's the caption that sets it rather than the 58px tile: it's the
   narrowest box that still fits the longest single word in the catalog on one line — "Mathematica"
   measures 70.9px and "NotebookLM" 71.0px at this font — so those names wrap after the word
   ("Mathematica" / "14.3") instead of being sliced mid-word. Anything narrower packs the tiles
   closer but starts cutting names in half. A future app with a longer unbreakable word will still
   break mid-word; the tile's title/aria-label carries the full name either way. */
.fav-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .35rem;
    width: 4.5rem;
}
.fav-caption {
    font-size: .72rem;
    line-height: 1.25;
    text-align: center;
    color: var(--ink-muted);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
    /* `anywhere`, not `break-word`: this caption is a centred column flex child, so its width is
       content-based and a long unbreakable name ("Mathematica", "NotebookLM") would overflow the
       item and collide with its neighbour's caption. `anywhere` lets the name break mid-word, which
       is what keeps the caption inside the 4rem box. */
    overflow-wrap: anywhere;
}

.app-launch-tile {
    display: inline-flex;
    padding: .5rem;
    border-radius: .625rem;
    border: 1px solid var(--hairline);
    background: var(--surface);
    line-height: 0;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.app-launch-tile:hover {
    border-color: var(--hairline-strong);
    box-shadow: 0 2px 8px rgba(45, 41, 38, .07);
}
[data-bs-theme="dark"] .app-launch-tile:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, .4); }
.app-launch-tile:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.1rem var(--surface), 0 0 0 0.25rem var(--bu-red);
}

/* 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, .app-launch-tile, .tag-link, .tag-toggle { 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; }
}

/* Blazor.Bootstrap's stylesheet styles the bare `main` element for its own default layout —
   `flex: 1 1 0%; overflow-x: hidden; height: 100vh` — none of which suits MainLayout. The
   overflow-x in particular breaks position: sticky inside <main>: per the CSS overflow spec an axis
   set to hidden forces the other axis's `visible` to compute as `auto`, which makes <main> its own
   scroll container, so a sticky child has only <main>'s non-scrolling box to stick to and never
   sticks at all. (Same spec quirk documented on .view-list further down.)

   overflow-x: clip gives the same "don't let anything bleed sideways" result WITHOUT promoting the
   other axis, so overflow-y stays visible and sticky resolves against the document scroller. The
   height is reset too — content, not the viewport, sets this page's height. app.css loads after
   blazor.bootstrap.css (see AppHost), so these win. */
main {
    overflow-x: clip;
    height: auto;
}

/* ===== Search toolbar =======================================================
   The catalog runs to ~70 apps, so the search box scrolls out of reach long before the grid ends
   and every find-an-app loop meant scrolling back to the top. Pinning it fixes that.

   Sticky only from 576px up: below that the form stacks vertically (flex-column) into ~200px of
   controls, which would eat most of a phone viewport if it were pinned. .app-header isn't sticky
   (it scrolls away), so top: 0 is correct and there's no header offset to keep in sync. The paper
   background and hairline let catalog cards pass cleanly underneath; z-index sits above the cards
   and their launch dropdowns (z-3) but below Bootstrap's modal/offcanvas layers (1050+). */
/* 1.5rem to match .section-divider's trailing margin, so the toolbar's rule and the section rules sit
   in the same vertical rhythm. */
.home-search { margin-bottom: 1.5rem; }

@media (min-width: 576px) {
    .home-search {
        position: sticky;
        top: 0;
        z-index: 1020;
        background: var(--paper);
        padding-block: .75rem;
        /* Same rule as .section-divider (2px, muted ink) so the toolbar's underline reads as one of the
           page's section breaks rather than a second, lighter kind of line. */
        border-bottom: 2px solid var(--ink-muted);
    }
    /* Keep the sticky bar from covering a section heading that gets scrolled to. */
    .home-section-title { scroll-margin-top: 5.5rem; }
}

/* The "/" shortcut hint (AppHost wires the key itself). Sits inside the field's trailing padding.
   Hidden while the field has focus — once you're typing it's stale — and on coarse pointers, where
   there's no physical key to press. It's aria-hidden in the markup, so it never reaches the field's
   accessible name. */
.search-wrap { min-width: 0; }
/* Room for the hint — but only while the field is empty, which is the only time the hint shows.
   Chrome lays the native clear button out at the end of the CONTENT box, so leaving this padding on
   once there's text shoved the "×" ~60px inward, nowhere near the right edge where you reach for it.
   Dropping back to the default padding puts it back on the edge. The text is left-aligned, so the
   padding change doesn't move anything visible as you type. */
@media (min-width: 576px) and (pointer: fine) {
    .search-wrap .form-control:placeholder-shown { padding-right: 2.75rem; }
}
.search-kbd {
    position: absolute;
    top: 50%;
    right: .6rem;
    transform: translateY(-50%);
    pointer-events: none;
    padding: .1rem .35rem;
    border: 1px solid var(--hairline-strong);
    border-radius: 4px;
    background: transparent;
    color: var(--ink-muted);
    font-family: inherit;
    font-size: .72rem;
    line-height: 1.4;
}
.search-wrap:focus-within .search-kbd { display: none; }
/* Also hide it the moment the field has content, not just while focused: that's exactly when the
   browser's clear button appears, in the same spot. Clicking that button doesn't reliably focus the
   input, so :focus-within alone left the hint sitting on top of the target you were aiming at.
   (:placeholder-shown is true only while the field is empty, which is why it needs a placeholder.) */
.search-wrap .form-control:not(:placeholder-shown) + .search-kbd { display: none; }

/* Chrome/Safari's native search clear button is ~13px — under the 24px WCAG 2.2 AA target minimum
   (2.5.8) and fiddly to hit. Swap it for a 1.5rem target with the same glyph, drawn as a mask over
   background-color so it tracks --ink-muted in both themes instead of being a baked-in color.
   Firefox renders no clear button at all, so this is a progressive improvement, not a dependency. */
.search-wrap .form-control::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
    height: 1.5rem;
    width: 1.5rem;
    cursor: pointer;
    background-color: var(--ink-muted);
    -webkit-mask: center / .85rem .85rem no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 4 L12 12 M12 4 L4 12' stroke='%23000' stroke-width='2' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
    mask: center / .85rem .85rem no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 4 L12 12 M12 4 L4 12' stroke='%23000' stroke-width='2' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
}
.search-wrap .form-control::-webkit-search-cancel-button:hover { background-color: var(--ink); }
@media (max-width: 575.98px), (pointer: coarse) {
    .search-kbd { display: none; }
}

/* 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; }

/* Quick-launch control: one uniform size everywhere. Every card's launch button (single "Open"
   or the "Open" dropdown) shares this min-width so they line up regardless of the access type,
   and none stretches to a full-width bar. */
.card-launch .btn { min-width: 6.5rem; }

/* Category overflow. Only the first two chips show by default so a four-category app can't wrap the
   footer and inflate every h-100 card in its grid row; the rest sit in the DOM behind
   .card-cat-extra until the "+N" button adds .is-expanded (see expandCardCats in AppHost, which then
   removes the button — expanding is one-way and lasts until the next load). Expanding deliberately
   grows the card, and with it its whole grid row, which is the point: it's the one card you asked to
   see in full. A plain display swap, no height animation: motion stays reserved for state feedback. */
.card-cats .card-cat-extra { display: none; }
.card-cats.is-expanded .card-cat-extra { display: inline-flex; }

/* At in-between card widths (Bootstrap's 2-column range) a long category label plus the fixed-width
   .card-launch button no longer fit on one row. .card-cats already has min-w-0 so it can shrink, but
   .tag's white-space: nowrap means an overlong chip just overflows instead of shrinking with it — so
   each chip gets its own cap and truncates instead of pushing the button out of the card. */
.card-cats .tag { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

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

/* Clamp the listing description to two lines — keeps catalog cards compact now that they also
   carry a launch control. */
.app-desc {
    font-size: .88rem;
    display: -webkit-box; -webkit-line-clamp: 2; -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); }

/* ===== Lists: master–detail hub + inline editor =============================
   Rail of lists on the left, the selected list's apps or editor on the right. */
@media (min-width: 992px) {
    .ml-grid { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 1.5rem; align-items: start; }
    .ml-rail { position: sticky; top: 76px; }
}
/* Stacked on mobile — gap + divider so the panes don't run together. */
@media (max-width: 991.98px) {
    .ml-detail { margin-top: 1.75rem; padding-top: 1.75rem; border-top: 1px solid var(--hairline); }
}
.ml-rail-eyebrow { font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-muted); }
.ml-rail-item {
    display: flex; align-items: center; gap: .6rem; width: 100%;
    padding: .5rem .7rem; border: 0; border-left: 3px solid transparent;
    border-radius: .4rem; background: transparent; color: var(--ink); text-align: left;
}
.ml-rail-item:hover { background: var(--surface); }
.ml-rail-item.active { background: var(--surface); border-left-color: var(--bu-red); font-weight: 600; }
[data-bs-theme="dark"] .ml-rail-item.active { border-left-color: var(--bu-red-bright); }
.ml-rail-item .ml-rail-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml-rail-item .ml-rail-count { flex: 0 0 auto; font-size: .78rem; font-weight: 400; color: var(--ink-muted); }
.ml-rail-icon { flex: 0 0 auto; color: var(--ink-muted); }
.ml-rail-item.active .ml-rail-icon { color: var(--bu-red); }
[data-bs-theme="dark"] .ml-rail-item.active .ml-rail-icon { color: var(--bu-red-bright); }

/* Stand-in tile for apps with no logo. */
.le-logo-fallback {
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--surface); border: 1px solid var(--hairline);
    color: var(--ink-muted); font-size: .7rem; font-weight: 600;
}

/* Editor: draggable rows + the search-to-add picker. */
.le-row {
    display: flex; align-items: center; gap: .6rem;
    padding: .4rem .5rem; border: 1px solid transparent; border-radius: .5rem;
}
.le-row + .le-row { margin-top: .1rem; }
.le-row:hover { background: var(--surface); }
.le-handle { cursor: grab; color: var(--ink-muted); }
.le-dragging { opacity: .45; }
.le-over { border-color: var(--bu-red); background: var(--surface); }
.le-results { border: 1px solid var(--hairline); border-radius: .5rem; overflow: hidden; }
.le-add-row {
    display: flex; align-items: center; gap: .6rem; width: 100%;
    padding: .45rem .6rem; border: 0; border-top: 1px solid var(--hairline);
    background: var(--surface); color: var(--ink);
}
.le-add-row:first-child { border-top: 0; }
.le-add-row:hover { background: var(--paper); }

/* ===== Help / documentation pages ==========================================
   Topic cards up top jump to collapsible sections, so the page stays short. */

/* Clear the sticky header on jump. */
.help-section, .help-details { scroll-margin-top: 76px; }

/* Topic cards — same hover as the app cards. */
.help-topic-card {
    display: flex;
    gap: .7rem;
    align-items: flex-start;
    height: 100%;
    padding: .9rem 1rem;
    border: 1px solid var(--hairline);
    border-radius: .5rem;
    background: var(--surface);
    color: inherit;
    text-decoration: none;
    transition: box-shadow .15s ease, border-color .15s ease;
}
.help-topic-card:hover {
    color: inherit;
    border-color: var(--hairline-strong);
    box-shadow: 0 2px 8px rgba(45, 41, 38, .07);
}
[data-bs-theme="dark"] .help-topic-card:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, .4); }
.help-topic-icon { flex: 0 0 auto; font-size: 1.1rem; line-height: 1.5; color: var(--bu-red); }
[data-bs-theme="dark"] .help-topic-icon { color: var(--bu-red-bright); }
.help-topic-title { font-weight: 600; }
.help-topic-hint { font-size: .82rem; color: var(--ink-muted); }

/* Native <details> — works without JS and stays accessible. */
.help-details {
    border: 1px solid var(--hairline);
    border-radius: .5rem;
    background: var(--surface);
    margin-bottom: .75rem;
    overflow: hidden;
}
.help-details > summary {
    display: flex;
    align-items: center;
    gap: .65rem;
    padding: .85rem 1rem;
    cursor: pointer;
    font-weight: 600;
    list-style: none;                 /* hide the default triangle (Firefox) */
}
.help-details > summary::-webkit-details-marker { display: none; } /* ...and WebKit */
.help-details > summary:hover { color: var(--bu-red); }
[data-bs-theme="dark"] .help-details > summary:hover { color: var(--bu-red-bright); }
.help-details > summary:focus-visible {
    outline: 3px solid var(--bu-red);
    outline-offset: -3px;
    border-radius: .5rem;
}
.help-summary-icon { flex: 0 0 auto; font-size: 1.05rem; color: var(--bu-red); }
[data-bs-theme="dark"] .help-summary-icon { color: var(--bu-red-bright); }
.help-summary-text { flex: 1 1 auto; min-width: 0; }
.help-summary-hint { display: block; font-weight: 400; font-size: .82rem; color: var(--ink-muted); }
/* Chevron flips when the section opens. */
.help-summary-chev { flex: 0 0 auto; color: var(--ink-muted); transition: transform .15s ease; }
.help-details[open] > summary .help-summary-chev { transform: rotate(180deg); }
.help-details[open] > summary { border-bottom: 1px solid var(--hairline); }
.help-details-body { padding: 1rem 1rem 1.1rem; }
.help-details-body > :last-child { margin-bottom: 0; }

@media (prefers-reduced-motion: reduce) {
    .help-topic-card, .help-summary-chev { transition: none; }
}

/* ===== Home sections ========================================================
   The home page stacks curated quick-access rows (Recently launched, Favorites,
   Featured at BU) above the full catalog ("All Applications"). To keep them from
   reading as one undifferentiated wall of cards, each row wears a labelled header
   with its own icon, the rows stay compact (launch cards with no description), and
   a full-width rule marks the break down to the catalog. No heavy fills — the cue
   is the header + the shape contrast, in keeping with the flat, hairline design. */
.home-section { margin-bottom: 1.5rem; }

.home-section-title {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: 1.05rem;
    font-weight: 600;
    margin-bottom: .85rem;
}
/* Section-header icons: muted by default so the label leads; the Featured star is
   the one BU-Red accent, marking the institution's picks. */
.home-section-title > i { color: var(--ink-muted); font-size: .95em; }
.home-section-title > i.home-featured-star { color: var(--bu-red); }
[data-bs-theme="dark"] .home-section-title > i.home-featured-star { color: var(--bu-red-bright); }

/* Divider between page sections — between the home page's curated rows and before the catalog, and
   before "Similar apps" at the foot of an app page. Muted-ink tone AND 2px (vs the 1px table rules)
   so it reads as a deliberate section break: in list view a 1px hairline-colored rule looked like
   just another table row line, so both the color and the extra weight set it apart. It's decorative
   (no meaning carried by the color), so this is a design choice, not a contrast requirement.

   No top margin: the home page's sections carry their own bottom spacing. A caller that needs it
   (the app detail page, where the rule follows a card) adds mt-* at the usage site. */
.section-divider {
    border: 0;
    border-top: 2px solid var(--ink-muted);
    opacity: 1;
    margin: 0 0 1.5rem;
}

/* ===== 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; }

/* .view-list is a Bootstrap .table-responsive, which sets overflow-x:auto for
   narrow screens. Per the CSS overflow spec, an axis set to auto forces the
   other axis's `visible` to compute as `auto` too — so the table's fractional
   height (e.g. 164.25px) leaves scrollHeight 1px over clientHeight and paints a
   phantom vertical scrollbar that scrolls nothing (and reads as dead space
   below the list). These tables never scroll vertically, so pin overflow-y to
   hidden; horizontal scrolling on mobile is unaffected. */
.view-list { overflow-y: hidden; }

/* 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; }

/* Vendor mark inside the Self Service helper button (Company Portal / IRU). object-fit keeps a
   non-square source from stretching, and the box stays 20px so the button matches the pin beside it. */
.helper-logo { height: 20px; width: 20px; 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; }
/* Launch column: shrink-to-fit and right-aligned, so the buttons form a clean edge down the list
   however wide the name and vendor columns run. */
.app-table-open { width: 1%; white-space: nowrap; text-align: right; }

/* 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; }

/* ===== Admin catalog toolbar =================================================
   Title on the left; the search box and the New App button. On desktop they sit
   together on the right of one row (search aligned beside New App); on mobile the
   search drops to its own full-width row below the title + New App. */
.admin-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.admin-toolbar-title { display: flex; align-items: center; gap: 1rem; margin-right: auto; }
.admin-toolbar-search { display: flex; gap: .5rem; order: 3; flex: 1 0 100%; }
.admin-toolbar-search .form-control { flex: 1 1 auto; min-width: 0; }
.admin-toolbar-new { order: 2; }
@media (min-width: 768px) {
    .admin-toolbar-search { order: 2; flex: 0 1 auto; }
    .admin-toolbar-search .form-control { flex: 0 0 18rem; max-width: 100%; }
    .admin-toolbar-new { order: 3; }
}

/* ===== Admin apps table: stacked cards on mobile ============================
   The 7-column admin catalog overflows on phones, hiding the Status column and
   the Edit/Delete buttons behind a sideways scroll. Below the md breakpoint each
   row becomes a free-standing card: Name is the title, the rest are label-above-
   value pairs, and the actions get their own divided row — everything reachable
   with no horizontal scroll. Table semantics survive the display change via the
   ARIA roles in the markup (browsers drop the native ones once cells aren't
   table-cells); the header row is visually hidden but kept for assistive tech. */
@media (max-width: 767.98px) {
    /* Drop the wrapping Card's frame so the rows read as the cards, not a card
       nested in a card — they sit on the page (--paper), each one --surface. */
    .admin-apps-card { border: 0; background: transparent; box-shadow: none; }

    .admin-app-table thead {
        position: absolute; width: 1px; height: 1px; overflow: hidden;
        clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
    }
    .admin-app-table, .admin-app-table tbody { display: block; width: 100%; }

    /* One card per app. flex + gap spaces the fields evenly and skips any hidden
       (empty) cell, so a non-featured app leaves no gap where its row would be. */
    .admin-app-table tbody tr {
        display: flex; flex-direction: column; gap: .55rem;
        padding: 1rem 1.1rem; margin-bottom: .75rem;
        background: var(--surface);
        border: 1px solid var(--hairline);
        border-radius: .6rem;
    }
    .admin-app-table tbody tr:last-child { margin-bottom: 0; }

    /* Cells inherit the card's --surface instead of Bootstrap's table-cell bg
       (--bs-table-bg resolves to --paper) and drop the row-hover accent, so every
       field matches the card in both themes. Text stays legible on --surface:
       --ink 13.6:1, --ink-muted 6.5:1 — both clear AA. */
    .admin-app-table > :not(caption) > * > * { background-color: transparent; box-shadow: none; }

    /* Each field stacks its label above the value — no more label-left/value-right
       split straddling the card. */
    .admin-app-table td { display: block; padding: 0; border: 0; }
    .admin-app-table td:empty { display: none; }
    .admin-app-table td::before {
        content: attr(data-label); display: block;
        font-size: .68rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
        color: var(--ink-muted); margin-bottom: .15rem;
    }

    /* Name is the card title: drop its label, size it up, keep the logo inline.
       Selectors are scoped through `td` so they outrank `.admin-app-table td`. */
    .admin-app-table td.admin-app-name::before { display: none; }
    .admin-app-table td.admin-app-name { font-size: 1.05rem; font-weight: 600; }

    /* Actions: a left-aligned button row set off from the fields by a divider. */
    .admin-app-table td.admin-app-actions::before { display: none; }
    .admin-app-table td.admin-app-actions {
        display: flex; flex-wrap: wrap; gap: .5rem; text-align: left;
        margin-top: .35rem; padding-top: .85rem;
        border-top: 1px solid var(--hairline);
    }
}

/* ---------------------------------------------------------------------------
   Feedback
   --------------------------------------------------------------------------- */

/* Settings screens (SettingsPage). Same measure CatalogTagAdmin uses for its form card: a column of
   checkboxes and short number fields reads badly at full content width. */
.admin-settings { max-width: 760px; }

/* The /feedback page. Narrow measure: this is one short form, and letting it run the full content
   width would make a five-line textarea look like a wall. */
.feedback-page { max-width: 40rem; }

/* The "someone already reported this" note in the form. Quiet by design — it's reassurance, not a
   warning, and it must not read as "don't bother telling us". */
.feedback-note {
    font-size: .875rem;
    color: var(--ink-muted);
    border-left: 3px solid var(--hairline-strong);
    padding: .35rem 0 .35rem .6rem;
}

/* One message in the admin inbox. Rows sit inside a per-app card, so the divider is between
   messages rather than around them. */
.feedback-row + .feedback-row {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--hairline);
}

/* Unread messages carry a BU Red edge, so a card of mixed statuses still reads at a glance. */
.feedback-row-new {
    border-left: 3px solid var(--bu-red);
    padding-left: .75rem;
    margin-left: -.75rem;
}

[data-bs-theme="dark"] .feedback-note { border-left-color: var(--hairline-strong); }
[data-bs-theme="dark"] .feedback-row-new { border-left-color: var(--bu-red-bright); }
