/* Coffeeagent styles on top of AdminLTE 4 / Bootstrap 5.3; colors follow the light/dark theme. */

/* Page head: Back, the title, the actions on the right; they wrap below the title on phones. */
.page-head { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; }
.page-head h1 { flex: 1 1 12rem; min-width: 0; }
.page-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-left: auto; }
.page-back { flex: none; }
@media (max-width: 575.98px) {
    .page-head h1 { flex-basis: calc(100% - 3.5rem); }
    .page-actions { margin-left: 0; width: 100%; justify-content: flex-end; }
}

/*
 * Detail pages: groups of rows, a label on the left and the value on the right. The groups
 * flow into as many columns as fit (one on phones). In the edit mode every value gives
 * way to its input, which has exactly the size of the value, so nothing moves.
 */
.props {
    --prop-pad-y: calc(.25rem + var(--bs-border-width));
    --prop-pad-x: calc(.5rem + var(--bs-border-width));
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 21rem), 1fr));
    gap: 1.25rem 2rem;
    align-items: start;
}
.prop-group { min-width: 0; }
.prop-group-wide { grid-column: 1 / -1; }
/* a group whose values are all hidden (empty, or private) disappears with its heading */
.prop-group:not(:has(.prop, .formset-form)) { display: none; }
.prop-group-title {
    font-size: .75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--bs-secondary-color);
    margin: 0 0 .25rem;
    padding-bottom: .25rem;
    border-bottom: 1px solid var(--bs-border-color);
}
.prop {
    display: grid;
    grid-template-columns: minmax(6.5rem, 38%) minmax(0, 1fr);
    column-gap: .5rem;
    align-items: start;
    padding: .125rem 0;
    border-bottom: 1px solid var(--bs-border-color-translucent);
}
.prop:last-child { border-bottom: 0; }
.prop-label {
    font-size: .875rem;
    line-height: 1.5rem;
    padding: var(--prop-pad-y) 0;
    color: var(--bs-secondary-color);
}
.prop-label label { cursor: pointer; }
.prop-value {
    font-size: 1rem;
    line-height: 1.5rem;
    min-height: calc(1.5rem + 2 * var(--prop-pad-y));
    padding: var(--prop-pad-y) var(--prop-pad-x);
    overflow-wrap: anywhere;
}
.prop-value:empty::after { content: "–"; color: var(--bs-tertiary-color); }
.prop-value a { text-decoration: none; }
.prop-wide { grid-template-columns: minmax(0, 1fr); }
.prop-wide .prop-label { padding-bottom: 0; }
.prop-input { display: none; }
/* the inputs: 1rem text (smaller makes iPhones zoom in), the height of a one-line value */
.prop-input .form-control,
.prop-input .form-select {
    font-size: 1rem;
    line-height: 1.5rem;
    padding: .25rem .5rem;
    min-height: calc(1.5rem + 2 * var(--prop-pad-y));
}
.prop-input .form-select { padding-right: 2rem; background-position: right .5rem center; }
.prop-input textarea.form-control { resize: vertical; overflow: hidden; }
.prop-input .form-check-input { margin: calc((1.5rem + 2 * var(--prop-pad-y) - 1.25em) / 2) var(--prop-pad-x); width: 1.25em; height: 1.25em; }
.prop-input .invalid-feedback:empty { display: none; }
.prop-input .is-invalid ~ .invalid-feedback { display: block; }
.detail-form.is-editing .prop-editable .prop-value { display: none; }
.detail-form.is-editing .prop-editable .prop-input { display: block; }
.detail-form.is-editing .prop:not(.prop-editable) { opacity: .55; }
.edit-only { display: none; }
.detail-form.is-editing .edit-only { display: block; }
/* while editing, the header only offers Cancel and Save */
body.is-editing .page-actions > :not([data-edit-cancel]):not([data-edit-toggle]) { display: none !important; }
[data-edit-toggle] { min-width: 5.5rem; }

