/* ==========================================================================
   EduCore: Components
   Everything that is not the shell. Each block is self-contained and reads
   only from tokens.css, so nothing here needs a dark-theme variant.
   ========================================================================== */

/* ==========================================================================
   BUTTONS
   ========================================================================== */

.ec-btn {
  display: inline-flex;
  gap: var(--ec-space-2);
  align-items: center;
  justify-content: center;
  padding: 0 var(--ec-space-4);
  height: 38px;
  font-size: var(--ec-text-md);
  font-weight: var(--ec-weight-medium);
  white-space: nowrap;
  cursor: pointer;
  border: 1px solid transparent;
  border-radius: var(--ec-radius-md);
  transition: background var(--ec-transition-fast), color var(--ec-transition-fast),
    border-color var(--ec-transition-fast), box-shadow var(--ec-transition-fast);
}

.ec-btn svg { width: 16px; height: 16px; }

.ec-btn--primary {
  color: var(--ec-accent-on);
  background: var(--ec-accent);
  box-shadow: var(--ec-shadow-sm);
}

.ec-btn--primary:hover {
  color: var(--ec-accent-on);
  background: var(--ec-accent-hover);
}

.ec-btn--outline {
  color: var(--ec-text);
  background: var(--ec-surface);
  border-color: var(--ec-border-strong);
}

.ec-btn--outline:hover {
  color: var(--ec-text);
  background: var(--ec-surface-hover);
}

.ec-btn--ghost {
  color: var(--ec-text-muted);
  background: transparent;
}

.ec-btn--ghost:hover {
  color: var(--ec-text);
  background: var(--ec-surface-hover);
}

.ec-btn--sm {
  height: 32px;
  padding-inline: var(--ec-space-3);
  font-size: var(--ec-text-sm);
}

.ec-btn:disabled,
.ec-btn[aria-disabled='true'] {
  pointer-events: none;
  opacity: 0.5;
}

/* ==========================================================================
   CARD
   ========================================================================== */

/*
 * The card sizes to its content.
 *
 * This was `height: 100%`, unconditionally, and that is right for the case it
 * was written for — one card alone in a Bootstrap column, next to another card
 * alone in the column beside it, the two wanting to end level with each other.
 * `.row` is `display: flex`, the columns stretch to the tallest, and the 100%
 * makes the cards fill the columns they were stretched to.
 *
 * It is wrong the moment a column holds TWO cards. They are not two halves of
 * the column: each one independently resolves 100% against the full stretched
 * height, so a card carrying a single homework row renders 912px tall beside a
 * results table that earned it, and the column overflows its own declared
 * height by the total of the extras. Three portal dashboards stack cards this
 * way — student, teacher and parent — and all three showed it.
 *
 * So the fill is kept, and narrowed to the case it was meant for: a card that
 * is the only child of its column. A stack sizes to content, which is what a
 * stack has always wanted. Nothing in any template had to change for this, and
 * `h-100` still forces the fill wherever a page asks for it by name.
 */
.ec-card {
  display: flex;
  flex-direction: column;
  height: auto;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  box-shadow: var(--ec-shadow-sm);
}

[class*='col-'] > .ec-card:only-child,
.ec-card.h-100 {
  height: 100%;
}

.ec-card__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--ec-space-4) var(--ec-space-5);
  border-block-end: 1px solid var(--ec-border);
}

.ec-card__title {
  font-size: var(--ec-text-base);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: -0.01em;
}

.ec-card__body {
  flex: 1;
  padding: var(--ec-space-5);
}

/* Stacked blocks inside a body need a gap, and cannot get one from a margin on
   themselves. `p { margin: 0 }` is global, so four paragraphs in a body run
   together and an .ec-alert lands its border directly on the baseline of the
   text above it — which is what the import preview's summary card did.

   Done here rather than with an mb-* on each block because the LAST child
   varies: several of those paragraphs are conditional, so a blanket bottom
   margin lands on whichever happens to be last and leaves the body padded
   unevenly top against bottom. An adjacent-sibling rule cannot. */
.ec-card__body > p + p,
.ec-card__body > p + .ec-alert,
.ec-card__body > .ec-alert + p,
.ec-card__body > * + .ec-field { margin-block-start: var(--ec-space-3); }

.ec-card__body--flush { padding: 0; }

.ec-card__foot {
  padding: var(--ec-space-3) var(--ec-space-5);
  border-block-start: 1px solid var(--ec-border);
}

/* ==========================================================================
   STAT CARD
   ========================================================================== */

.ec-stat {
  position: relative;
  display: flex;
  gap: var(--ec-space-4);
  align-items: flex-start;
  padding: var(--ec-space-5);
  overflow: hidden;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  box-shadow: var(--ec-shadow-sm);
  transition: box-shadow var(--ec-transition), transform var(--ec-transition);
}

.ec-stat:hover {
  box-shadow: var(--ec-shadow-md);
  transform: translateY(-2px);
}

/* Tinted wash in the corner, coloured per-card via --tint */
.ec-stat::after {
  position: absolute;
  top: -40px;
  right: -40px;
  width: 120px;
  height: 120px;
  content: '';
  background: var(--tint, var(--ec-primary-500));
  border-radius: 50%;
  opacity: 0.07;
  pointer-events: none;
}

.ec-stat__icon {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 46px;
  height: 46px;
  color: var(--tint, var(--ec-primary-600));
  background: color-mix(in srgb, var(--tint, var(--ec-primary-600)) 12%, transparent);
  border-radius: var(--ec-radius-md);
}

.ec-stat__icon svg { width: 22px; height: 22px; }

.ec-stat__body { flex: 1; min-width: 0; }

