/* ============================================================
   GODDY TABI — COMPONENTS
   Buttons, inputs, cards, badges, avatars
   ============================================================ */

/* ── Buttons ── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  padding: 13px var(--s5);
  border-radius: var(--r-lg);
  font-size: var(--text-sm);
  font-weight: var(--semibold);
  transition: all var(--fast);
  white-space: nowrap;
  cursor: pointer;
  border: none;
}
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.45; pointer-events: none; }
.btn--primary  { background: var(--navy); color: white; }
.btn--primary:hover { background: var(--navy-mid); }
.btn--gold     { background: var(--gold); color: var(--navy); }
.btn--outline  { background: transparent; border: 1.5px solid var(--border); color: var(--text-primary); }
.btn--outline:hover { border-color: var(--navy); color: var(--navy); }
.btn--ghost    { background: transparent; color: var(--text-secondary); }
.btn--ghost:hover { background: var(--bg); color: var(--text-primary); }
.btn--danger   { background: var(--red-bg); color: var(--red); }
.btn--full     { width: 100%; }
.btn--sm       { padding: 8px var(--s4); font-size: var(--text-xs); border-radius: var(--r-md); }
.btn--lg       { padding: 16px var(--s6); font-size: var(--text-base); }
.btn--pill     { border-radius: var(--r-full); }
.btn svg       { width: 16px; height: 16px; stroke: currentColor; flex-shrink: 0; }

/* ── Inputs ── */
/* An author `display` beats the browser's own [hidden] rule, which
   is only `display:none` at user-agent weight. Every `.field` in the
   app declares display:flex, so `el.hidden = true` on one did
   nothing at all and the field stayed on screen. Stated once, here,
   rather than worked around at each call site. */
[hidden] { display: none !important; }

.field { display: flex; flex-direction: column; gap: 6px; }
.field__label {
  font-size: var(--text-sm);
  font-weight: var(--semibold);
  color: var(--text-primary);
}
.field__input {
  padding: 13px var(--s4);
  border: 1.5px solid var(--border);
  border-radius: var(--r-lg);
  font-size: var(--text-base);
  color: var(--text-primary);
  background: var(--surface);
  transition: border-color var(--fast), box-shadow var(--fast);
  width: 100%;
}
.field__input:focus {
  border-color: var(--navy);
  box-shadow: 0 0 0 3px rgba(27,45,94,0.08);
}
.field__input::placeholder { color: var(--text-muted); }
.field__input.error { border-color: var(--red); }
.field__hint { font-size: var(--text-xs); color: var(--text-muted); }
.field__error { font-size: var(--text-xs); color: var(--red); font-weight: var(--medium); }

/* ── Card ── */
.card {
  background: var(--surface);
  border-radius: var(--r-xl);
  border: 1px solid var(--border-light);
  box-shadow: var(--shadow-xs);
}
.card--elevated {
  box-shadow: var(--shadow-md);
  border: none;
}
.card--interactive {
  cursor: pointer;
  transition: all var(--fast);
}
.card--interactive:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.card--interactive:active {
  transform: translateY(0) scale(0.99);
}

/* ── Badge ── */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: var(--r-full);
  font-size: var(--text-xs);
  font-weight: var(--semibold);
}
.badge--green  { background: var(--green-bg);  color: var(--green);  }
.badge--red    { background: var(--red-bg);    color: var(--red);    }
.badge--amber  { background: var(--amber-bg);  color: var(--amber);  }
.badge--blue   { background: var(--blue-bg);   color: var(--blue);   }
.badge--navy   { background: var(--navy);      color: white;         }
.badge--gold   { background: var(--gold-pale); color: var(--gold);   }

/* ── Avatar ── */
.avatar {
  border-radius: var(--r-full);
  object-fit: cover;
  background: var(--navy);
  color: var(--gold);
  font-weight: var(--bold);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
  font-size: var(--text-sm);
}
.avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--r-full); }
.avatar--xs  { width: 28px; height: 28px; font-size: 10px; }
.avatar--sm  { width: 36px; height: 36px; font-size: var(--text-xs); }
.avatar--md  { width: 44px; height: 44px; font-size: var(--text-sm); }
.avatar--lg  { width: 56px; height: 56px; font-size: var(--text-base); }
.avatar--xl  { width: 80px; height: 80px; font-size: var(--text-lg); }
.avatar--2xl { width: 100px; height: 100px; font-size: var(--text-xl); }
.avatar--ring { border: 2.5px solid var(--gold); }

