/* core.css - the revamp's shared components and tokens.
   Every selector is scoped under the class tsa on <html> (or is a :root
   token), and base.html links this sheet only on a page that opts in:
   {% block html_class %}tsa{% endblock %} in the page template. Keep it that
   way when editing - an unscoped rule here restyles every opted-in page, and
   a page's own rules must be scoped the same way to compete with these. */
:root{
  --ground:#0A0F1E; --surface:#111827; --raise:#16202F;
  --accent:#00C896; --accent-ink:#002E22; --info:#378ADD;
  --text:#FFFFFF; --muted:#8892A4; --line:#1E2A3A; --line-2:#2A3A50;
  --neg:#FF6B6B; --warn:#F2B544; --serious:#FF6B6B;
  --r-surface:14px; --r-control:9px; --r-pill:999px;
  --ctl:46px;      /* form controls and full-size buttons: one row, one height */
  --ctl-sm:36px;   /* compact controls inside a card header or a table row */
}
.tsa *, .tsa *::before, .tsa *::after{box-sizing:border-box;}
.tsa [hidden]{display:none !important;}
.tsa body{margin:0; background:var(--ground); color:var(--text);
     font-family:Montserrat,"Helvetica Neue",Arial,sans-serif; font-weight:500;
     line-height:1.55; -webkit-font-smoothing:antialiased;}
