﻿/*
 * PC layout, layered on top of agent.css.
 *
 * agent.css keeps its job: it styles the components â€” state pill, call buttons, caller block, menu.
 * This file only decides where they sit. Keeping the split that way means a change to
 * a component still lands in one place for both front ends, and only the arrangement is duplicated.
 *
 * The mobile stylesheet stacks everything in a single flex column and spends effort on safe areas
 * and dynamic viewport units. Neither applies here, so both are overridden rather than worked
 * around.
 */

/* ------------------------------------------------------------------- shell */

/*
 * Two columns, three rows. The menu is sized to its content, so when it is hidden its column
 * collapses to nothing and the main area takes the space â€” no media query, and nothing to keep in
 * sync with the panel's own visibility.
 *
 * The menu spans the two rows below the app bar, which is what puts it beside the call bar rather
 * than under it.
 */
/* Selected by id as well as class deliberately: agent.css sets `#app { display: flex }`, and an id
   beats a class however far down the cascade this file sits. */
#app.app-shell {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-rows: auto auto auto minmax(0, 1fr);
    grid-template-areas:
        "appbar appbar"
        "nav    callbar"
        "nav    widgets"
        "nav    main";
    height: 100vh;
    overflow: hidden;
}

/*
 * The custom widget layout's global slot: its own row, under the call bar and above whatever screen
 * is on show, so a widget an organisation wants everywhere is in the same place everywhere.
 *
 * A row rather than something inside main, because it must not scroll away with a screen. It is
 * auto-sized and the row collapses to nothing on a page that renders no host at all â€” which is
 * every page but Pages/WidgetMockup today, so the desk is laid out exactly as it was.
 */
.widget-global {
    grid-area: widgets;

    /* The sides match .main's padding, so a global widget lines up with the screen under it rather
       than sitting a few pixels wider than everything else on the page. */
    padding: .75rem 1.5rem 0;
    overflow-x: hidden;
}

/* The last row inside the slot carries the padding under it, so an empty slot adds no gap. */
.widget-global .row:last-child {
    margin-bottom: .75rem !important;
}

/* ---------------------------------------------------------------- main menu */

.sidenav {
    grid-area: nav;
    display: flex;
    flex-direction: column;
    gap: .125rem;
    width: 13rem;
    padding: .5rem;
    overflow-y: auto;
    overflow-x: hidden;
    background: var(--bs-body-bg);
    border-right: 1px solid var(--bs-border-color);
}

/* Only the labels go. Every item keeps its place and its icon, so collapsing does not move
   anything an agent has learned the position of. */
.sidenav.is-collapsed {
    width: 3.5rem;
}

.sidenav.is-collapsed .sidenav-label {
    display: none;
}

@media (prefers-reduced-motion: no-preference) {
    .sidenav {
        transition: width .15s ease;
    }
}

.sidenav-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .125rem;
}

.sidenav-item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: .75rem;
    min-height: 2.5rem;
    padding: .5rem;
    border: 0;
    border-radius: .5rem;
    background: none;
    color: var(--bs-body-color);
    font-size: .875rem;
    text-align: left;
    white-space: nowrap;
}

.sidenav-item:hover {
    background: var(--bs-tertiary-bg);
}

.sidenav-item:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: -2px;
}

.sidenav-item.is-active {
    background: color-mix(in srgb, var(--bs-primary) 12%, transparent);
    color: var(--bs-primary);
    font-weight: 600;
}

/* Same stroke treatment as the call buttons, so the two sets of icons read as one family. */
.sidenav-icon {
    flex: 0 0 auto;
    width: 1.375rem;
    height: 1.375rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/*
 * A Font Awesome icon standing in the same slot as the hand-drawn SVGs, for a menu item a layout
 * named an icon for.
 *
 * It keeps the SVG's box so an item is the same height whichever kind of icon it got, and the two
 * sit on the same vertical line down the menu â€” a column of icons that do not line up is more
 * noticeable than any single icon in it. The glyph is drawn at 1.05rem inside that 1.375rem box:
 * Font Awesome's icons fill their em where the drawn ones leave a margin, so matching the box makes
 * them look bigger than their neighbours.
 */
.sidenav-icon-fa {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    line-height: 1;

    /* The stroke properties above are meaningless on a font glyph, and fill: none would erase it. */
    fill: currentColor;
    stroke: none;
}

/*
 * The foot of the menu, where the collapse toggle lives.
 *
 * margin-top: auto rather than a fixed offset, so the toggle is on the floor of the panel whether
 * there is one destination above it or ten, and the rule separates it from them the way being
 * first used to separate it by position.
 */
.sidenav-foot {
    margin-top: auto;
    padding-top: .5rem;
    border-top: 1px solid var(--bs-border-color);
}

/* Set apart from the destinations above it: it changes the menu, it does not go anywhere. */
.sidenav-toggle {
    color: var(--bs-secondary-color);
}

/* Points the way the panel is about to move: inward while it is open, outward once it is folded. */
.sidenav-chevron {
    transition: transform .15s ease;
}

.sidenav.is-collapsed .sidenav-chevron {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
    .sidenav-chevron {
        transition: none;
    }
}

.appbar {
    grid-area: appbar;
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0 1rem;
    background: var(--bs-tertiary-bg);
    border-bottom: 1px solid var(--bs-border-color);

    /*
     * Lifted above the rows beneath it, because everything that drops out of this bar â€” the
     * account menu under the initials, the caller ID picker beside the dial field â€” opens downward
     * across them. Kept well under Bootstrap's own layers so offcanvas panels, the incoming call
     * and toasts all still come out on top of it.
     */
    position: relative;
    z-index: 20;
}

/* Inline in the app bar rather than a band of its own, and without the safe-area padding that
   only means something on a notched phone. */
.appbar .brandbar {
    padding: .5rem 0;
    background: none;
    border-bottom: 0;
    flex: 0 0 auto;
}

.appbar .topbar {
    position: static;
    flex: 1 1 auto;
    background: none;
    border-bottom: 0;
}

/*
 * Group the whole set of controls at the right, away from the brand.
 *
 * agent.css puts the auto margin on the connection badge, which on a phone correctly leaves the
 * state pill hugging the left edge. Here that stranded things against the wordmark.
 *
 * The row is packed to the end instead of any one item carrying an auto margin, so state,
 * connection and initials travel together whichever of them is on the page â€” the badge hides itself
 * when the connection is healthy, and the dial field left this bar for the desk entirely. Both of
 * those were their own rule while the margin sat on a named item.
 */
.appbar .topbar-inner {
    min-height: 4rem;
    padding: 0;
    justify-content: flex-end;
}

.appbar .conn-badge {
    margin-left: 0;
}

/*
 * Fixed width, so the bar stops resizing itself.
 *
 * The label is whatever the state happens to be called â€” "Taler" is five characters, "Efterbehandling"
 * is fifteen, and a tenant's own pause codes can be anything at all. Sized to the platform's own
 * longest word and let anything beyond that ellipsise: a label that grows and shrinks drags the
 * connection badge and the initials sideways with it every time a call changes phase, and things
 * that move are things you cannot aim at.
 */
/* The pill sits in a dropdown wrapper now, so the wrapper is what the flex row sizes. */
.appbar .state-dropdown {
    flex: 0 0 auto;
}

/*
 * Framed like the call bar's card rather than as a pill. The two sit one above the other and are
 * read together, so they should look like the same kind of thing.
 *
 * These are the globals a .card resolves its own variables from â€” not --bs-card-* itself, which
 * Bootstrap declares inside the .card rule and which therefore means nothing out here. Referencing
 * those left the pill with no radius, no background and a border in the text colour.
 */
.appbar .state-pill {
    width: 12rem;
    min-height: 2.75rem;
    border-color: var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius);
    background: var(--bs-body-bg);
}

/* Wide enough for a tenant's own pause codes without becoming a column of its own, and capped so a
   long list scrolls rather than running past the bottom of the window. */
.state-menu {
    width: 15rem;
    max-height: min(24rem, calc(100vh - 5rem));
    overflow-y: auto;
}

/* Every entry carries the dot it will put in the pill, so the list reads as the thing the pill is
   showing one of. */
.state-menu .dropdown-item {
    display: flex;
    align-items: center;
    gap: .625rem;
}

/* Bigger than on a phone, in the pill and in the list alike â€” it is the one thing on this bar an
   agent checks without reading. */
.appbar .state-dot,
.state-menu .state-dot {
    width: 1rem;
    height: 1rem;
    flex: 0 0 auto;
}

/*
 * Not Ready is red, where agent.css uses amber.
 *
 * --state-idle itself is deliberately left alone and only the dot is overridden, in every place it
 * appears â€” the bar, the picker, and the team table on the statistics screen. The variable is the
 * shared idle colour for both front ends, and redefining it here would make this stylesheet the odd
 * one out for anything that picks it up later.
 *
 * Every place, and it has to be every place: a colleague showing amber for the same state the
 * agent's own pill shows red is two colours for one fact, and the reader has no way to know which
 * of them means something.
 */
.appbar .state-pill[data-state="Idle"] .state-dot,
.state-menu .dropdown-item[data-state="Idle"] .state-dot,
.stats-state[data-state="Idle"] .state-dot,
.mate-state[data-state="Idle"] .state-dot,
.stats-staffbar > span[data-state="idle"] {
    background: var(--bs-danger);
}

.state-menu .dropdown-item[data-state="Available"] .state-dot {
    background: var(--state-available);
}

.appbar .state-text {
    min-width: 0;
    width: 100%;
}

.appbar #state-label {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ------------------------------------------------------------ menu dropdown */

.menu-dropdown {
    width: 20rem;
    /*
     * As tall as the window allows, and no fixed ceiling above that. There was one â€” 38rem, then
     * 41rem â€” and both were wrong the moment a field was added: a cap in rem is a guess at how much
     * menu there will be, made in a stylesheet that finds out last.
     *
     * The menu no longer comes anywhere near this now that the settings have their own dialog, and
     * that is the point of leaving the rule as the window rather than as a number: whatever this
     * list grows into, it stops at the screen and not before.
     *
     * The 5rem is the app bar the panel hangs from plus room to breathe at the bottom.
     */
    max-height: calc(100vh - 5rem);
    overflow-y: auto;
    padding: 1rem;
}

/* The menu blocks were written for a full-height sheet, where they could spread out. */
.menu-dropdown .menu-user {
    padding-bottom: .75rem;
    margin-bottom: .75rem;
}

.menu-dropdown .menu-field,
.menu-dropdown .shortcuts {
    margin-bottom: 1rem;
}

.menu-dropdown .menu-facts {
    margin-bottom: 1rem;
}

/* Separated from the rest: sign-out sits here, and it should not read as one more setting. */
.menu-dropdown .menu-actions {
    padding-top: .75rem;
    border-top: 1px solid var(--bs-border-color);
}

.menu-dropdown .menu-version {
    margin: .75rem 0 0;
}

/* ------------------------------------------------------ settings row */

/* The way in: a row, not a button, because it goes somewhere rather than doing something. */
.menu-nav {
    display: flex;
    align-items: center;
    gap: .625rem;
    width: 100%;
    padding: .625rem .75rem;
    margin-bottom: 1rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    background: transparent;
    color: inherit;
    font-size: .875rem;
    text-align: left;
}

.menu-nav:hover,
.menu-nav:focus-visible {
    background: var(--bs-secondary-bg);
}

.menu-nav-text {
    flex: 1 1 auto;
}

/* Same stroke family as the theme icons, so the whole menu reads as one set. */
.menu-nav-icon {
    flex: 0 0 auto;
    width: 1.125rem;
    height: 1.125rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.menu-nav-chevron {
    width: 1rem;
    height: 1rem;
    color: var(--bs-secondary-color);
}

/* ---------------------------------------------------- settings dialog */

/*
 * A card over the desk, the same idiom as the incoming-call overlay and deliberately its twin â€” one
 * kind of box to learn rather than two. Bigger, because this one is read rather than reacted to.
 *
 * z-index sits below .incoming (1080). A call arriving while this is open closes it, so the two are
 * never both up; the ordering is there for the moment between the two, and for the case where
 * something goes wrong and the agent has to be able to see the call regardless.
 */
.settings-dialog {
    position: fixed;
    inset: 0;
    z-index: 1070;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1.5rem;
}

/* Its own element rather than the dialog's background, so a click on it can be told from a click on
   the card without asking where the pointer landed. */
.settings-backdrop {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--bs-emphasis-color) 35%, transparent);
}

.settings-card {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    /*
     * Wide enough for two columns of settings and no wider. Past about this the eye has to travel
     * from a label on the left to a control on the right, and a settings screen is a thing you scan.
     */
    max-width: 46rem;
    max-height: calc(100vh - 4rem);
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 1rem;
    box-shadow: 0 1.5rem 3rem rgb(0 0 0 / .25);
}

/* Stays put while the body scrolls: the way out should not be something you scroll back up to. */
.settings-head {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex: 0 0 auto;
    padding: 1.25rem 1.5rem;
    border-bottom: 1px solid var(--bs-border-color);
}

.settings-title {
    flex: 1 1 auto;
    margin: 0;
    font-size: 1.125rem;
    font-weight: 600;
}

.settings-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 1.5rem;
}

