/* mySERV.IS Shared Components */

/* ── Base reset ───────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Reserve the scrollbar's width whether or not the page actually scrolls. Without
     this the centred content column sits 7px further left on a scrolling page than on
     a short one, so every navigation between the two shifts the whole page sideways —
     and it shifts against a fixed top bar that spans the viewport and does not move,
     which is what made it visible. Costs an always-present gutter on short pages. */
  scrollbar-gutter: stable;
}

/* ── Page frame ───────────────────────────────────────────────
   The one content column. Views used to declare their own geometry and had
   drifted to eight different widths (600–1400px) and gutters from 10px to
   30px, so no two pages lined up on desktop.

   No bottom-nav allowance here: body.has-standard-bottom-nav (app-bottom-nav.css)
   already reserves the bar's height at <=899px. Views that added
   calc(... + var(--bottom-nav-h)) themselves were reserving it twice. */
.app-page {
  /* 1400, not 1200. The narrower measure left a lot of empty page on a wide screen
     for anything that is not prose, and most views here are boards rather than
     articles. Genuine single-column forms opt down to --narrow instead. */
  max-width: 1400px;
  margin: 0 auto;
  padding: var(--space-5) var(--space-4) var(--space-10);
}

/* Forms and reading pages. A single column of fields stretched to 1200px is
   worse, not more consistent — these keep the same gutters and only cap the
   measure. */
.app-page--narrow { max-width: 760px; }

/* Maps and dense boards. 1200px is a good measure for reading, and a bad one for a
   map or a week grid — on a 1920 screen that left ~500px of empty page beside the
   content. Three widths, all from the same frame, rather than each view inventing
   its own again. */
.app-page--wide { max-width: 1600px; }

/* ── Buttons ──────────────────────────────────────────────── */
/* Shared view header */
.view-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}
.view-header-copy { min-width: 0; }
.view-kicker {
  margin: 0 0 var(--space-1);
  color: var(--primary-accent);
  font-size: var(--text-xs);
  font-weight: var(--fw-bold);
  /* Explicit, because an unset line-height resolves against whatever the view's body
     inherits: this line measured 12px tall on some pages, 14px on others and 18px on
     help, which pushed the page title 36/38/42px down the page across the app. */
  line-height: var(--lh-tight);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.view-title {
  margin: 0;
  color: var(--text);
  font-size: var(--text-2xl);
  font-weight: var(--fw-heavy);
  line-height: var(--lh-tight);
}
.view-subtitle {
  margin: var(--space-2) 0 0;
  max-width: 680px;
  color: var(--muted);
  font-size: var(--text-base);
  line-height: var(--lh-base);
}
.view-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  justify-content: flex-end;
}
/* Compact variant for full-height app surfaces — chat, oracle, story, voice. They
   cannot spend ~90px of a conversation pane on a page title, but with no header at
   all they read as a different product to every other view. Kicker and title share
   one line, the subtitle drops, and it sits as a bar on the pane. */