/* ── Icon button ── */
.icon-btn {
  width: 38px; height: 38px;
  border-radius: var(--r-full);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all var(--fast);
  position: relative;
  flex-shrink: 0;
  border: none;
  background: transparent;
}
.icon-btn svg { width: 20px; height: 20px; stroke: currentColor; }
.icon-btn:hover { background: var(--border-light); }
.icon-btn:active { transform: scale(0.92); }

/* ── Bell attention nudge ──────────────────────────────────────
   js/notifications.js's playNudge()/updateBellNudge() already handle
   WHEN this fires (unread > 0, tab visible, not prefers-reduced-motion,
   ~every 45s) — they just toggle this class on #bell-btn. This is the
   other half: what the class actually looks like. Without this rule the
   class was being added and removed with no visual effect at all.
   A short rotational wiggle, not a shake along an axis — reads as
   "something wants attention" without looking like an error state or
   jostling the rest of the topbar. Transform-only (no layout properties),
   so it can't nudge the logo or avatar next to it out of place. */
@keyframes bell-nudge-wiggle {
  0%   { transform: rotate(0deg); }
  15%  { transform: rotate(-14deg); }
  30%  { transform: rotate(11deg); }
  45%  { transform: rotate(-8deg); }
  60%  { transform: rotate(5deg); }
  75%  { transform: rotate(-3deg); }
  100% { transform: rotate(0deg); }
}
.bell-nudge svg { animation: bell-nudge-wiggle 0.9s ease-in-out; transform-origin: 50% 4px; }

/* ── Notification dot ── */
.notif-dot {
  position: absolute;
  top: 5px; right: 5px;
  width: 9px; height: 9px;
  background: var(--red);
  border-radius: var(--r-full);
  border: 2px solid white;
  display: none;
}
.notif-dot.show { display: block; }

/* Bell badge — shows the actual unread count (not just a dot), capped
   at "9+" so it never grows wider than the bell icon can comfortably
   hold. Deliberately a separate class from .notif-dot above so it never
   picks up the plain-dot rules used elsewhere (e.g. report list rows). */
.notif-count-badge {
  position: absolute;
  top: 2px; right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  background: var(--red);
  color: #fff;
  border: 2px solid white;
  border-radius: var(--r-full);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  display: none;
  align-items: center;
  justify-content: center;
}
.notif-count-badge.show { display: flex; }

/* ── Calendar icon event dot ──────────────────────────────────
   Deliberately its OWN class, not a reuse of .notif-count-badge —
   that one is selected wholesale by notifications.js's updateBadge()
   (document.querySelectorAll('.notif-count-badge')), which would
   otherwise also grab this span and overwrite it with the unread
   notification count on every badge refresh. This is a plain
   presence/absence bubble, never a number — see GT.calendarNudge in
   js/app.js for what shows and dismisses it. */
.cal-event-dot {
  position: absolute;
  top: 4px; right: 4px;
  width: 9px; height: 9px;
  background: var(--red);
  border: 2px solid white;
  border-radius: 50%;
  display: none;
}
.cal-event-dot.show { display: block; }

/* ── Divider ── */
.divider { height: 1px; background: var(--border-light); }

/* ── Section label ── */
.section-label {
  font-size: var(--text-xs);
  font-weight: var(--bold);
  color: var(--text-muted);
  letter-spacing: 1px;
  text-transform: uppercase;
  margin-bottom: var(--s3);
}

/* ── Bottom sheet modal ── */
.sheet-backdrop {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.45);
  backdrop-filter: blur(6px);
  z-index: 7000;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--base);
}
.sheet-backdrop.open { opacity: 1; pointer-events: all; }

.sheet {
  position: fixed;
  bottom: 0; left: 50%;
  transform: translateX(-50%) translateY(100%);
  width: 100%;
  max-width: 520px;
  background: var(--surface);
  border-radius: var(--r-2xl) var(--r-2xl) 0 0;
  padding-bottom: env(safe-area-inset-bottom);
  z-index: 7001;
  transition: transform var(--spring);
  max-height: 90vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.sheet.open { transform: translateX(-50%) translateY(0); }
.sheet__handle {
  width: 36px; height: 4px;
  background: var(--border);
  border-radius: var(--r-full);
  margin: 12px auto 0;
}
.sheet__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border-light);
}
.sheet__title { font-size: var(--text-md); font-weight: var(--bold); }
.sheet__body  { padding: var(--s4) var(--s5); }