/* Rating: five stars with halves for 0-100, the number beside them, the sources below */
.rating { display: flex; align-items: center; gap: .25rem; }
.rating-stars { display: inline-flex; gap: .125rem; font-size: 1.25rem; line-height: 1; color: #f0a500; padding: calc(var(--prop-pad-y) + .125rem) 0 calc(var(--prop-pad-y) + .125rem) var(--prop-pad-x); white-space: nowrap; }
.rating-stars.is-empty { color: var(--bs-tertiary-color); }
[data-rating-stars] { cursor: pointer; touch-action: manipulation; user-select: none; -webkit-user-select: none; border-radius: var(--bs-border-radius); }
[data-rating-stars]:focus-visible { outline: 2px solid var(--bs-primary); outline-offset: 2px; }
.rating-number { font-weight: 600; min-height: 0; }
.rating-number:empty::after { content: ""; }
.rating-input .form-control { width: 5rem; }
.detail-form.is-editing .prop-editable .rating > .rating-input { display: inline-block; }
.rating-sources { display: flex; flex-wrap: wrap; gap: .375rem; padding: 0 var(--prop-pad-x) .375rem; font-size: .8125rem; }
.rating-source { display: inline-flex; gap: .3rem; align-items: baseline; padding: .05rem .5rem; border-radius: 999px; border: 1px solid var(--bs-border-color); color: var(--bs-secondary-color); }
.rating-source strong { color: var(--bs-body-color); font-weight: 600; }
.rating-source.is-current { border-color: #f0a500; background: rgba(240, 165, 0, .1); }

/* Profile steps: what each step aims at, and below how the shot carried it out */
.steps { list-style: none; margin: 0; padding: 0; counter-reset: step; }
.step { counter-increment: step; display: grid; grid-template-columns: 2rem minmax(0, 1fr); column-gap: .25rem; padding: .625rem 1rem .625rem .75rem; border-bottom: 1px solid var(--bs-border-color-translucent); }
.step:last-child { border-bottom: 0; }
.step::before { content: counter(step); grid-row: span 3; width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); font-size: .75rem; font-weight: 600; display: grid; place-items: center; margin-top: .05rem; }
.step-name { font-weight: 600; }
.step-aim { font-size: .875rem; color: var(--bs-secondary-color); }
.step-execution { display: flex; gap: .4rem; align-items: baseline; font-size: .875rem; margin-top: .25rem; }
.step-execution > i { flex: none; }
.step-good > i { color: var(--bs-success); }
.step-fair > i { color: var(--bs-warning); }
.step-off > i { color: var(--bs-danger); }
.step-not-reached { color: var(--bs-tertiary-color); }

/* Charts are loaded after the page; reserve some space meanwhile. */
.plot-container {
    min-height: 200px;
    position: relative;
}

.plot-container.loading::after {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 2rem;
    height: 2rem;
    border: 0.25rem solid var(--bs-secondary-bg);
    border-top-color: var(--bs-primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Hidden "delete" checkboxes of dynamic formsets; rows are removed with a button. */
input[type="checkbox"][name$="-DELETE"] {
    display: none;
}

.formset-form + .formset-form {
    border-top: 1px dashed var(--bs-border-color);
    padding-top: 0.5rem;
}

/* DataTables toolbars wrap nicely on small screens. */
div.dt-container div.dt-layout-row {
    flex-wrap: wrap;
    gap: 0.5rem;
}

div.dt-container .dt-buttons {
    flex-wrap: wrap;
}

@media (max-width: 575.98px) {
    div.dt-container div.dt-layout-cell {
        flex-basis: 100%;
        justify-content: flex-start;
    }
    div.dt-container .dt-search input {
        width: 100%;
        margin-left: 0;
    }
    .app-content-header h1 {
        font-size: 1.25rem;
    }
}

table.dataTable td a {
    text-decoration: none;
}

.label-preview {
    min-height: 4rem;
}
/* the preview stays in view while the texts below are edited */
.label-preview-bar { position: sticky; top: 0; z-index: 2; background: var(--bs-modal-bg, var(--bs-body-bg)); padding: 1rem 0 .75rem; margin-bottom: .25rem; }
.label-preview img { display: block; margin: 0 auto; max-height: 40dvh; width: auto; max-width: 100%; }
.label-texts { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr)); gap: .5rem .75rem; }
.label-from { font-size: .75rem; color: var(--bs-secondary-color); white-space: nowrap; }
/* texts that the chosen tape is too narrow for */
.label-text.is-hidden input { color: var(--bs-tertiary-color); }
.label-text.is-hidden .label-from { color: var(--bs-warning-text-emphasis); font-weight: 600; }

.small-box .small-box-icon {
    opacity: 0.35;
}

@media (max-width: 575.98px) {
    .app-content .card-body {
        padding: 0.75rem;
    }
}

/* Virtual scrolling needs rows of equal height: one line per cell, full text on hover. */
table.dataTable > tbody > tr > td {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 20rem;
}

table.dataTable > tbody > tr.child > td {
    white-space: normal;
}

/* Mobile Safari counts its collapsible toolbars into 100vh, which pushed the footer
 * below the visible area on iPad and iPhone. 100dvh is the height actually visible. */
.app-wrapper {
    min-height: 100vh;
    min-height: 100dvh;
}

/* Info boxes slide in from the top. */
#toasts {
    z-index: 1090;
}