.tsa h1, .tsa h2, .tsa h3{margin:0; font-weight:800; letter-spacing:-.02em; line-height:1.2;}
.tsa p, .tsa ul{margin:0;}
.tsa ul{list-style:none; padding:0;}
.tsa a{color:var(--info); text-decoration:none;}
.tsa a:hover{text-decoration:underline; text-underline-offset:3px;}
.tsa button{font-family:inherit;}
html.tsa::selection, .tsa ::selection{background:var(--accent); color:var(--accent-ink);}
html.tsa{scrollbar-width:thin; scrollbar-color:#24354B var(--ground);}
html.tsa::-webkit-scrollbar, .tsa ::-webkit-scrollbar{width:11px; height:11px;}
html.tsa::-webkit-scrollbar-track, .tsa ::-webkit-scrollbar-track{background:transparent;}
html.tsa::-webkit-scrollbar-thumb, .tsa ::-webkit-scrollbar-thumb{background:#24354B; border-radius:99px; border:3px solid var(--ground);}
.tsa :where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{
  outline:2px solid var(--accent); outline-offset:2px; border-radius:4px;}
.tsa .vh{position:absolute; width:1px; height:1px; margin:-1px; overflow:hidden;
    clip:rect(0 0 0 0); white-space:nowrap;}
/* ------------------------------------------------------------ controls - */
/* The top bar, sidebar and footer that used to start here are in shell.css:
   base.html's chrome is one element, and five prototypes had drawn it five
   ways. */
.tsa .btn{display:inline-flex; align-items:center; justify-content:center; gap:8px;
     font-size:.86rem; font-weight:700; white-space:nowrap; padding:0 18px;
     height:var(--ctl); border-radius:var(--r-control); border:1px solid transparent;
     cursor:pointer;
     transition:transform .1s, background .13s, border-color .13s, color .13s;}
.tsa .btn:hover{text-decoration:none;}
.tsa .btn:active{transform:translateY(1px);}
.tsa .btn-primary{background:var(--accent); color:var(--accent-ink);}
.tsa .btn-primary:hover{background:#15D9A9;}
.tsa .btn-ghost{background:transparent; color:var(--text); border-color:var(--line-2);}
.tsa .btn-ghost:hover{border-color:#3C5170; background:rgba(255,255,255,.04);}
.tsa .btn-danger{background:transparent; color:var(--neg); border-color:rgba(255,107,107,.42);}
.tsa .btn-danger:hover{background:rgba(255,107,107,.1); border-color:var(--neg);}
.tsa .btn-xs{padding:0 13px; height:var(--ctl-sm); font-size:.79rem;}
.tsa .btn[disabled]{opacity:.5; pointer-events:none;}
.tsa .iconbtn{width:var(--ctl-sm); height:var(--ctl-sm); display:inline-flex; align-items:center;
         justify-content:center; border-radius:8px; border:1px solid var(--line-2);
         background:transparent; color:var(--muted); cursor:pointer;
         transition:color .13s, border-color .13s, background .13s;}
.tsa .iconbtn:hover{color:var(--text); border-color:#3C5170; background:rgba(255,255,255,.04);}
/* a class, not an inline width - inline beats a media query and this button
   stayed 26x26 under a thumb */
.tsa .iconbtn.sm{width:26px; height:26px; font-size:.86rem;}
/* ------------------------------------------------------------ page ----- */
.tsa .wrap{max-width:1400px; padding:clamp(20px,2.6vw,34px) clamp(15px,2.2vw,28px); min-width:0;}
.tsa .top{display:flex; align-items:flex-start; justify-content:space-between; gap:18px 20px;
     flex-wrap:wrap; margin-bottom:clamp(18px,2.2vw,26px);}
.tsa .top > div:first-child{flex:1 1 430px; min-width:0;}
.tsa .top h1{font-size:clamp(1.42rem,2.5vw,1.95rem);}
.tsa .top .sub{color:var(--muted); margin-top:7px; font-size:.9rem; max-width:70ch;}
.tsa .top .acts{display:flex; gap:9px; flex-wrap:wrap; align-items:center;}
.tsa .view{display:none;}
.tsa .view.on{display:block;}
/* ------------------------------------------------------------ cards ---- */
.tsa .card{background:var(--surface); border:1px solid var(--line);
      border-radius:var(--r-surface); min-width:0;}
.tsa .card > header{display:flex; align-items:center; justify-content:space-between;
               gap:12px 16px; flex-wrap:wrap; padding:clamp(16px,2vw,20px) clamp(16px,2.2vw,22px);
               border-bottom:1px solid var(--line);}
.tsa .card > header h2{display:flex; align-items:center; gap:10px; font-size:1rem;}
.tsa .card > header .top-acts{display:flex; align-items:center; gap:9px; flex-wrap:wrap;}
.tsa .card > header h2 i{color:var(--accent); font-size:1.1rem; flex:none;}
.tsa .card .pad{padding:clamp(16px,2.2vw,22px);}
/* Rhythm belongs to the column: every block in the main column is spaced
   from the one above it, whatever the two happen to be. `.card + .card` only
   knew about cards, so a callout or a total line between two of them left a
   0px join. `.pane` is a view's own container, which is a child of .wrap and
   would otherwise hide its contents from this rule. */
.tsa .wrap > * + *, .tsa .pane > * + *{margin-top:clamp(16px,2vw,20px);}
.tsa .lead{display:block; color:var(--muted); font-size:.88rem; max-width:72ch;}
/* a note the prototype makes about itself - never product copy */
/* The run of prototype notes under a view. A real margin, so the gap between
   blocks is a measurable thing and not padding pretending to be one. */
.tsa .wrap > .notes, .tsa .pane > .notes, .tsa .notes{display:grid; gap:14px; margin-top:clamp(18px,2.4vw,26px);}
.tsa .notes .protonote{margin-top:0;}
.tsa .protonote{display:flex; gap:10px; align-items:flex-start; margin-top:15px; padding:12px 14px;
           border:1px dashed #2E4059; border-radius:var(--r-control);
           color:var(--muted); font-size:.8rem; line-height:1.5; max-width:80ch;}
.tsa .protonote i{color:var(--warn); font-size:.92rem; margin-top:.15em; flex:none;}
.tsa .protonote b{color:#C3CCDA; font-weight:800;}
/* A code span is one unbroken token with no space to wrap at, so a long one
   pushes the document sideways - `style="background:#7c3aed!important"` was
   291px wide inside a 288px column. */
.tsa .protonote code, .tsa .lead code, .tsa .help code{font-size:.92em; color:#9FB0C6;
                                      overflow-wrap:anywhere; word-break:break-word;}
/* ------------------------------------------------------------ tables --- */
/* Eleven columns do not fit a phone, and a sideways scroll is not an answer
   for a ledger. Each cell carries a priority; the narrower the viewport the
   fewer survive, and under 620px the row becomes a card with every field
   labelled - nothing is dropped, it is re-laid out. */
.tsa .tscroll{overflow-x:auto; min-width:0; max-width:100%; container-type:inline-size;}
.tsa .tbl{width:100%; border-collapse:collapse; font-size:.87rem;}
.tsa .tbl th{text-align:left; padding:11px 13px; font-size:.72rem; font-weight:800;
        letter-spacing:.08em; text-transform:uppercase; color:var(--muted);
        border-bottom:1px solid var(--line-2); white-space:nowrap;}
.tsa .tbl td{padding:12px 13px; border-bottom:1px solid var(--line); vertical-align:middle;}
.tsa .tbl tbody tr:last-child td{border-bottom:0;}
.tsa .tbl tbody tr{transition:background .12s;}
.tsa .tbl tbody tr:hover{background:rgba(255,255,255,.022);}
.tsa .tbl .num{text-align:right; font-variant-numeric:tabular-nums lining-nums;}
.tsa .tbl tfoot td{padding:13px; border-top:1px solid var(--line-2); font-weight:800;
              background:rgba(255,255,255,.018);}
.tsa .tbl .sym{font-weight:800; letter-spacing:-.01em;}
/* The symbol is the primary route into the ticker page. As bare inline text
   "O" was a 14x25 target; the negative margin keeps the column alignment
   while the padding gives the finger something to land on. */
.tsa .tbl .sym a{color:var(--text); display:inline-flex; align-items:center;
            min-height:26px; min-width:34px; padding:2px 7px; margin:-2px -7px;
            border-radius:7px;}
.tsa .tbl .sym a:hover{background:rgba(255,255,255,.06); text-decoration:none;
                  color:var(--accent);}
.tsa .mut{color:var(--muted); font-size:.82rem;}
/* A hyphenated date gives the line breaker somewhere to break, so a narrow
   column splits "2026-09-24" across two lines. Better to let the fitter shed
   the column than to wrap a value that is one token to a reader. */
.tsa .date{white-space:nowrap;}
/* the field name, as real text. Hidden in grid mode, where the <th> names
   the column; shown in card mode, where there is no header row. */
.tsa .tbl td .cl{display:none; color:var(--muted); font-size:.78rem; font-weight:700;
            letter-spacing:.03em;}
.tsa .up{color:var(--accent);}
.tsa .down{color:var(--neg);}
/* zero is neither a gain nor a loss - the live pages paint it green */
.tsa .flat{color:var(--muted);}
.tsa .dash{color:var(--muted);}
/* Each table sheds tiers until it fits its own container - fit() measures,
   nothing here is a guessed breakpoint. The eight tables in this file need
   between 204px and 1498px for their full column set, so one threshold could
   never have served them; a viewport media query was wrong twice over, because
   the sidebar takes 214px the window does not know about. */
.tsa .tbl[data-tier="2"] [data-pri="3"]{display:none;}
.tsa .tbl[data-tier="1"] [data-pri="3"], .tsa .tbl[data-tier="1"] [data-pri="2"]{display:none;}
/* no JS: fall back to container thresholds sized for the widest table here */
html.tsa:not(.js) .tbl [data-pri="3"]{display:none;}
@container (max-width:940px){
  html.tsa:not(.js) .tbl [data-pri="2"]{display:none;}
}
@media (max-width:620px){
  .tsa .tbl, .tsa .tbl tbody, .tsa .tbl tfoot, .tsa .tbl tr, .tsa .tbl td{display:block; width:100%;}
  .tsa .tbl thead{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0);}
  .tsa .tbl tbody tr{border:1px solid var(--line); border-radius:12px; padding:6px 14px 10px;
                margin-bottom:10px; background:rgba(255,255,255,.012);}
  .tsa .tbl td{border:0; padding:5px 0; display:flex; align-items:baseline;
          justify-content:space-between; gap:16px; text-align:right;}
  .tsa .tbl td .cl{display:inline; flex:none; text-align:left;}
  /* every column comes back in card mode - the grid hid them, it did not drop
     them (the no-JS hiding too: it outranked the first selectors here) */
  .tsa .tbl [data-pri="2"], .tsa .tbl [data-pri="3"], .tsa .tbl[data-tier="1"] [data-pri="2"], .tsa .tbl[data-tier="1"] [data-pri="3"], .tsa .tbl[data-tier="2"] [data-pri="3"], html.tsa:not(.js) .tbl [data-pri="2"], html.tsa:not(.js) .tbl [data-pri="3"]{display:flex;}
  .tsa .tbl thead [data-pri]{display:none !important;}
  /* no shrinking here: in card mode these cells ARE the data */
  .tsa .tbl td.lead-cell{padding:8px 0 9px; border-bottom:1px solid var(--line); margin-bottom:4px;
                    font-size:1.02rem;}
  .tsa .tbl td.lead-cell .cl{display:none;}
  .tsa .tbl td.acts-cell{justify-content:flex-end; padding-top:10px;}
  .tsa .tbl td.acts-cell .cl{display:none;}
  .tsa .tbl tfoot td{border-top:0; border-radius:12px; border:1px solid var(--line-2);}
  /* the alignment spacers carry nothing to say once the grid is gone */
  .tsa .tbl td:empty{display:none;}
}
/* a browser without container queries keeps the old viewport thresholds rather
   than showing every column at every width */
.tsa .tbl tfoot td:first-child{white-space:nowrap;}
.tsa .rowacts{display:flex; gap:6px; justify-content:flex-end; align-items:center;}
/* ------------------------------------------------------------ chips ---- */
.tsa .chip{display:inline-flex; align-items:center; gap:6px; padding:3px 9px;
      border-radius:var(--r-pill); font-size:.73rem; font-weight:800;
      letter-spacing:.02em; white-space:nowrap; border:1px solid transparent;}
.tsa .chip i{font-size:.82rem;}
/* status is never colour alone: every chip carries an icon and a word */
.tsa .chip.good{background:rgba(0,200,150,.14); color:#4FE3B8; border-color:rgba(0,200,150,.3);}
.tsa .chip.warn{background:rgba(242,181,68,.14); color:#F6C86B; border-color:rgba(242,181,68,.32);}
.tsa .chip.bad{background:rgba(255,107,107,.15); color:#FF8F8F; border-color:rgba(255,107,107,.36);}
.tsa .chip.mute{background:rgba(136,146,164,.14); color:#A3AEC0; border-color:rgba(136,146,164,.28);}
.tsa .chip.info{background:rgba(55,138,221,.15); color:#79B4F0; border-color:rgba(55,138,221,.32);}
.tsa .tag{display:inline-flex; align-items:center; gap:5px; padding:2px 9px; border-radius:var(--r-pill);
     font-size:.73rem; font-weight:700; background:rgba(255,255,255,.055); color:#C3CCDA;}
/* A chip is nowrap by design, so a whole sentence in one is 501px wide on a
   360px phone and pushes the document sideways. Sentences get this instead. */
.tsa .notice{display:flex; gap:10px; align-items:flex-start; text-align:left;
        padding:11px 14px; border:1px solid var(--line-2); border-radius:var(--r-control);
        background:rgba(55,138,221,.07); font-size:.85rem; color:#C3CCDA;
        max-width:60ch; margin-inline:auto;}
.tsa .notice i{color:var(--info); font-size:.95rem; margin-top:.12em; flex:none;}
.tsa .tag .dot{width:7px; height:7px; border-radius:50%; flex:none;}
.tsa .count{display:inline-flex; align-items:baseline; gap:6px; color:var(--muted);
       font-size:.83rem; font-weight:600;}
.tsa .count b{color:var(--text); font-size:.95rem; font-weight:800;
         font-variant-numeric:tabular-nums;}
/* ------------------------------------------------------------ figures -- */
/* Exactly one hero figure per view, in the body sans, proportional digits.
   tabular-nums belongs in the columns, not on a 48px number. */
.tsa .hero{display:flex; align-items:flex-end; gap:16px 22px; flex-wrap:wrap;}
.tsa .hero .fig{font-size:clamp(2.1rem,5vw,3rem); font-weight:800; letter-spacing:-.035em;
           line-height:1;}
.tsa .hero .cap{color:var(--muted); font-size:.82rem; font-weight:700; letter-spacing:.06em;
           text-transform:uppercase; margin-bottom:9px;}
.tsa .hero .delta{font-size:1rem; font-weight:800; padding-bottom:.28em;}
.tsa .facts{display:flex; flex-wrap:wrap; gap:6px 26px; margin-top:16px; padding-top:14px;
       border-top:1px solid var(--line); font-size:.85rem; color:var(--muted);}
.tsa .facts b{color:var(--text); font-weight:800; font-variant-numeric:tabular-nums;}
/* ------------------------------------------------------------ segments - */
.tsa .seg{display:inline-flex; padding:3px; gap:3px; background:rgba(255,255,255,.04);
     border:1px solid var(--line); border-radius:var(--r-control);}
.tsa .seg button{appearance:none; border:0; background:transparent; color:var(--muted);
            font-size:.82rem; font-weight:700; padding:0 15px;
            height:calc(var(--ctl-sm) - 8px);
            border-radius:6px; cursor:pointer; display:inline-flex; align-items:center;
            gap:7px; transition:background .13s, color .13s;}
.tsa .seg button:hover{color:var(--text);}
.tsa .seg button[aria-pressed="true"]{background:var(--raise); color:var(--text);
                                 box-shadow:0 1px 2px rgba(0,0,0,.4);}
.tsa .seg .n{font-variant-numeric:tabular-nums; opacity:.72; font-weight:700;}
/* ------------------------------------------------------------ inputs --- */
.tsa .field{display:grid; gap:7px; min-width:0; align-content:start;}
.tsa .field label{font-size:.83rem; font-weight:700; color:#C3CCDA;}
.tsa .help{display:block; font-size:.79rem; color:var(--muted); line-height:1.5;}
.tsa input[type=text], .tsa input[type=number], .tsa input[type=date], .tsa input[type=file], .tsa select, .tsa textarea{
  width:100%; padding:11px 13px; height:var(--ctl); background:#0C1322; color:var(--text);
  border:1px solid var(--line-2); border-radius:var(--r-control);
  font-family:inherit; font-size:.9rem; font-weight:600;}
.tsa textarea{height:auto; min-height:82px; resize:vertical;}
/* The file input shipped with a native grey "Choose File" button sitting in
   the middle of a dark form. Style the button itself, drop the box padding so
   it can fill the control's height, and centre the filename beside it. */
.tsa input[type=file]{padding:0 14px 0 0; line-height:calc(var(--ctl) - 2px); cursor:pointer;
                 overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.tsa input[type=file]::file-selector-button{
  height:calc(var(--ctl) - 2px); margin:0 14px 0 0; padding:0 17px; border:0;
  border-right:1px solid var(--line-2);
  border-radius:calc(var(--r-control) - 1px) 0 0 calc(var(--r-control) - 1px);
  background:rgba(255,255,255,.06); color:var(--text);
  font-family:inherit; font-size:.84rem; font-weight:700; cursor:pointer;
  transition:background .13s, color .13s;}
.tsa input[type=file]:hover::file-selector-button{background:rgba(0,200,150,.16);
                                             color:var(--accent);}
.tsa input::placeholder{color:#63708A;}
.tsa select{appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%238892A4'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
       background-repeat:no-repeat; background-position:right 11px center;
       background-size:15px; padding-right:34px;}
.tsa input:focus, .tsa select:focus, .tsa textarea:focus{border-color:var(--accent); outline:none;}
.tsa .grid2{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(210px,100%),1fr)); gap:14px;}
.tsa .grid3{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(170px,100%),1fr)); gap:14px;}
.tsa .filters{display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end;}
.tsa .filters .field{min-width:168px;}
/* ------------------------------------------------------------ empty ---- */
.tsa .empty{display:grid; justify-items:center; text-align:center; gap:13px;
       padding:clamp(34px,6vw,58px) 22px; color:var(--muted);}
.tsa .empty i{font-size:1.9rem; color:#40546F;}
.tsa .empty h3{font-size:1.02rem; color:var(--text);}
.tsa .empty p{font-size:.88rem; max-width:44ch;}
/* ------------------------------------------------------------ modal ---- */
.tsa .mask{position:fixed; inset:0; background:rgba(4,8,16,.72); z-index:80;
      backdrop-filter:blur(2px);}
.tsa .modal{position:fixed; z-index:81; left:50%; top:50%; transform:translate(-50%,-50%);
       width:min(560px,calc(100vw - 28px)); max-height:min(86vh,760px);
       display:flex; flex-direction:column; background:var(--raise);
       border-radius:var(--r-surface);
       box-shadow:0 18px 44px rgba(0,0,0,.62), 0 3px 10px rgba(0,0,0,.45);}
.tsa .modal.wide{width:min(880px,calc(100vw - 28px));}
.tsa .modal header{display:flex; align-items:center; justify-content:space-between; gap:12px;
              padding:17px 20px; border-bottom:1px solid var(--line);}
.tsa .modal header h2{font-size:1rem;}
.tsa .mbody{padding:20px; overflow:auto; display:grid; gap:15px;}
.tsa .mfoot{display:flex; justify-content:flex-end; gap:10px; padding:15px 20px;
       border-top:1px solid var(--line); flex-wrap:wrap;}
/* ------------------------------------------------------- view switcher - */
/* prototype scaffolding inside the shell's footer; no such bar exists live */
.tsa .protobar{margin-top:14px; padding-top:12px; border-top:1px dashed #22314A; gap:4px 14px;
          font-size:.78rem;}
.tsa .protobar b{font-weight:800; letter-spacing:.12em; text-transform:uppercase;
            font-size:.72rem; color:var(--muted);}
.tsa .protobar a{display:inline-flex; align-items:center; justify-content:center;
            min-width:26px; min-height:26px;}
.tsa .protobar a[aria-current="page"]{color:var(--accent); font-weight:700;}
/* ------------------------------------------------------- grid ----- */
/* A dashboard is a composed grid, not a stack. The product dashboard has had
   one since its first build; the admin views were the only place still
   putting every panel on its own full-width row, which is most of why the
   analytics view read flat. Twelve columns, panels of different spans, so
   size carries importance instead of every panel claiming the same. */
.tsa .g12{display:grid; grid-template-columns:repeat(12,minmax(0,1fr));
     gap:clamp(14px,1.8vw,20px); align-items:start;}
.tsa .g12 > *{min-width:0;}
.tsa .sp4{grid-column:span 4;}
.tsa .sp5{grid-column:span 5;}
.tsa .sp6{grid-column:span 6;}
.tsa .sp7{grid-column:span 7;}
.tsa .sp8{grid-column:span 8;}
.tsa .sp12{grid-column:span 12;}
/* Three steps, set by what body copy needs rather than by round numbers.
   Montserrat's zero is 10.1px at the lead's size, so a 40-character measure
   wants 404px of text plus padding. A 5/12 column only clears that past a
   1400px viewport; equal halves clear it from about 1120. Below that, one
   column - a denser grid is not worth text at a 34-character stutter. */
@media(max-width:1120px){
  .tsa .sp4, .tsa .sp5, .tsa .sp6, .tsa .sp7, .tsa .sp8{grid-column:span 12;}
}
@media(min-width:1121px) and (max-width:1400px){
  .tsa .sp4, .tsa .sp5, .tsa .sp7, .tsa .sp8{grid-column:span 6;}
}
/* the column rhythm is for a stack; a grid has its own gap */
.tsa .wrap > .g12 > * + *, .tsa .pane > .g12 > * + *{margin-top:0;}
/* ------------------------------------------------- summary strip --- */
/* ONE panel with divided cells, not four cards in a row. Four same-size
   cards of label-plus-number IS the hero-metric template the craft floor
   refuses; a single strip reads as one object and keeps the figures
   comparable, which is the whole job. */
.tsa .kpi{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(210px,100%),1fr));
     background:var(--surface); border:1px solid var(--line);
     border-radius:var(--r-surface); overflow:hidden;}
.tsa .kpi > div{padding:clamp(16px,2vw,20px) clamp(16px,2.2vw,22px);
           border-left:1px solid var(--line); min-width:0;}
.tsa .kpi > div:first-child{border-left:0;}
@media(max-width:760px){
  .tsa .kpi > div{border-left:0; border-top:1px solid var(--line);}
  .tsa .kpi > div:first-child{border-top:0;}
}
.tsa .kpi .k{display:flex; align-items:center; gap:7px; color:var(--muted);
        font-size:.75rem; font-weight:800; letter-spacing:.08em;
        text-transform:uppercase;}
.tsa .kpi .k i{font-size:.9rem;}
.tsa .kpi .v{display:flex; align-items:baseline; gap:10px; margin-top:10px;
        flex-wrap:wrap;}
/* proportional digits: tabular-nums makes 121 look loose at display size */
.tsa .kpi .n{font-size:clamp(1.7rem,3.4vw,2.15rem); font-weight:800;
        letter-spacing:-.035em; line-height:1;}
.tsa .kpi .d{font-size:.83rem; font-weight:800;}
.tsa .kpi .spark{margin-top:12px; height:30px;}
.tsa .kpi .foot{margin-top:9px; color:var(--muted); font-size:.79rem;}
/* ------------------------------------------------------ charts ---- */
.tsa .tip{position:absolute; pointer-events:none; background:var(--raise);
     border:1px solid var(--line-2); border-radius:9px; padding:8px 11px;
     font-size:.79rem; font-weight:700; white-space:nowrap; z-index:5;
     box-shadow:0 8px 20px rgba(0,0,0,.45); transform:translate(-50%,-115%);}
.tsa .tip .d{color:var(--muted); font-weight:600; font-size:.74rem; display:block;
        margin-bottom:2px;}
.tsa .tip .v{font-variant-numeric:tabular-nums;}
.tsa .spark{display:block; width:100%; height:100%; overflow:visible;}
.tsa .spark .ln{fill:none; stroke:var(--accent); stroke-width:2;
           stroke-linejoin:round; stroke-linecap:round;}
.tsa .spark .fill{fill:url(#spk); stroke:none;}
.tsa .spark .dot{fill:var(--accent); stroke:var(--surface); stroke-width:2;}
.tsa .chartbox{display:block; width:100%; min-width:0;}
.tsa .bars{display:block; width:100%; height:auto; overflow:visible;}
.tsa .bars .grid{stroke:#22304A; stroke-width:1; shape-rendering:crispEdges;}
.tsa .bars .axis{fill:var(--muted); font-size:10.5px; font-weight:600;
            font-variant-numeric:tabular-nums;}
.tsa .bars .lab{fill:#C3CCDA; font-size:11px; font-weight:700;}
.tsa .bars .val{fill:var(--text); font-size:11px; font-weight:800;
           font-variant-numeric:tabular-nums;}
.tsa .bars .mark{fill:var(--accent);}
.tsa .bars .hit{fill:transparent; cursor:default;}
.tsa .bars g.row:hover .mark{fill:#15D9A9;}
/* A panel whose numbers the product cannot compute yet. It shows the SHAPE
   so the form can be judged, and it must never be mistakable for data: no
   accent colour, a dashed edge, and the missing column named on the panel.
   The craft trap here is a plausible-looking chart of invented figures. */
/* Four signals already say this is not data - the dashed edge, the grey
   palette, the "Not recorded" chip and the stamp. A diagonal hatch was a
   fifth, and the weakest; dropping it lets the other four read louder. */
.tsa .unwired{position:relative; border:1px dashed #3A4C66; border-radius:var(--r-surface);
         background:rgba(255,255,255,.012);}
.tsa .unwired .bars .mark{fill:#3E4E66;}
.tsa .unwired .bars .val, .tsa .unwired .bars .lab{fill:#78859A;}
.tsa .unwired .fig, .tsa .unwired .cap{color:#78859A;}
/* nothing in an unwired panel wears the accent - not even its icon */
.tsa .unwired .card > header h2 i, .tsa .unwired > header h2 i{color:#78859A;}
.tsa .unwired .stamp{display:flex; align-items:flex-start; gap:10px; margin:0 0 16px;
                padding:11px 14px; border:1px solid rgba(242,181,68,.4);
                background:rgba(242,181,68,.09); border-radius:var(--r-control);
                font-size:.84rem; color:#E8D6A8; line-height:1.5;}
.tsa .unwired .stamp i{color:var(--warn); font-size:1rem; margin-top:.12em; flex:none;}
.tsa .unwired .stamp b{color:#F6C86B; font-weight:800;}
.tsa .unwired .stamp code{color:#F6C86B; overflow-wrap:anywhere;}
/* ------------------------------------------------- option list ---- */
/* A list of selectable rows: the app picker, and the column chooser
   that columnsModal() renders for any wide table. */
.tsa .applist{display:grid; gap:11px;}
.tsa .appopt{display:flex; align-items:center; gap:14px; padding:14px 16px;
        border:1px solid var(--line-2); border-radius:var(--r-control);
        cursor:pointer; transition:border-color .14s, background .14s;
        min-height:44px;}
.tsa .appopt:hover{border-color:#3C5170; background:rgba(255,255,255,.03);}
.tsa .appopt:has(input:checked){border-color:var(--accent); background:rgba(0,200,150,.08);}
.tsa .appopt:has(input:disabled){opacity:.5; cursor:not-allowed;}
.tsa .appopt input{width:19px; height:19px; flex:none; accent-color:var(--accent); margin:0;
              min-height:0;}
/* A checkbox cannot simply be grown to 44px without looking wrong, so the
   box keeps its size and the padding around it carries the target. */
.tsa .cbox{display:inline-flex; align-items:center; justify-content:center;
      min-width:26px; min-height:26px; cursor:pointer;}
.tsa .cbox input{width:18px; height:18px; min-height:0; margin:0;
            accent-color:var(--accent); cursor:pointer;}
.tsa .appopt .ic{font-size:1.24rem; color:var(--accent); width:24px; text-align:center; flex:none;}
.tsa .appopt .nm{font-weight:800; font-size:.93rem;}
.tsa .appopt .ds{color:var(--muted); font-size:.82rem; margin-top:2px;}
.tsa .appopt .why{color:var(--warn); font-size:.78rem; font-weight:700; margin-top:3px;}
/* --------------------------------------------- production additions --- */
/* Appended to static/tsa/core.css by land_foundation.py. The prototypes'
   dialogs had no server to post to; a real one wraps its body and footer in a
   <form>, which then has to carry the dialog's column so the body still scrolls. */
.tsa .modal > form{display:flex; flex-direction:column; min-height:0; flex:1 1 auto;}
/* core.css styles text, number, date and file inputs; the live forms also use
   datetime-local, email, password and search. The same control, so no field
   falls back to the browser's white box. Date pickers draw their icon in the
   dark scheme so it shows on the dark field. */
.tsa input[type=datetime-local], .tsa input[type=email], .tsa input[type=password], .tsa input[type=search], .tsa input[type=time], .tsa input[type=url], .tsa input[type=tel]{
  width:100%; padding:11px 13px; height:var(--ctl); background:#0C1322; color:var(--text);
  border:1px solid var(--line-2); border-radius:var(--r-control);
  font-family:inherit; font-size:.9rem; font-weight:600;}
.tsa input[type=date], .tsa input[type=datetime-local], .tsa input[type=time]{color-scheme:dark;}
/* ...and their focus: core's input:focus sets the accent border, and the rule
   above, as specific and later, put the plain border back - the Cash search
   box showed no focus at all */
.tsa input:is([type=datetime-local],[type=email],[type=password],[type=search],[type=time],[type=url],[type=tel]):focus{
  border-color:var(--accent);}
/* ui.js sorts any table by a header button and pages its rows; these were
   the admin sheet's, and the product pages sort too */
.tsa .sortbtn{display:inline-flex; align-items:center; gap:5px; padding:0; border:0; background:none;
         color:inherit; font:inherit; letter-spacing:inherit; text-transform:inherit; cursor:pointer;}
.tsa .sortbtn i{font-size:.8em; opacity:.45;}
.tsa th[aria-sort] .sortbtn i, .tsa th[data-sorted] .sortbtn i{opacity:1; color:var(--accent);}
.tsa th[aria-sort="descending"] .sortbtn i, .tsa th[data-sorted="descending"] .sortbtn i{transform:scaleY(-1);}
/* Paging is ui.js's. The first page is printed as it will stand (rows past it
   marked is-paged-out, the count line and page buttons as they will read), so
   nothing moves when ui.js takes over. With script off nothing would ever
   page: every row shows, and the paging controls, which could not work and
   would say "1 to 25", are not drawn. */
html.tsa.js .tbl tbody tr.is-paged-out, .tsa .tbl tbody tr.is-filtered-out{display:none;}
html.tsa:not(.js) [data-dtfoot], html.tsa:not(.js) [data-pagefoot], html.tsa:not(.js) [data-pagesize], html.tsa:not(.js) .dtlen{display:none;}
/* A row a page's own filter sets aside (the Stock Ledger's Open / Closed,
   printed as it will stand: the open positions). Script off, as with
   paging: every row, and no filter buttons, which could not work. */
html.tsa.js .tbl tbody tr.is-tabbed-out{display:none;}
html.tsa:not(.js) [data-tabfilter]{display:none;}
/* ...and the empty-tab words ("No matching records found" over an Open tab
   with nothing in it) are not said under the rows script off shows anyway.
   A search box that filters in the page cannot work without script either. */
html.tsa:not(.js) .tbl tr[data-empty-row][data-has-rows]{display:none;}
html.tsa:not(.js) .tsearch:has(input[data-filter]){display:none;}
.tsa .pagefoot{display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;
          border-top:1px solid var(--line);}
/* a native checkbox or radio unticked is a white square in the light scheme */
.tsa input[type=checkbox], .tsa input[type=radio]{color-scheme:dark;}
/* ...and its keyboard focus. core's input:focus{outline:none} (a text field
   shows focus by its border) outranks the :where() ring, and a checkbox has
   no border to show it by: tabbing onto one showed nothing at all. */
.tsa input:is([type=checkbox],[type=radio]):focus-visible{outline:2px solid var(--accent); outline-offset:2px;}
/* A table wider than its box scrolls inside it. Its visually hidden words
   (.vh - "Actions", a dash's "None") are absolutely positioned, and with no
   positioned ancestor they escaped the box and pushed the whole page
   sideways: 141px on the watchlist at 768. The box holds them. */
.tsa .tscroll{position:relative;}
/* The page keeps the scrollbar's room whether it has one or not. Where a
   scrollbar takes room (Windows' default), a page whose height crossed the
   window's while it loaded narrowed by ~15px at that moment, and a table
   fitted to its box was fitted again - the admin users' columns moved. */
html.tsa{scrollbar-gutter:stable;}
/* A plan, as a rung on a ladder - Starter, Basic, Pro, Unlimited - not four
   unrelated Bootstrap colours (the dashboard and settings). By the plan's own
   class, not .tier alone: the tracker pages' "Available in ..." line is a
   .tier too, styled by trackers.css, and must not become a pill. */
.tsa .tier-starter, .tsa .tier-basic, .tsa .tier-pro, .tsa .tier-unlimited{display:inline-flex; align-items:center; gap:6px; padding:5px 13px; font-size:.75rem; font-weight:800;
      letter-spacing:.03em; border-radius:var(--r-pill); border:1px solid var(--line-2); color:var(--muted);
      white-space:nowrap;}
.tsa .tier-basic{border-color:rgba(55,138,221,.5); color:#8FC0F0;}
.tsa .tier-pro{border-color:rgba(0,200,150,.55); color:var(--accent);}
.tsa .tier-unlimited{background:var(--accent); border-color:var(--accent); color:var(--accent-ink);}
/* A table marked data-fit (every table in a .tscroll the server prints) is
   not drawn until base.html's tsaTable.fit has run - right after its first
   page of rows is read (a paged table: Cash, admin users; rows past it are
   not drawn, so cannot widen a column), else right after the table. On a
   repeat visit, its stylesheets cached, the page can be drawn while its
   rows are still arriving: the admin tables showed every column, then the
   fit dropped some (up to 0.27, layout shift); Cash showed its first rows,
   then a later one widened a column and the rest moved up to 97px (0.026).
   Waiting for the whole of a long table instead cost seconds: 1,509 Cash
   rows, 2.2 MB, drew 1.9s after the page at 10 Mbps, 11s at 1.6 Mbps.
   Only where the table is a grid: on a phone each row is a card, with no
   column widths to settle, and cards are drawn as they arrive. Script off:
   drawn at once (core.css's no-script column rules). Hidden-then-shown is
   not a shift. */
@media not all and (max-width:620px){
  html.tsa.js .tbl[data-fit]:not([data-fitted]){visibility:hidden;}
}
/* A column picked off in "Columns" (ui.js) hides in the grid only. In card
   mode every field comes back, the way the shed tiers do: a class, not the
   hidden attribute, which [hidden]{display:none !important} would keep hidden. */
.tsa .tbl .col-off{display:none;}
@media (max-width:620px){
  .tsa .tbl td.col-off{display:flex;}
}
/* In card mode each row is a card; the card holding the table then framed
   cards inside a card. The frame goes; the rows' own borders remain. */
@media (max-width:620px){
  .tsa .card:has(> .tscroll > .tbl){border-color:transparent; background:none;}
  .tsa .card:has(> .tscroll > .tbl) > header{padding-inline:0;}
  /* a cell with nothing in it: in the grid it holds its column; on a card it
     would be a label beside nothing (a cell of whitespace is not :empty) */
  .tsa .tbl td[data-blank]{display:none;}
  /* A sortable table keeps its sort buttons on a phone. The header row was
     hidden in card mode, and with it every way to sort; here the sortable
     headers stay, as a row of buttons above the cards - the same buttons,
     the same aria-sort. (The templates print .sortable and th.has-sort, so
     the row is there at the first paint.) */
  .tsa .tbl.sortable thead{position:static; width:auto; height:auto; overflow:visible; clip:auto;
                      display:block; margin-bottom:12px;}
  .tsa .tbl.sortable thead tr{display:flex; flex-wrap:wrap; align-items:center; gap:6px; border:0;}
  .tsa .tbl.sortable thead tr::before{content:"Sort by"; color:var(--muted); font-size:.72rem; font-weight:800;
                                 letter-spacing:.08em; text-transform:uppercase; margin-right:4px;}
  .tsa .tbl.sortable thead th{display:none !important;}
  .tsa .tbl.sortable thead th.has-sort{display:block !important; padding:0; border:0;}
  .tsa .tbl.sortable thead th.has-sort .sortbtn{min-height:var(--ctl-sm); padding:0 12px;
                                           border:1px solid var(--line-2); border-radius:var(--r-pill);}
  .tsa .tbl.sortable thead th[aria-sort] .sortbtn, .tsa .tbl.sortable thead th[data-sorted] .sortbtn{border-color:rgba(0,200,150,.5); color:var(--text);}
}
/* Windows High Contrast (forced colours) drops box-shadow and forces border
   colours, so a focus shown only by a ring of shadow or a border colour - the
   fields here, on sign-in and on the feedback form, and the training video's
   picture - showed nothing at all. There, a real outline in the system's
   focus colour. !important: each of those rules says outline:none, more
   specifically than this. */
@media (forced-colors: active){
  .tsa :is(input,select,textarea):focus{outline:2px solid Highlight !important; outline-offset:1px !important;}
  .tsa .vthumb:focus-visible{outline:3px solid Highlight !important; outline-offset:-4px !important;}
}
/* A notice says what kind it is by its whole box, not its icon alone: a
   warning or a success was the blue of an information notice with a
   different icon (Import's "Double-check N warning(s)!", "N row(s) will be
   skipped", "STO in this file"; live showed them yellow and green). */
.tsa .notice.warn{border-color:rgba(242,181,68,.42); background:rgba(242,181,68,.08);}
.tsa .notice.warn i{color:var(--warn);}
.tsa .notice.good{border-color:rgba(0,200,150,.38); background:rgba(0,200,150,.07);}
.tsa .notice.good i{color:var(--accent);}
.tsa .notice.danger{border-color:rgba(255,107,107,.4); background:rgba(255,107,107,.08); color:#FFC2C2;}
.tsa .notice.danger i{color:var(--neg);}
/* The "unknown cost" mark (_macros.html unknown_cost): a dash and an info
   icon that explains it. Its colours were Bootstrap's utility classes, which
   these pages do not load. The icon is 14px; the padding gives a finger more
   to land on without moving the figure. ui.js shows its words as a
   .ucost-tip - not .tip, which is core's chart tooltip, centred by a transform. */
.tsa .ucost-dash{color:var(--muted);}
/* On a card the cell spreads its parts to both ends; the dash and its mark
   are one value - the dash takes the free space, so they stay together */
@media (max-width:620px){
  .tsa .tbl td .ucost-dash{margin-left:auto;}
  .tsa .tbl td .ucost-dash + .ucost{margin-left:-17px;}
  /* the card's 16px gap, less: 4px, as in the grid */
}
.tsa .ucost{display:inline-flex; padding:5px; margin:-5px -5px -5px -1px; cursor:help;
       border-radius:6px; vertical-align:baseline;}
.tsa .ucost i{color:var(--info);}
.tsa .ucost-tip{position:fixed; z-index:90; max-width:min(300px, calc(100vw - 16px)); padding:9px 12px;
     border-radius:8px; background:var(--raise); border:1px solid var(--line-2);
     color:#C3CCDA; font-size:.8rem; font-weight:600; line-height:1.45; letter-spacing:0;
     text-transform:none; box-shadow:0 10px 28px rgba(0,0,0,.45); pointer-events:none;}
/* ============================================ every device (Paulo, 7 Oct) ===
   A finger needs about 44px to land on. On a touch screen every control gets
   that much: as its real size where it can grow without moving its neighbours
   (buttons, pager), and as an invisible pad round it where it cannot (a sort
   button in a header, a ticker link in a row, a card's "View ..." link) -
   the pad is a ::after, so nothing on the page moves. A phone's sentences
   are 16px; labels stay small on purpose. */
@media (pointer:coarse){
  .tsa .btn, .tsa .btn-xs{min-height:44px;}
  .tsa :is(.sortbtn, .sym a, a.sym, .dcard .more, .item .go){position:relative;}
  .tsa :is(.sym a, a.sym, .dcard .more, .item .go)::after{content:""; position:absolute; left:50%; top:50%;
      width:max(100% + 8px, 44px); height:max(100%, 44px); transform:translate(-50%,-50%);}
  /* a header's sort buttons are wide already: their pad adds height only - wider, the last one
     reached past its table's scroll box and let the table slide 1-2px. Its own rule, not in the
     :is() above with a width override after it: :is() takes its strongest selector's weight
     (.dcard .more), so that override lost. */
  .tsa .sortbtn::after{content:""; position:absolute; left:0; top:50%;
      width:100%; height:max(100%, 44px); transform:translateY(-50%);}
}
@media (max-width:640px){
  .tsa .notice{font-size:1rem;}
}
