/* tg.css — Pigment Uptime Telegram Mini App. Dark, premium, theme-aware:
   Telegram theme variables with dark fallbacks for non-Telegram browsers. */

/* Yekan Bakh 3, self-hosted — the same single variable file the panel uses (see
   globals.css for why the weight/width RANGES must be declared). Absolute path:
   this stylesheet is served at /tg/tg.css, so a relative url() would look for
   the font under /tg/. */
@font-face {
  font-family: "Yekan Bakh";
  src: url("/vendor/fonts/YekanBakh-VF.woff2") format("woff2");
  font-weight: 100 950;
  font-stretch: 62.5% 150%;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: var(--tg-theme-bg-color, #0b1220);
  --card: var(--tg-theme-secondary-bg-color, #121b2e);
  --text: var(--tg-theme-text-color, #e9eefb);
  --hint: var(--tg-theme-hint-color, #8b98b5);
  --link: var(--tg-theme-link-color, #6ab0ff);
  --accent: var(--tg-theme-button-color, #3b82f6);
  --accent-text: var(--tg-theme-button-text-color, #ffffff);
  --border: rgba(139, 152, 181, 0.16);

  /* uptime scale */
  --c-none: #263041;
  --c-100: #16c964;   /* vivid green */
  --c-99:  #58d98d;   /* lighter green */
  --c-95:  #b9c93f;   /* yellow-green */
  --c-80:  #e69a2e;   /* amber */
  --c-low: #e5484d;   /* red */

  /* chart ink — series hues live in tg.js (they go on SVG attributes);
     these are the chrome tokens. Validated against the card surface #121b2e. */
  --grid: rgba(139, 152, 181, 0.16);   /* hairline, solid, recessive */
  --axis-ink: #8b98b5;

  /* status ink for stat tiles: green ≥99.5, amber ≥80, red <80 */
  --s-good: #16c964;
  --s-warn: #e69a2e;
  --s-bad:  #e5484d;

  --cell: 11px;
  --gap: 3px;
  --radius: 14px;

  /* Fixed chrome heights, declared once so the body padding that clears them
     can never drift out of step with the bars themselves. */
  --appbar-h: 56px;
  --tabbar-h: 62px;

  /* Liquid glass: a translucent pane that samples what is behind it, with a
     bright top edge reading as a lit bevel and a soft drop shadow lifting it
     off the page. saturate() is what stops the blur going grey and lifeless. */
  --glass-bg: color-mix(in srgb, var(--card) 62%, transparent);
  --glass-edge: rgba(255, 255, 255, 0.10);
  --glass-lip: rgba(255, 255, 255, 0.14);
  --glass-shadow: 0 10px 34px rgba(0, 0, 0, 0.34);
  --glass-blur: blur(26px) saturate(185%);

  /* Backdrop for a TRANSPARENT site icon, chosen per icon from its own ink
     (see analysePlate in faviconemoji.js). A blanket light plate was tried and
     measurement rejected it: 3 of 5 transparent icons are light ink, one pure
     white, which a light plate would erase. Opaque icons get no plate at all —
     they cover the tile themselves. */
  --icon-plate: rgba(139, 152, 181, 0.12);   /* neutral: unknown / opaque */
  --icon-plate-light: #eef1f7;
  --icon-plate-dark: #10151f;
  --icon-plate-edge: rgba(0, 0, 0, 0.10);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
/* min-height, NOT height: with a fixed 100% height the bottom padding belongs to
   a box the content overflows, so the footer scrolled underneath the tab bar
   instead of stopping above it. */
html { height: 100%; }
body { min-height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font: 15px/1.45 "Yekan Bakh", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  /* Clear both fixed bars, plus a gap so nothing tucks under the glass. */
  padding: calc(env(safe-area-inset-top) + var(--appbar-h) + 14px) 12px
           calc(env(safe-area-inset-bottom) + var(--tabbar-h) + 18px);
}

#app { max-width: 680px; margin: 0 auto; }
.view { animation: fade-in 0.22s ease both; }
@keyframes fade-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* ===== top bar ===== */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 8px 2px 14px;
}
.topbar-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
.titles { min-width: 0; }
.title { font-size: 18px; font-weight: 700; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.subtitle { font-size: 14.5px; color: var(--hint); margin-top: 1px; }
.nav { display: flex; gap: 6px; flex: none; }
.nav-btn {
  width: 34px; height: 34px; border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--card); color: var(--text);
  font-size: 20px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 0.15s ease, opacity 0.15s ease;
}
.nav-btn:active { background: var(--border); }
.nav-btn:disabled { opacity: 0.35; cursor: default; }
.back-btn { flex: none; }

/* ===== period bar — the selected period, large, with its scope switch =====
   Everything below this bar is scoped to what it says, so it is the loudest
   element on the screen. The label is a fixed-width flex child so stepping
   Jan → February doesn't shove the arrows sideways. */
.periodbar {
  display: flex; flex-direction: column; align-items: center;
  gap: 10px; padding: 2px 2px 18px;
}
/* At the top of a view the bar IS the heading and must sit tight under the
   chrome. Mid-page (the Shop tab steps months half way down the screen) it needs
   air above it, or it reads as the footer of the block it follows. Sibling
   selector rather than padding so one bar can be both. */
.card + .periodbar, .stat-grid + .periodbar, .section-head + .periodbar { padding-top: 26px; }

/* The period control rides in the site header beside the Year/Month switch:
   which period, and what kind of period, on ONE row. Stacking them put two
   full-width bars between the tabs and the first number. */
/* Its own row under the tab bar: it scopes the Uptime tab, and sitting in the
   header it looked like it scoped everything. */
.sitebar-scope {
  display: flex; align-items: center; gap: 8px;
  justify-content: space-between; width: 100%;
  padding: 0 2px; margin: 2px 0 14px;
}
.period-inline { display: inline-flex; align-items: center; gap: 3px; flex: none; }
.period-inline .nav-btn { width: 30px; height: 30px; border-radius: 9px; }
.period-inline-label {
  font-size: 17px; font-weight: 700; letter-spacing: -0.01em;
  min-width: 84px; text-align: center; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.period-nav { display: flex; align-items: center; justify-content: center; gap: 4px; min-width: 0; }
.period-label {
  font-size: 26px; font-weight: 700; letter-spacing: -0.02em;
  padding: 0 6px; min-width: 148px; text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.seg {
  display: inline-flex; flex: none; padding: 3px; gap: 2px;
  border: 1px solid var(--border); border-radius: 11px; background: var(--card);
}
.seg-btn {
  border: 0; background: transparent; color: var(--hint);
  font-size: 14.5px; font-weight: 600; font-family: inherit;
  padding: 5px 13px; border-radius: 8px; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.seg-btn.active { background: var(--accent); color: var(--accent-text); cursor: default; }
.seg-btn:not(.active):active { background: var(--border); }

@media (max-width: 380px) {
  .period-label { font-size: 22px; min-width: 118px; }
}

/* ===== overview block (stat tiles + charts) ===== */
.overview { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }

.section-head {
  font-size: 13.5px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--hint); text-transform: uppercase;
  margin: 4px 2px 10px;
}
/* A heading that follows ANYTHING starts a new section — without a clear gap
   above it, it reads as a caption belonging to the block it just closed. The
   universal sibling selector on purpose: every screen builds its own run of
   blocks, and listing the ones seen so far only defers the next miss.
   A heading that opens a view has no sibling, so it stays tight to the chrome. */
* + .section-head, * + .list-head { margin-top: 24px; }
.list-head .section-head { margin-top: 0; }
/* Air under the last block, so the final card never butts against the footer
   rule or the tab bar's glass. */
.view, .tabbody { padding-bottom: 20px; }

/* --- stat tiles: 2×2 on a phone, 4-up once there's room --- */
/* Column count and the last tile's width are written by balanceGrids() in tg.js
   from the tile count, so a grid never ends in a lone tile on a row of its own.
   The fallbacks here are what a grid gets if that never runs. */
.stat-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.stat-grid > :last-child { grid-column: span var(--span-narrow, 1); }
.tile {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 11px 12px 10px;
  min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.tile-label { font-size: 12.5px; color: var(--hint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Proportional figures on purpose — tabular-nums makes big numbers look loose. */
.tile-value { font-size: 22px; font-weight: 650; line-height: 1.15; letter-spacing: -0.02em; }
.tile.s-good .tile-value { color: var(--s-good); }
.tile.s-warn .tile-value { color: var(--s-warn); }
.tile.s-bad  .tile-value { color: var(--s-bad); }
.tile.s-none .tile-value { color: var(--hint); }
.tile-caption { font-size: 12px; color: var(--hint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tile-spark { margin-top: 6px; }
.spark { display: block; width: 100%; overflow: visible; }

/* --- charts --- */
/* .card is declared further down, so this needs the extra class to win on padding */
.card.chart-card { display: flex; flex-direction: column; gap: 14px; padding: 13px 14px 10px; }
.chart { margin: 0; }
.chart-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; margin-bottom: 9px;
}
.chart-title { font-size: 13.5px; font-weight: 600; color: var(--text); min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* endpoint value — text stays in ink, the little key carries the series colour */
.chart-latest {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  font-size: 13.5px; color: var(--hint); font-variant-numeric: tabular-nums;
}
.chart-key { width: 8px; height: 3px; border-radius: 2px; flex: none; }

.chart-body { display: flex; align-items: stretch; gap: 6px; }
.chart-yaxis {
  flex: none; width: 36px;
  display: flex; flex-direction: column; justify-content: space-between;
  text-align: right; font-size: 11px; line-height: 1;
  color: var(--axis-ink); font-variant-numeric: tabular-nums;
  padding: 1px 0 1px;
}
.chart-canvas { position: relative; flex: 1; min-width: 0; }
/* overflow visible so a 2px stroke on the first/last point isn't half-clipped */
.chart-svg { display: block; width: 100%; overflow: visible; }
.chart-xaxis {
  display: flex; justify-content: space-between; gap: 6px;
  margin: 5px 0 0 42px;
  font-size: 11px; color: var(--axis-ink); font-variant-numeric: tabular-nums;
}
.chart-xaxis span:nth-child(2) { text-align: center; }
.chart-xaxis span:last-child { text-align: right; }

/* markers: ≥8px with a 2px surface ring so they stay legible over the line */
.chart-mark {
  position: absolute; width: 8px; height: 8px; border-radius: 50%;
  transform: translate(-50%, -50%); pointer-events: none;
  box-shadow: 0 0 0 2px var(--card);
}
.chart-cursor { width: 10px; height: 10px; box-shadow: 0 0 0 2px var(--card), 0 0 0 3px rgba(255,255,255,0.22); }
/* the hit area is the whole plot (plus slack) — never a pinpoint target */
.chart-hit { position: absolute; inset: -8px 0; cursor: crosshair; touch-action: pan-y; }
.chart-hit:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; border-radius: 6px; }
.chart-empty {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 12.5px; color: var(--hint);
}

/* ===== view 1: site list ===== */
.site-list { display: flex; flex-direction: column; gap: 10px; }
.site-row {
  display: flex; align-items: center; gap: 12px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 12px 12px 14px;
  cursor: pointer;
  transition: transform 0.12s ease, background 0.15s ease;
}
.site-row:active { transform: scale(0.985); }
.avatar {
  position: relative; flex: none;
  width: 36px; height: 36px; border-radius: 10px;
  background: var(--icon-plate);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
/* Fill the tile edge to edge. A 22px image centred in a 36px box left a ring of
   container showing, which looks like padding around any icon that has its own
   background — cover + inset:0 makes the icon BE the tile. */
.avatar img { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; }
.avatar-letter {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 15px; color: var(--hint);
}

/* Per-icon backdrop. Applies to every surface that shows a site picture. */
.plate-light { background: var(--icon-plate-light) !important; box-shadow: inset 0 0 0 1px var(--icon-plate-edge); }
.plate-dark  { background: var(--icon-plate-dark)  !important; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.10); }
/* Once a real picture has loaded, the letter goes — otherwise it shows through
   any icon with transparency. */
.avatar.has-img .avatar-letter { display: none; }
.site-body { flex: 1; min-width: 0; }
.site-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
.site-name { font-weight: 600; font-size: 15.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* 30-day uptime, same thresholds as the tiles; the strip beside it is the second channel */
/* "Worth a look" marker beside a site's name. Loud colour, tiny footprint:
   it must catch the eye scanning a list of 35 without competing with the
   uptime badge, which reports something actually broken. */
.needs-look {
  flex: none; color: var(--c-low); font-weight: 800; font-size: 16px;
  line-height: 1; margin-left: -2px; cursor: help;
}
.site-metric { flex: none; margin-left: auto; font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.site-metric.s-good { color: var(--s-good); }
.site-metric.s-warn { color: var(--s-warn); }
.site-metric.s-bad  { color: var(--s-bad); }
.site-metric.s-none { color: var(--hint); font-weight: 500; }
.chev { flex: none; color: var(--hint); font-size: 18px; padding-left: 2px; }

.strip { display: flex; gap: var(--gap); overflow-x: auto; scrollbar-width: none; }
.strip::-webkit-scrollbar { display: none; }

/* ===== cells (shared) ===== */
.cell {
  flex: none;
  width: var(--cell); height: var(--cell);
  border-radius: 3px;
  background: var(--c-none);
}
.c-none   { background: var(--c-none); }
.c-100    { background: var(--c-100); }
.c-99     { background: var(--c-99); }
.c-95     { background: var(--c-95); }
.c-80     { background: var(--c-80); }
.c-low    { background: var(--c-low); }
.c-future { background: transparent; box-shadow: inset 0 0 0 1px var(--border); }
.c-void   { background: transparent; }

/* ===== view 2: year heatmap ===== */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
}
/* Blocks in a run (one card per Elementor form, a tile grid then a chart) were
   touching: elsewhere a section heading happened to sit between them and hid
   the missing gap. */
.card + .card, .stat-grid + .card, .card + .stat-grid, .stat-grid + .stat-grid { margin-top: 10px; }
/* Months stacked VERTICALLY — one row per month. Cells are comfortably tappable;
   if 31 of them don't fit, the whole block scrolls horizontally as one unit
   (month labels stay pinned on the right/left edge). */
.year-scroll { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.year-scroll::-webkit-scrollbar { display: none; }
.year-months { display: flex; flex-direction: column; gap: 4px; width: max-content; min-width: 100%; }
.month-row { display: flex; align-items: center; gap: 8px; }
.month-name {
  position: sticky; left: 0; z-index: 2;
  flex: none; width: 32px;
  font-size: 12.5px; font-weight: 600; color: var(--hint); text-align: right;
  background: var(--card);
  padding-right: 2px;
}
.month-strip {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(31, minmax(18px, 1fr));
  gap: 3px;
}
.month-strip .cell {
  width: auto; height: 18px;      /* width comes from the grid track */
  border-radius: 4px;
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.month-strip .cell[data-day]:active { transform: scale(1.25); }
.cell.is-active {
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px #6ea8ff;
  transform: scale(1.15);
}

/* legend */
.legend {
  display: flex; align-items: center; gap: 4px;
  margin-top: 12px; flex-wrap: wrap;
}
.legend .cell { cursor: default; }
.legend-label { font-size: 12.5px; color: var(--hint); margin: 0 4px; }
.legend-none { display: flex; align-items: center; gap: 4px; margin-left: auto; }

/* summary */
.summary { margin-top: 12px; font-size: 14.5px; color: var(--hint); display: flex; gap: 8px; flex-wrap: wrap; padding: 0 2px; }
.summary-stat { color: var(--text); }
.summary-stat:first-child { font-weight: 600; }
.summary-dot { color: var(--hint); }

/* ===== popover ===== */
.popover {
  position: fixed; z-index: 50;
  background: #0e1526;
  color: #e9eefb;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 11px;
  font-size: 13.5px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  pointer-events: none;
  animation: fade-in 0.12s ease both;
}
/* tail — points at the tapped day cell */
.popover-tail {
  position: absolute; bottom: -7px;
  width: 0; height: 0;
  margin-left: -7px;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
  border-top: 7px solid #0e1526;
  filter: drop-shadow(0 1px 0 var(--border));
}
.popover.below .popover-tail {
  bottom: auto; top: -7px;
  border-top: none;
  border-bottom: 7px solid #0e1526;
  filter: drop-shadow(0 -1px 0 var(--border));
}

.popover-head { display: flex; align-items: baseline; gap: 6px; margin-bottom: 4px; }
.popover-day { font-weight: 700; }
.popover-site { font-size: 12.5px; color: var(--hint); max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.popover-val { display: flex; align-items: center; gap: 6px; font-weight: 600; margin-bottom: 6px; }
.popover-val.ok { color: var(--c-100); }
.popover-val.warn { color: var(--c-80); }
.popover-val.bad { color: var(--c-low); }
.popover-val.muted { color: #8b98b5; font-weight: 500; margin-bottom: 0; }
.popover-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.popover-rows { display: grid; grid-template-columns: auto auto; gap: 2px 14px; }
.popover-row { display: contents; }
.popover-row .k { color: #8b98b5; }
.popover-row .v { text-align: right; font-variant-numeric: tabular-nums; }
.popover-err {
  margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--border);
  color: var(--c-low); max-width: 220px;
}

/* ===== fixed app bar + footer ===== */
/* Edge-to-edge sticky bar, mirroring the tab bar at the other end of the screen:
   same glass, same hairline, one rule each. A floating rounded pane was tried and
   read as a third, competing surface between them. */
#appbar {
  position: fixed; z-index: 40;
  top: 0; left: 0; right: 0;
  display: flex; align-items: center; gap: 10px;
  height: calc(var(--appbar-h) + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) 14px 0;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--glass-edge);
  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.18);
}
.appbar-logo { width: 30px; height: 30px; border-radius: 9px; flex: none; }
.appbar-titles { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.appbar-name { font-size: 16px; font-weight: 700; letter-spacing: -0.01em; }
/* The subtitle is live status, written by tg.js — it earns the space a static
   tagline was wasting. */
.appbar-sub {
  font-size: 12px; color: var(--hint);
  display: inline-flex; align-items: center; gap: 5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.appbar-sub .pip {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--hint); box-shadow: 0 0 0 3px transparent;
}
.appbar-sub.up   .pip { background: var(--c-100); box-shadow: 0 0 8px color-mix(in srgb, var(--c-100) 60%, transparent); }
.appbar-sub.down .pip { background: var(--c-low); box-shadow: 0 0 8px color-mix(in srgb, var(--c-low) 60%, transparent); }
.appbar-sub.up   { color: var(--c-100); }
.appbar-sub.down { color: var(--c-low); }
/* Version: quiet metadata, not a badge. Tabular so it doesn't jitter on updates. */
.appbar-version {
  margin-left: auto; flex: none;
  font-size: 11px; color: var(--hint); opacity: 0.6;
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
}
#appfooter {
  display: flex; justify-content: center; align-items: center; gap: 6px; flex-wrap: wrap;
  /* Space above AND below: body padding clears the tab bar, this keeps the
     footer from reading as clipped against the end of the scroll. */
  margin: 30px auto 0; padding: 16px 0 8px;
  border-top: 1px solid var(--border);
  font-size: 12.5px; color: var(--hint); max-width: 680px;
  opacity: 0.75;
}
#appfooter .dot { opacity: 0.5; }

/* ===== bottom tab bar ===== */
#tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex; align-items: stretch;
  min-height: var(--tabbar-h);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border-top: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--glass-lip);
  padding-bottom: env(safe-area-inset-bottom);
  /* Nothing scrolls past it edge-on: the bar is the last thing on the screen. */
}
#tabbar:empty { display: none; }
.tab {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  padding: 8px 4px 7px;
  border: 0; background: transparent; color: var(--hint);
  font: inherit; cursor: pointer;
  transition: color 0.15s ease;
  /* A tab must stay comfortably tappable — 44px is the accessible floor. */
  min-height: 48px;
}
.tab-ico { display: inline-flex; line-height: 0; opacity: 0.7; transition: opacity 0.15s ease, transform 0.15s ease; }
.tab.active .tab-ico { opacity: 1; transform: translateY(-1px); }
.tab-label { font-size: 12px; font-weight: 600; letter-spacing: 0.01em; }
.tab.active { color: var(--accent); }
.tab:not(.active):active { color: var(--text); }

/* ===== gate / error ===== */
.gate {
  min-height: 70vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 10px; padding: 24px;
}
.gate-icon { font-size: 44px; }
.gate-title { font-size: 20px; font-weight: 700; }
.gate-text { color: var(--hint); max-width: 320px; }
.btn {
  margin-top: 8px;
  background: var(--accent); color: var(--accent-text);
  border: 0; border-radius: 10px;
  padding: 10px 22px; font-size: 15px; font-weight: 600;
  cursor: pointer;
}
.btn:active { opacity: 0.85; }

/* ===== skeletons ===== */
.skeleton {
  background: linear-gradient(90deg, rgba(139,152,181,0.10) 25%, rgba(139,152,181,0.22) 50%, rgba(139,152,181,0.10) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.3s linear infinite;
  border-radius: 6px;
}
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
.skeleton-row { pointer-events: none; }
.sk-avatar { width: 36px; height: 36px; border-radius: 10px; flex: none; }
.sk-line { height: 12px; width: 45%; margin-bottom: 9px; }
.sk-strip { height: var(--cell); width: 100%; border-radius: 3px; }
.sk-grid { height: 130px; width: 100%; border-radius: 8px; }
.sk-chart { height: 148px; width: 100%; border-radius: 8px; }
.sk-tile-label { height: 10px; width: 60%; margin-bottom: 8px; }
.sk-tile-value { height: 20px; width: 75%; margin-bottom: 0; }

@media (min-width: 480px) {
  :root { --cell: 12px; }
  .stat-grid { grid-template-columns: repeat(var(--cols, 4), minmax(0, 1fr)); }
  .stat-grid > :last-child { grid-column: span var(--span-wide, 1); }
  .tile-value { font-size: 24px; }
}

/* ===== hosting renewals ===== */

.host-list { display: flex; flex-direction: column; gap: 10px; }
.host-row {
  background: var(--card); border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius); padding: 12px 14px;
}
.host-row.h-ok   { border-left-color: var(--c-100); }
.host-row.h-bad  { border-left-color: var(--c-low); }
.host-row.h-warn { border-left-color: var(--c-80); }
.host-row.h-off  { border-left-color: var(--hint); opacity: 0.75; }

.host-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.host-name { font-weight: 700; font-size: 15px; flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.host-badge {
  flex: none; font-size: 12.5px; padding: 2px 9px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--hint); white-space: nowrap;
}
.host-badge.h-ok   { color: var(--c-100); border-color: rgba(22,201,100,0.35); background: rgba(22,201,100,0.10); }
.host-badge.h-bad  { color: var(--c-low); border-color: rgba(229,72,77,0.4); background: rgba(229,72,77,0.12); }
.host-badge.h-warn { color: var(--c-80); border-color: rgba(230,154,46,0.4); background: rgba(230,154,46,0.12); }

.host-due { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: 14.5px; }
.host-date { font-variant-numeric: tabular-nums; font-weight: 600; }
.host-jdate { color: var(--hint); font-size: 13.5px; font-variant-numeric: tabular-nums; }
.host-rel { color: var(--hint); font-size: 13.5px; }
.host-rel.bad { color: var(--c-low); font-weight: 600; }
.host-sub { color: var(--hint); font-size: 13px; margin-top: 3px; }

/* ===== robots.txt =====
   Shown verbatim and monospaced: a robots file is whitespace- and order-
   sensitive, so reflowing it would misrepresent what the crawler reads. Long
   lines scroll inside the block rather than widening the page. */
.codeblock {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 12px 14px; margin: 0;
  font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  color: var(--text);
  white-space: pre; overflow-x: auto; -webkit-overflow-scrolling: touch;
  max-height: 60vh; overflow-y: auto;
  tab-size: 4;
}
.robots-alarm {
  border-color: color-mix(in srgb, var(--c-low) 45%, transparent);
  background: color-mix(in srgb, var(--c-low) 10%, var(--card));
  font-size: 13.5px; line-height: 1.55; color: var(--text);
}
.robots-alarm .empty-title { color: var(--c-low); }
.robots-foot { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.robots-foot .btn-inline { margin-top: 0; }
.robots-when { margin-top: 10px; font-size: 12px; color: var(--hint); opacity: 0.8; word-break: break-all; }

/* ===== loading =====
   A turning ring says "working" the instant it appears. A box with the word
   "Loading" in it reads as content that arrived empty. */
.loading { display: flex; align-items: center; justify-content: center; padding: 48px 0; }
.spinner {
  width: 30px; height: 30px; border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--hint) 26%, transparent);
  border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Motion sensitivity: slow it right down rather than freezing it — a still ring
   is indistinguishable from a broken one. */
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.6s; } }

/* ===== a day, written in both calendars =====
   Gregorian and Jalali are ONE fact — the same day twice — so they read as one
   run of text with a dot between, not as two competing chips. Both in
   YYYY-MM-DD so the eye can line them up digit for digit. */
.dates { font-variant-numeric: tabular-nums; white-space: nowrap; }
.dates .d-sep { opacity: 0.45; padding: 0 4px; }
.dates .d-j { color: var(--hint); }

/* The page a form sits on is the one thing in that card you tap through to, so
   it gets a real target instead of a line of coloured text. */
.page-link {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 34px; padding: 4px 10px; margin: 3px 0;
  border: 1px solid color-mix(in srgb, var(--link) 30%, transparent);
  border-radius: 9px;
  background: color-mix(in srgb, var(--link) 10%, transparent);
  font-size: 14px; font-weight: 600;
}

/* Anything carrying `is-link` is an anchor, and an unstyled anchor falls back to
   the BROWSER's link blue — a saturated hue picked for white paper that all but
   disappears on a dark card. Every link here uses the theme's own colour, which
   boot has already checked for contrast. */
.is-link { color: var(--link); text-decoration: none; }
.is-link:active { opacity: 0.7; }

/* Renewal price. A link when there is somewhere to pay, plain text otherwise —
   the amount answers "how much", the link answers "where". */
.host-price, .upnext-price {
  font-size: 13px; font-variant-numeric: tabular-nums;
  padding: 1px 7px; border-radius: 6px;
  background: rgba(139, 152, 181, 0.14); color: var(--text);
  white-space: nowrap;
}
.host-price.is-link, .upnext-price.is-link {
  color: var(--link); text-decoration: none;
  background: color-mix(in srgb, var(--link) 14%, transparent);
}
.host-price.is-link:active, .upnext-price.is-link:active { opacity: 0.7; }
.host-btn.h-renew {
  display: inline-flex; align-items: center; gap: 5px; text-decoration: none;
  color: var(--link); border-color: color-mix(in srgb, var(--link) 35%, transparent);
}

/* ===== "Up next" — hosting + domain + SSL deadlines in one list ===== */
.upnext { margin-bottom: 18px; }
.upnext-count { color: var(--hint); font-size: 13px; white-space: nowrap; }
.upnext-empty { color: var(--hint); font-size: 14.5px; line-height: 1.5; }
.upnext-group {
  font-size: 12.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--hint); margin: 10px 2px 6px;
}
.upnext-card { padding: 2px 0; }
.upnext-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
}
.upnext-row:last-child { border-bottom: 0; }
.upnext-row.over { background: rgba(229, 72, 77, 0.06); }
/* Site picture with the deadline's kind badged on its corner: you look for the
   site first, then ask what kind of deadline it is. */
/* ===== site sub-tabs ===== */
.subtabs {
  display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none;
  padding: 0 2px 14px; margin: 0 -2px;
}
.subtabs::-webkit-scrollbar { display: none; }
.subtab {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 7px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--card); color: var(--hint);
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.subtab.active { background: var(--accent); color: var(--accent-text); border-color: transparent; cursor: default; }
.subtab:not(.active):active { background: var(--border); }
/* Reference detail nested inside a tab — quieter than the site tabs above it,
   so the two rows never read as one bar. */
.subtabs-inner { padding-bottom: 10px; }
.subtabs-inner .subtab { padding: 6px 11px; font-size: 13.5px; }
.subtabs-inner .subtab.active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--text); border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
.tabbody { animation: fade-in 0.2s ease both; }

.muted-note { color: var(--hint); font-size: 14px; line-height: 1.6; }

/* key/value fact lists */
.kv { display: flex; flex-direction: column; gap: 0; }
.kv-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 14.5px;
}
.kv-row:last-child { border-bottom: 0; }
.kv-k { color: var(--hint); flex: none; }
.kv-v { text-align: left; min-width: 0; word-break: break-word; }

.btn-inline {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 12px;
  padding: 7px 12px; border-radius: 9px; text-decoration: none;
  border: 1px solid var(--border); background: var(--card); color: var(--link);
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
}
.btn-inline:active { background: var(--border); }

/* ===== site identity header ===== */
.sitebar { padding: 6px 2px 14px; }
.sitebar-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* The Year/Month switch rides with the identity, not over the charts. */
.sitebar-top .seg { flex: none; margin-left: auto; }
.sitebar-art { flex: none; width: 38px; height: 38px; }
.sitebar-art { border-radius: 11px; overflow: hidden; background: var(--icon-plate); }
.sitebar-fav { width: 100%; height: 100%; object-fit: cover; display: block; }
.sitebar-titles { min-width: 0; flex: 1; }
/* ‹ › step to the neighbouring site. Pinned to the right of the identity, and
   `flex: none` so a long domain shrinks the title rather than these. */
.sitebar-nav { display: flex; gap: 6px; flex: none; margin-left: auto; }
.sitebar-nav .site-step { width: 30px; height: 30px; border-radius: 9px; }
.sitebar-name {
  font-size: 18px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sitebar-url {
  display: block; font-size: 13.5px; color: var(--hint); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sitebar-url:active { color: var(--link); }

.sitebar-mods { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }
.mod {
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  width: 30px; height: 30px; padding: 0;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--card); color: var(--hint);
  font: inherit; cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.mod svg { opacity: 0.45; }
.mod.on { color: var(--c-100); border-color: color-mix(in srgb, var(--c-100) 32%, transparent); background: color-mix(in srgb, var(--c-100) 10%, var(--card)); }
.mod.on svg { opacity: 1; }
.mod:active { transform: scale(0.94); }
.mod-count { width: auto; padding: 0 9px; }
.mod-n { font-size: 13.5px; font-weight: 700; font-variant-numeric: tabular-nums; }

/* plain-text body inside the shared popover (module + plugin-list bubbles) */
.popover-note { font-size: 13.5px; line-height: 1.55; white-space: pre-line; color: var(--text); }

.empty-ico { display: flex; justify-content: center; color: var(--hint); margin-bottom: 10px; }
/* An empty state is an ABSENCE. A bordered, filled box draws a frame around
   nothing and reads as heavier than the content it stands in for — so these
   keep the words and drop the card chrome entirely. */
.host-empty, .upnext-empty {
  padding: 22px 14px;
  text-align: center;
}
.card.host-empty, .card.upnext-empty {
  background: none; border: 0; box-shadow: none; backdrop-filter: none;
}
.host-empty .gate-text, .upnext-empty {
  /* Wide enough that a short sentence stays on ONE line instead of breaking
     after the second-to-last word. */
  max-width: 52ch; margin: 0 auto; text-wrap: balance;
  font-size: 14.5px; line-height: 1.6; color: var(--hint);
}
.empty-title { font-size: 16px; font-weight: 650; color: var(--text); margin-bottom: 4px; }

.upnext-art { position: relative; flex: none; width: 30px; height: 30px; border-radius: 8px; overflow: visible; }
.upnext-fav {
  width: 100%; height: 100%; border-radius: 8px; object-fit: cover;
  background: var(--icon-plate); display: block;
}
.upnext-badge {
  position: absolute; right: -4px; bottom: -4px;
  width: 17px; height: 17px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--card); color: var(--hint);
  border: 1px solid var(--border);
}
.upnext-row.over .upnext-badge { color: var(--c-low); border-color: color-mix(in srgb, var(--c-low) 45%, transparent); }

/* Both calendars on one line: Gregorian in ink, Jalali beside it in hint. */
.upnext-sub { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.upnext-g { font-variant-numeric: tabular-nums; }
.upnext-j {
  font-variant-numeric: tabular-nums; opacity: 0.85;
  padding: 0 5px; border-radius: 5px;
  background: rgba(139, 152, 181, 0.12);
}
.upnext-extra { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ===== kind filter ===== */
.kindbar { display: flex; gap: 6px; margin: 0 2px 12px; flex-wrap: wrap; }
.kind-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 10px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--card); color: var(--hint);
  font: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.kind-chip .kind-n {
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 12.5px;
  opacity: 0.75; padding-left: 1px;
}
.kind-chip.active {
  background: var(--accent); color: var(--accent-text); border-color: transparent;
}
.kind-chip.active .kind-n { opacity: 0.9; }
.kind-chip.empty { opacity: 0.4; cursor: default; }
.kind-chip:not(.active):not(.empty):active { background: var(--border); }
.upnext-mid { min-width: 0; flex: 1; }
.upnext-top { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.upnext-site {
  font-size: 15px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.upnext-kind {
  flex: none; font-size: 11px; font-weight: 600; letter-spacing: 0.03em;
  color: var(--hint); text-transform: uppercase;
  border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px;
}
/* "+3 sites" — one hosting account covering several sites. Tinted rather than
   outlined so it reads as a fact about the row, not another category chip. */
.upnext-shared {
  flex: none; font-size: 11px; font-weight: 700; letter-spacing: 0.02em;
  color: var(--accent); border-radius: 5px; padding: 1px 5px;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
}
.upnext-sub {
  color: var(--hint); font-size: 13px; margin-top: 2px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.upnext-rel {
  flex: none; font-size: 13.5px; color: var(--hint);
  font-variant-numeric: tabular-nums; text-align: right;
}
.upnext-rel.warn { color: var(--c-80); font-weight: 600; }
.upnext-rel.bad  { color: var(--c-low); font-weight: 700; }

.host-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.host-btn {
  flex: none; font: inherit; font-size: 14px; line-height: 1;
  padding: 8px 12px; border-radius: 10px; cursor: pointer;
  background: rgba(139,152,181,0.10); color: var(--text);
  border: 1px solid var(--border);
  transition: transform 0.1s ease, opacity 0.15s ease;
}
.host-btn:active { transform: scale(0.95); }
.host-btn:disabled { opacity: 0.5; cursor: default; }
.host-btn.primary { background: var(--accent); color: var(--accent-text); border-color: transparent; font-weight: 600; }
.host-btn.ghost { color: var(--hint); }
.host-empty { text-align: center; padding: 30px 20px; }

/* toast */
.tg-toast {
  /* ABOVE the tab bar, not inside it. At `bottom: 22px` this landed in the
     62px-tall bar, blurred into its glass and easy to miss entirely — which
     read as "the button did nothing". */
  position: fixed; left: 50%; transform: translate(-50%, 12px);
  bottom: calc(env(safe-area-inset-bottom) + var(--tabbar-h) + 18px);
  max-width: 88vw; z-index: 60;
  background: #0e1526; color: #e9eefb;
  border: 1px solid var(--border); border-radius: 12px;
  padding: 10px 16px; font-size: 14.5px; text-align: center;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.tg-toast.show { opacity: 1; transform: translate(-50%, 0); }
.tg-toast.err { border-color: rgba(229,72,77,0.5); color: #ffd7d8; }

/* ===== list header + sort ===== */
.list-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.list-head .section-head { margin-bottom: 10px; }
.sort-wrap { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; flex: none; }
.sort-label { font-size: 12.5px; color: var(--hint); text-transform: uppercase; letter-spacing: 0.02em; }
.sort-select {
  font: inherit; font-size: 13.5px; color: var(--text);
  background: var(--card); border: 1px solid var(--border);
  border-radius: 9px; padding: 5px 8px; cursor: pointer;
}
.sort-select:focus-visible { outline: 2px solid var(--link); outline-offset: 1px; }

/* ===== empty year ===== */
.year-empty { text-align: center; padding: 34px 20px; }
.year-empty-num {
  font-size: 54px; font-weight: 700; letter-spacing: -0.02em;
  color: var(--hint); opacity: 0.55; line-height: 1;
}
.year-empty-text { margin-top: 10px; color: var(--hint); font-size: 15px; }

/* A birthday has no site behind it, so its tile carries the kind glyph itself
   rather than a favicon slot standing empty. */
.upnext-glyph {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}

/* ===== hosting filters =====
   A Jalali-month scope and, inside it, paid vs outstanding. The chips only
   exist while a month is chosen — "paid" needs something to be paid FOR. */
.host-filters { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.host-monthbar { display: flex; align-items: center; gap: 8px; }
.host-monthbar .host-step { width: 28px; height: 28px; border-radius: 8px; }
.host-month {
  font-size: 15px; font-weight: 650; letter-spacing: -0.01em;
  min-width: 8.5ch; text-align: center;
}
.host-allmonths { margin-left: auto; }
.host-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip-btn {
  font: inherit; font-size: 13px; line-height: 1;
  padding: 7px 11px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--card); color: var(--hint);
  cursor: pointer; transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.chip-btn:active { background: var(--border); }
/* Same "selected" treatment the Year/Month switch uses, so one idea reads one way. */
.chip-btn.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.chip-n { opacity: 0.7; font-variant-numeric: tabular-nums; }
