/* Integra app shell — dashboard layout. Hairlines and whitespace, no cards. */

/* --row-hover now lives in tokens.css so the wash follows the theme (#F3F3F6
   light / #1E1D25 dark). Redeclaring it here would pin the app shell to the
   light value, since this sheet loads after tokens.css. */

/* No spinners: data containers hold their height (min-heights below) and fade
   in when the first render lands. html.js-loading is set by a 2-line inline
   script in <head> and removed by dashboard.js in a finally, so a mid-boot
   failure can never leave the page veiled. base.css zeroes the transition
   under prefers-reduced-motion. */
#stat-tiles, #growth, #streaks, #accounts, #phones, #phones-note, #feed {
  transition: opacity 120ms var(--ease);
}

html.js-loading #stat-tiles,
html.js-loading #growth,
html.js-loading #streaks,
html.js-loading #accounts,
html.js-loading #phones,
html.js-loading #phones-note,
html.js-loading #feed {
  opacity: 0;
}

.app-wrap {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 clamp(16px, 4vw, 48px);
}

/* ---- Top bar ---- */

#app-header {
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  padding-top: var(--s4);
  padding-bottom: var(--s4);
}

.header-brand { display: inline-flex; }
/* One height for both themes: light and dark now ship the SAME badged lockup
   (1080×267, inverted for the dark surface), so the identity keeps its mark and
   does not change size or shape when the theme flips. */
.header-brand img { display: block; height: 28px; width: auto; }

.header-right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s4);
  flex-wrap: wrap;
  /* Grow to fill whatever is left on its flex line. On one line that is the
     space `justify-content: space-between` was already leaving, so desktop is
     unchanged; once the cluster wraps to a line of its own it fills that line,
     and flex-end lands Sign out exactly on the content edge instead of 12px
     short of the tab-bar hairline directly beneath it. */
  flex: 1 1 auto;
  min-width: 0;
}

#greeting {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  min-height: 1em;
}

.greeting-name { font-weight: 600; font-size: 15px; }
/* .greeting-date wears .label from base.css — "MON 28 JUL", 11px mono. */

.btn-sm { padding: 13px 16px; font-size: 13px; }

/* Avatar chip — creator initials ahead of the name in the greeting. A circle,
   same shape class as the status dots; fill is the documented blurple wash
   (--blurple-wash: rgba of --blurple, 0.10 on light and 0.16 on dark, where
   0.10 would read 1.10:1 and disappear). Sits in #greeting's baseline flow:
   its own 12px text baseline lines up with the name's. */
.avatar-chip {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--blurple-text);
  background: var(--blurple-wash);
}

/* ---- Tab bar (Overview / Records) ---- */
/* Directly under the app header, above the stat tiles. Same mono/underline
   pattern as .toggle-btn, keyed off aria-selected; the hairline under the bar
   stays on the content column (#tabs lives inside .app-wrap). The 2px blurple
   underline lands flush on that hairline. */
#tabs {
  display: flex;
  align-items: stretch;
  gap: var(--s5);
  /* All sides pinned so a `main > section` shorthand can never push the
     hairline away from the buttons' underline slot. */
  padding: var(--s3) 0 0;
  border-bottom: 1px solid var(--rule);
}

#tabs button {
  appearance: none;
  background: none;
  border: 0;
  border-radius: 0;
  position: relative;
  min-height: 40px;
  min-width: 40px;
  display: inline-flex;
  align-items: flex-end;
  justify-content: center;
  padding: 12px 6px 8px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--graphite);
  transition: color var(--t-fast) var(--ease);
}

#tabs button::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--blurple);
  opacity: 0;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
}

#tabs button:hover { color: var(--ink); }
#tabs button[aria-selected="true"] { color: var(--ink); }
#tabs button[aria-selected="true"]::after { opacity: 1; transform: scaleX(1); }

/* ---- Section-label glyphs ---- */
/* 16×16 inline line icons ahead of section labels (aria-hidden in markup).
   Inline so glyph and text share one baseline: -4px drop centers the 16px
   glyph on the 12px label's cap height. 8px gap, graphite like the label. */
