@import '/shared/css/portal-chrome.css';

/* File: portal-admin/css/admin-portal-identity.css
 *
 * Administration's own look.
 *
 * This portal changes how the system behaves for everybody else: who holds which role, where
 * a ticket is routed, what an SLA promises, what is published. It should not feel like the
 * other two, and it should not feel casual. So its public surfaces are ink rather than paper,
 * its hero sits inside a drawn frame instead of spreading across the page, and its type is
 * small, letter-spaced and deliberate — the visual language of an instrument panel rather
 * than of a document (Support) or a working queue (Departments).
 *
 * The restriction is stated, not implied: every public surface carries a "restricted" band
 * naming what is required. Somebody who lands here by accident should be able to tell in one
 * glance that this is not their portal.
 *
 * Same ECVALLA palette and typefaces as the other two. Nothing else shared.
 *
 * Loads after ecvalla-identity.css and portal-auth.css.
 */

.ap-portal {
    margin: 0;
    background: var(--ecv-canvas, #f5f5f3);
    color: var(--text-color, #17191b);
}

/* ================================================================== *
 * Admin Home — Viewport-Fitted Command Console
 * ================================================================== */

.ahome {
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 18px clamp(18px, 2.4vw, 44px) 10px;
    height: calc(100svh - 66px);
    height: calc(100dvh - 66px);
    max-height: calc(100svh - 66px);
    max-height: calc(100dvh - 66px);
    display: flex;
    flex-direction: column;
    gap: 12px;
    box-sizing: border-box;
    overflow: hidden;
    background: var(--ecv-canvas, #f5f5f3);
    color: var(--text-color, #17191b);
}

/* Hosts the shared front-door texture (.phero-canvas, in shared/css/portal-chrome.css).
   `isolation` is what the mount needs: it keeps the canvas's z-index: -1 inside the panel,
   above the panel's own background and below its content, rather than behind the page. */
.ahero {
    position: relative;
    isolation: isolate;
    margin: 0;
    padding: clamp(28px, 4vw, 72px) clamp(28px, 5vw, 88px);
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 12px;
    text-align: start;
    max-width: none;
    width: 100%;
    box-sizing: border-box;
    flex: 1 1 0;
    min-height: 0;
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(300px, .9fr);
    gap: clamp(36px, 7vw, 128px);
    align-items: center;
    overflow: hidden;
    background: var(--ecv-paper, #ffffff);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
}

.ahero-copy {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 700px;
    justify-self: start;
}

.ahero-copy > * {
    animation: ahero-copy-enter .72s cubic-bezier(.16, 1, .3, 1) both;
}

.ahero-copy > :nth-child(1) { animation-delay: .08s; }
.ahero-copy > :nth-child(2) { animation-delay: .16s; }
.ahero-copy > :nth-child(3) { animation-delay: .25s; }
.ahero-copy > :nth-child(4),
.ahero-copy > :nth-child(5) { animation-delay: .34s; }
.ahero-copy > :nth-child(6),
.ahero-copy > :nth-child(7) { animation-delay: .43s; }

/* Corner ticks on the frame */
.ahero::before,
.ahero::after {
    content: "";
    position: absolute;
    width: 14px;
    height: 14px;
    border: 2px solid var(--ecv-muted, #73787b);
}

.ahero::before {
    top: -1px;
    inset-inline-start: -1px;
    border-inline-end: 0;
    border-bottom: 0;
}

.ahero::after {
    bottom: -1px;
    inset-inline-end: -1px;
    border-inline-start: 0;
    border-top: 0;
}

.ahero-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 12px;
    padding: 4px 12px;
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 4px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: var(--ecv-muted, #73787b);
    background: var(--ecv-soft, #f0f0ee);
}

.ahero-eyebrow i {
    transform-origin: 50% 70%;
    animation: ahero-lock-confirm 5.5s ease-in-out 1.2s infinite;
}

html[dir="rtl"] .ahero-eyebrow {
    letter-spacing: normal;
    text-transform: none;
    font-size: 12.5px;
}

.ahero-art {
    position: relative;
    margin: 0;
    max-width: 540px;
    width: 100%;
    justify-self: end;
    isolation: isolate;
    animation: ahero-art-enter .95s cubic-bezier(.16, 1, .3, 1) .18s both;
}

.ahero-art::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 8% 3% 2% 6%;
    border-radius: 32px;
    background: rgba(23, 25, 27, .1);
    filter: blur(38px);
    opacity: .62;
}

.aconsole {
    position: relative;
    display: grid;
    grid-template-rows: auto 1fr 1fr auto;
    gap: 14px;
    min-height: 390px;
    padding: 24px;
    box-sizing: border-box;
    overflow: hidden;
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 24px;
    background:
        linear-gradient(rgba(23, 25, 27, .035) 1px, transparent 1px),
        linear-gradient(90deg, rgba(23, 25, 27, .035) 1px, transparent 1px),
        var(--ecv-soft, #f0f0ee);
    background-size: 28px 28px;
    box-shadow: 0 28px 70px rgba(23, 25, 27, .16), inset 0 1px 0 rgba(255, 255, 255, .7);
}

.aconsole-sheen {
    position: absolute;
    z-index: 0;
    inset: 0 auto 0 -45%;
    width: 36%;
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .42), transparent);
    transform: skewX(-16deg);
    animation: aconsole-sheen 8s cubic-bezier(.16, 1, .3, 1) 1.4s infinite;
    pointer-events: none;
}

.aconsole-head,
.aconsole-card,
.aconsole-audit {
    position: relative;
    z-index: 1;
}

.aconsole-head {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 38px;
    padding: 0 3px 10px;
    border-bottom: 1px solid var(--ecv-line, #e5e6e4);
}

.aconsole-mark,
.aconsole-icon,
.aconsole-audit-icon {
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    color: var(--text-color, #17191b);
}

.aconsole-mark {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    background: var(--text-color, #17191b);
    color: var(--ecv-paper, #ffffff);
}

.aconsole-head-lines,
.aconsole-audit-lines {
    display: grid;
    gap: 6px;
    flex: 1 1 auto;
}

.aconsole-head-lines i,
.aconsole-audit-lines i,
.aconsole-line {
    display: block;
    height: 7px;
    border-radius: 99px;
    background: var(--text-color, #17191b);
}

.aconsole-head-lines i:first-child { width: 82px; opacity: .78; }
.aconsole-head-lines i:last-child { width: 54px; opacity: .2; }

.aconsole-live {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 11px;
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 999px;
    background: var(--ecv-paper, #ffffff);
}

.aconsole-live i {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #13813b;
    box-shadow: 0 0 0 5px rgba(19, 129, 59, .12);
    animation: aconsole-live 2.4s ease-in-out infinite;
}

.aconsole-live b {
    width: 34px;
    height: 6px;
    border-radius: 99px;
    background: var(--text-color, #17191b);
    opacity: .5;
}

.aconsole-card {
    display: flex;
    align-items: center;
    gap: 16px;
    min-height: 102px;
    padding: 18px;
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 16px;
    background: var(--ecv-paper, #ffffff);
    box-shadow: 0 12px 30px rgba(23, 25, 27, .09);
}

.aconsole-access {
    margin-inline-end: 22px;
    animation: aconsole-card-a 5.8s ease-in-out 1s infinite;
}

.aconsole-routing {
    margin-inline-start: 22px;
    animation: aconsole-card-b 6.4s ease-in-out 1.4s infinite;
}

.aconsole-icon {
    width: 44px;
    height: 44px;
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 13px;
    background: var(--ecv-soft, #f0f0ee);
    font-size: 16px;
}

.aconsole-body {
    display: grid;
    gap: 9px;
    min-width: 0;
    flex: 1 1 auto;
}

.aconsole-line.is-strong { width: 48%; opacity: .86; }
.aconsole-line.is-soft { width: 72%; height: 6px; opacity: .17; }

.aconsole-people {
    display: flex;
    padding-top: 1px;
}

.aconsole-people i {
    width: 21px;
    height: 21px;
    margin-inline-end: -5px;
    border: 2px solid var(--ecv-paper, #ffffff);
    border-radius: 50%;
    background: var(--ecv-muted, #73787b);
}

.aconsole-people i:nth-child(2) { opacity: .72; }
.aconsole-people i:nth-child(3) { opacity: .44; }

.aconsole-state,
.aconsole-audit-seal {
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: rgba(19, 129, 59, .12);
    color: #13813b;
}

.aconsole-state.is-clock {
    background: var(--ecv-soft, #f0f0ee);
    color: var(--text-color, #17191b);
}

.aconsole-route {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: min(100%, 210px);
    height: 18px;
}

.aconsole-route::before,
.aconsole-route b {
    content: "";
    position: absolute;
    inset-inline: 4px;
    top: 8px;
    height: 2px;
    border-radius: 99px;
}

.aconsole-route::before { background: var(--ecv-line, #e5e6e4); }

.aconsole-route b {
    background: var(--text-color, #17191b);
    transform: scaleX(.28);
    transform-origin: left center;
    animation: aconsole-route 3.8s cubic-bezier(.16, 1, .3, 1) infinite;
}

html[dir="rtl"] .aconsole-route b { transform-origin: right center; }

.aconsole-route i {
    position: relative;
    z-index: 1;
    width: 10px;
    height: 10px;
    border: 3px solid var(--ecv-paper, #ffffff);
    border-radius: 50%;
    background: var(--ecv-muted, #73787b);
}

.aconsole-audit {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 50px;
    padding: 9px 12px;
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 13px;
    background: rgba(255, 255, 255, .45);
}

.aconsole-audit-icon { width: 28px; height: 28px; font-size: 14px; }
.aconsole-audit-lines i:first-child { width: 46%; opacity: .55; }
.aconsole-audit-lines i:last-child { width: 66%; height: 5px; opacity: .14; }
.aconsole-audit-seal { width: 30px; height: 30px; font-size: 11px; }

@keyframes aconsole-sheen {
    0%, 72% { transform: skewX(-16deg) translateX(0); opacity: 0; }
    76% { opacity: .75; }
    92%, 100% { transform: skewX(-16deg) translateX(520%); opacity: 0; }
}

@keyframes aconsole-live {
    0%, 100% { transform: scale(.9); opacity: .72; }
    50% { transform: scale(1.12); opacity: 1; }
}

@keyframes aconsole-card-a {
    0%, 100% { transform: translateY(0) rotate(-.25deg); }
    50% { transform: translateY(-6px) rotate(0); }
}

@keyframes aconsole-card-b {
    0%, 100% { transform: translateY(0) rotate(.25deg); }
    50% { transform: translateY(5px) rotate(0); }
}

@keyframes aconsole-route {
    0%, 12% { transform: scaleX(.08); opacity: .45; }
    70%, 100% { transform: scaleX(1); opacity: .9; }
}

@keyframes ahero-copy-enter {
    from {
        opacity: 0;
        transform: translateY(18px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes ahero-art-enter {
    from {
        opacity: 0;
        transform: translateX(34px) scale(.96);
    }
    to {
        opacity: 1;
        transform: translateX(0) scale(1);
    }
}

@keyframes ahero-lock-confirm {
    0%, 84%, 100% { transform: rotate(0deg) scale(1); }
    88% { transform: rotate(-7deg) scale(1.08); }
    92% { transform: rotate(7deg) scale(1.08); }
    96% { transform: rotate(0deg) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .ahero-copy > *,
    .ahero-eyebrow i,
    .ahero-art,
    .aconsole-sheen,
    .aconsole-live i,
    .aconsole-access,
    .aconsole-routing,
    .aconsole-route b {
        animation: none !important;
        transform: none;
        opacity: 1;
    }
}

.ahero h1 {
    margin: 0 0 18px;
    max-width: 15ch;
    font-size: clamp(42px, 4.6vw, 68px);
    line-height: 1.02;
    letter-spacing: -.028em;
    font-weight: 800;
    color: var(--text-color, #17191b);
}

.ahero h1 em {
    display: block;
    font-style: normal;
    color: var(--ecv-muted, #73787b);
    margin-inline-start: 0;
}

.ahero-lede {
    margin: 0 0 24px;
    max-width: 62ch;
    font-size: clamp(14px, 1.15vw, 17px);
    line-height: 1.6;
    color: var(--ecv-muted, #73787b);
}

.ahero-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    justify-content: flex-start;
    margin-bottom: 14px;
}

.ahero-btn {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    min-height: 50px;
    padding: 0 28px;
    border: 1px solid var(--ecv-ink, #17191b);
    border-radius: 10px;
    background: var(--ecv-ink, #17191b);
    color: #ffffff !important;
    font-size: 15px;
    font-weight: 700;
    text-decoration: none;
    box-shadow: 0 4px 14px rgba(23, 25, 27, 0.18);
    transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1), background-color 0.18s ease, box-shadow 0.18s ease;
    position: relative;
    isolation: isolate;
    overflow: hidden;
}

.ahero-btn::after {
    content: "";
    position: absolute;
    z-index: 0;
    inset: -50% auto -50% -35%;
    width: 28%;
    background: rgba(255, 255, 255, .24);
    transform: skewX(-18deg) translateX(-260%);
    transition: transform .55s cubic-bezier(.16, 1, .3, 1);
    pointer-events: none;
}

.ahero-btn:hover::after,
.ahero-btn:focus-visible::after {
    transform: skewX(-18deg) translateX(620%);
}

.ahero-btn,
.ahero-btn:visited {
    color: #ffffff !important;
}

.ahero-btn i,
.ahero-btn span {
    position: relative;
    z-index: 1;
    color: #ffffff !important;
}

.ahero-btn:hover,
.ahero-btn:focus {
    background: #2b2e30 !important;
    color: #ffffff !important;
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(23, 25, 27, 0.3);
}

.ahero-btn:hover i,
.ahero-btn:hover span {
    color: #ffffff !important;
}

.ahero-btn.is-ghost {
    background: transparent;
    color: var(--text-color, #17191b) !important;
    border-color: var(--ecv-line, #e5e6e4);
    box-shadow: none;
}

.ahero-btn.is-ghost i,
.ahero-btn.is-ghost span {
    color: var(--text-color, #17191b) !important;
}

.ahero-btn.is-ghost:hover {
    background: var(--ecv-soft, rgba(0, 0, 0, 0.05)) !important;
    color: var(--text-color, #17191b) !important;
}

.ahero-btn:focus-visible {
    outline: 2px solid var(--ecv-ink, #17191b);
    outline-offset: 3px;
}

.ahero-note {
    margin: 0;
    font-size: 12px;
    color: var(--ecv-muted, #73787b);
}

.ahero-note b {
    color: var(--text-color, #17191b);
}

.ahero-note button {
    border: 0;
    background: transparent;
    padding: 0;
    color: var(--text-color, #17191b);
    font: inherit;
    font-weight: 700;
    text-decoration: underline;
    cursor: pointer;
}

.ahero-note button:focus-visible {
    outline: 2px solid var(--ecv-ink, #17191b);
    outline-offset: 2px;
}

/* ------------------------------------------------------------------ *
 * Home — Administration Governance Areas Grid
 * ------------------------------------------------------------------ */

.aareas {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 12px;
    margin: 0;
    max-width: none;
    width: 100%;
    flex-shrink: 0;
}

.aarea-card {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    background: var(--ecv-paper, #ffffff);
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
    transition: border-color 0.18s ease, transform 0.18s ease;
}

.aarea-card:hover {
    border-color: var(--ecv-ink, #17191b);
    transform: translateY(-1px);
}

.aarea-card i {
    font-size: 16px;
    color: var(--ecv-ink, #17191b);
    margin-top: 2px;
    flex-shrink: 0;
}

.aarea-title {
    display: block;
    font-size: 13px;
    font-weight: 700;
    color: var(--text-color, #17191b);
    margin-bottom: 3px;
}

.aarea-desc {
    margin: 0;
    font-size: 11px;
    line-height: 1.4;
    color: var(--ecv-muted, #73787b);
}

@media (max-width: 1200px) {
    .aareas {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (max-width: 768px) {
    .aareas {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.afoot {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: space-between;
    padding: 8px 0 2px;
    border-top: 1px solid var(--ecv-line, #e5e6e4);
    font-size: 11.5px;
    color: var(--ecv-muted, #73787b);
    max-width: none;
    margin: 0;
    width: 100%;
    flex-shrink: 0;
}

/* ================================================================== *
 * Dark Theme Explicit Overrides for Admin Home
 * ================================================================== */

html.dark-theme .ahome,
[data-theme="dark"] .ahome {
    background: #0f1113;
    color: #e9eae8;
}

html.dark-theme .ahero,
[data-theme="dark"] .ahero {
    background: #17191b;
    border-color: rgba(255, 255, 255, 0.14);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}

html.dark-theme .ahero::before,
html.dark-theme .ahero::after,
[data-theme="dark"] .ahero::before,
[data-theme="dark"] .ahero::after {
    border-color: rgba(255, 255, 255, 0.5);
}

html.dark-theme .ahero-eyebrow,
[data-theme="dark"] .ahero-eyebrow {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.2);
    color: rgba(255, 255, 255, 0.85);
}

html.dark-theme .ahero-art::before,
[data-theme="dark"] .ahero-art::before {
    background: rgba(255, 255, 255, .08);
}

html.dark-theme .aconsole,
[data-theme="dark"] .aconsole {
    border-color: rgba(255, 255, 255, .15);
    background:
        linear-gradient(rgba(255, 255, 255, .035) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, .035) 1px, transparent 1px),
        #111315;
    background-size: 28px 28px;
    box-shadow: 0 32px 80px rgba(0, 0, 0, .48), inset 0 1px 0 rgba(255, 255, 255, .055);
}

html.dark-theme .aconsole-sheen,
[data-theme="dark"] .aconsole-sheen {
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .11), transparent);
}

html.dark-theme .aconsole-head,
[data-theme="dark"] .aconsole-head {
    border-color: rgba(255, 255, 255, .13);
}

html.dark-theme .aconsole-card,
[data-theme="dark"] .aconsole-card {
    background: #1d2022;
    border-color: rgba(255, 255, 255, .14);
    box-shadow: 0 16px 38px rgba(0, 0, 0, .34), inset 0 1px 0 rgba(255, 255, 255, .035);
}

html.dark-theme .aconsole-mark,
[data-theme="dark"] .aconsole-mark {
    background: #f2f2f0;
    color: #17191b;
}

html.dark-theme .aconsole-live,
[data-theme="dark"] .aconsole-live,
html.dark-theme .aconsole-icon,
[data-theme="dark"] .aconsole-icon {
    background: #25282a;
    border-color: rgba(255, 255, 255, .13);
}

html.dark-theme .aconsole-people i,
[data-theme="dark"] .aconsole-people i,
html.dark-theme .aconsole-route i,
[data-theme="dark"] .aconsole-route i {
    border-color: #1d2022;
}

html.dark-theme .aconsole-audit,
[data-theme="dark"] .aconsole-audit {
    background: rgba(255, 255, 255, .035);
    border-color: rgba(255, 255, 255, .13);
}

html.dark-theme .ahero h1,
[data-theme="dark"] .ahero h1 {
    color: #ffffff;
}

html.dark-theme .ahero h1 em,
[data-theme="dark"] .ahero h1 em {
    color: rgba(255, 255, 255, 0.5);
}

html.dark-theme .ahero-lede,
[data-theme="dark"] .ahero-lede {
    color: rgba(255, 255, 255, 0.75);
}

html.dark-theme .ahero-btn,
[data-theme="dark"] .ahero-btn {
    background: #ffffff;
    color: #17191b !important;
    border-color: #ffffff;
    box-shadow: 0 8px 26px rgba(0, 0, 0, 0.32);
}

/* The base rule colours child spans/icons white with !important. On the white dark-mode
   plate they must explicitly invert too, otherwise the CTA renders as a blank rectangle. */
html.dark-theme .ahero-btn:not(.is-ghost) i,
html.dark-theme .ahero-btn:not(.is-ghost) span,
[data-theme="dark"] .ahero-btn:not(.is-ghost) i,
[data-theme="dark"] .ahero-btn:not(.is-ghost) span {
    color: #17191b !important;
}

html.dark-theme .ahero-btn::after,
[data-theme="dark"] .ahero-btn::after {
    background: rgba(17, 19, 21, .14);
}

html.dark-theme .ahero-btn:hover,
[data-theme="dark"] .ahero-btn:hover {
    background: #e9ebec !important;
    border-color: #e9ebec;
    color: #111315 !important;
}

html.dark-theme .ahero-btn:not(.is-ghost):hover i,
html.dark-theme .ahero-btn:not(.is-ghost):hover span,
[data-theme="dark"] .ahero-btn:not(.is-ghost):hover i,
[data-theme="dark"] .ahero-btn:not(.is-ghost):hover span {
    color: #111315 !important;
}

html.dark-theme .ahero-btn:focus-visible,
[data-theme="dark"] .ahero-btn:focus-visible {
    outline-color: #ffffff;
}

.ahero-btn.is-loading,
.pbar-action.is-loading {
    pointer-events: none;
    cursor: wait;
    opacity: .82;
}

.ahero-btn.is-loading i,
.pbar-action.is-loading i {
    animation: admin-signin-pulse .8s ease-in-out infinite alternate;
}

@keyframes admin-signin-pulse {
    from { opacity: .35; }
    to { opacity: 1; }
}

html.dark-theme .ahero-btn.is-ghost,
[data-theme="dark"] .ahero-btn.is-ghost {
    background: transparent;
    color: #ffffff;
    border-color: rgba(255, 255, 255, 0.3);
}

html.dark-theme .ahero-btn.is-ghost:hover,
[data-theme="dark"] .ahero-btn.is-ghost:hover {
    background: rgba(255, 255, 255, 0.1);
}

html.dark-theme .ahero-note,
[data-theme="dark"] .ahero-note {
    color: rgba(255, 255, 255, 0.55);
}

html.dark-theme .ahero-note b,
html.dark-theme .ahero-note button,
[data-theme="dark"] .ahero-note b,
[data-theme="dark"] .ahero-note button {
    color: #ffffff;
}

html.dark-theme .aarea-card,
[data-theme="dark"] .aarea-card {
    background: #17191b;
    border-color: rgba(255, 255, 255, 0.12);
    box-shadow: none;
}

html.dark-theme .aarea-card:hover,
[data-theme="dark"] .aarea-card:hover {
    border-color: rgba(255, 255, 255, 0.3);
}

html.dark-theme .aarea-card i,
[data-theme="dark"] .aarea-card i {
    color: rgba(255, 255, 255, 0.85);
}

html.dark-theme .aarea-title,
[data-theme="dark"] .aarea-title {
    color: #ffffff;
}

html.dark-theme .aarea-desc,
[data-theme="dark"] .aarea-desc {
    color: rgba(255, 255, 255, 0.6);
}

html.dark-theme .afoot,
[data-theme="dark"] .afoot {
    border-top-color: rgba(255, 255, 255, 0.12);
    color: rgba(255, 255, 255, 0.5);
}

@media (max-height: 700px) {
    .ahome {
        height: auto;
        min-height: calc(100svh - 66px);
        min-height: calc(100dvh - 66px);
        max-height: none;
        overflow-y: auto;
        padding-block: 14px 12px;
    }

    .ahero {
        min-height: 390px;
        padding-block: 28px;
    }
}

@media (max-width: 960px) {
    .ahome {
        height: auto;
        min-height: calc(100svh - 66px);
        min-height: calc(100dvh - 66px);
        max-height: none;
        overflow-y: auto;
        padding: 20px;
    }

    .ahero {
        grid-template-columns: minmax(0, 1fr);
        gap: 28px;
        min-height: 0;
        padding: 40px clamp(20px, 6vw, 48px);
        text-align: center;
    }

    .ahero-copy {
        max-width: 680px;
        justify-self: center;
    }

    .ahero-eyebrow {
        justify-content: center;
    }

    .ahero h1 {
        margin-inline: auto;
        max-width: 18ch;
    }

    .ahero-lede {
        margin-inline: auto;
    }

    .ahero-actions {
        justify-content: center;
    }

    .ahero-art {
        max-width: 460px;
        justify-self: center;
    }

    .aareas {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 600px) {
    .ahome {
        padding: 12px;
    }

    .ahero {
        padding: 30px 18px;
    }

    .ahero h1 {
        font-size: clamp(36px, 12vw, 52px);
    }

    .ahero-art {
        max-width: 100%;
    }

    .aconsole {
        min-height: 330px;
        gap: 10px;
        padding: 14px;
        border-radius: 18px;
    }

    .aconsole-card {
        min-height: 84px;
        gap: 11px;
        padding: 13px;
    }

    .aconsole-access {
        margin-inline-end: 8px;
    }

    .aconsole-routing {
        margin-inline-start: 8px;
    }

    .aconsole-icon {
        width: 38px;
        height: 38px;
    }

    .aconsole-state {
        width: 32px;
        height: 32px;
    }

    .aareas {
        grid-template-columns: 1fr;
    }
}

/* ================================================================== *
 * Sign-in
 *
 * Full ink page, one narrow column, the restriction stated above the action. No hero art and
 * no split panel: the other two portals' sign-in screens each have one, so administration's
 * absence of both is itself the distinguishing mark — the least decorated screen in the
 * system, for the most consequential door.
 *
 * No Back control: this page is the entrance.
 * ================================================================== */

.alogin-body {
    display: grid;
    place-items: center;
    min-height: calc(100svh - 66px);
    padding: 20px;
    box-sizing: border-box;
}

.alogin-card {
    width: 100%;
    max-width: 396px;
    display: grid;
    gap: 18px;
}

.alogin-rule {
    height: 1px;
    background: rgba(255, 255, 255, .18);
}

.alogin-kicker {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 5px 11px;
    border: 1px solid rgba(255, 255, 255, .3);
    border-radius: 4px;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .82);
    justify-self: start;
}

html[dir="rtl"] .alogin-kicker {
    letter-spacing: normal;
    text-transform: none;
    font-size: 12px;
}

.alogin-card h1 {
    margin: 0;
    font-size: clamp(22px, 3vw, 28px);
    letter-spacing: -.02em;
    line-height: 1.2;
}

.alogin-card > p:first-of-type {
    margin: -8px 0 0;
    color: rgba(255, 255, 255, .66);
    font-size: 13.5px;
    line-height: 1.65;
}

/* On ink, the Microsoft button inverts: white plate, ink label. */
.ap-portal.is-dark .auth-ms-btn {
    background: #fff;
    color: var(--ecv-ink, #17191b);
    border-radius: 6px;
}

.ap-portal.is-dark .auth-ms-btn:hover:not(:disabled) {
    background: rgba(255, 255, 255, .88);
}

.ap-portal.is-dark .auth-ms-btn:focus-visible {
    outline-color: #fff;
}

.ap-portal.is-dark .auth-note {
    color: rgba(255, 255, 255, .56);
}

.ap-portal.is-dark .auth-error {
    background: rgba(173, 41, 31, .18);
    border-color: rgba(239, 200, 196, .4);
    color: #f6c9c4;
}

.ap-portal.is-dark .auth-error::before {
    background: #f6c9c4;
    color: #6d1a13;
}

.ap-portal.is-dark .skip-link {
    background: #fff;
    color: var(--ecv-ink, #17191b);
}

/* ================================================================== *
 * No access
 * ================================================================== */

.ap-portal.is-dark .denied-card {
    background: var(--ecv-paper, #fff);
    color: var(--ecv-ink, #17191b);
    border-radius: 4px;
}

@media (max-width: 700px) {
    .ahero {
        padding: 28px 18px;
    }
}

/* ================================================================== *
 * Authenticated screens
 *
 * The nine administration screens were built around a fixed sidebar. Their content is
 * unchanged — the same dense tables, charts, drawers and modals — but the frame is now this
 * portal's two-tier bar, so the shell collapses to one column.
 *
 * The content components are deliberately still shared with the Support portal: that is a
 * component library, and rebuilding it per portal would mean three drifting copies of every
 * table. The chrome, which is what a person reads to know where they are, is not shared.
 * ================================================================== */

html.ap-portal,
body.ap-portal {
    height: 100vh;
    height: 100dvh;
    overflow: hidden !important;
}

.ap-portal .up-shell {
    display: flex;
    flex-direction: column;
    height: calc(100vh - var(--topbar-h, 60px));
    height: calc(100dvh - var(--topbar-h, 60px));
    max-height: calc(100vh - var(--topbar-h, 60px));
    max-height: calc(100dvh - var(--topbar-h, 60px));
    min-height: 0;
    overflow: hidden;
}

.ap-portal .up-main {
    max-width: 100%;
    width: 100%;
    margin: 0;
    padding: 16px clamp(14px, 2.4vw, 34px) 14px;
    box-sizing: border-box;
    display: block;
    flex: 1 1 auto;
    min-height: 0;
    height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--ecv-muted, #73787b) transparent;
}

.ap-portal #tabPanel,
.ap-portal #dashPanel {
    display: block;
    height: auto;
    max-height: none;
    min-height: auto;
    overflow: visible;
}

.ap-portal .adm-split {
    height: auto;
    max-height: none;
    min-height: auto;
    overflow: visible;
}

/* People & Access is the one intentional viewport-fitted data grid. Its user container is
 * injected after load with an inline overflow lock, so keep that page as a flex column and
 * let the sticky table itself own the scroll. Every other Admin screen uses .up-main as the
 * single vertical scroll owner, preventing Dashboard/Reports/Tickets from shrinking into a
 * non-scrollable flex slot. */
.ap-portal .up-main:has(#usersTabContainer) {
    display: flex;
    flex-direction: column;
    overflow-y: hidden;
}

.ap-portal #tabPanel:has(#usersTabContainer),
.ap-portal #usersTabContainer {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    height: 100%;
    min-height: 0;
    overflow: hidden !important;
}

.ap-portal .up-main::-webkit-scrollbar,
.ap-portal #usersTabContainer::-webkit-scrollbar {
    width: 10px;
}

.ap-portal .up-main::-webkit-scrollbar-track,
.ap-portal #usersTabContainer::-webkit-scrollbar-track {
    background: transparent;
}

.ap-portal .up-main::-webkit-scrollbar-thumb,
.ap-portal #usersTabContainer::-webkit-scrollbar-thumb {
    min-height: 42px;
    border: 3px solid transparent;
    border-radius: 999px;
    background: var(--ecv-muted, #73787b);
    background-clip: padding-box;
}

.ap-portal .up-main::-webkit-scrollbar-thumb:hover,
.ap-portal #usersTabContainer::-webkit-scrollbar-thumb:hover {
    background: var(--text-color, #17191b);
    background-clip: padding-box;
}

.ap-portal .up-table-wrap.adm-sticky-table,
.ap-portal .up-table-wrap {
    display: block;
    flex: 1 1 auto;
    min-height: 0;
    max-height: 100%;
    overflow-y: auto !important;
    overflow-x: auto;
    position: relative;
    border-radius: var(--ecv-radius-card, 8px);
    border: 1px solid var(--ecv-line, var(--border-color, #e5e6e4));
    background-color: var(--ecv-paper, var(--card-bg, #1e2124));
    scrollbar-width: thin;
}

.ap-portal .adm-sticky-table thead th,
.ap-portal .up-table thead th,
.ap-portal .up-table th {
    position: sticky;
    top: 0;
    z-index: 10;
    height: 42px;
    padding: 0 12px;
    text-align: center;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-color);
    background: var(--ecv-soft, #25282c);
    border-bottom: 1px solid var(--ecv-line, var(--border-color, #e5e6e4));
    white-space: nowrap;
}

.ap-portal .up-page-head {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 4px;
}

.ap-portal .up-page-head h1 {
    margin: 0;
}

.ap-portal .up-page-head p {
    margin: 0;
}

.ap-portal .up-sidebar,
.ap-portal .adm-sidebar,
.ap-portal .up-fab,
.ap-portal .up-langbar {
    display: none !important;
}

/* ------------------------------------------------------------------ *
 * Fitting the viewport
 *
 * With the sidebar gone the content column is the full width of the window, and a few of the
 * administration screens were built assuming a fixed 248px was taken off the left. Wide
 * children — tables, the permissions matrix, the volume chart — could therefore push past the
 * right edge and make the whole page scroll sideways, which is the worst kind of overflow: it
 * moves the navigation off screen too.
 *
 * The rule is that the PAGE never scrolls horizontally; a wide child scrolls inside its own
 * box. min-width: 0 on the grid children is what actually allows that — a grid item's
 * automatic minimum size is its content, so without it a wide table forces the column wider
 * than the viewport no matter what overflow the wrapper declares.
 * ------------------------------------------------------------------ */

.ap-portal {
    overflow-x: hidden;
}

.ap-portal .up-main,
.ap-portal .up-main > * {
    max-width: 100%;
    min-width: 0;
}

/* Every direct child of a card or a grid cell may shrink below its content. */
.ap-portal .adm-grid-2,
.ap-portal .adm-grid-2 > *,
.ap-portal .up-card,
.ap-portal .up-card > * {
    min-width: 0;
}

/* Wide content scrolls inside its own container, never the page. */
.ap-portal .up-table-wrap,
.ap-portal .adm-matrix-wrap,
.ap-portal .adm-chart-wrap {
    max-width: 100%;
    overflow-x: auto;
}

.ap-portal table {
    max-width: 100%;
}

/* Long values — addresses, ticket subjects, department names — wrap rather than widen the
 * column they sit in. */
.ap-portal .up-card td,
.ap-portal .up-card th,
.ap-portal .adm-facts dd {
    overflow-wrap: anywhere;
}

/* The KPI row is the most common overflow: fixed-width tiles on a narrow window. */
.ap-portal .up-kpis {
    grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
}

/* SVG charts scale to their box instead of asserting their intrinsic width. */
.ap-portal .up-card svg {
    max-width: 100%;
    height: auto;
}

/* ------------------------------------------------------------------ *
 * Rail palette — administration
 *
 * The darkest and most neutral of the three. This portal changes how the system behaves for
 * everybody else, and its chrome should feel like instrumentation: near-black, low contrast
 * between resting states, and a single bright accent reserved for where you actually are.
 * ------------------------------------------------------------------ */

/* The public front door is a full ink page and has no rail, so it must not be inset by one.
 * Belt and braces: renderPortalPublicBar() also removes the shell class. */
.ap-portal.is-dark {
    padding-inline-start: 0;
}

/* ================================================================== *
 * Hero — viewport-filling, horizontal command panel
 *
 * This is the final home-layout layer, so it deliberately repeats the two-column declaration:
 * later viewport rhythm must never collapse the copy and console into a centred vertical stack
 * on a wide screen. Only the narrow-screen media rule below is allowed to stack them.
 * ================================================================== */

.ahome {
    padding-block: clamp(10px, 1.6vh, 22px) clamp(10px, 1.4vh, 20px);
}

.ahero {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(400px, .95fr);
    align-items: center;
    justify-content: initial;
    gap: clamp(40px, 6vw, 112px);
    flex: 1 1 auto;
    min-height: 0;
    margin-block: 0;
    padding: clamp(28px, 4vh, 54px) clamp(36px, 5vw, 96px);
}

.ahero-copy {
    align-self: center;
    justify-self: start;
    max-width: 720px;
}

.ahero-eyebrow { margin-block: 0 clamp(6px, 1.2vh, 16px); }

.ahero h1 {
    margin-block: 0;
    font-size: clamp(38px, 3.1vw + 1.7vh, 80px);
    line-height: 1.05;
    letter-spacing: -.03em;
}

.ahero-lede {
    margin-block: clamp(10px, 1.5vh, 20px) 0;
    font-size: clamp(15.5px, .55vw + .55vh, 20px);
    line-height: 1.66;
}

.ahero-art {
    align-self: center;
    justify-self: end;
    width: 100%;
    max-width: 540px;
    max-height: none;
    margin: 0;
}

.ahero-actions { margin-block: clamp(14px, 2vh, 26px) 0; }

.ahero-btn {
    min-height: clamp(50px, 5.8vh, 64px);
    padding-inline: clamp(24px, 2.1vw, 36px);
    font-size: clamp(15px, .38vw + .38vh, 17.5px);
}

.ahero-note { margin-block: clamp(10px, 1.4vh, 18px) 0 0; }

@media (max-height: 660px) and (min-width: 961px) {
    .ahome { height: auto; max-height: none; overflow: visible; }
    .ahero {
        grid-template-columns: minmax(0, 1.05fr) minmax(340px, .95fr);
        flex: 0 0 auto;
        padding-block: 30px;
    }
    .aconsole { min-height: 320px; }
}

@media (max-width: 960px) {
    .ahero {
        grid-template-columns: minmax(0, 1fr);
        gap: 30px;
        padding: 40px clamp(20px, 6vw, 48px);
        text-align: center;
    }

    .ahero-copy,
    .ahero-art {
        justify-self: center;
    }

    .ahero-copy {
        max-width: 680px;
    }

    .ahero-art {
        max-width: 460px;
    }
}

/* ================================================================== *
 * Hero — rhythm and optical alignment
 *
 * The framed hero is centred rather than distributed, so it never had the pooled-gap problem
 * the other two did. It still needs the optical correction on its display type, and its
 * auth-state bands need the slot rhythm rather than their own margins.
 * ================================================================== */

/* Optical left alignment for this headline only.
 *
 * The side bearing — how far inside its own box a glyph's ink begins — is a property of the
 * letter, not of the size. Measured in Manrope at this hero's 77px: "R" carries 5px of bearing,
 * while the lede's first glyph carries none. So the boxes line up perfectly and the ink does
 * not, which is what the eye actually reads as a shifted headline. Pulling the headline back by
 * its own bearing puts the ink on the column.
 *
 * The Support and Department headlines begin with "A" and "W", whose bearings measured at ~1px
 * — the same as their body text — so they are deliberately left alone. If this headline's first
 * letter changes, this number has to be re-measured; it is a correction for one glyph, not a
 * general rule about display type.
 */
/* Latin only. The correction is the bearing of the letter "R"; the Arabic headline begins
 * with a different glyph in a different face, and applying this there would pull its ink 5px
 * off the column rather than onto it. i18n.js sets dir="rtl" on <html> for Arabic. */
:root:not([dir="rtl"]) .ahero h1 { margin-inline-start: -.065em; }

.ahero .phero-slot-note { margin-block: clamp(14px, 2vh, 22px) 0; }

/* ================================================================== *
 * Tickets — the table owns the vertical scroll
 *
 * The rule for this portal was "`.up-main` is the single vertical scroll owner", with People &
 * Access as the one viewport-fitted exception. On Tickets that produced the wrong thing: the
 * whole page scrolled, so `position: sticky` on the header row had nothing to stick inside —
 * `.up-table-wrap` never scrolled itself, it simply travelled up and off the screen taking its
 * header with it. Ten rows in, you could no longer tell which column was which.
 *
 * Tickets is a table you read against its own headings, so it joins People as a viewport-fitted
 * screen: the shell holds the height, the table's wrapper is the scroll container, and the
 * sticky header sticks inside it. The detail pane beside it scrolls on its own, so reading a
 * long ticket does not move the list.
 *
 * Keyed on `:has(#ticketListPane)` — the same idiom as the People rules above, so the screen
 * declares itself by its own markup rather than by a class somebody has to remember to add.
 * ================================================================== */

.ap-portal .up-main:has(#ticketListPane) {
    display: flex;
    flex-direction: column;
    overflow-y: hidden;
}

/* Each wrapper between the shell and the table has to pass the height down AND allow itself to
 * be smaller than its content. Without `min-height: 0` a flex item refuses to shrink below its
 * content, the overflow lands back on the page, and the sticky header breaks again. */
.ap-portal .up-main:has(#ticketListPane) > #tabPanel,
.ap-portal #tabPanel:has(#ticketListPane) {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.ap-portal .adm-split:has(#ticketListPane),
.ap-portal .adm-split:has(> #ticketListPane) {
    display: grid;
    grid-template-columns: minmax(0, 1.55fr) minmax(320px, .85fr);
    gap: 14px;
    flex: 1 1 0;
    height: auto;
    min-height: 0;
    max-height: none;
    overflow: hidden;
}

.ap-portal #ticketListPane {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
    overflow: hidden;
}

/* The scroll container. The sticky header row measures its offset against this element, which
 * is the whole point of moving the scroll here. */
.ap-portal #ticketListPane > .up-table-wrap {
    --tickets-sort-target: 36px;
    --tickets-sort-icon-size: 11px;
    flex: 1 1 0;
    height: auto;
    min-height: 0;
    max-height: none;
    overflow-y: auto !important;
    overflow-x: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
}

.ap-portal #ticketListPane > .up-table-wrap:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

/* The header row: opaque, above the rows, and with its own bottom edge so it stays legible
 * over whatever scrolls beneath it. `--ecv-soft` rather than a literal, so it is right in both
 * themes; a transparent header would show the rows sliding through it. */
.ap-portal #ticketListPane > .up-table-wrap thead th {
    position: sticky;
    top: 0;
    z-index: 6;
    background: var(--ecv-soft, #25282c);
    box-shadow: inset 0 -1px 0 var(--ecv-line, #e5e6e4);
}

.ap-portal #ticketListPane .adm-sort-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ecv-space-2);
    width: 100%;
    min-height: var(--tickets-sort-target);
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-color);
    font: inherit;
    font-weight: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    cursor: pointer;
}

.ap-portal #ticketListPane .adm-sort-button i {
    color: var(--text-light);
    font-size: var(--tickets-sort-icon-size);
}

.ap-portal #ticketListPane .adm-sort-button.is-active i {
    color: var(--text-color);
}

.ap-portal #ticketListPane .adm-sort-button:hover {
    color: var(--text-color);
    background: var(--hover-bg);
}

.ap-portal #ticketListPane .adm-sort-button:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: -2px;
}

/* The detail pane scrolls independently, so opening a long ticket does not scroll the list out
 * from under the person reading it. */
.ap-portal .adm-split:has(#ticketListPane) > .adm-detail {
    min-height: 0;
    min-width: 0;
    max-height: 100%;
    overflow-y: auto;
}

/* THE CHAIN THAT MAKES THAT SCROLL CONTAINER REAL.
 *
 * Everything above declares the list pane's wrapper as the scroll owner — height: 100%,
 * max-height: 100%, overflow-y: auto !important — and none of it did anything, because a
 * percentage height only resolves against a parent with a DEFINITE height and this one had
 * none. The chain above the split is:
 *
 *     .up-main          height: 100%, overflow-y: auto     definite
 *     #tabPanel         height: auto, display: block       <-- breaks here
 *     .adm-split         height: 100%  -> resolves to auto
 *     .up-table-wrap    height: 100%  -> resolves to auto
 *
 * So the table grew to its content, the wrapper had nothing to overflow, no scrollbar
 * appeared inside it, and the whole list scrolled the page instead — taking the sticky header
 * row with it, which is the thing that scroll container exists to keep still.
 *
 * `.ap-portal #tabPanel, .ap-portal #dashPanel { height: auto; overflow: visible }` is right
 * for Dashboard, Reports and the rest — they are ordinary documents and .up-main is their
 * scroller. Tickets is the second viewport-fitted screen in this portal, alongside People &
 * Access, so it gets the same treatment People already has: .up-main becomes a flex column
 * that does not scroll, and the two panes below it own their own scrolling.
 *
 * :has() carries its argument's specificity, so `:has(#ticketListPane)` puts these above the
 * generic #tabPanel rule without !important. They sit BEFORE the stacking media query below
 * on purpose: under 1100px the panes stack and the page scrolls again, and that block has to
 * be the one that wins. */
.ap-portal .up-main:has(#ticketListPane) {
    display: flex;
    flex-direction: column;
    overflow-y: hidden;
}

.ap-portal #tabPanel:has(#ticketListPane) {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    height: auto;
    max-height: none;
    min-height: 0;
    overflow: hidden;
}

/* Below the split point the two panes stack, and a page-level scroll is then the right answer:
 * two nested scroll regions on a phone is a trap. */
@media (max-width: 1100px) {
    .ap-portal .up-main:has(#ticketListPane) {
        display: block;
        overflow-y: auto;
    }

    .ap-portal .up-main:has(#ticketListPane) > #tabPanel,
    .ap-portal #tabPanel:has(#ticketListPane),
    .ap-portal .adm-split:has(#ticketListPane),
    .ap-portal #ticketListPane,
    .ap-portal .adm-split:has(#ticketListPane) > .adm-detail {
        display: block;
        height: auto;
        max-height: none;
        overflow: visible;
    }

    .ap-portal #ticketListPane > .up-table-wrap {
        display: block;
        height: clamp(320px, calc(100dvh - var(--topbar-h, 60px) - 230px), 720px);
        min-height: 0;
        max-height: clamp(320px, calc(100dvh - var(--topbar-h, 60px) - 230px), 720px);
        overflow-y: auto !important;
        overflow-x: auto;
    }
}

/* ================================================================== *
 * Tickets — dedicated detail page
 *
 * A ticket is work, not a peek. Rows now open a dedicated URL while the modern live detail
 * renderer remains the one implementation for replies, staff notes, files and moderation.
 * Each responsibility owns a separate, calm bordered surface.
 * ================================================================== */

.ap-portal.admin-ticket-detail-page .adm-subnav-bar,
.ap-portal.admin-ticket-detail-page #ticketChips,
.ap-portal.admin-ticket-detail-page #ticketToolbar,
.ap-portal.admin-ticket-detail-page #ticketListPane {
    display: none !important;
}

.ap-portal.admin-ticket-detail-page .up-main:has(#ticketListPane) {
    display: block;
    overflow-y: auto;
    padding-bottom: 32px;
}

.ap-portal.admin-ticket-detail-page .adm-split:has(#ticketListPane) {
    display: block;
    height: auto;
    max-height: none;
    overflow: visible;
}

.ap-portal.admin-ticket-detail-page .adm-split:has(#ticketListPane) > .adm-detail {
    width: min(100%, 1500px);
    margin-inline: auto;
    max-height: none;
    overflow: visible;
}

/* The ticket page's sections. ONE rule — this component had four, added by two sessions that
 * could not see each other's work, disagreeing about whether it was a grid or a column. The
 * winner was whichever sat last in the file, which is not a layout decision.
 *
 * A single column, because the sections are not adjacent in the DOM: Description sits between
 * Ticket Information and Moderation, so pairing the short ones two-up left each alone on its row
 * with an empty half beside it. The width is used inside each section instead — Information lays
 * its facts three across, Moderation puts its fields two across — which is where a section that
 * knows its own content should solve it.
 *
 * Full width with gutters: the page fills the viewport, and text still never touches the edge. */
.ap-portal .adm-ticket-detail-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: clamp(12px, 1.4vw, 20px);
    max-width: none;
    margin-inline: 0;
    background: transparent;
    border: 0;
    box-shadow: none;
}

.ap-portal .adm-ticket-detail-head,
.ap-portal .adm-ticket-moderation {
    grid-column: 1 / -1;
}

.ap-portal .adm-ticket-detail-head {
    display: grid;
    gap: 10px;
    padding: 4px 2px 16px;
    border-bottom: 1px solid var(--ecv-line);
}

.ap-portal .adm-ticket-back {
    display: inline-flex;
    align-items: center;
    width: max-content;
    min-height: 38px;
    gap: 9px;
    padding: 0 13px;
    color: var(--ecv-ink);
    background: var(--ecv-soft);
    border: 1px solid var(--ecv-line);
    border-radius: 10px;
    font-size: 12px;
    font-weight: 750;
    text-decoration: none;
    transition: border-color 160ms ease, background-color 160ms ease, transform 160ms ease;
}

.ap-portal .adm-ticket-back:hover {
    border-color: var(--ecv-ink-soft);
    transform: translateY(-1px);
}

.ap-portal .adm-ticket-detail-card > .adm-block {
    min-width: 0;
    padding: clamp(15px, 1.8vw, 20px);
    background: var(--ecv-paper);
    border: 1px solid var(--ecv-line);
    border-radius: 14px;
}

.ap-portal .adm-ticket-comms {
    display: flex;
    flex-direction: column;
    min-height: 360px;
}

.ap-portal .adm-ticket-comms .adm-thread {
    flex: 1 1 auto;
    min-height: 180px;
    max-height: min(42vh, 440px);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
    padding: 10px;
    background: var(--ecv-soft);
    border: 1px solid var(--ecv-line);
    border-radius: 11px;
}

.ap-portal .adm-ticket-composer {
    display: grid;
    gap: 10px;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--ecv-line);
}

.ap-portal .adm-ticket-composer textarea {
    width: 100%;
    min-height: 92px;
    resize: vertical;
    border-radius: 10px;
}

.ap-portal .adm-ticket-composer-actions {
    display: flex;
    justify-content: flex-end;
}

.ap-portal .adm-ticket-composer-actions .dept-btn {
    inline-size: clamp(148px, 32%, 188px);
    min-block-size: 42px;
    justify-content: center;
    border-radius: 10px;
    font-size: 11.5px;
    font-weight: 800;
    letter-spacing: .06em;
}

.ap-portal .adm-ticket-notes {
    border-top: 2px solid color-mix(in srgb, #d5a11e 72%, var(--ecv-line)) !important;
}

.ap-portal .adm-ticket-conversation {
    border-top: 2px solid color-mix(in srgb, #2563eb 62%, var(--ecv-line)) !important;
}

/* The surface first distinguishes public replies from private notes; the author accent then
 * distinguishes authority. The role is stamped into each new row, so it remains historically
 * correct if the writer's permissions change later. */
.ap-portal .adm-msg-author {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    min-width: 0;
}

.ap-portal .adm-role-badge {
    display: inline-flex;
    align-items: center;
    min-height: 20px;
    padding: 2px 7px;
    border: 1px solid currentColor;
    border-radius: 999px;
    font-size: 9px;
    font-weight: 850;
    letter-spacing: .08em;
    line-height: 1;
    text-transform: uppercase;
}

[dir="rtl"] .ap-portal .adm-role-badge { letter-spacing: 0; }

.ap-portal .adm-role-badge.is-super-admin { color: #6d28d9; background: color-mix(in srgb, #7c3aed 11%, transparent); }
.ap-portal .adm-role-badge.is-admin { color: #1d4ed8; background: color-mix(in srgb, #2563eb 10%, transparent); }
.ap-portal .adm-role-badge.is-moderator { color: #047857; background: color-mix(in srgb, #059669 10%, transparent); }
.ap-portal .adm-role-badge.is-support { color: var(--ecv-ink-soft); background: var(--ecv-soft); }

.ap-portal .adm-msg.role-super-admin { border-inline-start: 3px solid #7c3aed; }
.ap-portal .adm-msg.role-admin { border-inline-start: 3px solid #2563eb; }
.ap-portal .adm-msg.role-moderator { border-inline-start: 3px solid #059669; }

[data-theme="dark"] .ap-portal .adm-role-badge.is-super-admin { color: #c4b5fd; }
[data-theme="dark"] .ap-portal .adm-role-badge.is-admin { color: #93c5fd; }
[data-theme="dark"] .ap-portal .adm-role-badge.is-moderator { color: #6ee7b7; }

@media (max-width: 900px) {
    .ap-portal .adm-ticket-comms {
        min-height: 0;
    }

    .ap-portal .adm-ticket-composer-actions .dept-btn {
        width: 100%;
    }
}

/* ================================================================== *
 * Access requests (People & Access → Access requests)
 *
 * A queue an administrator works, so it reads as a list of people and decisions rather than as
 * a data table: the person's name carries the row, the reason they gave is the wide column
 * because it is what the decision turns on, and the two actions sit at the end.
 * ================================================================== */

/* The waiting count on the tab. Only ever drawn when it is non-zero — a "0" badge trains
 * people to stop looking at badges. */
.ap-portal .adm-tab-count {
    display: inline-grid;
    place-items: center;
    min-width: 19px;
    height: 19px;
    margin-inline-start: 8px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--ecv-amber-wash, rgba(176, 122, 12, .18));
    color: var(--ecv-amber, #8a5d00);
    font-size: 11px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.ap-portal .ar-table td { vertical-align: middle; }

.ap-portal .ar-person strong {
    display: block;
    font-size: 13px;
    font-weight: 700;
}

.ap-portal .ar-person small {
    display: block;
    margin-top: 2px;
    color: var(--text-light, #73787b);
    font-size: 11.5px;
}

.ap-portal .ar-portal {
    display: inline-flex;
    align-items: center;
    padding: 4px 11px;
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 700;
    white-space: nowrap;
}

/* The reason is the column that matters, so it gets the room — capped so one long paragraph
 * cannot push the actions off the side. */
.ap-portal .ar-reason {
    max-width: 38ch;
    color: var(--text-light, #73787b);
    font-size: 12.5px;
    line-height: 1.55;
    text-align: start;
    white-space: normal;
}

.ap-portal .ar-reason em { opacity: .8; }

.ap-portal .ar-when {
    color: var(--text-light, #73787b);
    font-size: 12px;
    white-space: nowrap;
}

.ap-portal .ar-status {
    display: inline-flex;
    align-items: center;
    padding: 4px 11px;
    border: 1px solid transparent;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 700;
    white-space: nowrap;
}

.ap-portal .ar-status.is-pending {
    border-color: var(--ecv-amber-line, rgba(176, 122, 12, .32));
    background: var(--ecv-amber-wash, rgba(176, 122, 12, .14));
    color: var(--ecv-amber, #8a5d00);
}

.ap-portal .ar-status.is-approved {
    border-color: var(--ecv-green-line, rgba(19, 129, 59, .32));
    background: var(--ecv-green-wash, rgba(19, 129, 59, .12));
    color: var(--ecv-green, #13813b);
}

.ap-portal .ar-status.is-declined {
    border-color: var(--ecv-red-line, rgba(173, 41, 31, .32));
    background: var(--ecv-red-wash, rgba(173, 41, 31, .1));
    color: var(--ecv-red, #ad291f);
}

.ap-portal .ar-status.is-withdrawn {
    border-color: var(--ecv-line, #e5e6e4);
    background: var(--ecv-soft, #eff0ee);
    color: var(--text-light, #73787b);
}

.ap-portal .ar-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 7px;
    white-space: nowrap;
}

.ap-portal .ar-actions .up-btn.is-small {
    min-height: 32px;
    padding: 0 11px;
    font-size: 12px;
}

.ap-portal .adm-block-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 14px;
    margin-bottom: 18px;
}

.ap-portal .adm-block-lead {
    margin: 6px 0 0;
    max-width: 78ch;
    color: var(--text-light, #73787b);
    font-size: 12.5px;
    line-height: 1.62;
}

.ap-portal .adm-note {
    margin: 0 0 16px;
    padding: 10px 13px;
    border: 1px solid var(--ecv-amber-line, rgba(176, 122, 12, .32));
    border-radius: 9px;
    background: var(--ecv-amber-wash, rgba(176, 122, 12, .12));
    color: var(--ecv-amber, #8a5d00);
    font-size: 12.5px;
    line-height: 1.55;
}

@media (max-width: 900px) {
    .ap-portal .ar-reason { max-width: none; }
    .ap-portal .ar-actions { justify-content: flex-start; }
}

/* ================================================================== *
 * Department detail modal — the details card is now the edit form
 *
 * There used to be two modals for one department: a detail view showing name, code, status and
 * description as read-only facts, and an Edit modal holding the same values as inputs. The
 * detail modal now edits in place, so there is one surface per record and no close-and-reopen
 * to change a description.
 *
 * Save is disabled until something actually changes, and a Discard control appears beside it
 * only then — so the footer is quiet while you are reading and present the moment you are
 * editing.
 * ================================================================== */

.ap-portal .adm-dept-form .up-modal-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

.ap-portal .adm-dept-form label {
    display: block;
    margin-bottom: 6px;
    color: var(--text-light, #73787b);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
}

/* The derived pair — members and ticket counts. Stated, not offered: they are changed in the
 * members table below and in the queue, not by typing over a number. The recessed plate is what
 * says "this is a reading, not a field". */
.ap-portal .adm-dept-derived {
    padding: 12px 14px;
    border: 1px solid var(--ecv-line, #e5e6e4);
    border-radius: 9px;
    background: var(--sys-surface-sunken, var(--ecv-soft, #eff0ee));
}

.ap-portal .adm-dept-derived dt {
    color: var(--text-light, #73787b);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
}

.ap-portal .adm-dept-derived dd {
    margin: 3px 0 0;
    font-size: 13px;
}

.ap-portal .adm-dept-form-foot {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--ecv-line, #e5e6e4);
}

/* Disabled here means "nothing has changed", not "unavailable" — so it reads as resting rather
 * than as broken. */
.ap-portal .adm-dept-form-foot .dept-btn:disabled {
    opacity: .5;
    cursor: default;
}

/* Confirmation that the write landed, in the place the person was looking. */
.ap-portal .adm-dept-saved {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border: 1px solid var(--ecv-green-line, rgba(19, 129, 59, .32));
    border-radius: 999px;
    background: var(--ecv-green-wash, rgba(19, 129, 59, .1));
    color: var(--ecv-green, #13813b);
    font-size: 11px;
    font-weight: 700;
}

@media (max-width: 700px) {
    .ap-portal .adm-dept-form .up-modal-row { grid-template-columns: 1fr; }
}

/* ================================================================== *
 * Department modal — density and alignment
 *
 * The modal had grown tall enough to scroll on a 1080px screen for a record with five fields.
 * Three separate causes, all of them space nobody chose:
 *
 *   1. The two top cards sat in a stretch grid, so the short Workload card was pulled to the
 *      height of the tall form beside it and carried a few hundred pixels of void.
 *   2. Field heights, label gaps, the derived plate and the footer each carried their own
 *      generous margin, and they add up.
 *   3. The Active toggle was nudged up 6px by an inline offset, so it never lined up with the
 *      Code input on its row — the kind of misalignment that reads as sloppiness rather than
 *      as a bug.
 * ================================================================== */

/* Cards size to their own content. A short card next to a tall one is honest; a short card
 * stretched to match is just a void with a border. */
.ap-portal .adm-dept-modal-grid {
    align-items: start;
    gap: 12px;
}

.ap-portal .adm-dept-modal-body {
    padding: 16px;
    gap: 12px;
}

/* --- The form: one density, applied once ------------------------------------ */

.ap-portal .adm-dept-form .up-modal-row {
    gap: 10px;
}

.ap-portal .adm-dept-form .up-modal-row + .up-modal-row {
    margin-top: 10px;
}

.ap-portal .adm-dept-form label {
    margin-bottom: 4px;
}

/* One control height across the row, so the input and the toggle beside it share a baseline. */
.ap-portal .adm-dept-form input[type="text"] {
    height: 34px !important;
}

.ap-portal .adm-dept-form textarea {
    min-height: 62px;
    padding: 8px 10px;
    line-height: 1.5;
    resize: vertical;
}

/* The switch is a control on a labelled row, so it aligns to the bottom of that row and
 * matches the input's height rather than sitting proud of it. */
.ap-portal .adm-dept-switch-cell {
    display: flex;
    align-items: flex-end;
}

.ap-portal .adm-dept-form .adm-switch {
    height: 34px;
    padding: 0 12px;
    gap: 10px;
    font-size: 12.5px;
    white-space: nowrap;
}

/* --- The derived pair and the footer ---------------------------------------- */

.ap-portal .adm-dept-derived {
    margin-top: 12px;
    padding: 9px 12px;
    gap: 6px 12px;
}

.ap-portal .adm-dept-derived dd {
    margin-top: 2px;
    font-size: 12.5px;
}

.ap-portal .adm-dept-form-foot {
    margin-top: 12px;
    padding-top: 11px;
}

/* The block heading was carrying a section's worth of space above a form that starts
 * immediately below it. */
.ap-portal .adm-dept-form .adm-block-head {
    margin-bottom: 10px;
}

.ap-portal .adm-dept-form .adm-block-head h3 {
    font-size: 14px;
}

/* --- Workload, beside it --------------------------------------------------- */
/* Its rows are short and there are only ever a handful, so it states its own height instead of
 * inheriting the form's. */
.ap-portal .adm-dept-modal-grid > .up-card:has(.adm-workload),
.ap-portal .adm-dept-modal-grid > .up-card:nth-child(2) {
    align-self: start;
}

@media (max-width: 860px) {
    .ap-portal .adm-dept-modal-grid { grid-template-columns: 1fr; }
}

/* The dense form's control height.
 *
 * `.up-field input` carries `min-height: 44px` — the portal's comfortable control size, and a
 * min-height always beats a height, which is why setting `height: 34px !important` here changed
 * nothing at all. The floor itself has to move. 36px is the dense size for this modal: five
 * fields in a dialog on a desktop screen, where 44px each was most of the height the user was
 * asking me to give back. Still well above the 24px minimum target size, and the switch beside
 * it matches exactly so the row reads as one control height. */
.ap-portal .adm-dept-form .up-field input[type="text"] {
    min-height: 36px;
    height: 36px !important;
}

.ap-portal .adm-dept-form .adm-switch {
    height: 36px;
}

/* The description is a sentence or two in practice, and it can be dragged taller when it is
 * not. Three rows of reserved height for a one-line answer was space nobody asked for. */
.ap-portal .adm-dept-form textarea {
    min-height: 54px;
}

/* ================================================================== *
 * Department modal — full-width details, no sideways scroll
 *
 * THE HORIZONTAL SCROLLBAR. The body had `overflow-y: auto` and nothing for the x axis, so it
 * defaulted to visible: any child wider than the body — the SLA summary table — pushed a
 * scrollbar across the whole modal, and reading one wide table meant scrolling everything else
 * sideways with it. The wide thing should scroll inside its own box; the modal should not move.
 *
 * FULL WIDTH, USED. Making the details card span both columns is only worth doing if the fields
 * then use the width — a full-width card with a 2-up field layout is the same height and more
 * empty space. `display: contents` on the row wrappers lets all four controls join one grid, so
 * Name / Name (Arabic) / Code / Active sit on a single line and the card gets shorter rather
 * than wider.
 * ================================================================== */

.ap-portal .adm-dept-modal-body {
    /* clip, not hidden: it cannot be scrolled by script either, which is what stops a focused
       field inside a wide table from nudging the whole modal sideways. */
    overflow-x: clip;
    padding: 14px;
    gap: 10px;
}

/* Every box between the body and a wide table must be allowed to be narrower than its content,
 * or the overflow simply moves up a level and reappears. */
.ap-portal .adm-dept-modal-body,
.ap-portal .adm-dept-modal-body > *,
.ap-portal .adm-dept-modal-grid > * {
    min-width: 0;
}

/* The wide thing scrolls in its own box. */
.ap-portal .adm-dept-modal-body .up-table-wrap,
.ap-portal .adm-dept-modal-body table {
    max-width: 100%;
}

.ap-portal .adm-dept-modal-body .up-table-wrap {
    overflow-x: auto;
}

/* --- Details: the full width of the modal ---------------------------------- */

.ap-portal .adm-dept-modal-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.ap-portal .adm-dept-modal-grid > .up-card:first-child {
    grid-column: 1 / -1;
}

/* Workload follows, full width and short — it is two bars, not a panel. */
.ap-portal .adm-dept-modal-grid > .up-card:nth-child(2) {
    grid-column: 1 / -1;
}

/* --- One line of fields ----------------------------------------------------- */
/* The row wrappers stop being boxes and their children join the form's own grid, so the four
 * controls line up on one row instead of stacking two and two. */
@media (min-width: 820px) {
    .ap-portal .adm-dept-form {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 10px 12px;
        align-items: end;
    }

    .ap-portal .adm-dept-form > .adm-block-head,
    .ap-portal .adm-dept-form > .up-field,
    .ap-portal .adm-dept-form > .adm-dept-derived,
    .ap-portal .adm-dept-form > .adm-dept-form-foot {
        grid-column: 1 / -1;
    }

    .ap-portal .adm-dept-form .up-modal-row {
        display: contents;
    }

    /* The description is wide but not four columns wide — a 90ch line is unreadable. */
    .ap-portal .adm-dept-form > .up-field {
        grid-column: 1 / span 2;
        margin: 0 !important;
    }

    .ap-portal .adm-dept-form > .adm-dept-derived {
        grid-column: 3 / -1;
        margin-top: 0;
        align-self: stretch;
    }

    /* Nothing carries its own vertical margin any more; the grid gap owns the rhythm. */
    .ap-portal .adm-dept-form .up-modal-row + .up-modal-row,
    .ap-portal .adm-dept-form .adm-block-head {
        margin: 0;
    }

    .ap-portal .adm-dept-form > .adm-dept-form-foot {
        margin-top: 2px;
        padding-top: 10px;
    }
}

/* Cards get their padding from one place rather than each carrying its own. */
.ap-portal .adm-dept-modal-body > .up-card,
.ap-portal .adm-dept-modal-grid > .up-card {
    padding: 14px;
}

.ap-portal .adm-dept-modal-body section.adm-block {
    margin: 0;
}

/* What the chosen role implies, in place of the switch that used to duplicate it.
 *
 * A statement, not a control: it reports a consequence of the role above it. Styling it as a
 * field would invite somebody to try to change it here, which is the confusion that removing
 * the switch was meant to end. */
.ap-portal .adm-member-implies {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 9px 12px;
    border: 1px dashed var(--ecv-line, #e5e6e4);
    border-radius: 9px;
    background: transparent;
    color: var(--text-light, #73787b);
    font-size: 12px;
    line-height: 1.5;
}

.ap-portal .adm-member-implies i { font-size: 12px; }

/* Managing a department is authority, so it reads as a granted state rather than a neutral one. */
.ap-portal .adm-member-implies.is-manager {
    border-style: solid;
    border-color: var(--ecv-green-line, rgba(19, 129, 59, .32));
    background: var(--ecv-green-wash, rgba(19, 129, 59, .1));
    color: var(--ecv-green, #13813b);
    font-weight: 700;
}

/* Tickets: one column, because the list and the detail take turns.
 *
 * The side preview held a permanent third of the width to say "select a ticket". The list has
 * eight columns and could only be read by scrolling sideways; now it has the whole width and the
 * detail takes over when a ticket is open.
 *
 * A FLEX COLUMN, not display: block, and that is load-bearing rather than cosmetic. Only one of
 * the two children is ever visible — showTicketView() toggles the other's [hidden] — so a
 * single-child flex column and a block render identically. What flex adds is a definite height
 * to hand down.
 *
 * The list's scroll container is .up-table-wrap, so that the sticky header row has something to
 * measure its offset against (see the note on it above). A scroll container needs a bounded
 * height, and a bounded height has to come from somewhere: .up-main is a flex column with
 * overflow hidden, so this element can take its share and pass it on. As display: block with
 * height: auto it passed nothing on, #ticketListPane had no height to give either, and the
 * wrapper collapsed to its 2px border with 1482px of table clipped inside it — a ticket list
 * that had loaded, counted 87 tickets, rendered every row, and showed none of them.
 *
 * Everything here is flex sizing rather than percentage heights on purpose: a percentage needs a
 * definite parent, which is exactly the dependency that broke.
 */
.ap-portal .adm-split:has(#ticketListPane) {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

/* The pane takes the split's height so the wrapper inside it has a bound to scroll within. */
.ap-portal .adm-split:has(#ticketListPane) > #ticketListPane {
    flex: 1 1 auto;
    min-height: 0;
}

.ap-portal #ticketListPane,
.ap-portal .adm-split:has(#ticketListPane) > .adm-detail {
    width: 100%;
    max-width: none;
}

/* With the width back, the columns fit and the sideways scroll goes with them. */
.ap-portal #ticketListPane > .up-table-wrap {
    overflow-x: auto;
}

/* The way back, at the top of an open ticket. */
.ap-portal .adm-ticket-back {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
    padding: 7px 13px 7px 11px;
    border: 1px solid var(--ecv-line);
    border-radius: 999px;
    background: transparent;
    color: var(--text-color);
    font: inherit;
    font-size: 12.5px;
    font-weight: 700;
    text-decoration: none;
    cursor: pointer;
    transition: background-color .16s ease, border-color .16s ease;
}

.ap-portal .adm-ticket-back:hover {
    background: var(--ecv-soft);
    border-color: var(--ecv-muted);
}

.ap-portal .adm-ticket-back:focus-visible {
    outline: 2px solid var(--sys-action-primary, #17191b);
    outline-offset: 2px;
}

/* ================================================================== *
 * The admin ticket page, now that it is a page
 *
 * It was built for a third of the screen and inherited the whole of it, which is not an
 * improvement on its own: an 1800px-wide conversation is harder to read than a 600px one, and a
 * single column that long means scrolling past the whole ticket to reach the notes.
 *
 * So the sections are arranged rather than stretched. Information and moderation are short and
 * factual, so they pair off; the conversation and the notes are the substance and take the full
 * measure; history and feedback are references and pair off at the end. Every line is capped at a
 * readable measure regardless.
 *
 * Sections are addressed by their own classes, not by position, so adding one does not silently
 * re-shuffle the rest.
 * ================================================================== */


/* Each section reads as its own card now that the wrapper is not one. */
.ap-portal .adm-ticket-detail-card > .adm-block {
    margin: 0;
    padding: 16px;
    border: 1px solid var(--ecv-line);
    border-radius: 12px;
    background-color: var(--ecv-paper);
    min-width: 0;
}

.ap-portal .adm-ticket-detail-card > .adm-ticket-detail-head {
    padding: 0;
    border: 0;
    background: transparent;
}

/* The conversation is the reason somebody opened this, so it is not the thing that gets
 * squeezed — but it is also the thing that runs longest, so it scrolls in its own box rather
 * than pushing everything below it off the page. */
.ap-portal .adm-ticket-comms .adm-thread {
    max-height: min(46vh, 460px);
    overflow-y: auto;
    scrollbar-width: thin;
}



/* The ticket page, full width and with the header on one line.
 *
 * The 1360px cap goes: the request is for the whole viewport, and on a page whose columns are
 * already split the measure is set by the column, not by the page. Gutters stay so text never
 * runs into the window edge.
 *
 * The header was three stacked rows — back, then reference and pills, then subject — spending
 * about 120px of the most expensive space on the page on four short things that fit on one line.
 */
.ap-portal .adm-ticket-detail-head {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 12px;
    /* Was ~28px above the reference plus a stacked back link. The page starts at the top now. */
    margin: 0 0 4px;
    padding: 0;
    min-width: 0;
}

/* The reference identifies the ticket, so it does not shrink or wrap. */
.ap-portal .adm-ticket-detail-head .up-ref {
    flex: 0 0 auto;
    font-size: 13px;
}

/* The subject takes what is left and truncates. Letting it wrap would return the header to two
 * rows and undo the point; the full text is on the title attribute. */
.ap-portal .adm-ticket-subject {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: 16px;
    font-weight: 700;
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* State pills sit at the end, where the eye lands after reading the subject. */
.ap-portal .adm-ticket-head-pills {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    gap: 6px;
    white-space: nowrap;
}

.ap-portal .adm-ticket-detail-head .adm-ticket-back {
    flex: 0 0 auto;
    margin-bottom: 0;
}

/* Narrow: the back control keeps only its arrow, and the subject gets its own line rather than
 * being truncated to nothing. */
@media (max-width: 900px) {
    .ap-portal .adm-ticket-detail-head { flex-wrap: wrap; }
    .ap-portal .adm-ticket-back-label { display: none; }
    .ap-portal .adm-ticket-subject { flex-basis: 100%; white-space: normal; }
}

/* ================================================================== *
 * Ticket page — organised across, not down
 *
 * The page had the whole viewport and was still spending it vertically: four Moderation selects
 * at ~700px each for values like "IT" and "Medium", a Description card several times taller than
 * its two lines of text, and generous padding on every card. Wide screen, tall page, and the
 * conversation — the reason anybody opens this — pushed below the fold.
 *
 * The fix is per-section rather than a smaller font: each card lays its own content out across
 * the width it has. `display: contents` on the wrapper rows is what lets fields from separate
 * rows share one line without touching the markup.
 * ================================================================== */

/* --- Moderation: four fields, one row ------------------------------------- */

.ap-portal .adm-ticket-moderation {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 10px 12px;
    align-items: end;
}

/* The two wrapper rows stop being boxes so their four fields join the grid above. Without this
 * the fields can only ever pair off two and two, whatever the width. */
.ap-portal .adm-ticket-moderation > .up-modal-row {
    display: contents;
}

/* The heading and the action row are not fields; they take the whole width. */
.ap-portal .adm-ticket-moderation > h3,
.ap-portal .adm-ticket-moderation > div[style*="display:flex"] {
    grid-column: 1 / -1;
    margin: 0;
}

.ap-portal .adm-ticket-moderation .up-field { margin: 0; min-width: 0; }

.ap-portal .adm-ticket-moderation .up-field select {
    min-height: 36px;
    height: 36px;
}

/* --- Description: text beside its attachments ----------------------------- */

/* --- Less padding, everywhere it was decoration -------------------------- */

.ap-portal .adm-ticket-detail-card {
    gap: 10px;
    padding: clamp(10px, 1vw, 16px);
}

.ap-portal .adm-ticket-detail-card > .adm-block {
    padding: 12px 14px;
}

.ap-portal .adm-ticket-detail-card > .adm-block > h3 {
    margin: 0 0 8px;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--text-light);
}

/* The two comms cards are the substance, so they keep their room while everything above them
 * gives some back. */
.ap-portal .adm-ticket-comms { padding: 14px; }


/* ================================================================== *
 * Ticket detail — enterprise workbench
 * ================================================================== */

.ap-portal.admin-ticket-detail-page .up-main:has(#ticketListPane) {
    padding-inline: clamp(14px, 1.35vw, 28px);
    background: radial-gradient(circle at 82% 0%, color-mix(in srgb, var(--primary-color) 7%, transparent) 0, transparent 28rem), var(--sys-surface-page, var(--ecv-canvas, #f5f5f3));
}
.ap-portal.admin-ticket-detail-page .adm-split:has(#ticketListPane) > .adm-detail { width: 100%; max-width: none; margin-inline: 0; }
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); align-items: start; gap: clamp(10px, .9vw, 16px); width: 100%; padding: clamp(10px, 1vw, 18px) 0 28px; }
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-head { grid-column: 1 / -1; min-height: 52px; padding: 6px 4px 14px; border-bottom: 1px solid var(--ecv-line); }
.ap-portal.admin-ticket-detail-page .adm-ticket-subject { font-size: clamp(17px, 1.15vw, 21px); font-weight: 760; letter-spacing: -.018em; }
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block { position: relative; overflow: hidden; padding: clamp(14px, 1.15vw, 20px); border-color: color-mix(in srgb, var(--ecv-line) 84%, var(--text-color) 16%); border-radius: 16px; background: linear-gradient(145deg, color-mix(in srgb, var(--ecv-paper) 98%, white 2%), var(--ecv-paper)); box-shadow: 0 1px 1px color-mix(in srgb, var(--text-color) 6%, transparent), 0 12px 32px color-mix(in srgb, var(--text-color) 4%, transparent); }
html[data-theme="dark"] .ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block { background: linear-gradient(145deg, color-mix(in srgb, var(--ecv-paper) 94%, white 6%), var(--ecv-paper)); box-shadow: 0 14px 34px rgba(0, 0, 0, .16); }
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block > h3 { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; color: color-mix(in srgb, var(--text-color) 66%, var(--text-light)); font-size: 10.5px; letter-spacing: .135em; }
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block > h3::before { content: ''; width: 5px; height: 5px; flex: 0 0 auto; border-radius: 50%; background: var(--ecv-muted); box-shadow: 0 0 0 4px color-mix(in srgb, var(--ecv-muted) 12%, transparent); }
.ap-portal.admin-ticket-detail-page .adm-ticket-form-details { grid-column: 1 / -1; }
.ap-portal.admin-ticket-detail-page .adm-ticket-conversation { grid-column: span 8; }
.ap-portal.admin-ticket-detail-page .adm-ticket-notes { grid-column: span 4; }
.ap-portal.admin-ticket-detail-page .adm-ticket-history { grid-column: span 8; }
.ap-portal.admin-ticket-detail-page .adm-ticket-feedback { grid-column: span 4; }
.ap-portal.admin-ticket-detail-page .adm-facts dt, .ap-portal.admin-ticket-detail-page .up-field > label { color: var(--text-light); font-size: 10px; font-weight: 750; letter-spacing: .1em; }
.ap-portal.admin-ticket-detail-page .adm-facts dd { margin-top: 5px; color: var(--text-color); font-size: 13px; font-weight: 560; line-height: 1.45; }
.ap-portal.admin-ticket-detail-page .adm-ticket-moderation select, .ap-portal.admin-ticket-detail-page .adm-ticket-composer textarea { border-color: color-mix(in srgb, var(--ecv-line) 80%, var(--text-light) 20%); background-color: color-mix(in srgb, var(--ecv-paper) 94%, var(--ecv-soft) 6%); transition: border-color 180ms ease, box-shadow 180ms ease, background-color 180ms ease; }
.ap-portal.admin-ticket-detail-page .adm-ticket-moderation select:focus, .ap-portal.admin-ticket-detail-page .adm-ticket-composer textarea:focus { border-color: var(--primary-color); background-color: var(--ecv-paper); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 16%, transparent); outline: none; }
.ap-portal.admin-ticket-detail-page .adm-ticket-comms .adm-thread { min-height: 210px; max-height: none; padding: 12px; border-radius: 12px; background: linear-gradient(180deg, color-mix(in srgb, var(--ecv-soft) 78%, transparent), var(--ecv-soft)); }
.ap-portal.admin-ticket-detail-page .adm-ticket-conversation { border-top-color: color-mix(in srgb, #3b82f6 72%, var(--ecv-line)) !important; }
.ap-portal.admin-ticket-detail-page .adm-ticket-conversation > h3::before { background: #3b82f6; box-shadow: 0 0 0 4px color-mix(in srgb, #3b82f6 15%, transparent); }
.ap-portal.admin-ticket-detail-page .adm-ticket-notes { border-top-color: color-mix(in srgb, #d5a11e 82%, var(--ecv-line)) !important; }
.ap-portal.admin-ticket-detail-page .adm-ticket-notes > h3::before { background: #d5a11e; box-shadow: 0 0 0 4px color-mix(in srgb, #d5a11e 16%, transparent); }
.ap-portal.admin-ticket-detail-page .adm-ticket-composer textarea { min-height: 88px; padding: 12px 14px; font-size: 13px; line-height: 1.55; }
.ap-portal.admin-ticket-detail-page .adm-ticket-composer-actions .dept-btn, .ap-portal.admin-ticket-detail-page .adm-ticket-moderation .dept-btn, .ap-portal.admin-ticket-detail-page .adm-ticket-back { min-height: 44px; }
@media (max-width: 1280px) {
    .ap-portal.admin-ticket-detail-page .adm-ticket-conversation { grid-column: span 7; }
    .ap-portal.admin-ticket-detail-page .adm-ticket-notes { grid-column: span 5; }
    .ap-portal.admin-ticket-detail-page .adm-ticket-history, .ap-portal.admin-ticket-detail-page .adm-ticket-feedback { grid-column: span 6; }
}
@media (max-width: 560px) {
    .ap-portal.admin-ticket-detail-page .up-main:has(#ticketListPane) { padding-inline: 10px; }
    .ap-portal.admin-ticket-detail-page .adm-ticket-moderation { grid-template-columns: 1fr; }
    .ap-portal.admin-ticket-detail-page .adm-ticket-head-pills { flex-wrap: wrap; white-space: normal; }
}
@media (prefers-reduced-motion: reduce) {
    .ap-portal.admin-ticket-detail-page .adm-ticket-moderation select, .ap-portal.admin-ticket-detail-page .adm-ticket-composer textarea { transition: none; }
}

/* Ticket workbench v2 — precise, compact, and content-led. */
.ap-portal.admin-ticket-detail-page {
    --td-space-1: 4px;
    --td-space-2: 8px;
    --td-space-3: 12px;
    --td-space-4: 16px;
    --td-space-5: 24px;
    --td-space-6: 32px;
    --td-radius: var(--ecv-radius-card);
    --td-control-height: 44px;
    --td-font-xs: 11px;
    --td-font-sm: 13px;
    --td-weight-bold: 700;
    --td-border: var(--border-color);
    --td-surface: var(--card-bg);
    --td-surface-subtle: var(--ecv-soft);
}

.ap-portal.admin-ticket-detail-page .up-main:has(#ticketListPane) {
    background: var(--bg-light);
}

.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card {
    gap: var(--td-space-4);
    padding-block: var(--td-space-4) var(--td-space-6);
}

.ap-portal.admin-ticket-detail-page .adm-ticket-detail-head {
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr) auto;
    gap: var(--td-space-3);
    min-height: auto;
    padding: var(--td-space-2) 0 var(--td-space-4);
}

.ap-portal.admin-ticket-detail-page .adm-ticket-subject {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block {
    overflow: visible;
    padding: var(--td-space-5);
    border: 1px solid var(--td-border);
    border-radius: var(--td-radius);
    background: var(--td-surface);
    box-shadow: var(--ecv-shadow-raise);
}

html[data-theme="dark"] .ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block {
    background: var(--td-surface);
    box-shadow: none;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block > h3,
.ap-portal.admin-ticket-detail-page .adm-section-heading h3 {
    margin: 0;
    color: var(--text-color);
    font-size: var(--td-font-sm);
    font-weight: var(--td-weight-bold);
    letter-spacing: 0;
    text-transform: none;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block > h3::before {
    display: none;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-editor {
    grid-column: span 8;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation {
    grid-column: span 4;
    display: flex;
    flex-direction: column;
    gap: var(--td-space-4);
    /* align-self: stretch was here. It made this card as tall as the editor beside it — 684px
     * for about 380px of controls — so a third of the sidebar was empty and the action row sat
     * marooned at the bottom. A card is as tall as what is in it; the row's height is not a
     * measurement of this card's content. */
    align-self: start;
    border-top: 1px solid var(--td-border);
}

.ap-portal.admin-ticket-detail-page .adm-ticket-form-details {
    grid-column: 1 / -1;
}

.ap-portal.admin-ticket-detail-page .adm-section-heading {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--td-space-4);
    padding-block-end: var(--td-space-4);
    border-block-end: 1px solid var(--td-border);
}

.ap-portal.admin-ticket-detail-page .adm-section-heading p {
    margin: var(--td-space-1) 0 0;
    color: var(--text-light);
    font-size: var(--td-font-xs);
    font-variant-numeric: tabular-nums;
}

.ap-portal.admin-ticket-detail-page .adm-editor-grid,
.ap-portal.admin-ticket-detail-page .adm-form-details-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--td-space-4);
    margin-block-start: var(--td-space-5);
}

.ap-portal.admin-ticket-detail-page .adm-form-details-grid {
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.ap-portal.admin-ticket-detail-page .adm-field-span-2 {
    grid-column: 1 / -1;
}

.ap-portal.admin-ticket-detail-page .up-field > label {
    display: block;
    margin-block-end: var(--td-space-2);
    color: var(--text-light);
    font-size: var(--td-font-xs);
    font-weight: var(--td-weight-bold);
    letter-spacing: 0;
    text-transform: none;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-editor input,
.ap-portal.admin-ticket-detail-page .adm-ticket-editor select,
.ap-portal.admin-ticket-detail-page .adm-ticket-editor textarea,
.ap-portal.admin-ticket-detail-page .adm-ticket-form-details input {
    width: 100%;
    min-height: var(--td-control-height);
    padding-inline: var(--td-space-3);
    border: 1px solid var(--td-border);
    border-radius: var(--ecv-radius-control);
    background: var(--input-bg);
    color: var(--text-color);
    font: inherit;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-editor textarea {
    min-height: 132px;
    padding-block: var(--td-space-3);
    resize: vertical;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-editor :is(input, select, textarea):focus-visible,
.ap-portal.admin-ticket-detail-page .adm-ticket-form-details input:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

.ap-portal.admin-ticket-detail-page .adm-attachments-zone {
    margin-block-start: var(--td-space-4);
    padding-block-start: var(--td-space-4);
    border-block-start: 1px solid var(--td-border);
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation .up-modal-row {
    grid-template-columns: 1fr;
    gap: var(--td-space-4);
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation > div:last-child {
    margin-block-start: auto !important;
    padding-block-start: var(--td-space-4);
    border-block-start: 1px solid var(--td-border);
}

.ap-portal.admin-ticket-detail-page .adm-ticket-comms {
    min-height: 420px;
    height: auto;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-comms .adm-thread {
    min-height: 180px;
    padding: var(--td-space-3);
    border: 1px solid var(--td-border);
    border-radius: var(--ecv-radius-control);
    background: var(--td-surface-subtle);
}

.ap-portal.admin-ticket-detail-page .adm-ticket-conversation,
.ap-portal.admin-ticket-detail-page .adm-ticket-notes {
    border-top-color: var(--td-border) !important;
}

@media (max-width: 1100px) {
    .ap-portal.admin-ticket-detail-page .adm-ticket-editor,
    .ap-portal.admin-ticket-detail-page .adm-ticket-moderation {
        grid-column: 1 / -1;
    }
}

@media (max-width: 700px) {
    .ap-portal.admin-ticket-detail-page .adm-ticket-detail-head {
        grid-template-columns: auto 1fr;
    }
    .ap-portal.admin-ticket-detail-page .adm-ticket-subject,
    .ap-portal.admin-ticket-detail-page .adm-ticket-head-pills {
        grid-column: 1 / -1;
        white-space: normal;
    }
    .ap-portal.admin-ticket-detail-page .adm-editor-grid {
        grid-template-columns: 1fr;
    }
    .ap-portal.admin-ticket-detail-page .adm-field-span-2 {
        grid-column: auto;
    }
    .ap-portal.admin-ticket-detail-page .adm-section-heading {
        flex-direction: column;
    }
}

/* ================================================================== *
 * THE TICKET WORKBENCH — ONE AUTHORITATIVE LAYOUT
 *
 * This block is the single place the detail page's geometry is decided. It exists because
 * there were two of them, from two generations of this screen, in this same file — and the
 * layout a reader actually got was neither, because which rule won depended on source order:
 *
 *   .adm-ticket-information { grid-column: span 8 }   matched NOTHING. The template emits
 *   .adm-ticket-description { grid-column: span 4 }   .adm-ticket-editor. Fourteen rules were
 *                                                    written against a DOM that no longer
 *                                                    existed, so the top of the page had four
 *                                                    of its twelve columns assigned to a block
 *                                                    that was never rendered — the empty
 *                                                    right-hand third of the screen.
 *
 *   .adm-ticket-moderation { grid-column: 1 / -1;     …was overridden 120 lines later by
 *                           grid-template-columns:   { grid-column: span 4; display: flex }.
 *                           repeat(4, minmax(170px)) So Moderation became a 520px flex column
 *                         }                          holding a four-column grid that needed
 *                                                    680px, inside a block with
 *                                                    overflow: hidden. That is why its fields
 *                                                    looked stacked, ragged and pushed to one
 *                                                    side: they were a clipped grid, not a
 *                                                    column of fields.
 *
 *   .adm-ticket-comms { min-height: 480px;            A card whose only content was
 *                      height: min(62vh, 650px) }    "No messages yet." was 650px tall. Two of
 *                                                    them side by side was most of a screen
 *                                                    spent on two sentences.
 *
 * All of the above is deleted. What follows assigns every block exactly once.
 *
 * THE SHAPE
 *
 *   +---------------------------------+---------------+
 *   | Ticket details        (span 8)  | Moderation    |  the editor is the work; the controls
 *   | subject, requester, type, SLA,  | (span 4)      |  sit beside it and stay in view while
 *   | description, attachments        | sticky        |  a long description is edited
 *   +---------------------------------+---------------+
 *   | Form details                          (full)    |
 *   +---------------------------------+---------------+
 *   | Conversation          (span 8)  | Notes (span 4)|  the reply the requester sees is wider
 *   +---------------------------------+---------------+  than the note only staff see
 *   | History               (span 6)  | Feedback (6)  |  two records of equal weight
 *   +---------------------------------+---------------+
 * ================================================================== */

/* Full width. The workbench is a comparison surface — an editor beside its controls, a
 * conversation beside its notes — and a max-width would put a margin where a column belongs. */
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    align-items: start;
    gap: clamp(10px, .85vw, 16px);
    width: 100%;
    max-width: none;
    margin-inline: 0;
    padding: clamp(10px, 1vw, 18px) 0 32px;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-detail-head { grid-column: 1 / -1; }
.ap-portal.admin-ticket-detail-page .adm-ticket-editor { grid-column: span 8; }
.ap-portal.admin-ticket-detail-page .adm-ticket-moderation { grid-column: span 4; }
.ap-portal.admin-ticket-detail-page .adm-ticket-form-details { grid-column: 1 / -1; }
.ap-portal.admin-ticket-detail-page .adm-ticket-conversation { grid-column: span 8; }
.ap-portal.admin-ticket-detail-page .adm-ticket-notes { grid-column: span 4; }
.ap-portal.admin-ticket-detail-page .adm-ticket-history { grid-column: span 6; }
.ap-portal.admin-ticket-detail-page .adm-ticket-feedback { grid-column: span 6; }

/* EVERY CARD IS AS TALL AS ITS CONTENT.
 *
 * No min-height anywhere on these blocks. An empty History card is two lines tall, and it
 * should look like two lines — a 150px box holding one sentence reads as something failing to
 * load. The one place a floor belongs is the scrolling thread, below. */
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block {
    height: auto;
    min-height: 0;
    max-height: none;
    /* overflow was hidden, which is what clipped the Moderation grid rather than revealing the
     * bug. Clipping is not the job of a card; a child that overflows is a child to fix. */
    overflow: visible;
}

/* --- Moderation, as a proper sidebar ------------------------------
 *
 * A single column of full-width fields, which is what a 4-of-12 column can hold honestly.
 * .up-modal-row is a two-up wrapper from the modal library; here it must not pair anything up,
 * so its children become a column instead. That is what stops the ragged select widths — each
 * field now measures against the column, not against its sibling. */
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-ticket-moderation {
    display: flex;
    flex-direction: column;
    gap: 12px;
    /* align-items MUST be stated. An earlier generation set align-items: end on this element
     * while it was a grid; in a flex COLUMN that property controls the horizontal axis, so
     * every field shrank to its own content and hugged the trailing edge — a 120px Status
     * select above a 452px Department field, drifting right. That is the ragged sidebar in the
     * review, and it was one inherited declaration, not a layout mistake. */
    align-items: stretch;
    /* NOT sticky. It was, and that is the overlap in the review.
     *
     * A sticky grid item travels inside its own grid area, and this one's area is row one —
     * so once the card was taller than the viewport it stuck, kept travelling, and painted
     * over Form Details and Internal Notes on the rows beneath. The controls appeared through
     * the cards below them and the action row was clipped at the bottom of the screen.
     *
     * Sticky was there to keep the controls in view while a long description scrolled past.
     * Now that the card is content-height rather than stretched to the editor's 684px, it is
     * about 380px tall and mostly in view anyway — so the cure cost more than the symptom.
     * Ordinary flow, no overlap. */
    position: static;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation > .up-modal-row {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation .up-field {
    margin: 0;
    min-width: 0;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation .up-field select {
    /* Full width of the column. These were sizing to their widest option, so "Unassigned" was
     * twice the width of "None" and the sidebar read as a ragged stack. */
    width: 100%;
    min-height: 38px;
    text-align: start;
}

/* The action row sits at the foot of the sidebar, separated by a rule rather than by space. */
.ap-portal.admin-ticket-detail-page .adm-ticket-moderation > div[style*="display:flex"] {
    margin-top: 4px;
    padding-top: 12px;
    border-top: 1px solid var(--ecv-line);
}

/* --- The threads --------------------------------------------------
 *
 * The scroll region is the only thing here with a height, and it is a ceiling rather than a
 * fixed figure: tall enough to be worth scrolling, and an empty one collapses to its message
 * instead of holding open a void. */
.ap-portal.admin-ticket-detail-page .adm-ticket-comms .adm-thread {
    height: auto;
    min-height: 0;
    max-height: 46svh;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* --- Responsive ---------------------------------------------------
 *
 * Two steps, and each states the whole layout for its width rather than patching the step
 * above — which is how the two generations above came to disagree.
 */

/* Tablet and small laptop: the editor takes the width, and the controls sit under it as a row
 * of fields again, because a full-width sidebar would be a column of stretched selects. */
@media (max-width: 1180px) {
    .ap-portal.admin-ticket-detail-page .adm-ticket-editor,
    .ap-portal.admin-ticket-detail-page .adm-ticket-moderation,
    .ap-portal.admin-ticket-detail-page .adm-ticket-conversation,
    .ap-portal.admin-ticket-detail-page .adm-ticket-notes,
    .ap-portal.admin-ticket-detail-page .adm-ticket-history,
    .ap-portal.admin-ticket-detail-page .adm-ticket-feedback {
        grid-column: 1 / -1;
    }

    .ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-ticket-moderation {
        position: static;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
        align-items: end;
    }

    .ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-ticket-moderation > .up-modal-row {
        display: contents;
    }

    .ap-portal.admin-ticket-detail-page .adm-ticket-moderation > h3,
    .ap-portal.admin-ticket-detail-page .adm-ticket-moderation > div[style*="display:flex"] {
        grid-column: 1 / -1;
    }
}

/* Phone. One column, and the card stops being a grid at all — a 12-column grid with every
 * child spanning all of it is a flex column with extra steps. The phone layer
 * (shared/css/portal-mobile.css) has already released this portal's viewport lock by here, so
 * the page scrolls normally and the cards simply stack. */
@media (max-width: 767.98px) {
    .ap-portal.admin-ticket-detail-page .adm-ticket-detail-card {
        display: flex;
        flex-direction: column;
        gap: 10px;
        padding-block: 8px 24px;
        /* MUST be restated. The base rule sets align-items: start, which is right for a grid —
         * it stops a short card stretching to its row's height — but this is now a flex column,
         * where align-items governs the HORIZONTAL axis instead. Left as start, every card
         * shrank to its own content and the stack came out ragged: 302px, 344px, 257px, 224px.
         * Same property, same value, opposite meaning once the display type changes. */
        align-items: stretch;
    }

    .ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-ticket-moderation {
        display: flex;
        flex-direction: column;
        position: static;
        /* The tablet step above is a max-width query, so it also matches a phone, and it sets
         * align-items: end for its single-row grid. Back to stretch here or the fields hug the
         * trailing edge again. */
        align-items: stretch;
    }

    .ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-ticket-moderation > .up-modal-row {
        display: flex;
        flex-direction: column;
        gap: 10px;
    }

    .ap-portal.admin-ticket-detail-page .adm-editor-grid,
    .ap-portal.admin-ticket-detail-page .adm-form-details-grid {
        grid-template-columns: 1fr;
        gap: 10px;
        margin-block-start: 12px;
    }

    .ap-portal.admin-ticket-detail-page .adm-ticket-comms .adm-thread {
        max-height: 52svh;
    }

    /* The head stacks: back control and reference, then the subject, then the pills. */
    .ap-portal.admin-ticket-detail-page .adm-ticket-detail-head {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
    }

    .ap-portal.admin-ticket-detail-page .adm-ticket-subject {
        flex: 1 1 100%;
        font-size: 16.5px;
        white-space: normal;
    }
}

/* The hint under the Department select. One line, quiet, sitting with its field rather than
 * floating between fields — it explains that this control is the transfer, which is the thing
 * an administrator could not previously tell by looking. */
.ap-portal.admin-ticket-detail-page .adm-field-hint {
    margin: 5px 0 0;
    color: var(--text-light);
    font-size: 11px;
    font-weight: 500;
    line-height: 1.45;
    letter-spacing: 0;
    text-transform: none;
}

/* ================================================================== *
 * COMPACTION
 *
 * The layout was right and still loose. Measured on the review screenshot: 16px between every
 * control in a sidebar of five, 20px of padding inside every card, and a 6-row description
 * textarea — so a ticket carrying two facts occupied a screen and a half.
 *
 * Everything here is spacing and nothing here is size: no text shrinks, no control loses its
 * touch target. Density comes from the gaps between things, which is the only place it can come
 * from without costing legibility.
 * ================================================================== */

/* Cards. 20px of padding on a card whose heading is 10.5px reads as a frame around a label. */
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block {
    padding: clamp(11px, .8vw, 15px);
}

/* The gap between cards, and the grid's own gutter. */
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card {
    gap: 10px;
    padding-block: 10px 24px;
}

/* Section headings sit closer to what they label. */
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-block > h3 {
    margin-bottom: 8px;
}

/* The head. A 52px minimum on a row holding one line of text is a reserved gap. */
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-head {
    min-height: 0;
    padding: 2px 2px 10px;
}

/* --- Moderation ---------------------------------------------------
 *
 * Five fields at 16px apart came to 380px of sidebar for 190px of controls. 10px is still a
 * clear separation between a label/field pair and the next one — the label's own 6px margin
 * does the fine grouping. */
.ap-portal.admin-ticket-detail-page .adm-ticket-detail-card > .adm-ticket-moderation {
    gap: 10px;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation > .up-modal-row {
    gap: 10px;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation .up-field > label {
    margin-block-end: 4px;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-moderation .up-field select {
    min-height: 36px;
}

/* The action row: closer to the last field, and its own separator carries the grouping. */
.ap-portal.admin-ticket-detail-page .adm-ticket-moderation > div[style*="display:flex"] {
    margin-top: 2px;
    padding-top: 10px;
}

/* --- The editor ---------------------------------------------------
 *
 * The description was six rows tall whether it held one line or twenty. It now starts at three
 * and grows with what is in it up to a ceiling, so a one-line description costs one line. */
.ap-portal.admin-ticket-detail-page .adm-editor-grid {
    gap: 10px 12px;
    margin-block-start: 12px;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-editor textarea {
    min-height: 84px;
    max-height: 40svh;
    field-sizing: content;
}

.ap-portal.admin-ticket-detail-page .adm-form-details-grid {
    gap: 10px 12px;
    margin-block-start: 12px;
}

.ap-portal.admin-ticket-detail-page .adm-attachments-zone {
    margin-block-start: 10px;
}

/* --- The threads --------------------------------------------------
 *
 * An empty thread is one line of text, so it should be one line of box. The composer keeps its
 * own comfortable size — it is the thing being typed into. */
.ap-portal.admin-ticket-detail-page .adm-ticket-comms .adm-thread {
    padding: 10px;
    margin-block-end: 10px;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-composer textarea {
    min-height: 72px;
}

.ap-portal.admin-ticket-detail-page .adm-ticket-composer-actions {
    margin-block-start: 8px;
}

/* --- Arabic in an LTR interface -----------------------------------
 *
 * A ticket raised in Arabic is read on an English screen every day here. The identity's Latin
 * stack now carries the Arabic cut as a fallback (shared/css/ecvalla-identity.css), so Arabic
 * glyphs get ECVALLA's own face in either direction. What that stack cannot fix is form
 * controls: a browser does not inherit the page font into <input>, <select> and <textarea>
 * unless it is told to, so an Arabic subject typed into a field fell back to the platform's
 * default UI face while the same words in a heading beside it used the identity's.
 *
 * Stated on the controls rather than globally: `font: inherit` on a control is the standard
 * fix, and doing it here keeps it next to the reason. */
.ap-portal.admin-ticket-detail-page .adm-ticket-editor :is(input, select, textarea),
.ap-portal.admin-ticket-detail-page .adm-ticket-moderation :is(input, select, textarea),
.ap-portal.admin-ticket-detail-page .adm-ticket-form-details :is(input, select, textarea),
.ap-portal.admin-ticket-detail-page .adm-ticket-composer textarea {
    font-family: var(--ecv-font-latin);
}
