/* ==========================================================================
   Client Hub only.

   Loaded after brand.css, which is the shared layer and is copied into other
   Tech Dynamix applications. Nothing here is: these are components this
   application has and the others do not.

   The test for whether a rule belongs in the other file is whether it would
   be true of a marketing site admin. A banner stack for impersonation and a
   booking calendar are not.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The banner stack

   The view as client warning and any announcement showing share one strip
   across the top of the viewport. Tabler has no component for it, so the
   positioning is ours. Color is not: each bar takes one of Tabler's own
   subtle backgrounds, and every bar says what it is in words, so color never
   carries the meaning on its own.

   Only the stack is positioned. The bars inside are ordinary blocks, so a
   third one needs no arithmetic. Fixed and above the sidebar, which is
   z-index 1030 and pinned to the top itself, and out of the flow so the page
   beneath keeps exactly the spacing it has when there are no bars.

   The offset that stops the stack covering the page is driven by a count class
   on the body rather than by measuring, because measuring needs JavaScript and
   an inline style, and the policy here is style-src 'self'.

   .page has min-height:100% and border-box sizing, so padding-top comes out of
   that height rather than adding to it and putting a scrollbar on every page.
   -------------------------------------------------------------------------- */

:root { --td-banner-height: 3rem; }

.banner-stack {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 1040;
}

.banner {
  height: var(--td-banner-height);
}

/* One rule per count. A loop would need a preprocessor and this is a list of
   four numbers. BANNER_MAX_NOTICES caps the bars at three, plus the view as
   bar, so four is the whole range. */
.banners-1 { --td-banner-stack: 1; }
.banners-2 { --td-banner-stack: 2; }
.banners-3 { --td-banner-stack: 3; }
.banners-4 { --td-banner-stack: 4; }

/* Both offsets are written against the body class rather than the element on
   its own, and that is not style: it is specificity.

   Tabler pins the sidebar with

     .navbar-vertical.navbar-expand-lg { position: fixed; top: 0 }

   which is two classes. A bare .navbar-vertical rule here is one, so it loses
   however late it is loaded, and the sidebar stays at the top with the banner
   drawn over it. That is exactly what happened: the view as bar had always
   worked because its selector was .viewing-as-client .navbar-vertical, two
   classes, which ties and then wins on order.

   Every count is listed rather than using [class*="banners-"], so the selector
   says what it matches and cannot pick up a class that merely contains the
   word later. */
.banners-1 .page,
.banners-2 .page,
.banners-3 .page,
.banners-4 .page {
  padding-top: calc(var(--td-banner-height) * var(--td-banner-stack, 0));
}

.banners-1 .navbar-vertical,
.banners-2 .navbar-vertical,
.banners-3 .navbar-vertical,
.banners-4 .navbar-vertical {
  top: calc(var(--td-banner-height) * var(--td-banner-stack, 0));
}

/* --------------------------------------------------------------------------
   The booking calendar

   A month grid in the shape of the FullCalendar month view Tabler demos, built
   from Tabler's own table classes. FullCalendar itself carries its stylesheet
   inside its JavaScript and injects it as a style element, and writes inline
   style attributes as it lays out, both of which style-src 'self' blocks. This
   keeps the look without turning unsafe-inline on for every page in the hub.

   Layout only. Every color is Tabler's: table-active marks the chosen day,
   and the count on each day is a badge in bg-primary-lt, which brand.css
   already maps to Dynamix Orange.
   -------------------------------------------------------------------------- */

.booking-calendar {
  /* Seven equal columns whatever the cells hold, so the grid does not go
     ragged because one day says "12 times" and another says "1 time". */
  table-layout: fixed;
}

.booking-calendar th {
  font-weight: normal;
  color: var(--tblr-secondary);
  text-align: center;
}

.booking-calendar td {
  height: 4.5rem;
  vertical-align: top;
}

/* The whole cell is the target, not just the number, which is the difference
   between a calendar and a list of small links. */
