/* Lconnect customer portal design system.
 *
 * This lived as a 162-line inline <style> in base.html, re-sent uncached on
 * every response. It is real CSS rather than utility classes for the same two
 * reasons core/static/core/css/lead_table.css gives: page weight, and the fact
 * that these rules repeat per row and per page.
 *
 * NOTE: the portal does not extend django-unfold and loads none of its CSS, so
 * the precompiled-Tailwind trap that governs the back office does not apply
 * here. Write ordinary CSS.
 */

/* ---- Tokens ----------------------------------------------------------
 *
 * THREE STATES, TWO PALETTES.
 *
 *   data-theme="light"   the light palette below
 *   data-theme="dark"    the dark palette below
 *   no attribute         light, unless the device says dark
 *
 * The third is "match my device", and it is the ABSENCE of an attribute
 * rather than a value of its own -- which is what lets the media query below
 * stay the only place that consults the OS. base.html omits the attribute
 * for ClientUser.Theme.SYSTEM.
 *
 * THE DARK PALETTE IS WRITTEN TWICE, and there is no way around it: plain CSS
 * cannot share a custom-property block between a selector and a media query,
 * and this file has no build step to do it for us. PortalThemeTests asserts
 * the two blocks declare the same token NAMES, so one can never quietly grow
 * a token the other lacks -- which is the failure this shape invites.
 *
 * `color-scheme` is a real property here, not only the <meta> in base.html.
 * Once a customer can force light on a dark laptop, the meta tag is no longer
 * telling the truth, and without the property their scrollbars, <select>
 * popups, date pickers and autofill all stay dark against a white page.
 */
:root{
  color-scheme:light;
  --bg:#f7f7f7; --surface:#ffffff; --surface-2:#fafafa;
  /* The rail is LIGHT in light mode. It used to be near-black in both
   * themes, which is why the portal read as dark whatever the palette said:
   * 250px of black down the side of a white page wins the argument. */
  --sidebar:#ffffff; --sidebar-ink:#525252;
  --sidebar-ink-strong:#171717; --sidebar-active:#fff2f0;
  --sidebar-border:#e5e5e5; --sidebar-hover:rgba(0,0,0,.04);
  --border:#e5e5e5; --ink:#171717; --muted:#525252; --faint:#a3a3a3;
  --primary:#ff3a28; --primary-600:#d62a1a; --primary-50:#fff2f0; --primary-ink:#d62a1a;
  /* Brand accents and text-bearing controls need different contrast pairs. */
  --action-bg:#d62a1a; --action-hover:#b02416; --action-ink:#fff;
  --ok:#137333; --ok-bg:#e7f6ee; --warn:#92400e; --warn-bg:#fef3e2; --danger:#b91c1c; --danger-bg:#fdeaea;
  --info:#1d4ed8; --info-bg:#e8f0fe;
  --radius:12px; --radius-sm:8px; --radius-pill:999px;
  --shadow:0 1px 2px rgba(16,24,40,.04), 0 4px 16px rgba(16,24,40,.06);
  --shadow-sm:0 1px 2px rgba(16,24,40,.06);
  --sidebar-w:250px;
  --tabbar-h:0px;
}

/* An explicit choice of dark. */
:root[data-theme="dark"]{
  color-scheme:dark;
  --bg:#0a0a0a; --surface:#171717; --surface-2:#1f1f1f;
  --sidebar:#080808; --sidebar-ink:#a3a3a3;
  --sidebar-ink-strong:#fafafa; --sidebar-active:#262626;
  --sidebar-border:rgba(255,255,255,.08); --sidebar-hover:rgba(255,255,255,.06);
  --border:#2a2a2a; --ink:#f5f5f5; --muted:#a3a3a3; --faint:#737373;
  --primary:#ff6e5c; --primary-600:#ff3a28; --primary-50:#2a1310; --primary-ink:#ff8f7a;
  --action-bg:#ff8f7a; --action-hover:#ff9e91; --action-ink:#171717;
  --ok:#34d399; --ok-bg:#0f2a20; --warn:#fbbf24; --warn-bg:#2a2011; --danger:#f87171; --danger-bg:#2a1414;
  --info:#60a5fa; --info-bg:#12203a;
  --shadow:0 1px 2px rgba(0,0,0,.3), 0 8px 24px rgba(0,0,0,.35);
  --shadow-sm:0 1px 2px rgba(0,0,0,.3);
}

/* No choice made, and the device asks for dark. `:not([data-theme])` is the
 * load-bearing part: somebody who has explicitly chosen LIGHT keeps it on a
 * dark laptop, which is the whole point of offering the choice. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme]){
    color-scheme:dark;
    --bg:#0a0a0a; --surface:#171717; --surface-2:#1f1f1f;
    --sidebar:#080808; --sidebar-ink:#a3a3a3;
    --sidebar-ink-strong:#fafafa; --sidebar-active:#262626;
    --sidebar-border:rgba(255,255,255,.08); --sidebar-hover:rgba(255,255,255,.06);
    --border:#2a2a2a; --ink:#f5f5f5; --muted:#a3a3a3; --faint:#737373;
    --primary:#ff6e5c; --primary-600:#ff3a28; --primary-50:#2a1310; --primary-ink:#ff8f7a;
    --action-bg:#ff8f7a; --action-hover:#ff9e91; --action-ink:#171717;
    --ok:#34d399; --ok-bg:#0f2a20; --warn:#fbbf24; --warn-bg:#2a2011; --danger:#f87171; --danger-bg:#2a1414;
    --info:#60a5fa; --info-bg:#12203a;
    --shadow:0 1px 2px rgba(0,0,0,.3), 0 8px 24px rgba(0,0,0,.35);
    --shadow-sm:0 1px 2px rgba(0,0,0,.3);
  }
}

/* One lockup, chosen by theme. Both ship; CSS hides one. A <picture> with
 * media="(prefers-color-scheme:dark)" cannot see data-theme, so it would be
 * right for "match my device" and wrong for an explicit choice.
 *
 * `img.logo.logo-dark` rather than `.logo-dark`, because `.brand .logo` below
 * sets `display:block` at (0,2,0) and a bare class is (0,1,0) -- so the first
 * version of this hid nothing and BOTH lockups rendered, one under the other.
 * Caught by measuring what the browser actually displayed rather than by
 * reading the rule. */
img.logo.logo-dark{display:none}
:root[data-theme="dark"] img.logo.logo-light{display:none}
:root[data-theme="dark"] img.logo.logo-dark{display:block}
@media (prefers-color-scheme:dark){
  :root:not([data-theme]) img.logo.logo-light{display:none}
  :root:not([data-theme]) img.logo.logo-dark{display:block}
}

*{box-sizing:border-box}
html,body{margin:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
     background:var(--bg); color:var(--ink); font-size:15px; line-height:1.5; -webkit-font-smoothing:antialiased;}
a{color:var(--primary-ink); text-decoration:none}
a:hover{text-decoration:underline}
svg{flex-shrink:0}

/* Alpine hides x-cloak elements until it boots; without this the mobile drawer
   flashes open on every page load. */
[x-cloak]{display:none !important}

/* Several controls here are custom-styled and would otherwise lose the UA
   outline entirely, so keyboard focus is restated explicitly. */
:focus-visible{outline:2px solid var(--primary); outline-offset:2px}

/* Present to a screen reader, absent on screen -- for a label whose job the
   placeholder and the surrounding layout already do visually. */
/* The `hidden` attribute has to beat our own display rules.
   `[hidden]` is an attribute selector (0,1,0) -- the same specificity as a
   class -- so `.btn{display:inline-flex}` later in this file simply wins, and
   `el.hidden = true` on a button does nothing at all. Every script here that
   hides something toggles `.hidden`, so this is load-bearing rather than
   defensive. */
[hidden]{display:none !important}

.visually-hidden{position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0}

.skip-link{position:absolute; left:-9999px; top:0; background:var(--surface); color:var(--ink);
  padding:10px 16px; border-radius:0 0 var(--radius-sm) 0; z-index:100}
.skip-link:focus{left:0}

/* ---- App shell ---- */
.layout{display:grid; grid-template-columns:var(--sidebar-w) 1fr; min-height:100vh}
/* The border is what separates a white rail from a near-white page. In
 * dark mode the token is a faint white alpha and the edge all but vanishes,
 * which is correct: there the background difference does the separating. */
.sidebar{background:var(--sidebar); color:var(--sidebar-ink); display:flex; flex-direction:column;
         position:sticky; top:0; height:100vh; padding:20px 14px; overflow-y:auto;
         border-right:1px solid var(--sidebar-border)}
