/* Corrections layered on top of the Valex template.
 *
 * Measured, not guessed: computed styles were compared against the template's
 * own preview at 1440x900 and these were the deltas.
 *
 * Keep this file tiny and every rule justified. It exists because our shell
 * renders the template's markup from React rather than from Valex's static
 * HTML, and a couple of things the template gets from its own page furniture
 * have to be restated. */

/* Valex's header measures 63.6px because its header-links carry full-size
 * icons. Ours are shorter, so the header came out 9px short and everything
 * below it sat high. Pin the height rather than chase each icon. */
.app-header {
  min-height: 63.59px;
}

/* .page resolves to display:flex in our tree, and as a flex container it
 * positioned main-content 28px below where the template puts it — the margin
 * was not even contributing (setting it to 0 changed nothing; switching the
 * container to block moved it to exactly the header height). The sidebar and
 * header are both position:fixed, so main-content is the only in-flow child
 * and block is the correct formatting context for it. */
.page {
  display: block;
}

.main-content.app-content {
  margin-block-start: 63.59px;
  padding-block-start: 0;
}

/* ---------------------------------------------------------------------------
   Keyboard focus
   ---------------------------------------------------------------------------
   The template sets `outline: 0` in twenty-nine places and replaces it with a
   focus ring in almost none of them, so tabbing through a form left no visible
   trace of where you were. That is not a preference — a bursar working down an
   invoicing screen on the keyboard, and anybody using this without a mouse at
   all, cannot use a control they cannot find.

   `:focus-visible`, not `:focus`: the browser already knows the difference
   between a click and a Tab, and putting a ring on every clicked button is why
   the outlines were removed in the first place.
   -------------------------------------------------------------------------*/
a:focus-visible,
button:focus-visible,
[role="button"]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid rgb(var(--primary-rgb));
  outline-offset: 2px;
  border-radius: 0.25rem;
}

/* Straight to the page, past thirty menu links. The first Tab on every screen
 * landed in the sidebar and stayed there for a while. */
.skip-to-content {
  position: absolute;
  inset-inline-start: 0.5rem;
  inset-block-start: -4rem;
  z-index: 200;
  padding: 0.5rem 0.9rem;
  border-radius: 0.35rem;
  background: rgb(var(--primary-rgb));
  color: #fff;
  transition: inset-block-start 0.15s ease;
}
.skip-to-content:focus-visible {
  inset-block-start: 0.5rem;
  color: #fff;
}

/* ---------------------------------------------------------------------------
   Reduced motion
   ---------------------------------------------------------------------------
   The template ships no `prefers-reduced-motion` rule at all, and the console
   has a sliding sidebar, a page loader and a menu that animates open. Somebody
   who has asked their device for less movement has usually asked for a reason.
   -------------------------------------------------------------------------*/
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* A scrolling table on a desktop got the browser's full-width scrollbar,
 * which is a lot of furniture under a five-column table. */
.table-responsive {
  scrollbar-width: thin;
}

/* ---------------------------------------------------------------------------
   Buttons that are meant to read as links
   ---------------------------------------------------------------------------
   Nine of them — "Show", "Remove", "History" — carried only text utilities.
   Bootstrap styles `.btn` and nothing else, and we deliberately do not load
   Tailwind's preflight (which is what normally strips a button's native
   appearance), so every one of them rendered as the operating system's own
   grey 3D button in the middle of a sentence. `.btn-link` is Bootstrap's
   answer and is the wrong shape: it carries a button's padding, which is not
   what a single word inside a table cell wants.
   -------------------------------------------------------------------------*/
.link-button {
  appearance: none;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
  text-underline-offset: 2px;
}
.link-button:hover {
  text-decoration: underline;
}

/* ===========================================================================
   Motion and the shared components
   ===========================================================================
   Everything here is inside the `prefers-reduced-motion` block further up,
   which zeroes every duration — so a person who has asked their device for
   less movement gets the same console without any of it.

   The rule applied throughout: motion is used to say where something CAME
   FROM, and nowhere else. A drawer slides from the edge it lives on, a modal
   rises slightly, a row lifts under the cursor to say it can be clicked.
   Nothing bounces, nothing pulses, nothing draws attention to itself while a
   bursar is trying to read a column of figures.
   ------------------------------------------------------------------------ */

/* ---- sortable table headers --------------------------------------------- */