/*
 * The reason this dialog exists. The settings outgrew a 20rem dropdown by only ever being able to
 * grow downwards; here the next section costs a column.
 *
 * Columns rather than a grid, and that is the whole trick. Grid places a section in a cell and the
 * cell is as tall as the tallest section in its row, so a short section next to the sound block
 * left a hand's width of nothing under it. Columns pack: a section starts where the last one ended.
 *
 * The 17rem is the width below which two columns stop being readable, at which point they become
 * one â€” so the same rule covers a narrow window without a media query.
 */
.settings-grid {
    columns: 2 17rem;
    column-gap: 2rem;
}

/* A section split across a column break would read as two settings groups with one heading. */
.settings-section {
    break-inside: avoid;
    margin-bottom: 1.5rem;
}

.settings-section-title {
    margin-bottom: .75rem;
    font-size: .75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--bs-secondary-color);
}

/* ------------------------------------------------------- audio settings */

/* Tighter than the menu's rhythm: a section of six fields spaced for a list of two is a column of
   air, and the grid gives the sections their separation already. */
.settings-section .menu-field {
    margin-bottom: .875rem;
}

.settings-section .menu-field:last-child {
    margin-bottom: 0;
}

/* Tucked under the select it qualifies, at the size of the hint text it sits where. */
.menu-check {
    margin-top: .375rem;
    min-height: auto;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}

.menu-check .form-check-input {
    margin-top: .2em;
}

/*
 * The label carries the reading as well as the name, so it spans the field and puts them at either
 * end. Bootstrap's .form-label is inline-block and shrinks to its text, which is why this is a flex
 * row rather than the float it looks like it wants to be.
 */
.settings-section .form-label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
    width: 100%;
}

/* Tabular figures, so the number does not jog sideways as the agent drags the slider. */
.menu-range-value {
    font-variant-numeric: tabular-nums;
    color: var(--bs-secondary-color);
}

/* The slider and its try-it button, which is only useful beside the thing it demonstrates. */
.menu-range-row {
    display: flex;
    align-items: center;
    gap: .625rem;
}

.menu-range-row .form-range {
    flex: 1 1 auto;
}

.menu-range-row .btn {
    flex: 0 0 auto;
}

/* ------------------------------------------------------------ theme picker */

/*
 * Lys / MÃ¸rk / System, in the account menu under the initials.
 *
 * Three choices rather than one switch, because the desk has followed the operating system since
 * it was built and that has to stay reachable â€” a two-way switch would silently pin whichever
 * theme the agent happened to be looking at when they first touched it, and there would be no way
 * back to following the machine.
 *
 * A segmented row rather than a select: three options, all worth showing at once, and the group is
 * narrow enough that the labels still fit beside their icons at the menu's 20rem.
 */
.menu-theme .btn-group {
    width: 100%;
}

.menu-theme .btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .375rem;
    padding-inline: .25rem;
}

/* Same stroke family as the menu icons, so the three read as one set. */
.menu-theme-icon {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* --------------------------------------------------------------- shortcuts */

.shortcuts-title {
    font-size: .75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--bs-secondary-color);
    margin-bottom: .5rem;
}

/* Two pairs per row. In a dropdown the height is the scarce dimension, and eight single-file rows
   pushed sign-out below the fold â€” where the one control nobody should have to hunt for was the
   one you had to scroll to. */
.shortcuts-list {
    display: grid;
    grid-template-columns: repeat(2, auto minmax(0, 1fr));
    gap: .25rem .5rem;
    margin: 0;
    font-size: .8125rem;
}

.shortcuts-list dt {
    font-weight: 400;
}

.shortcuts-list dd {
    margin: 0;
    color: var(--bs-secondary-color);
}

kbd {
    display: inline-block;
    min-width: 1.5rem;
    padding: .0625rem .3125rem;
    font-size: .75rem;
    text-align: center;
    background: var(--bs-secondary-bg);
    color: var(--bs-body-color);
    border: 1px solid var(--bs-border-color);
    border-bottom-width: 2px;
    border-radius: .25rem;
}

/* -------------------------------------------------------------------- main */

.main {
    grid-area: main;
    overflow-y: auto;
    padding: 1.5rem;

    /* Same tablet caps as the call bar. The column already bounds this, and the readable width is
       handled below on the screens themselves. */
    max-width: none;
    width: auto;
    margin-inline: 0;
}

/*
 * The working area is centred and capped. A caller's number stretched across a 27-inch monitor is
 * harder to read than the same number in a column, not easier.
 */
.main > .screen {
    max-width: 46rem;
    margin: 0 auto;
}

.screen-center {
    min-height: 100%;
}

/*
 * The between-calls state, which on this layout sits inside the desk card rather than in a bare
 * column. agent.css pushes it a long way down the phone screen, where it is the only thing there;
 * here it is one of three cards in a row and has a heading above it, so it gets the same 2rem the
 * other two cards give their empty states. Any more and the row's three "nothing yet" messages sit
 * at three different heights.
 */
.desk-idle {
    padding-top: 2rem;
}

/* ----------------------------------------------------------------- callbar */

/*
 * A strip of its own under the app bar, holding the controls and the call variables side by side.
 *
 * agent.css builds this as a five-column grid stuck to the bottom of a phone. Here it is a row in
 * the frame, so both the positioning and the grid are replaced outright.
 */
.callbar {
    grid-area: callbar;
    position: static;
    display: block;

    /*
     * 1.5rem sideways to match `.main` below, which is the other half of this grid column: at 1rem
     * the strip ran half a rem wider than the row of cards under it at both ends, and had done
     * since this layout was written.
     *
     * This is the one that moved, not `.main`. Narrowing `.main` to match would shift every other
     * screen in the app â€” sign-in, station, statistics, the task list â€” to fix an edge only the desk
     * has. The .75rem stays: the strip is meant to be tight under the app bar.
     *
     * The app bar is not part of this and should not be. It spans both grid columns, over the side
     * menu as well, so its 1rem is measured from the window edge rather than from where these two
     * measure from.
     */
    padding: .75rem 1.5rem;
    background: transparent;
    border: 0;

    /*
     * agent.css gives this z-index 1030 to keep the sticky bottom bar above a scrolling phone
     * screen. There is nothing sticky here, but a grid item honours z-index without being
     * positioned â€” so the bar was painting over the app bar's dropdowns.
     */
    z-index: auto;

    /*
     * agent.css caps this at 46rem, then 60rem, and centres it â€” right for the mobile app on a
     * tablet in landscape, wrong for a strip that is part of this frame. Media queries add no
     * specificity, so saying it here is enough; without it the bar sat 120px inside the grid on
     * either side and the variables scrolled while the space beside them went unused.
     */
    max-width: none;
    width: auto;
    margin-inline: 0;
}

.callbar-body {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding: .625rem 1rem;

    /* Holds the card's height steady whether or not the controls are on screen. Sized to the
       controls, which are the tallest thing in here â€” without it the card shrinks between calls and
       the desk below jumps every time one ends, which is the whole thing this bar was moved up to
       avoid. */
    min-height: 5.25rem;
}

/* ------------------------------------------------------- time and call state */

.callbar-state {
    flex: 0 0 auto;
    width: 8.5rem;
    min-width: 0;
}

.callbar-timer {
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
}

.callbar-status {
    font-size: .8125rem;
    color: var(--bs-secondary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ------------------------------------------------------------ call variables */

/*
 * Three rows, stacked, in a two-column grid of labels and values.
 *
 * The rows dissolve into that grid with display:contents, so every label lands in the first column
 * and every value in the second and the three actually line up â€” three independent key/value pairs
 * would each be as wide as their own text and read as a ragged list.
 *
 * Sized to its content rather than growing: the variables belong next to the clock, and the
 * controls take the right-hand end on their own.
 *
 * Read-only throughout: the Contact Center SDK offers no way to write these back.
 */
.callbar .cad {
    flex: 0 0 auto;
    min-width: 0;
    display: grid;
    grid-template-columns: auto auto;
    gap: .0625rem .75rem;
    align-content: center;
    padding-bottom: 0;
}

.callbar .cad-row {
    display: contents;
}

.callbar .cad-key {
    flex: 0 0 auto;
    font-size: .6875rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

.callbar .cad-value {
    min-width: 0;
    font-size: .8125rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------------------------------------------------------------- controls */

/*
 * Right-hand end, and gone entirely between calls rather than dimmed â€” there is nothing to press,
 * and the card's own height keeps the layout still without them.
 */
.callbar-controls {
    flex: 0 0 auto;
    margin-left: auto;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 5.5rem));
    gap: .25rem;
}

/* ------------------------------------------------------------ queue levels */

/*
 * What a queue's three levels look like. QueueLevels decides which level a wait is; this decides
 * what that looks like. One rule, kept apart from the table that reads it for the same reason
 * QueueLevels is its own file: it was two panels until the strip under the call bar was removed,
 * and a second panel drawing a queue must land on this shade of amber rather than its own.
 *
 * Calm is the ready green rather than no colour at all: a queue nobody is waiting in is a fact worth
 * stating, and a grey edge reads as a queue whose figures have not arrived.
 */
.stats-queues-row[data-level="calm"]  { --q-level: var(--state-available); }
.stats-queues-row[data-level="warn"]  { --q-level: var(--bs-warning); }
.stats-queues-row[data-level="alarm"] { --q-level: var(--bs-danger); }

/* ---------------------------------------------------------------- incoming */

/*
 * A ringing call, in the bottom-right corner.
 *
 * It was a dialog over the whole desk with a dimmed backdrop behind it, and giving that up is the
 * real cost: that could not be missed, and this can. What makes it safe enough was all already
 * here â€” the synthesised ringtone, the browser notification when the window is in the background,
 * the call bar changing under it, and A / D as shortcuts from anywhere.
 *
 * What is gained is that the desk keeps working while a call rings. An agent halfway through a
 * message to another customer can finish the sentence; under the dialog they could not see it.
 *
 * Bottom right because the top of this window is the app bar and the call bar, and a card over
 * those covers the state pill and the very controls the agent is about to reach for.
 *
 * Not a Bootstrap toast despite the name: those stack in #toasts and dismiss themselves after a few
 * seconds, and this one must do neither. It stands until the call is answered, declined, or the
 * platform takes it back.
 */
.calltoast {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    /* Above the offcanvas panels, below nothing: a call arriving while the agent is in the transfer
       sheet is the case this exists for. */
    z-index: 1080;
    width: 20rem;
    padding: 1rem;
    background: var(--bs-body-bg);
    border: 1px solid color-mix(in srgb, var(--bs-danger) 45%, var(--bs-border-color));
    border-radius: .75rem;
    box-shadow: 0 1rem 2.5rem rgb(0 0 0 / .25);
}

/* The one thing on the screen allowed to move, because it is the one thing that has to be noticed
   from the corner of an eye. */
@media (prefers-reduced-motion: no-preference) {
    .calltoast {
        animation: calltoast-in .18s ease-out;
    }

    .calltoast-dot {
        animation: calltoast-pulse 1.1s ease-in-out infinite;
    }
}

@keyframes calltoast-in {
    from { transform: translateY(1rem); opacity: 0; }
}

@keyframes calltoast-pulse {
    50% { opacity: .25; }
}

.calltoast-label {
    display: flex;
    align-items: center;
    gap: .375rem;
    margin: 0 0 .5rem;
    font-size: .625rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-danger-text-emphasis);
}

.calltoast-dot {
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: var(--bs-danger);
}

.calltoast-who {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 600;
    word-break: break-word;
}

.calltoast-meta {
    margin: .125rem 0 0;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}

/* Empty between the number and the buttons rather than collapsed, so the two do not jump closer
   together on a call whose queue nobody named. */
.calltoast-meta:empty::before {
    content: "\00a0";
}

/* Besvar on the right, where the hand is already going, and the wider of the two: pressing the
   wrong one of these costs a customer. */
.calltoast-actions {
    display: flex;
    gap: .5rem;
    margin-top: .875rem;
}

.calltoast-actions .btn {
    flex: 1 1 auto;
}

.calltoast-actions .btn-success {
    flex: 1.4 1 auto;
}

/* Only on the solid button. Bootstrap's kbd is dark on light, which is right on the outline one and
   unreadable on the green â€” so the pale treatment goes where the button is already coloured, and
   the other keeps the default. */
.calltoast-actions kbd {
    margin-left: .375rem;
}

.calltoast-actions .btn-success kbd {
    background: rgb(255 255 255 / .25);
    border-color: rgb(255 255 255 / .35);
    color: inherit;
}

/* ------------------------------------------------------------- side panels */

/* No notch to clear, and a fixed width reads as a panel rather than as a sheet dragged in. */
.sheet.offcanvas-end {
    width: 24rem;
    padding-top: 0;
    padding-right: 0;
}

.sheet .offcanvas-body .d-grid .btn {
    min-height: 0;
}


/* ------------------------------------------------------- caller statistics */

/*
 * While a call is up the desk is two columns: what we already know about the caller on the left,
 * the caller themselves on the right. Bootstrap's grid does the split â€” a .row of col-4 and col-8
 * inside the screen, the right one taking the width back when the deployment has no history to
 * show â€” so all that is left here is undoing the single-column cap and styling the panel.
 *
 * The panel lives in this stylesheet rather than in agent.css because it is a PC screen's worth of
 * information. A phone shows the caller and the buttons; a history table beside them is exactly the
 * kind of thing that does not survive the trip to a 390px screen, so it is not pretended to be
 * shared.
 */

/* The 46rem cap centres one readable column, which is right for sign-in and the station form and
   wrong the moment a screen has columns to place. */
.main > .screen-split {
    max-width: none;
}

.callerstats {
    height: 100%;
}

.callerstats-body,
#callerstats-data {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1rem;
}