.brand{display:flex; align-items:center; gap:10px; padding:6px 10px 20px; font-size:19px; font-weight:700; color:var(--sidebar-ink-strong)}
.brand .logo{height:26px; width:auto; display:block; flex-shrink:0}
.nav{display:flex; flex-direction:column; gap:2px; flex:1}
.nav a{display:flex; align-items:center; gap:11px; padding:10px 12px; border-radius:var(--radius-sm);
       color:var(--sidebar-ink); font-weight:500; transition:background .12s, color .12s}
.nav a:hover{background:var(--sidebar-hover); color:var(--sidebar-ink-strong); text-decoration:none}
/* On light the active row is a brand tint (--sidebar-active is
 * --primary-50 there); on dark it is a lifted grey. The inset bar is the
 * constant, and it is what survives both. */
.nav a.active{background:var(--sidebar-active); color:var(--sidebar-ink-strong);
  box-shadow:inset 3px 0 var(--primary)}
.nav a.active{font-weight:600}
.nav a.active svg{opacity:1}
.nav a svg{width:18px; height:18px; opacity:.75}
/* Section label between nav groups. */
.nav .nav-sep{margin:12px 12px 4px; font-size:11px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--faint); font-weight:600}
/* Unread counter riding on a nav link. */
.nav a .count{margin-left:auto; background:var(--action-bg); color:var(--action-ink); border-radius:var(--radius-pill);
  font-size:11px; font-weight:700; padding:1px 7px; min-width:19px; text-align:center}

.side-foot{border-top:1px solid var(--sidebar-border); padding-top:14px; margin-top:8px}
.side-user{display:flex; align-items:center; gap:10px; padding:4px 8px}
.avatar{width:34px; height:34px; border-radius:50%; background:var(--action-bg); color:var(--action-ink);
        display:grid; place-items:center; font-weight:700; font-size:14px; flex-shrink:0}
.side-user .who{overflow:hidden}
.side-user .who b{display:block; color:var(--sidebar-ink-strong); font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.side-user .who a{font-size:12px; color:var(--sidebar-ink)}
/* Logout is a POST button, styled to read as the link it replaced. */
.logout-btn{background:none; border:0; padding:0; margin:0; cursor:pointer;
  font-size:12px; font-family:inherit; color:var(--sidebar-ink); text-align:left}
.logout-btn:hover{text-decoration:underline}

.main{min-width:0; display:flex; flex-direction:column}
/* Wider than the 1000px it started at: these pages carry 7-8 column tables
   (contract ref, title, type, status, period, count, document) and at the
   old width every one of them wrapped to two or three lines. */
.content{max-width:1440px; width:100%; margin:0 auto; padding:34px 32px 60px}

/* Mobile top bar and drawer scrim: hidden until the breakpoint. */
.topbar{display:none; align-items:center; gap:12px; background:var(--sidebar);
  color:var(--sidebar-ink-strong); padding:10px 14px; position:sticky; top:0; z-index:30}
.topbar .logo{height:22px; width:auto}
.hamburger{background:none; border:0; color:inherit; padding:6px; cursor:pointer; display:grid; place-items:center; width:44px; height:44px}
.hamburger svg{width:22px; height:22px}
.scrim{display:none; position:fixed; inset:0; background:rgba(0,0,0,.5); z-index:39}

/* ---- Page header ---- */
.page-head{display:flex; align-items:flex-end; justify-content:space-between; gap:16px; margin-bottom:24px; flex-wrap:wrap}
h1{font-size:24px; font-weight:700; margin:0; letter-spacing:-.01em}
.page-head .sub{color:var(--muted); margin-top:4px; font-size:14px}
h2{font-size:15px; font-weight:600; margin:28px 0 12px; color:var(--ink)}
.crumb{color:var(--muted); font-size:13px; margin-bottom:14px}
.crumb a{color:var(--muted)}
/* A page that reads as one column — a form, a survey, a notice. The width cap
   used to sit on the card alone, which pinned heading and card to the left of
   the 1440px content area with half a desktop screen empty beside them. The
   whole column (crumb, heading, card) moves to the middle instead, so the cap
   and the centring live in one place and every one-column page sits the same
   way. */
.narrow-page{max-width:640px; margin:0 auto}

/* ---- Sign-in ---- */
/* The guest column: lockup, card, one line of help. It sits a little below
   the top rather than dead centre, so a phone keyboard does not push the
   card off the screen. These were inline styles on login.html; they live
   here so the reset pages can take the same shape without copying them. */
.auth{margin-top:12vh}
.auth-brand{text-align:center; margin-bottom:24px}
/* `display:block` at (0,2,0) so light and dark lay out identically -- an
   inline image gets a baseline gap a block does not. It loses, by design, to
   img.logo.logo-dark{display:none} at (0,2,1) and to the themed rules at
   (0,3,1) near the top of this file: those decide WHICH lockup shows, this
   only decides how the shown one sits. */
.auth-brand .logo{display:block; height:40px; width:auto; margin:0 auto 10px}
.auth-sub{color:var(--muted); font-size:14px}
.auth-card{padding:26px 24px}
.auth-card .stack > label:first-of-type{margin-top:0}
.auth-submit{width:100%; margin-top:20px}
.auth-links{text-align:center; margin:16px 0 0}
.auth-foot{text-align:center; margin-top:16px}

/* ---- Cards / panels ---- */
.card{background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow-sm)}
.pad{padding:20px 22px}
.panel{background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow-sm)}

/* ---- Stacked document (the privacy notice) ----
   .card carries no margin of its own: everywhere else in the portal a card
   either stands alone or sits in a grid that owns the gap. A page that is
   nothing but a column of cards has neither, so they render edge to edge.
   This gives the column its own gap, and pulls up the first heading -- h2's
   28px top margin is meant to separate it from the text above it, and there
   is no text above it at the top of a card. */
.privacy-doc{display:flex; flex-direction:column; gap:18px}
.privacy-doc .card.pad > h2:first-child{margin-top:0}

/* ---- Stat grid ---- */
.stats{display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:14px; margin-bottom:12px}
.stat{background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:18px 18px; box-shadow:var(--shadow-sm)}
.stat .ic{width:38px; height:38px; border-radius:var(--radius); background:var(--primary-50); color:var(--primary-ink);
          display:grid; place-items:center; margin-bottom:12px}
.stat .ic svg{width:20px; height:20px}
.stat .val{font-size:28px; font-weight:700; letter-spacing:-.02em; font-variant-numeric:tabular-nums}
.stat .lbl{color:var(--muted); font-size:13px; margin-top:2px}
a.stat{color:inherit; display:block}
a.stat:hover{text-decoration:none; border-color:var(--primary)}
/* A tile that POSTs. The cover tile has to be a button rather than a link --
   pressing it records the request and alerts the account manager, and a GET
   that mails somebody fires on a refresh and a crawler -- so it needs the
   button reset a link does not: browsers give <button> their own font, a
   centred label and a fixed width. .stat-form carries the grid cell so the
   form itself does not become one. */
.stat-form{display:contents}
button.stat{display:block; width:100%; text-align:left; font:inherit;
  color:inherit; cursor:pointer}
button.stat .ic{display:grid}
button.stat .val,
button.stat .lbl{display:block}
/* COLOURED AT REST, NOT ONLY ON HOVER. This tile sits in a row of three that
   are pure figures, and wearing the same 1px grey border as its neighbours it
   read as a fourth figure -- the customer was told they had no cover and
   given nothing that looked like a way to do anything about it. Border in the
   brand red over the palest tint of it, so the one thing in the row that DOES
   something is the one thing that is coloured.

   Hover deepens what is already there rather than introducing it: a tile
   whose only affordance appears on hover has no affordance at all on a
   touch screen, which is where a customer opens this. */
button.stat{border-color:var(--primary); background:var(--primary-50)}
button.stat:hover{border-color:var(--primary-600)}
/* The icon chip is normally primary-50 on surface; on this tile that is the
   ground it sits on, so it would vanish. Inverted rather than re-tinted. */
button.stat .ic{background:var(--surface)}
/* The label is the instruction now, not a caption, so it stops being muted.
   The arrow is a pseudo-element rather than a character inside the msgid: no
   translator has to carry it, and it cannot be lost in a catalogue entry. */
button.stat .lbl{color:var(--primary-ink); font-weight:600}
button.stat .lbl::after{content:"\00a0\2192"}