.ec-stat__label {
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

.ec-stat__value {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: baseline;
  margin-block-start: var(--ec-space-1);
  font-size: var(--ec-text-xl);
  font-weight: var(--ec-weight-bold);
  letter-spacing: -0.02em;
}

.ec-stat__split {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  margin-block-start: var(--ec-space-3);
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

.ec-stat__split b {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text);
}

/* Delta chip: direction sets the colour */
.ec-delta {
  display: inline-flex;
  gap: 3px;
  align-items: center;
  padding: 2px 7px;
  font-size: var(--ec-text-2xs);
  font-weight: var(--ec-weight-semibold);
  border-radius: var(--ec-radius-full);
}

.ec-delta svg { width: 12px; height: 12px; }
.ec-delta--up   { color: var(--ec-success); background: var(--ec-success-soft); }
.ec-delta--down { color: var(--ec-danger);  background: var(--ec-danger-soft); }
.ec-delta--flat { color: var(--ec-text-muted); background: var(--ec-surface-2); }

/* ==========================================================================
   BADGES / PILLS
   ========================================================================== */

.ec-badge {
  display: inline-flex;
  gap: var(--ec-space-1);
  align-items: center;
  padding: 3px 10px;
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-medium);
  white-space: nowrap;
  border-radius: var(--ec-radius-full);
}

/* The small one. Five files were already asking for it — the timetable's cover
   flag, the council's incident count, the honour roll's grades, the settings
   key indicators — and nothing defined it, so every one of them drew at full
   size inside a table cell that had no room for it.

   The type only drops to 11px: a badge is short and already the smallest text
   on the page, and taking it below that trades a legibility problem for a
   density one. What actually buys the space is the padding. */
.ec-badge--xs {
  padding: 1px 7px;
  font-size: var(--ec-text-2xs);
}

.ec-badge--success { color: var(--ec-success); background: var(--ec-success-soft); }
.ec-badge--warning { color: var(--ec-warning); background: var(--ec-warning-soft); }
.ec-badge--danger  { color: var(--ec-danger);  background: var(--ec-danger-soft); }
.ec-badge--info    { color: var(--ec-info);    background: var(--ec-info-soft); }
.ec-badge--accent  { color: var(--ec-accent);  background: var(--ec-accent-soft); }
/* The marketer console's badge, in the colour its rail wears (tokens.css). */
.ec-badge--marketer { color: var(--ec-marketer); background: var(--ec-marketer-soft); }

/* The one badge whose fill does not identify it: --ec-surface-2 on the cream
   page is a 1.06:1 difference, so the border is the whole boundary and has to
   be seen. --ec-border, a hairline meant for a table rule, measured 1.22:1
   there. */
.ec-badge--neutral {
  color: var(--ec-text-muted);
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border-strong);
}

/* Leading status dot */
.ec-badge__dot {
  width: 6px;
  height: 6px;
  background: currentcolor;
  border-radius: 50%;
}

/* ==========================================================================
   AVATAR
   Initials-based. No image assets, no external requests, no licensing.
   Hue is derived from the name in JS and set via --hue.
   ========================================================================== */

/* The language switcher.

   .ec-icon-btn is a 36x36 grid box built for exactly one icon. This is the
   only control in the product carrying an icon AND a label, and grid
   auto-flow stacked them into two rows, pushing the "FR" out of the button.
   It opts out into a flex pill instead.

   Shared rather than marketing-local: the public header and the application's
   top bar both render it, and a switcher that looks like a different control
   either side of the sign-in page is two switchers. */
.ec-langbtn {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  width: auto;
  padding-inline: var(--ec-space-3);
  border-radius: var(--ec-radius-full);
}

.ec-lang {
  font-size: var(--ec-text-2xs);
  font-weight: var(--ec-weight-semibold);
  line-height: 1;
}

.ec-avatar {
  display: inline-grid;
  flex-shrink: 0;
  place-items: center;
  width: 38px;
  height: 38px;
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-semibold);
  /* 34% lightness looked even across the wheel and was not: HSL lightness is
     not perceptual, so at hue 60 the ink and the tint were 3.31:1 while at
     hue 250 they were comfortably clear. The initials are 11–13px, so they
     need 4.5:1 at *every* hue avatar() can produce, not the average one.
     28% is the lightest value where the worst hue still measures 4.63:1. */
  color: hsl(var(--hue, 250) 62% 28%);
  background: hsl(var(--hue, 250) 70% 92%);
  border-radius: var(--ec-radius-full);
  user-select: none;
}

[data-theme='dark'] .ec-avatar {
  color: hsl(var(--hue, 250) 70% 82%);
  background: hsl(var(--hue, 250) 40% 26%);
}

.ec-avatar--sm { width: 30px; height: 30px; font-size: var(--ec-text-xs); }
.ec-avatar--lg { width: 56px; height: 56px; font-size: var(--ec-text-base); }
.ec-avatar--xl { width: 88px; height: 88px; font-size: var(--ec-text-xl); }

.ec-avatar--square { border-radius: var(--ec-radius-md); }

/* A real photograph in the same box as the initials.

   It takes .ec-avatar's box so a list where some pupils are photographed and
   some are not still lines up — the sizes above apply unchanged. What it drops
   is the tinted background and the hue variable, which exist to make two
   letters legible and would show as a coloured ring around a face.

   `cover` crops to the box rather than letterboxing, which is what makes a
   circle of thirty different photographs read as one column. The faint ring is
   there so a light background in the photograph does not merge into the page.  */
.ec-avatar--photo {
  object-fit: cover;
  color: transparent;          /* an alt that fails should not print letters */
  background: var(--ec-surface-2);
  box-shadow: inset 0 0 0 1px var(--ec-border);
}

/* Overlapping stack */
.ec-avatar-stack {
  display: flex;
  align-items: center;
}

.ec-avatar-stack .ec-avatar {
  border: 2px solid var(--ec-surface);
}

.ec-avatar-stack .ec-avatar + .ec-avatar { margin-inline-start: -10px; }

.ec-avatar-stack__more {
  display: grid;
  place-items: center;
  min-width: 30px;
  height: 30px;
  padding-inline: 6px;
  margin-inline-start: -10px;
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text-muted);
  background: var(--ec-surface-2);
  border: 2px solid var(--ec-surface);
  border-radius: var(--ec-radius-full);
}

/* Name + sub-label pairing used in every table's first column */
.ec-person {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  min-width: 0;
}

