/* hototel - design tokens (light) */
:root {
  color-scheme: light;
  --page:           #f9f9f7;
  --surface:        #fcfcfb;
  --ink:            #0b0b0b;
  --ink-2:          #52514e;
  --muted:          #898781;
  --grid:           #e1e0d9;
  --baseline:       #c3c2b7;
  --border:         rgba(11, 11, 11, 0.10);
  /* series: color follows the token type, everywhere */
  --s-out:          #2a78d6;   /* output      - slot 1 blue   */
  --s-in:           #eb6834;   /* input       - slot 2 orange */
  --s-cw:           #1baf7a;   /* cache write - slot 3 aqua   */
  --s-cr:           #eda100;   /* cache read  - slot 4 yellow */
  --s-ctx:          #4a3aa7;   /* context line - slot 7 violet */
  /* categorical slots, for stacking by an entity (a person) rather than by
     token type. Same validated eight-hue theme the --s-* roles are drawn from,
     under their own names: blue means "output" wherever a chart stacks token
     types, and reusing that token for a person would make one hue mean two
     things in the same legend. Assigned in fixed order by identity, never by
     rank, so filtering the chart never repaints the series that remain. */
  --cat-1:          #2a78d6;
  --cat-2:          #eb6834;
  --cat-3:          #1baf7a;
  --cat-4:          #eda100;
  --cat-5:          #e87ba4;
  --cat-6:          #008300;
  --cat-7:          #4a3aa7;
  --cat-8:          #e34948;
  /* danger: errors and destructive actions. Its own hue, deliberately not
     --s-in -- that orange means input tokens everywhere else. */
  --danger:         #e34948;
  --danger-bg:      rgba(227, 73, 72, 0.12);
  --danger-edge:    rgba(227, 73, 72, 0.40);
  /* status: healthy / gone quiet, for the admin page's collector dots. Their
     own values rather than --s-cw and --s-cr: those two mean cache write and
     cache read wherever a chart is drawn, and a status dot is not a series. */
  --ok:             #1f9d63;
  --stale:          #d08700;
  --lift:           brightness(1.06);
}
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --page:         #0d0d0d;
    --surface:      #1a1a19;
    --ink:          #ffffff;
    --ink-2:        #c3c2b7;
    --muted:        #898781;
    --grid:         #2c2c2a;
    --baseline:     #383835;
    --border:       rgba(255, 255, 255, 0.10);
    --s-out:        #3987e5;
    --s-in:         #d95926;
    --s-cw:         #199e70;
    --s-cr:         #c98500;
    --s-ctx:        #9085e9;
    --cat-1:        #3987e5;
    --cat-2:        #d95926;
    --cat-3:        #199e70;
    --cat-4:        #c98500;
    --cat-5:        #d55181;
    --cat-6:        #008300;
    --cat-7:        #9085e9;
    --cat-8:        #e66767;
    --danger:       #f2706f;
    --danger-bg:    rgba(242, 112, 111, 0.14);
    --danger-edge:  rgba(242, 112, 111, 0.42);
    --ok:           #34b37e;
    --stale:        #d4930f;
    --lift:         brightness(1.15);
  }
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--page);
  color: var(--ink);
  font: 14px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif;
}
.wrap { max-width: 1100px; margin: 0 auto; padding: 20px 20px 48px; }

/* Full-width bar on its own surface, with the contents held to the same 1100px
   column as the page. The rule under it runs edge to edge; add
   `position: sticky; top: 0; z-index: 20` here to keep it on screen. */
header.top { background: var(--surface); border-bottom: 1px solid var(--border); }
.topin { max-width: 1100px; margin: 0 auto; padding: 10px 20px;
         display: flex; align-items: center; gap: 12px; }
header.top h1 { font-size: 19px; font-weight: 650; margin: 0; letter-spacing: -.012em; }
header.top .sub { color: var(--ink-2); font-size: 13px; }
header.top .spacer { flex: 1; }
/* Three tiers, one base. Default is the quiet one and stays the majority:
   `primary` marks the single main action of a card (at most one), and
   `danger-out` marks an action whose blast radius is a whole organization.
   The primary fill borrows --s-out because that hue is already this UI's
   interactive accent -- it draws every focus ring. */