.view-header--compact {
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: 0;
  border-bottom: 1px solid var(--border);
  background: var(--card-bg);
  flex: none;
}
.view-header--compact .view-header-copy {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  min-width: 0;
}
.view-header--compact .view-kicker { margin: 0; flex: none; }
.view-header--compact .view-title {
  font-size: var(--text-lg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.view-header--compact .view-subtitle { display: none; }

@media (max-width: 899px) {
  .view-header {
    align-items: stretch;
    flex-direction: column;
  }
  .view-actions {
    justify-content: flex-start;
  }
  /* The compact one stays a single row — it is chrome, not page copy. */
  .view-header--compact { align-items: center; flex-direction: row; }
  .view-header--compact .view-kicker { display: none; }
}

/* ── Section headings ─────────────────────────────────────────
   Two treatments, and only two. Headings inside pages had grown nine: uppercase
   eyebrows at 10/11/11.2/12/14px and block headings at 15/16/20/21/22/24px in
   weights 400–800, which is most of why the views read as unrelated.

   .section-label — the small uppercase eyebrow that names a group.
   .section-title — the normal-case heading of a block or card.
   Views that already had their own class name for these keep it; the per-view
   rule is set to these values rather than churning every template's markup. */
.section-label {
  font-size: var(--text-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}
.section-title {
  font-size: var(--text-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
  margin: 0;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: 0 var(--space-4);
  border: none;
  border-radius: var(--radius-xs);
  font-family: var(--font);
  font-size: var(--text-base);
  font-weight: var(--fw-semibold);
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  transition: opacity 0.15s, transform 0.1s, box-shadow 0.15s;
  white-space: nowrap;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.btn:active { transform: scale(0.97); }
.btn:disabled, .btn[disabled] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

.btn-primary {
  background: var(--primary-color);
  color: var(--on-primary);
}
.btn-primary:hover { opacity: 0.88; }

.btn-secondary {
  background: var(--primary-color-light);
  color: var(--primary-color);
}
/* Was a hardcoded #dde8ff, which stayed pale-blue on a dark ground. Matches
   .btn-primary:hover's idiom instead so it works in both themes. */
.btn-secondary:hover { opacity: 0.85; }

.btn-ghost {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
}
.btn-ghost:hover { background: var(--bg); border-color: var(--border-hover); }

.btn-danger {
  background: var(--danger);
  color: #fff;
}
.btn-danger:hover { opacity: 0.88; }

.btn-sm {
  min-height: 34px;
  padding: 0 var(--space-3);
  font-size: var(--text-sm);
  border-radius: var(--radius-xs);
}

/* ── Cards ────────────────────────────────────────────────── */
.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-5);
  box-shadow: var(--shadow-sm);
}
.card-sm {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  box-shadow: var(--shadow-sm);
}

/* ── Badges ───────────────────────────────────────────────── */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 3px 9px;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.4;
  white-space: nowrap;
}
/* Pale fill + full-strength hue as the label is the pairing the --on-tint-* tokens exist
   to replace: measured in light it gave warning 2.07:1, success 2.41:1, danger 3.44:1 and
   info 3.51:1 — every badge in the app below AA. The tint pairs already carry per-theme
   values, so both halves flip together. */
.badge-success  { background: var(--tint-success);  color: var(--on-tint-success);  }
.badge-warning  { background: var(--tint-warning);  color: var(--on-tint-warning);  }
.badge-danger   { background: var(--tint-danger);   color: var(--on-tint-danger);   }
.badge-info     { background: var(--tint-info);     color: var(--on-tint-info);     }

/* ── Forms ────────────────────────────────────────────────── */
.form-label {
  display: block;
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: var(--space-1);
}
.form-input {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  font-family: var(--font);
  font-size: var(--text-md); /* 16px — prevents iOS auto-zoom */
  color: var(--text);
  background: var(--card-bg);
  transition: border-color 0.15s, box-shadow 0.15s;
  outline: none;
  appearance: none;
  -webkit-appearance: none;
}
.form-input:focus {
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(53,122,255,0.12);
}
.form-input::placeholder { color: var(--faint); }

.form-hint  { font-size: var(--text-xs); color: var(--muted);  margin-top: var(--space-1); }
.form-error { font-size: var(--text-xs); color: var(--danger); margin-top: var(--space-1); }

/* ── Typography helpers ───────────────────────────────────── */
.text-muted  { color: var(--muted);        }
.text-accent { color: var(--text-accent);  }
.text-danger { color: var(--danger);       }
.text-success{ color: var(--success);      }
.text-xs     { font-size: var(--text-xs);  }
.text-sm     { font-size: var(--text-sm);  }
.text-md     { font-size: var(--text-md);  }
.fw-medium   { font-weight: var(--fw-medium); }
.fw-semibold { font-weight: var(--fw-semibold); }
.fw-bold     { font-weight: var(--fw-bold); }

/* ── Pro feature lock ─────────────────────────────────────── */
.pro-locked { opacity: 0.45; }
.pro-locked .app-nav-pro-lock { margin-left: auto; font-size: 0.65rem; }

/* ── Skeleton ─────────────────────────────────────────────────
   Placeholder shaped like the content that's coming, so a loading
   screen doesn't read as an empty one. */
.skeleton {
  background: linear-gradient(90deg, var(--border) 25%, var(--bg) 50%, var(--border) 75%);
  background-size: 200% 100%;
  animation: skeleton-sweep 1.4s ease-in-out infinite;
  border-radius: var(--radius-xs);
  min-height: 0.9em;
}
.skeleton-line { height: 0.85em; margin-bottom: var(--space-2); }
.skeleton-line:last-child { margin-bottom: 0; }
.skeleton-line--short { width: 45%; }
.skeleton-line--half  { width: 65%; }

@keyframes skeleton-sweep {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}
/* A sweeping gradient is decorative motion — hold it still, but keep the
   block visible so the layout still reads as "loading". */
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; background: var(--border); }
}

/* ── Empty state ──────────────────────────────────────────────
   For "nothing here yet", which should explain rather than just be blank. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-4);
  color: var(--muted);
}
.empty-state > i { font-size: 1.6rem; color: var(--faint); margin-bottom: var(--space-1); }
.empty-state strong { color: var(--text); font-weight: var(--fw-semibold); font-size: var(--text-md); }
.empty-state span { font-size: var(--text-base); max-width: 34ch; line-height: var(--lh-base); }

/* Icon-only variant — a square button for secondary actions that would otherwise crowd a
   header row. The label lives in aria-label/title, not on screen. */
.btn-icon {
  padding: 0;
  width: 32px;
  min-width: 32px;
  aspect-ratio: 1;
  justify-content: center;
  color: var(--muted);
}
.btn-icon:hover { color: var(--text); }