.label > svg,
svg.label-glyph {
  width: 16px;
  height: 16px;
  flex: none;
  color: var(--graphite);
  vertical-align: -4px;
  margin-right: var(--s2);
}

/* ---- Rows ---- */

main.app-wrap { padding-bottom: var(--s8); }

main > section {
  padding: var(--s5) 0;
  border-bottom: 1px solid var(--rule);
}

main > section.row-bottom { border-bottom: 0; }

/* ---- Row 1: stat tiles ---- */

#stat-tiles {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  min-height: 96px; /* label + 24px figure + delta line, reserved pre-render */
}

.stat-tile {
  min-width: 0;
  padding: var(--s3) var(--s3) var(--s3) 0;
}

.stat-tile + .stat-tile {
  border-left: 1px solid var(--rule);
  padding-left: var(--s3);
}

.stat-value {
  font-weight: 500;
  font-size: 24px;
  line-height: 1.2;
  margin-top: var(--s2);
  overflow-wrap: anywhere;
}

.stat-sub {
  font-size: 13px;
  color: var(--graphite);
  margin-top: var(--s1);
}

/* ---- Row 2: growth chart + streak calendar ---- */

.row-growth {
  display: grid;
  grid-template-columns: 2fr 1fr;
}

.growth-panel { min-width: 0; padding-right: var(--s6); }

.streak-panel {
  min-width: 0;
  border-left: 1px solid var(--rule);
  padding-left: var(--s6);
}

.growth-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3) var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s4);
}

.growth-toggles {
  display: flex;
  gap: var(--s3) var(--s5);
  flex-wrap: wrap;
}

.toggle-group { display: flex; gap: var(--s3); }

.toggle-btn {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  position: relative;
  min-height: 40px;
  min-width: 40px;
  display: inline-flex;
  align-items: flex-end;
  justify-content: center;
  padding: 12px 6px 4px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--graphite);
  transition: color var(--t-fast) var(--ease);
}

/* Active underline slides/fades in over 150ms instead of snapping. The
   transparent 2px border above reserves its slot; ::after overlays it. */
.toggle-btn::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  background: var(--blurple);
  opacity: 0;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
}

.toggle-btn:hover { color: var(--ink); }
.toggle-btn[aria-pressed="true"] { color: var(--ink); }
.toggle-btn[aria-pressed="true"]::after { opacity: 1; transform: scaleX(1); }

#growth { min-height: 260px; }

.streak-panel .label { display: block; }
#streaks { margin-top: var(--s4); overflow-x: auto; min-height: 160px; }

/* ---- Row 3: accounts ---- */

.row-accounts > .label { display: block; }
#accounts { margin-top: var(--s3); min-height: 240px; /* five rows, reserved pre-render */ }

.accounts-empty,
.phones-empty {
  font-size: 15px;
  color: var(--graphite);
  padding: var(--s4) 0;
  margin: 0;
  max-width: var(--measure);
}

/* Fixed name and handle tracks, not max-content: every row is its own grid, so
   content-sized tracks are solved per row and the columns never line up —
   measured @handle starting at 328/328/328/356/328 and the phone label at
   430/414/430/465/430 across the five rows. 132px and 116px are the measured
   widest name ("YouTube Shorts", 112px) and handle ("@AvaRaeClips", 94px) plus
   headroom; anything longer truncates below rather than shunting the column.
   The followers and spark tracks are already anchored to the right edge, so
   they can stay content-sized. */
.account-row {
  display: grid;
  grid-template-columns: 10px 18px 132px 116px 1fr max-content 96px;
  grid-template-areas: "dot glyph name handle phone followers spark";
  align-items: center;
  column-gap: var(--s4);
  padding: var(--s3) 0;
  position: relative;
  z-index: 0;
}

.account-row + .account-row { border-top: 1px solid var(--rule); }

/* Hover wash — rows are records, not buttons, so the cursor stays default.
   The wash bleeds var(--s3) past the text edge on a non-interactive pseudo
   layer, so the hairlines above and below keep exactly to the content column.
   Shared with the phone rows below: same record, same feedback. */