/* ---- Tables ---- */
.table-wrap{overflow-x:auto; border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow-sm)}
table{width:100%; border-collapse:collapse; background:var(--surface); min-width:520px}
/* One line per cell. A table of references, dates and statuses is scanned
   down its columns, and a wrapped cell breaks that scan -- the row grows and
   the eye loses the line. The wrapper already scrolls horizontally, so long
   content is reachable rather than lost. Cells that hold real prose opt back
   in with .wrap. */
th,td{text-align:left; padding:13px 16px; font-size:14px; border-bottom:1px solid var(--border);
      white-space:nowrap}
th.wrap,td.wrap{white-space:normal}
/* The secondary line under a title (a service description, a survey blurb) is
   prose, so it wraps and is bounded rather than stretching the column. */
td .small{white-space:normal; max-width:46ch}
th{background:var(--surface-2); color:var(--muted); font-weight:600; font-size:12px}
tbody tr:last-child td{border-bottom:none}
tbody tr{transition:background .1s}
tbody tr:hover{background:var(--surface-2)}
tfoot td,tfoot th{border-top:2px solid var(--border); border-bottom:none; font-weight:700; background:transparent}
.right{text-align:right}
/* Numbers in a column line up digit by digit. */
td.right,th.right{font-variant-numeric:tabular-nums}
/* "In progress now" beside a status badge: a dot and a word, not a second
   blue pill saying what the first one already says. */
.live-now{display:inline-flex; align-items:center; gap:6px; margin-left:8px;
  font-size:12px; color:var(--info); white-space:nowrap}
.live-now::before{content:""; width:7px; height:7px; border-radius:50%; background:currentColor}
td.strong{font-weight:600}

/* ---- Invoices ---- */
/* A register, not a set of cards -- the reasoning is in invoice_list.html.
   The document buttons sit in the last column, so this table must not
   scroll sideways: that is what put the Buy button off-screen on the
   packages page, and it did the same to these buttons at 1024px. Six
   columns need about 900px, and beside the 250px sidebar the content column
   only has that from 1200px up -- so below 1200px each row turns into a
   compact block instead: number and amount on the first line, the two dates
   under them with their labels restored from data-label, the status beside,
   and the document buttons across the bottom. */
.invoice-late{display:block; font-size:12px; font-weight:600; color:var(--danger)}
.invoice-actions .btn+.btn{margin-left:6px}
@media(max-width:1200px){
  .invoices,.invoices tbody,.invoices tr,.invoices td{display:block}
  .invoices{min-width:0}
  .invoices thead{position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%)}
  .invoices tr{display:grid; grid-template-columns:minmax(0,1fr) auto; gap:4px 14px; padding:14px 16px; border-bottom:1px solid var(--border)}
  .invoices tr:last-child{border-bottom:0}
  .invoices td{padding:0; border:0; white-space:normal}
  .invoices .invoice-number{font-size:15px}
  .invoices .invoice-amount{grid-column:2; grid-row:1; font-size:15px}
  .invoices .invoice-date,.invoices .invoice-due{font-size:13px; color:var(--muted)}
  .invoices td[data-label]::before{content:attr(data-label) ": "}
  /* One piece: "12 dias de atraso" wrapped as "12 / dias de atraso" inline. */
  .invoices .invoice-late{display:inline-block; margin-left:6px; white-space:nowrap}
  .invoices .invoice-status{grid-column:2; grid-row:2; text-align:right}
  .invoices .invoice-actions{grid-column:1/-1; display:flex; gap:8px; margin-top:8px}
  .invoices .invoice-actions .btn{flex:1 1 auto; max-width:220px; margin:0}
  /* The dash that stands for "no document" is a column's placeholder; with
     no column it is a stray mark under the row. */
  .invoices .invoice-actions-none{display:none}
}

/* ---- Badges ---- */
.badge{display:inline-flex; align-items:center; gap:5px; padding:3px 10px; border-radius:var(--radius-pill); font-size:12px; font-weight:600; white-space:nowrap}
.badge::before{content:""; width:6px; height:6px; border-radius:50%; background:currentColor; opacity:.9}
/* .b-mitigated is "impact contained -- still being resolved": work under way,
   so it reads as in progress. It and .b-waiting_third_party had no rule at
   all and drew as bare bold text beside every other status's pill;
   PortalBadgeCoverageTests now fails for any ticket status left unlisted. */
.b-open,.b-in_progress,.b-mitigated,.b-active,.b-sent{background:var(--info-bg); color:var(--info)}
/* .b-pending is ClientChangeRequest.Status.PENDING -- distinct from
   .b-pending_approval above it, which is a quote waiting on a colleague. An
   unlisted status renders a badge with no background at all, which looks
   deliberate and is how a state quietly stops being visible. */
.b-waiting,.b-waiting_third_party,.b-draft,.b-planned,.b-pending,.b-pending_approval{background:var(--warn-bg); color:var(--warn)}
.b-resolved,.b-closed,.b-done,.b-accepted{background:var(--ok-bg); color:var(--ok)}
.b-urgent,.b-high,.b-declined,.b-expired,.b-terminated,.b-rejected{background:var(--danger-bg); color:var(--danger)}
.b-normal,.b-low,.b-muted,.b-superseded,.b-converted{background:var(--surface-2); color:var(--muted)}
/* Invoice payment states */
.b-pay-paid{background:var(--ok-bg); color:var(--ok)}
.b-pay-overdue{background:var(--danger-bg); color:var(--danger)}
.b-pay-pending{background:var(--warn-bg); color:var(--warn)}
.b-pay-credit{background:var(--info-bg); color:var(--info)}
.badge.plain::before{display:none}

/* Commercial records keep their facts and actions together at every width. */
.record-list{display:grid; gap:16px}
.sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0}
.record{padding:22px; min-width:0; overflow-wrap:anywhere}
.record h2{font-size:17px; line-height:1.4; margin:0}
.record-head{display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap}
.record-ref{font-size:12px; color:var(--muted); margin-bottom:4px}
.record-facts{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(110px,100%),1fr)); gap:16px 24px; margin:20px 0 0}
.record-facts dt{font-size:12px; color:var(--muted); margin-bottom:4px}
.record-facts dd{margin:0; font-size:14px; font-variant-numeric:tabular-nums}
.record-facts .record-amount dd{font-size:21px; font-weight:700; line-height:1.3}
.record-money{font-variant-numeric:tabular-nums; font-weight:600}
.record-overdue{font-size:12px; color:var(--danger); margin-top:4px}
.record-disclosure{margin-top:20px; border-top:1px solid var(--border)}
.record-disclosure summary{min-height:44px; padding:12px 0; cursor:pointer; font-size:14px; font-weight:600}
.record-disclosure summary:hover{color:var(--primary-ink)}
/* Support prices (support_pricing*.html): the switcher between the three
   pages, one section per 360 tier, and each "Know more". The disclosure is a
   card of its own there, so it drops .record-disclosure's divider and opens
   with the summary as its first line. */
.pricing-pages{margin:0 0 20px}
.pricing-tier{margin-bottom:28px}
.pricing-more{margin:0 0 16px; border-top:0; padding-top:4px; padding-bottom:4px}
.pricing-more[open]{padding-bottom:20px}
.pricing-more h3{font-size:14px; margin:16px 0 8px}
.pricing-more .table-wrap{box-shadow:none; border-radius:var(--radius-sm)}
.pricing-list{margin:0; padding-left:18px; font-size:14px; line-height:1.55}
.pricing-list li{margin:2px 0}
/* Several tables stacked on one page (a category or a tier each) must line
   up column for column. Auto layout sizes each table to its own content, so
   "Por hora" sat at a different x in every card. Fixed layout: the first
   column takes the same share everywhere and the rest split what is left
   evenly, whatever is in them. The name column may wrap -- under a fixed
   width a nowrap name would run into the price beside it -- and the minimum
   width keeps a phone scrolling the table rather than crushing it. */
.pricing-table{table-layout:fixed; min-width:640px}
.pricing-table th:first-child{width:36%}
.pricing-table td:first-child{white-space:normal; overflow-wrap:anywhere}
.pricing-table th{white-space:normal}
/* The packages page (support_packages.html) holds a quantity box and a
   "+ IVA" total that need more room than the Ask button, so its columns are
   set explicitly rather than split evenly. Same widths on every category's
   table, so they still line up. */