.ec-person__name {
  overflow: hidden;
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ec-person__meta {
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

/* ==========================================================================
   TABLE
   ========================================================================== */

.ec-table-wrap {
  width: 100%;
  overflow-x: auto;
}

.ec-table {
  width: 100%;
  border-collapse: collapse;
  white-space: nowrap;
}

.ec-table th,
.ec-table td {
  padding: var(--ec-space-3) var(--ec-space-5);
  text-align: left;
}

/* A cell the import preview could not make sense of. Tinted rather than
   outlined, because a bad row usually has several and a grid of red borders
   is harder to read than the values themselves. */
.ec-cell--bad {
  background: var(--ec-danger-soft);
  white-space: normal;
  min-width: 12rem;
}

.ec-table thead th {
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--ec-surface-2);
  border-block: 1px solid var(--ec-border);
}

.ec-table tbody td {
  font-size: var(--ec-text-md);
  border-block-end: 1px solid var(--ec-border);
}

.ec-table tbody tr:last-child td { border-block-end: 0; }

.ec-table tbody tr {
  transition: background var(--ec-transition-fast);
}

.ec-table tbody tr:hover { background: var(--ec-surface-2); }

.ec-table__num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Sortable header */
.ec-th-sort {
  cursor: pointer;
  user-select: none;
}

.ec-th-sort:hover { color: var(--ec-text); }

.ec-th-sort__inner {
  display: inline-flex;
  gap: var(--ec-space-1);
  align-items: center;
}

.ec-th-sort__arrow {
  width: 13px;
  height: 13px;
  opacity: 0;
  transition: opacity var(--ec-transition-fast), transform var(--ec-transition-fast);
}

.ec-th-sort:hover .ec-th-sort__arrow { opacity: 0.4; }

.ec-th-sort[aria-sort='ascending'] .ec-th-sort__arrow,
.ec-th-sort[aria-sort='descending'] .ec-th-sort__arrow {
  color: var(--ec-accent);
  opacity: 1;
}

.ec-th-sort[aria-sort='descending'] .ec-th-sort__arrow {
  transform: rotate(180deg);
}

/* ==========================================================================
   TOOLBAR (search + filters above a table)
   ========================================================================== */

.ec-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--ec-space-4) var(--ec-space-5);
  border-block-end: 1px solid var(--ec-border);
}

.ec-toolbar__group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
}

.ec-search {
  position: relative;
  display: flex;
  align-items: center;
}

.ec-search svg {
  position: absolute;
  inset-inline-start: var(--ec-space-3);
  width: 16px;
  height: 16px;
  color: var(--ec-text-subtle);
  pointer-events: none;
}

.ec-input,
.ec-select {
  height: 38px;
  padding-inline: var(--ec-space-3);
  font-family: inherit;
  font-size: var(--ec-text-md);
  color: var(--ec-text);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border-strong);
  border-radius: var(--ec-radius-md);
  transition: border-color var(--ec-transition-fast), box-shadow var(--ec-transition-fast);
}

.ec-input:focus,
.ec-select:focus {
  border-color: var(--ec-accent);
  outline: none;
  box-shadow: var(--ec-ring);
}

.ec-search .ec-input {
  min-width: 240px;
  padding-inline-start: calc(var(--ec-space-3) + 22px);
}

.ec-select {
  padding-inline-end: var(--ec-space-8);
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--ec-space-3) center;
}

[dir='rtl'] .ec-select { background-position: left var(--ec-space-3) center; }

.ec-select--sm {
  height: 32px;
  font-size: var(--ec-text-sm);
}

/* ==========================================================================
   PAGINATION
   ========================================================================== */

.ec-pagination {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--ec-space-3) var(--ec-space-5);
  border-block-start: 1px solid var(--ec-border);
}

.ec-pagination__info {
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

.ec-pagination__pages {
  display: flex;
  gap: var(--ec-space-1);
  align-items: center;
}

.ec-page-btn {
  display: grid;
  place-items: center;
  min-width: 34px;
  height: 34px;
  padding-inline: var(--ec-space-2);
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text-muted);
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  transition: all var(--ec-transition-fast);
}

.ec-page-btn:hover:not(:disabled) {
  color: var(--ec-text);
  background: var(--ec-surface-hover);
}

.ec-page-btn.is-active {
  color: var(--ec-accent-on);
  background: var(--ec-accent);
  border-color: var(--ec-accent);
}

.ec-page-btn:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

.ec-page-btn svg { width: 15px; height: 15px; }

.ec-page-ellipsis {
  padding-inline: var(--ec-space-1);
  color: var(--ec-text-subtle);
}

/* ==========================================================================
   TABS
   ========================================================================== */

.ec-tabs {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
}

.ec-tab {
  padding: var(--ec-space-1) var(--ec-space-3);
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text-muted);
  cursor: pointer;
  background: transparent;
  border: 0;
  border-radius: var(--ec-radius-sm);
  transition: all var(--ec-transition-fast);
}

.ec-tab:hover { color: var(--ec-text); }

.ec-tab.is-active {
  color: var(--ec-text);
  background: var(--ec-surface);
  box-shadow: var(--ec-shadow-sm);
}

/* ==========================================================================
   LISTS  (schedules, notices, leave requests, activity, todo)
   ========================================================================== */

.ec-list { display: flex; flex-direction: column; }

.ec-list__item {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  padding: var(--ec-space-4) var(--ec-space-5);
  border-block-end: 1px solid var(--ec-border);
}

.ec-list__item:last-child { border-block-end: 0; }

.ec-list--hover .ec-list__item {
  transition: background var(--ec-transition-fast);
}

.ec-list--hover .ec-list__item:hover { background: var(--ec-surface-2); }

.ec-list__body { flex: 1; min-width: 0; }

.ec-list__title {
  font-size: var(--ec-text-md);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
}

.ec-list__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-1) var(--ec-space-3);
  margin-block-start: var(--ec-space-1);
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

.ec-list__meta span {
  display: inline-flex;
  gap: 4px;
  align-items: center;
}

.ec-list__meta svg { width: 13px; height: 13px; }

/* Date block used by schedules and notices */
.ec-datechip {
  display: grid;
  flex-shrink: 0;
  place-content: center;
  width: 46px;
  height: 46px;
  text-align: center;
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-md);
}

.ec-datechip__day {
  font-size: var(--ec-text-base);
  font-weight: var(--ec-weight-bold);
  line-height: 1;
  color: var(--ec-accent);
}