.account-row::before,
.phone-row::before {
  content: '';
  position: absolute;
  inset: 0 calc(-1 * var(--s3));
  z-index: -1;
  border-radius: var(--radius);
  pointer-events: none;
  transition: background-color var(--t-fast) var(--ease);
}

.account-row:hover::before,
.phone-row:hover::before { background: var(--row-hover); }

.acc-dot { grid-area: dot; }

.acc-glyph {
  grid-area: glyph;
  display: inline-flex;
  width: 16px;
  height: 16px;
  color: var(--ink);
}
.acc-glyph svg { width: 16px; height: 16px; display: block; }

/* Both sit in fixed tracks, so an unusually long name or handle ends in an
   ellipsis instead of pushing every column after it out of alignment. */
.acc-name,
.acc-handle,
.ph-handle {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.acc-name { grid-area: name; font-weight: 600; font-size: 15px; }

.acc-handle { grid-area: handle; font-size: 13px; }
.acc-handle:hover { text-decoration: underline; text-underline-offset: 3px; }

.acc-phone { grid-area: phone; min-width: 0; overflow-wrap: anywhere; }

.acc-followers {
  grid-area: followers;
  text-align: right;
  font-size: 15px;
  white-space: nowrap;
}
.acc-followers .muted { font-size: 12px; }

.acc-spark {
  grid-area: spark;
  justify-self: end;
  min-height: 28px;
}
.acc-spark svg { display: block; }

/* ---- Row 3b: phones ---- */
/* One device per account. The label leads (it is the thing being tracked),
   the check-in reads as metadata, and the 7-day post count closes the row. */

.row-phones > .label { display: block; }

.phones-note {
  margin: var(--s3) 0 0;
  max-width: var(--measure);
  font-size: 13px;
  color: var(--graphite);
  min-height: 21px; /* one line, reserved pre-render */
}

#phones { margin-top: var(--s3); min-height: 220px; /* five rows, reserved pre-render */ }

/* Same fix as .account-row: content-sized label and handle tracks are solved
   per row, which staggered the check-ins across all five rows (measured
   .ph-seen at 499/483/499/507/499). 164px and 112px are the measured widest
   label ("PHONE 01 · SIM US", 153px) and handle (94px) plus headroom, chosen
   tight enough that the longest check-in ("last seen 2d ago · stale", 188px)
   still fits the 1fr track down to the 760px fold. */
.phone-row {
  display: grid;
  grid-template-columns: 10px 164px 18px 112px 1fr max-content;
  grid-template-areas: "dot label glyph handle seen posts";
  align-items: center;
  column-gap: var(--s4);
  padding: var(--s3) 0;
  position: relative;
  z-index: 0;
}

.phone-row + .phone-row { border-top: 1px solid var(--rule); }

.ph-dot { grid-area: dot; }

.ph-label {
  grid-area: label;
  min-width: 0;
  font-size: 15px;
  font-weight: 500;
  overflow-wrap: anywhere;
}

/* No device on record — the row still shows, it just says so. */
.ph-label-none { color: var(--graphite); }

.ph-glyph {
  grid-area: glyph;
  display: inline-flex;
  width: 16px;
  height: 16px;
  color: var(--ink);
}
.ph-glyph svg { width: 16px; height: 16px; display: block; }

/* Truncation rules live with .acc-name / .acc-handle above. */
.ph-handle { grid-area: handle; font-size: 13px; }

.ph-seen {
  grid-area: seen;
  min-width: 0;
  font-size: 13px;
  color: var(--graphite);
}

/* Stale reads in weight, not colour: --attention is a status dot, not text. */
.ph-stale { font-weight: 500; }

.ph-posts {
  grid-area: posts;
  text-align: right;
  font-size: 13px;
  white-space: nowrap;
}
.ph-posts .muted { font-size: 12px; }

/* ---- Row 4: feed + notifications ---- */

.row-bottom {
  display: grid;
  grid-template-columns: 2fr 1fr;
}

.feed-panel { min-width: 0; padding-right: var(--s6); }
.feed-panel > .label { display: block; }
#feed { margin-top: var(--s3); min-height: 320px; /* reserved pre-render */ }

/* Same hover wash as account rows (row structure lives in js/feed.js).
   Compact: vertical padding drops to s3 — the ID+class selector outranks the
   module's own injected .integra-feed-row rule regardless of sheet order. */
#feed .integra-feed-row {
  position: relative;
  z-index: 0;
  padding: var(--s3) 0;
}