.toast.slide-in.showing,
.toast.slide-in.show {
    animation: toast-slide-in 0.35s ease-out;
}

@keyframes toast-slide-in {
    from {
        transform: translateY(-120%);
        opacity: 0;
    }
    to {
        transform: none;
        opacity: 1;
    }
}

/* Settings: section cards over the full width, the save bar stays in reach */
.settings-form .card-body > .mb-3:last-child { margin-bottom: 0 !important; }
.settings-actions { position: sticky; bottom: 0; z-index: 2; }
.site-intro p:last-child { margin-bottom: 0; }
.welcome { border: 0; border-left: 4px solid #8a5a3b; background: linear-gradient(90deg, rgba(138, 90, 59, .08), transparent 60%), var(--bs-body-bg); }
.welcome-icon { width: 3.5rem; height: 3.5rem; }
.welcome .site-intro { font-size: 1.02rem; line-height: 1.55; color: var(--bs-body-color); max-width: 60rem; }
.welcome-icon-small { width: 1.75rem; height: 1.75rem; }

/* Expandable row details: a plain background in the room kept for them, left-aligned values */
div.dt-scroll-body.room-for-details { background: none !important; }
table.dataTable > tbody > tr.child ul.dtr-details > li > span { text-align: start !important; }

/* Dashboard */
.kpi { border-radius: var(--bs-border-radius); padding: .6rem .8rem; height: 100%; box-shadow: var(--bs-box-shadow-sm); }
.kpi-label, .kpi-note { font-size: .8rem; opacity: .85; }
.kpi-value { font-size: 1.35rem; font-weight: 600; line-height: 1.3; }
.beverage-bar { display: flex; height: 1.4rem; border-radius: var(--bs-border-radius); overflow: hidden; font-size: .75rem; font-weight: 600; }
.beverage-bar > div { display: flex; align-items: center; justify-content: center; min-width: 1.4rem; white-space: nowrap; overflow: hidden; }
.bar-chart { display: flex; align-items: flex-end; gap: .35rem; height: 8rem; }
.bar-column { flex: 1; height: 100%; display: flex; flex-direction: column-reverse; align-items: stretch; }
.bar { border-radius: .25rem .25rem 0 0; display: flex; justify-content: center; min-height: 2px; }
.bar-value { font-size: .75rem; font-weight: 600; padding-top: .15rem; }
.bar-value-above { text-align: center; color: var(--bs-secondary-color); }
.bar-labels { display: flex; gap: .35rem; margin-top: .25rem; font-size: .75rem; color: var(--bs-secondary-color); }
.bar-labels > span { flex: 1; text-align: center; }
.badge.phase-rest { background: var(--bs-primary-bg-subtle); color: var(--bs-primary-text-emphasis); }
.badge.phase-peak { background: var(--bs-success-bg-subtle); color: var(--bs-success-text-emphasis); }
.badge.phase-old { background: var(--bs-secondary-bg); color: var(--bs-secondary-color); }
.badge[class*="phase-"] { font-weight: 500; }
.sync-detail { font-size: .8rem; padding-left: 1.4rem; opacity: .9; }
@media (max-width: 767.98px) {
    [data-sync-status]:not(.show-all) .sync-ok { display: none !important; }
    [data-sync-status].show-all .sync-toggle { display: none !important; }
}
.dashboard .card-header::after { display: none; }

/* Installed app (home screen): room for the notch and the home indicator, a reload button */
.standalone-only { display: none; }
@media (display-mode: standalone) {
    .standalone-only { display: list-item; }
    body { -webkit-tap-highlight-color: transparent; overscroll-behavior-y: none; }
}
/* viewport-fit=cover lets the page reach under the status bar, the notch/Dynamic Island in landscape and
 * the home indicator: whatever must stay readable or tappable keeps clear of these edges. */
html { background-color: var(--bs-body-bg); }
.app-header { padding-top: env(safe-area-inset-top); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
.app-sidebar { padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); }
.app-main { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
.app-footer { padding-bottom: calc(.75rem + env(safe-area-inset-bottom)); padding-left: max(1rem, env(safe-area-inset-left)); padding-right: max(1rem, env(safe-area-inset-right)); }
.settings-actions { padding-bottom: calc(1rem + env(safe-area-inset-bottom)) !important; }
#toasts { margin-top: env(safe-area-inset-top); padding-left: max(1rem, env(safe-area-inset-left)) !important; padding-right: max(1rem, env(safe-area-inset-right)) !important; }
.modal { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
.modal-fullscreen-sm-down .modal-header { padding-top: calc(var(--bs-modal-header-padding-y) + env(safe-area-inset-top)); }
.modal-fullscreen-sm-down .modal-footer { padding-bottom: calc(var(--bs-modal-padding) + env(safe-area-inset-bottom)); }
.dropdown-menu { max-height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 4rem); overflow-y: auto; }
.roast-day-stats { border-bottom: 1px solid var(--bs-border-color-translucent); }
.roast-day-stats + .list-group { border-top: 0; }
.row-failed > td { opacity: .6; }
.row-failed > td:first-child, .row-failed > td.dtr-control { opacity: 1; }
.dropdown-item.bulk-blocked { color: var(--bs-tertiary-color); }
.dropdown-item.bulk-blocked i { opacity: .5; }

/* settings: default table columns as draggable labels, removed ones after the shown ones */
.column-lists { display: flex; flex-wrap: wrap; gap: .5rem; align-items: stretch; }
.column-list { display: flex; flex-wrap: wrap; align-items: center; align-content: flex-start; gap: .375rem; min-height: 2.25rem; padding: .375rem; border-radius: var(--bs-border-radius); }
.column-shown { flex: 1 1 22rem; background: var(--bs-tertiary-bg); }
.column-hidden { flex: 1 1 12rem; border: 1px dashed var(--bs-border-color); }
.column-hidden.is-empty::before { content: attr(data-empty); color: var(--bs-secondary-color); font-size: .8125rem; align-self: center; padding: 0 .25rem; }
.column-chip { display: inline-flex; align-items: center; gap: .25rem; padding: .125rem .25rem .125rem .375rem; border-radius: 999px; font-size: .875rem; background: var(--bs-primary-bg-subtle); color: var(--bs-primary-text-emphasis); border: 1px solid var(--bs-primary-border-subtle); cursor: grab; user-select: none; -webkit-user-select: none; }
.column-hidden .column-chip { background: var(--bs-body-bg); color: var(--bs-secondary-color); border-color: var(--bs-border-color); }
.column-chip.sortable-ghost { opacity: .4; }
.column-chip.sortable-chosen { cursor: grabbing; }
.column-grip { opacity: .5; font-size: .75rem; }
.column-toggle { border: 0; background: none; color: inherit; padding: 0 .25rem; line-height: 1; font-size: .75rem; border-radius: 999px; cursor: pointer; }
.column-toggle:hover, .column-toggle:focus-visible { background: rgba(var(--bs-emphasis-color-rgb), .1); }

/* the Decent machine in the menu: power button, state, temperature, water */
.machine-status { padding: .25rem .5rem .5rem; }
.machine-box { display: flex; align-items: center; gap: .75rem; padding: .45rem .5rem .45rem .375rem; border-radius: var(--bs-border-radius-lg); background: rgba(255, 255, 255, .06); }
.machine-power { flex: none; position: relative; width: 1.75rem; height: 1.75rem; border-radius: 50%; border: 2px solid rgba(255, 255, 255, .3); background: transparent; color: rgba(255, 255, 255, .65); display: grid; place-items: center; font-size: .85rem; padding: 0; transition: background-color .2s, border-color .2s, color .2s, box-shadow .2s, transform .1s; }
.machine-power-icon { width: .875rem; height: .875rem; display: block; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; }
.machine-power:not(:disabled):hover { border-color: rgba(255, 255, 255, .8); color: #fff; }
.machine-power:not(:disabled):active { transform: scale(.85); }
.machine-power:disabled { cursor: default; }
.is-awake .machine-power { border-color: var(--bs-success); background: var(--bs-success); color: #fff; box-shadow: 0 0 .4rem rgba(var(--bs-success-rgb), .6); }
/* right after a click: a spinning ring in the color of the wanted state, until the machine got there */
.is-pending .machine-power { background: transparent; box-shadow: none; animation: none; border-color: rgba(255, 255, 255, .25); color: #fff; }
.is-pending .machine-power::after { content: ""; position: absolute; inset: -2px; border-radius: 50%; border: 2px solid transparent; border-top-color: rgba(255, 255, 255, .9); animation: machine-spin .8s linear infinite; }
.is-pending.wants-on .machine-power::after { border-top-color: var(--bs-success); border-right-color: var(--bs-success); }
.is-pending .machine-state { color: #fff; font-style: italic; }
@keyframes machine-spin { to { transform: rotate(360deg); } }
.machine-info { min-width: 0; flex: 1; line-height: 1.25; }
.machine-state { font-size: .8125rem; font-weight: 600; color: rgba(255, 255, 255, .9); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.is-offline .machine-state { color: rgba(255, 255, 255, .55); font-weight: 400; }
.machine-values { display: flex; gap: .75rem; font-size: .8125rem; color: rgba(255, 255, 255, .7); white-space: nowrap; }
.machine-values i { margin-right: .2rem; opacity: .8; }
.machine-water { height: 3px; margin-top: .3rem; border-radius: 2px; background: rgba(255, 255, 255, .12); overflow: hidden; }
.machine-water > div { height: 100%; border-radius: 2px; transition: width .4s; }
/* the narrow menu of a collapsed sidebar: only the button */
.sidebar-collapse .app-sidebar:not(:hover) .machine-info { display: none; }
.sidebar-collapse .app-sidebar:not(:hover) .machine-box { padding: .25rem; background: none; justify-content: center; }
/* HomeKit page */
.homekit-qr svg { width: 180px; height: 180px; display: block; }
.homekit-code { font-size: 1.75rem; letter-spacing: .08em; }
.machine-heat { height: 3px; margin-top: .3rem; border-radius: 2px; background: rgba(255, 255, 255, .12); overflow: hidden; }
.machine-heat > div { height: 100%; border-radius: 2px; background: var(--bs-orange); transition: width .8s; }
.is-heating:not(.is-pending) .machine-power { border-color: var(--bs-orange); background: var(--bs-orange); box-shadow: 0 0 .4rem rgba(253, 126, 20, .7); animation: machine-glow 1.6s ease-in-out infinite; }
.is-heating .machine-state { color: var(--bs-orange); }
@keyframes machine-glow { 50% { box-shadow: 0 0 .2rem rgba(253, 126, 20, .3); } }

/* verdict of the shot analysis: a compact colored badge in the shot table */
.analysis-badge { display: inline-flex; align-items: center; gap: .25rem; padding: 0 .45rem 0 .3rem; border-radius: 999px; font-size: .78rem; line-height: 1.35rem; font-weight: 600; border: 1px solid transparent; cursor: help; white-space: nowrap; }
.analysis-badge i { font-size: .8rem; }
.analysis-success { color: var(--bs-success-text-emphasis); background: var(--bs-success-bg-subtle); border-color: var(--bs-success-border-subtle); }
.analysis-warning { color: var(--bs-warning-text-emphasis); background: var(--bs-warning-bg-subtle); border-color: var(--bs-warning-border-subtle); }
.analysis-danger { color: var(--bs-danger-text-emphasis); background: var(--bs-danger-bg-subtle); border-color: var(--bs-danger-border-subtle); }
.analysis-secondary { color: var(--bs-secondary-color); background: var(--bs-tertiary-bg); }
.analysis-guess { background: transparent; border-style: dashed; font-weight: 500; }
/* the analysis on the shot page: titles like the values of the details, texts like their labels */
.analysis-title { font-size: 1rem; font-weight: 600; }
.analysis-text { font-size: .875rem; }
.analysis-verdict { display: flex; gap: .75rem; align-items: flex-start; }
.analysis-verdict > i { font-size: 1.75rem; line-height: 1; }
.analysis-finding { display: flex; gap: .6rem; }
.analysis-finding > i { font-size: 1.05rem; margin-top: .1rem; }
.confidence-badge { font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; padding: .1rem .4rem; border-radius: 999px; border: 1px solid var(--bs-border-color); color: var(--bs-secondary-color); white-space: nowrap; }
.confidence-certain { border-color: var(--bs-success-border-subtle); color: var(--bs-success-text-emphasis); }
.confidence-likely { border-color: var(--bs-primary-border-subtle); color: var(--bs-primary-text-emphasis); }
.confidence-guess { border-style: dashed; }