.ec-datechip__mon {
  margin-block-start: 2px;
  font-size: 10px;
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Left accent stripe, colour set inline per item */
.ec-stripe {
  border-inline-start: 3px solid var(--stripe, var(--ec-accent));
}

/* ==========================================================================
   QUICK LINKS
   ========================================================================== */

.ec-quick {
  display: flex;
  flex-direction: column;
  gap: var(--ec-space-2);
  align-items: center;
  padding: var(--ec-space-4) var(--ec-space-2);
  text-align: center;
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  transition: all var(--ec-transition-fast);
}

.ec-quick:hover {
  background: var(--ec-surface);
  border-color: var(--tint, var(--ec-accent));
  box-shadow: var(--ec-shadow-md);
  transform: translateY(-2px);
}

.ec-quick__icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  color: var(--tint, var(--ec-accent));
  background: color-mix(in srgb, var(--tint, var(--ec-accent)) 12%, transparent);
  border-radius: var(--ec-radius-md);
}

.ec-quick__icon svg { width: 20px; height: 20px; }

.ec-quick__label {
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
}

/* ==========================================================================
   PROGRESS
   ========================================================================== */

.ec-progress {
  width: 100%;
  height: 6px;
  overflow: hidden;
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-full);
}

.ec-progress__bar {
  height: 100%;
  background: var(--bar, var(--ec-accent));
  border-radius: var(--ec-radius-full);
  transition: width var(--ec-transition);
}

.ec-progress--lg { height: 10px; }

/* Legend row shared by charts and attendance blocks */
.ec-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-4);
}

.ec-legend__item {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

.ec-legend__swatch {
  width: 9px;
  height: 9px;
  background: var(--swatch);
  border-radius: 3px;
}

.ec-legend__value {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text);
}

/* ==========================================================================
   TODO
   ========================================================================== */

.ec-todo {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  padding: var(--ec-space-3) var(--ec-space-5);
  border-block-end: 1px solid var(--ec-border);
}

.ec-todo:last-child { border-block-end: 0; }

.ec-todo input[type='checkbox'] {
  width: 17px;
  height: 17px;
  margin-block-start: 2px;
  accent-color: var(--ec-accent);
  cursor: pointer;
}

.ec-todo__text { flex: 1; }

.ec-todo input:checked + .ec-todo__text .ec-todo__title {
  color: var(--ec-text-muted);
  text-decoration: line-through;
}

.ec-todo__title {
  font-size: var(--ec-text-md);
  font-weight: var(--ec-weight-medium);
}

/* ==========================================================================
   EMPTY / LOADING STATES
   ========================================================================== */

.ec-empty {
  display: flex;
  flex-direction: column;
  gap: var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-12) var(--ec-space-5);
  text-align: center;
}

.ec-empty__icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  color: var(--ec-text-subtle);
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-full);
}

.ec-empty__icon svg { width: 26px; height: 26px; }

.ec-empty__title {
  font-size: var(--ec-text-base);
  font-weight: var(--ec-weight-semibold);
}

.ec-empty__text {
  max-width: 380px;
  font-size: var(--ec-text-md);
  color: var(--ec-text-muted);
}

.ec-skeleton {
  background: linear-gradient(
    90deg,
    var(--ec-surface-2) 25%,
    var(--ec-surface-hover) 37%,
    var(--ec-surface-2) 63%
  );
  background-size: 400% 100%;
  border-radius: var(--ec-radius-sm);
  animation: ec-shimmer 1.4s ease infinite;
}