#feed .integra-feed-row::before {
  content: '';
  position: absolute;
  inset: 0 calc(-1 * var(--s3));
  z-index: -1;
  border-radius: var(--radius);
  pointer-events: none;
  transition: background-color var(--t-fast) var(--ease);
}

#feed .integra-feed-row:hover::before { background: var(--row-hover); }

/* Post thumbnail — right-side 56px column, added only on rows that carry one
   (.integra-feed-row--thumb, set by js/feed.js). Rows without a thumb keep the
   module's own 3-column template untouched, so their text alignment is
   byte-identical and no empty track (or its gap) is ever reserved — cleaner
   than a permanent fixed column. The hover wash stays on the row's ::before at
   z-index:-1, so it renders under the image. */
#feed .integra-feed-row--thumb {
  grid-template-columns: 16px minmax(0, 1fr) auto 56px;
}

/* Pinned to track 4 so a row with a thumb but no "view ↗" link cannot
   auto-place the image into the link column. */
#feed .integra-feed-thumb {
  grid-column: 4;
  grid-row: 1;
  display: block;
  line-height: 0;
}

#feed .integra-feed-thumb img {
  display: block;
  width: 56px;
  height: 74px;
  object-fit: cover;
  background: var(--row-hover); /* quiet placeholder while the lazy image loads */
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  transition: border-color var(--t-fast) var(--ease);
}

/* No hairline until the picture is actually there (js/feed.js sets data-loaded
   on the load event). A framed empty box reads as a broken image — down the
   length of the feed that patchwork was the loudest thing on the page — while
   a bare wash reads as reserved space, which is what it is. */
#feed .integra-feed-thumb img:not([data-loaded]) { border-color: transparent; }

#feed .integra-feed-view:hover { text-decoration: underline; text-underline-offset: 3px; }
#feed-more { margin-top: var(--s5); }
#feed-end { margin-top: var(--s5); margin-bottom: 0; }

/* start, not the grid default stretch: the panel is ~200px tall and the feed
   beside it runs ~2,600px, so a stretched panel dragged its border-left down
   ~2,400px of empty page — a hairline delimiting a void. Sticky then keeps the
   panel beside the feed as the feed scrolls, so the column stays useful instead
   of simply ending. Reverted to static below 900px, where the panel stacks
   under the feed and has nothing to ride alongside. */
.notify-panel {
  min-width: 0;
  align-self: start;
  position: sticky;
  top: var(--s5);
  border-left: 1px solid var(--rule);
  padding-left: var(--s6);
}
.notify-panel > .label { display: block; }

#prefs { margin-top: var(--s4); }

.field { margin-bottom: var(--s4); }
.field label { font-size: 13px; font-weight: 500; }

.field-check {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
}
.field-check input[type="checkbox"] {
  width: 16px;
  height: 16px;
  flex: none;
  margin: 3px 0 0;
  padding: 0;
  accent-color: var(--blurple);
}
.field-check label { margin: 0; }
.check-note { display: block; }

.prefs-actions {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
}

.install-block {
  margin-top: var(--s6);
  border-top: 1px solid var(--rule);
  padding-top: var(--s5);
}
.install-block > .label { display: block; margin-bottom: var(--s3); }

.link-btn {
  appearance: none;
  background: none;
  border: 0;
  border-radius: var(--radius);
  padding: 0;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-sans);
  font-size: 15px;
  color: var(--blurple-text);
  transition: color var(--t-fast) var(--ease);
}
.link-btn:hover { color: var(--blurple-text-hover); }