/* Nested, so the padding belongs to the card body alone. */
#callerstats-data {
    padding: 0;
}

/*
 * Every panel heading in this app, from one rule â€” the desk's four cards, the sections on the
 * statistics screen, the phone book, the task list and the colleague list in the dial panel.
 *
 * It used to be a small uppercase line with letters spread apart, which reads as a label above
 * something rather than as the name of it. An external widget arrives as a plain Bootstrap card and
 * is headed in the body font, and with the two on the same screen the difference read as two kinds
 * of thing rather than one kind arranged differently. So the body font won, and the wording in the
 * markup is already sentence case â€” "Kald pr. time" was only ever uppercase because of this rule.
 */
.callerstats-title,
.callpath-title,
.deskcard-title,
.queue-tiles-title,
.team-mates-title,
.outdial-title,
.stats-panel-title,
.stats-section-title,
.transcript-title,
.widget-title {
    margin: 0;
    font-size: .875rem;
    font-weight: 600;
    color: inherit;
}

.callerstats-who {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/*
 * The name, and it is the whole of the block now.
 *
 * The number used to be the line above it, in the size this rule carries: the call bar two inches
 * up prints it on every call, and a second copy spent the widest line in the column repeating what
 * the agent had already read. The name inherits the treatment because it is what the panel is
 * identified by now â€” and when there is no name the block hides itself rather than leaving its gap
 * behind (caller-stats.js).
 */
.callerstats-name {
    font-size: 1.125rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* All four periods on one line in a col-4, which is what decides the wording: "24 timer" and
   "1 mÃ¥ned" rather than a spelled-out range. */
.callerstats-range {
    width: 100%;
}

.callerstats-range .btn {
    padding-inline: .25rem;
    font-size: .75rem;
}

.callerstats-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .5rem;
}

/*
 * Icon and count on the top line, label under it, bar across the bottom. Areas rather than nesting,
 * so the count sits against the right edge of its own tile and the four numbers line up in a column
 * the eye can run down.
 */
.callerstats-tile {
    display: grid;
    width: 100%;
    text-align: left;
    color: inherit;
    font: inherit;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "icon  count"
        "label caret"
        "bar   bar";
    align-items: center;
    gap: .125rem .5rem;
    padding: .625rem .75rem;
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius);
}

/* Same stroke treatment as the call buttons and the menu, so this is one family of icons. */
.callerstats-icon {
    grid-area: icon;
    width: 1.125rem;
    height: 1.125rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--callerstats-accent);
}

.callerstats-count {
    grid-area: count;
    justify-self: end;
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.callerstats-label {
    grid-area: label;
    font-size: .75rem;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

/*
 * The count as a share of the largest of the four. Deliberately not a scale with an axis: it says
 * which channel this caller uses, at a glance and without being read, and any more than that
 * belongs in a report rather than in the corner of a ringing call.
 */
.callerstats-bar {
    grid-area: bar;
    height: .25rem;
    margin-top: .375rem;
    border-radius: 1rem;
    background: var(--bs-border-color);
    overflow: hidden;
}

.callerstats-bar i {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--callerstats-accent);
}

/* The state palette, reused. These are the colours an agent already associates with talking, being
   available, wrap-up and consulting â€” close enough that the channels do not introduce a second
   colour language into the same window. */
.callerstats-tile[data-channel="in"]    { --callerstats-accent: var(--state-talking); }
.callerstats-tile[data-channel="out"]   { --callerstats-accent: var(--state-available); }
.callerstats-tile[data-channel="email"] { --callerstats-accent: var(--state-wrapup); }
.callerstats-tile[data-channel="chat"]  { --callerstats-accent: var(--state-consulting); }

/* Pressing a tile opens what its number is made of, so every tile is a button â€” the appearance is
   the rule above, and only the affordances are added here. */
.callerstats-tile:hover {
    border-color: var(--callerstats-accent);
}

.callerstats-tile:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

/* The open tile keeps its accent while the list below is showing, so it is clear which of the four
   numbers the rows belong to â€” the list itself names the channel, but the eye goes to the tile. */
.callerstats-tile[aria-expanded="true"] {
    border-color: var(--callerstats-accent);
    box-shadow: inset 0 0 0 1px var(--callerstats-accent);
}

.callerstats-caret {
    grid-area: caret;
    width: .875rem;
    height: .875rem;
    justify-self: end;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--bs-secondary-color);
}

.callerstats-tile[aria-expanded="true"] .callerstats-caret {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: no-preference) {
    .callerstats-caret {
        transition: transform .15s ease;
    }
}

/* ------------------------------------------------------------ the rows behind a number */

.callerstats-detail {
    padding-top: .75rem;
    border-top: 1px solid var(--bs-border-color);
}

.callerstats-detail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    margin-bottom: .5rem;
}

.callerstats-detail-title {
    margin: 0;
    font-size: .6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-secondary-color);
}

/*
 * Capped and scrolled. A month of contact from a heavy caller is a long list, and letting it push
 * the card down the screen would move the caller block beside it â€” the one thing this whole area
 * was arranged to keep still.
 */
.callerstats-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .375rem;
    max-height: 16rem;
    overflow-y: auto;
}

/* A channel with nothing in the chosen period. Same row height as a real one, so opening a tile
   never makes the panel jump. */
.callerstats-none {
    padding: .375rem .625rem;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.callerstats-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0 .5rem;
    padding: .375rem .625rem;
    background: var(--bs-tertiary-bg);
    border-left: 3px solid var(--callerstats-outcome);
    border-radius: var(--bs-border-radius-sm);
    font-size: .75rem;
}

/* Answered or not, said in colour down the edge rather than in a column of its own. Green and red
   the way they are used everywhere else in this window. */
.callerstats-row[data-outcome="answered"] { --callerstats-outcome: var(--state-available); }
.callerstats-row[data-outcome="missed"]   { --callerstats-outcome: var(--bs-danger); }

.callerstats-row-when {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.callerstats-row-time {
    justify-self: end;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A missed call has no talk time, so the slot says why instead of standing empty. */
.callerstats-row[data-outcome="missed"] .callerstats-row-time {
    color: var(--bs-danger);
    font-weight: 600;
}

.callerstats-row-queue,
.callerstats-row-who {
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

/* The queue is the one that gives way. An agent scanning this list is looking for who took the
   call, and half a name with an ellipsis after it is no answer â€” the queue is still recognisable
   from its first half, a person is not. */
.callerstats-row-queue {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.callerstats-row-who {
    justify-self: end;
}

.callerstats-foot {
    display: flex;
    justify-content: space-between;
    gap: .5rem;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.callerstats-empty {
    margin: 0;
    padding: 2rem 0;
    text-align: center;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}


/* ------------------------------------------------------------ the call's path */

/*
 * The middle column: how this particular call got to this particular desk.
 *
 * Beside the history rather than under it, because the two answer the same question from different
 * ends â€” the history is every time we have spoken to this customer, the path is this time â€” and an
 * agent deciding how to open the call reads them together or not at all.
 *
 * In this stylesheet and not in agent.css for the same reason the history panel is: it is a PC
 * screen's worth of information. A phone shows the caller and the buttons, and a seven-stop rail
 * beside them does not survive the trip to a 390px screen.
 */

.callpath {
    height: 100%;
}

.callpath-body {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1rem;
}

.callpath-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
}

/*
 * When the picture was last true, and it will not always be "now".
 *
 * The path is read out of the reporting API, which is fed by the same events the desk sees but is
 * not the desk â€” a stop can be a second or two behind the thing it describes. Small and grey
 * because it is only wanted when the rail disagrees with what the agent just watched happen.
 */
.callpath-fresh {
    font-size: .6875rem;
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
}

/* The two numbers an agent wants before reading a single stop. Same tile treatment as the channel
   counts in the panel beside it, so the two cards read as one row. */
.callpath-summary {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .5rem;
}

.callpath-fact {
    padding: .5rem .75rem;
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius);
}

.callpath-fact-label {
    display: block;
    font-size: .6875rem;
    color: var(--bs-secondary-color);
}

.callpath-fact-value {
    display: block;
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------- the rail and its stops */

/*
 * Chronological, oldest at the top, and not foldable. The question this card answers is "how did
 * this call get to me", and that is asked once, in the first seconds, by an agent who cannot afford
 * to open anything. Everything a stop knows is therefore already on the screen â€” a row per stop
 * that expanded would hide the transfer that is the entire reason to look.
 *
 * Capped and scrolled the way the history list is, and for the same reason: a call that has been
 * round four departments must not push the caller block down the screen. 25rem rather than that
 * list's 16rem because seven stops is what a genuinely awkward call looks like, and a cap that hid
 * the ordinary bad day would not be a cap but a bug.
 */
.callpath-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 25rem;
    overflow-y: auto;
}

.callpath-step {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0 .5rem;
    padding: 0 0 .875rem 1.375rem;
    font-size: .75rem;
}

.callpath-step:last-child {
    padding-bottom: 0;
}

/* The line between two stops, drawn by the upper one so the last stop has nothing hanging off it.
   It starts below the dot rather than behind it â€” a rail running through an open circle shows
   through the hole. */
.callpath-step:not(:last-child)::before {
    content: "";
    position: absolute;
    left: calc(.375rem - 1px);
    top: 1rem;
    bottom: -.125rem;
    width: 2px;
    background: var(--bs-border-color);
}

.callpath-dot {
    position: absolute;
    left: 0;
    top: .1875rem;
    width: .75rem;
    height: .75rem;
    border-radius: 50%;
    background: var(--bs-body-bg);
    border: 2px solid var(--callpath-accent, var(--bs-secondary-color));
}

.callpath-step-what {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.callpath-step-when {
    justify-self: end;
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.callpath-step-where,
.callpath-step-note {
    grid-column: 1 / -1;
    color: var(--bs-secondary-color);
}

/* A queue name is recognisable from its first half, so truncating it costs nothing. */
.callpath-step-where {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/*
 * What happened inside a stop rather than what the stop was â€” the hold, the consult, the time it
 * rang. Set apart so the rail still reads as a list of places at a glance.
 *
 * The one line on this card allowed to wrap. At col-4 the busiest version of it â€” rang, held, and
 * consulted somebody â€” runs about 30px past the panel, and an ellipsis there eats the consult: the
 * agent is told the previous one held the customer and not who they went and asked. Two short lines
 * beat one line with the answer cut off it.
 */
.callpath-step-note {
    font-size: .6875rem;
    opacity: .85;
}

/*
 * The same colours the state pill uses, so a stop is the colour of the state it was. Waiting is the
 * orange of Idle, talking the blue of Talking, a consult the cyan of Consulting â€” an agent already
 * knows this palette and should not have to learn a second one in the corner of a live call.
 */
.callpath-step[data-kind="entry"]    { --callpath-accent: var(--state-offline); }
.callpath-step[data-kind="ivr"]      { --callpath-accent: var(--state-wrapup); }
.callpath-step[data-kind="queue"]    { --callpath-accent: var(--state-idle); }
.callpath-step[data-kind="agent"]    { --callpath-accent: var(--state-talking); }
.callpath-step[data-kind="offered"]  { --callpath-accent: var(--state-reserved); }
.callpath-step[data-kind="answered"] { --callpath-accent: var(--state-talking); }
.callpath-step[data-kind="transfer"] { --callpath-accent: var(--state-reserved); }
.callpath-step[data-kind="consult"]  { --callpath-accent: var(--state-consulting); }
.callpath-step[data-kind="hold"]     { --callpath-accent: var(--state-hold); }

/* Where the call is now: a filled dot with a ring around it, and the only bold time on the rail.
   The stop an agent is standing on should not have to be counted to. */
.callpath-step[data-current="true"] .callpath-dot {
    background: var(--callpath-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--callpath-accent) 25%, transparent);
}

.callpath-step[data-current="true"] .callpath-step-when {
    color: var(--bs-body-color);
    font-weight: 600;
}

/* ------------------------------------------------------------- what it adds up to */

/*
 * For the agent who wants the shape without reading the rail: how many queues, how many transfers,
 * how long on hold. Every one is a count the backend took off the legs, so none is a sum this panel
 * worked out and could get wrong.
 */
.callpath-chips {
    display: flex;
    flex-wrap: wrap;
    gap: .375rem;
    padding-top: .75rem;
    border-top: 1px solid var(--bs-border-color);
}

.callpath-chip {
    padding: .125rem .5rem;
    font-size: .6875rem;
    color: var(--bs-secondary-color);
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: 1rem;
    white-space: nowrap;
}

/* A chip for something that ought to be noticed â€” the second queue, the long hold. Warning rather
   than danger: none of it is wrong, it is just worth knowing before saying hello. */
.callpath-chip[data-tone="warn"] {
    color: var(--bs-warning-text-emphasis);
    background: var(--bs-warning-bg-subtle);
    border-color: var(--bs-warning-border-subtle);
}

/* Same wording and same shape as the history panel's empty state, because the two are empty for the
   same reason and at the same moment. */
.callpath-empty {
    margin: 0;
    padding: 2rem 0;
    text-align: center;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}


/* ------------------------------------------------------------ the left stack */

/*
 * Kaldets vej over Indringer, in one column.
 *
 * They were side by side, and they stay together because they answer the same question from two
 * ends â€” every time we have spoken to this customer, and this time. Stacked rather than beside each
 * other only because the transcript in the middle wants the width, and of the three things on the
 * desk these two are the ones an agent reads once when the call arrives and then leaves alone.
 *
 * The rail is on top because it is the one that keeps moving: it grows a stop while the call is
 * live, where the caller's four figures are fetched once and then sit still. The thing that changes
 * belongs where the eye lands.
 *
 * Each panel still hides itself â€” no Service App and the caller panel goes, no DataHub and the rail
 * goes â€” and when both have gone this column goes with them. That last part is Shell.fitDesk's,
 * because neither module can see the other.
 */
/*
 * The desk is as tall as the space it is given, and never taller.
 *
 * Every card on this row is bounded by what it holds except the transcript, which grows a line at a
 * time for as long as the customer keeps talking. Without a ceiling that growth pushes the row down
 * the page instead of scrolling inside the card: the transcript's own scrollbar never engages, the
 * two panels beside it walk off the bottom of the screen, and the agent scrolls the whole desk to
 * follow a conversation. The column rules below were written against a bounded row and only do what
 * they say once there is one.
 */
/*
 * It used to say `height: 100%` on the section and on its row, which worked for exactly as long as
 * the desk had one row. A layout can put several there now, and each one claimed the whole height â€”
 * so a queue strip above the panels pushed them a screen down and the page scrolled.
 *
 * A column instead: rows take what they hold, and one of them takes what is left. Which one is
 * ui/widgets.js's to mark, because "the leftover" is a decision about the layout rather than about
 * the stylesheet â€” a row whose widget asked to fill, and the last row when none did.
 */
#screen-desk {
    display: flex;
    flex-direction: column;
    height: 100%;
}

#screen-desk > .row {
    flex: 0 0 auto;
    min-height: 0;
}

/* Named with the id in front, or the rule above it wins on specificity and nothing ever grows. */
#screen-desk > .widget-row-fills {
    flex: 1 1 auto;
}

/*
 * And the row does not wrap, which is what makes the height above mean anything.
 *
 * A wrapping flex container sizes its line to the tallest item's own content, so the transcript
 * would go on deciding the row's height rather than the other way round â€” the height rule alone
 * changes nothing. A single-line one takes the height it was given and hands it to its columns,
 * which is the whole arrangement. The three columns add up to twelve and have never wrapped.
 */
#screen-desk > .row {
    flex-wrap: nowrap;
}

/* Flex items refuse to shrink below their content unless told, and the transcript's content is a
   conversation with no end. Its column is the one that has to give. */
#screen-desk > .row > [class*="col-"] {
    min-height: 0;
}