@keyframes ec-shimmer {
  0%   { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* ==========================================================================
   CHART HOST
   ApexCharts injects its own markup; this only reserves space so the layout
   does not jump between first paint and chart render.
   ========================================================================== */

.ec-chart { width: 100%; min-height: 200px; }
.ec-chart--sm  { min-height: 140px; }
.ec-chart--lg  { min-height: 300px; }
.ec-chart--donut { min-height: 230px; }

.ec-spark { height: 46px; }

/* Text inside the chart SVG is themed here rather than through chart options.
   Passing colours via updateOptions means re-theming has to touch structural
   keys, which silently collapses bar geometry; CSS re-colours for free and
   cannot break the layout. `!important` is required because ApexCharts writes
   `fill` as an inline style on these nodes. */
.apexcharts-text,
.apexcharts-xaxis-label,
.apexcharts-yaxis-label,
.apexcharts-legend-text,
.apexcharts-datalabel-label {
  fill: var(--ec-text-muted) !important;
}

.apexcharts-datalabel-value {
  fill: var(--ec-text) !important;
}

.apexcharts-legend-text { color: var(--ec-text-muted) !important; }

.apexcharts-tooltip.apexcharts-theme-light,
.apexcharts-tooltip.apexcharts-theme-dark {
  color: var(--ec-text);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  box-shadow: var(--ec-shadow-lg);
}

.apexcharts-tooltip-title {
  font-weight: var(--ec-weight-semibold);
  background: var(--ec-surface-2);
  border-block-end: 1px solid var(--ec-border);
}

/* ==========================================================================
   DROPDOWN  (rides on Bootstrap's JS, restyled to our tokens)
   ========================================================================== */

.dropdown-menu {
  --bs-dropdown-bg: var(--ec-surface);
  --bs-dropdown-border-color: var(--ec-border);
  --bs-dropdown-border-radius: var(--ec-radius-md);
  --bs-dropdown-link-color: var(--ec-text);
  --bs-dropdown-link-hover-bg: var(--ec-surface-hover);
  --bs-dropdown-link-hover-color: var(--ec-text);
  --bs-dropdown-link-active-bg: var(--ec-accent);
  --bs-dropdown-link-active-color: var(--ec-accent-on);
  --bs-dropdown-divider-bg: var(--ec-border);
  --bs-dropdown-font-size: var(--ec-text-md);
  padding: var(--ec-space-2);
  box-shadow: var(--ec-shadow-lg);
}

.dropdown-item {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  padding: var(--ec-space-2) var(--ec-space-3);
  border-radius: var(--ec-radius-sm);
}

.dropdown-item svg { width: 16px; height: 16px; opacity: 0.7; }

.dropdown-header {
  padding: var(--ec-space-2) var(--ec-space-3);
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.ec-dropdown--wide { min-width: 320px; }

/* A table row that exists but holds nothing yet — a certificate this school
   prepares that no filière currently has candidates for. Shown rather than
   dropped: a zero is a fact about the school, a missing row reads as though
   the certificate does not exist. Same meaning as .is-blank on the bulletin. */
.ec-table tbody tr.is-blank td { color: var(--ec-text-muted); }

/* ---------------------------------------------------------------------------
   SCHOOL SWITCHER
   ---------------------------------------------------------------------------
   Each entry is a <button> inside its own <form>, because switching school
   changes what every later page shows and therefore has to be a POST. Bootstrap
   already gives .dropdown-item width:100% and a transparent background, so all
   this adds is the two-line layout and a current-school marker that does not
   rely on Bootstrap's blue .active fill — which reads as "selected in a form"
   rather than as "you are here".
   --------------------------------------------------------------------------- */

.ec-switcher__menu { min-width: 300px; }

.ec-switcher__menu .dropdown-item { align-items: flex-start; }

.ec-switcher__menu .dropdown-item svg { margin-top: 2px; flex: 0 0 auto; }

.ec-switcher__menu .dropdown-item small {
  font-size: var(--ec-text-xs);
  line-height: 1.35;
}

.ec-switcher__menu .dropdown-item.active,
.ec-switcher__menu .dropdown-item.active:focus {
  color: var(--ec-text);
  background: var(--ec-surface-2);
  font-weight: var(--ec-weight-semibold);
}

/* The marker itself. A tick rather than a fill: the row is not a choice that
   has been made, it is where the reader already is. */
.ec-switcher__menu .dropdown-item.active::after {
  content: '✓';
  margin-left: auto;
  padding-left: var(--ec-space-2);
  color: var(--ec-accent);
}

/* Notification row inside a dropdown */
.ec-notif {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  padding: var(--ec-space-3);
  border-radius: var(--ec-radius-sm);
  transition: background var(--ec-transition-fast);
}

.ec-notif:hover { background: var(--ec-surface-hover); }

.ec-notif__text {
  font-size: var(--ec-text-sm);
  line-height: var(--ec-leading-normal);
  color: var(--ec-text);
  white-space: normal;
}

.ec-notif__time {
  margin-block-start: 2px;
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

/* Profile block in the header dropdown trigger */
.ec-profile-btn {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  padding: var(--ec-space-1) var(--ec-space-2) var(--ec-space-1) var(--ec-space-1);
  cursor: pointer;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--ec-radius-full);
  transition: background var(--ec-transition-fast);
}

.ec-profile-btn:hover { background: var(--ec-surface-hover); }

.ec-profile-btn__name {
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-semibold);
  line-height: 1.2;
  text-align: left;
}

.ec-profile-btn__role {
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

/* ==========================================================================
   PROFILE HEADER (student details)
   ========================================================================== */

.ec-profile-hero {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-5);
  align-items: center;
  padding: var(--ec-space-6);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  box-shadow: var(--ec-shadow-sm);
}

.ec-profile-hero__name {
  font-size: var(--ec-text-lg);
  letter-spacing: -0.02em;
}

.ec-profile-hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2) var(--ec-space-4);
  margin-block-start: var(--ec-space-2);
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

/* Definition list used for all detail panels */
.ec-dl {
  display: grid;
  grid-template-columns: minmax(120px, max-content) 1fr;
  gap: var(--ec-space-3) var(--ec-space-4);
  font-size: var(--ec-text-md);
}

.ec-dl dt { color: var(--ec-text-muted); }

.ec-dl dd {
  margin: 0;
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
}

@media (max-width: 480px) {
  .ec-dl { grid-template-columns: 1fr; gap: var(--ec-space-1); }
  .ec-dl dd { margin-block-end: var(--ec-space-3); }
}

/* ==========================================================================
   DASHBOARD HERO
   The welcome banner every portal dashboard opens with. Painted with the
   brand ramp directly rather than --ec-accent: it is the one deliberately
   saturated surface in the app, and it must stay the brand green in both
   themes rather than lift with the dark theme's accent.
   ========================================================================== */

/* The ramp stops at --ec-primary-600, not --ec-primary-500.
   118deg runs the light end into the top right, which is exactly where the
   action buttons sit, and on the 500 nothing white held up: the button label
   measured 2.68:1, the chips 3.4, and --ec-hero__sub 2.67. Nothing translucent
   and white can fix that — raising the fill lightens the ground under white
   text and makes it worse — so the ground itself had to come down a step. On
   the 600 the same three measure 4.74, 4.54 and 4.97:1. */
.ec-hero {
  position: relative;
  padding: var(--ec-space-6);
  overflow: hidden;
  color: #fff;
  background: linear-gradient(118deg, var(--ec-primary-800), var(--ec-primary-700) 55%, var(--ec-primary-600));
  border-radius: var(--ec-radius-lg);
}

/* The banner is the same green in both themes, which on a near-black page
   made the one element carrying the least information — a greeting and the
   school's name — the brightest thing on the screen. Two steps down the same
   ramp: still unmistakably the brand, no longer a lightbox. White on the 700
   clears 8.2:1, so nothing above loses contrast by moving. */
[data-theme='dark'] .ec-hero {
  background: linear-gradient(118deg, var(--ec-primary-900), var(--ec-primary-800) 55%, var(--ec-primary-700));
}

/* Decorative circles, as in the reference: drawn with borders rather than
   fills so they read at any banner height. aria-hidden by position only —
   they are ::before/::after, so no markup carries them. */
.ec-hero::before,
.ec-hero::after {
  position: absolute;
  content: '';
  border: 40px solid rgb(255 255 255 / 0.06);
  border-radius: 50%;
  pointer-events: none;
}

.ec-hero::before {
  top: -120px;
  right: -60px;
  width: 280px;
  height: 280px;
}

.ec-hero::after {
  right: 140px;
  bottom: -130px;
  width: 220px;
  height: 220px;
  border-width: 28px;
}

.ec-hero__row {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-5);
  align-items: flex-start;
}

.ec-hero__avatar {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 72px;
  height: 72px;
  font-size: var(--ec-text-lg);
  font-weight: var(--ec-weight-bold);
  color: #fff;
  background: rgb(255 255 255 / 0.18);
  border: 1px solid rgb(255 255 255 / 0.28);
  border-radius: var(--ec-radius-lg);
}