.booking-calendar .booking-day {
  display: block;
  height: 100%;
  padding: 0.5rem;
  color: inherit;
  text-decoration: none;
}

.booking-calendar .booking-day-number {
  display: block;
  text-align: right;
}

/* A day with nothing on it, and the padding either side of the month. Faded
   rather than hidden, so the shape of the month stays readable. */
.booking-calendar .booking-day-off .booking-day-number {
  display: block;
  text-align: right;
  color: var(--tblr-secondary);
}

.booking-calendar .booking-day-empty {
  background: var(--tblr-bg-surface-secondary);
}

/* The divider between the calendar and the times, on the two column layout
   only. Below that breakpoint they stack and a right border would sit across
   the page. */
@media (min-width: 768px) {
  .border-end-md {
    border-right: 1px solid var(--tblr-border-color);
  }
}

@media print {
  /* The impersonation and announcement bars are screen furniture. */
  .banner-stack { display: none !important; }
}

/* ---------------------------------------------------------------------------
   Dragging on the inventory settings screen.

   hub.css rather than brand.css: a marketing site admin has no category tree
   to reorder, and the test for which file a rule belongs in is whether it
   would be true over there.
   --------------------------------------------------------------------------- */

/* The handle, and the row it sits in. Only shown to somebody who may reorder,
   so the cursor never promises something the page will refuse. */
.inv-drag {
  cursor: grab;
  color: var(--tblr-secondary);
  margin-right: .25rem;
}

.inv-drag:active { cursor: grabbing; }

/* Where a chip will land. Without a visible target the two columns read as two
   lists rather than one thing with two sides. */
.inv-droplist {
  min-height: 3rem;
  border: 1px dashed var(--tblr-border-color);
  border-radius: var(--tblr-border-radius);
}

.inv-drag-ghost {
  opacity: .4;
}

/* ---------------------------------------------------------------------------
   Rows that have to line up with the row below them.

   On the attributes screen a value is edited in a list row and added in a row
   underneath. The two carry the same fields and have to sit in the same
   columns, or the field somebody types into is not where the value appears.

   The list rows are inside a bordered drop list and the add row is outside it,
   so the indent is matched rather than inherited.
   --------------------------------------------------------------------------- */

/* Stands in for the drag handle, which the add row has no use for. Matches
   .inv-drag: a 20px icon plus its margin. */
.inv-drag-spacer {
  display: inline-block;
  flex: none;
  width: 20px;
  margin-right: .25rem;
}

/* The add row sits outside the drop list, so it takes the list item's own
   horizontal padding plus that border. */
.inv-addrow {
  padding-left: calc(var(--tblr-list-group-item-padding-x, 1rem) + 1px);
  padding-right: calc(var(--tblr-list-group-item-padding-x, 1rem) + 1px);
}

/* The trailing column the Add button sits in. Present and empty on the rows
   above it, because the value field can only be the same width in both rows if
   both rows spend the same space to the right of it. */
.inv-rowaction {
  flex: none;
  width: 4rem;
}

/* Text that keeps its line breaks: control statements, implementation
   narratives, scope notes. Tabler has no utility for pre-wrap, and an inline
   style would be discarded by style-src 'self' with nothing to say so. */
.text-prewrap {
  white-space: pre-wrap;
}

/* The four faces on the feedback page. Buttons, so the keyboard reaches them
   and the form posts without a script; the word under each face is part of
   the button, so it is never the emoji alone that says what a choice means. */
/* Four abreast inside Tabler's 30rem tight container, card padding
   included, which is what sets the width of one. */
.fb-faces {
  display: flex;
  justify-content: center;
  gap: 0.25rem;
  flex-wrap: wrap;
}
.fb-face {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  width: 5.5rem;
  padding: 0.75rem 0.25rem;
}
.fb-face .fb-emoji {
  font-size: 2.75rem;
  line-height: 1;
}
.fb-emoji-lg {
  font-size: 4rem;
  line-height: 1;
}
.fb-emoji-sm {
  font-size: 1.25rem;
  line-height: 1;
  vertical-align: -0.15em;
}