.packs-table{min-width:720px}
.packs-table th:nth-child(1){width:32%}
.packs-table th:nth-child(2){width:13%}
.packs-table th:nth-child(3){width:21%}
.packs-table th:nth-child(4){width:19%}
.packs-table th:nth-child(5){width:15%}
.record-items{list-style:none; padding:0; margin:0}
.record-items>li{padding:16px 0; border-top:1px solid var(--border)}
.record-items .record-facts{margin-top:12px}
.record-items strong{font-size:14px}
.record-total{display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; padding-top:16px; border-top:1px solid var(--border); font-variant-numeric:tabular-nums}
.record-actions{display:flex; gap:10px; flex-wrap:wrap; margin:20px 0 0}
.record-section{margin-top:24px}
@media(max-width:600px){.record{padding:18px}.record-facts{gap:14px}.record-actions .btn{flex:1 1 auto}}

/* ---- Buttons ---- */
.btn{display:inline-flex; align-items:center; justify-content:center; gap:8px; background:var(--action-bg); color:var(--action-ink); border:1px solid transparent;
     min-height:44px; padding:9px 16px; border-radius:var(--radius-sm); font-weight:600; font-size:14px; cursor:pointer; transition:background .12s, transform .04s}
.btn:hover{background:var(--action-hover); text-decoration:none}
.btn:active{transform:translateY(1px)}
.btn svg{width:16px; height:16px}
.btn.ghost{background:var(--surface); color:var(--ink); border-color:var(--border)}
.btn.ghost:hover{background:var(--surface-2)}
.btn.danger{background:var(--danger)}
.btn.sm{min-height:38px; padding:7px 12px; font-size:13px}
.btn[disabled],.btn.is-disabled{opacity:.5; pointer-events:none}
.btn-row{display:flex; gap:10px; flex-wrap:wrap; margin-top:20px}

/* ---- Toolbar: search + filter chips above a list ---- */
.toolbar{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:16px}
.toolbar form{display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin:0}
.toolbar input[type=search],.toolbar select{width:auto; min-width:190px}
.chips{display:flex; gap:6px; flex-wrap:wrap}
.chip{display:inline-flex; align-items:center; padding:5px 12px; border-radius:var(--radius-pill); font-size:13px;
  border:1px solid var(--border); background:var(--surface); color:var(--muted)}
.chip:hover{text-decoration:none; background:var(--surface-2)}
.chip.on{background:var(--action-bg); border-color:var(--action-bg); color:var(--action-ink)}
/* A count riding inside a chip (the cover picker names how much equipment
   each site holds). .muted is a fixed grey, which all but disappears on the
   selected chip -- so on .on it follows the chip's own colour and is held
   back with opacity instead. Written against the token rather than a literal
   so it keeps working through a palette change. */
.chip .muted{font-variant-numeric:tabular-nums}
.chip.on .muted{color:inherit; opacity:.75}

/* ---- Pagination ---- */
.pager{display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:16px; flex-wrap:wrap}
.pager .links{display:flex; gap:6px}
.pager .links a,.pager .links span{padding:6px 12px; border-radius:8px; border:1px solid var(--border);
  background:var(--surface); font-size:13px; color:var(--ink)}
.pager .links a:hover{background:var(--surface-2); text-decoration:none}
.pager .links .here{background:var(--action-bg); border-color:var(--action-bg); color:var(--action-ink)}
.pager .count{color:var(--muted); font-size:13px}

/* ---- Forms ---- */
.lc-scroll-region:focus-visible{outline:2px solid var(--primary-ink); outline-offset:3px}
.main :is(input,select,textarea,button,a,summary){scroll-margin-block:88px}
input::placeholder,textarea::placeholder{color:var(--muted); opacity:1}
.project-progress{display:flex; align-items:center; gap:12px; margin-top:20px; font-size:13px; color:var(--muted)}
.project-progress progress{flex:1; min-width:0; height:8px; border:0; border-radius:6px; overflow:hidden; background:var(--surface-2); accent-color:var(--primary-ink)}
.project-progress progress::-webkit-progress-bar{background:var(--surface-2)}
.project-progress progress::-webkit-progress-value{background:var(--primary-ink); border-radius:6px}
.project-progress progress::-moz-progress-bar{background:var(--primary-ink)}
.record h3{font-size:14px; margin:20px 0 10px}
.msg .close,.ann .close{min-width:36px; min-height:36px}
/* Applied to every single-choice select: the class existed but no form used
   it, so each one still opened the OS list. */
@supports(appearance:base-select){
  select:not([multiple]),select:not([multiple])::picker(select){appearance:base-select}
  select:not([multiple])::picker(select){border:1px solid var(--border); border-radius:10px; background:var(--surface); color:var(--ink); padding:6px; box-shadow:var(--shadow); max-height:320px}
  select:not([multiple]) option{padding:10px 12px; min-height:44px; border-radius:6px; white-space:normal; overflow-wrap:anywhere}
  select:not([multiple]) option:checked{background:var(--primary-50); color:var(--primary-ink)}
  select:not([multiple]) option:hover,select:not([multiple]) option:focus{background:var(--surface-2)}
}
@media(max-width:600px){.toolbar .btn,.chip,.pager .links a{min-height:44px; display:inline-flex; align-items:center}.msg .close,.ann .close{min-width:44px; min-height:44px}}
form.stack label{display:block; font-weight:600; font-size:13px; margin:14px 0 6px; color:var(--ink)}
input,select,textarea{width:100%; padding:10px 12px; border:1px solid var(--border); border-radius:10px;
     font:inherit; background:var(--surface); color:var(--ink); transition:border-color .12s, box-shadow .12s}
/* A ring you can see in both themes: --primary-50 was #fff2f0 on white and
   #2a1310 on near-black, so only a 1px border change was left. */
input:focus,select:focus,textarea:focus{outline:2px solid var(--primary); outline-offset:1px;
  border-color:var(--primary); box-shadow:none}
textarea{resize:vertical}
input[type=file]{padding:8px; background:var(--surface-2)}
input[type=radio],input[type=checkbox]{width:auto}
.hint{color:var(--muted); font-size:12px; margin-top:6px}

/* A quantity picked inside a table row (support packages). Inputs are
   width:100% by default, which in a table cell stretches the column to the
   width of its heading and pushes the money columns off the card, so this
   one is sized to the digits it holds. The unit sits beside it because
   "3" alone does not say packs or hours. */
.qty-box{display:inline-flex; align-items:center; gap:6px; white-space:nowrap}
.qty{width:66px; padding:6px 8px; text-align:right; font-size:14px}
/* The floor is the widest unit word we render (pt "pacotes", measured at
   44.6px) so the boxes line up down the column: right-aligned in the cell
   with nothing holding it, "horas" pulled the ad-hoc row's box 13px right of
   the packs' above it. A longer word in some other language overflows the
   floor rather than being clipped -- only that row moves. */
.qty-unit{color:var(--muted); font-size:13px; min-width:46px; text-align:left}

/* Validation. Django renders field errors as <ul class="errorlist">, so style
   that directly rather than asking every template to unpack it by hand. */
.errorlist{list-style:none; margin:6px 0 0; padding:0; color:var(--danger); font-size:13px; font-weight:500}
.errorlist li{margin-top:2px}
.form-errors{background:var(--danger-bg); color:var(--danger); border-radius:12px; padding:12px 16px;
  margin-bottom:16px; font-size:14px; font-weight:500}
.form-errors ul{margin:6px 0 0; padding-left:18px}

/* ---- Messages ---- */
.msg{display:flex; align-items:center; gap:10px; padding:12px 16px; border-radius:12px; margin-bottom:16px; font-size:14px; font-weight:500}
.msg svg{width:18px;height:18px}
.msg .close{margin-left:auto; background:none; border:0; color:inherit; cursor:pointer; padding:0 2px; font-size:18px; line-height:1; width:auto}
.msg.success{background:var(--ok-bg); color:var(--ok)}
.msg.error{background:var(--danger-bg); color:var(--danger)}
.msg.warning{background:var(--warn-bg); color:var(--warn)}
.msg.info,.msg.debug{background:var(--info-bg); color:var(--info)}

/* ---- Announcement banners ---- */
.ann{padding:12px 28px; border-bottom:1px solid var(--border)}
.ann .inner{max-width:1440px; margin:0 auto; display:flex; align-items:center; gap:10px}
.ann .close{margin-left:auto; background:none; border:0; color:inherit; cursor:pointer; font-size:18px; line-height:1; padding:0 2px; width:auto}
.ann-info{background:var(--info-bg); color:var(--info)}
.ann-success{background:var(--ok-bg); color:var(--ok)}
.ann-warning{background:var(--warn-bg); color:var(--warn)}
.ann-error{background:var(--danger-bg); color:var(--danger)}