button.refresh {
  background: var(--surface); color: var(--ink-2);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 5px 12px; font: inherit; font-size: 13px; cursor: pointer;
}
button.refresh:hover { color: var(--ink); }
button.refresh.primary {
  background: var(--s-out); border-color: var(--s-out); color: #fff; font-weight: 600;
}
button.refresh.primary:hover { color: #fff; filter: var(--lift); }
button.refresh:disabled { opacity: 0.45; cursor: default; }

/* ---- filter row: one row, above everything it scopes ----
   Each label travels with the control it names (.fgroup), so a wrap breaks
   between groups instead of stranding a label at the end of a line. The
   groups that hide themselves when there is nothing to filter hide the label
   with them, for the same reason. */
.filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-bottom: 16px;
}
.fgroup { display: flex; align-items: center; gap: 7px; min-width: 0; }
.fgroup[hidden] { display: none; }
.filters .spacer { flex: 1; }
.seg { display: inline-flex; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 2px; }
.seg button {
  border: 0; background: transparent; color: var(--ink-2);
  font: inherit; font-size: 13px; padding: 4px 12px; border-radius: 6px; cursor: pointer;
}
.seg button[aria-pressed="true"] { background: var(--page); color: var(--ink); font-weight: 600; box-shadow: inset 0 0 0 1px var(--border); }
select.project {
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 5px 10px; font: inherit; font-size: 13px; max-width: 260px;
}
.filters .flabel { color: var(--muted); font-size: 12px; }

/* ---- stat tiles ---- */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 16px; }
.tile { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; }
.tile .label { color: var(--ink-2); font-size: 12.5px; margin-bottom: 4px; }
.tile .value { font-size: 26px; font-weight: 600; letter-spacing: -0.01em; }
.tile .hint { color: var(--muted); font-size: 11.5px; margin-top: 2px; }

/* ---- cards ---- */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 16px 18px; margin-bottom: 16px; }
.card-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; flex-wrap: wrap; }
.card-head h2 { font-size: 14.5px; font-weight: 650; margin: 0; }
.card-head .spacer { flex: 1; }
.legend { display: flex; gap: 14px; flex-wrap: wrap; }
.legend .item { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-2); font-size: 12.5px; }
.legend .swatch { width: 10px; height: 10px; border-radius: 3px; }
.viewtoggle { display: inline-flex; gap: 2px; }
.viewtoggle button {
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  font: inherit; font-size: 12px; padding: 2px 9px; border-radius: 6px; cursor: pointer;
}
.viewtoggle button[aria-pressed="true"] { color: var(--ink); background: var(--page); }

.chartbox { position: relative; }
.chartbox.loading { opacity: 0.6; }
.chartbox svg { display: block; width: 100%; height: auto; }

/* ---- scrollers ----
   Anything too wide for its card carries the overflow itself. Without this the
   widest table on the page sets the width of the document and the whole body
   scrolls sideways, which on a phone means the header and every other card
   drift off-screen while you are only trying to read one column.

   The four gradients are the affordance: the two `local` layers travel with the
   content and paint the card's own surface over each end, the two `scroll`
   layers are pinned to the box and show a shadow wherever surface has been
   scrolled away. A scroller that fits shows nothing; one that does not shows a
   shadow on whichever side still has content. macOS overlay scrollbars are
   invisible until you touch them, so there is otherwise no way to tell. */
.scroller {
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--surface) 40%, rgba(0, 0, 0, 0)) left center / 28px 100% no-repeat local,
    linear-gradient(to left, var(--surface) 40%, rgba(0, 0, 0, 0)) right center / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .17), rgba(0, 0, 0, 0)) left center / 11px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .17), rgba(0, 0, 0, 0)) right center / 11px 100% no-repeat scroll;
}
/* the daily table scrolls DOWN inside a capped box; the horizontal cover layers
   above would scroll up out of the box with it and strand the shadows */
.scroller.vscroll { max-height: 320px; overflow-y: auto; background: none; }