/* The subject column on the feedback page. A ticket title can run to a
   sentence and a half; capped so the columns beside it keep their width, the
   full title on hover. */
.fb-subject {
  max-width: 24rem;
}

/* The survey card. The question carries the brand color, which is the one
   place on the client side it is allowed to, because the page is the
   question. The pill holds the ticket number and client under it. */
/* The old page's look, in stylesheet form so the policy allows it: a dark
   ground with two warm blurs drifting slowly behind a frosted card, the
   question in brand orange, and the primary button carrying a glow. Scoped
   to the survey page; nothing else in the hub draws like this. Motion is
   off for anyone who asked their device for that. */
.fb-page {
  position: relative;
  min-height: 100vh;
  overflow: hidden;
  background: #0b0a09;
  color: #f6f1ea;
}
.fb-page::before,
.fb-page::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  pointer-events: none;
  z-index: 0;
}
/* The mood. The blur colors are registered properties so they can cross
   fade, and they shift from cold to warm as a face is hovered or chosen:
   the unhappy face cools the room, the very happy one heats it. Done with
   :has() rather than a script, because the page has none. The old page did
   this in JavaScript; a browser without :has() keeps the warm default. */
@property --fb-c1 {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(232, 119, 34, 0.32);
}
@property --fb-c2 {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(209, 86, 14, 0.26);
}
.fb-page {
  transition: --fb-c1 1.6s ease, --fb-c2 1.6s ease;
}
.fb-page::before {
  width: 55vmax;
  height: 55vmax;
  top: -15%;
  left: -12%;
  background: radial-gradient(circle at 40% 40%, var(--fb-c1), transparent 66%);
  animation: fb-drift-a 75s ease-in-out infinite;
}
.fb-page::after {
  width: 46vmax;
  height: 46vmax;
  bottom: -22%;
  right: -10%;
  background: radial-gradient(circle at 55% 45%, var(--fb-c2), transparent 66%);
  animation: fb-drift-b 90s ease-in-out -25s infinite;
}
/* Cold to hot. A hovered face wins over a chosen one, and the second and
   third steps carry the chosen mood as a class from the server. */