/* ---- Empty ---- */
/* ---- Blocked notice: an action refused, with the way out ----
   Not `.empty`: that is a dashed placeholder for a list with nothing in it,
   centred and quiet. This is a refusal the customer has to act on, so it keeps
   the solid card and puts a marker beside the sentence that explains it. Warn
   rather than danger — nothing has gone wrong, the cover simply is not there. */
.notice-top{display:flex; gap:14px; align-items:flex-start}
.notice-top .ic{flex:none; width:40px; height:40px; border-radius:var(--radius); display:grid;
  place-items:center; background:var(--warn-bg); color:var(--warn)}
.notice-top .ic svg{width:20px; height:20px}
.notice-top p{margin:0}

/* Conditions to scan, not prose: a bare <ul> in a card brings the browser's
   40px indent and a bullet heavier than anything else on the page. */
.reqs{list-style:none; margin:10px 0 0; padding:0; display:grid; gap:9px}
.reqs li{position:relative; padding-left:22px; font-size:14px}
.reqs li::before{content:""; position:absolute; left:6px; top:7px; width:6px; height:6px;
  border-radius:50%; background:var(--faint)}

/* Footnote inside a card: bled to the card's edges so it reads as a footer
   rather than a last paragraph. The negative margins mirror `.card.pad`, so
   they move together if that padding ever changes. */
.card-note{margin:20px -22px -20px; padding:14px 22px; border-top:1px solid var(--border);
  border-radius:0 0 var(--radius) var(--radius); background:var(--surface-2);
  color:var(--muted); font-size:13px; line-height:1.55;
  display:flex; gap:8px 20px; align-items:baseline; justify-content:space-between;
  flex-wrap:wrap}
.card-note a{white-space:nowrap}

/* ---- Campaign offer: the two ways to take one up ----
   Asking for a call and ringing us are peers, so they sit on one line. The
   phone number is a real button rather than a line of text under the card: a
   customer who does not want to wait should not have to hunt for it. */
.offer-act{display:flex; gap:8px; align-items:center; flex-wrap:wrap}
.offer-act .btn svg{flex:none}

/* The callback form, revealed by "I'm interested". Bordered off from the offer
   above it so it reads as a step, not as more of the card. */
.interest{margin-top:14px; padding-top:14px; border-top:1px solid var(--border)}
.interest-row{display:grid; grid-template-columns:minmax(180px,240px) 1fr; gap:12px}
.interest label{display:block; margin-bottom:4px; font-size:13px; font-weight:600}
.interest .btn-row{margin-top:12px}
@media (max-width:640px){
  /* Two side-by-side fields at phone width leave neither usable. */
  .interest-row{grid-template-columns:1fr}

  /* The term ladder on the cover page. Three columns need 520px and a phone
     gives 360, so .table-wrap scrolls -- which put the BUY BUTTON off-screen
     at x=524 in a 400px viewport. A table you scroll sideways to reach the
     only action on the page is a table nobody buys from, so at this width
     each term becomes a block: term, price, button, stacked and all visible.
     Scoped to .tiers rather than .table-wrap, because horizontal scroll is
     the right answer for the wide data tables elsewhere in the portal. */
  /* `table{min-width:520px}` is what actually forces the scroll -- width:auto
     alone loses to it, which is why the button stayed off-screen after the
     cells were already stacking. */
  .tiers table, .tiers tbody, .tiers tr, .tiers td{display:block; width:auto}
  .tiers table{min-width:0}
  .tiers thead{display:none}
  .tiers tr{padding:12px 0; border-bottom:1px solid var(--border)}
  .tiers tr:last-child{border-bottom:0}
  .tiers td{padding:2px 0; text-align:left}
  .tiers td.right{text-align:left}
  .tiers td .btn{margin-top:8px; width:100%; justify-content:center}
}

.empty{background:var(--surface); border:1px dashed var(--border); border-radius:var(--radius); padding:44px 24px; text-align:center; color:var(--muted)}
.empty .ic{width:46px; height:46px; border-radius:var(--radius); background:var(--surface-2); color:var(--faint); display:grid; place-items:center; margin:0 auto 14px}
.empty .ic svg{width:24px;height:24px}

.muted{color:var(--muted)}
.small{font-size:13px}
.nowrap{white-space:nowrap}

/* ---- Ticket thread ----
   Read as a conversation: our side on the left, the customer's own messages on
   the right, the way every chat client arranges them. The side answers "who
   said this" before any name is read, which is why the bubble no longer
   stretches the full width -- a full-width bubble has no side. */
.thread{display:flex; flex-direction:column; gap:14px; margin:16px 0}
.bubble{display:flex; gap:10px; align-items:flex-end; max-width:100%}
.bubble .av{width:30px;height:30px;border-radius:50%;display:grid;place-items:center;
  font-weight:700;font-size:12px;color:var(--surface);flex-shrink:0;background:var(--muted)}
.bubble.staff .av{background:var(--action-bg); color:var(--action-ink)}
.bubble .body{background:var(--surface); border:1px solid var(--border);
  border-radius:14px 14px 14px 4px; padding:10px 13px; box-shadow:var(--shadow-sm);
  max-width:min(680px, 78%); min-width:0}
.bubble .meta{font-size:11px; color:var(--muted); margin-bottom:4px}
.bubble .text{white-space:pre-wrap; word-break:break-word}

/* The customer's own messages. `mine` rather than a colour swap: the position
   is the signal, and the tint only reinforces it. */
.bubble.mine{flex-direction:row-reverse; align-self:flex-end}
.bubble.mine .av{background:var(--action-bg); color:var(--action-ink)}
.bubble.mine .body{background:var(--primary-50); border-color:var(--primary-50);
  border-radius:14px 14px 4px 14px}
.bubble.mine .meta{text-align:right}

.attach{display:inline-flex; align-items:center; gap:6px; margin-top:8px; font-size:13px}

/* ---- Support workspace ---- */
.request-list{list-style:none; margin:0; padding:0; border:1px solid var(--border);
  border-radius:var(--radius); background:var(--surface)}
.request-list li+li{border-top:1px solid var(--border)}

/* The request list: one line per request (ticket_list.html).

   Nine columns are about 1,300px of content, which beside the 250px sidebar
   is a 1440px screen. So it narrows least-useful-first rather than scrolling
   sideways: Type goes below 1440px, the opening date below 1280px (Updated
   stays), and below 1024px each row becomes a block -- the invoices
   pattern, labels restored from data-label.

   One line means nothing wraps: a long subject or a long status is cut with
   an ellipsis and carries its full text in its title. The subject is the one
   column that gives way, so it has no nowrap width of its own to defend --
   max-width:0 plus width:100% is what lets a table cell shrink below its
   content and truncate instead of widening the table. */
.tickets th,.tickets td{padding:11px 12px; font-size:13px; white-space:nowrap}
.tickets .ticket-ref a{font-weight:600; font-size:12px}
.tickets .ticket-subject{width:100%; max-width:0}
.tickets .ticket-subject a{display:block; overflow:hidden; text-overflow:ellipsis;
  color:var(--ink); font-weight:600}
.tickets .ticket-status .badge{max-width:190px; overflow:hidden; text-overflow:ellipsis;
  display:inline-block; vertical-align:middle}
.tickets .ticket-person{max-width:160px; overflow:hidden; text-overflow:ellipsis}
@media(max-width:1440px){.tickets .col-type{display:none}}
@media(max-width:1280px){.tickets .col-opened{display:none}}
@media(max-width:1024px){
  .tickets,.tickets tbody,.tickets tr,.tickets td{display:block}
  .tickets{min-width:0}
  .tickets thead{position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%)}
  .tickets tr{display:grid; grid-template-columns:minmax(0,1fr) auto; gap:4px 14px;
    padding:14px 16px; border-bottom:1px solid var(--border)}
  .tickets tr:last-child{border-bottom:0}
  .tickets td{padding:0; border:0; max-width:none; white-space:normal; font-size:13px}
  .tickets .ticket-ref{grid-column:1; grid-row:1}
  .tickets .ticket-priority{grid-column:2; grid-row:1; text-align:right}
  /* max-width:none HERE, not only on `.tickets td` above: the desktop
     max-width:0 is a two-class rule and outranks it, and a subject held to
     zero width wraps one word per line -- a 1,700px row at 400px. */
  .tickets .ticket-subject{grid-column:1; grid-row:2; width:auto; max-width:none}
  .tickets .ticket-subject a{white-space:normal; overflow-wrap:anywhere; font-size:15px}
  .tickets .ticket-status{grid-column:2; grid-row:2; text-align:right}
  .tickets .ticket-status .badge{max-width:160px}
  .tickets .col-opened{display:block}
  .tickets td[data-label]{grid-column:1/-1; color:var(--muted)}
  .tickets td[data-label]::before{content:attr(data-label) ": "}
}