.ec-hero__body { flex: 1; min-width: 240px; }

.ec-hero__hi {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  font-size: var(--ec-text-md);
  font-weight: var(--ec-weight-medium);
}

.ec-hero__role {
  display: inline-block;
  padding: 2px 10px;
  margin-block: var(--ec-space-2);
  font-size: var(--ec-text-2xs);
  font-weight: var(--ec-weight-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: rgb(255 255 255 / 0.16);
  border-radius: var(--ec-radius-full);
}

.ec-hero__title {
  font-size: var(--ec-text-xl);
  color: #fff;
  letter-spacing: -0.02em;
}

.ec-hero__sub {
  margin-block-start: var(--ec-space-1);
  font-size: var(--ec-text-md);
  color: rgb(255 255 255 / 0.85);
}

.ec-hero__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  margin-block-start: var(--ec-space-3);
}

.ec-hero__chip {
  display: inline-flex;
  gap: var(--ec-space-1);
  align-items: center;
  padding: 3px 12px;
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-medium);
  background: rgb(255 255 255 / 0.14);
  border: 1px solid rgb(255 255 255 / 0.22);
  border-radius: var(--ec-radius-full);
}

.ec-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
}

/* Buttons that sit on the hero's green. The ordinary outline button carries
   the page's ink, which disappears here. */
.ec-hero .ec-btn--hero {
  color: #fff;
  background: rgb(255 255 255 / 0.12);
  border: 1px solid rgb(255 255 255 / 0.35);
}

.ec-hero .ec-btn--hero:hover {
  color: #fff;
  background: rgb(255 255 255 / 0.22);
}

/* The ring is drawn on green, so it turns white here, same reasoning as the
   accent-filled controls at the foot of layout.css. */
.ec-hero .ec-btn--hero:focus-visible { --ec-focus: #fff; }

@media print {
  .ec-hero {
    color: var(--ec-text);
    background: none;
    border: 1px solid #ddd;
  }

  .ec-hero__title, .ec-hero__avatar { color: var(--ec-text); }

  .ec-hero__sub { color: var(--ec-text-muted); }

  .ec-hero__avatar, .ec-hero__role, .ec-hero__chip { border-color: #ddd; background: none; }

  .ec-hero__actions { display: none; }
}

/* ==========================================================================
   MODULE LAUNCHER
   The dashboard's navigation grid: one tile per nav entry the signed-in user
   may see. Tints cycle through the categorical chart palette, so the grid is
   colourful in exactly the colours the app already owns.
   ========================================================================== */

.ec-hub__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: center;
  margin-block-end: var(--ec-space-4);
}

.ec-hub__title {
  font-size: var(--ec-text-base);
  letter-spacing: -0.01em;
}

.ec-hub__search { max-width: 380px; }

/* One group per nav section. --tint is set on the group, so every tile inside
   inherits the section's colour rather than carrying its own. */
.ec-hub__group + .ec-hub__group { margin-block-start: var(--ec-space-6); }

.ec-hub__group[hidden] { display: none; }

.ec-hub__section {
  margin-block-end: var(--ec-space-3);
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.ec-module {
  display: flex;
  flex-direction: column;
  gap: var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-6) var(--ec-space-3);
  text-align: center;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  box-shadow: var(--ec-shadow-sm);
  transition: border-color var(--ec-transition-fast),
              box-shadow var(--ec-transition-fast),
              transform var(--ec-transition-fast);
}

.ec-module:hover {
  border-color: var(--tint, var(--ec-accent));
  box-shadow: var(--ec-shadow-md);
  transform: translateY(-2px);
}

.ec-module[hidden] { display: none; }

/* --tint and --tint-on are set together on .ec-hub__group, one pair per nav
   section. The ink was a hardcoded #fff, which this file is not supposed to
   contain and which the amber tile could not carry. See --ec-chart-*-on. */
.ec-module__icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  color: var(--tint-on, var(--ec-accent-on));
  background: var(--tint, var(--ec-accent));
  border-radius: var(--ec-radius-md);
}

.ec-module__icon svg { width: 22px; height: 22px; }

.ec-module__label {
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
  overflow-wrap: anywhere;
}

/* ==========================================================================
   UTILITIES
   ========================================================================== */

.ec-muted   { color: var(--ec-text-muted); }
.ec-subtle  { color: var(--ec-text-subtle); }
.ec-strong  { font-weight: var(--ec-weight-semibold); }
.ec-t-xs    { font-size: var(--ec-text-xs); }
.ec-t-sm    { font-size: var(--ec-text-sm); }
.ec-t-lg    { font-size: var(--ec-text-lg); }
.ec-success { color: var(--ec-success); }
.ec-danger  { color: var(--ec-danger); }
.ec-nowrap  { white-space: nowrap; }
.ec-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ec-divider {
  height: 1px;
  margin-block: var(--ec-space-4);
  background: var(--ec-border);
}

.ec-icon-14 { width: 14px; height: 14px; }
.ec-icon-16 { width: 16px; height: 16px; }
.ec-icon-18 { width: 18px; height: 18px; }
.ec-icon-20 { width: 20px; height: 20px; }

/* The connection pill.

   Rendered empty and hidden by includes/layout.php and filled by
   assets/js/offline.js, so a page with no JavaScript shows nothing rather than
   a state it cannot keep true. It reuses .ec-badge for its shape and only adds
   the one thing a badge does not have: it is sometimes a button. */
.ec-conn[role="button"] {
  cursor: pointer;
  border: 0;
  font: inherit;
}

.ec-conn[role="button"]:hover { filter: brightness(1.06); }

.ec-conn[role="button"]:focus-visible {
  outline: 2px solid var(--ec-focus, currentColor);
  outline-offset: 2px;
}

/* The sync panel.

   Anchored bottom-right on a desktop and edge-to-edge along the bottom on a
   handset, because that is where a thumb is and because a teacher reads this
   standing up. It is deliberately not a modal: it must not stop her from
   looking at the register behind it while she decides what to do. */