/* ── Toggle switch ── */
.toggle {
  width: 46px; height: 26px;
  border-radius: var(--r-full);
  background: var(--border);
  position: relative;
  cursor: pointer;
  transition: background var(--fast);
  flex-shrink: 0;
}
.toggle::after {
  content: '';
  position: absolute;
  width: 20px; height: 20px;
  border-radius: var(--r-full);
  background: white;
  top: 3px; left: 3px;
  transition: transform var(--fast);
  box-shadow: var(--shadow-xs);
}
.toggle.on { background: var(--green); }
.toggle.on::after { transform: translateX(20px); }

/* ── Empty state ── */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--s12) var(--s6);
  text-align: center;
  gap: var(--s3);
}
.empty-state__icon { font-size: 48px; opacity: 0.4; }
.empty-state__title { font-size: var(--text-md); font-weight: var(--bold); color: var(--text-secondary); }
.empty-state__body  { font-size: var(--text-sm); color: var(--text-muted); line-height: 1.6; }

/* ── Progress bar ── */
.progress-bar {
  height: 6px;
  background: var(--border-light);
  border-radius: var(--r-full);
  overflow: hidden;
}
.progress-bar__fill {
  height: 100%;
  background: linear-gradient(90deg, var(--navy), var(--gold));
  border-radius: var(--r-full);
  transition: width 0.8s ease;
}

/* .alert-strip removed — the dashboard's "needs attention" state now
   lives inside the Overview stat row instead of a separate strip
   (see js/modules/dashboard.js). */

/* ── List item row ── */
.list-item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border-light);
  cursor: pointer;
  transition: background var(--fast);
}
.list-item:last-child { border-bottom: none; }
.list-item:hover { background: var(--bg); }
.list-item:active { background: var(--border-light); }
.list-item__icon {
  width: 40px; height: 40px;
  border-radius: var(--r-md);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.list-item__icon svg { width: 20px; height: 20px; }
.list-item__content  { flex: 1; min-width: 0; }
.list-item__title    { font-size: var(--text-sm); font-weight: var(--semibold); color: var(--text-primary); }
.list-item__sub      { font-size: var(--text-xs); color: var(--text-muted); margin-top: 1px; }
.list-item__right    { display: flex; align-items: center; gap: var(--s2); }
.list-item__arrow    { width: 16px; height: 16px; stroke: var(--text-muted); }

/* ══════════════════════════════════════════════════════════
   SMART CONTACT BUBBLE — parent-only shortcut shown on Report,
   Spotlight, and Forum (js/modules/contact-bubble.js). Fixed to
   the viewport (not any one screen), positioned clear of the
   bottom nav and of spotlight-fab/forum-fab — those only ever
   show for teacher/admin, this only ever shows for parent, so
   they never actually compete for the same corner in practice.
   ══════════════════════════════════════════════════════════ */
.contact-bubble {
  position: fixed;
  right: 18px;
  /* Now anchored off the SAME --bottom-nav-h token the FAB button
     (css/spotlight.css .spotlight-fab, css/dashboard.css .forum-fab)
     already uses and is known to render correctly above the bottom
     nav on every tested device — this used to be a hand-guessed
     fixed 90px instead, which happened to be more generous than the
     FAB's own clearance, so this particular change is not expected to
     be the fix for the bubble not appearing at all; it's a
     consistency correction made alongside the real diagnostic below. */
  bottom: calc(var(--bottom-nav-h, 62px) + 24px + env(safe-area-inset-bottom));
  width: 52px; height: 52px;
  border-radius: var(--r-full);
  border: 2.5px solid var(--surface);
  padding: 0;
  background: var(--navy);
  color: var(--gold);
  font-weight: var(--bold);
  font-size: var(--text-sm);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  box-shadow: var(--shadow-lg), 0 0 0 1px rgba(15,30,66,0.08);
  cursor: pointer;
  /* Was z-index: 40 — the "gentle nudge" toast (.gt-nudge, css/notif-pwa.css)
     sits at almost this exact same bottom-right screen position with
     z-index: 9999, spanning up to 92% of the viewport width. Any time
     that nudge is visible, it was completely burying this bubble
     underneath it — worth raising regardless of whatever else is going
     on, since the two competing for the same spot was never intended. */
  z-index: 10000;
  animation: contact-bubble-in 0.3s var(--spring);
}
.contact-bubble img { width: 100%; height: 100%; object-fit: cover; }
@keyframes contact-bubble-in {
  from { transform: scale(0.5); opacity: 0; }
  to   { transform: scale(1);   opacity: 1; }
}

.contact-bubble-teaser {
  position: fixed;
  right: 18px;
  bottom: calc(var(--bottom-nav-h, 62px) + 82px + env(safe-area-inset-bottom));
  max-width: min(78vw, 260px);
  background: var(--text-primary);
  color: white;
  padding: 10px 14px;
  border-radius: var(--r-lg);
  font-size: var(--text-xs);
  font-weight: var(--semibold);
  line-height: 1.4;
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  z-index: 9999; /* was 39 — see the matching note on .contact-bubble's z-index above */
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--base), transform var(--base);
}
.contact-bubble-teaser.show { opacity: 1; transform: translateY(0); }
/* Little tail pointing down at the bubble, so it's obvious the two
   belong together rather than reading as an unrelated toast. */