#install-steps { margin-top: var(--s4); }
#install-steps .install-os { display: block; margin: var(--s3) 0 var(--s1); }
#install-steps ol { margin: 0 0 var(--s3); padding-left: 18px; }
#install-steps li { margin: 2px 0; }
/* Every number wears Plex Mono — including list markers. Older engines
   without ::marker font support simply keep the fallback. */
#install-steps li::marker {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--graphite);
}

/* ---- Footer ---- */

.app-footer { border-top: 1px solid var(--rule); }

.footer-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding-top: var(--s5);
  padding-bottom: var(--s5);
}
.footer-inner img { display: block; }

/* ---- Responsive ---- */

@media (max-width: 900px) {
  .row-growth,
  .row-bottom { grid-template-columns: 1fr; }

  .growth-panel,
  .feed-panel { padding-right: 0; }

  .streak-panel,
  .notify-panel {
    border-left: 0;
    padding-left: 0;
    border-top: 1px solid var(--rule);
    margin-top: var(--s6);
    padding-top: var(--s6);
  }

  .notify-panel { position: static; }
}

@media (max-width: 760px) {
  #stat-tiles { grid-template-columns: 1fr 1fr; }

  .stat-tile:nth-child(odd) { border-left: 0; padding-left: 0; }
  .stat-tile:nth-child(-n+2) { padding-top: var(--s2); }
  .stat-tile:nth-child(n+3) { border-top: 1px solid var(--rule); padding-bottom: var(--s2); }
}

/* Phone rows fold earlier than account rows: they carry a sentence ("last
   seen 2d ago · stale") where the account row carries a figure, and below
   ~760px that sentence wraps inside its column and leaves the rows ragged.
   Folded, every row is the same three lines: label + count, then the account
   the phone runs, then the check-in. Worst case at 360px: 10 + 18 +
   "4 posts · 7 days" (~126px) + three 12px gaps ≈ 190px of fixed tracks — the
   1fr label column takes what is left, and overflow-wrap keeps a long label
   inside it. */
@media (max-width: 760px) {
  .phone-row {
    grid-template-columns: 10px 18px 1fr max-content;
    grid-template-areas:
      "dot label  label  posts"
      ".   glyph  handle handle"
      ".   .      seen   seen";
    column-gap: var(--s3);
    row-gap: var(--s1);
  }
}

@media (max-width: 640px) {
  /* The sparkline shares the handle's line instead of taking a fourth one of
     its own: on its own line it sat left with ~250px of empty space beside it
     and pushed every row to ~130px, so barely two and a half accounts fit a
     390×844 screen. Right-aligned under the followers figure, it costs nothing
     — worst case at 360px the right column is the widest of the two (~126px)
     and the 1fr text column still clears 126px for "@AvaRaeClips" (94px). */
  .account-row {
    grid-template-columns: 10px 18px 1fr max-content;
    grid-template-areas:
      "dot glyph name   followers"
      ".   .     handle spark"
      ".   .     phone  phone";
    row-gap: var(--s1);
  }

  .acc-spark { justify-self: end; margin-top: 0; }

  /* Feed thumb shrinks to 48×64. Worst case at 360px:
     16 + 48 + "view ↗" (~48) + three 12px gaps ≈ 148px of fixed tracks;
     the minmax(0,1fr) content column absorbs the rest — no overflow. */
  #feed .integra-feed-row--thumb {
    grid-template-columns: 16px minmax(0, 1fr) auto 48px;
  }

  #feed .integra-feed-thumb img {
    width: 48px;
    height: 64px;
  }
}

@media (max-width: 420px) {
  /* 20px mono keeps a 10-digit figure on one line in the ~124px 2-up column;
     overflow-wrap:anywhere on .stat-value remains the last-resort guard. */
  .stat-value { font-size: 20px; }

  /* The theme toggle adds a 36px item to the header row. At 360px the 16px
     gap would push Sign out onto a line of its own and grow the header by
     52px; 8px keeps chip + greeting + toggle + Sign out on the one line the
     header used before the toggle existed. */
  .header-right { gap: var(--s2); }
}