svg text { font: 11px system-ui, -apple-system, "Segoe UI", sans-serif; fill: var(--muted); }
svg .tick { font-variant-numeric: tabular-nums; }
svg .gridline { stroke: var(--grid); stroke-width: 1; }
svg .baseline { stroke: var(--baseline); stroke-width: 1; }
svg .hitcol { fill: transparent; cursor: default; outline: none; }
svg g.day.lift rect.segment { filter: var(--lift); }
svg .crosshair { stroke: var(--baseline); stroke-width: 1; }
svg .direct-label { fill: var(--ink-2); font-weight: 600; }

/* ---- data table ---- */
table.data { width: 100%; border-collapse: collapse; font-size: 13px; }
table.data th {
  text-align: left; color: var(--muted); font-weight: 500; font-size: 12px;
  padding: 6px 10px; border-bottom: 1px solid var(--grid); white-space: nowrap;
}
table.data th.sortable { cursor: pointer; user-select: none; }
table.data th.sortable:hover { color: var(--ink); }
table.data th .arrow { font-size: 9px; margin-left: 3px; }
table.data td { padding: 8px 10px; border-bottom: 1px solid var(--grid); vertical-align: middle; }
table.data tr:last-child td { border-bottom: 0; }
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }
table.data tbody tr.sess { cursor: pointer; }
table.data tbody tr.sess:hover td { background: var(--page); }
table.data tbody tr.sess:focus-visible td { background: var(--page); outline: none; }
.sess .title { font-weight: 550; color: var(--ink); max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sess .meta { color: var(--muted); font-size: 11.5px; margin-top: 1px; max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.minibar { display: flex; height: 10px; width: 140px; border-radius: 3px; overflow: hidden; background: transparent; }
.minibar span { height: 100%; }
.minibar span + span { margin-left: 2px; }
.sess .models { color: var(--ink-2); font-size: 12px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pager { display: flex; align-items: center; gap: 10px; padding: 10px 2px 2px; }
.pager .spacer { flex: 1; }
.pager .flabel { color: var(--muted); font-size: 12px; }
.pager button {
  background: var(--surface); color: var(--ink-2);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 4px 12px; font: inherit; font-size: 13px; cursor: pointer;
}
.pager button:hover:not(:disabled) { color: var(--ink); }
.pager button:disabled { opacity: 0.45; cursor: default; }
.detailrow > td { background: var(--page); padding: 14px 16px; }
.detailrow h3 { font-size: 13px; font-weight: 650; margin: 0 0 6px; }
.detailrow .dmeta { color: var(--ink-2); font-size: 12px; margin-bottom: 10px; }

/* ---- tooltip ---- */
#tooltip {
  position: fixed; z-index: 10; pointer-events: none; display: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14);
  padding: 8px 11px; font-size: 12.5px; max-width: 280px;
}
#tooltip .tt-head { color: var(--muted); font-size: 11.5px; margin-bottom: 5px; }
#tooltip .tt-row { display: flex; align-items: center; gap: 7px; margin: 2px 0; }
#tooltip .tt-key { width: 10px; height: 10px; border-radius: 3px; flex: none; }
#tooltip .tt-key.line { height: 2.5px; width: 12px; border-radius: 1px; }
#tooltip .tt-val { font-weight: 650; font-variant-numeric: tabular-nums; color: var(--ink); }
#tooltip .tt-label { color: var(--ink-2); }
#tooltip .tt-row.total { border-top: 1px solid var(--grid); padding-top: 4px; margin-top: 5px; }

footer.foot { color: var(--muted); font-size: 12px; margin-top: 8px; }
.empty { color: var(--muted); padding: 24px 0; text-align: center; }

/* ---- invite modal ---- */
.modal { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 20;
         display: flex; align-items: flex-start; justify-content: center; padding-top: 14vh; }
.modal[hidden] { display: none; }
.modal-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
              padding: 22px 24px; width: min(460px, 92vw); }
.modal-card h2 { font-size: 16px; font-weight: 650; margin: 0 0 4px; }
.modal-sub { color: var(--ink-2); font-size: 12.5px; margin: 0 0 14px; }
.modal-row, .modal-result { display: flex; gap: 8px; margin-bottom: 10px; }
.modal-row input, .modal-result input { flex: 1; background: var(--page); color: var(--ink);
  border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; font: inherit; font-size: 13px; }