/*
 * The desk's panels are headed like every other card on the screen.
 *
 * They were headed their own way: a small uppercase line in the body's own padding, with nothing
 * behind it. An external widget is a plain Bootstrap card and gets a card-header â€” grey, sentence
 * case, the body font â€” and with the two beside each other on the same row the difference read as
 * two kinds of thing rather than one kind arranged differently.
 *
 * Done here rather than in the markup because the four partials are shared with the mockups and the
 * phone, and because the heading elements are already exactly where a card header goes. The negative
 * margin is what lets them span the card: each of these bodies pads by 1rem, and a header inside
 * that padding cannot reach the card's edges. The values come from Bootstrap's own card variables,
 * so a header written by widgets.js and one written here stay the same colour.
 *
 * Deliberately not applied to .outdial-title, .stats-panel-title or .stats-section-title, which
 * share the old heading rule: those are sections inside a sheet and a screen, not cards, and a grey
 * bar across them would be inventing a card that is not there.
 */
.callerstats-head,
.callpath-head,
.deskcard-head,
.queue-tiles-head,
.team-mates-head,
.transcript-head {
    margin: -1rem -1rem .25rem;
    padding: .5rem var(--bs-card-cap-padding-x);
    background-color: var(--bs-card-cap-bg);
    border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);

    /* The old rule aligned these on the baseline, which is right for a line of text among other
       text and wrong in a bar of its own, where the two ends should sit level. */
    align-items: center;
}

/*
 * The phone book and the task list are headed the same way, and their heading sits in the same place
 * â€” the first thing in a card body that pads by 1rem. The statistics screen is deliberately not in
 * this list: its own heading is hidden and the top of its card is the tab strip, so the only
 * .stats-section-heads there are bands *inside* one card. A grey bar across those would be inventing
 * a card that is not there.
 */
.stats-panel-body > .stats-section-head {
    margin: -1rem -1rem .25rem;
    padding: .5rem var(--bs-card-cap-padding-x);
    background-color: var(--bs-card-cap-bg);
    border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);
    align-items: center;
}

/*
 * A head whose card the layout gave no name.
 *
 * A card is headed because something is written there, so a layout that writes nothing gets no
 * heading and no bar behind one â€” the same as the call bar, which has always been controls without
 * a title over them. ui/widgets.js takes the whole head away where the name was all it held; this
 * is the other case, where it also carries the transcript's status, the composer's recipient or a
 * refresh button, and only the grey is wrong.
 *
 * The first selector is only there to out-weigh the rule above it, which is a child selector and
 * would otherwise keep the phone book's and the task list's bar grey. Written as a pair rather than
 * with !important, which would be one more thing to unpick the next time a head is styled.
 *
 * The negative margins go too. They exist to let a grey bar reach the card's edges, and with no bar
 * to reach them the controls that are left should line up with the body under them instead.
 */
.stats-panel-body > .widget-headless,
.widget-headless {
    margin: 0 0 .25rem;
    padding-left: 0;
    padding-right: 0;
    background-color: transparent;
    border-bottom: 0;
}

.widget-headless > h2 {
    display: none;
}

/*
 * A widget that asked for the height the page has.
 *
 * Three rules and they only work together: the page becomes a column, the row it is in takes what
 * the rows above and below did not, and the column takes all of that. Written as classes the module
 * adds only where a layout asked, so a page of ordinary cards is laid out exactly as it was â€” a
 * height nobody asked for is how a card of four figures ends up two screens tall.
 *
 * This is what an external widget usually wants. Without it an iframe falls back to its own 150px,
 * which is the browser's answer to a question nobody asked, and a CRM in a strip that size is
 * useless.
 */
.widget-page-fills {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.widget-row-fills {
    flex: 1 1 auto;
    min-height: 0;
}

.widget-fills {
    height: 100%;
    min-height: 0;
}

/*
 * An external widget's frame fills the card it is in.
 *
 * min-height: 0 on the body is the one that is easy to miss and impossible to miss the effect of: an
 * iframe never surrenders its 150px of content height, so a flex child holding one refuses to shrink
 * and the card grows past the row it is in, pushing everything beside it off the screen.
 *
 * The card body has no padding of its own â€” a frame is the whole of what the card holds, and a
 * border of white around somebody else's page reads as a mistake rather than as a margin.
 */
.widget-framed > .card-body {
    display: flex;
    min-height: 0;
    flex: 1 1 auto;
}

.widget-frame {
    display: block;
    width: 100%;
    height: 100%;
    min-height: 0;
    border: 0;
}

/*
 * A panel that a layout has moved into a column fills it.
 *
 * Its own card asks for height: 100% â€” the transcript's does, and it is the reason its scrollbar
 * engages instead of the conversation pushing the row down the page. That resolves against the
 * panel's wrapper, which used to *be* the column and is now a plain div inside one, so without this
 * the height has nothing to measure against and the card shrinks to its content.
 *
 * Not in a stack. There the column is a flex container and its children are sized by the rules
 * below â€” the rail with a floor and a share, the caller panel by its content â€” and a height of 100%
 * on each would fight both.
 */
[data-widget-col]:not(.widget-stack) > .widget-part {
    height: 100%;
    min-height: 0;
}

/*
 * A column holding several widgets, one above the other. On the desk it is the rail over the caller
 * panel; a layout can ask for one anywhere.
 *
 * The rules below it are keyed on the panels' own ids rather than on this class, because a panel
 * carries its id wherever a layout moves it, and what they say is true of that panel and not of
 * stacks in general.
 */
.widget-stack {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-height: 0;
}

/*
 * The rail takes the height, and takes it as a floor rather than as a share.
 *
 * A share would leave it as short as whatever the caller panel below happens to need, and the rail
 * is the panel with somewhere to grow: a call transferred twice has five stops where most have
 * three. The floor is also what stops the column re-laying itself out every time a stop is added,
 * which on a live call is exactly when nobody wants it moving.
 */
.widget-stack > #callpath-col {
    flex: 1 1 auto;
    min-height: 26rem;
}

.widget-stack > #callpath-col > .card {
    height: 100%;
}

/* The stops spread down the card rather than bunching under the header with the height sitting
   empty below them. */
#callpath-col #callpath-data {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    flex: 1 1 auto;
    min-height: 0;
}

#callpath-col .callpath-steps {
    flex: 1 1 auto;
}

/* Content-height: four figures and a period picker, and nothing in it wants more room than that. */
.widget-stack > #caller-stats-col {
    flex: 0 0 auto;
}

/*
 * The tiles inside both panels, smaller than they were.
 *
 * They were sized for a column a third of the screen wide, where a 1.5rem figure in a tile with
 * 12px of padding is proportionate. In a quarter-width column stacked two deep they were the reason
 * the two panels filled everything and the rail had nowhere to grow. Only the padding and the type
 * come down â€” the layout, the colours and the bars are untouched, so a tile still reads as the same
 * tile.
 *
 * Scoped to the caller panel itself rather than changed at the source, because these are the caller panel's own
 * rules and the phone uses them at their full size.
 */
#caller-stats-col .callerstats-grid {
    gap: .375rem;
}

#caller-stats-col .callerstats-tile {
    padding: .375rem .5rem;
}

#caller-stats-col .callerstats-count {
    font-size: 1.125rem;
}

#caller-stats-col .callerstats-icon {
    width: .9375rem;
    height: .9375rem;
}

#caller-stats-col .callerstats-label {
    font-size: .6875rem;
}

#callpath-col .callpath-fact {
    padding: .375rem .5rem;
}

#callpath-col .callpath-fact-value {
    font-size: 1rem;
}

#callpath-col .callpath-fact-label {
    font-size: .625rem;
}

/* --------------------------------------------------------------- transcript */

/*
 * The middle column: what is being said, as it is said.
 *
 * A card like the two beside it, one frame deep, with the same small uppercase heading â€” the desk is
 * one card deep everywhere and this is not the place to start nesting.
 *
 * Nothing fills it yet; see _Transcript.cshtml for why it ships anyway. The rules for the lines are
 * here rather than waiting with it, because they are what the column's width was decided against.
 */
.transcript {
    height: 100%;
}

.transcript-body {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1rem;
    min-height: 0;
}

.transcript-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
}

/* .transcript-title is the desk's own panel heading â€” the rule is shared with .callpath-title and
   the others further up rather than copied. */

/*
 * Whether the platform is listening, in one word.
 *
 * Two states and no third: green while the transcription is running for this call, amber where the
 * organisation has not switched the feature on. The amber one is the panel saying the silence is
 * not its fault â€” an agent who can see that goes to an administrator instead of reporting a blank
 * column as a bug.
 */
.transcript-status {
    padding: .0625rem .5rem;
    font-size: .625rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-warning-text-emphasis);
    background: var(--bs-warning-bg-subtle);
    border: 1px solid var(--bs-warning-border-subtle);
    border-radius: 1rem;
}

.transcript-status[data-state="live"] {
    color: var(--bs-success-text-emphasis);
    background: var(--bs-success-bg-subtle);
    border-color: var(--bs-success-border-subtle);
}

.transcript-when {
    font-size: .75rem;
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
}

/* The lines scroll inside the card rather than growing it: a call can run half an hour, and the
   panels beside it must not be pushed off the bottom of the screen by a talkative customer. */
.transcript-lines {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: .875rem;
    padding-right: .25rem;
}

.transcript-line {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    max-width: 82%;
}

/*
 * The customer on the left, the agent on the right, and the difference carried by the side as well
 * as by the colour â€” a transcript read at a glance mid-call is read by shape. System lines are the
 * machine talking and sit left with the customer, flatter and dashed, so they cannot be mistaken
 * for something anybody said.
 */
.transcript-line[data-role="agent"] {
    align-self: flex-end;
    align-items: flex-end;
}

.transcript-who {
    font-size: .625rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-secondary-color);
}

.transcript-text {
    margin: 0;
    padding: .5rem .75rem;
    font-size: .8125rem;
    line-height: 1.45;
    border-radius: .75rem;
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
}

.transcript-line[data-role="agent"] .transcript-text {
    background: color-mix(in srgb, var(--bs-primary) 10%, var(--bs-body-bg));
    border-color: color-mix(in srgb, var(--bs-primary) 25%, transparent);
}

.transcript-line[data-role="system"] .transcript-text {
    font-style: italic;
    color: var(--bs-secondary-color);
    background: none;
    border-style: dashed;
}

/* The words still arriving. A caret rather than a spinner: the line is being written, not loaded,
   and the difference is what an agent reads it as. */
@media (prefers-reduced-motion: no-preference) {
    .transcript-line.is-live .transcript-text::after {
        content: "";
        display: inline-block;
        width: .0625rem;
        height: .8em;
        margin-left: .125rem;
        vertical-align: -.05em;
        background: currentColor;
        animation: transcript-caret 1s steps(2, start) infinite;
    }
}

@keyframes transcript-caret {
    to { opacity: 0; }
}