.th-sort {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.th-sort:hover {
  color: rgb(var(--primary-rgb));
}
/* The neutral chevrons stay faint until the column is in play, so a header
 * row does not read as a row of controls. */
.th-sort__icon {
  display: inline-flex;
  opacity: 0.35;
  transition: opacity 0.15s ease, color 0.15s ease;
}
.th-sort:hover .th-sort__icon {
  opacity: 0.7;
}
.th-sort__icon--on {
  opacity: 1;
  color: rgb(var(--primary-rgb));
}

/* ---- rows you can click -------------------------------------------------- */

.table-row-clickable {
  cursor: pointer;
  transition: background-color 0.12s ease;
}
.table-row-clickable:hover {
  background-color: var(--list-hover-focus-bg);
}

/* ---- the scrim behind a drawer or a modal -------------------------------- */

.talvora-scrim {
  position: fixed;
  inset: 0;
  z-index: 1044;
  background: rgb(0 0 0 / 45%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.talvora-scrim--open {
  opacity: 1;
  pointer-events: auto;
}

/* ---- drawer -------------------------------------------------------------- */

.talvora-drawer {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: 1045;
  width: min(30rem, 100vw);
  display: flex;
  flex-direction: column;
  visibility: hidden;
  transform: translateX(100%);
  transition: transform 0.22s ease, visibility 0.22s;
  background: var(--custom-white);
  border-inline-start: 1px solid var(--default-border);
}
.talvora-drawer--wide {
  width: min(46rem, 100vw);
}
.talvora-drawer--open {
  visibility: visible;
  transform: translateX(0);
}
[dir="rtl"] .talvora-drawer {
  transform: translateX(-100%);
}
[dir="rtl"] .talvora-drawer--open {
  transform: translateX(0);
}
/* Bootstrap's offcanvas-header/body padding is scoped under `.offcanvas`,
 * which the root deliberately no longer carries — so the title sat flush in
 * the corner with the close button on top of it. Ours now. */
.talvora-drawer .offcanvas-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
}
.talvora-drawer .offcanvas-title {
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.talvora-drawer .offcanvas-header .btn-close {
  flex: none;
  margin: 0;
}
.talvora-drawer .offcanvas-body {
  overflow-y: auto;
  flex: 1;
  padding: 1.25rem;
}
/* Full width on a phone: a 30rem panel on a 390px screen is the whole screen
 * anyway, and a sliver of list behind it is just something to mis-tap. */
@media (max-width: 575.98px) {
  .talvora-drawer,
  .talvora-drawer--wide {
    width: 100vw;
  }
}

/* ---- modal --------------------------------------------------------------- */

.talvora-modal {
  position: fixed;
  z-index: 1045;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  width: min(28rem, calc(100vw - 2rem));
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
  animation: talvora-rise 0.18s ease;
}
.talvora-modal--wide {
  width: min(46rem, calc(100vw - 2rem));
}
@keyframes talvora-rise {
  from {
    opacity: 0;
    translate: -50% calc(-50% + 8px);
  }
}

/* ---- buttons ------------------------------------------------------------- */

/* A press that answers. Bootstrap's own active state changes colour only,
 * which on a slow screen is easy to miss. */
.btn:active:not(:disabled) {
  transform: translateY(1px);
}
.btn {
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.06s ease;
}
.btn-loading {
  position: relative;
}
.btn-spinner {
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  margin-inline-end: 0.45em;
  vertical-align: -0.1em;
  border: 2px solid currentColor;
  border-inline-end-color: transparent;
  border-radius: 50%;
  animation: talvora-spin 0.65s linear infinite;
}
@keyframes talvora-spin {
  to {
    rotate: 360deg;
  }
}

/* ---- skeletons ----------------------------------------------------------- */

.talvora-skeleton {
  display: block;
  height: 0.85rem;
  border-radius: 0.3rem;
  background: linear-gradient(
    90deg,
    var(--default-border) 25%,
    var(--custom-bg-color) 37%,
    var(--default-border) 63%
  );
  background-size: 400% 100%;
  animation: talvora-shimmer 1.4s ease infinite;
}
.talvora-skeleton--wide {
  flex-grow: 2;
}
@keyframes talvora-shimmer {
  from {
    background-position: 100% 50%;
  }
  to {
    background-position: 0 50%;
  }
}

/* ---- cards arriving ------------------------------------------------------ */

/* Once, on mount, and only 4px. Enough that a panel appearing reads as
 * arriving rather than blinking into place; not enough to notice twice. */
.card.custom-card {
  animation: talvora-settle 0.2s ease both;
}
@keyframes talvora-settle {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}

/* ---- toasts -------------------------------------------------------------- */

.talvora-toasts {
  position: fixed;
  z-index: 1080;
  inset-block-end: 1.25rem;
  inset-inline-end: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  pointer-events: none;
  max-width: min(24rem, calc(100vw - 2rem));
}
/* Bottom-centre on a phone, where the top-right corner is under the thumb of
 * whoever is holding it and the bottom is where the eye already is. */
@media (max-width: 575.98px) {
  .talvora-toasts {
    inset-inline: 1rem;
    max-width: none;
  }
}
.talvora-toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.7rem 0.9rem;
  border-radius: 0.6rem;
  border-inline-start: 3px solid currentColor;
  background: var(--custom-white);
  box-shadow: 0 8px 24px rgb(0 0 0 / 14%);
  font-size: 0.8125rem;
  line-height: 1.4;
  animation: talvora-toast-in 0.18s ease;
}
.talvora-toast > span {
  color: var(--default-text-color);
}
.talvora-toast--success {
  color: rgb(var(--success-rgb));
}
.talvora-toast--error {
  color: rgb(var(--danger-rgb));
}
.talvora-toast--info {
  color: rgb(var(--primary-rgb));
}
.talvora-toast__close {
  margin-inline-start: auto;
  border: 0;
  background: none;
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0 0.1rem;
  cursor: pointer;
}
@keyframes talvora-toast-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

/* ===========================================================================
   Buttons
   ===========================================================================
   The template's buttons are square-cornered, heavy, and flat — the look of a
   2015 admin theme, which is what they are. They are also the control people
   press most, so they set the tone of the whole console.

   Changed here rather than in the button component so EVERY `.btn` gets it,
   including the ones inside markup the template owns and the ones written as
   plain `<button className="btn ...">` across thirty pages.
   ------------------------------------------------------------------------ */

.btn {
  --talvora-btn-radius: 0.5rem;
  border-radius: var(--talvora-btn-radius);
  font-weight: 500;
  letter-spacing: 0;
  padding-block: 0.5rem;
  padding-inline: 0.9rem;
}
.btn-sm {
  --talvora-btn-radius: 0.4rem;
  padding-block: 0.3rem;
  padding-inline: 0.6rem;
}

/* A primary button should sit slightly above the page rather than be painted
 * onto it — enough to read as the thing to press, not enough to shout. */
.btn-primary,
.btn-danger,
.btn-success {
  box-shadow: 0 1px 2px rgb(0 0 0 / 8%);
}
.btn-primary:hover,
.btn-danger:hover,
.btn-success:hover {
  box-shadow: 0 3px 8px rgb(0 0 0 / 14%);
}

/* Outline and light buttons carry a real border. The template's `btn-light` on
 * a light card was a button-shaped area of nothing. */
.btn-light {
  border: 1px solid var(--default-border);
  background-color: var(--custom-white);
}
.btn-light:hover {
  background-color: var(--custom-bg-color);
}

/* A group of buttons in a toolbar reads as a group, not as a wall. */
.btn + .btn {
  margin-inline-start: 0;
}

/* ---- form controls ------------------------------------------------------- */

/* Matched to the buttons beside them: a 0.5rem button next to a square input
 * is the detail that makes a toolbar look assembled rather than designed. */
.form-control,
.form-select {
  border-radius: 0.5rem;
}
.form-control:focus,
.form-select:focus {
  border-color: rgba(var(--primary-rgb), 0.45);
  box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.12);
}

/* ---- search field with an icon ------------------------------------------- */

.talvora-search {
  position: relative;
  flex: 1 1 16rem;
  min-width: 0;
}
.talvora-search__icon {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 0.7rem;
  translate: 0 -50%;
  color: var(--text-muted);
  pointer-events: none;
  display: inline-flex;
}
.talvora-search .form-control {
  padding-inline-start: 2.2rem;
}

/* ---- tables -------------------------------------------------------------- */

/* Column headings should read as headings, not as another row of data. */
.table > thead th {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  border-block-end: 1px solid var(--default-border);
}
.table > tbody > tr > td {
  vertical-align: middle;
}

/* ---- initials ------------------------------------------------------------ */

/* A pupil's initials beside their name. A roll of four hundred identical rows
 * gives the eye nothing to travel down; two letters in a circle do, and cost
 * no request — unlike a photograph, which four hundred of would be the whole
 * page's weight again on a connection that cannot afford it. */
.talvora-avatar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 50%;
  background: rgba(var(--primary-rgb), 0.12);
  color: rgb(var(--primary-rgb));
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* A roll inside a card body already sits in the card's padding, so its rows
   need only the rule between them — not a second inset. */
.talvora-roll {
  margin: 0;
}
.talvora-roll > .list-group-item {
  padding-inline: 0;
}

/* Nothing here yet.
   A single grey line under a table header is what a broken render looks like.
   A block with air around it reads as an answer, and leaves room for the one
   button that would change it. */
.talvora-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  padding: 2.5rem 1rem;
  text-align: center;
}
.talvora-empty__message {
  margin: 0;
  max-width: 32rem;
  color: var(--text-muted);
  font-size: 0.9375rem;
}

/* Dashboard tiles.
   The whole card is the link, so the target is the card and not four words
   inside it — and it says so on hover rather than leaving the reader to
   discover it by accident. */
.talvora-tile {
  display: block;
  color: inherit;
  height: 100%;
}
.talvora-tile .card {
  transition:
    transform 0.15s ease,
    box-shadow 0.15s ease;
}
.talvora-tile:hover .card,
.talvora-tile:focus-visible .card {
  transform: translateY(-2px);
  box-shadow: 0 0.5rem 1.25rem rgba(0, 0, 0, 0.08);
}
.talvora-tile__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 0.75rem;
  flex-shrink: 0;
}
/* The swatch that ties a legend line to its slice of the bar. */
.talvora-key {
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 0.1875rem;
}