.ec-sync-panel {
  position: fixed;
  inset-block-end: 1rem;
  inset-inline-end: 1rem;
  z-index: 1080;
  width: min(24rem, calc(100vw - 2rem));
  max-height: min(70vh, 34rem);
  display: flex;
  flex-direction: column;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg, 14px);
  box-shadow: var(--ec-shadow-lg, 0 12px 32px rgb(0 0 0 / 18%));
}

.ec-sync-panel__head,
.ec-sync-panel__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ec-space-3);
  padding: var(--ec-space-3) var(--ec-space-4);
}

.ec-sync-panel__head { border-block-end: 1px solid var(--ec-border); }
.ec-sync-panel__foot { border-block-start: 1px solid var(--ec-border); }

.ec-sync-panel__body {
  overflow-y: auto;
  padding: var(--ec-space-3) var(--ec-space-4);
}

.ec-sync-item {
  padding-block: var(--ec-space-3);
  border-block-end: 1px solid var(--ec-border);
}

.ec-sync-item:last-child { border-block-end: 0; }

.ec-sync-item__title {
  font-weight: var(--ec-weight-semibold);
  font-size: var(--ec-text-sm);
}

/* Refused work is marked, because it is the only kind the teacher has to do
   something about. Queued work needs no colour: waiting is the normal state. */
.ec-sync-item--conflict { border-inline-start: 3px solid var(--ec-warning); padding-inline-start: var(--ec-space-3); }
.ec-sync-item--rejected { border-inline-start: 3px solid var(--ec-danger);  padding-inline-start: var(--ec-space-3); }

@media (max-width: 575.98px) {
  .ec-sync-panel {
    inset-inline: 0;
    inset-block-end: 0;
    width: auto;
    border-radius: var(--ec-radius-lg, 14px) var(--ec-radius-lg, 14px) 0 0;
  }
}

/* A booklet row printed at zero marks — level 3's 6A2 Written and Practical.
   The row is on the paper, so it shows; it cannot take a mark, so it is not a
   box. See includes/basic-card/entry.php. */
.ec-bcard-mark--nil {
  display: inline-block;
  min-width: 3.5rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  cursor: help;
}

/* ---------------------------------------------------------------------------
   Review: the stat tiles double as the state filter, so they are links.

   One row of counters that can be clicked, replacing a row of tiles above a row
   of chips that repeated the same four numbers. See includes/academic/review.php.
   --------------------------------------------------------------------------- */

.ec-stat--link { text-decoration: none; color: inherit; transition: border-color .12s, transform .12s; }
.ec-stat--link:hover { border-color: var(--tint, var(--ec-border)); transform: translateY(-1px); }
.ec-stat--link.is-active { border-color: var(--tint, var(--ec-accent)); box-shadow: inset 0 0 0 1px var(--tint, var(--ec-accent)); }

/* The filter row that replaced a whole card of chrome. */
.ec-filterbar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: 1rem; }
.ec-filterbar .ec-select { width: auto; min-width: 9rem; }

.ec-chip--sm { font-size: .78rem; padding: .18rem .6rem; }

/* ---------------------------------------------------------------------------
   Four classes the class-document screens ask for and nothing defined.

   includes/academic/basic-mastersheet.php and basic-summary.php have been
   using .ec-ncard-head, .ec-table--dense, .ec-filters and .ec-field--actions
   since they were written. `ec-ncard-*` lives only in nursery-card.css, which
   is linked by app/nursery-print.php alone — so on the Academic screens those
   documents were topped by an unstyled browser-default <h2> and their tables
   carried no density at all. That is most of what "does not look professional"
   was: not a design that needed rethinking, a design that was never applied.
   --------------------------------------------------------------------------- */

.ec-ncard-head {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding-block-end: var(--ec-space-3);
  border-block-end: 1px solid var(--ec-border);
}

.ec-ncard-head__title {
  margin: 0;
  font-size: var(--ec-text-lg);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: -0.01em;
  color: var(--ec-text);
}

.ec-ncard-head__meta {
  margin: 0;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

.ec-ncard-head__meta strong {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text);
}

/* Asked for by both class documents. The master sheet overrides it with its
   own tighter scale; this is what the term summary gets. */
.ec-table--dense th,
.ec-table--dense td { padding: var(--ec-space-2) var(--ec-space-3); }
.ec-table--dense tbody td { font-size: var(--ec-text-sm); }

.ec-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: flex-end;
}

.ec-field--actions { margin-inline-start: auto; }

/* Two card parts the class-council rule and the printed card ask for.

   `.ec-card__sub` is the sentence under a card's title — the promotion rule
   states itself there in words, because "below 40, below 50" is what a head
   teacher checks against and two numbers in a form are not. `--divided` is the
   rule between a card's sections, which is what separates stating the rule from
   applying it: two different acts that must not read as one form. */
.ec-card__sub {
  margin: 0.2rem 0 0;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

.ec-card__body--divided { border-block-start: 1px solid var(--ec-border); }

/* The small variant of the text input, used by the review filter row and the
   card's own cells. `.ec-input` had no size modifier at all. */
.ec-input--sm {
  padding: var(--ec-space-1) var(--ec-space-2);
  font-size: var(--ec-text-sm);
}

/* ==========================================================================
   THE TRANSACTION DIALOGUE  (assets/js/payflow.js)

   One dialogue for every place money moves: sending, approving on a handset,
   processing, and the verdict. A native <dialog>, so the focus trap, the
   Escape key and the backdrop are the browser's rather than Bootstrap's — the
   parent portal loads no Bootstrap at all.

   Restraint is the brief. One quiet spinner while something is genuinely
   happening, the three steps of a payment as a short list, and the facts that
   matter afterwards. The only motion is the spinner and a 160ms entrance, and
   prefers-reduced-motion removes both.
   ========================================================================== */

.ec-payflow {
  width: min(420px, calc(100vw - 2 * var(--ec-space-4)));
  max-height: calc(100vh - 2 * var(--ec-space-4));
  padding: var(--ec-space-6);
  overflow: auto;
  color: var(--ec-text);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  box-shadow: var(--ec-shadow-lg);
}

.ec-payflow[open] { animation: ec-payflow-in 160ms cubic-bezier(0.2, 0, 0, 1); }

.ec-payflow::backdrop { background: var(--ec-overlay); }

@keyframes ec-payflow-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

.ec-payflow__head { text-align: center; }

.ec-payflow__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  margin: 0 auto var(--ec-space-4);
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-full);
}