/* Between calls, and on every deployment until there is a transcript at all. A sentence where the
   lines would be, the way the caller panel says it has nothing to show. */
.transcript-empty {
    margin: auto;
    padding: 2rem 1rem;
    max-width: 22rem;
    text-align: center;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}

/* ------------------------------------------------------------------- widget */

/*
 * The middle column when it holds someone else's page instead of the conversation.
 *
 * Built as the same card as the transcript it replaced, and for the same reason: the desk is one
 * card deep everywhere. The header is the desk's, the frame under it is the widget's, and there is
 * nothing of ours in between â€” a second border around an embedded page reads as a page that failed
 * to fill its box.
 */
.widget {
    height: 100%;
}

.widget-body {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1rem;
    min-height: 0;
}

.widget-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
}

/* .widget-title is the desk's own panel heading â€” the rule is shared with .transcript-title and
   the others further up rather than copied. */

/* Which system is in the frame, for the morning it is the wrong one. The host and never the query
   string: a long address pushes the heading out of the header and helps nobody. */
.widget-where {
    font-size: .75rem;
    color: var(--bs-secondary-color);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * The frame takes whatever the header left, and scrolls inside itself.
 *
 * min-height is what makes that true: a flex item will not shrink below its content, and an iframe
 * reports a default content height of 150px it never gives up. Without the zero the frame sits at
 * its own idea of a size and the card grows past the desk row.
 */
.widget-frame {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    border: 0;
    border-radius: var(--bs-border-radius);
    background: var(--bs-body-bg);
}

/* No address configured, which is the state every deployment starts in. A sentence where the frame
   would be, the way the transcript says there is no conversation yet. */
.widget-empty {
    margin: auto;
    padding: 2rem 1rem;
    max-width: 22rem;
    text-align: center;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}

/* --------------------------------------------------------- who is on the line */

/*
 * The third card on the desk row, and the newest of the three.
 *
 * It was bare markup in its column for as long as it was the only thing on the row that was not a
 * panel â€” one card and one plain block beside it reads as a card with a caption. With a second
 * panel on the row it read as an unfinished third, so it became a card too.
 *
 * Nothing of what is inside it is styled here. The caller block, the avatar and the flags are
 * agent.css's and are shared with the phone, where they are the whole screen rather than a card â€”
 * so this adds the card around them and changes none of it.
 */
.deskcard {
    height: 100%;
}

.deskcard-body {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1rem;
}

/* Present in both states, unlike the two panels beside it, whose headers sit above a body that is
   swapped out. Here the header is what stays while the desk goes from idle to a call. */
.deskcard-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
}

/* agent.css gives this a bottom border to divide the caller from the controls under it on a phone.
   In the card the flags are the only thing below, and a rule across a card with two lines under it
   is a heavier division than there is anything to divide. */
.deskcard .caller {
    padding-bottom: 0;
    border-bottom: 0;
}

/* Which leaves the flags sitting against the caller. They keep the gap the card body gives every
   other pair of children, and lose the padding that was standing in for it. */
.deskcard .task-flags {
    padding: 0;
}

/* Nothing to show is not the same as nothing there: the flags are written by task.js and are empty
   on a call that is not being recorded, muted or consulted. An empty flex row would still take its
   gap and leave the card looking as though a line had failed to load. */
.deskcard .task-flags:empty {
    display: none;
}


/* ------------------------------------------------------ send a message */

/*
 * The composer that stands where "PÃ¥ linjen" used to. It borrows the deskcard shell â€” the card, the
 * body's flex column, the header â€” and adds only the form. messaging.js owns the behaviour; see the
 * partial in Pages/Index.cshtml for what each id is.
 *
 * No internal scroll: the form is long, and on a call the whole desk scrolls rather than a panel
 * within a panel. It is the same choice the caller-history list makes.
 */

/* Who the message goes to, beside the heading, in the same muted note the call bar uses for its
   status line. Truncates rather than wrapping the header onto two lines. */