.modal-result input { font-variant-numeric: tabular-nums; }
.modal-err { background: var(--danger-bg); border: 1px solid var(--danger-edge);
             border-radius: 8px; padding: 7px 10px; font-size: 12.5px; margin-bottom: 10px; }
.modal-close { background: transparent; border: 0; color: var(--muted); font: inherit;
               font-size: 12.5px; cursor: pointer; padding: 2px 0; }
.modal-close:hover { color: var(--ink); }
#teamMaxUses { max-width: 110px; }
.modal-hint { color: var(--muted); font-size: 12px; margin: 0 0 12px; }
.modal-actions { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
.modal-actions .spacer { flex: 1; }
/* the display rules above beat the hidden attribute unless it is spelled out */
.modal-row[hidden], .modal-result[hidden], .modal-actions[hidden] { display: none; }

/* One focus ring for every control. Only the sign-in form had one, so tabbing
   through the dashboard's filters or the admin forms showed nothing. No radius
   here: an outline already follows the element's own corners, and setting one
   would reshape the element while it happens to be focused. */
:where(input, select, button, a):focus-visible {
  outline: 2px solid var(--s-out); outline-offset: 2px;
}
.field input:focus-visible { outline-offset: -1px; }

/* ---- shared header nav ---- */
.brand { display: flex; align-items: center; gap: 9px; flex: 0 0 auto;
         text-decoration: none; color: inherit; margin-right: 4px; }
/* flex, not `place-items` on a grid: the SVG is a replaced element and grid
   puts its start corner on the centre instead of centring it */
.markwrap { display: flex; flex: 0 0 auto; }
.mark { display: block; }
.navlinks { display: flex; gap: 2px; }
.navlink { color: var(--ink-2); text-decoration: none; font-size: 13px;
           padding: 5px 10px; border-radius: 7px; }
/* --page, not --surface: the header bar is --surface now, so a --surface chip
   would be invisible against it */
.navlink:hover { color: var(--ink); background: var(--page); }
.navlink.current { color: var(--ink); background: var(--page);
                   border: 1px solid var(--border); padding: 4px 9px; }
.accountwrap { position: relative; }
.account { display: flex; align-items: center; gap: 6px; background: var(--surface);
           color: var(--ink); border: 1px solid var(--border); border-radius: 8px;
           padding: 5px 10px; font: inherit; font-size: 13px; cursor: pointer; }
.account .caret { color: var(--muted); font-size: 11px; }
.menu { position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
        min-width: 200px; background: var(--surface); border: 1px solid var(--border);
        border-radius: 10px; padding: 6px; box-shadow: 0 10px 30px rgba(0,0,0,.35); }
.menu[hidden] { display: none; }
.menu-email { color: var(--muted); font-size: 12px; padding: 6px 8px 8px; }
.menu-sep { height: 1px; background: var(--border); margin: 0 4px 6px; }
.menu-item { display: block; color: var(--ink); text-decoration: none; font-size: 13px;
             padding: 7px 8px; border-radius: 7px; }
.menu-item:hover { background: var(--page); }

/* ---- first-paint skeleton ----
   The shell (header, filters, cards) is static HTML, but tiles, the chart and
   the session list are built from data. Without placeholders the page paints
   as an empty dark rectangle and then jumps once the fetch resolves, so each
   region reserves its real size up front. .skel-chart matches the daily
   chart's rendered height (plotH 230 + margins 38). */
.skel { background: linear-gradient(90deg, var(--page) 25%, var(--border) 37%, var(--page) 63%);
        background-size: 400% 100%; border-radius: 6px;
        animation: skelshift 1.4s ease-in-out infinite; }
@keyframes skelshift { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) {
  .skel { animation: none; background: var(--page); }
}
.skel-label { height: 18px; width: 58%; margin-bottom: 10px; }
.skel-value { height: 38px; width: 42%; }
.skel-chart { height: 268px; }
.skel-row { height: 38px; margin: 6px 0; }
.menu-org { width: 100%; text-align: left; background: none; border: 0;
            font: inherit; cursor: pointer; }
.menu-org.active { color: var(--muted); cursor: default; }

/* ---------------------------------------------------------------------------
   Narrow viewports
   ---------------------------------------------------------------------------
   Two width breakpoints and one pointer query, and all three only ADD to the
   default: the desktop layout above is what a 1100px column is tuned for and
   nothing below reaches it.

   760px is the threshold because that is the width at which the header's four
   flex children -- brand, two page links, account -- stop fitting on one line
   at their real type size. Everything else that has to give way gives way at
   the same width, so there is one place to reason about rather than six
   different points at which the page changes shape. 430px is the second band:
   phones held in portrait, where a label and its control no longer share a
   line and the stat tiles have to go single file.
   --------------------------------------------------------------------------- */
@media (max-width: 760px) {
  .wrap { padding: 16px 14px 40px; }
  .topin { padding: 9px 14px; }

  /* The nav drops to its own full-width line rather than being truncated: both
     links are how you move between the only two pages there are, and a header
     that hides half its navigation on a phone is the wrong half to hide. The
     order values put brand and account back on line one with the spacer
     between them; `width: 100%` is what forces the break. */
  .topin { flex-wrap: wrap; row-gap: 8px; }
  .navlinks { order: 3; width: 100%; }
  .topin .spacer, .accountwrap { order: 2; }

  /* the account button carries an organization name, which is free text and can
     be long enough on its own to push the header past the viewport */
  #navWho { display: block; max-width: 16ch; overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }

  /* The spacer exists to push "fetched at / Refresh" to the far right of a
     single row. Once the row wraps it is a flex item that eats an entire line
     and strands that group below the filters, so it stops being a spacer. */
  .filters .spacer { display: none; }
  .filters { gap: 8px 10px; }
  /* a select given no basis refuses to shrink below its longest option, and a
     project path is long; min-width is already on .fgroup, this is the other half */
  select.project { flex: 1 1 auto; min-width: 0; max-width: 100%; }

  .card { padding: 14px 14px; }
}

@media (max-width: 430px) {
  /* Below this the label and its control stop fitting side by side -- "Stack by"
     plus a two-button segment is wider than the viewport. Wrapping inside the
     group puts the label on its own line above the control it names, which is
     still the pairing the group exists to preserve. */
  .fgroup { flex-wrap: wrap; gap: 4px 7px; }
  .fgroup .flabel { flex: 0 0 100%; }
  /* the refresh group is a note and a button, not a label and a control: it
     reads as one line or not at all */
  .fgroup:last-child { flex-wrap: nowrap; }
  .fgroup:last-child .flabel { flex: 0 1 auto; }

  /* auto-fit already drops to one column here (280px of content cannot hold two
     150px tracks), but the tile is then a full-width band and 26px numerals
     over 14px of padding makes six of them most of the first screen */
  .tiles { gap: 10px; }
  .tile { padding: 12px 14px; }
  .tile .value { font-size: 22px; }

  /* the team invite pane puts a domain and a use count on one row; at this
     width neither field is wide enough to type into */
  .modal-row { flex-wrap: wrap; }
  .modal-row input { flex: 1 1 100%; }
  #teamMaxUses { max-width: none; }
}

/* Touch targets are about the pointer, not the viewport: a 24px-tall segment
   button is just as hard to hit on an 11in tablet as on a phone. ~40px is the
   floor, reached by padding so the type size and the radii stay as designed. */
@media (pointer: coarse) {
  .seg button { padding: 9px 12px; }
  .viewtoggle button { padding: 8px 11px; }
  button.refresh, .pager button { padding: 9px 14px; }
  .navlink { padding: 9px 10px; }
  .navlink.current { padding: 8px 9px; }
  .account { padding: 9px 12px; }
  .menu-item { padding: 10px 8px; }
  select.project, .modal-row input, .modal-result input { padding: 9px 10px; }
  /* a text-link button has no box to pad, so it gets an explicit one */
  .linkbtn { min-height: 40px; display: inline-flex; align-items: center; }
  .modal-close { min-height: 40px; }
}