.contact-bubble-teaser::after {
  content: '';
  position: absolute;
  right: 18px; bottom: -6px;
  width: 12px; height: 12px;
  background: var(--text-primary);
  transform: rotate(45deg);
  border-radius: 0 0 3px 0;
}

/* ── Inline composer — a compact card, not a full sheet, so the
   screen underneath stays visible and nothing else about the
   current view (scroll position, filters) is disturbed. ────────── */
.contact-bubble-composer-bd {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.35);
  z-index: 7000;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--base);
}
.contact-bubble-composer-bd.open { opacity: 1; pointer-events: all; }

.contact-bubble-composer {
  position: fixed;
  left: 50%; bottom: calc(18px + env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(20px);
  width: calc(100% - 36px);
  max-width: 420px;
  background: var(--surface);
  border-radius: var(--r-xl);
  padding: 16px;
  box-shadow: var(--shadow-lg);
  z-index: 7001;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--base), transform var(--base);
}
.contact-bubble-composer.open {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: all;
}
.contact-bubble-composer__header {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 12px;
}
.contact-bubble-composer__avatar {
  width: 40px; height: 40px;
  border-radius: var(--r-full);
  overflow: hidden;
  background: var(--navy);
  color: var(--gold);
  font-weight: var(--bold);
  font-size: var(--text-xs);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.contact-bubble-composer__avatar img { width: 100%; height: 100%; object-fit: cover; }
.contact-bubble-composer__who { flex: 1; min-width: 0; }
.contact-bubble-composer__name { font-size: var(--text-sm); font-weight: var(--bold); color: var(--text-primary); }
.contact-bubble-composer__hint { font-size: var(--text-xs); color: var(--text-muted); }
.contact-bubble-composer__input {
  width: 100%;
  border: 1.5px solid var(--border);
  border-radius: var(--r-md);
  padding: 10px 12px;
  font-size: var(--text-sm);
  font-family: inherit;
  resize: none;
  margin-bottom: 12px;
}
.contact-bubble-composer__input:focus { outline: none; border-color: var(--navy); }

/* ══════════════════════════════════════════════════════════════
   MORE SHEET
   Everything the five-slot bottom bar cannot hold. On a phone
   there is no sidebar to fall back on, so this is the only way
   to most admin screens — it shows the whole menu, grouped the
   same way, minus what is already on the bar.
   ══════════════════════════════════════════════════════════════ */
.more-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 10px;
  padding: var(--s4) var(--s5) var(--s6);
}
.more-grid__group {
  grid-column: 1 / -1;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: 6px;
}
.more-grid__group:first-child { margin-top: 0; }

.more-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 92px;
  padding: 14px 8px;
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 14px;
  background: var(--surface-2, #f9fafb);
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-align: center;
}
.more-item:active { transform: scale(.97); }
/* The nav icons declare no stroke of their own — the sidebar and the
   bottom bar each set it, so a third place showing them has to as
   well, or the tiles come out blank. */
.more-item__icon svg {
  width: 22px; height: 22px;
  stroke: currentColor;
  fill: none;
}
.more-item { color: var(--text-primary, #111); }
.more-item__label {
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