.ec-payflow--ok   .ec-payflow__mark { color: var(--ec-success); background: color-mix(in srgb, var(--ec-success) 14%, transparent); }
.ec-payflow--bad  .ec-payflow__mark { color: var(--ec-danger);  background: color-mix(in srgb, var(--ec-danger) 12%, transparent); }
.ec-payflow--hold .ec-payflow__mark,
.ec-payflow--ask  .ec-payflow__mark { color: var(--ec-warning); background: color-mix(in srgb, var(--ec-warning) 14%, transparent); }

/* The spinner is the ring itself; on the approve stage a handset sits inside it. */
.ec-payflow__spinner {
  position: absolute;
  inset: 0;
  border: 2.5px solid color-mix(in srgb, var(--ec-accent) 22%, transparent);
  border-top-color: var(--ec-accent);
  border-radius: 50%;
  animation: ec-payflow-spin 0.9s linear infinite;
}

.ec-payflow--busy .ec-payflow__mark { background: transparent; }

@keyframes ec-payflow-spin { to { transform: rotate(360deg); } }

.ec-payflow__title {
  margin: 0;
  font-size: var(--ec-text-lg);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-leading-tight);
}

.ec-payflow__text {
  margin: var(--ec-space-2) 0 0;
  font-size: var(--ec-text-md);
  line-height: var(--ec-leading-normal);
  color: var(--ec-text-muted);
}

.ec-payflow__steps {
  display: grid;
  gap: var(--ec-space-2);
  padding: 0;
  margin: var(--ec-space-5) 0 0;
  list-style: none;
}

.ec-payflow__step {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-subtle);
}

.ec-payflow__bullet {
  flex: none;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--ec-border-strong);
  border-radius: 50%;
}

.ec-payflow__step.is-active { color: var(--ec-text); font-weight: var(--ec-weight-medium); }
.ec-payflow__step.is-active .ec-payflow__bullet { border-color: var(--ec-accent); box-shadow: inset 0 0 0 4px var(--ec-surface), inset 0 0 0 9px var(--ec-accent); }
.ec-payflow__step.is-done   { color: var(--ec-text-muted); }
.ec-payflow__step.is-done .ec-payflow__bullet {
  background: var(--ec-success) center / 12px no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
  border-color: var(--ec-success);
}

.ec-payflow__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--ec-space-2) var(--ec-space-4);
  padding: var(--ec-space-4);
  margin: var(--ec-space-5) 0 0;
  font-size: var(--ec-text-sm);
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-md);
}

.ec-payflow__facts dt { color: var(--ec-text-muted); }

.ec-payflow__facts dd {
  margin: 0;
  font-weight: var(--ec-weight-medium);
  font-variant-numeric: tabular-nums;
  text-align: end;
  overflow-wrap: anywhere;
}

.ec-payflow__note,
.ec-payflow__fine {
  margin: var(--ec-space-4) 0 0;
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
  text-align: center;
}

.ec-payflow__note { font-variant-numeric: tabular-nums; }
.ec-payflow__fine { margin-top: var(--ec-space-2); }

.ec-payflow__actions {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--ec-space-2);
  margin-top: var(--ec-space-5);
}

.ec-payflow__actions .ec-btn { justify-content: center; width: 100%; }

@media (min-width: 480px) {
  .ec-payflow__actions { flex-direction: row-reverse; }
  .ec-payflow__actions .ec-btn { width: auto; flex: 1 1 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ec-payflow[open] { animation: none; }
  .ec-payflow__spinner { animation: none; border-color: var(--ec-accent); }
}

/* ==========================================================================
   THE BELL  (includes/partial-notifications.php, assets/js/notifications.js)
   ========================================================================== */

.ec-notif__menu {
  width: min(360px, calc(100vw - 2 * var(--ec-space-4)));
  padding: 0;
  overflow: hidden;
}

.ec-notif__head {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--ec-space-3) var(--ec-space-4);
  border-block-end: 1px solid var(--ec-border);
}

.ec-notif__title { font-weight: var(--ec-weight-semibold); }

.ec-notif__readall-form { margin: 0; }

.ec-notif__readall {
  padding: 0;
  font: inherit;
  font-size: var(--ec-text-xs);
  color: var(--ec-accent);
  cursor: pointer;
  background: none;
  border: 0;
}

.ec-notif__readall:hover { text-decoration: underline; }

.ec-notif__list { max-height: min(420px, 60vh); overflow-y: auto; }

.ec-notif__empty {
  padding: var(--ec-space-6) var(--ec-space-4);
  margin: 0;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
  text-align: center;
}

.ec-notif__item {
  display: flex;
  gap: var(--ec-space-3);
  padding: var(--ec-space-3) var(--ec-space-4);
  color: var(--ec-text);
  text-decoration: none;
  border-block-end: 1px solid var(--ec-border);
}

.ec-notif__item:last-child { border-block-end: 0; }
.ec-notif__item:hover { background: var(--ec-surface-hover); }
.ec-notif__item.is-unread { background: color-mix(in srgb, var(--ec-accent) 6%, var(--ec-surface)); }
.ec-notif__item.is-unread:hover { background: color-mix(in srgb, var(--ec-accent) 10%, var(--ec-surface)); }

.ec-notif__icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 32px;
  height: 32px;
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-full);
}

.ec-notif__text { display: grid; gap: 2px; min-width: 0; }

.ec-notif__line { font-size: var(--ec-text-sm); font-weight: var(--ec-weight-semibold); }
.ec-notif__item.is-unread .ec-notif__line::after {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-inline-start: var(--ec-space-2);
  vertical-align: middle;
  content: '';
  background: var(--ec-accent);
  border-radius: 50%;
}

.ec-notif__body {
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
  overflow-wrap: anywhere;
}

.ec-notif__when { font-size: var(--ec-text-2xs); color: var(--ec-text-subtle); }

.ec-notif__foot {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  justify-content: center;
  padding: var(--ec-space-3) var(--ec-space-4);
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
  text-decoration: none;
  background: var(--ec-surface-2);
  border-block-start: 1px solid var(--ec-border);
}

.ec-notif__foot:hover { color: var(--ec-text); }