/* ---- Registers: the .tickets pattern for any wide table ----
   The beta-test crawl found three tables that scrolled sideways at a normal
   laptop width and so hid their last column: the IT estate's per-site and
   flat lists (Edit / Remove off-screen even at 1440px) and the hour packs on
   Horas de assistência (Status cut off at 1280px). Each now says which
   columns give way, instead of growing until the wrapper scrolls.

   - table-layout:fixed: the widths come from the header cells, so a long
     hostname or model is cut with an ellipsis (full text in `title`) rather
     than widening the table -- and every type's table under one site lines
     its columns up with the next one, which auto layout never did.
   - A header cell without a width shares what is left; give the column that
     should give way no width.
   - .hide-1440 / .hide-1280 drop a secondary column below that width.
   - Below 1024px each row is a block: .lead first, .side beside it on the
     right, every cell with data-label underneath as "Label: value". */
.register{table-layout:fixed; min-width:0}
.register td{overflow:hidden; text-overflow:ellipsis}
.register td.wrap{overflow:visible; overflow-wrap:anywhere}
.register th.pick{width:40px}
.register .badge{max-width:100%; overflow:hidden; text-overflow:ellipsis; vertical-align:middle}
.register .end{text-align:right}
@media(max-width:1440px){.register .hide-1440{display:none}}
@media(max-width:1280px){.register .hide-1280{display:none}}
@media(max-width:1024px){
  .register,.register tbody,.register tr,.register td{display:block; width:auto}
  .register thead{position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%)}
  .register tr{display:grid; grid-template-columns:minmax(0,1fr) auto; gap:4px 14px;
    padding:14px 16px; border-bottom:1px solid var(--border)}
  .register tr:last-child{border-bottom:0}
  .register td{padding:0; border:0; white-space:normal; overflow:visible; font-size:13px}
  .register td.lead{grid-column:1; grid-row:1; font-weight:600; font-size:15px;
    overflow-wrap:anywhere}
  .register td.side{grid-column:2; grid-row:1; text-align:right}
  .register td.pick{grid-column:2; grid-row:2; text-align:right; width:auto; padding:0}
  .register td[data-label]{grid-column:1/-1; color:var(--muted)}
  .register td[data-label]::before{content:attr(data-label) ": "}
  .register .hide-1440,.register .hide-1280{display:block}
  .register td.end{grid-column:1/-1; text-align:left; margin-top:6px}
  .register td.is-empty{display:none}   /* no "Detail:" line with nothing after it */
}

/* A table inside a card is already framed by the card: a second border and
   shadow drew a box inside a box on seven pages. */
.card > .table-wrap{border:0; border-radius:0; box-shadow:none}

/* Long unbroken text -- a URL, a path, a serial -- wraps instead of pushing
   the page sideways on a phone: a help article, how a request was resolved,
   a survey answer, an announcement. The last is a flex child, which also
   needs min-width:0 before it will shrink at all. */
article.card p,.card .text,.question .text{overflow-wrap:anywhere}
.ann .inner > div{min-width:0; overflow-wrap:anywhere}

/* The two people on a ticket, side by side; stacked on a phone. */
.support-people{display:grid; grid-template-columns:repeat(2, minmax(0,1fr));
  gap:18px; margin-bottom:16px}
.support-person-name{font-weight:600; margin-top:2px; overflow-wrap:anywhere}
.support-contact{display:flex; flex-wrap:wrap; gap:8px; margin-top:10px}
.support-contact .btn{font-size:13px; padding:6px 12px; max-width:100%;
  overflow-wrap:anywhere}
/* The promises on a request: one column each, the same type as the people
   above; stacked on a phone. */
.support-sla{display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:18px}
@media (max-width:640px){
  .support-people{grid-template-columns:1fr}
  .support-sla{grid-template-columns:1fr}
}
.support-tags{display:flex; align-items:center; flex-wrap:wrap; gap:8px}
.support-detail{max-width:1000px; margin-inline:auto; min-width:0}
.support-detail h1,.support-request .text{overflow-wrap:anywhere}
.support-section-head{display:flex; align-items:center; justify-content:space-between;
  flex-wrap:wrap; gap:12px; margin-top:28px}
.support-section-head h2{margin:0}
.support-section-head a{font-size:13px; padding:10px 0}
.support-detail .bubble .body{max-width:min(680px,85%); padding:14px 16px}
.support-detail .bubble .meta{font-size:12px; line-height:1.6}
.support-detail .attach{max-width:100%; overflow-wrap:anywhere}
.support-form-grid{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0 20px}
.support-form-grid>div{min-width:0}
.support-form input[type=file]::file-selector-button{font:inherit; color:var(--ink);
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-sm);
  padding:8px 12px; margin-right:12px; cursor:pointer}
@media(max-width:700px){
  .support-form-grid{grid-template-columns:1fr}
}

/* ---- Reply composer ----
   One bar, the way a chat input is: write, attach, send. It was a card with a
   heading, a labelled textarea, a bare file input and a button underneath --
   four stacked controls for what is one action. */
/* `--tabbar-h` is 0px above the breakpoint and 62px below it, where a fixed
   tab bar owns the bottom of the viewport -- without the offset the composer
   sticks underneath it and the send button lands behind the nav. `.content`
   already pads for the same reason. */
.composer{position:sticky; bottom:calc(12px + var(--tabbar-h)); background:var(--surface);
  border:1px solid var(--border); border-radius:22px; box-shadow:var(--shadow);
  padding:8px 8px 8px 16px; margin-top:18px}
.composer:focus-within{border-color:var(--primary-ink)}
.composer form{display:flex; align-items:flex-end; gap:8px; margin:0}
/* Grows with the message instead of scrolling inside a fixed box; capped so a
   long paste cannot push the send button off screen. */
.composer textarea{flex:1; min-width:0; border:0; background:none; padding:8px 0; resize:none;
  min-height:24px; max-height:180px; overflow-y:auto; font:inherit; color:var(--ink)}
.composer textarea:focus{outline:none; box-shadow:none; border:0}
.composer .icon-btn{width:44px; height:44px; border-radius:50%; border:0; flex-shrink:0;
  display:grid; place-items:center; cursor:pointer; background:var(--surface-2);
  color:var(--muted); padding:0}
.composer .icon-btn:hover{background:var(--border); color:var(--ink)}
.composer .icon-btn svg{width:18px; height:18px}
.composer .send{background:var(--action-bg); color:var(--action-ink)}
.composer .send:hover{background:var(--action-hover); color:var(--action-ink)}
/* Keep the native file input in the tab order and over the paperclip so
   choosing files works with either a keyboard or a pointer, without JS. */
.composer .file-picker{position:relative}
.composer .file-picker input[type=file]{position:absolute; inset:0; width:100%; height:100%;
  margin:0; padding:0; opacity:0; cursor:pointer}
.composer .file-picker:focus-within{outline:2px solid var(--primary-ink); outline-offset:2px}
/* Chosen files, listed above the bar rather than as a bare "2 files selected". */
.composer .files{display:flex; flex-wrap:wrap; gap:6px; padding:0 0 8px}
.composer .files span{display:inline-flex; align-items:center; gap:6px; font-size:12px;
  background:var(--surface-2); border:1px solid var(--border); border-radius:20px;
  max-width:100%; overflow-wrap:anywhere; padding:3px 10px; color:var(--muted)}
.composer .files span.bad{background:var(--danger-bg); color:var(--danger); border-color:var(--danger-bg)}
/* The same picker on the new-request form (ticket_new.html): our button with
   the native input over it, the chosen files listed under it as chips. */
.file-field .file-picker{position:relative; overflow:hidden}
.file-field .file-picker input[type=file]{position:absolute; inset:0; width:100%; height:100%;
  margin:0; padding:0; opacity:0; cursor:pointer}
.file-field .file-picker:focus-within{outline:2px solid var(--primary-ink); outline-offset:2px}
.file-field .files{display:flex; flex-wrap:wrap; gap:6px; padding:8px 0 0}
.file-field .files:empty{display:none}
.file-field .files span{display:inline-flex; align-items:center; gap:6px; font-size:12px;
  background:var(--surface-2); border:1px solid var(--border); border-radius:20px;
  max-width:100%; overflow-wrap:anywhere; padding:3px 10px; color:var(--muted)}
