Files
itflow/css/itflow_custom.css

3914 lines
120 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
For screens below 576px (xs):
- Make the button full-width, display:block
*/
@media (max-width: 575.98px) {
.btn-responsive {
display: block;
width: 100%;
}
}
/*
For screens 576px (sm) and above:
- Revert to an inline style
*/
@media (min-width: 576px) {
.btn-responsive {
display: inline-block;
width: auto;
}
}
.drag-handle {
cursor: grab !important;
}
.drag-handle:active {
cursor: grabbing !important;
}
.star-toggle input {
display: none;
}
.star-toggle i {
cursor: pointer;
font-size: 1.2rem;
color: #adb5bd;
}
.star-toggle input:checked + i {
color: #f1c40f;
font-weight: 900;
}
.checkbox-column {
width: 40px;
min-width: 40px;
text-align: center;
}
.checkbox-column .form-check {
padding-left: 0;
margin-bottom: 0;
}
.checkbox-column .form-check-input {
/* BS5 uses float:left here where BS4 used position:absolute - without
float:none the box ignores the cell's text-align and pins to the left */
float: none;
position: static;
margin: 0;
vertical-align: middle;
}
/* ---------------------------------------------------------------
AdminLTE 4 compatibility.
v4 dropped .text-sm and the accent-* / navbar-* / sidebar-dark-*
trio that ITFlow's 16 theme choices were built on. In v3 the theme
colour drove the TOP NAVBAR, link colours and the active sidebar
item - the sidebar itself was always dark. That is reproduced here.
--------------------------------------------------------------- */
/* Font stack. v3 led with "Source Sans Pro" then -apple-system; v4/BS5 leads
with "Source Sans 3" then system-ui. ITFlow loads neither webfont, so the
two stacks fall through to different system fonts. Pinned to v3's. */
:root {
--bs-font-sans-serif: "Source Sans Pro", -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
/* The full .text-sm cascade. AdminLTE 3 shipped ~100 rules for this; these are
the ones that actually set sizing, and without them the app renders a step
larger everywhere than it used to. */
.text-sm {
font-size: .875rem;
}
.text-sm .form-control,
.text-sm .form-select,
.text-sm .input-group-text,
.text-sm table {
font-size: .875rem;
}
.text-sm .btn,
.text-sm .dropdown-menu {
font-size: .875rem !important;
}
.text-sm .card-title {
font-size: 1rem;
}
.text-sm .content-header h1,
.text-sm .app-content-header h1 {
font-size: 1.5rem;
}
.text-sm .brand-link {
font-size: inherit;
}
/* Sidebar density. v3 tightened nav-link padding under .text-sm to .4rem .8rem;
BS5's default is .5rem 1rem, which is what made the menu items taller. */
.text-sm .nav-link {
padding: .4rem .8rem;
}
/* v3 nav-icon metrics - v4 switched these to a 1.5rem flex box */
.sidebar-menu .nav-icon {
min-width: 1.6rem;
max-width: 1.6rem;
margin-left: .05rem;
margin-right: .2rem;
font-size: 1.2rem;
}
/* one accent per theme - v3 used the same hex for navbar, links and
the active sidebar item */
/* black has no upstream token - AdminLTE calls its darkest neutral
gray-dark (#343a40), which is a different colour */
.theme-black { --itflow-accent: #000000; }
.theme-blue { --itflow-accent: var(--bs-blue, #007bff); }
.theme-cyan { --itflow-accent: var(--bs-cyan, #17a2b8); }
.theme-fuchsia { --itflow-accent: var(--bs-fuchsia, #f012be); }
.theme-gray { --itflow-accent: var(--bs-gray, #6c757d); }
.theme-green { --itflow-accent: var(--bs-green, #28a745); }
.theme-indigo { --itflow-accent: var(--bs-indigo, #6610f2); }
.theme-lightblue { --itflow-accent: var(--bs-lightblue, #3c8dbc); }
.theme-maroon { --itflow-accent: var(--bs-maroon, #d81b60); }
.theme-navy { --itflow-accent: var(--bs-navy, #001f3f); }
.theme-olive { --itflow-accent: var(--bs-olive, #3d9970); }
.theme-orange { --itflow-accent: var(--bs-orange, #fd7e14); }
.theme-pink { --itflow-accent: var(--bs-pink, #e83e8c); }
.theme-purple { --itflow-accent: var(--bs-purple, #6f42c1); }
.theme-red { --itflow-accent: var(--bs-red, #dc3545); }
.theme-teal { --itflow-accent: var(--bs-teal, #20c997); }
.theme-yellow { --itflow-accent: var(--bs-yellow, #ffc107); }
/* top navbar takes the theme colour */
.app-header.navbar {
background-color: var(--itflow-accent, #007bff) !important;
}
/* Navbar height. v4 pairs .app-header{max-height:2.75rem} with 0.5rem of
navbar padding, but the tallest children want 47-48px and overflow that
44px cap. Everything below is sized so it fits inside a 3rem bar - the
.navbar-search field is ~33px and clears it without help.
NOTE: .sidebar-brand is the matching half of this pair and is set to the
same height further down - change both or they stop lining up. */
.app-header.navbar {
--bs-navbar-padding-y: .4rem;
min-height: 3rem;
max-height: 3rem;
}
/* Every item stretches the full bar and centres its own contents, so icons,
the avatar and the search box all sit on the same optical line regardless of
their individual heights. */
.app-header .navbar-nav {
align-items: stretch;
}
.app-header .navbar-nav > .nav-item {
display: flex;
align-items: center;
}
.app-header .navbar-nav .nav-link {
display: flex;
align-items: center;
height: 100%;
max-height: none;
padding-top: 0;
padding-bottom: 0;
}
/* v4 sizes the avatar 2rem with a -2px nudge, which is too tall for this bar */
.app-header .navbar-nav > .user-menu .user-image {
width: 1.6rem;
height: 1.6rem;
margin-top: 0;
}
/* .navbar-badge positions itself (top:9px/right:5px) for a taller bar. Pinned
to the link's edge, it slid over the icon whenever the link got narrower -
on phones the link is 28px wide and the badge covered the bell completely.
The icon is centred in the link, so anchoring to the centre puts the badge
on the icon's top-right corner at every width. */
.app-header .navbar-badge {
top: 50%;
right: auto;
left: calc(50% + .2rem);
margin-top: -.9rem;
font-size: .65rem;
}
/* sidebar stays dark; only the ACTIVE item picks up the theme.
The :hover and :focus variants are load-bearing, not decoration. A bare
`.nav-link.active` scores exactly the same specificity as the grey hover
rule further down this file (both 0,5,0), and that rule comes later - so
pointing at the current page's menu item repainted its accent grey.
AdminLTE 3 won this same tie on source order alone: its per-theme
`.sidebar-dark-<colour> ... .nav-link.active` rule was emitted AFTER the
`[class*=sidebar-dark-] ... .nav-item:hover > .nav-link` block, so the
accent held on hover. Spelling the states out here wins on specificity
instead, which survives any future reordering of this sheet.
v3 also pinned its treeview active item the same way
(`.nav-link.active, .nav-link.active:hover, .nav-link.active:focus`), so
both levels are covered. Deliberately NO separate hover shade for the
active item: v3 had none, and the current item is already the loudest
thing in the sidebar. */
.app-sidebar .sidebar-menu > .nav-item > .nav-link.active,
.app-sidebar .sidebar-menu > .nav-item:hover > .nav-link.active,
.app-sidebar .sidebar-menu > .nav-item > .nav-link.active:focus,
.app-sidebar .nav-treeview > .nav-item > .nav-link.active,
.app-sidebar .nav-treeview > .nav-item > .nav-link.active:hover,
.app-sidebar .nav-treeview > .nav-item > .nav-link.active:focus {
background-color: var(--itflow-accent, #007bff);
color: #fff;
}
/* Bootstrap 5's reboot underlines every link; Bootstrap 4 did not, and
AdminLTE 3 additionally suppressed the hover underline. Restored so links
read the way they did before the migration. */
a,
a:hover,
a:focus {
text-decoration: none;
}
/* links and accented controls */
/* Tabs and pills. Bootstrap points inactive nav links at --bs-link-color and
hardcodes --bs-nav-pills-link-active-bg to #0d6efd, so both states came out
blue regardless of theme. Scoped to .nav-tabs/.nav-pills rather than .nav:
.navbar-nav redefines these same variables for the top bar, and the sidebar
is protected by its own higher-specificity rules. */
.nav-tabs,
.nav-pills {
/* inactive sits at 70% so the active state still reads as the highlight */
--bs-nav-link-color: color-mix(in srgb, var(--itflow-accent, #007bff) 70%, transparent);
--bs-nav-link-hover-color: var(--itflow-accent, #007bff);
}
.nav-tabs {
--bs-nav-tabs-link-active-color: var(--itflow-accent, #007bff);
/* accent rule along the top edge marks the selected tab */
--bs-nav-tabs-link-active-border-color: var(--itflow-accent, #007bff) var(--bs-border-color) var(--bs-body-bg);
}
.nav-tabs .nav-link.active {
font-weight: 600;
}
/* Pagination. Driving Bootstrap's own variables rather than overriding
.page-link directly: a flat `.page-item .page-link{color:...}` ties with
BS5's `.active>.page-link` and `.disabled>.page-link` on specificity and,
because this sheet loads last, silently won both - so the active number was
accent-on-accent (invisible) and disabled Prev/Next lost their muted grey. */
/* Bootstrap's generic focus ring (buttons, links, .btn-check labels) */
.form-check-input:checked {
background-color: var(--itflow-accent, #007bff);
border-color: var(--itflow-accent, #007bff);
}
/* sidebar is ALWAYS dark - v4 gives .app-sidebar no background at all.
These are AdminLTE 3.2.0's [class*=sidebar-dark-] values verbatim. */
.app-sidebar {
background-color: #343a40;
}
/* Sidebar header. v4 scopes its brand rules under .sidebar-brand, and its own
defaults centre the brand in a fixed 2.75rem box. v3's .brand-link was a
left-aligned block, and several of these headers carry a "Back | ..." line
that is taller than that, so height is relaxed and alignment restored. */
.app-sidebar .sidebar-brand {
/* v4 deliberately pairs .sidebar-brand{height:2.75rem} with
.app-header{max-height:2.75rem} so the sidebar header lines up with the
top bar. Both are set to 3rem above - change them together or
they stop lining up. v4's .8125rem padding leaves too little room for
the heading, so it is cut right down. */
height: 3rem;
padding: .3rem .5rem;
justify-content: flex-start;
background-color: #343a40;
border-bottom: 1px solid #4b545c;
}
.app-sidebar .sidebar-brand .brand-link {
width: 100%;
color: #fff;
}
.app-sidebar .sidebar-brand .brand-text {
color: #fff;
line-height: 1.2;
}
/* Bootstrap sizes .h1-.h4 responsively (e.g. .h4 is calc(1.275rem + .3vw)), so
on a wide screen they outgrow the bar and v4's .sidebar-brand{overflow:hidden}
clips them. Cap ONLY those - .h5 (1.25rem) and .h6 (1rem) are fixed and must
keep their own size, or the "Back | ..." headers get blown up to h4. */
.app-sidebar .sidebar-brand .brand-text.h1,
.app-sidebar .sidebar-brand .brand-text.h2,
.app-sidebar .sidebar-brand .brand-text.h3,
.app-sidebar .sidebar-brand .brand-text.h4,
.app-sidebar .sidebar-brand h1.brand-text,
.app-sidebar .sidebar-brand h2.brand-text,
.app-sidebar .sidebar-brand h3.brand-text,
.app-sidebar .sidebar-brand h4.brand-text {
font-size: min(1.6rem, calc(1.275rem + .3vw));
}
/* Company brand - agent/includes/side_nav.php (main nav only; the client,
admin and reports navs keep their "Back | ..." headers above).
The logo or initials tile with the company name beside it, the whole row one
rounded hover target like a workspace switcher. Sized to sit inside the 3rem
header that lines up with the top bar. */
.app-sidebar .itflow-brand-link {
gap: .6rem;
height: 100%;
padding: .2rem .4rem;
border-radius: var(--bs-border-radius);
color: #fff;
transition: background-color .15s ease-in-out;
}
.app-sidebar .itflow-brand-link:hover {
background-color: rgba(255, 255, 255, .07);
color: #fff;
}
.app-sidebar .itflow-brand-link:focus-visible {
outline: 2px solid var(--itflow-accent, var(--bs-primary));
outline-offset: -2px;
}
.itflow-brand-mark {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
height: 2rem;
border-radius: var(--bs-border-radius);
overflow: hidden;
}
/* Logos are drawn for a light page, so they get a light tile. A wide logo
widens the tile (up to a point) instead of shrinking to fit a square. */
.itflow-brand-mark-logo {
min-width: 2rem;
max-width: 5.5rem;
padding: .2rem .3rem;
background-color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, .35);
}
.itflow-brand-mark-logo img {
display: block;
width: auto;
height: 100%;
max-width: 100%;
object-fit: contain;
}
/* No logo: the company's initials on the theme colour. The inner ring keeps
the tile visible when the theme colour is close to the sidebar's (black,
navy, gray). */
.itflow-brand-mark-initials {
width: 2rem;
font-size: .7rem;
font-weight: 700;
letter-spacing: .03em;
color: #fff;
background-color: var(--itflow-accent, #007bff);
background-image: linear-gradient(135deg, rgba(255, 255, 255, .18), rgba(0, 0, 0, .18));
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .18), 0 1px 2px rgba(0, 0, 0, .35);
}
.theme-yellow .itflow-brand-mark-initials {
color: #1f2d3d;
}
/* Two lines, then an ellipsis. v4's .sidebar-brand is nowrap, so wrapping
has to be switched back on here. */
.itflow-brand-name {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
min-width: 0;
overflow: hidden;
font-size: .95rem;
font-weight: 600;
line-height: 1.15;
letter-spacing: -.01em;
white-space: normal;
overflow-wrap: anywhere;
}
.app-sidebar .sidebar-menu .nav-link,
.app-sidebar .nav-header {
color: #c2c7d0;
}
.app-sidebar .sidebar-menu > .nav-item > .nav-link:focus,
.app-sidebar .sidebar-menu > .nav-item:hover > .nav-link,
.app-sidebar .sidebar-menu > .nav-item.menu-open > .nav-link {
background-color: rgba(255, 255, 255, .1);
color: #fff;
}
.app-sidebar .sidebar-menu > .nav-item > .nav-link.active {
color: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);
}
.app-sidebar .nav-treeview {
background-color: transparent;
}
/* --- Bootstrap 5 unpaired background utilities -------------------------
BS4 + AdminLTE 3 shipped `.bg-dark,.card.bg-dark .card-body{color:#fff}`.
BS5 split the pair: .bg-* sets ONLY a background and you are expected to
use .text-bg-* for the pairing. ITFlow uses bg-dark 303 times (every modal
header, most card headers), so without this the header text is dark on
dark and effectively invisible. Contrast choices below mirror Bootstrap's
own .text-bg-* decisions. */
.bg-dark, .bg-primary, .bg-secondary, .bg-success, .bg-danger, .bg-purple {
color: #fff;
}
.bg-info, .bg-warning, .bg-light, .bg-olive, .bg-pink {
color: #000;
}
.card.bg-dark .card-body {
color: #fff;
}
/* keep close buttons visible on the dark headers they sit in */
.bg-dark > .btn-close, .modal-header.bg-dark .btn-close {
filter: var(--bs-btn-close-white-filter, invert(1) grayscale(100%) brightness(200%));
}
/* --- dashboard small-box ------------------------------------------------
AdminLTE 4 dropped v3's watermark icon. 4.8.5 restored it, but under a new
class name (.small-box-icon) that ITFlow's markup does not use - the boxes
still carry v3's <div class="icon"> wrapper - so these rules stay.
The inner padding and the p > small rule ARE back upstream at identical
values and have been dropped from here. */
.small-box .icon {
color: rgba(0, 0, 0, .15);
z-index: 0;
}
.small-box .icon > i,
.small-box .icon > svg {
position: absolute;
top: 15px;
right: 15px;
font-size: 90px;
transition: transform .3s linear;
}
.small-box:hover .icon > i,
.small-box:hover .icon > svg {
transform: scale(1.1);
}
/* Upstream sets z-index:5 on these but no `position`, which leaves it inert -
the icon is absolutely positioned at z-index 0, so the text needs a
stacking context of its own to stay above it. */
.small-box h3,
.small-box .h3,
.small-box p {
position: relative;
}
/* 4.8.5 sets this same 1rem, but also shrinks it to 12px below 576px. This
file loads last at equal specificity, so keeping the line deliberately
holds the desktop size on phones too; delete it to take their mobile
treatment instead. */
.small-box p {
font-size: 1rem;
}
.small-box hr {
opacity: .25;
}
.small-box > .small-box-footer {
color: rgba(255, 255, 255, .8);
}
.small-box > .small-box-footer:hover {
color: #fff;
}
/* --- utility classes AdminLTE 4 dropped -------------------------------- */
.text-bold, .text-bold.table td, .text-bold.table th {
font-weight: 700;
}
.text-xs {
font-size: .75rem !important;
}
.btn-default {
color: #6c757d;
background-color: #f8f9fa;
border-color: #ced4da;
}
.btn-default:hover {
color: #495057;
background-color: #e2e6ea;
border-color: #adb5bd;
}
.btn-flat {
border-radius: 0;
border-width: 1px;
box-shadow: none;
}
/* sidebar count badges - v3 positioned these absolutely */
.sidebar-menu .nav-link > .right,
.sidebar-menu .nav-link > p > .right {
position: absolute;
right: 1rem;
top: .7rem;
}
/* v3's softened box colours, kept for the three small-boxes that still use
them on the dashboard. The other fourteen were dropped - nothing in the
tree carries those classes, and the only class names PHP interpolates are
Bootstrap's seven contextual ones, so they were unreachable. adminlte-
colors-v3 defines all of them anyway if a page ever wants one back. */
.bg-olive { background-color: rgba(116,200,163,.9) !important; }
.bg-pink { background-color: rgba(232,62,140,.9) !important; }
.bg-purple { background-color: rgba(111,66,193,.9) !important; }
/* --- Flatpickr date-filter preset panel ---------------------------------
Flatpickr has no preset-ranges feature; js/date_filter.js injects this
panel to reproduce the nine ranges daterangepicker used to provide. */
/* The panel sits in a second column to the right of the month grid. This has to
be a GRID rather than flex: .flatpickr-calendar's own children are the month
nav and the day grid STACKED, so flexing the container would put those two
side by side as well. Two explicit columns keep flatpickr's own stacking in
column 1 and give the panel column 2, spanning both rows.
width:auto is safe in this layout but was NOT when the panel ran along the
bottom. The calendar is position:absolute, so auto means shrink-to-fit, which
resolves to max-content - and max-content for a horizontal row of nine nowrap
buttons is one long line that flex-wrap never breaks, while .flatpickr-days
keeps its own fixed 307.875px. Everything past the month grid then rendered as
an empty second pane. Stacked in a column the panel's max-content is just the
widest single label, so the container comes out calendar + panel wide.
The `> *` rule catches anything flatpickr adds later - an unplaced child would
otherwise auto-flow into column 2 and land on top of the panel. */
.flatpickr-calendar.itflow-fp-with-ranges.open {
display: grid;
grid-template-columns: max-content max-content;
grid-template-rows: auto auto;
align-items: start;
width: auto;
}
.flatpickr-calendar.itflow-fp-with-ranges > * {
grid-column: 1;
}
.flatpickr-calendar.itflow-fp-with-ranges > .itflow-fp-ranges {
grid-column: 2;
grid-row: 1 / span 2;
/* stretch so the divider runs the calendar's full height, not just the
height of the nine buttons */
align-self: stretch;
}
.itflow-fp-ranges {
display: flex;
flex-direction: column;
gap: .125rem;
padding: .5rem;
border-left: 1px solid var(--bs-border-color, #dee2e6);
}
.itflow-fp-range {
padding: .2rem .5rem;
font-size: .8125rem;
/* .flatpickr-calendar sets line-height:24px and it inherits. Nine buttons at
that height overshoot the calendar's own ~296px; 1.4 brings the column back
under it so the two sides finish level. */
line-height: 1.4;
text-align: left;
color: var(--itflow-accent, #007bff);
white-space: nowrap;
background: none;
border: 1px solid transparent;
border-radius: .25rem;
}
.itflow-fp-range:hover,
.itflow-fp-range:focus,
.itflow-fp-range.active {
color: #fff;
background-color: var(--itflow-accent, #007bff);
border-color: var(--itflow-accent, #007bff);
}
/* The preset currently in effect - without this there is nothing on screen
telling you which range you are looking at once the calendar is open. */
.itflow-fp-range.active {
font-weight: 600;
}
/* --- Flash toast ---------------------------------------------------------
Restores the four things toastr did that a plain Bootstrap toast does not:
sits top-right rather than centred over what you are reading, carries an
icon for its type, counts its own timer down, and dismisses on a click
anywhere instead of only on the small x.
It also appears on the FIRST PAINT. includes/inc_alert_feedback.php renders
the element with .show already set and every state change here is an
animation, so nothing waits on bootstrap.Toast - which used to mean waiting
for DOMContentLoaded, and therefore for every parser-blocking script in the
document. The inline script in that file only handles the click and removes
the node; neither gates the appearance.
Durations are toastr's own: 300ms in, 5s on screen, 1s out. */
.itflow-toast {
position: relative;
overflow: hidden;
cursor: pointer;
animation:
itflow-toast-in 300ms ease-out both,
itflow-toast-out 1s ease-in 5s both;
}
/* Hovering holds it open, so a long message cannot disappear mid-sentence.
The progress bar needs its own rule - animation-play-state does not
cascade to a child's separate animation. */
.itflow-toast:hover,
.itflow-toast:hover .itflow-toast-progress {
animation-play-state: paused;
}
/* currentColor, so the bar inherits whichever contrast text-bg-* already
picked - white on success and danger, dark on warning and info - rather
than needing a rule per type. */
.itflow-toast-progress {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 3px;
background: currentColor;
opacity: .4;
transform-origin: left;
animation: itflow-toast-progress 5s linear both;
}
@keyframes itflow-toast-in {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* visibility flips on the last one percent rather than at the end, because a
discrete property switches at the MIDPOINT of its keyframe interval - put
it only on the 100% stop and the toast would vanish halfway through its own
fade. This is the fallback that matters if the inline remover never runs:
without it an invisible toast keeps sitting over the user menu, eating
clicks. */
@keyframes itflow-toast-out {
0% {
opacity: 1;
visibility: visible;
}
99% {
opacity: 0;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
@keyframes itflow-toast-progress {
from {
transform: scaleX(1);
}
to {
transform: scaleX(0);
}
}
/* Reduced motion drops the slide and the moving bar but keeps the timed
dismissal - that is a timeout, not an animation the user is watching. */
@media (prefers-reduced-motion: reduce) {
.itflow-toast {
animation: itflow-toast-out 1s linear 5s both;
}
.itflow-toast-progress {
animation: none;
transform: scaleX(0);
}
}
/* The JS-raised toasts from itflowToast() still go through bootstrap.Toast,
so they keep the slower fade. */
.itflow-toast-js .toast.fade {
transition: opacity .5s linear;
}
/* --- Filter footer -------------------------------------------------------
The pagination row wants a touch more breathing room than .py-2 gives, but
.py-3 is a 16px jump that makes the bar noticeably chunky. Bootstrap has no
step between the two, hence the named rule. */
.card-footer.itflow-filter-footer {
padding-top: .5rem;
padding-bottom: .5rem;
}
/* --- Primary button follows the theme -----------------------------------
Bootstrap 5 buttons are entirely variable driven, so these set --bs-btn-*
rather than overriding background-color. That keeps hover, active,
disabled and the focus ring consistent instead of only recolouring the
resting state.
The shade steps are Bootstrap's own: hover is shade 15%, active shade 20%,
and shade(c, n%) is literally mix(black, c, n%) - which color-mix
reproduces exactly. */
/* Focus ring has to be an RGB triplet, so it is per theme. */
/* Text colour on a solid primary button. Threshold derived from Bootstrap's
own palette (white up to L=0.183, black from L=0.486), so only these two
themes are light enough to need dark text - the rest keep white. */
/* Same L>0.335 contrast cut as the primary button. */
.theme-orange .nav-pills,
.theme-teal .nav-pills,
.theme-yellow .nav-pills {
--bs-nav-pills-link-active-color: #000;
}
/* --- Theme picker (admin/settings_theme.php) -----------------------------
Each choice is a miniature of the app rather than a colour circle, so you
can see where the colour actually lands before committing to it. Structure
and proportions are lifted from AdminLTE 4's own theme generator
(dist/generate/theme.html) - note those .lte-skin-preview-* rules live in
that page's inline <style> and are NOT in adminlte.min.css, so they are
reproduced here under ITFlow-owned names to avoid colliding with anything
upstream may ship later.
The mini-app reflects how ITFlow actually applies a theme, not AdminLTE's
presets: the sidebar is always dark with the accent on its active item, the
top bar takes the accent, and the content area shows an accent widget
beside a neutral one.
The preview carries .theme-<name>, so --itflow-accent is re-scoped for that
subtree and every swatch paints itself from the same variable the theme
will apply - add a theme to the stylesheet and its preview is correct with
no extra rule.
The radio is a visually hidden .btn-check and the label is the control.
Selection reads as a ring around the preview plus a bolder name. */
.itflow-theme-swatch {
display: block;
padding: .5rem .25rem;
text-align: center;
cursor: pointer;
border-radius: .5rem;
transition: background-color .15s ease;
}
.itflow-theme-swatch:hover {
background-color: var(--bs-secondary-bg);
}
.itflow-theme-preview {
display: inline-flex;
width: 6rem;
height: 3.75rem;
overflow: hidden;
vertical-align: middle;
background-color: var(--bs-tertiary-bg);
border: 1px solid var(--bs-border-color);
border-radius: var(--bs-border-radius);
transition: border-color .15s ease, box-shadow .15s ease;
}
/* the sidebar is always dark in ITFlow, whatever the theme */
.itflow-theme-preview-sidebar {
display: flex;
flex: 0 0 30%;
flex-direction: column;
gap: 9%;
padding: 10% 12%;
color: #fff;
background-color: #343a40;
}
.itflow-theme-preview-brand,
.itflow-theme-preview-item {
display: block;
flex: none;
height: 7%;
min-height: 2px;
background: currentColor;
border-radius: 99px;
opacity: .45;
}
.itflow-theme-preview-brand {
width: 70%;
height: 11%;
min-height: 3px;
margin-bottom: 6%;
opacity: .9;
}
/* only the active sidebar item takes the accent - same as the real sidebar */
.itflow-theme-preview-active {
background: var(--itflow-accent, #007bff);
opacity: 1;
}
.itflow-theme-preview-main {
display: flex;
flex: auto;
flex-direction: column;
min-width: 0;
}
.itflow-theme-preview-header {
display: flex;
flex: 0 0 24%;
gap: 6%;
align-items: center;
padding: 0 8%;
color: #fff;
background-color: var(--itflow-accent, #007bff);
}
.itflow-theme-preview-header .itflow-theme-preview-item {
width: 24%;
height: 22%;
opacity: .55;
}
.itflow-theme-preview-avatar {
display: block;
width: 14%;
margin-left: auto;
aspect-ratio: 1;
background: currentColor;
border-radius: 50%;
opacity: .55;
}
.itflow-theme-preview-body {
display: grid;
flex: auto;
grid-template-rows: 1fr 1fr;
grid-template-columns: 1fr 1fr;
gap: 8%;
padding: 10%;
}
.itflow-theme-preview-box {
display: block;
background-color: var(--itflow-accent, #007bff);
border-radius: 2px;
}
.itflow-theme-preview-box-muted {
background-color: var(--bs-secondary-bg);
}
.itflow-theme-preview-card {
display: block;
grid-column: 1 / -1;
background-color: var(--bs-body-bg);
border: 1px solid var(--bs-border-color);
border-radius: 2px;
}
.itflow-theme-name {
display: block;
margin-top: .4rem;
font-size: .8125rem;
text-transform: capitalize;
}
.btn-check:checked + .itflow-theme-swatch .itflow-theme-preview {
border-color: var(--bs-body-color);
box-shadow: 0 0 0 1px var(--bs-body-color);
}
.btn-check:checked + .itflow-theme-swatch .itflow-theme-name {
font-weight: 600;
}
.btn-check:focus-visible + .itflow-theme-swatch {
outline: 2px solid var(--itflow-accent, #007bff);
outline-offset: 2px;
}
/* --- Dark mode ------------------------------------------------------------
Bootstrap treats .bg-light / .text-dark / .bg-white as ABSOLUTE colours, so
none of them flip under [data-bs-theme=dark] - 69 bg-light and 425 text-dark
usages stayed bright. Overriding the utilities rather than the underlying
--bs-light-rgb / --bs-dark-rgb variables, because those same two variables
also drive .text-light and .bg-dark, which must NOT flip. */
[data-bs-theme=dark] .bg-light {
background-color: var(--bs-secondary-bg) !important;
}
[data-bs-theme=dark] .bg-white {
background-color: var(--bs-body-bg) !important;
}
[data-bs-theme=dark] .text-dark {
color: var(--bs-body-color) !important;
}
/* .table-light hardcodes #f8f9fa and #000 outright */
[data-bs-theme=dark] .table-light {
--bs-table-bg: var(--bs-secondary-bg);
--bs-table-color: var(--bs-body-color);
--bs-table-border-color: var(--bs-border-color);
--bs-table-striped-bg: var(--bs-tertiary-bg);
--bs-table-striped-color: var(--bs-body-color);
--bs-table-hover-bg: var(--bs-tertiary-bg);
--bs-table-hover-color: var(--bs-body-color);
}
/* Secondary and light buttons are fixed greys - #6c757d and #f8f9fa - so in
dark mode one goes muddy and the other glares. 260 btn-light and 185
btn-secondary uses, so these matter more than most. */
[data-bs-theme=dark] .btn-secondary {
--bs-btn-color: #dee2e6;
--bs-btn-bg: #3d4349;
--bs-btn-border-color: #495057;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #495057;
--bs-btn-hover-border-color: #565e64;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #565e64;
--bs-btn-active-border-color: #6c757d;
--bs-btn-disabled-color: #adb5bd;
--bs-btn-disabled-bg: #3d4349;
--bs-btn-disabled-border-color: #495057;
}
[data-bs-theme=dark] .btn-light {
--bs-btn-color: #dee2e6;
--bs-btn-bg: #343a40;
--bs-btn-border-color: #454d55;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #454d55;
--bs-btn-hover-border-color: #565e64;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #565e64;
--bs-btn-active-border-color: #6c757d;
--bs-btn-disabled-color: #6c757d;
--bs-btn-disabled-bg: #343a40;
--bs-btn-disabled-border-color: #454d55;
}
/* .btn-dark is #212529 - EXACTLY --bs-body-bg in dark mode, so the button was
the same colour as the page. Its main use is the search button welded to an
input group, so it needs to read as a control next to a .form-control rather
than invert to a bright button. Sits a step above the input, and above the
other two neutrals, mirroring its light-mode role as the strongest of them:
btn-light #343a40 < btn-secondary #3d4349 < btn-dark #495057 */
[data-bs-theme=dark] .btn-dark {
--bs-btn-color: #dee2e6;
--bs-btn-bg: #495057;
--bs-btn-border-color: #545b62;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #545b62;
--bs-btn-hover-border-color: #6c757d;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #6c757d;
--bs-btn-active-border-color: #7d858c;
--bs-btn-disabled-color: #adb5bd;
--bs-btn-disabled-bg: #495057;
--bs-btn-disabled-border-color: #545b62;
}
[data-bs-theme=dark] .btn-outline-dark {
--bs-btn-color: #dee2e6;
--bs-btn-border-color: #6c757d;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #495057;
--bs-btn-hover-border-color: #6c757d;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #545b62;
--bs-btn-active-border-color: #6c757d;
}
[data-bs-theme=dark] .btn-outline-secondary {
--bs-btn-color: #adb5bd;
--bs-btn-border-color: #6c757d;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #6c757d;
--bs-btn-hover-border-color: #6c757d;
}
/* .btn-default is my own AdminLTE 3 shim and hardcodes a light grey, so it
glares in dark mode like the Bootstrap greys above. */
[data-bs-theme=dark] .btn-default {
color: #dee2e6;
background-color: #343a40;
border-color: #454d55;
}
[data-bs-theme=dark] .btn-default:hover {
color: #fff;
background-color: #454d55;
border-color: #565e64;
}
/* --- TinyMCE chrome ------------------------------------------------------
TinyMCE's oxide skins ship their own palette - a blue-slate #222f3e with
#161f29 borders in dark - which sits next to the app's neutral #212529 and
#495057 and reads as a mismatch rather than a choice. The toolbar and
frame live in the main document (only the editing area is iframed), so
they can be driven from the same --bs-* variables as every other control,
which also means light and dark are handled by one set of rules.
The frame is matched to .form-control: same border, radius and focus ring. */
.tox.tox-tinymce {
border: var(--bs-border-width) solid var(--bs-border-color);
border-radius: var(--bs-border-radius);
}
.tox .tox-editor-header,
.tox .tox-toolbar,
.tox .tox-toolbar__primary,
.tox .tox-toolbar-overlord,
.tox .tox-menubar,
.tox .tox-statusbar {
background-color: var(--bs-tertiary-bg) !important;
border-color: var(--bs-border-color) !important;
}
.tox .tox-editor-header {
box-shadow: none;
}
.tox .tox-statusbar {
border-top: var(--bs-border-width) solid var(--bs-border-color);
color: var(--bs-secondary-color);
}
.tox .tox-edit-area__iframe {
background-color: var(--bs-body-bg) !important;
}
/* Toolbar buttons.
The skin gives every .tox-tbtn its own background:#222f3e, which sits as a
visible blue-slate box on the retinted toolbar - they need to be transparent
so the toolbar surface shows through, the way oxide looks in light mode.
Colour is set via `color`, never `fill`: the skin already declares
`.tox .tox-tbtn svg{fill:currentColor!important}`, so setting fill directly
fights its own model and, worse, overrides the forecolor swatch that is
supposed to show the currently selected text colour. */
.tox .tox-tbtn,
.tox .tox-mbtn,
.tox .tox-split-button {
background: transparent !important;
color: var(--bs-body-color) !important;
}
.tox .tox-tbtn:hover,
.tox .tox-mbtn:hover,
.tox .tox-tbtn:focus,
.tox .tox-split-button:hover {
background: var(--bs-secondary-bg) !important;
color: var(--bs-emphasis-color) !important;
}
.tox .tox-tbtn--enabled,
.tox .tox-tbtn--enabled:hover,
.tox .tox-tbtn.tox-tbtn--active {
background: var(--itflow-accent, #007bff) !important;
color: #fff !important;
}
/* light accents need a dark glyph - same L>0.335 cut as the buttons */
.theme-yellow .tox .tox-tbtn--enabled,
.theme-orange .tox .tox-tbtn--enabled,
.theme-teal .tox .tox-tbtn--enabled,
.theme-yellow .tox .tox-tbtn.tox-tbtn--active,
.theme-orange .tox .tox-tbtn.tox-tbtn--active,
.theme-teal .tox .tox-tbtn.tox-tbtn--active {
color: #000 !important;
}
.theme-yellow .tox .tox-mbtn--active,
.theme-orange .tox .tox-mbtn--active,
.theme-teal .tox .tox-mbtn--active {
color: #000 !important;
}
.theme-yellow .tox .tox-collection__item--active,
.theme-orange .tox .tox-collection__item--active,
.theme-teal .tox .tox-collection__item--active,
.theme-yellow .tox .tox-collection__item--enabled,
.theme-orange .tox .tox-collection__item--enabled,
.theme-teal .tox .tox-collection__item--enabled {
color: #000 !important;
}
/* Form controls that live IN the toolbar - the Styles listbox is the obvious
one - keep their own #2b3b4e fill and #161f29 border. Matched to .form-select
/ .form-control instead so they read as the same kind of control as the rest
of the app. */
.tox .tox-listboxfield .tox-listbox--select,
.tox .tox-textfield,
.tox .tox-toolbar-textfield,
.tox .tox-textarea {
background-color: var(--bs-body-bg) !important;
border-color: var(--bs-border-color) !important;
border-radius: var(--bs-border-radius) !important;
color: var(--bs-body-color) !important;
}
.tox .tox-listboxfield .tox-listbox--select:focus,
.tox .tox-textfield:focus,
.tox .tox-toolbar-textfield:focus,
.tox .tox-textarea:focus {
border-color: var(--itflow-accent, #007bff) !important;
box-shadow: 0 0 0 .25rem color-mix(in srgb, var(--itflow-accent, #007bff) 25%, transparent) !important;
}
/* the caret and label inside those controls */
.tox .tox-listboxfield .tox-listbox--select,
.tox .tox-tbtn__select-chevron {
color: var(--bs-body-color) !important;
}
.tox .tox-label,
.tox .tox-toolbar-label {
color: var(--bs-secondary-color) !important;
}
/* the Styles button face - 'styles' is in every ITFlow toolbar */
.tox .tox-tbtn--bespoke {
background: var(--bs-body-bg) !important;
border: var(--bs-border-width) solid var(--bs-border-color) !important;
border-radius: var(--bs-border-radius) !important;
}
.tox .tox-tbtn--bespoke:hover {
background: var(--bs-secondary-bg) !important;
}
.tox .tox-tbtn__select-label {
color: var(--bs-body-color) !important;
}
/* menubar active state was a fixed #599fef blue */
.tox .tox-mbtn--active,
.tox .tox-mbtn:not(:disabled).tox-mbtn--active:focus {
background: var(--itflow-accent, #007bff) !important;
color: #fff !important;
}
/* split buttons (forecolor, backcolor) - the focus fill was a fixed #222f3e */
.tox .tox-split-button:hover,
.tox .tox-split-button__main:focus,
.tox .tox-split-button__chevron:focus {
background: var(--bs-secondary-bg) !important;
color: var(--bs-body-color) !important;
}
/* disabled toolbar items */
.tox .tox-tbtn--disabled,
.tox .tox-tbtn:disabled {
color: var(--bs-secondary-color) !important;
}
/* focus ring, so the editor matches every other focused input */
.tox.tox-tinymce.tox-edit-focus,
.tox.tox-tinymce:focus-within {
border-color: var(--itflow-accent, #007bff);
box-shadow: 0 0 0 .25rem color-mix(in srgb, var(--itflow-accent, #007bff) 25%, transparent);
}
/* TinyMCE also draws its own focus box inside the editor - a fixed blue
(#006ce7) border on .tox-edit-area::before - so on any other theme a blue
box sat inside the theme-coloured ring. The ring above is the one every
input gets, so the inner box goes. */
.tox:not(.tox-tinymce-inline) .tox-edit-area::before {
display: none;
}
/* menus and dropdowns the toolbar opens */
.tox .tox-menu,
.tox .tox-collection--list .tox-collection__group {
background-color: var(--bs-body-bg) !important;
border-color: var(--bs-border-color) !important;
}
.tox .tox-collection__item {
color: var(--bs-body-color) !important;
}
.tox .tox-collection__item--active,
.tox .tox-collection__item--enabled {
background-color: var(--itflow-accent, #007bff) !important;
color: #fff !important;
}
/* --- Select picker - js/itflow_select.js ---------------------------------
Every <select class="select2">. The field is a .form-select, so height,
border, chevron, focus ring and dark mode all come from Bootstrap; this
adds what goes inside it and the panel that opens under it.
Before the script runs the browser paints the raw <select>. A single select
looks the same either way, but a multiple one renders as a four-row listbox
(~100px) that would collapse to a one-line field and yank the form up. So
it is held at field height with its text hidden until the picker takes
over (.itflow-select-native). If the script never runs, the delayed reveal
brings the plain control back after 3s so the form stays usable. */
select.select2[multiple]:not(.itflow-select-native) {
height: calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));
overflow-y: auto;
visibility: hidden;
animation: itflowRevealUninitSelect 1ms linear 3s forwards;
}
@keyframes itflowRevealUninitSelect {
to { visibility: visible; }
}
.itflow-select {
position: relative;
display: block;
width: 100%;
min-width: 0;
}
/* The real <select> stays in the form, under the field, for posting and for
the browser's "please select an item" bubble - just not seen or clicked */
.itflow-select > select.itflow-select-native {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
min-height: 0;
padding: 0;
border: 0;
opacity: 0;
pointer-events: none;
}
.itflow-select-control {
display: flex;
align-items: center;
gap: .375rem;
min-height: calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));
cursor: pointer;
user-select: none;
text-align: start;
}
.itflow-select-control:focus,
.itflow-select.is-open > .itflow-select-control {
border-color: var(--itflow-accent, #86b7fe);
outline: 0;
box-shadow: 0 0 0 .25rem color-mix(in srgb, var(--itflow-accent, #0d6efd) 25%, transparent);
}
.itflow-select.is-disabled > .itflow-select-control {
cursor: default;
color: var(--bs-secondary-color);
background-color: var(--bs-secondary-bg);
}
.itflow-select.is-invalid > .itflow-select-control,
.itflow-select.is-invalid-class > .itflow-select-control,
.was-validated .itflow-select:has(> select:invalid) > .itflow-select-control {
border-color: var(--bs-form-invalid-border-color);
}
.itflow-select-value {
display: flex;
align-items: center;
gap: .5rem;
min-width: 0;
flex: 1 1 auto;
}
.itflow-select-value .itflow-select-label {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.itflow-select-placeholder,
.itflow-select-label.is-none {
color: var(--bs-secondary-color);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* × to put the field back to its "nothing" option - only on optional fields */
.itflow-select.is-clearable > .itflow-select-control {
padding-right: 3.5rem;
}
.itflow-select-clear {
position: absolute;
top: 50%;
right: 2.25rem;
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.25rem;
height: 1.25rem;
padding: 0;
border: 0;
border-radius: 50%;
font-size: .7rem;
color: var(--bs-secondary-color);
background: transparent;
transform: translateY(-50%);
opacity: 0;
}
.itflow-select:hover .itflow-select-clear,
.itflow-select-control:focus .itflow-select-clear,
.itflow-select.is-open .itflow-select-clear {
opacity: 1;
}
.itflow-select-clear:hover {
color: var(--bs-body-color);
background: var(--bs-secondary-bg);
}
/* Multiple: chips in the field */
.itflow-select.is-multiple > .itflow-select-control {
flex-wrap: wrap;
gap: .25rem;
padding-top: .25rem;
padding-bottom: .25rem;
padding-left: .375rem;
}
.itflow-select.is-multiple .itflow-select-placeholder {
padding-left: .375rem;
}
.itflow-select-chip {
display: inline-flex;
align-items: center;
gap: .3rem;
max-width: 100%;
height: 1.625rem;
padding: 0 .25rem 0 .55rem;
border: 1px solid var(--bs-border-color-translucent);
border-radius: 2rem;
font-size: .8125rem;
color: var(--bs-body-color);
background: var(--bs-tertiary-bg);
}
.itflow-select-chip.has-lead {
padding-left: .2rem;
}
.itflow-select-chip.is-more {
padding-right: .55rem;
color: var(--bs-secondary-color);
}
.itflow-select-chip-label {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.itflow-select-chip .itflow-select-avatar,
.itflow-select-chip .itflow-select-badge {
width: 1.25rem;
height: 1.25rem;
font-size: .55rem;
}
.itflow-select-chip .itflow-select-dot {
margin: 0 .15rem 0 .3rem;
}
.itflow-select-chip-remove {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 1.125rem;
height: 1.125rem;
padding: 0;
border: 0;
border-radius: 50%;
font-size: .6rem;
color: var(--bs-secondary-color);
background: transparent;
}
.itflow-select-chip-remove:hover {
color: var(--bs-body-color);
background: var(--bs-secondary-bg);
}
/* In an .input-group the picker is the flexible middle piece */
.input-group > .itflow-select {
flex: 1 1 auto;
width: 1%;
}
.input-group > .itflow-select:focus-within,
.input-group > .itflow-select.is-open {
z-index: 5;
}
.input-group > .itflow-select:not(:first-child) > .itflow-select-control {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.input-group > .itflow-select:not(:last-child) > .itflow-select-control {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
/* Pictures, tiles, dots and icons in front of a label */
.itflow-select-avatar {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
font-size: .6rem;
font-weight: 600;
letter-spacing: .02em;
object-fit: cover;
}
.itflow-select-avatar.is-tile {
border-radius: var(--bs-border-radius-sm);
}
.itflow-select-control .itflow-select-avatar,
.itflow-select-control .itflow-select-badge {
width: 1.375rem;
height: 1.375rem;
}
.itflow-select-dot {
flex: 0 0 auto;
width: .625rem;
height: .625rem;
margin: 0 .3125rem;
border-radius: 50%;
background-color: var(--itflow-select-color, var(--bs-secondary));
box-shadow: 0 0 0 1px rgba(0, 0, 0, .08);
}
.itflow-select-badge {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
border-radius: var(--bs-border-radius);
font-size: .65rem;
color: #fff;
background-color: var(--itflow-select-color, var(--bs-secondary));
}
.itflow-select-icon {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 1.25rem;
color: var(--bs-secondary-color);
}
/* The panel */
.itflow-select-panel {
position: fixed;
z-index: 1075;
display: flex;
flex-direction: column;
overflow: hidden;
font-size: .9375rem;
color: var(--bs-body-color);
background: var(--bs-body-bg);
border: 1px solid var(--bs-border-color-translucent);
border-radius: var(--bs-border-radius-lg);
box-shadow: 0 .75rem 2rem rgba(0, 0, 0, .14), 0 .125rem .375rem rgba(0, 0, 0, .06);
animation: itflowSelectIn .12s ease-out;
}
[data-bs-theme="dark"] .itflow-select-panel {
background: var(--bs-tertiary-bg);
box-shadow: 0 .75rem 2rem rgba(0, 0, 0, .5);
}
.itflow-select-panel.is-above {
animation-name: itflowSelectInAbove;
}
@keyframes itflowSelectIn {
from { opacity: 0; transform: translateY(-4px); }
}
@keyframes itflowSelectInAbove {
from { opacity: 0; transform: translateY(4px); }
}
.itflow-select-sheet-head {
display: none;
}
.itflow-select-search {
display: flex;
align-items: center;
gap: .5rem;
padding: .5rem .75rem;
border-bottom: 1px solid var(--bs-border-color-translucent);
color: var(--bs-secondary-color);
font-size: .875rem;
}
.itflow-select-search[hidden] {
display: none;
}
.itflow-select-search input {
flex: 1 1 auto;
min-width: 0;
padding: .125rem 0;
border: 0;
outline: 0;
font-size: .9375rem;
color: var(--bs-body-color);
background: transparent;
}
.itflow-select-search input::placeholder {
color: var(--bs-secondary-color);
}
.itflow-select-list {
max-height: 18rem;
padding: .25rem;
overflow-y: auto;
overscroll-behavior: contain;
}
.itflow-select-option {
display: flex;
align-items: center;
gap: .625rem;
min-height: 2.25rem;
padding: .375rem .625rem;
border-radius: var(--bs-border-radius);
cursor: pointer;
user-select: none;
}
.itflow-select-option.is-active {
background: var(--bs-secondary-bg);
}
.itflow-select-option.is-disabled {
cursor: default;
opacity: .5;
}
.itflow-select-option.is-none .itflow-select-label {
color: var(--bs-secondary-color);
}
.itflow-select-text {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-width: 0;
line-height: 1.3;
}
.itflow-select-text .itflow-select-label {
overflow-wrap: anywhere;
}
.itflow-select-description {
font-size: .8125rem;
color: var(--bs-secondary-color);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.itflow-select-option mark {
padding: 0;
color: inherit;
font-weight: 600;
background: color-mix(in srgb, var(--itflow-accent, #0d6efd) 16%, transparent);
border-radius: 2px;
}
.itflow-select-option.is-selected .itflow-select-label {
font-weight: 600;
}
.itflow-select-tick {
flex: 0 0 auto;
font-size: .8rem;
color: var(--itflow-accent, var(--bs-primary));
visibility: hidden;
}
.itflow-select-option.is-selected .itflow-select-tick {
visibility: visible;
}
/* Multiple: a checklist */
.itflow-select-checkbox {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 1rem;
height: 1rem;
border: 1px solid var(--bs-border-color);
border-radius: .3rem;
font-size: .6rem;
color: transparent;
background: var(--bs-body-bg);
}
.itflow-select-option.is-selected .itflow-select-checkbox {
border-color: var(--itflow-accent, var(--bs-primary));
color: #fff;
background: var(--itflow-accent, var(--bs-primary));
}
.theme-yellow .itflow-select-option.is-selected .itflow-select-checkbox,
.theme-orange .itflow-select-option.is-selected .itflow-select-checkbox,
.theme-teal .itflow-select-option.is-selected .itflow-select-checkbox {
color: #000;
}
.itflow-select-create .itflow-select-icon {
color: var(--itflow-accent, var(--bs-primary));
}
.itflow-select-group {
padding: .5rem .625rem .25rem;
font-size: .75rem;
font-weight: 600;
color: var(--bs-secondary-color);
}
.itflow-select-empty,
.itflow-select-more {
padding: .75rem .625rem;
font-size: .875rem;
color: var(--bs-secondary-color);
}
.itflow-select-more {
padding-top: .375rem;
font-size: .8125rem;
}
.itflow-select-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: .25rem .375rem .25rem .875rem;
border-top: 1px solid var(--bs-border-color-translucent);
font-size: .8125rem;
color: var(--bs-secondary-color);
}
.itflow-select-footer .btn,
.itflow-select-done {
--bs-btn-padding-y: .125rem;
text-decoration: none;
}
/* Phones: the panel comes up from the bottom of the screen */
.itflow-select-backdrop {
position: fixed;
inset: 0;
z-index: 1074;
background: rgba(0, 0, 0, .35);
animation: itflowSelectFade .15s ease-out;
}
.itflow-select-panel.is-sheet {
top: auto !important;
right: 0;
bottom: 0;
left: 0 !important;
width: auto !important;
max-height: 80vh;
padding-bottom: env(safe-area-inset-bottom);
border-width: 1px 0 0;
border-radius: 1rem 1rem 0 0;
font-size: 1rem;
animation: itflowSelectUp .2s ease-out;
}
.itflow-select-panel.is-sheet .itflow-select-sheet-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: .75rem .75rem .5rem 1rem;
font-weight: 600;
}
.itflow-select-panel.is-sheet .itflow-select-list {
flex: 1 1 auto;
max-height: none;
}
.itflow-select-panel.is-sheet .itflow-select-option {
min-height: 2.75rem;
}
.itflow-select-panel.is-sheet .itflow-select-search input {
font-size: 1rem;
}
@keyframes itflowSelectUp {
from { transform: translateY(100%); }
}
@keyframes itflowSelectFade {
from { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.itflow-select-panel,
.itflow-select-backdrop {
animation: none;
}
}
/* --- Muted contextual palette for dark mode ------------------------------
AdminLTE 3 swapped in softer variants of the contextual colours whenever
dark mode was on, so a success badge or a danger button did not glare off a
dark surface. Bootstrap 5 does not do this - it keeps one palette for both
modes. These are v3's own dark-mode values, taken from its
.dark-mode .card-<colour> > .card-header rules.
Overriding --bs-<colour> and its -rgb pair carries the change through every
consumer at once: .bg-*, .text-*, .text-bg-*, .border-*, badges and tables.
Buttons are handled separately below because .btn-* hardcodes its own hex
rather than reading the theme colour.
--bs-primary is deliberately left alone: btn-primary already follows
--itflow-accent, and moving --bs-primary would fight that. */
[data-bs-theme=dark] {
--bs-success: #00bc8c;
--bs-success-rgb: 0, 188, 140;
--bs-info: #3498db;
--bs-info-rgb: 52, 152, 219;
--bs-warning: #f39c12;
--bs-warning-rgb: 243, 156, 18;
--bs-danger: #e74c3c;
--bs-danger-rgb: 231, 76, 60;
}
/* .text-bg-* hardcodes its foreground, so the pairs that changed lightness
need re-stating. Same L>0.335 cut used everywhere else: the muted success
(L=0.379) and warning (L=0.429) take dark text, info and danger keep white. */
[data-bs-theme=dark] .text-bg-success {
color: #000;
}
[data-bs-theme=dark] .text-bg-info,
[data-bs-theme=dark] .text-bg-danger {
color: #fff;
}
/* Contextual table rows hardcode a pale tint plus black text, which is
unreadable on a dark table. ITFlow uses danger/warning/primary/secondary. */
[data-bs-theme=dark] .table-danger,
[data-bs-theme=dark] .table-warning,
[data-bs-theme=dark] .table-primary,
[data-bs-theme=dark] .table-secondary {
--bs-table-color: var(--bs-body-color);
--bs-table-striped-color: var(--bs-body-color);
--bs-table-active-color: var(--bs-body-color);
--bs-table-hover-color: var(--bs-body-color);
--bs-table-border-color: var(--bs-border-color);
}
[data-bs-theme=dark] .table-danger {
--bs-table-bg: rgba(231, 76, 60, .22);
}
[data-bs-theme=dark] .table-warning {
--bs-table-bg: rgba(243, 156, 18, .22);
}
[data-bs-theme=dark] .table-primary {
--bs-table-bg: rgba(63, 103, 145, .28);
}
[data-bs-theme=dark] .table-secondary {
--bs-table-bg: rgba(108, 117, 125, .25);
}
/* Buttons hardcode their own hex instead of reading --bs-<colour>. */
[data-bs-theme=dark] .btn-success {
--bs-btn-color: #000;
--bs-btn-bg: #00bc8c;
--bs-btn-border-color: #00bc8c;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #26c69d;
--bs-btn-hover-border-color: #1ac295;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #33cda3;
--bs-btn-active-border-color: #1ac295;
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #00bc8c;
--bs-btn-disabled-border-color: #00bc8c;
}
[data-bs-theme=dark] .btn-info {
--bs-btn-color: #fff;
--bs-btn-bg: #3498db;
--bs-btn-border-color: #3498db;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #2c81ba;
--bs-btn-hover-border-color: #2a7aaf;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #2a7aaf;
--bs-btn-active-border-color: #2772a4;
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #3498db;
--bs-btn-disabled-border-color: #3498db;
}
[data-bs-theme=dark] .btn-warning {
--bs-btn-color: #000;
--bs-btn-bg: #f39c12;
--bs-btn-border-color: #f39c12;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #f5ab36;
--bs-btn-hover-border-color: #f4a62a;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #f5b041;
--bs-btn-active-border-color: #f4a62a;
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #f39c12;
--bs-btn-disabled-border-color: #f39c12;
}
[data-bs-theme=dark] .btn-danger {
--bs-btn-color: #fff;
--bs-btn-bg: #e74c3c;
--bs-btn-border-color: #e74c3c;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #c44133;
--bs-btn-hover-border-color: #b93d30;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #b93d30;
--bs-btn-active-border-color: #ae392d;
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #e74c3c;
--bs-btn-disabled-border-color: #e74c3c;
}
/* outline variants read --bs-<colour>, so they follow automatically apart
from the foreground on hover */
[data-bs-theme=dark] .btn-outline-success:hover,
[data-bs-theme=dark] .btn-outline-warning:hover {
--bs-btn-hover-color: #000;
}
/* --- FullCalendar --------------------------------------------------------
v7's classic theme ships a dark palette keyed on [data-color-scheme=dark],
which nothing was setting - that attribute is now emitted next to
data-bs-theme in includes/header.php, so the built-in dark mode activates.
These then pull the palette onto the app's own colours, so the calendar
matches in BOTH modes rather than being a near-black (#030712) panel next to
a #212529 page, and so events and the toolbar follow the theme accent. */
#calendar {
/* Reserve the calendar's final height before it exists.
agent/calendar.php renders an empty <div id="calendar"></div> and
FullCalendar only fills it on DOMContentLoaded - which cannot happen
until every parser-blocking script has run, including the 777KB
fullcalendar.global.js that loads after the footer. Until then the div
is zero pixels tall, so the page paints with no calendar and then snaps
to full height, shoving everything below it down. That jump is the
flash.
90vh is not a guess: it is the exact `height: '90vh'` the calendar is
configured with in calendar.php, set outside the mobile branch so it
applies on both. Matching it here means the box is the right size from
first paint and nothing moves when the widget arrives. */
min-height: 90vh;
--fc-classic-background: var(--bs-body-bg);
--fc-classic-foreground: var(--bs-body-color);
--fc-classic-muted-foreground: var(--bs-secondary-color);
--fc-classic-border: var(--bs-border-color);
--fc-classic-strong-border: var(--bs-border-color);
/* events and the "primary" accent */
--fc-classic-primary: var(--itflow-accent, #007bff);
--fc-classic-event: var(--itflow-accent, #007bff);
/* Only the pressed/active toolbar button is themed. FullCalendar's resting
slate is the same in both its palettes, i.e. deliberate, and recolouring
it to a dark grey would look wrong in light mode. */
--fc-classic-button-strong: var(--itflow-accent, #007bff);
--fc-classic-button-strong-border: var(--itflow-accent, #007bff);
}
/* light accents need a dark foreground on events and the active view button */
.theme-yellow #calendar,
.theme-orange #calendar,
.theme-teal #calendar {
--fc-classic-primary-foreground: #000;
--fc-classic-event-contrast: #000;
--fc-classic-button-foreground: #000;
}
/* --- SweetAlert2 ----------------------------------------------------------
v11 keys its dark palette on [data-swal2-theme=dark], an attribute it sets
on its own container from the `theme` option - which defaults to "light",
so the confirm dialogs stayed white in dark mode. js/confirm_modal.js now
hands it the app's mode.
Same treatment as FullCalendar above: its dark theme is its own near-black
(#19191a) next to a #212529 page, so the two seed variables are repointed
onto the app's colours. Everything else in its dark block - text, input
background, validation message - derives from these two, so nothing further
needs restating here. */
[data-swal2-theme=dark] {
--swal2-dark-theme-black: var(--bs-body-bg);
--swal2-dark-theme-white: var(--bs-body-color);
/* The popup is borderless by default, which reads fine as a white panel on
the backdrop but leaves a #212529 one with no edge against it. */
--swal2-border: 1px solid var(--bs-border-color);
}
/* --- Close button focus ring ---------------------------------------------
Bootstrap styles .btn-close on plain :focus rather than :focus-visible, so
any programmatic or mouse-driven focus leaves a visible ring on it. Keyboard
users still get the ring; a modal that merely opened does not. */
.btn-close:focus:not(:focus-visible) {
box-shadow: none;
}
/* the modal container is focused on open and is not an interactive control,
so it should never draw a ring of its own */
.modal:focus {
outline: 0;
}
/* --- Primary colour: the two cases upstream cannot cover -------------------
`data-lte-primary` on <body> now drives buttons, links, pagination, pills,
dropdowns, list groups, progress, accordions and every form focus state
from AdminLTE's own palette. --itflow-accent is still ours and still drives
the navbar, sidebar, nav-tabs, TinyMCE, the select picker, FullCalendar and the
theme picker.
1. There is no `black` in AdminLTE's palette - its darkest neutral is
gray-dark (#343a40), which is a different colour - so the same variables
are supplied here. The generic [data-lte-primary] component rules then
pick these up exactly as they do for a built-in colour. */
.theme-black {
--bs-primary: #000000;
--bs-primary-rgb: 0, 0, 0;
--bs-link-color: #000000;
--bs-link-color-rgb: 0, 0, 0;
--bs-link-hover-color: #333333;
--bs-link-hover-color-rgb: 51, 51, 51;
--lte-primary-hover: #262626;
--lte-primary-hover-border: #1a1a1a;
--lte-primary-active: #1a1a1a;
--lte-primary-active-border: #000000;
}
/* 2. AdminLTE gives teal WHITE button text (its v3 YIQ rule). White on
#20c997 is a 2.13 contrast ratio, below the 4.5 AA threshold; black is
9.86. Overriding their own variable keeps the dark text we had. */
.theme-teal {
--lte-primary-color: #000000;
--lte-primary-color-rgb: 0, 0, 0;
}
/* --- Colour inputs ---------------------------------------------------------
Bootstrap sizes `.form-control-color` at width:3rem, but every colour input
in ITFlow sits inside an .input-group, and `.input-group > .form-control`
(0,2,0) sets `width: 1%; flex: 1 1 auto` - which outranks .form-control-color
(0,1,0) and collapses the swatch to a sliver. Matching that specificity is
the only way to win it back; the selector is strictly narrower than the one
it ties with, so nothing but a colour input is affected.
4rem rather than Bootstrap's 3rem: the swatch is the entire point of the
control, and .text-sm keeps the height at the 35px of its neighbours, so
there is only width available to read the colour by. */
.input-group > .form-control-color {
flex: 0 0 auto;
width: 4rem;
}
/* --- intl-tel-input --------------------------------------------------------
The library wraps the input in a .iti container, which inside an
.input-group becomes the flex item instead of the input. Bootstrap sizes its
children with `.input-group > .form-control { flex: 1 1 auto; width: 1% }`,
and that selector no longer matches the now-nested input - so without these
the wrapper collapses to its content width and the field sits detached from
the addon beside it.
The wrapper takes the sizing the input used to get, and the input inside it
is squared off on the left because .iti draws the country button there and
the addon draws the rounded edge. */
.input-group > .iti {
position: relative;
flex: 1 1 auto;
width: 1%;
min-width: 0;
}
.input-group > .iti > .form-control {
width: 100%;
}
.input-group > .iti:not(:last-child) > .form-control {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group > .iti:not(:first-child) > .form-control,
.iti--separate-dial-code .form-control {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
/* the country button is chrome, not a control - keep it on the form-control
palette so it does not read as a raised button in either mode */
.iti__selected-country {
background-color: var(--bs-tertiary-bg);
border: var(--bs-border-width) solid var(--bs-border-color);
border-right: 0;
border-radius: var(--bs-border-radius) 0 0 var(--bs-border-radius);
}
.iti__country-list,
.iti__dropdown-content {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
border: var(--bs-border-width) solid var(--bs-border-color);
}
.iti__country.iti__highlight,
.iti__country:hover {
background-color: var(--bs-secondary-bg);
}
.iti__dial-code {
color: var(--bs-secondary-color);
}
/* --- Print ----------------------------------------------------------------
AdminLTE 3 bundled Bootstrap 4, whose print reboot shrank every printout by
declaring `@page { size: a3 }` and letting the browser scale that oversized
page down onto the real sheet - roughly 71% on A4, 73% on Letter. Bootstrap
5 dropped that block and AdminLTE 4 ships no page scaling of its own, so
pages come out at 1:1 and read about 1.4x larger than they used to.
AdminLTE 4.9.1 did add a real print block - it fixes the layout, the
chrome and the trailing blank page, but not the scale, so this stays.
Deliberately NOT restoring the @page rule: modern browsers treat a CSS page
size as a paper REQUEST rather than a scale hint, so it can hand you literal
A3 output instead of shrunken A4. Scaling the root font instead keeps the
page at whatever paper is actually selected and lets the layout reflow to
its real width, which suits the wide report tables better than shrinking the
whole page ever did.
Nothing else in the app sets a root font-size, and Bootstrap, AdminLTE and
the .text-sm wrapper all size in rem, so this one line is the only knob:
raise it for larger print, lower it for smaller. At 71% of a 16px default
the app's .text-sm body text lands at ~9.9px (~7.4pt), which is what v3's
page scaling was effectively producing. */
@media print {
html {
font-size: 71%;
}
/* AdminLTE 3 zeroed Bootstrap's card border (`border: 0 solid`) and drew
the card's edge with a box-shadow instead - and Bootstrap 4's print
reboot then killed box-shadows, so a printed card had no outline at all,
just the header's own bottom rule. AdminLTE 4 gives .card a real 1px
border, keeps the shadow, and its print block leaves cards alone, so
both still print.
border-WIDTH rather than the border shorthand, so .card-outline's 3px
accent keeps its colour and can be handed its width straight back - it
is a border-top longhand and survived v3's border:0 the same way. */
.card {
border-width: 0 !important;
}
.card.card-outline {
border-top-width: 3px !important;
}
.card-footer {
border-top-width: 0 !important;
}
/* Bootstrap 4 dropped every shadow when printing; they render as grey
smudges on paper. Same rule it used, which also covers the dashboard
small-boxes rather than cards alone. */
*,
*::before,
*::after {
box-shadow: none !important;
}
}
/* Keep the layout viewport a constant width.
This is what was making some navigations judder and others not. AdminLTE
sizes .app-wrapper, .app-header, .app-main and .app-footer against 100vw,
and 100vw INCLUDES the scrollbar, so the usable width is 100vw minus
whatever the scrollbar takes. Nothing reserves that space, so the vertical
scrollbar - and with it the layout width - appears and disappears purely on
whether a given page happens to be taller than the window.
For a view transition that is not cosmetic. ::view-transition-group(root) is
sized to the viewport, and the UA animates the group's width and height from
the outgoing value to the incoming one, stretching BOTH snapshots to fit for
the whole duration and snapping at the end. Navigate between a page that
scrolls and one that does not and every glyph on the page smears sideways by
the scrollbar width and then jumps back.
The client side nav is where this shows worst, because
agent/includes/inc_client_top_head.php renders #clientHeader expanded on
client_overview.php and collapsed everywhere else - so moving between
Overview and any sibling page flips the scrollbar on and off almost every
time.
scrollbar-gutter reserves the space permanently, so the width never moves.
The max-width correction has to come with it: with the gutter always
present, an unqualified 100vw now overruns the layout viewport on EVERY
page rather than only on tall ones, which would trade an intermittent
horizontal jump for a permanent horizontal scrollbar. 100% is the width of
the grid area these four already occupy, which is what the rule wanted.
AdminLTE made the same correction upstream in 4.8.3. */
html {
scrollbar-gutter: stable;
}
.app-wrapper,
.app-header,
.app-main,
.app-footer {
max-width: 100%;
}
/* AdminLTE puts a clearfix ::after on every card header, body and footer. On a
flex container that pseudo-element becomes an empty last flex item, and any
gap-* utility then adds a gap after it - so the last real item (the client
header's actions button, the overview's "View all" link) sits a gap short of
the padding. Floats don't apply inside flex anyway, so the clearfix only
ever does harm there. */
.card-header.d-flex::after,
.card-body.d-flex::after,
.card-footer.d-flex::after {
content: none;
}
/* Client header - agent/includes/inc_client_top_head.php
Initials, name and tags in one row; the name opens the panel underneath
(#clientHeader), which holds the same columns as the overview's header. Which way the chevron points is carried by the icon class
itself (fa-chevron-right / fa-chevron-down) - emitted by PHP for the first
paint, swapped by the include's own script after that. Not a CSS rotation,
so the direction cannot go stale behind a cached stylesheet. */
.itflow-client-head-main,
.itflow-client-head-toggle {
min-width: 0;
}
.itflow-client-head-toggle {
display: inline-flex;
align-items: center;
gap: .5rem;
color: var(--bs-body-color);
text-decoration: none;
}
.itflow-client-head-toggle:hover,
.itflow-client-head-toggle:focus-visible {
color: var(--bs-body-color);
}
/* Name with the client type under it, together no taller than the 2rem
initials tile beside them, so the row stays the same height */
.itflow-client-head-title {
display: flex;
flex-direction: column;
min-width: 0;
}
.itflow-client-head-name {
font-size: 1.125rem;
font-weight: 600;
line-height: 1.25rem;
overflow-wrap: anywhere;
}
.itflow-client-head-type {
font-size: .75rem;
line-height: .75rem;
color: var(--bs-secondary-color);
}
.client-header-chevron {
font-size: .7rem;
color: var(--bs-secondary-color);
}
.client-header-toggle:hover .client-header-chevron {
color: var(--bs-body-color);
}
/* Open tickets and balance due beside the menu */
.itflow-client-head-glance {
font-size: .875rem;
white-space: nowrap;
}
.itflow-client-head-glance a {
text-decoration: none;
}
/* Phone: a smaller name so most fit on one line beside the menu button, the
tags under it, then open tickets and balance due on their own line */
@media (max-width: 767.98px) {
.itflow-client-head-name {
font-size: 1rem;
}
.itflow-client-head-glance {
flex-basis: 100%;
}
}
/* Search palette - js/global_search.js
Opens over the page on Ctrl+K. Sits high rather than centred, so the
results grow downwards from the box instead of the box jumping as they
arrive. Everything takes its colour from the Bootstrap variables, so dark
mode needs nothing extra; the highlighted row uses the theme accent. */
.itflow-search .modal-dialog {
max-width: 44rem;
margin-top: 8vh;
}
.itflow-search .modal-content {
overflow: hidden;
border: 0;
box-shadow: 0 1rem 3rem rgba(0, 0, 0, .35);
}
.itflow-search-bar {
display: flex;
align-items: center;
gap: .75rem;
padding: .75rem 1rem;
border-bottom: 1px solid var(--bs-border-color);
}
.itflow-search-icon,
.itflow-search-spinner {
flex: 0 0 auto;
color: var(--bs-secondary-color);
}
.itflow-search-input {
flex: 1 1 auto;
min-width: 0;
padding: .25rem 0;
border: 0;
outline: 0;
background: transparent;
color: var(--bs-body-color);
font-size: 1.15rem;
}
.itflow-search-input::placeholder {
color: var(--bs-secondary-color);
}
.itflow-search-results {
max-height: min(60vh, 34rem);
overflow-y: auto;
padding: .25rem 0 .5rem;
}
.itflow-search-group {
padding: .65rem 1rem .25rem;
font-size: .7rem;
font-weight: 600;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--bs-secondary-color);
}
.itflow-search-group .fa-fw {
margin-right: .35rem;
}
.itflow-search-item {
display: flex;
align-items: center;
gap: .75rem;
padding: .45rem 1rem .45rem calc(1rem - 3px);
border-left: 3px solid transparent;
color: var(--bs-body-color);
text-decoration: none;
cursor: pointer;
}
.itflow-search-item:hover,
.itflow-search-item:focus {
color: var(--bs-body-color);
text-decoration: none;
}
.itflow-search-item.active {
border-left-color: var(--itflow-accent, #007bff);
background-color: rgba(var(--bs-emphasis-color-rgb), .06);
}
.itflow-search-item-icon {
flex: 0 0 auto;
color: var(--bs-secondary-color);
}
.itflow-search-item.active .itflow-search-item-icon {
color: var(--itflow-accent, #007bff);
}
.itflow-search-item-body {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-width: 0;
}
.itflow-search-item-title,
.itflow-search-item-sub,
.itflow-search-item-context {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.itflow-search-item-sub,
.itflow-search-item-context {
font-size: .8rem;
color: var(--bs-secondary-color);
}
.itflow-search-item-aside {
display: flex;
flex: 0 1 auto;
align-items: center;
gap: .5rem;
max-width: 40%;
}
.itflow-search-ref {
margin-right: .5rem;
font-family: var(--bs-font-monospace);
font-size: .8rem;
color: var(--bs-secondary-color);
}
.itflow-search mark {
padding: 0;
background: transparent;
color: inherit;
font-weight: 700;
}
.itflow-search-more .itflow-search-item-title,
.itflow-search-all .itflow-search-item-title {
color: var(--bs-secondary-color);
}
.itflow-search-more.active .itflow-search-item-title,
.itflow-search-all.active .itflow-search-item-title {
color: var(--bs-body-color);
}
.itflow-search-all {
margin-top: .25rem;
border-top: 1px solid var(--bs-border-color-translucent);
padding-top: .6rem;
}
.itflow-search-empty,
.itflow-search-hint {
padding: 1.25rem 1rem;
text-align: center;
color: var(--bs-secondary-color);
}
.itflow-search-empty .fa-fw {
font-size: 1.5rem;
margin-bottom: .5rem;
}
.itflow-search-hint {
padding: .75rem 1rem;
font-size: .85rem;
}
.itflow-search-footer {
gap: 1rem;
align-items: center;
padding: .45rem 1rem;
border-top: 1px solid var(--bs-border-color);
background-color: var(--bs-tertiary-bg);
font-size: .75rem;
color: var(--bs-secondary-color);
}
.itflow-search kbd,
.navbar-search-kbd {
padding: .05rem .35rem;
border: 1px solid var(--bs-border-color);
border-radius: .25rem;
background-color: var(--bs-body-bg);
color: var(--bs-secondary-color);
font-family: inherit;
font-size: .7rem;
box-shadow: none;
}
@media (max-width: 575.98px) {
.itflow-search .modal-dialog {
margin-top: 0;
}
.itflow-search .modal-content {
display: flex;
flex-direction: column;
}
.itflow-search-results {
flex: 1 1 auto;
max-height: none;
}
/* No room beside the title on a phone: client and status drop under it */
.itflow-search-item {
flex-wrap: wrap;
row-gap: .15rem;
}
.itflow-search-item-body {
flex: 1 1 0;
}
.itflow-search-item-aside {
order: 3;
max-width: none;
width: 100%;
padding-left: calc(1.25em + .75rem);
}
}
/* The Ctrl K hint inside the top bar search box. Pointer events off so a click on
it still lands on the box underneath and opens the palette. */
.navbar-search .navbar-search-field .navbar-search-kbd {
position: absolute;
top: 50%;
right: .6rem;
transform: translateY(-50%);
pointer-events: none;
background-color: transparent;
border-color: rgba(var(--bs-emphasis-color-rgb), .25);
}
.navbar-search .navbar-search-field .form-control {
cursor: pointer;
}
/* Full search results - agent/global_search.php. Same bold-not-yellow match
marking as the palette, and the client column capped so a long client name
cannot push the title off the row. */
.global-search-group mark {
padding: 0;
background: transparent;
color: inherit;
font-weight: 700;
}
.global-search-context {
max-width: 14rem;
}
.global-search-group {
scroll-margin-top: 4rem;
}
/* Notifications - js/notifications.js, the bell panel and agent/notifications.php
One row design for both. The icon tile takes Bootstrap's subtle colour
utilities (bg-*-subtle / text-*-emphasis), so each tone already has a dark
mode version. Unread rows carry a dot and a heavier title; read rows step
back. The read / unread button shows on hover and keyboard focus, and
always on touch screens, where there is no hover. */
/* data-bs-theme on the panel (to escape the dark navbar) also puts Bootstrap's
default blue links back. Carry the page's themed link colours in instead -
resolved on body, where the theme colour and the page's mode both apply. */
body {
--itflow-link-color: var(--bs-link-color);
--itflow-link-color-rgb: var(--bs-link-color-rgb);
--itflow-link-hover-color: var(--bs-link-hover-color);
--itflow-link-hover-color-rgb: var(--bs-link-hover-color-rgb);
}
.itflow-notify-panel {
--bs-link-color: var(--itflow-link-color);
--bs-link-color-rgb: var(--itflow-link-color-rgb);
--bs-link-hover-color: var(--itflow-link-hover-color);
--bs-link-hover-color-rgb: var(--itflow-link-hover-color-rgb);
width: min(25rem, calc(100vw - 1rem));
overflow: hidden;
box-shadow: 0 .75rem 2rem rgba(0, 0, 0, .25);
}
.itflow-notify-head {
display: flex;
align-items: center;
padding: .75rem 1rem .25rem;
}
.itflow-notify-tabs {
gap: 1rem;
padding: 0 1rem;
border-bottom: 1px solid var(--bs-border-color);
font-size: .875rem;
}
.itflow-notify-tabs .nav-link {
padding: .4rem 0;
color: var(--bs-secondary-color);
}
.itflow-notify-tabs .nav-link.active {
color: var(--bs-emphasis-color);
border-bottom-color: var(--itflow-accent, #007bff);
}
.itflow-notify-list {
max-height: min(65vh, 32rem);
overflow-y: auto;
}
.itflow-notify-group {
padding: .6rem 1rem .2rem;
font-size: .7rem;
font-weight: 600;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--bs-secondary-color);
}
.itflow-notify-item {
position: relative;
}
.itflow-notify-item .itflow-notify-link {
display: flex;
align-items: flex-start;
gap: .75rem;
padding: .6rem 2.75rem .6rem 1.75rem;
white-space: normal;
color: var(--bs-body-color);
text-decoration: none;
}
.itflow-notify-item .itflow-notify-link:hover,
.itflow-notify-item .itflow-notify-link:focus {
color: var(--bs-body-color);
background-color: rgba(var(--bs-emphasis-color-rgb), .05);
}
.itflow-notify-icon {
display: flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
font-size: .85rem;
}
.itflow-notify-body {
display: flex;
flex: 1 1 0;
flex-direction: column;
min-width: 0;
}
.itflow-notify-top {
display: flex;
align-items: baseline;
gap: .5rem;
}
.itflow-notify-type {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: .875rem;
}
.itflow-notify-time {
flex: 0 0 auto;
font-size: .75rem;
color: var(--bs-secondary-color);
}
.itflow-notify-text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
font-size: .8125rem;
color: var(--bs-secondary-color);
overflow-wrap: anywhere;
}
.itflow-notify-client {
margin-top: .15rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: .75rem;
color: var(--bs-secondary-color);
}
.itflow-notify-client .fa-fw {
margin-right: .25rem;
opacity: .7;
}
.itflow-notify-item.is-unread .itflow-notify-type {
font-weight: 600;
color: var(--bs-emphasis-color);
}
.itflow-notify-item.is-unread::before {
content: "";
position: absolute;
top: 1.35rem;
left: .7rem;
width: .5rem;
height: .5rem;
border-radius: 50%;
background-color: var(--itflow-accent, #007bff);
}
.itflow-notify-item:not(.is-unread) .itflow-notify-icon {
opacity: .6;
}
.itflow-notify-toggle {
position: absolute;
top: .55rem;
right: .5rem;
width: 1.9rem;
height: 1.9rem;
padding: 0;
border: 0;
border-radius: 50%;
color: var(--bs-secondary-color);
opacity: 0;
}
.itflow-notify-toggle:hover,
.itflow-notify-toggle:focus-visible {
color: var(--bs-emphasis-color);
background-color: rgba(var(--bs-emphasis-color-rgb), .1);
}
.itflow-notify-item:hover .itflow-notify-toggle,
.itflow-notify-item:focus-within .itflow-notify-toggle {
opacity: 1;
}
@media (hover: none) {
.itflow-notify-toggle {
opacity: 1;
}
}
/* A finger needs a bigger target than a pointer: 44px on touch screens, with
the row's text kept clear of it */
@media (pointer: coarse) {
.itflow-notify-toggle {
top: .3rem;
right: .25rem;
width: 2.75rem;
height: 2.75rem;
}
.itflow-notify-item .itflow-notify-link {
padding-right: 3.25rem;
}
}
.itflow-notify-empty {
padding: 2rem 1rem;
text-align: center;
color: var(--bs-secondary-color);
}
.itflow-notify-foot {
display: block;
padding: .6rem 1rem;
border-top: 1px solid var(--bs-border-color);
font-size: .875rem;
text-align: center;
text-decoration: none;
}
.itflow-notify-foot:hover {
background-color: rgba(var(--bs-emphasis-color-rgb), .05);
}
/* The full page lists the same rows inside a card */
.itflow-notify-page .itflow-notify-item + .itflow-notify-item {
border-top: 1px solid var(--bs-border-color-translucent);
}
.itflow-notify-page .itflow-notify-link {
padding-top: .75rem;
padding-bottom: .75rem;
}
.itflow-notify-page .itflow-notify-text {
-webkit-line-clamp: 3;
}
.itflow-notify-page .itflow-notify-group {
background-color: var(--bs-tertiary-bg);
border-top: 1px solid var(--bs-border-color-translucent);
}
/* Bootstrap does not use Popper for dropdowns inside a navbar, so the panel is
pinned to the bell's right edge and a phone screen cuts off its left side.
On narrow screens it spans the width under the top bar instead. */
@media (max-width: 575.98px) {
.app-header .navbar-nav .itflow-notify-panel[data-bs-popper] {
position: fixed;
top: 3rem;
right: .5rem;
left: .5rem;
width: auto;
margin-top: .25rem;
}
}
/* Dashboard - agent/dashboard.php
Built on cards, list-groups and Bootstrap's subtle colour utilities; this is
only what those do not cover: the greeting and section headings, quieter
card headers, the "Needs attention" rail, the money strip, the priority dot
and chart heights. Every number on the page uses tabular figures so columns
of amounts line up without a monospace font. */
.itflow-dash {
font-variant-numeric: tabular-nums;
}
.itflow-dash-greeting {
font-size: 1.6rem;
font-weight: 600;
letter-spacing: -.01em;
}
.itflow-dash-section {
font-size: 1.125rem;
font-weight: 600;
}
.itflow-dash .card-header {
background: transparent;
border-bottom: 0;
padding-bottom: .25rem;
}
.itflow-dash .card-title {
font-size: 1rem;
font-weight: 600;
}
.itflow-dash .card > .list-group-flush {
border-top: var(--bs-border-width) solid var(--bs-border-color-translucent);
}
.itflow-dash .card-header + .list-group {
margin-top: .5rem;
}
/* Clickable rows read as normal text, not the greyed default */
.itflow-dash .list-group {
--bs-list-group-action-color: var(--bs-body-color);
}
/* Needs attention: one card split into cells, each with a severity bar on top.
The -1px margins tuck every cell's left/top hairline under the card edge, so
only the lines between cells show however the row wraps. */
.itflow-dash-rail {
display: flex;
flex-wrap: wrap;
margin: -1px 0 0 -1px;
}
.itflow-dash-rail-item {
display: flex;
flex: 1 1 10rem;
flex-direction: column;
gap: .15rem;
min-width: 0;
padding: 1rem 1.1rem .9rem;
border-top: var(--bs-border-width) solid var(--bs-border-color-translucent);
border-left: var(--bs-border-width) solid var(--bs-border-color-translucent);
box-shadow: inset 0 3px 0 var(--itflow-dash-tone, var(--bs-secondary));
color: inherit;
text-decoration: none;
}
a.itflow-dash-rail-item:hover,
a.itflow-dash-rail-item:focus-visible {
background-color: var(--bs-tertiary-bg);
color: inherit;
}
.itflow-dash-rail-count {
font-size: 1.75rem;
font-weight: 600;
line-height: 1.1;
}
.itflow-dash-rail-label {
font-size: .875rem;
font-weight: 500;
}
.itflow-dash-rail-label .fas {
margin-right: .35rem;
}
.itflow-dash-rail-detail {
font-size: .8rem;
color: var(--bs-secondary-color);
}
.itflow-dash-rail-clear {
flex-basis: 100%;
}
/* Finance: the period figures side by side, then "Right now" beside them */
.itflow-dash-money + .itflow-dash-money {
border-top: var(--bs-border-width) solid var(--bs-border-color-translucent);
}
.itflow-dash-money a:hover,
.itflow-dash-money a:focus-visible {
background-color: var(--bs-tertiary-bg);
}
.itflow-dash-money-value {
margin: .1rem 0 .15rem;
font-size: 1.5rem;
font-weight: 600;
line-height: 1.2;
}
.itflow-dash-now {
border-top: var(--bs-border-width) solid var(--bs-border-color-translucent);
background-color: var(--bs-tertiary-bg);
}
.itflow-dash-now .list-group {
--bs-list-group-bg: transparent;
}
@media (min-width: 768px) {
.itflow-dash-money + .itflow-dash-money {
border-top: 0;
border-left: var(--bs-border-width) solid var(--bs-border-color-translucent);
}
}
@media (min-width: 992px) {
.itflow-dash-now {
border-top: 0;
border-left: var(--bs-border-width) solid var(--bs-border-color-translucent);
}
}
/* Your schedule: day headings, then time | colour mark | what */
.itflow-dash-schedule {
border-top: var(--bs-border-width) solid var(--bs-border-color-translucent);
margin-top: .5rem;
padding-bottom: .4rem;
}
/* Beside the ticket list it takes that card's height and scrolls the rest;
stacked on smaller screens it stops at a sensible height */
@media (max-width: 1199.98px) {
.itflow-dash-schedule {
max-height: 26rem;
overflow-y: auto;
}
}
@media (min-width: 1200px) {
.itflow-dash-schedule {
flex: 1 1 0;
min-height: 14rem;
overflow-y: auto;
}
}
.itflow-dash-schedule-day {
padding: .6rem 1rem .2rem;
font-size: .8rem;
font-weight: 600;
color: var(--bs-secondary-color);
}
.itflow-dash-schedule-item {
display: flex;
align-items: center;
gap: .75rem;
padding: .4rem 1rem;
color: var(--bs-body-color);
text-decoration: none;
}
.itflow-dash-schedule-item:hover,
.itflow-dash-schedule-item:focus-visible {
background-color: var(--bs-tertiary-bg);
color: var(--bs-body-color);
}
/* Earlier today - still listed, but stepped back */
.itflow-dash-schedule-item.is-past {
opacity: .55;
}
.itflow-dash-schedule-time {
flex: 0 0 4.25rem;
font-size: .8rem;
color: var(--bs-secondary-color);
white-space: nowrap;
}
.itflow-dash-schedule-mark {
flex: 0 0 auto;
align-self: stretch;
width: 3px;
border-radius: 2px;
}
/* Ranked rows with a bar underneath: open tickets by tech, top clients */
.itflow-dash-bars {
display: flex;
flex-direction: column;
gap: .35rem;
margin-inline: -.5rem;
}
.itflow-dash-bar {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
column-gap: .75rem;
row-gap: .3rem;
padding: .35rem .5rem;
border-radius: var(--bs-border-radius-sm);
color: var(--bs-body-color);
text-decoration: none;
}
.itflow-dash-bar:hover,
.itflow-dash-bar:focus-visible {
background-color: var(--bs-tertiary-bg);
color: var(--bs-body-color);
}
.itflow-dash-bar-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.itflow-dash-bar-value {
font-weight: 600;
white-space: nowrap;
}
.itflow-dash-bar-track {
display: flex;
grid-column: 1 / -1;
height: .375rem;
overflow: hidden;
border-radius: 1rem;
background-color: var(--bs-secondary-bg);
}
.itflow-dash-bar-fill {
height: 100%;
background-color: var(--itflow-accent);
}
/* Receivables aging: one bar split by lateness, green through to deep red */
.itflow-dash-aging {
display: flex;
gap: 2px;
height: .75rem;
overflow: hidden;
border-radius: 1rem;
background-color: var(--bs-secondary-bg);
}
.itflow-dash-age-current {
background-color: var(--bs-success);
}
.itflow-dash-age-1-30 {
background-color: var(--bs-warning);
}
.itflow-dash-age-31-60 {
background-color: var(--bs-orange);
}
.itflow-dash-age-61-90 {
background-color: var(--bs-danger);
}
.itflow-dash-age-90plus {
background-color: #842029;
}
[data-bs-theme=dark] .itflow-dash-age-90plus {
background-color: #a52834;
}
.itflow-dash-dot {
flex: 0 0 auto;
width: .55rem;
height: .55rem;
border-radius: 50%;
}
/* Urgent is "dark" everywhere else in the ticket lists - invisible on a dark page */
[data-bs-theme=dark] .itflow-dash-dot.bg-dark {
background-color: var(--bs-emphasis-color) !important;
}
.itflow-dash-chart {
position: relative;
height: 240px;
}
.itflow-dash-chart-donut {
height: 180px;
}
.itflow-dash-donut-total {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
line-height: 1.25;
pointer-events: none;
}
.itflow-dash-donut-total [data-donut-total] {
font-size: 1.2rem;
}
.itflow-dash-legend {
margin-inline: -.4rem;
font-size: .85rem;
}
.itflow-dash-legend li {
display: flex;
align-items: center;
gap: .5rem;
padding: .2rem .4rem;
border-radius: var(--bs-border-radius-sm);
}
.itflow-dash-legend li:hover {
background-color: var(--bs-tertiary-bg);
}
.itflow-dash-legend-swatch {
flex: 0 0 auto;
width: .6rem;
height: .6rem;
border-radius: 50%;
}
.itflow-dash-legend-label {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.itflow-dash-legend-value {
white-space: nowrap;
}
.itflow-dash-legend-share {
flex: 0 0 2.5rem;
text-align: right;
color: var(--bs-secondary-color);
}
.itflow-dash-chart-lg {
height: 300px;
}
.itflow-dash-chart-sm {
min-height: 150px;
}
/* Clients list - agent/clients.php
Rows are a table with Bootstrap's own utilities; this is only the initials
tile beside each name (also used at the top of the client side nav), the
small tag pills under it, and a tint on rows ticked for a bulk action. */
.itflow-client-avatar {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: var(--bs-border-radius-lg);
font-size: .8rem;
font-weight: 600;
letter-spacing: .02em;
}
/* Tags - functions/tags.php tagPills(), on every list and detail page that has
them. A pill opens the list filtered to that tag; the dashed "+" at the end
opens the tag picker (js/tag_picker.js). The group sits above stretched-link
row and cell links so the pills stay clickable on top of them. */
.itflow-tags {
position: relative;
z-index: 2;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: .25rem;
max-width: 100%;
vertical-align: middle;
}
.itflow-tag {
display: inline-flex;
align-items: center;
gap: .2rem;
padding: .05rem .45rem;
border: 1px solid transparent;
border-radius: 1rem;
font-size: .75rem;
font-weight: normal;
line-height: 1.4;
color: #fff;
background-color: var(--itflow-tag-color, var(--bs-secondary));
text-decoration: none;
white-space: nowrap;
}
.itflow-tag:hover,
.itflow-tag:focus-visible {
color: #fff;
filter: brightness(1.1);
}
.itflow-tag-add {
color: var(--bs-secondary-color);
background-color: transparent;
border-color: var(--bs-border-color);
border-style: dashed;
cursor: pointer;
}
.itflow-tag-add:hover,
.itflow-tag-add:focus-visible,
.itflow-tag-add[aria-expanded="true"] {
color: var(--itflow-accent, var(--bs-primary));
border-color: currentColor;
filter: none;
}
/* The picker - the select picker's panel, plus the bulk note and the dash for
a tag only some of the selected records have */
.itflow-tag-panel-note {
padding: .5rem .75rem;
border-bottom: 1px solid var(--bs-border-color-translucent);
font-size: .8125rem;
color: var(--bs-secondary-color);
}
.itflow-tag-panel .itflow-select-footer {
gap: .25rem;
padding-top: .375rem;
padding-bottom: .375rem;
}
.itflow-tag-done {
--bs-btn-padding-x: .75rem;
}
/* On a phone the sheet's own header has the Done */
.itflow-select-panel.is-sheet .itflow-tag-done {
display: none;
}
.itflow-select-option.is-mixed .itflow-select-checkbox {
border-color: var(--itflow-accent, var(--bs-primary));
color: var(--itflow-accent, var(--bs-primary));
}
.itflow-clients-street {
max-width: 16rem;
}
.itflow-clients tbody tr:has(.bulk-select:checked) > * {
--bs-table-bg-state: var(--bs-primary-bg-subtle);
}
/* Quick pickers - js/quick_pick.js. A ticket's priority, assignee or billing
badge is a button that opens a small menu under it; the menu itself is the
select picker's panel. On the ticket page the button also carries
.ticket-prop-edit, so it looks like the other properties there. */
.itflow-quick-pick {
padding: 0;
border: 0;
border-radius: 2rem;
font: inherit;
color: inherit;
background: none;
cursor: pointer;
transition: opacity .15s;
}
.itflow-quick-pick-list:hover .badge,
.itflow-quick-pick-list:focus-visible .badge,
.itflow-quick-pick-list[aria-expanded="true"] .badge {
box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--itflow-accent, #0d6efd) 30%, transparent);
}
.itflow-quick-pick:focus-visible {
outline: 0;
}
.itflow-quick-pick-detail {
border-radius: var(--bs-border-radius);
}
.itflow-quick-pick.is-saving {
opacity: .5;
pointer-events: none;
}
/* A brief ring on the value that just changed */
.itflow-quick-pick.is-saved .badge {
animation: itflowQuickSaved .9s ease-out;
}
@keyframes itflowQuickSaved {
from { box-shadow: 0 0 0 .3rem color-mix(in srgb, var(--bs-success) 45%, transparent); }
to { box-shadow: 0 0 0 .3rem transparent; }
}
@media (prefers-reduced-motion: reduce) {
.itflow-quick-pick.is-saved .badge {
animation: none;
}
}
/* Client side nav - agent/includes/client_side_nav.php: the client's initials
and name at the top, under the "All clients" link */
/* Same width, corners and left padding as the menu links below it, so the
tile lines up with their icons; sits right under the "All clients" bar */
.itflow-client-nav-identity {
display: flex;
align-items: center;
gap: .6rem;
margin: 0;
padding: .35rem .8rem;
border: 1px solid rgba(255, 255, 255, .08);
border-radius: var(--bs-border-radius);
background-color: rgba(255, 255, 255, .05);
}
.itflow-client-avatar-sm {
width: 2rem;
height: 2rem;
border-radius: var(--bs-border-radius);
font-size: .7rem;
}
.itflow-client-nav-identity-label {
font-size: .65rem;
font-weight: 600;
letter-spacing: .05em;
line-height: 1.2;
text-transform: uppercase;
color: rgba(255, 255, 255, .55);
}
/* The card is the client switcher's button (js/client_switcher.js) */
button.itflow-client-nav-identity {
width: 100%;
text-align: left;
color: inherit;
font: inherit;
cursor: pointer;
}
button.itflow-client-nav-identity:hover,
button.itflow-client-nav-identity[aria-expanded="true"] {
background-color: rgba(255, 255, 255, .1);
}
button.itflow-client-nav-identity:focus-visible {
outline: 2px solid var(--itflow-accent, var(--bs-primary));
outline-offset: 1px;
}
.itflow-client-switcher-caret {
flex: 0 0 auto;
margin-left: auto;
font-size: .75rem;
color: rgba(255, 255, 255, .45);
}
.itflow-client-switcher {
margin-bottom: .5rem;
}
.itflow-client-switcher-menu {
width: 100%;
margin-top: .25rem !important;
padding: .5rem 0 .25rem;
}
.itflow-client-switcher-list {
max-height: min(22rem, 60vh);
overflow-y: auto;
}
.itflow-client-switcher-item {
display: flex;
align-items: center;
gap: .5rem;
padding-top: .3rem;
padding-bottom: .3rem;
font-size: .875rem;
}
.itflow-client-avatar-xs {
width: 1.5rem;
height: 1.5rem;
border-radius: var(--bs-border-radius-sm);
font-size: .6rem;
}
.itflow-client-switcher-empty {
padding: .4rem 1rem;
font-size: .8rem;
color: var(--bs-secondary-color);
}
/* Long names get two lines, then an ellipsis - the full name is in the tooltip */
.itflow-client-nav-identity-name {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
font-size: .875rem;
font-weight: 600;
line-height: 1.25;
color: #fff;
overflow-wrap: anywhere;
}
/* Client overview - agent/client_overview.php
Reuses the dashboard's pieces (quiet card headers, the attention rail, the
schedule list, money figures); this adds the large initials tile, the small
labels in the header, contact avatars, the notes box and the money strip. */
.itflow-client-avatar-lg {
width: 3.5rem;
height: 3.5rem;
border-radius: var(--bs-border-radius-lg);
font-size: 1.1rem;
}
/* Phone: the name stays beside the initials and the buttons drop under them */
@media (max-width: 767.98px) {
.itflow-ov-hero-name {
flex: 1 1 0;
}
.itflow-ov-hero-actions {
flex-basis: 100%;
}
}
.itflow-ov-label {
margin-bottom: .25rem;
font-size: .8rem;
color: var(--bs-secondary-color);
}
.itflow-ov-person {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
font-size: .8rem;
font-weight: 600;
object-fit: cover;
}
.itflow-ov-notes {
min-height: 12rem;
resize: vertical;
}
/* The money strip is the attention rail's layout without the coloured bar */
.itflow-ov-stats .itflow-dash-rail-item {
--itflow-dash-tone: transparent;
flex-basis: 12rem;
}
/* Racks - agent/racks.php, agent/rack.php, agent/includes/rack_elevation.php
A rack is drawn as a cabinet: dark frame, numbered rails either side and a
bay with one CSS grid row per U. Items are placed by grid-row (PHP works out
the row from the U and which way the rack is numbered), empty U are buttons
that open "add here". The frame stays dark in both themes - it is a picture
of a rack, not page chrome - so its colours are fixed rather than taken from
the Bootstrap variables.
The row height shrinks for tall racks so a 42U rack still fits a screen. */
.min-w-0 {
min-width: 0;
}
.itflow-rack-pair {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1.5rem;
}
.itflow-rack {
--rack-u: clamp(1.3rem, calc(60rem / var(--rack-rows)), 1.75rem);
flex: 1 1 19rem;
max-width: 30rem;
}
.itflow-rack-cap {
margin-bottom: .35rem;
font-size: .7rem;
font-weight: 600;
letter-spacing: .08em;
text-align: center;
text-transform: uppercase;
color: var(--bs-secondary-color);
}
.itflow-rack-frame {
display: flex;
padding: .5rem .3rem;
border-radius: .75rem;
background: linear-gradient(180deg, #2b3035, #212529);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06), 0 .25rem .75rem rgba(0, 0, 0, .18);
}
.itflow-rack-rail {
display: grid;
flex: 0 0 1.75rem;
grid-template-rows: repeat(var(--rack-rows), var(--rack-u));
}
.itflow-rack-rail span {
display: flex;
align-items: center;
justify-content: center;
font-family: var(--bs-font-monospace);
font-size: .62rem;
color: rgba(255, 255, 255, .42);
}
.itflow-rack-bay {
position: relative;
display: grid;
flex: 1 1 auto;
min-width: 0;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: repeat(var(--rack-rows), var(--rack-u));
border-right: 2px solid #3a4148;
border-left: 2px solid #3a4148;
background-color: #15181b;
background-image: repeating-linear-gradient(to bottom, transparent 0, transparent calc(var(--rack-u) - 1px), rgba(255, 255, 255, .05) calc(var(--rack-u) - 1px), rgba(255, 255, 255, .05) var(--rack-u));
}
.itflow-rack-slot {
grid-column: 1;
display: flex;
align-items: center;
justify-content: center;
margin: 1px 2px;
padding: 0;
border: 1px dashed transparent;
border-radius: .3rem;
background: transparent;
font-size: .7rem;
color: rgba(255, 255, 255, .85);
}
.itflow-rack-slot span {
opacity: 0;
transition: opacity .12s ease-in-out;
}
button.itflow-rack-slot:hover,
button.itflow-rack-slot:focus-visible {
border-color: rgba(255, 255, 255, .35);
background-color: rgba(255, 255, 255, .07);
outline: 0;
}
button.itflow-rack-slot:hover span,
button.itflow-rack-slot:focus-visible span {
opacity: 1;
}
/* Items */
.itflow-rack-item {
position: relative;
grid-column: 1;
margin: 1px 2px;
overflow: hidden;
border-radius: .3rem;
color: #fff;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), inset 0 -1px 0 rgba(0, 0, 0, .25), 0 1px 2px rgba(0, 0, 0, .45);
}
.itflow-rack-item-body {
display: flex;
align-items: center;
gap: .5rem;
width: 100%;
height: 100%;
padding: 0 .5rem;
border: 0;
background: transparent;
font: inherit;
text-align: left;
color: inherit;
text-decoration: none;
}
a.itflow-rack-item-body:hover,
button.itflow-rack-item-body:hover {
color: inherit;
background-color: rgba(255, 255, 255, .08);
}
.itflow-rack-item-body:focus-visible {
outline: 2px solid #fff;
outline-offset: -2px;
}
.itflow-rack-item-icon {
flex: 0 0 auto;
font-size: .8rem;
opacity: .9;
}
.itflow-rack-item-text {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-width: 0;
line-height: 1.15;
}
.itflow-rack-item-name,
.itflow-rack-item-meta {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.itflow-rack-item-name {
font-size: .78rem;
font-weight: 600;
}
.itflow-rack-item-meta {
font-size: .68rem;
opacity: .78;
}
/* One U is one line: the name, then the quieter text beside it - which gives
way first when the line is short */
.itflow-rack-h1 .itflow-rack-item-text {
flex-direction: row;
align-items: baseline;
gap: .5rem;
}
.itflow-rack-h1 .itflow-rack-item-name {
flex: 0 1 auto;
max-width: 75%;
}
.itflow-rack-h1 .itflow-rack-item-meta {
flex: 1 1 0;
min-width: 0;
}
.itflow-rack-item-pos {
flex: 0 0 auto;
margin-left: auto;
font-family: var(--bs-font-monospace);
font-size: .62rem;
opacity: .6;
}
.itflow-rack-item-edit {
position: absolute;
top: 50%;
right: .3rem;
display: flex;
align-items: center;
justify-content: center;
width: 1.35rem;
height: 1.2rem;
margin-top: -.6rem;
padding: 0;
border: 0;
border-radius: .25rem;
background-color: rgba(0, 0, 0, .35);
font-size: .6rem;
color: #fff;
opacity: 0;
transition: opacity .12s ease-in-out;
}
.itflow-rack-item:hover .itflow-rack-item-edit,
.itflow-rack-item:focus-within .itflow-rack-item-edit {
opacity: 1;
}
.itflow-rack-item:has(.itflow-rack-item-edit):hover .itflow-rack-item-pos {
visibility: hidden;
}
.itflow-rack-item-edit:hover {
background-color: rgba(0, 0, 0, .55);
}
.itflow-rack-item.is-archived {
opacity: .55;
}
.itflow-rack-item.is-archived .itflow-rack-item-name {
text-decoration: line-through;
}
/* A full-depth item seen from its other side: it fills this face too */
.itflow-rack-item.is-ghost {
border: 1px dashed rgba(255, 255, 255, .16);
background: repeating-linear-gradient(45deg, rgba(255, 255, 255, .06) 0, rgba(255, 255, 255, .06) 5px, transparent 5px, transparent 10px), #1c2024;
color: rgba(255, 255, 255, .55);
box-shadow: none;
}
.itflow-rack-item.is-ghost .itflow-rack-item-body {
cursor: default;
}
/* Tones - the faceplate colour of each kind of kit, shared by the elevation,
the mini racks on the listing, the list chips and the legend */
.itflow-rack-tone-server { background: linear-gradient(180deg, #3d72bb, #2c5891); }
.itflow-rack-tone-network { background: linear-gradient(180deg, #27907f, #1b6c60); }
.itflow-rack-tone-firewall { background: linear-gradient(180deg, #c04e4e, #963838); }
.itflow-rack-tone-power { background: linear-gradient(180deg, #b8862f, #8e6420); }
.itflow-rack-tone-device { background: linear-gradient(180deg, #6d5fc4, #5447a1); }
.itflow-rack-tone-shelf { background: linear-gradient(180deg, #6b7682, #57616c); }
.itflow-rack-tone-patch {
background: radial-gradient(rgba(0, 0, 0, .38) 1.2px, transparent 1.7px) 0 0 / 7px 7px, linear-gradient(180deg, #4d5864, #3b444e);
}
.itflow-rack-tone-cable {
background: repeating-linear-gradient(90deg, rgba(255, 255, 255, .08) 0, rgba(255, 255, 255, .08) 2px, transparent 2px, transparent 9px), linear-gradient(180deg, #31373d, #262b30);
}
.itflow-rack-tone-blank {
background: repeating-linear-gradient(90deg, rgba(255, 255, 255, .05) 0, rgba(255, 255, 255, .05) 10px, transparent 10px, transparent 13px), #1f2327;
color: rgba(255, 255, 255, .7);
}
/* Small coloured tile with the item's icon - item list, "not racked yet" */
.itflow-rack-chip {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 1.9rem;
height: 1.9rem;
border-radius: var(--bs-border-radius);
font-size: .8rem;
color: #fff;
}
.itflow-rack-legend {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: .35rem 1.1rem;
}
.itflow-rack-swatch {
display: inline-block;
width: .75rem;
height: .75rem;
margin-right: .35rem;
border-radius: .2rem;
vertical-align: -.1rem;
}
.itflow-rack-swatch-ghost {
border: 1px dashed var(--bs-secondary-color);
background: repeating-linear-gradient(45deg, var(--bs-secondary-bg) 0, var(--bs-secondary-bg) 2px, transparent 2px, transparent 4px);
}
/* Rack page header and side cards */
.itflow-rack-head-icon {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
border-radius: var(--bs-border-radius-lg);
background: linear-gradient(180deg, #2b3035, #1c1f23);
font-size: 1.2rem;
color: #fff;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08);
}
.itflow-rack-dl {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: .4rem 1rem;
font-size: .875rem;
}
.itflow-rack-dl dt {
font-weight: 500;
color: var(--bs-secondary-color);
}
.itflow-rack-dl dd {
margin: 0;
overflow-wrap: anywhere;
}
.itflow-rack-stats .fw-semibold {
font-size: 1.1rem;
}
.itflow-rack-photo {
position: relative;
display: block;
overflow: hidden;
border-radius: var(--bs-border-radius);
}
.itflow-rack-photo img {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
transition: transform .2s ease-in-out;
}
.itflow-rack-photo:hover img {
transform: scale(1.04);
}
.itflow-rack-photo span {
position: absolute;
bottom: .35rem;
left: .35rem;
padding: .05rem .5rem;
border-radius: 50rem;
background-color: rgba(0, 0, 0, .6);
font-size: .7rem;
color: #fff;
}
.itflow-rack-form-photo img {
max-width: 100%;
max-height: 8rem;
border-radius: var(--bs-border-radius);
}
.itflow-rack-fit:empty {
display: none;
}
/* Listing cards: the whole card opens the rack, the menu sits above that link */
.itflow-rack-card {
transition: box-shadow .15s ease-in-out, border-color .15s ease-in-out;
}
.itflow-rack-card:hover {
border-color: var(--itflow-accent, var(--bs-primary));
box-shadow: 0 .25rem .9rem rgba(0, 0, 0, .1);
}
.itflow-rack-card-menu {
position: relative;
z-index: 2;
}
.itflow-rack-mini {
display: grid;
flex: 0 0 2.4rem;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: repeat(var(--rack-rows), minmax(0, 1fr));
align-self: flex-start;
height: clamp(5.5rem, calc(var(--rack-rows) * .32rem), 9rem);
padding: 2px;
border: 3px solid #2b3035;
border-radius: .4rem;
background-color: #15181b;
}
.itflow-rack-mini span {
grid-column: 1;
margin: .5px 0;
border-radius: 1px;
}
.itflow-rack-mini span.is-ghost {
opacity: .45;
}
@media print {
.itflow-rack-frame,
.itflow-rack-bay,
.itflow-rack-item,
.itflow-rack-chip,
.itflow-rack-swatch,
.itflow-rack-head-icon {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.itflow-rack-slot span,
.itflow-rack-item-edit {
display: none;
}
.itflow-rack {
--rack-u: clamp(1rem, calc(48rem / var(--rack-rows)), 1.5rem);
}
.itflow-rack-items,
.itflow-rack-pair {
break-inside: avoid;
}
}
/* Assets - agent/assets.php, agent/asset.php, agent/modals/asset/
Each kind of asset has a colour (functions/asset.php assetTypeTone()), used
for the icon tile on the list, the asset page header and the related-asset
chips. The tile is the colour at low strength with the icon at full, so it
reads in light and dark alike. */
.itflow-asset-tone-workstation { --itflow-asset-tone: #3d72bb; }
.itflow-asset-tone-server { --itflow-asset-tone: #6d5fc4; }
.itflow-asset-tone-virtual { --itflow-asset-tone: #9a5cc6; }
.itflow-asset-tone-network { --itflow-asset-tone: #23897a; }
.itflow-asset-tone-firewall { --itflow-asset-tone: #c04e4e; }
.itflow-asset-tone-wireless { --itflow-asset-tone: #1b8fc2; }
.itflow-asset-tone-phone { --itflow-asset-tone: #b8862f; }
.itflow-asset-tone-peripheral { --itflow-asset-tone: #5b6f8a; }
.itflow-asset-tone-other { --itflow-asset-tone: #6c757d; }
[data-bs-theme="dark"] .itflow-asset-tone-workstation { --itflow-asset-tone: #6f9fe0; }
[data-bs-theme="dark"] .itflow-asset-tone-server { --itflow-asset-tone: #a197ee; }
[data-bs-theme="dark"] .itflow-asset-tone-virtual { --itflow-asset-tone: #c595ea; }
[data-bs-theme="dark"] .itflow-asset-tone-network { --itflow-asset-tone: #4fc4b0; }
[data-bs-theme="dark"] .itflow-asset-tone-firewall { --itflow-asset-tone: #ec8585; }
[data-bs-theme="dark"] .itflow-asset-tone-wireless { --itflow-asset-tone: #5cc0ec; }
[data-bs-theme="dark"] .itflow-asset-tone-phone { --itflow-asset-tone: #e3b35c; }
[data-bs-theme="dark"] .itflow-asset-tone-peripheral { --itflow-asset-tone: #9fb2cc; }
[data-bs-theme="dark"] .itflow-asset-tone-other { --itflow-asset-tone: #adb5bd; }
.itflow-asset-icon {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: var(--bs-border-radius-lg);
background-color: color-mix(in srgb, var(--itflow-asset-tone) 14%, transparent);
color: var(--itflow-asset-tone);
font-size: 1.05rem;
}
.itflow-asset-icon-lg {
width: 3.5rem;
height: 3.5rem;
font-size: 1.5rem;
}
.itflow-asset-icon-sm {
width: 1.75rem;
height: 1.75rem;
border-radius: var(--bs-border-radius);
font-size: .8rem;
}
img.itflow-asset-icon {
object-fit: cover;
background: none;
}
/* Kind-of-asset tabs above the filter bar */
.itflow-asset-tabs .nav-link {
padding: .3rem .75rem;
color: var(--bs-secondary-color);
}
.itflow-asset-tabs .nav-link .badge {
background-color: var(--bs-secondary-bg);
color: var(--bs-secondary-color);
font-weight: 500;
}
.itflow-asset-tabs .nav-link.active .badge {
background-color: rgba(255, 255, 255, .25);
color: inherit;
}
/* List rows */
.itflow-asset-table td {
padding-top: .6rem;
padding-bottom: .6rem;
}
.itflow-asset-name-cell {
min-width: 15rem;
}
.itflow-asset-desc {
max-width: 18rem;
}
.itflow-asset-model-cell {
min-width: 9rem;
}
/* Tag pills and other controls inside a cell with a stretched-link stay clickable */
.itflow-above-stretch {
position: relative;
z-index: 2;
}
.btn.btn-xs {
--bs-btn-padding-y: 0;
--bs-btn-padding-x: .4rem;
--bs-btn-font-size: .7rem;
--bs-btn-border-radius: var(--bs-border-radius-sm);
font-size: .7rem;
line-height: 1.4;
}
/* Copy button beside an IP / MAC / serial - quiet until the row is pointed at */
.itflow-copy {
margin-left: .3rem;
padding: 0 .15rem;
border: 0;
background: none;
font-size: .8em;
color: var(--bs-secondary-color);
opacity: 0;
transition: opacity .12s ease-in-out;
}
tr:hover .itflow-copy,
.itflow-copy-always .itflow-copy,
.itflow-copy:focus-visible {
opacity: 1;
}
.itflow-copy:hover {
color: var(--bs-emphasis-color);
}
@media (hover: none) {
.itflow-copy {
opacity: 1;
}
}
/* Asset page */
.itflow-asset-dl {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: .45rem 1rem;
font-size: .875rem;
}
.itflow-asset-dl dt {
font-weight: 500;
color: var(--bs-secondary-color);
}
.itflow-asset-dl dd {
margin: 0;
overflow-wrap: anywhere;
}
.itflow-asset-pills {
gap: .25rem;
}
.itflow-asset-related .itflow-asset-pills .nav-link {
padding: .3rem .7rem;
font-size: .875rem;
}
.itflow-asset-related .itflow-asset-pills .nav-link:not(.active) {
color: var(--bs-body-color);
}
.itflow-asset-related .itflow-asset-pills .nav-link.is-empty:not(.active) {
color: var(--bs-secondary-color);
}
.itflow-asset-pills .nav-link .badge {
background-color: var(--bs-secondary-bg);
color: var(--bs-secondary-color);
font-weight: 500;
}
.itflow-asset-pills .nav-link .badge.text-bg-danger {
background-color: var(--bs-danger) !important;
color: #fff !important;
}
.itflow-asset-pills .nav-link.active .badge {
background-color: rgba(255, 255, 255, .25);
color: inherit;
}
.itflow-asset-pane-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: .5rem;
padding: .75rem 1rem;
border-bottom: 1px solid var(--bs-border-color-translucent);
}
.itflow-asset-related .table > :not(caption) > * > :first-child {
padding-left: 1rem;
}
.itflow-asset-related .table > :not(caption) > * > :last-child {
padding-right: 1rem;
}
.itflow-asset-related .table td:first-child {
min-width: 11rem;
}
.itflow-asset-related .table td.checkbox-column {
min-width: 0;
}
.itflow-asset-empty {
padding: 2.5rem 1rem;
text-align: center;
color: var(--bs-secondary-color);
}
.itflow-asset-dl .font-monospace {
font-size: .82rem;
}
/* Section heading inside a long form (asset add/edit) */
.itflow-form-section {
margin-bottom: .75rem;
padding-bottom: .35rem;
border-bottom: 1px solid var(--bs-border-color-translucent);
font-size: .75rem;
font-weight: 600;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--bs-secondary-color);
}
/* Asset quick pickers - status, location and who it is assigned to, as pills
on the assets list and in the asset page header (functions/asset.php
assetQuickHtml), as lines in the asset page's Where & who card */
.itflow-asset-pill {
max-width: 15rem;
padding: .35em .7em;
vertical-align: middle;
}
.itflow-asset-pill > .text-truncate {
min-width: 0;
}
.itflow-asset-pill-person {
padding: .2rem .6rem .2rem .2rem;
}
/* Nothing set yet - a dashed slot, so it reads as something to fill in */
.itflow-asset-pill-empty {
border: 1px dashed var(--bs-border-color);
color: var(--bs-secondary-color);
background: transparent;
}
.itflow-asset-avatar {
display: inline-flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 50%;
font-size: .6rem;
font-weight: 600;
color: #fff;
background-color: var(--bs-secondary);
}
/* In the Where & who card, and the quick look's glance lines, the picker is
the whole line: it lights up on hover with a chevron, like the ticket
page's properties. The negative margin keeps its text in line with the
plain values above and below. */
.itflow-asset-dl .itflow-quick-pick-detail,
.itflow-asset-glance .itflow-quick-pick-detail {
display: flex;
align-items: center;
justify-content: space-between;
gap: .5rem;
width: calc(100% + 1rem);
min-width: 0;
margin: -.3rem -.5rem;
padding: .3rem .5rem;
text-align: left;
}
.itflow-asset-dl .itflow-quick-pick-detail:hover,
.itflow-asset-glance .itflow-quick-pick-detail:hover,
.itflow-asset-dl .itflow-quick-pick-detail:focus-visible,
.itflow-asset-glance .itflow-quick-pick-detail:focus-visible,
.itflow-asset-dl .itflow-quick-pick-detail[aria-expanded="true"],
.itflow-asset-glance .itflow-quick-pick-detail[aria-expanded="true"] {
background-color: var(--bs-tertiary-bg);
}
.itflow-asset-dl .itflow-quick-pick-detail + :not(:empty),
.itflow-asset-glance .itflow-quick-pick-detail + :not(:empty) {
margin-top: .35rem;
}
.itflow-asset-dl .ticket-prop-edit-icon,
.itflow-asset-glance .ticket-prop-edit-icon {
flex: 0 0 auto;
font-size: .7rem;
color: var(--bs-secondary-color);
opacity: 0;
transition: opacity .15s;
}
.itflow-asset-dl .itflow-quick-pick-detail:hover .ticket-prop-edit-icon,
.itflow-asset-glance .itflow-quick-pick-detail:hover .ticket-prop-edit-icon,
.itflow-asset-dl .itflow-quick-pick-detail:focus-visible .ticket-prop-edit-icon,
.itflow-asset-glance .itflow-quick-pick-detail:focus-visible .ticket-prop-edit-icon,
.itflow-asset-dl .itflow-quick-pick-detail[aria-expanded="true"] .ticket-prop-edit-icon,
.itflow-asset-glance .itflow-quick-pick-detail[aria-expanded="true"] .ticket-prop-edit-icon {
opacity: 1;
}
@media (hover: none) {
.itflow-asset-dl .ticket-prop-edit-icon,
.itflow-asset-glance .ticket-prop-edit-icon {
opacity: .6;
}
}
.itflow-asset-dl .itflow-quick-pick-detail.is-saved,
.itflow-asset-glance .itflow-quick-pick-detail.is-saved {
animation: itflowAssetQuickSaved .9s ease-out;
}
@keyframes itflowAssetQuickSaved {
from { background-color: var(--bs-success-bg-subtle); }
to { background-color: transparent; }
}
@media (prefers-reduced-motion: reduce) {
.itflow-asset-dl .itflow-quick-pick-detail.is-saved,
.itflow-asset-glance .itflow-quick-pick-detail.is-saved {
animation: none;
}
}
/* Asset add / edit tabs - one line, scrolling sideways on a phone */
.itflow-asset-form-tabs {
gap: .25rem;
scrollbar-width: thin;
}
.itflow-asset-form-tabs .nav-link {
padding-inline: .75rem;
white-space: nowrap;
}
/* Asset quick look - agent/modals/asset/asset.php. The glance lines under the
header: an icon in place of a label, then the value */
.itflow-asset-glance {
font-size: .875rem;
}
.itflow-glance-item {
display: flex;
align-items: flex-start;
gap: .6rem;
min-width: 0;
padding: .3rem 0;
}
.itflow-glance-item > .fa-fw:first-child {
flex: 0 0 auto;
margin-top: .2rem;
color: var(--bs-secondary-color);
}
.itflow-glance-item .font-monospace {
font-size: .82rem;
}
.itflow-asset-quicknote {
min-height: 2.6rem;
max-height: 12rem;
field-sizing: content;
border-color: transparent;
background-color: var(--bs-tertiary-bg);
resize: vertical;
}
.itflow-asset-quicknote:focus {
background-color: var(--bs-body-bg);
}
.itflow-asset-quick-related {
padding-top: .75rem;
border-top: 1px solid var(--bs-border-color-translucent);
}
.itflow-asset-quick-related .table,
.itflow-asset-quick-related .list-group {
font-size: .875rem;
}
.itflow-asset-quick-related .table > :not(caption) > * > :first-child {
padding-left: 0;
}
.itflow-asset-quick-related .table > :not(caption) > * > :last-child {
padding-right: 0;
}
.itflow-asset-quick-related .table td:first-child {
min-width: 0;
}
.itflow-asset-quick-related .itflow-asset-pills {
flex-wrap: nowrap;
gap: .15rem;
overflow-x: auto;
scrollbar-width: thin;
}
.itflow-asset-quick-related .itflow-asset-pills .nav-link {
padding: .25rem .55rem;
}