.msgcard-to {
    min-width: 0;
    overflow: hidden;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.msgcard-form {
    display: flex;
    flex-direction: column;
    gap: .75rem;
}

.msg-field {
    display: flex;
    flex-direction: column;
    gap: .25rem;
}

.msg-field > label {
    font-size: .75rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
}

/* Channel and template on one row: the choice and the thing it fills in, read left to right. They
   stack only if the column is ever narrower than a col-4. */
.msg-pickers {
    display: grid;
    grid-template-columns: 8.5rem minmax(0, 1fr);
    gap: .5rem;
}

@media (max-width: 480px) {
    .msg-pickers {
        grid-template-columns: 1fr;
    }
}

.msg-body {
    min-height: 8.5rem;
    resize: vertical;
}

/* Always rendered, so the buttons below never jump between a one-line hint and none. */
.msg-hint {
    min-height: 1.25rem;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.msg-hint.is-warn {
    color: var(--bs-warning-text-emphasis);
}

/* The merge-field chips: a short row, so chips rather than a menu. */
.msg-merge {
    display: flex;
    flex-wrap: wrap;
    gap: .375rem;
}

.msg-merge-chip {
    padding: .125rem .5rem;
    font-family: var(--bs-font-monospace);
    font-size: .75rem;
    line-height: 1.4;
    color: var(--bs-body-color);
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 1rem;
    cursor: pointer;
}

.msg-merge-chip:hover {
    background: var(--bs-secondary-bg);
}

.msg-actions {
    display: flex;
    gap: .5rem;
}

/* What has already gone out on this call. A receipt, not a thread â€” kept short, and only shown once
   there is a row in it. */
.msg-sent {
    padding-top: .75rem;
    border-top: 1px solid var(--bs-border-color);
}

.msg-sent-title {
    margin: 0 0 .375rem;
    font-size: .6875rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
}

.msg-sent-list {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.msg-sent-row {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    font-size: .8125rem;
}

.msg-sent-row .msg-sent-when {
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
}

.msg-sent-row .msg-sent-what {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


/* ------------------------------------------------------ outbound dialling */

/*
 * The dial control in the app bar: a button the size and shape of the state pill beside it, with a
 * panel behind it the size and shape of the account menu.
 *
 * Both of those are deliberate. The three controls at the right of the bar are the three things an
 * agent does between calls, and a row where each one is its own invention is a row that has to be
 * learned three times. The state pill's second line carries the timer; this one carries the number
 * the customer will see, which is the setting here worth knowing without opening anything.
 */
.outdial-toggle {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 12rem;
    min-height: 2.75rem;
    padding: .25rem .75rem;
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius);
    background: var(--bs-body-bg);
    color: inherit;
    font: inherit;
    text-align: left;
}

.outdial-toggle:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

/* Out of reach during a call, and visibly so â€” see setBusy in outdial.js. */
.outdial-toggle:disabled {
    opacity: .55;
}

.outdial-toggle-icon {
    width: 1.125rem;
    height: 1.125rem;
    flex: 0 0 auto;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--bs-secondary-color);
}

/* Same two-line block as the state pill's, so the two buttons sit on one baseline. */
.outdial-toggle-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
    line-height: 1.15;
}

.outdial-toggle-label,
.outdial-toggle-ani {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.outdial-toggle-ani {
    color: var(--bs-secondary-color);
}

/* The account menu's proportions. A dial panel that opened at a different width beside it would
   read as a different kind of thing for no reason. */
.outdial-menu {
    width: 20rem;
    max-height: min(34rem, calc(100vh - 5rem));
    overflow-y: auto;
    padding: 1rem;
}

.outdial-field {
    display: flex;
    flex-direction: column;
    min-width: 0;
    margin-bottom: .75rem;
}

.outdial-field .form-label {
    margin-bottom: .25rem;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.outdial-field .form-text {
    margin-top: .25rem;
    font-size: .75rem;
}

/* Tabular figures, for the same reason the caller's own number has them: it is typed while being
   read off a note, and proportional digits make that harder than it needs to be. */
#outdial-number {
    font-variant-numeric: tabular-nums;
}

/* Set apart from the fields, like sign-out in the account menu: this one places the call. */
.outdial-buddies {
    margin-top: 1rem;
    padding-top: .75rem;
    border-top: 1px solid var(--bs-border-color);
}

.outdial-buddy-list {
    list-style: none;
    margin: .5rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .25rem;
}

/*
 * Name and number on the first line, team under them â€” the same two-line row the caller history
 * uses, because it answers the same shape of question: who is this, and what do I press.
 */
.outdial-buddy {
    display: grid;
    width: 100%;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0 .5rem;
    padding: .375rem .5rem;
    border: 1px solid transparent;
    border-radius: var(--bs-border-radius);
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
}

.outdial-buddy:hover,
.outdial-buddy:focus-visible {
    border-color: var(--bs-border-color);
    background: var(--bs-tertiary-bg);
}

.outdial-buddy-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: .875rem;
}

.outdial-buddy-number {
    font-size: .8125rem;
    font-variant-numeric: tabular-nums;
    color: var(--bs-secondary-color);
}

.outdial-buddy-team {
    grid-column: 1 / -1;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

/* Carries the spinner while the list loads as well as the two ways it can come back empty, so all
   three states sit in the same place at the same size. */
.outdial-buddy-none {
    padding: .5rem;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}

/* ---------------------------------------------------------------- statistik */

/*
 * The agent's own day and their team's, on a screen of its own reached from the main menu.
 *
 * Desktop only, and not pretended to be shared: this is a table of twenty rows and a chart with
 * thirteen columns, which is exactly the kind of thing that does not survive the trip to a 390px
 * screen.
 *
 * The colour language is the one already in this window â€” blue for incoming, green for outgoing,
 * red for what went unanswered â€” so a number here means the same thing it means on the desk.
 */

/* The full width of the main area, the way the desk's three columns take it. The 46rem reading
   column that .main > .screen imposes is for sign-in and the station form â€” a table of ten columns
   and a chart of thirteen bars have nothing to gain from being squeezed into one. */
.main > .screen-stats {
    max-width: none;
}

/* ------------------------------------------------------------------- panels */

/*
 * One panel, built as the same card as Indringer on the desk: one card, a small uppercase heading,
 * a 1rem body. Everything inside is a plain section rather than a card of its own â€” a card within a
 * card puts two frames around every number, and the desk is one card deep for exactly that reason.
 *
 * The agent's day and the team's are two tabs in that one card. They were two cards under each
 * other, which made the team's half something you scrolled past the first to reach; tabs put them
 * the same distance away.
 */
.stats-tabs {
    gap: .25rem;
    margin-bottom: .25rem;
    border-bottom: 1px solid var(--bs-border-color);
}

/*
 * Underlined rather than boxed. Bootstrap draws a tab as three borders and a notch in the strip,
 * which reads as a second frame inside a card that already has one â€” the same reason nothing else
 * on this screen is a card of its own.
 */
.stats-tabs .nav-link {
    padding: .375rem .625rem .5rem;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    background: none;
    color: var(--bs-secondary-color);
    font-size: .8125rem;
    font-weight: 600;
}

.stats-tabs .nav-link:hover {
    color: var(--bs-body-color);
    border-bottom-color: var(--bs-border-color);
}

.stats-tabs .nav-link.active {
    background: none;
    color: var(--bs-primary);
    border-bottom-color: var(--bs-primary);
}

.stats-tabs .nav-link:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: -2px;
}

/* The pane carries the spacing the card body used to give each of the two panels. Only the shown
   one is a flex column: .tab-pane is display:none until Bootstrap shows it, and a display here
   would override that and put both panes on the screen at once. */
.stats-pane.active {
    display: flex;
    flex-direction: column;
    gap: .75rem;
}

/* Where the panel heading was: what this tab is showing, and when it was last read. */
.stats-pane-note {
    margin: 0;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.stats-panel-body {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1rem;
}

.stats-panel-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

/* .stats-panel-title and .stats-section-title are the desk's own panel heading â€” the rule is shared
   with .callerstats-title rather than copied, further down. */

.stats-panel-note {
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

/* Shown instead of the cards when the day could not be fetched. Same treatment as the caller
   panel's between-calls text: a sentence, centred, where the numbers would have been. */
.stats-unavailable {
    margin: 0;
    padding: 3rem 0;
    text-align: center;
    font-size: .875rem;
    color: var(--bs-secondary-color);
}

/* ----------------------------------------------------------------- sections */

/*
 * The parts of a panel: the chart, the two rings, a table. Separated by a rule rather than by a
 * frame, so the panel reads as one card with things in it.
 */
.stats-section {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding-top: .75rem;
    border-top: 1px solid var(--bs-border-color);
}

/* The chart and the rings are two columns of the same band, so the rule runs across both of them
   once â€” and between them, where the row would otherwise butt one against the other. */
.stats-aside {
    display: flex;
    flex-direction: column;
    padding: .75rem 0 0 1rem;
    margin-left: 1rem;
    border-top: 1px solid var(--bs-border-color);
    border-left: 1px solid var(--bs-border-color);

    /* The two columns are a Bootstrap row of col-8 and col-4; the left margin has to come back out
       of the width or the pair no longer adds up to twelve. */
    width: calc(33.333333% - 1rem);
}

.stats-section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

.stats-section-note {
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

/* The note and the "skjul logget ud" checkbox share the right-hand end of the section head. */
.stats-section-aside {
    display: flex;
    align-items: baseline;
    gap: 1rem;
}

.stats-agents-toggle {
    display: flex;
    align-items: center;
    gap: .35rem;
    font-size: .75rem;
    color: var(--bs-secondary-color);
    white-space: nowrap;
    cursor: pointer;
}

.stats-agents-toggle input {
    margin: 0;
}

/* --------------------------------------------------------------------- tiles */

.stats-kpis {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: .75rem;
}

.stats-kpis-team {
    grid-template-columns: repeat(5, minmax(0, 1fr));
}

/*
 * The accent is a bar down the left edge rather than a coloured number: five saturated figures in a
 * row is a traffic light, and the value is the thing that has to be readable.
 */
.stats-kpi {
    display: flex;
    flex-direction: column;
    gap: .125rem;
    padding: .75rem .875rem;
    /* Same fill as the tiles in the caller panel: inside a card, body-bg is the card's own colour
       and the tiles would be outlines with nothing in them. */
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color-translucent);
    border-left: 3px solid var(--stats-accent, var(--bs-border-color));
    border-radius: var(--bs-border-radius);
}

.stats-kpi[data-tone="in"]       { --stats-accent: var(--state-talking); }
.stats-kpi[data-tone="out"]      { --stats-accent: var(--state-available); }
.stats-kpi[data-tone="talk"]     { --stats-accent: var(--state-consulting); }
.stats-kpi[data-tone="wrap"]     { --stats-accent: var(--state-wrapup); }
.stats-kpi[data-tone="avail"]    { --stats-accent: var(--state-idle); }
.stats-kpi[data-tone="staff"]    { --stats-accent: var(--state-reserved); }

/*
 * The team's staffing, as a bar of the states it is made of.
 *
 * A track that is always drawn, so the tile keeps its height when the team is empty: a bar that
 * vanishes with the last colleague reads as a panel that has stopped working, which is the one
 * thing this tile must not say while everyone is at lunch.
 *
 * The segments are sized by flex-grow, which means the counts themselves are the proportions and
 * nothing here computes a percentage. A minimum width so that one colleague among thirty is a mark
 * on the bar rather than a hairline nobody sees.
 */
.stats-staffbar {
    display: flex;
    gap: 1px;
    height: .375rem;
    margin: .25rem 0 .125rem;
    border-radius: 999px;
    overflow: hidden;
    background: var(--bs-secondary-bg);
}

.stats-staffbar > span {
    min-width: .1875rem;
    background: var(--bs-secondary-color);
}

.stats-staffbar > span[data-state="available"] { background: var(--state-available); }
.stats-staffbar > span[data-state="ringing"]   { background: var(--state-reserved); }
.stats-staffbar > span[data-state="connected"] { background: var(--state-talking); }
.stats-staffbar > span[data-state="wrapup"]    { background: var(--state-wrapup); }

/* The tile is the roster added up, so its figures line up with the states in it rather than
   spreading across the tile's width. */
.stats-staff-figures {
    grid-template-columns: auto auto;
    justify-content: start;
    column-gap: .375rem;
}

.stats-avail-figures dt[data-state="connected"] { --stats-dot: var(--state-talking); }

/*
 * The last tile carries a ring and two figures instead of one big number, because what it reports is
 * a proportion. Same height as the four beside it: the grid stretches them all to the tallest, and
 * this is the tallest.
 */
.stats-avail {
    display: flex;
    align-items: center;
    gap: .625rem;
    margin-top: .125rem;
}

.stats-avail-ring {
    flex: 0 0 auto;
    width: 2.75rem;
    height: 2.75rem;
    /* Green for the ready arc; the not-ready remainder is the track behind it, and not-ready reads
       red everywhere else on this screen, so the track does too. */
    --stats-accent: var(--state-available);
}

.stats-avail-ring .stats-donut-track {
    stroke: var(--bs-danger);
}

.stats-avail-figures {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0 .5rem;
    margin: 0;
    font-size: .75rem;
    line-height: 1.35;
}

.stats-avail-figures dt {
    display: flex;
    align-items: center;
    gap: .3125rem;
    font-weight: 400;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

.stats-avail-figures dt::before {
    content: "";
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: var(--stats-dot);
}

.stats-avail-figures dt[data-state="available"] { --stats-dot: var(--state-available); }

/* Not-ready reads red everywhere it appears â€” the appbar pill, the state picker, the team roster â€”
   and the day's not-ready total is the same statement, so it carries the same colour. */
.stats-avail-figures dt[data-state="idle"]         { --stats-dot: var(--bs-danger); }
.stats-avail-figures dt[data-state="idle"] + dd    { color: var(--bs-danger); }

.stats-avail-figures dd {
    margin: 0;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.stats-kpi-label {
    font-size: .6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-secondary-color);
}

.stats-kpi-value {
    font-size: 1.75rem;
    font-weight: 600;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.stats-kpi-sub {
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.stats-legend {
    list-style: none;
    display: flex;
    gap: 1rem;
    margin: 0;
    padding: 0;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.stats-legend-item::before {
    content: "";
    display: inline-block;
    width: .625rem;
    height: .625rem;
    margin-right: .375rem;
    border-radius: .1875rem;
    background: var(--stats-accent);
}

.stats-legend-item[data-tone="in"]  { --stats-accent: var(--state-talking); }
.stats-legend-item[data-tone="out"] { --stats-accent: var(--state-available); }

/* -------------------------------------------------------------- bar charts */

/*
 * Two charts on one set of classes: calls per hour, and calls per queue. They share everything
 * except how many columns there are, which is the one thing they genuinely disagree about.
 *
 * The base is the queue chart's answer â€” a column per queue the agent has actually had a call from,
 * however many that is. A queue she has had nothing from today is not in the day's figures at all,
 * so there is no empty column to leave room for.
 */
.stats-chart {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-auto-flow: column;
    /* Capped and centred rather than 1fr each, which is the difference between a chart and two
       sticks marooned in half a panel apiece: an agent who has taken calls from two queues would
       otherwise get two columns three hundred pixels wide holding one bar each. The track still
       shrinks below the cap, so a dozen queues fit rather than overflowing. */
    grid-auto-columns: minmax(0, 7rem);
    justify-content: center;
    gap: .75rem;
    align-items: end;
}

/*
 * Thirteen columns, 08 through 20, each holding an incoming and an outgoing bar.
 *
 * Fixed rather than fitted to the day's traffic, which is the opposite of the queue chart above and
 * for a reason that only applies here: the chart is read as "when am I busy", and an hour with
 * nothing in it is still an hour. An axis that dropped its empty hours and rescaled itself every
 * morning would make a quiet day look like a busy one.
 */
.stats-hours {
    grid-auto-flow: row;
    grid-template-columns: repeat(13, minmax(0, 1fr));
    justify-content: stretch;
    gap: .375rem;
}

/* Thirteen columns, two bars each, so they are the narrow ones. The queue chart's columns are wide
   enough to carry a name and its bars are sized to match. */
.stats-hours .stats-bar {
    max-width: 1.125rem;
}

.stats-hours .stats-chart-bars {
    gap: .1875rem;
}

/* Says so, rather than collapsing. The chart's height lives in the bars, so a day with no calls in
   it would otherwise leave the section a heading with nothing under it and no hint why. */
.stats-chart-empty {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 8.5rem;
    font-size: .8125rem;
    border-bottom: 1px solid var(--bs-border-color);
}

.stats-chart-col {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .375rem;
    min-width: 0;
}

/* The bars grow from the floor of a fixed box, so every column shares one baseline and the heights
   can be compared across the row. */
.stats-chart-bars {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: .1875rem;
    width: 100%;
    height: 8.5rem;
    padding-top: .875rem;
    border-bottom: 1px solid var(--bs-border-color);
}

.stats-bar {
    position: relative;
    flex: 1 1 0;
    max-width: 2.25rem;
    min-height: 2px;
    border-radius: .1875rem .1875rem 0 0;
    background: var(--stats-accent);
}

.stats-bar[data-tone="in"]  { --stats-accent: var(--state-talking); }
.stats-bar[data-tone="out"] { --stats-accent: var(--state-available); }

/* The queue chart's two: the calls that were taken, and the ones that were offered and were not.
   Missed shares the pill shape rather than getting a hatch of its own, because it is the same
   measurement â€” the difference is which of them the agent would rather see shorter. */
.stats-bar[data-tone="taken"]  { --stats-accent: var(--state-talking); }
.stats-bar[data-tone="missed"] { --stats-accent: var(--bs-danger); }

/* Above the bar rather than inside it: the short ones have no room for a figure, and a number that
   sometimes sits inside and sometimes outside is harder to scan than one that never moves. */
.stats-bar-count {
    position: absolute;
    left: 50%;
    bottom: 100%;
    transform: translate(-50%, -.125rem);
    font-size: .6875rem;
    font-variant-numeric: tabular-nums;
    color: var(--bs-secondary-color);
}

/* A queue's name, unlike an hour, does not fit under its column. One line, cut where it must be,
   with the whole of it in the bar's tooltip and in the column's own title. */
.stats-chart-label {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: .6875rem;
    color: var(--bs-secondary-color);
}

/*
 * An hour from before the socket started counting.
 *
 * The rest of this screen is counted from DataHub's own start â€” local midnight, or its last restart
 * â€” while the hour chart comes from the Search API and always covers the whole day. Cutting the
 * chart to the socket's period is what stops the tiles saying no calls beside a chart showing a
 * morning of them.
 *
 * Faded rather than removed, because the axis is fixed at thirteen hours and every one of them
 * holds its place. What must not happen is for it to read as a nought: an empty column says "no
 * calls that hour", which about the morning before a restart is untrue, and avoiding exactly that
 * untruth is the whole point of the cut. So the sliver goes almost invisible and the label with it,
 * and the column's title says what it is.
 */
.stats-chart-col.is-uncounted {
    opacity: .35;
}

.stats-chart-col.is-uncounted .stats-bar {
    /* Not the direction's colour: a faint blue sliver still reads as a very small inbound hour. */
    --stats-accent: var(--bs-secondary-color);
}

/* -------------------------------------------------------------------- rings */

/*
 * Ring on the left, what it splits into on the right, heading across the top. Side by side rather
 * than stacked, because the two of them share a column beside a chart that is only so tall â€” and a
 * ring with its own legend under it twice over is taller than the chart it is standing next to.
 */
.stats-total {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "title title"
        "ring  split";
    align-items: center;
    gap: .5rem .875rem;
    --stats-accent: var(--state-talking);
}

.stats-total[data-tone="out"] {
    --stats-accent: var(--state-available);
}

.stats-total .stats-section-title {
    grid-area: title;
}

/* The second ring is separated from the first the way the sections of a panel are. */
.stats-total + .stats-total {
    margin-top: .75rem;
    padding-top: .75rem;
    border-top: 1px solid var(--bs-border-color);
}

.stats-donut {
    grid-area: ring;
    position: relative;
    width: 5.5rem;
    height: 5.5rem;
}

/* Turned so the arc starts at twelve o'clock, which is where the eye starts reading a dial. */
.stats-donut-svg {
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
}

.stats-donut-track,
.stats-donut-value {
    fill: none;
    stroke-width: 4;
}

.stats-donut-track {
    stroke: var(--bs-border-color);
}

/* The remainder of the ring is the track showing through, so the unanswered share needs no arc of
   its own â€” and the two can never add up to anything but the whole. */
.stats-donut-value {
    stroke: var(--stats-accent);
    stroke-linecap: butt;
}

.stats-donut-center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    line-height: 1.1;
}

.stats-donut-value-text {
    font-size: 1.5rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.stats-donut-unit {
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.stats-split {
    grid-area: split;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: .125rem .75rem;
    margin: 0;
    font-size: .8125rem;
}

.stats-split dt {
    display: flex;
    align-items: center;
    gap: .375rem;
    font-weight: 400;
    color: var(--bs-secondary-color);
}

.stats-split dt::before {
    content: "";
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: var(--stats-accent);
}

.stats-split dt[data-outcome="missed"] {
    --stats-accent: var(--bs-border-color);
}

.stats-split dd {
    margin: 0;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* -------------------------------------------------------------------- tables */

/* Capped and scrolled, for the same reason the caller's history is: a busy day should not push the
   team block off the bottom of a screen that is meant to be read as one page. */
.stats-table-wrap {
    max-height: 28rem;
    overflow-y: auto;
}

.stats-table {
    margin: 0;
    font-size: .8125rem;
}

/* The header stays put while the day scrolls under it. */
.stats-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--bs-body-bg);
    font-size: .6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

.stats-table td {
    vertical-align: middle;
}

.stats-col-num {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.stats-none {
    color: var(--bs-secondary-color);
}

/* --------------------------------------------------------------- queue table */

/*
 * The team's queues on the team tab: seven columns, the first two of which are the queue as it is
 * this second and the rest how its day has gone.
 *
 * Fixed columns for the same reason the roster below it has them â€” a queue's depth and its wait are
 * rewritten every round, and a browser sizing columns from their contents makes the whole table
 * twitch sideways when a caller arrives. Narrower than the roster's minimum on purpose: there are
 * fewer columns and none of them holds a sentence, so it fits a panel without scrolling sideways.
 */
.stats-table-queues {
    table-layout: fixed;
    min-width: 44rem;
}

.stats-table-queues th:nth-child(2),
.stats-table-queues th:nth-child(3) { width: 6rem; }    /* venter, Ã¦ldste */
.stats-table-queues th:nth-child(4),
.stats-table-queues th:nth-child(5) { width: 6.5rem; }  /* besvaret, afbrudt */
.stats-table-queues th:nth-child(6),
.stats-table-queues th:nth-child(7) { width: 7.5rem; }  /* gns. vent, lÃ¦ngste vent */

.stats-table-queues td,
.stats-queues-name {
    overflow: hidden;
}

/* Fixed columns can be wider than the panel, so the wrapper scrolls rather than the columns
   collapsing â€” the same arrangement the roster table has. */
.stats-table-wrap:has(.stats-table-queues) {
    overflow-x: auto;
}

/* The queue's own cell: a lamp and a name, the lamp in the same colour the strip under the call bar
   gives the same queue. It is the row's whole colour language â€” a coloured row would read as an
   alarm, and a queue two minutes deep is not one. */
.stats-queues-name {
    display: flex;
    align-items: center;
    gap: .4375rem;
    font-weight: 600;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.stats-queues-lamp {
    flex: 0 0 auto;
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: var(--q-level, var(--bs-border-color));
}

.stats-queues-waiting {
    font-weight: 600;
}

/* The wait carries the colour, and the depth beside it â€” the two are one statement, and a queue
   four deep with nobody waiting long is a queue that is working. */
.stats-queues-row[data-level="warn"] .stats-queues-waiting,
.stats-queues-row[data-level="warn"] td:nth-child(3),
.stats-queues-row[data-level="alarm"] .stats-queues-waiting,
.stats-queues-row[data-level="alarm"] td:nth-child(3) {
    color: var(--q-level);
}

/* Nothing to report, said quietly: an empty queue and a wait nobody knows are both dashes, and
   neither should read as a figure. */
.stats-table-queues .is-muted {
    color: var(--bs-secondary-color);
}

/*
 * Figures that are a floor rather than a day: DataHub counts from its own start, so a restart at two
 * in the afternoon leaves a tally that looks like a day and is half of one. Faded with the reason in
 * the title, the same treatment the hour chart gives the hours it cannot account for â€” and the
 * header says it in words, because a shade is not a sentence.
 */
.stats-table-queues .is-partial {
    opacity: .7;
}

/* ---------------------------------------------------------------- team table */

/*
 * Fixed columns, because the contents of this table move on their own.
 *
 * Every row redraws each time a roster arrives, and the status cell is a different width each time
 * it does â€” "Klar 8 sek" one round, "Ikke klar 12 min Â· Frokost" the next. With the browser sizing
 * columns from their contents, one colleague going on a break shifted every figure in the table
 * sideways, and a table that twitches every three seconds is unreadable whatever is in it.
 *
 * The widths below are therefore the layout, not a hint. Anything too long for its column is cut
 * with an ellipsis rather than allowed to widen it.
 */
.stats-table-team {
    table-layout: fixed;
    /* The named columns come to 58rem; the agent's name takes what is left, and this stops it
       being squeezed to nothing on a narrow window â€” the wrapper scrolls instead. */
    min-width: 67rem;
}

.stats-table-team th:nth-child(2)  { width: 6.5rem; } /* enhed */
.stats-table-team th:nth-child(3)  { width: 13rem; }  /* status â€” holds "Ikke klar - <kode>" plus the clock */
.stats-table-team th:nth-child(4)  { width: 9rem; }   /* kÃ¸ */
.stats-table-team th:nth-child(5),
.stats-table-team th:nth-child(6)  { width: 4rem; }   /* ind, ud */
.stats-table-team th:nth-child(7),
.stats-table-team th:nth-child(8)  { width: 6.25rem; } /* taletid ind, ud */
.stats-table-team th:nth-child(9)  { width: 9rem; }   /* andel */

/* Fixed columns cut rather than stretch, so anything that can be long has to say where. */
.stats-table-team td {
    overflow: hidden;
}

/* Horizontal, now that a fixed layout can be wider than the panel. It was only ever vertical
   because the columns used to shrink to fit whatever was in them. */
.stats-table-wrap:has(.stats-table-team) {
    overflow-x: auto;
}

/*
 * Where a colleague is right now.
 *
 * It carries `state-pill` and Webex's states translated into the desk's own names, so the colours
 * are not listed again here â€” `.state-pill[data-state="â€¦"] .state-dot` in agent.css is the one
 * table, and this row picks it up. A second list is how a team panel ends up amber for Not Ready
 * while the agent's own pill beside it is red, which is precisely what happened.
 *
 * What is overridden is the pill's chrome. `.state-pill` is a 44px tap target with a border and a
 * background, which is right at the top of the screen and wrong in a table row.
 */
.stats-state {
    min-height: 0;
    padding: 0;
    gap: .375rem;
    align-items: baseline;
    border: 0;
    border-radius: 0;
    background: none;
    white-space: nowrap;
}

.stats-state .state-dot {
    align-self: center;
    width: .5rem;
    height: .5rem;
}

/*
 * Reserved blinks on the agent's own pill because it is the one state that needs her to act. A
 * colleague's Reserved needs nothing from the person reading this table, and a roster of them would
 * be a row of flashing dots saying so.
 */
.stats-state[data-state="Reserved"] .state-dot {
    animation: none;
}

/* The state itself is what the eye lands on; the clock beside it is for when two colleagues are in
   the same state and only one of them has been there twenty minutes. */
.stats-state-since {
    font-size: .6875rem;
    font-variant-numeric: tabular-nums;
    color: var(--bs-secondary-color);
}

/* The enhed and kÃ¸ columns: secondary text beside the status, each cut with an ellipsis rather than
   allowed to widen its column â€” a pause code is a sentence somebody typed into Webex, and left to
   size its column it pushes the figures off the right of the table. */
.stats-col-aside {
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.stats-col-aside span {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.stats-agent {
    display: flex;
    align-items: center;
    gap: .5rem;
    min-width: 0;
}

/* The name gives way, not the "dig" badge beside it: a badge that is cut in half stops reading as a
   badge, where a cut name is still obviously a name. */
.stats-agent > span:first-child {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.stats-agent-me {
    flex: 0 0 auto;
}

/* Bootstrap paints table cells from --bs-table-bg, so a background on the row itself is covered
   over by them â€” the variable is the way in. */
.stats-row-me {
    --bs-table-bg: color-mix(in srgb, var(--bs-primary) 8%, transparent);
}

.stats-agent-me {
    padding: .0625rem .375rem;
    font-size: .625rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    background: var(--bs-primary);
    color: #fff;
    border-radius: 1rem;
}

.stats-col-share {
    width: 9rem;
}

/* The row's share of the busiest colleague's day. No axis, deliberately â€” it is there to be
   compared down the column, not read as a figure. */
.stats-share {
    display: block;
    height: .375rem;
    border-radius: 1rem;
    background: var(--bs-border-color);
    overflow: hidden;
}

.stats-share i {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--bs-primary);
}

/* ==========================================================================
   Dagens opgaver
   ==========================================================================

   The agent's own tasks today, one row per task. Desktop only, and not pretended to be shared: this
   is a table of eight columns, which is exactly the kind of thing that does not survive the trip to
   a 390px screen.

   The card, the section head, the scrolling wrapper and the sticky header are the .stats-* rules
   above, used as they are rather than copied â€” the two screens are the same card with different
   things in it, and a second set of rules is how they end up half a millimetre apart. What is below
   is only what is genuinely new: the row itself. */

/* The full width of the main area, as the statistics screen takes it. The 46rem reading column
   .main > .screen imposes is for sign-in and the station form; a table of ten columns has nothing
   to gain from being squeezed into it. */
.main > .screen-tasks {
    max-width: none;
}

/*
 * Fixed columns, for the same reason the team table has them: every cell here is a different width
 * from one row to the next â€” a number, a queue name, a dash â€” and a browser sizing columns from
 * their contents puts the taletid column somewhere else on every screenful of the day.
 *
 * The widths below are therefore the layout, not a hint. Destination takes what is left, because it
 * is the one column that can hold anything from a Danish number to an e-mail address.
 */
.tasks-table {
    table-layout: fixed;
    min-width: 72rem;
}

.tasks-table th:nth-child(1)  { width: 4.5rem; }  /* kl. */
.tasks-table th:nth-child(2)  { width: 6rem; }    /* retning */
.tasks-table th:nth-child(3)  { width: 8.5rem; }  /* kanal, plus room for a message mark */
.tasks-table th:nth-child(4)  { width: 7.5rem; }  /* type */
.tasks-table th:nth-child(6)  { width: 9rem; }    /* kÃ¸ */
.tasks-table th:nth-child(7)  { width: 7rem; }    /* taletid */
.tasks-table th:nth-child(8)  { width: 8rem; }    /* efterbehandling */
.tasks-table th:nth-child(9)  { width: 11rem; }   /* resultat */
.tasks-table th:nth-child(10) { width: 3.5rem; }  /* ring op */

/* Fixed columns cut rather than stretch, so anything that can be long has to say where. */
.tasks-table td {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Outbound calling is a per-agent permission. Without it the column is a control the agent can
   never press, on every row of the day â€” so it goes, rather than sitting there greyed out. */
.tasks-table.is-nodial th:nth-child(10),
.tasks-table.is-nodial td:nth-child(10) {
    display: none;
}

/* Horizontal, because a fixed layout can be wider than the panel. */
.tasks-table-wrap {
    overflow-x: auto;
}

/*
 * Where the day is added up, on the floor of the table rather than in a line above it: "42:42" a
 * long way from the taletid column it is the sum of is a number the reader has to be told what to
 * do with.
 *
 * It stays on the floor while the rows scroll under it, the way the headings stay on the ceiling. A
 * total that scrolls out of sight on a busy day is a total nobody sees on the day it matters.
 */
.tasks-table tfoot td {
    position: sticky;
    bottom: 0;
    background: var(--bs-body-bg);
    border-top: 1px solid var(--bs-border-color);
    font-weight: 600;
}

/* Which way the call went. The window's own colour language â€” blue in, green out â€” so a row here
   means what the same colour means on the desk and in the statistics. */
.task-dir {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    font-size: .75rem;
    font-weight: 600;
}

.task-dir::before {
    content: "";
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: currentColor;
}

.task-dir[data-direction="in"]  { color: var(--bs-primary); }
.task-dir[data-direction="out"] { color: var(--bs-success); }

/*
 * What the task was: Voice, e-mail or chat, with the drawing beside the word.
 *
 * The icon is the thing that is actually read down the column â€” three shapes are told apart at a
 * glance where three words have to be read one at a time â€” and the word is there because an icon on
 * its own has to be learned first, and this table is not somewhere to make an agent learn anything.
 */
.task-channel {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    min-width: 0;
}

.task-channel-icon {
    flex: 0 0 auto;
    width: .875rem;
    height: .875rem;
    fill: none;
    stroke: var(--bs-secondary-color);
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/*
 * How the task ended: transferred to a colleague, put back in a queue, or never answered.
 *
 * Blank on an ordinary handled call, and that is the design rather than an omission â€” a column that
 * reads "Afsluttet" on nine rows out of ten is a column nobody scans, and leaving those rows empty
 * is what makes the two that are not jump off the screen.
 */
.task-outcome {
    font-size: .75rem;
}

.task-outcome[data-outcome="missed"] {
    color: var(--bs-danger-text-emphasis);
    font-weight: 600;
}

/*
 * A message that reached the handset. The only one of the four message statuses that needed a
 * colour of its own: the two that went wrong borrow "missed" above, because red means the customer
 * was not reached and that is as true of a message that bounced as of a call nobody answered.
 */
.task-outcome[data-outcome="delivered"] {
    color: var(--bs-success-text-emphasis);
    font-weight: 600;
}

/*
 * Sent, and nothing back yet. Deliberately the quiet one — green here would say it arrived, and the
 * whole reason a delivery report exists is that those are two different facts.
 */
.task-outcome[data-outcome="pending"] {
    color: var(--bs-secondary-color);
}

/*
 * Messages sent while a task was up, as one mark beside the channel rather than a column of their
 * own: most rows have none, and a tenth column that is empty nine times out of ten costs every row
 * width it cannot spare.
 *
 * The colour is the worst status among them, so a call where one message bounced is visible without
 * opening anything.
 */
.task-msg {
    display: inline-flex;
    align-items: center;
    gap: .125rem;
    margin-left: .375rem;
    padding: .0625rem .25rem;
    font-size: .6875rem;
    font-weight: 600;
    line-height: 1;
    vertical-align: .0625rem;
    border: 1px solid var(--bs-border-color);
    border-radius: .625rem;
}

.task-msg[data-outcome="delivered"] { color: var(--bs-success); }
.task-msg[data-outcome="missed"]    { color: var(--bs-danger); }
.task-msg[data-outcome="pending"]   { color: var(--bs-secondary-color); }

/* currentColor, so the mark reads as one thing rather than a grey drawing beside a red number. */
.task-msg-icon {
    width: .75rem;
    height: .75rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* currentColor, so the arrow turns red with the words in a cell that says the customer was not
   served â€” a grey arrow beside red text reads as two separate statements. */
.task-outcome-icon {
    width: .875rem;
    height: .875rem;
    margin-right: .25rem;
    vertical-align: -.125rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Nothing to show in this cell. Grey rather than absent, so the column still reads as a column when
   several rows running have no queue. */
.task-none {
    color: var(--bs-secondary-color);
}

/*
 * The one control on the screen. An icon on its own: the column is the label, a word on every row
 * would be the widest thing in the table, and the title carries the number for anyone who wants to
 * be sure before they press it.
 */
.task-call {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    color: var(--bs-success);
    background: none;
    border: 1px solid var(--bs-border-color);
    border-radius: 50%;
}

.task-call:hover:not(:disabled) {
    color: #fff;
    background: var(--bs-success);
    border-color: var(--bs-success);
}

.task-call:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

/* Two things put it out of reach â€” the row is a chat or an e-mail, or there is a call up already â€”
   and both leave it in place rather than removing it. A control that disappears from some rows makes
   the column unreadable; one that is visibly out of reach says what it means, and its title says
   which of the two it is. */
.task-call:disabled {
    color: var(--bs-secondary-color);
    opacity: .45;
}

.task-call svg {
    width: 1rem;
    height: 1rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* ============================================================== telefonbogen */

/* The employee phone book. Desktop only, for the same reason the tasks screen is: seven columns of
   a person's details do not survive the trip to a 390px screen.

   The card, the section head, the scrolling wrapper and the sticky header are the .stats-* rules
   above, used as they are rather than copied â€” these screens are the same card with different things
   in it, and a second set of rules is how they end up half a millimetre apart. What is below is only
   what is genuinely new: the search line, and the two columns that are controls. */

/* The full width of the main area. The 46rem reading column .main > .screen imposes is for sign-in
   and the station form; a table of seven columns has nothing to gain from being squeezed into it. */
.main > .screen-directory {
    max-width: none;
}

/*
 * The search line, between the head and the table.
 *
 * The field is capped rather than full width: a search box the width of the screen invites a
 * sentence, and what goes in here is a word or a number. The count sits beside it rather than under
 * the table, because it is the answer to what was just typed and belongs where the eye already is.
 */
.directory-search {
    display: flex;
    align-items: center;
    gap: .75rem;
    margin-bottom: .75rem;
}

.directory-search .form-control {
    max-width: 22rem;
}

.directory-count {
    font-size: .8125rem;
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
}

/*
 * Fixed columns, for the same reason the tasks table has them: every cell is a different width from
 * one row to the next â€” a name, a dash, a full address â€” and a browser sizing columns from their
 * contents puts the mobile column somewhere else on every screenful of the book.
 *
 * The named columns come to 65rem; the name takes what is left, and the wrapper scrolls rather than
 * squeezing it to nothing on a narrow window.
 */
.directory-table {
    table-layout: fixed;
    min-width: 78rem;
}

/*
 * Every column has a width, including the first, and the seven add up to exactly the min-width
 * above. That is what makes them proportions rather than measurements.
 *
 * A fixed layout hands the slack to whichever columns have no width of their own, so leaving the
 * name unstated put the whole of a wide screen into it: on a 1700px window the name column was
 * airy enough to fit two of them while the address beside it was still being cut. With all seven
 * stated the table scales as a piece, and the columns that hold the longest values â€” the address
 * and the e-mail â€” grow with the window instead of watching the name do it.
 *
 * Afdeling is the widest of the short ones on purpose. It is the column that gets cut first in a
 * real organisation: departments are named things like "Fakturering og debitorstyring", where a
 * title is two words and a name is three.
 */
.directory-table th:nth-child(1) { width: 11rem; }   /* navn */
.directory-table th:nth-child(2) { width: 10rem; }   /* titel */
.directory-table th:nth-child(3) { width: 11rem; }   /* afdeling */
.directory-table th:nth-child(4) { width: 9.5rem; }  /* arbejde */
.directory-table th:nth-child(5) { width: 9.5rem; }  /* mobil */
.directory-table th:nth-child(6) { width: 13rem; }   /* e-mail */
.directory-table th:nth-child(7) { width: 14rem; }   /* adresse */

/* Fixed columns cut rather than stretch, so everything that can be long says so â€” and every cell
   here carries its own title, because every one of them can be cut. */
.directory-table td {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The one column that is read down rather than across. */
.directory-name {
    font-weight: 600;
}

.directory-address,
.directory-email {
    font-size: .8125rem;
}

.directory-email a {
    color: inherit;
    text-decoration: none;
}

.directory-email a:hover {
    text-decoration: underline;
}

/* Grey and quiet, because on most rows this is the least interesting thing in it. */
.directory-address {
    color: var(--bs-secondary-color);
}

/*
 * A number that rings when pressed.
 *
 * The number is the control rather than an icon beside it: there are two per row, and a column of
 * round buttons twice over turns a phone book into a table of controls. It is drawn as text with a
 * small handset in front, and only looks like a button under the pointer â€” at rest the column has to
 * read as a column of numbers, which is what somebody scanning it is looking for.
 */
.directory-call {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    max-width: 100%;
    padding: .125rem .375rem;
    margin-left: -.375rem;
    font-variant-numeric: tabular-nums;
    color: inherit;
    background: none;
    border: 1px solid transparent;
    border-radius: .375rem;
}

.directory-call:hover:not(:disabled) {
    color: var(--bs-success);
    border-color: var(--bs-border-color);
}

.directory-call:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 1px;
}

/* A call is up. Left in place and visibly out of reach rather than turned back into text: a column
   whose cells change shape when a call arrives is a column that moves under the reader's finger. */
.directory-call:disabled {
    color: var(--bs-secondary-color);
    opacity: .55;
}

.directory-call-icon {
    flex: 0 0 auto;
    width: .875rem;
    height: .875rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    /* Grey at rest and the button's colour on hover, so the column reads as numbers until the
       pointer says otherwise. */
    color: var(--bs-secondary-color);
}

.directory-call:hover:not(:disabled) .directory-call-icon {
    color: inherit;
}

.directory-call span {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ------------------------------------------------------------- queues on the desk */

/*
 * The team's queues as boxes, for a layout that puts them beside the work.
 *
 * A grid rather than a flex row, so the boxes stay the same width as their number changes: a team
 * answering two queues and one answering seven should not be two different-looking panels, and a
 * box that grows to fill the row makes the figure inside it sit somewhere new on every deployment.
 * auto-fill with a minimum is what keeps them the same size and wraps when the column is narrow.
 *
 * The minimum is 7rem because this panel has to work in a col-4 as well as across the desk. That is
 * two boxes to a line in a third of the screen and six or seven across the whole of it, and the
 * same box either way â€” a panel that laid itself out differently at two widths would be two panels
 * to keep looking right. Whatever does not fit scrolls; the card is bounded by its column.
 */
/*
 * Half the padding of an ordinary card, and the head's negative margin halved with it.
 *
 * This panel is usually a strip: a row eight lines tall across the top of the desk, where a card's
 * 1rem gutter is a third of the height spent on nothing. Every other card here holds a column of
 * content and wants the room; this one holds one line of boxes and has to earn its place against
 * the panels under it.
 *
 * The head keeps the card's own horizontal padding, so its title still lines up with every other
 * card's on the screen â€” it is only the gutter around the boxes that gives.
 */
.queue-tiles-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: 100%;
    padding: .5rem;
}

.queue-tiles-head {
    margin: -.5rem -.5rem .375rem;
}

.queue-tiles {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    overflow: auto;
    min-height: 0;
}

/*
 * One queue is a card of three lines: which queue, how bad it is now, and how the day has gone.
 *
 * It was a grid of small boxes, on the idea of fitting several queues on one line above the desk.
 * That was the wrong trade twice over: a box narrow enough to fit four has no room for a name, and
 * the figures had to be squeezed onto one line with the day's pair cut to an ellipsis. Stacked and
 * full width, each card gets its three lines and the cards are compared by running an eye down
 * them.
 *
 * The colour is the left edge rather than the whole card. A filled amber card is louder than the
 * figure in it, and a stack of them is a wall of colour with no hierarchy, which is the opposite of
 * what the panel is for. The edge is enough to find across a screen, and it is the same three
 * shades the statistics table uses, from the same variables.
 */
.queue-tile {
    padding: .3125rem .5rem;
    border: 1px solid var(--bs-border-color);
    border-left: 3px solid var(--q-level, var(--bs-border-color));
    border-radius: var(--bs-border-radius-sm);
    background: var(--bs-body-bg);
    min-width: 0;
}

.queue-tile[data-level="calm"]  { --q-level: var(--state-available); }
.queue-tile[data-level="warn"]  { --q-level: var(--bs-warning); }
.queue-tile[data-level="alarm"] { --q-level: var(--bs-danger); }

/* Cut rather than wrapped: a long queue name must not make its card taller than the one under it,
   or a stack of them stops being scannable. The whole name is in the title attribute. */
.queue-tile-name {
    font-size: .75rem;
    line-height: 1.3;
    color: var(--bs-secondary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The line an agent actually reads. The two figures sit together rather than at opposite ends of
   the card, because at full width the eye would have to travel to pair them up. */
.queue-tile-now {
    display: flex;
    align-items: baseline;
    gap: .875rem;
    line-height: 1.2;
}

.queue-figure {
    display: inline-flex;
    align-items: baseline;
    gap: .25rem;
}

/* The one thing on the card an agent reads across a room, so it carries at a size nothing else in
   the panel does. Tabular, or a stack of cards jitters as the counts change width. */
.queue-figure-value {
    font-size: 1.5rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.queue-figure-label {
    font-size: .6875rem;
    color: var(--bs-secondary-color);
}

/* Smaller and quieter: the day is context, not the thing being watched. */
.queue-tile-day {
    font-size: .625rem;
    line-height: 1.3;
    color: var(--bs-tertiary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.queue-tiles-empty {
    color: var(--bs-secondary-color);
    font-size: .8125rem;
}

/* ----------------------------------------------------------------- the team roster */

.team-mates-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: 100%;
}

.team-mates {
    overflow: auto;
    min-height: 0;
}

/*
 * One step under the desk's own size, and that is the whole of the smaller type here.
 *
 * The panel is a column of names read at a glance rather than prose read a line at a time, so it
 * takes the size a list takes; the room that buys is what lets a whole name fit in a narrow column.
 */
.mate-list {
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: .8125rem;
}

.mate-row {
    border-bottom: 1px solid var(--bs-border-color);
}

.mate-row:last-child { border-bottom: 0; }

/* The open row tinted, so the two buttons plainly belong to the name above them and not to the one
   below. */
.mate-row-open {
    background: var(--bs-tertiary-bg);
}

/*
 * The always-drawn half of a row, and the whole of what is pressed to unfold the rest of it.
 *
 * Stripped of every button look it inherits, because it is a row that happens to be pressable: a
 * column of bordered buttons down the panel would read as the controls rather than as the list. The
 * name is still what gives if anything has to, the state keeping its width as the thing being read
 * and the mark being a single stroke, but with the two buttons out of the row there is now room for
 * a name before that happens.
 */
.mate-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 100%;
    padding: .3125rem 0;
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    text-align: left;
}

/* Only the foldable rows are buttons: her own row is a plain element, and hover or a caret on it
   would promise a press that does nothing. */
button.mate-head {
    cursor: pointer;
}

button.mate-head:hover .mate-name,
button.mate-head:focus-visible .mate-name {
    text-decoration: underline;
}

button.mate-head:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 1px;
}

.mate-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The agent's own row, marked rather than hidden: she is on the team, and a roster that left her
   out would not add up against the figures on the statistics screen. */
.mate-row-self .mate-name {
    font-weight: 600;
}

/* Same chrome-stripping as the statistics roster's, and for the same reason: .state-pill is a 44px
   tap target, which is right at the top of the screen and wrong in a list row. The colour still
   comes from the one table in agent.css. */
.mate-state {
    flex: 0 0 auto;
    min-height: 0;
    padding: 0;
    gap: .375rem;
    border: 0;
    border-radius: 0;
    background: none;
    font-size: .6875rem;
    white-space: nowrap;
}

.mate-state .state-dot {
    width: .4375rem;
    height: .4375rem;
}

/* A colleague's Reserved needs nothing from the person reading this list: see the same rule on the
   statistics roster. A column of flashing dots is not a call anybody has to answer. */
.mate-state[data-state="Reserved"] .state-dot {
    animation: none;
}

/* Quiet, and it turns rather than being swapped for a second glyph, the way the side menu's chevron
   does. Quiet because the state pill beside it is the thing worth a colour in this row. */
.mate-caret {
    flex: 0 0 auto;
    width: .75rem;
    height: .75rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--bs-tertiary-color);
    transition: transform .15s ease;
}

.mate-head[aria-expanded="true"] > .mate-caret {
    transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
    .mate-caret {
        transition: none;
    }
}

/* Under the name rather than beside it, which is the width the fold was opened for, and inset to the
   name above so the pair reads as that colleague's. */
.mate-actions {
    display: flex;
    gap: .25rem;
    padding: 0 .5rem .375rem;
}

/* Small, but on their own line now, so they can have the padding a press wants rather than the
   padding a row had left over. */
.mate-action {
    --bs-btn-padding-y: .125rem;
    --bs-btn-padding-x: .5rem;
    --bs-btn-font-size: .6875rem;
}

.team-mates-empty {
    color: var(--bs-secondary-color);
    font-size: .8125rem;
}

/*
 * Both panels take the height their wrapper was given, the way the transcript does.
 *
 * The wrapper is what ui/widgets.js moved into the column and what the layout's height landed on,
 * so the card inside it has to be told to fill â€” without this a queue row asked to be 7rem tall is
 * a 7rem column with a content-height card floating at the top of it.
 */
.queue-tiles-panel,
.team-mates-panel {
    height: 100%;
}

/* The head keeps its size and the list scrolls, rather than the head being squeezed out of a short
   column first â€” it carries the only word saying what the panel is. */
.queue-tiles-head,
.team-mates-head {
    flex: 0 0 auto;
}