.fb-page.fb-mood-1,
.fb-page:has(.fb-face[value="1"].active),
.fb-page:has(.fb-face[value="1"]:hover) {
  --fb-c1: rgba(64, 108, 182, 0.30);
  --fb-c2: rgba(48, 76, 148, 0.26);
}
.fb-page.fb-mood-2,
.fb-page:has(.fb-face[value="2"].active),
.fb-page:has(.fb-face[value="2"]:hover) {
  --fb-c1: rgba(122, 138, 176, 0.28);
  --fb-c2: rgba(96, 108, 156, 0.24);
}
.fb-page.fb-mood-3,
.fb-page:has(.fb-face[value="3"].active),
.fb-page:has(.fb-face[value="3"]:hover) {
  --fb-c1: rgba(236, 136, 52, 0.30);
  --fb-c2: rgba(190, 120, 90, 0.24);
}
.fb-page.fb-mood-4,
.fb-page:has(.fb-face[value="4"].active),
.fb-page:has(.fb-face[value="4"]:hover) {
  --fb-c1: rgba(255, 122, 18, 0.38);
  --fb-c2: rgba(250, 90, 20, 0.28);
}
@keyframes fb-drift-a {
  0%   { transform: translate(0, 0) scale(1); }
  25%  { transform: translate(16%, 34%) scale(1.14); }
  50%  { transform: translate(30%, 10%) scale(0.94); }
  75%  { transform: translate(10%, -14%) scale(1.2); }
  100% { transform: translate(0, 0) scale(1); }
}
@keyframes fb-drift-b {
  0%   { transform: translate(0, 0) scale(1); }
  30%  { transform: translate(-14%, -36%) scale(1.12); }
  55%  { transform: translate(-30%, -8%) scale(0.9); }
  80%  { transform: translate(-6%, 16%) scale(1.06); }
  100% { transform: translate(0, 0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .fb-page::before,
  .fb-page::after {
    animation: none;
  }
}
.fb-main {
  position: relative;
  z-index: 1;
}
.fb-page .fb-card {
  border-radius: 1.75rem;
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid rgba(255, 255, 255, 0.09);
  box-shadow: 0 24px 80px -24px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(28px) saturate(1.5);
  -webkit-backdrop-filter: blur(28px) saturate(1.5);
}
.fb-page .btn-primary {
  border: 0;
  border-radius: 1rem;
  padding: 0.9rem 2rem;
  font-weight: 700;
  color: #fff;
  background-image: linear-gradient(135deg, #ffb066 0%, #e87722 52%, #d1560e 100%);
  box-shadow: 0 12px 36px -10px rgba(232, 119, 34, 0.45);
}
.fb-page .btn-primary:hover {
  box-shadow: 0 18px 48px -10px rgba(232, 119, 34, 0.45);
}
.fb-page .fb-face {
  color: #a89f94;
}
.fb-page .fb-face:hover,
.fb-page .fb-face.active {
  color: #f6f1ea;
}
.fb-page .alert-secondary {
  background: rgba(232, 119, 34, 0.14);
  border-color: rgba(232, 119, 34, 0.25);
  color: #cfc5b8;
}
.fb-title {
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--tblr-primary);
}
.fb-chip {
  font-size: 0.8125rem;
  padding: 0.45em 0.9em;
  letter-spacing: 0.04em;
}

/* The scanned serials on the receive page, newest on top. Capped so a
   pallet's worth does not push the Finish button off a five inch screen. */
.scan-rows {
  max-height: 22rem;
  overflow: auto;
}

/* ---------------------------------------------------------------------------
   The proposal document. One column the width of a page, printed as it
   reads on screen, with the toolbar gone and no break inside a section.
   --------------------------------------------------------------------------- */
.proposal {
  max-width: 52rem;
  margin: 0 auto;
}
.proposal-toolbar {
  border-bottom: 1px solid var(--tblr-border-color);
  background: var(--tblr-bg-surface);
}
.proposal-cover {
  padding-bottom: 1.5rem;
  margin-bottom: 2rem;
  border-bottom: 2px solid var(--tblr-primary);
}
.proposal-kicker {
  margin-top: 1.5rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  font-size: .8125rem;
  color: var(--tblr-secondary);
}
.proposal-title {
  font-size: 2rem;
  margin: .25rem 0 1rem;
}
.proposal-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: .25rem 1.5rem;
}
.proposal-section {
  margin-bottom: 2rem;
  break-inside: avoid;
}
.proposal-section h2 {
  font-size: 1.25rem;
  margin-bottom: .75rem;
}
.proposal-group {
  font-size: 1rem;
  margin: 1rem 0 .5rem;
}
.proposal-totals td, .proposal-totals th { font-size: 1.0625rem; }
.proposal-quoted td { font-weight: 600; }
@media print {
  .proposal-page { background: #fff; }
  .proposal { max-width: none; }
  .proposal-toolbar { display: none !important; }
  .proposal-cover { border-bottom-color: #000; }
}

/* The handheld pages. One column, big controls, no rail. */
.bench-bar {
  min-height: 3rem;
}
.bench-page .form-control-lg,
.bench-page .form-select-lg,
.bench-page .btn-lg {
  font-size: 1.25rem;
}

/* A badge on a printed page: the code at a size a scan engine reads from a
   card, the rest of the page gone. */
.bench-badge-qr {
  max-width: 14rem;
}
@media print {
  .bench-badge {
    border: 1px solid #000;
    box-shadow: none;
  }
}

/* The badge in the profile modal. */
.profile-qr {
  max-width: 12rem;
}

/* Draggable column headers: the hand says so. */
table[data-column-order] thead th {
  cursor: grab;
}

/* A row that is a link. */
tr[data-href] {
  cursor: pointer;
}
tr[data-href]:focus-visible {
  outline: 2px solid var(--tblr-primary);
  outline-offset: -2px;
}