.file-field .files span.bad{background:var(--danger-bg); color:var(--danger); border-color:var(--danger-bg)}
.composer .hint{padding:0 0 6px; margin:0}

/* ---- Survey ---- */
.question{margin-bottom:28px}
.question .q-text{font-weight:600; display:block; margin-bottom:8px}
.question .req{color:var(--danger)}
.choices{display:flex; flex-direction:column; gap:6px}
.choices label{display:flex; align-items:center; gap:8px; font-weight:400; margin:0}
.rating{display:flex; gap:10px}
.rating label{display:flex; flex-direction:column; align-items:center; gap:4px; cursor:pointer; font-weight:400; margin:0}

/* ---- Error pages ---- */
.errpage{max-width:520px; margin:8vh auto; text-align:center}
.errpage .code{font-size:52px; font-weight:700; letter-spacing:-.03em; color:var(--faint)}
.errpage h1{margin:6px 0 10px}
.errpage p{color:var(--muted); margin:0 0 22px}

/* ---- Charts ---- */
.chart-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr)); gap:14px; margin:12px 0}
.chart-grid>*{min-width:0}
/* Height lives here, not on the canvas: Chart.js with maintainAspectRatio off
   sizes to its container, and a canvas with no bounded parent grows on every
   resize event. */
.chart-box{position:relative; width:100%; height:260px}
.chart-legend{list-style:none; margin:14px 0 0; padding:0; display:flex; flex-wrap:wrap; gap:6px 18px}
.chart-legend li{display:flex; align-items:center; gap:7px; font-size:13px; color:var(--muted)}
.chart-legend .swatch{width:10px; height:10px; border-radius:3px; flex-shrink:0}

/* ---- Progress meter ---- */
.meter{width:88px; height:6px; border-radius:6px; background:var(--surface-2);
  border:1px solid var(--border); overflow:hidden; display:block}
/* Blue while all is well -- brand red read as the .spent state below, so a
   pack a third used looked like a warning (beta test, 8 Oct 2026). */
.meter span{display:block; height:100%; background:var(--info)}
/* A meter that is nearly spent should say so before the number is read. */
.meter.warn span{background:var(--warn)}
.meter.spent span{background:var(--danger)}
.meter.wide{width:100%; height:8px}

/* The `.help` span that used to live here is gone. It styled
   `<span class="help" title="...">?</span>`, and its own comment told the next
   reader to write exactly that -- but a `title` tooltip renders NOTHING on a
   phone, and most portal customers are on one. The live control is the shared
   `{% help %}` component (core/static/core/css/help.css), which 14 portal
   templates use. core/tests/test_help_component.py treats `class="help"` as a
   forbidden marker, so the CSS was an instruction to fail that test. */

/* ---- Responsive ---- */
.tabbar{display:none}

@media (max-width:860px){
  :root{--tabbar-h:62px}
  .layout{grid-template-columns:1fr}
  .topbar{display:flex}
  /* Off-canvas drawer. The sidebar keeps its own scroll so a long nav still
     reaches the sign-out block. */
  .sidebar{position:fixed; top:0; left:0; bottom:0; width:270px; z-index:40;
    transform:translateX(-100%); transition:transform .18s ease; height:100dvh}
  body.drawer-open .sidebar{transform:none}
  body.drawer-open .scrim{display:block}
  body.drawer-open{overflow:hidden}
  .content{padding:22px 18px calc(28px + var(--tabbar-h))}
  h1{font-size:21px}
  .page-head{align-items:flex-start}
  .ann{padding:12px 18px}
  /* Primary destinations stay one thumb away. */
  .tabbar{display:flex; position:fixed; left:0; right:0; bottom:0; z-index:35;
    background:var(--surface); border-top:1px solid var(--border); height:var(--tabbar-h);
    padding-bottom:env(safe-area-inset-bottom)}
  .tabbar a{flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:3px; font-size:11px; color:var(--muted); font-weight:600; position:relative}
  .tabbar a:hover{text-decoration:none}
  .tabbar a.active{color:var(--primary-ink)}
  .tabbar svg{width:20px; height:20px}
  .tabbar .count{position:absolute; top:8px; left:50%; margin-left:4px; background:var(--action-bg);
    color:var(--action-ink); border-radius:20px; font-size:10px; padding:0 5px; font-weight:700}
  input,select,textarea,.composer textarea,input.qty{font-size:16px}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition-duration:0s !important; animation-duration:.01ms !important;
    animation-iteration-count:1 !important; scroll-behavior:auto !important}
}

@media print{
  .sidebar,.topbar,.tabbar,.ann,.pager,.toolbar,.btn,.skip-link{display:none !important}
  .layout{grid-template-columns:1fr}
  .content{max-width:none; padding:0}
  .card,.table-wrap{box-shadow:none; border-color:#ccc}
}

/* ---- Confirm dialog ----
   Replaces native confirm(), which the OS draws and no stylesheet reaches --
   see the note in base.html. <dialog> is a real element, so it takes the
   portal's tokens like anything else here. */
dialog.confirm {
    max-width: 420px;
    width: calc(100% - 32px);
    padding: 22px 24px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    box-shadow: 0 12px 40px rgba(0, 0, 0, .28);
}

dialog.confirm::backdrop {
    background: rgba(0, 0, 0, .45);
}

dialog.confirm h2 {
    margin: 0 0 18px;
    font-size: 16px;
    font-weight: 650;
    line-height: 1.45;
}

dialog.confirm .confirm-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* ---- Estate: a site, and what is in it ----
   Sites are <details>, so expanding needs no JavaScript and is keyboard
   operable for free. The browser's own triangle is hidden and replaced with a
   chevron that rotates, because the default marker is tiny, sits outside the
   padding, and on Safari cannot be styled at all -- three ways for the one
   affordance that tells somebody the row opens to go unnoticed. */
.site{margin-bottom:14px}
.site > summary{display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:16px 22px; cursor:pointer; border-radius:var(--radius); list-style:none;
  font-weight:600; transition:background .12s ease}
.site > summary::-webkit-details-marker{display:none}
.site > summary::marker{content:""}
.site > summary:hover{background:var(--surface-2)}
.site > summary:focus-visible{outline:2px solid var(--primary); outline-offset:-2px}
.site[open] > summary{border-bottom:1px solid var(--border); border-radius:var(--radius) var(--radius) 0 0}
.site-title{display:flex; align-items:center; gap:10px; min-width:0}
.site-caret{width:16px; height:16px; flex:none; color:var(--muted); transition:transform .15s ease}
.site[open] .site-caret{transform:rotate(90deg)}
/* On a phone the one-line summary ("24 PC / portátil · 3 Switch · … 32
   equipamentos") ran 145px past the screen, nowrap, beside the site name.
   It drops onto its own line under the name and wraps there. */
@media(max-width:600px){
  .site > summary{flex-wrap:wrap; gap:6px 12px; padding:14px 16px}
  .site > summary .site-count{flex-basis:100%; white-space:normal; padding-left:26px}
  .site > summary .site-total{white-space:nowrap}
}
.site-count{color:var(--muted); font-weight:500; white-space:nowrap; font-size:13px}
/* The site's own facts: address, and how many people work there. */
.site-meta{display:flex; flex-wrap:wrap; gap:6px 20px; padding:14px 22px 0; color:var(--muted); font-size:13px}
.site-meta b{color:var(--ink); font-weight:600}
.site-body{padding:14px 22px 0}
/* The table sits inside a card already -- a second border around it reads as a
   box in a box. */
.site-body .table-wrap{border-radius:var(--radius-sm); box-shadow:none}
/* A kind of equipment within a site. The heading carries the type and the
   count, so the table underneath does not repeat either. */
.type-group{margin-bottom:18px}
.type-group:last-child{margin-bottom:0}
.type-head{display:flex; align-items:baseline; justify-content:space-between;
  gap:16px; padding:0 2px 8px}
.type-name{font-weight:600; font-size:14px}
.type-count{color:var(--muted); font-size:13px; white-space:nowrap}
/* The actions for one site, ranged RIGHT. They are what you do after reading
   the equipment above them, so they sit under the end of that table rather
   than under its first column, where they competed with the data for the
   left edge the eye reads down. Wraps back to the left when there is no room
   for them on one line. */
.site-actions{display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  padding:16px 22px; justify-content:flex-end}

/* Renaming a place, as an icon on the place's own name. Held back until the
   row is hovered or the link focused, so it never competes with the name --
   but always reachable by keyboard, which a hover-only control is not. */
.site-edit{display:inline-grid; place-items:center; width:26px; height:26px;
  border-radius:8px; color:var(--faint); flex-shrink:0}
.site-edit svg{width:14px; height:14px}
.site-edit:hover{background:var(--surface-2); color:var(--ink); text-decoration:none}
.site-edit:focus-visible{background:var(--surface-2); color:var(--ink)}
.site summary:hover .site-edit{color:var(--muted)}

/* What kinds of thing the whole estate holds, on one line. A row of counts
   rather than a table: it is a summary somebody reads in a glance on the way
   to the list below, and eight one-line rows would push that list off the
   screen. Wraps rather than scrolls -- there is no column to keep aligned. */
.type-totals{display:flex; flex-wrap:wrap; gap:8px 20px}
.type-total{white-space:nowrap; color:var(--muted); font-size:14px}
.type-total b{color:var(--ink); font-weight:650}
/* The type breakdown inside a collapsed site row, so a shut accordion still
   says what is in it. Quieter and smaller than the standalone row above, and
   the total keeps its own weight so the eye still lands on it first. */
.site-types{color:var(--muted); font-size:13px; font-weight:400; margin-right:14px}
.site-total{font-weight:650}
/* A site with nothing in it is not worth a card and a caret. It still has to
   be there -- an empty site is exactly where somebody adds their first
   machine -- but it should not take the same room as one holding 32 things. */
.site.is-empty{background:transparent; box-shadow:none; border-style:dashed}
.site.is-empty > summary{padding-top:12px; padding-bottom:12px}
.site.is-empty .site-title{color:var(--muted); font-weight:500}

/* ---- Add equipment, in a dialog ---- */
dialog.sheet{border:1px solid var(--border); border-radius:var(--radius); background:var(--surface);
  color:var(--ink); padding:0; width:min(760px, calc(100vw - 32px)); max-height:calc(100vh - 64px);
  box-shadow:0 12px 40px rgba(0,0,0,.28)}
dialog.sheet::backdrop{background:rgba(0,0,0,.45)}
dialog.sheet .sheet-head{display:flex; align-items:flex-start; justify-content:space-between; gap:16px;
  padding:20px 22px; border-bottom:1px solid var(--border)}
dialog.sheet .sheet-head h2{margin:0; font-size:17px}
dialog.sheet .sheet-body{padding:20px 22px; overflow-y:auto; max-height:calc(100vh - 260px)}
dialog.sheet .sheet-foot{display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  padding:16px 22px; border-top:1px solid var(--border)}
.sheet-close{background:none; border:0; color:var(--muted); font-size:22px; line-height:1;
  cursor:pointer; padding:0 4px}
.sheet-close:hover{color:var(--ink)}

/* .cover-cta is gone. It was a full-width band in --primary-50 behind a 1px
   --primary border -- the only saturated object and the only non-surface
   background on the whole page -- offering exactly what the stat tile above it
   and the per-site button below it already offered. The offer now lives in the
   tile (button.stat, above), which is where the status was anyway. */

/* A site whose entry we have not confirmed yet. A dot in front of the name
   rather than words after it: the site list is read down its left edge, and a
   phrase on one row and nothing on the next stops the names lining up. */
.pending-mark{color:var(--warn); font-size:10px; vertical-align:middle; margin-right:2px}
/* "Ask for help" sits beside Edit in the actions cell. Quieter than Edit --
   it is the rarer of the two and should not pull the eye down every row. */
.ask-help{color:var(--muted); margin-right:12px}
.ask-help:hover{color:var(--primary)}
/* Remove sits after Edit, as quiet as Ask for help: it is the rarest of the
   three, and a destructive word in the brand red down every row would read as
   the thing the page wants you to do. It turns red only under the pointer. */
.row-remove{color:var(--muted); margin-left:12px}
.row-remove:hover{color:var(--danger)}

/* ---- Selecting equipment ----
   The checkbox column is as narrow as the box, so the name column does not
   move right by more than it has to. */
th.pick,td.pick{width:1%; padding-right:0}
.pick input{width:18px; height:18px; margin:0; vertical-align:middle;
  accent-color:var(--primary-ink); cursor:pointer}
/* The selection bar: pinned to the bottom of the screen while something is
   selected, clearing the phone tab bar the way .composer does. A card, so it
   reads as part of the page rather than as a browser banner. */
.bulk-bar{position:sticky; bottom:calc(12px + var(--tabbar-h)); z-index:20;
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:10px 16px; margin-top:18px; padding:10px 12px 10px 18px;
  background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius); box-shadow:var(--shadow)}
.bulk-count{font-weight:600; white-space:nowrap}
.bulk-actions{display:flex; flex-wrap:wrap; align-items:center; gap:8px}
/* No font-size here: below the breakpoint every control is forced to 16px so
   iOS does not zoom on focus, and a rule this specific would undo that. */
.bulk-actions select{width:auto; min-width:170px; min-height:38px; padding:7px 10px}

.lc-bar-chart{margin:16px 0}
.lc-bar-chart h2{margin:0 0 16px}
.lc-bar-chart summary{cursor:pointer;min-height:44px;align-content:center;font-weight:600}
/* The site accordion below draws its own chevron and kills the native marker
   (.site > summary). This block kept the browser's triangle, so the page
   showed two different disclosure affordances stacked. One page, one. */
.lc-bar-chart summary{list-style:none}
.lc-bar-chart summary::-webkit-details-marker{display:none}
.lc-bar-chart summary::marker{content:""}
.lc-bar-chart[open] summary{margin-bottom:12px}
.lc-bar-row+.lc-bar-row{margin-top:18px}
.lc-bar-label{display:flex;justify-content:space-between;flex-wrap:wrap;gap:4px 16px;margin-bottom:6px;font-size:14px}
.lc-bar-label a{min-height:24px;overflow-wrap:anywhere;color:var(--primary-ink)}
.lc-bar-label>span:last-child{font-variant-numeric:tabular-nums}
.lc-bar-chart meter{display:block;width:100%;height:16px;border:0;border-radius:8px;background:var(--surface-2);appearance:none}
.lc-bar-chart meter::-webkit-meter-bar{height:16px;border:0;border-radius:8px;background:var(--surface-2)}
.lc-bar-chart meter::-webkit-meter-optimum-value{border-radius:8px;background:var(--info)}
.lc-bar-chart meter::-moz-meter-bar{border-radius:8px;background:var(--info)}

/* ---- Theme switch ----
   Three submit buttons in the sidebar foot. A segmented control rather than a
   <select>, because a native select popup is drawn by the OS and is one of
   the things that goes out of step when a customer forces light on a dark
   machine -- see the `color-scheme` note at the top of this file.

   Sized down deliberately: this is a preference, not a feature. It should be
   findable and never the loudest thing on the rail. */
.theme-switch{margin-top:12px; padding:0 8px}
/* On the sign-in page: under the card, centred, no wider than on the rail. */
.auth .theme-switch{max-width:240px; margin:28px auto 0; padding:0; text-align:center}
.theme-switch-label{display:block; font-size:11px; text-transform:uppercase;
  letter-spacing:.06em; color:var(--faint); font-weight:600; margin-bottom:6px}
.theme-switch-options{display:flex; gap:2px; background:var(--sidebar-hover);
  border-radius:var(--radius-sm); padding:2px}
.theme-switch-options button{flex:1; width:auto; padding:5px 4px; border:0; background:none;
  color:var(--sidebar-ink); font-size:11px; font-weight:600; cursor:pointer;
  border-radius:calc(var(--radius-sm) - 2px); transition:background .12s, color .12s}
.theme-switch-options button:hover{color:var(--sidebar-ink-strong)}
/* The chosen one. `aria-current` carries the same fact to a screen reader --
   colour alone would not. */
.theme-switch-options button.is-on{background:var(--sidebar); color:var(--sidebar-ink-strong);
  box-shadow:var(--shadow-sm)}

/* Privacy and the app version under the client's name ({% app_version %}):
   one quiet line, the tag icon in the sidebar's own ink. */
.side-legal{display:flex; align-items:center; gap:2px; margin-top:6px; opacity:.7}
.side-legal .small{font-size:12px; color:var(--sidebar-ink)}
.side-legal .lc-help-btn{color:var(--sidebar-ink); opacity:1}
