/* Base components.
 *
 * Naming follows BEM: .block, .block__element, .block--modifier. It is verbose
 * on purpose -- reading a class tells you what it belongs to without opening
 * this file, which matters once templates outnumber stylesheets.
 *
 * Every value here comes from a token in tokens.css. No literal colours.
 */

/* --- Button --- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* 36px, down from 44px.
   *
   * The old comment said 44px because thumbs miss below it, which is the
   * WCAG 2.1 AAA target and the right instinct for a bar of icons. It is the
   * wrong number for a button with a word in it: the AA rule added in WCAG
   * 2.2 (2.5.8) asks for 24px, the reference products this project measured
   * itself against ship 32px, and 44px is what made every form on every
   * screen read as a stack of slabs.
   *
   * The bottom navigation keeps its generous targets -- that is the control
   * actually used one-handed in a hallway, and it is sized in base.css. */
  min-height: var(--control-height);
  padding: 0 var(--space-4);
  font: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  letter-spacing: 0.005em;
  text-align: center;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition:
    background-color var(--transition-fast) var(--ease-out),
    border-color var(--transition-fast) var(--ease-out),
    box-shadow var(--transition-fast) var(--ease-out),
    transform var(--transition-fast) var(--ease-out);
}

/* Pressing gives back a millimetre. It is the cheapest possible signal that
 * the tap registered, and on a phone -- where there is no cursor and no
 * hover state -- it is the only one available before the page changes. */
.button:active:not(:disabled) {
  transform: translateY(1px) scale(0.985);
}

.button:disabled,
.button[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.button--primary {
  background-color: var(--color-accent);
  color: var(--color-text-inverted);
  box-shadow: var(--shadow-sm);
}

.button--primary:hover:not(:disabled) {
  background-color: var(--color-accent-hover);
  box-shadow: var(--shadow-md);
}

.button--secondary {
  background-color: transparent;
  color: var(--color-text);
  border-color: var(--color-border-strong);
}

.button--secondary:hover:not(:disabled) {
  background-color: var(--color-surface);
}

.button--danger {
  background-color: var(--color-danger-solid);
  color: var(--color-on-danger);
}

/* Renamed from --block along with its template flag: see
 * templates/components/button.html for why the old name could never work. */
.button--full {
  display: flex;
  width: 100%;
}

/* --- Form field ---
 * One block wrapping label, control, hint and error, so every form in the app
 * has identical spacing and identical error placement. */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

.field__label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}

.field__input,
.field textarea,
.field select,
.field input:not([type="checkbox"]):not([type="radio"]) {
  width: 100%;
  min-height: var(--control-height);
  padding: var(--space-1) var(--space-3);
  font: inherit;
  font-size: var(--font-size-md);
  color: var(--color-text);
  background-color: var(--color-surface-raised);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  transition:
    border-color var(--transition-fast) var(--ease-out),
    box-shadow var(--transition-fast) var(--ease-out);
}

/* A ring in the accent, not only a colour swap on the border: across 1px the
 * swap is nearly invisible, and the field somebody is typing in has to be
 * findable without hunting for it. */
.field__input:focus,
.field textarea:focus,
.field select:focus,
.field input:not([type="checkbox"]):not([type="radio"]):focus {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-subtle);
}

.field__input::placeholder,
.field textarea::placeholder,
.field input::placeholder {
  color: var(--color-text-muted);
}

.field textarea {
  min-height: 6rem;
  padding: var(--space-2) var(--space-3);
  /* Vertical only: a horizontal drag pushes the whole layout sideways. */
  resize: vertical;
}

.field__input:disabled,
.field textarea:disabled,
.field input:disabled {
  background-color: var(--color-surface);
  cursor: not-allowed;
}

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

.field__error {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-danger);
}

/* Driven by aria-invalid rather than a modifier class: the attribute is what
 * a screen reader announces, so the visual state cannot drift from the
 * accessible state. */
.field__input[aria-invalid="true"],
.field textarea[aria-invalid="true"],
.field input[aria-invalid="true"] {
  border-color: var(--color-danger);
}

/* --- Card --- */

.card {
  padding: var(--space-3) var(--space-4);
  background-color: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition:
    box-shadow var(--transition-base) var(--ease-out),
    transform var(--transition-base) var(--ease-out),
    border-color var(--transition-base) var(--ease-out);
}

.card__title {
  margin-bottom: var(--space-1);
  font-size: var(--font-size-md);
}

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

/* --- Badge --- */

.badge {
  display: inline-block;
  padding: 1px var(--space-2);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.01em;
  white-space: nowrap;
  border-radius: var(--radius-full);
  background-color: var(--color-surface);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
}

.badge--accent {
  background-color: var(--color-accent-subtle);
  color: var(--color-accent);
  border-color: transparent;
}

.badge--success {
  background-color: var(--color-success-subtle);
  color: var(--color-success);
  border-color: transparent;
}

.badge--danger {
  background-color: var(--color-danger-subtle);
  color: var(--color-danger);
  border-color: transparent;
}

/* --- Alert ---
 * For messages about what just happened. Pair with role="status" (polite) or
 * role="alert" (assertive) in the template so it is announced, not just shown. */

.alert {
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-size: var(--font-size-sm);
}

.alert :last-child {
  margin-bottom: 0;
}

.alert--error {
  background-color: var(--color-danger-subtle);
  border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
  color: var(--color-danger);
}

.alert--success {
  background-color: var(--color-success-subtle);
  border-color: color-mix(in srgb, var(--color-success) 30%, transparent);
  color: var(--color-success);
}

/* --- Loading ---
 * A skeleton is preferred over a spinner where the final shape is known: it
 * reserves the space, so content does not jump when it arrives. */

.skeleton {
  background-color: var(--color-surface);
  border-radius: var(--radius-sm);
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}

.skeleton--text {
  height: 1em;
  margin-bottom: var(--space-2);
}

.skeleton--title {
  height: 1.5em;
  width: 60%;
  margin-bottom: var(--space-3);
}

@keyframes skeleton-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

.spinner {
  display: inline-block;
  width: 1.25em;
  height: 1.25em;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-accent);
  border-radius: var(--radius-full);
  animation: spinner-rotate 0.7s linear infinite;
}

@keyframes spinner-rotate {
  to { transform: rotate(360deg); }
}

/* --- Empty state ---
 * Most screens start with no data, so this is the first real screen a student
 * sees, not a fallback. It is a component, not an afterthought. */

.empty-state {
  position: relative;
  padding: var(--space-8) var(--space-4);
  text-align: center;
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
  overflow: hidden;
}

/* The logo colours, very faint, behind the message. Most screens in this app
 * start empty, so this is the first thing a new student sees -- and an empty
 * box with a dashed border reads as something that failed to load. */
.empty-state::before {
  content: "";
  position: absolute;
  right: -1.5rem;
  bottom: -1.5rem;
  width: 9rem;
  height: 9rem;
  background: var(--gradient-brand);
  opacity: 0.07;
  border-radius: var(--radius-full);
  pointer-events: none;
}

.empty-state > * {
  position: relative;
}

.empty-state__title {
  margin-bottom: var(--space-1);
  font-size: var(--font-size-md);
}

.empty-state__body {
  margin: 0 auto var(--space-4);
  max-width: 40ch;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* --- Utilities --- */

.stack > * + * {
  margin-top: var(--space-3);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Messages --- */

.messages {
  margin-bottom: var(--space-4);
}

/* Django's message framework tags a message info/warning as well as
   error/success, and an unstyled alert--info would render as unstyled text
   rather than as a notice. */
.alert--info {
  background: var(--color-accent-subtle);
  border-color: var(--color-accent);
  color: var(--color-text);
}

/* A warning is not an error and not a notice, and until now it borrowed the
 * plain surface, which made "te falta una correlativa" read as body text in
 * a box. It takes the accent fill with a real edge instead: the danger
 * palette stays reserved for what has actually gone wrong. */
.alert--warning {
  background: var(--color-accent-subtle);
  border-color: color-mix(in srgb, var(--color-accent) 35%, transparent);
  color: var(--color-text);
}

/* --- Rating --- */

.rating {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
}

.rating__stars {
  letter-spacing: 1px;
  /* The off state uses the strong border token, not the muted text one: these
     are UI shapes rather than text, so they answer to the 3:1 non-text
     contrast minimum, and --color-border is below it in both themes. */
  color: var(--color-border-strong);
}

.rating__star--on {
  color: var(--color-accent);
}

.rating__value {
  font-weight: var(--font-weight-medium);
  color: var(--color-text);
}

.rating__count,
.rating--empty {
  color: var(--color-text-muted);
}

/* --- Search --- */

.search {
  margin-bottom: var(--space-4);
}

.search__row {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.search__input {
  flex: 1;
  min-width: 0;
  padding: var(--space-3);
  font: inherit;
  color: var(--color-text);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
}

/* --- Teacher directory --- */

.teacher-list {
  padding: 0;
  margin: 0;
  list-style: none;
}

.teacher-list__item + .teacher-list__item {
  margin-top: var(--space-2);
}

.teacher-card {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-3);
  color: inherit;
  text-decoration: none;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition:
    border-color var(--transition-fast) var(--ease-out),
    box-shadow var(--transition-fast) var(--ease-out),
    transform var(--transition-fast) var(--ease-out);
}

.teacher-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
  border-color: var(--color-accent);
}

.teacher-card__name {
  font-weight: var(--font-weight-medium);
  font-weight: var(--font-weight-medium);
}

/* --- Teacher stats --- */

.stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  padding: var(--space-4);
  margin-bottom: var(--space-8);
  background: var(--color-surface);
  border-radius: var(--radius-md);
}

.stats__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.stats__label {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.stats__value {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
}

/* --- Course section and its reviews --- */

.course-section {
  padding-top: var(--space-6);
  margin-bottom: var(--space-8);
  border-top: 1px solid var(--color-border);
}

.course-section__title {
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-lg);
}

.course-section__term {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-normal);
  color: var(--color-text-muted);
}

.course-section__cta,
.course-section__empty {
  color: var(--color-text-muted);
}

.review {
  padding: var(--space-4);
  margin-top: var(--space-4);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.review__header {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}

.review__comment {
  margin: var(--space-3) 0;
  /* Reviews are free text a student typed; a long unbroken string must wrap
     rather than push the page into a horizontal scroll. */
  overflow-wrap: anywhere;
}

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

/* --- Data-rights notice --- */

.data-rights {
  padding: var(--space-4);
  margin-top: var(--space-12);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.data-rights h2 {
  margin-top: 0;
  font-size: var(--font-size-md);
}

/* --- Radio groups --- */

.fieldset {
  padding: 0;
  margin: 0 0 var(--space-4);
  border: 0;
}

.fieldset__legend {
  padding: 0;
  margin-bottom: var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
}

.fieldset__options {
  margin-top: var(--space-2);
}

.fieldset__options label {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  padding: var(--space-2) 0;
  /* The whole row is the hit target, not just the 16px dot. */
  cursor: pointer;
}

/* --- Page prose ---
 *
 * The one place the density pass runs the other way. The interface is 14px
 * because it is an instrument; these are the strings a student actually
 * reads a sentence of, so they go back up to 16px and take the looser prose
 * leading with them. */

.page-lead {
  max-width: var(--prose-max-width);
  margin-top: calc(var(--space-2) * -1);
  margin-bottom: var(--space-4);
  font-size: var(--font-size-md);
  color: var(--color-text-muted);
}

/* A note about how a screen is scoped, not an alert. It reads as an aside:
 * indented behind a rule in the accent rather than boxed, because a box here
 * competes with the real alerts directly above it on several screens. */
.page-note {
  max-width: var(--prose-max-width);
  padding: var(--space-1) 0 var(--space-1) var(--space-3);
  margin: 0 0 var(--space-4);
  border-left: 2px solid var(--color-accent-subtle);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* --- Weekly schedule grid --- */

.term-picker {
  margin-bottom: var(--space-4);
}

.term-picker__row {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.term-picker__row .field__input {
  width: auto;
  flex: 1;
}

.week {
  display: grid;
  /* A narrow fixed column for the hour labels, then one equal column per day.
     minmax(0, 1fr) rather than 1fr: a grid column's default minimum is its
     content, so a long course name would otherwise refuse to shrink and push
     the whole week wider than the screen. */
  grid-template-columns: 3.5rem repeat(var(--week-days, 6), minmax(0, 1fr));
  gap: var(--space-1);
  margin-bottom: var(--space-8);
  /* Six days do not fit a phone. Scrolling the timetable sideways is fine;
     scrolling the page is not, so the overflow is contained here. */
  overflow-x: auto;
  /* Room for the half-line that .week__hour pulls itself up by.
   *
   * This is a real bug fix, not spacing. `overflow-x: auto` makes the *other*
   * axis compute to `auto` as well -- a box cannot clip one direction and let
   * the other spill -- so this element clips vertically whether or not anyone
   * intended it to. The first hour label sits at the very top row and is
   * translated up by 0.4em, which put it outside the padding box: the earliest
   * hour of the week, the one every student looks for first, was being cut off
   * entirely. .week__hours gives the same amount back so nothing else moves. */
  padding-top: 0.4em;
}

.week__hours,
.week__slots {
  display: grid;
  grid-template-rows: repeat(var(--week-rows), 1.1rem);
}

.week__slots {
  /* One lane per simultaneous class. Without explicit columns the browser
     invents an implicit one for the second of two overlapping blocks, which
     pushes it out over the following day -- a Monday clash rendering on top of
     Tuesday. */
  grid-template-columns: repeat(var(--day-lanes, 1), minmax(0, 1fr));
  gap: 2px;
}

.week__hours {
  /* Pushed down by the height of a day heading so the labels line up with the
     rows they name rather than with the headings above them. Less the padding
     .week now reserves above it, so the labels stay on their own rows. */
  margin-top: calc(1.75rem - 0.4em);
}

.week__hour {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  /* The label marks the boundary, so it sits on the line rather than filling
     the row below it. */
  transform: translateY(-0.4em);
}

.week__day {
  min-width: 7rem;
  /* Cancels the padding .week reserves for the first hour label, so the day
     headings stay where they were and only the labels gained room. */
  margin-top: -0.4em;
}

.week__day-name {
  margin: 0 0 var(--space-1);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.week__block {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2);
  overflow: hidden;
  font-size: var(--font-size-xs);
  background: var(--color-accent-subtle);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-sm);
}

.week__block--clash {
  background: var(--color-danger-subtle);
  border-left-color: var(--color-danger-solid);
}

.week__block-materia {
  font-weight: var(--font-weight-medium);
  color: var(--color-text);
  /* A day column is narrow and halves again when two classes clash, so a long
     course name has to be allowed to break rather than be clipped mid-word. */
  overflow-wrap: anywhere;
  hyphens: auto;
}

.week__block-time,
.week__block-where {
  color: var(--color-text-muted);
}

/* --- Schedule list --- */

.clash-list {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-6);
}

.entry-list,
.option-list {
  padding: 0;
  margin: 0;
  list-style: none;
}

.entry,
.option {
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-md);
  /* Times stacked down a list only read as a column when the digits are the
   * same width: proportional figures make 18:00 and 11:45 different lengths
   * and the colons stop lining up. */
  font-variant-numeric: tabular-nums;
}

.entry--clash,
.option--clash {
  border-left-color: var(--color-danger-solid);
}

.entry__head,
.option__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin-bottom: var(--space-2);
}

.entry__materia,
.option__code {
  font-weight: var(--font-weight-medium);
}

.entry__blocks,
.option__blocks {
  padding-left: var(--space-6);
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.entry__no-blocks,
.option__no-blocks,
.option__teachers {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.option__warning {
  font-size: var(--font-size-sm);
  color: var(--color-danger);
}

.materia-code {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* --- Legal documents --- */

.prose {
  /* Prose wants a narrower measure than the app frame: past roughly 75
     characters the eye loses the start of the next line. */
  max-width: 42rem;
}

.prose p,
.prose li {
  max-width: var(--prose-max-width);
  font-size: var(--font-size-lg);
  line-height: var(--line-height-prose);
}

.prose h2 {
  margin-top: var(--space-8);
  font-size: var(--font-size-lg);
}

.prose ul {
  padding-left: var(--space-6);
}

.prose li + li {
  margin-top: var(--space-2);
}

.prose-table {
  width: 100%;
  margin: var(--space-4) 0;
  font-size: var(--font-size-sm);
  border-collapse: collapse;
}

.prose-table th,
.prose-table td {
  padding: var(--space-2) var(--space-3);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--color-border);
}

.prose-table th {
  color: var(--color-text-muted);
  text-transform: uppercase;
  font-size: var(--font-size-xs);
  letter-spacing: 0.04em;
}

.app-footer__links {
  margin-top: var(--space-2);
  font-size: var(--font-size-sm);
}

.signup-consent {
  margin-bottom: var(--space-4);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* --- Personal calendar ---
 *
 * A list of days, not a grid. The week grid in schedule.html answers "when in
 * the week"; this answers "what happens on the 24th", and a phone reads that
 * far better as a column than as seven narrow boxes. */

.calendar {
  list-style: none;
  margin: var(--space-4) 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  font-variant-numeric: tabular-nums;
}

.calendar__day {
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.calendar__day--today {
  border-color: var(--color-accent);
}

/* Outside the term the day still gets a box -- a week missing two of them
 * reads as a rendering fault rather than as a term that starts on a
 * Wednesday. It is just quieter. */
.calendar__day--outside {
  opacity: 0.6;
}

.calendar__date {
  margin: 0 0 var(--space-2);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-medium);
}

.calendar__today {
  margin-left: var(--space-2);
  padding: 0 var(--space-2);
  border-radius: var(--radius-full);
  background: var(--color-accent);
  color: var(--color-text-inverted);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.calendar__item {
  margin: 0 0 var(--space-2);
  font-size: var(--font-size-sm);
}

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

.calendar__note {
  margin: 0 0 var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.calendar__add {
  font-size: var(--font-size-sm);
}

.calendar__delete {
  display: inline;
}

.week-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
  font-size: var(--font-size-sm);
}

.week-nav__end {
  color: var(--color-text-muted);
}

/* A button that has to read as a link: destructive actions must be POSTs, and
 * a POST needs a button, but "Borrar" next to "Editar" should not look like a
 * primary action. */
.link-button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-accent);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

/* --- Academic progress ---
 *
 * Four screens share these: the overview, the attendance index, one
 * section's classes, and the correlativa explanation. They are all the same
 * two shapes -- a row of numbers at the top, a list of rows under it -- so
 * they share the same two blocks rather than each growing its own. */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
  padding: 0;
  list-style: none;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.stat--flagged {
  border-color: var(--color-danger);
  background-color: var(--color-danger-subtle);
}

.stat__label {
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.stat__value {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  letter-spacing: var(--tracking-tight);
  /* Lining figures, so a column of percentages does not jitter as the
   * numbers change width. */
  font-variant-numeric: tabular-nums;
}

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

/* A bar, not a <progress>: the element cannot carry a second marker, and the
 * 75% threshold line is the whole point of the attendance one -- a number
 * without the line it has to clear is just a number. */
.meter {
  position: relative;
  height: 8px;
  margin: 0 0 var(--space-6);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  overflow: hidden;
}

.meter__fill {
  height: 100%;
  width: var(--meter, 0%);
  background-color: var(--color-accent);
  border-radius: var(--radius-full);
  transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1);
}

.meter--danger .meter__fill {
  background-color: var(--color-danger-solid);
}

.meter__threshold {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--meter, 75%);
  width: 2px;
  background-color: var(--color-text);
  opacity: 0.55;
}

.record-list {
  margin: 0 0 var(--space-6);
  padding: 0;
  list-style: none;
}

.record-list__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}

.record-list__item--flagged .record__name {
  color: var(--color-danger);
}

.record__link {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  color: inherit;
  text-decoration: none;
}

.record__link:hover .record__name {
  color: var(--color-accent);
}

.record__name {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--font-weight-medium);
}

.record__grade {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.record__edit {
  font-size: var(--font-size-sm);
}

.action-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-6);
}

.danger-zone {
  margin-top: var(--space-8);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

/* --- One section's classes --- */

.week-block {
  margin-bottom: var(--space-4);
}

.week-block__title {
  margin: 0 0 var(--space-2);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.class-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.class-list__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}

.class-list__item--future {
  color: var(--color-text-muted);
}

.class-list__when {
  flex: 1;
  min-width: 8rem;
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
}

.class-list__time {
  color: var(--color-text-muted);
}

.class-list__pending,
.class-list__assumed {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

/* Three buttons in a row rather than a select plus a save: correcting one
 * stray falta is the interaction this screen exists to make cheap, and a
 * select would put two clicks and a page think between the student and it. */
.attend {
  display: flex;
  gap: 2px;
  margin: 0;
  padding: 2px;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
}

.attend__option {
  padding: var(--space-1) var(--space-3);
  font: inherit;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  background: none;
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast);
}

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

.attend__option[aria-pressed="true"] {
  color: var(--color-text-inverted);
  background-color: var(--color-accent);
}

.attend__option--falta[aria-pressed="true"] {
  background-color: var(--color-danger-solid);
  color: var(--color-on-danger);
}

.attend__option--media_falta[aria-pressed="true"] {
  background-color: var(--color-text-muted);
}

/* --- Correlativas --- */

.requirement-list {
  margin: 0 0 var(--space-6);
  padding: 0;
  list-style: none;
}

.requirement {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}

.requirement__name {
  flex: 1;
  font-weight: var(--font-weight-medium);
}

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

.requirement form {
  margin: 0;
}

/* --- Forms are capped narrower than the page ---
 *
 * A 46rem-wide text input is a target you have to aim at, and the label above
 * it ends up most of a screen away from the right edge of the box it names.
 * Every product this project measured itself against caps a form around
 * 360-420px regardless of how wide the window is.
 *
 * Scoped with :has(.field) so it only catches real data-entry forms. The
 * search row, the term picker and the three attendance buttons are also
 * <form> elements and all of them want the full width. A browser without
 * :has() drops the rule and keeps today's behaviour, which is the right way
 * for this to fail.
 */
.app-main form:has(.field),
.app-main form:has(.fieldset) {
  max-width: 26rem;
}

/* --- Signed-out screens ---
 *
 * Login, signup, the password-reset chain and the activation notices. They are
 * the only screens a person sees before the app has anything of theirs on it,
 * so they are the only place the mark gets to be the size of a mark rather
 * than a favicon. */

.auth-mark {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-3);
}

.auth-mark .logo {
  filter: drop-shadow(0 4px 12px color-mix(in srgb, var(--color-accent) 35%, transparent));
}

.app-main:has(.auth-mark) h1 {
  text-align: center;
}

.app-main:has(.auth-mark) form:has(.field),
.app-main:has(.auth-mark) form:has(.fieldset) {
  margin-inline: auto;
}

/* Everything on these screens shares the form's column and its centre line.
 * Left-aligned links under a centred form read as two different pages. */
.app-main:has(.auth-mark) > p,
.app-main:has(.auth-mark) > .alert,
.app-main:has(.auth-mark) > .empty-state {
  max-width: 26rem;
  margin-inline: auto;
  text-align: center;
}

.app-main:has(.auth-mark) .page-lead {
  text-align: center;
}

.app-footer__mark {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-2);
  opacity: 0.55;
}

/* --- Action link ---
 * The ordinary way onward. Quieter than a button on purpose: a row of primary
 * buttons is a screen with no primary action at all. See
 * templates/components/action_link.html. */

.action-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 1.75rem;
  padding: 0 var(--space-2);
  margin-right: var(--space-1);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-accent);
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  transition:
    background-color var(--transition-fast) var(--ease-out),
    border-color var(--transition-fast) var(--ease-out),
    transform var(--transition-fast) var(--ease-out);
}

.action-link:hover {
  background-color: var(--color-accent-subtle);
  border-color: color-mix(in srgb, var(--color-accent) 25%, transparent);
}

.action-link:active {
  transform: scale(0.97);
}

/* Nudges toward where it goes. Small enough to read as motion rather than as
 * the icon changing place. */
.action-link .icon {
  width: 1rem;
  height: 1rem;
  transition: transform var(--transition-fast) var(--ease-out);
}

.action-link--end {
  flex-direction: row-reverse;
}

.action-link--end:hover .icon {
  transform: translateX(2px);
}

.action-link:not(.action-link--end):hover .icon {
  transform: translateX(-1px);
}

/* --- Card grid and card footer ---
 * Three summary cards stacked down a 46rem column is a lot of scrolling for
 * three sentences. They sit side by side as soon as there is room. */

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.card-grid .card {
  display: flex;
  flex-direction: column;
}

.card__footer {
  margin: auto 0 0;
  padding-top: var(--space-2);
}

.card + .card {
  margin-top: var(--space-3);
}

.card-grid .card + .card {
  margin-top: 0;
}

/* The one card on a screen that is about right now rather than about the
 * student's data in general -- what class is next. The mark's own gradient
 * down its leading edge is what separates it from the summaries under it
 * without making it a second heading. */
.card--featured {
  position: relative;
  overflow: hidden;
  border-color: color-mix(in srgb, var(--color-accent) 30%, var(--color-border));
}

.card--featured::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--gradient-brand);
}

.card--featured .card__title {
  color: var(--color-accent);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.card--featured .card__body {
  color: var(--color-text);
  font-size: var(--font-size-md);
}

/* --- Full bleed ---
 *
 * Escapes the reading column and takes the width the window actually has.
 * A timetable is not prose: six days of boxes squeezed into 46rem is what
 * made every materia wrap onto two lines with its name cut off.
 *
 * The negative margin is computed from the container rather than typed, so
 * it stays correct when --content-max-width changes and produces no margin
 * at all on a screen narrower than the column -- where the content is
 * already full width and pulling it further would push it off the edge.
 */
.full-bleed {
  width: calc(100vw - var(--space-8));
  max-width: calc(100vw - var(--space-8));
  margin-inline: calc(50% - 50vw + var(--space-4));
}

@media (min-width: 64rem) {
  /* The sidebar is not part of the viewport this content sits in, so the
   * 100vw above overshoots by its width once it exists. */
  .full-bleed {
    width: calc(100vw - var(--sidebar-width) - var(--space-12));
    max-width: calc(100vw - var(--sidebar-width) - var(--space-12));
    margin-inline: calc(50% - 50vw + (var(--sidebar-width) / 2) + var(--space-6));
  }

  :root[data-sidebar="collapsed"] .full-bleed {
    width: calc(100vw - 3.25rem - var(--space-12));
    max-width: calc(100vw - 3.25rem - var(--space-12));
    margin-inline: calc(50% - 50vw + 1.625rem + var(--space-6));
  }
}

/* --- A colour per materia ---
 *
 * Components read --materia-ink and --materia-fill, which the template sets
 * inline from the palette tokens. That indirection is what keeps eight
 * colours from becoming eight rules in every component that shows a materia.
 */

.week__block {
  color: var(--materia-ink, var(--color-text));
  background-color: var(--materia-fill, var(--color-surface));
  border-left: 3px solid var(--materia-ink, var(--color-accent));
}

.week__block-materia {
  color: var(--materia-ink, var(--color-text));
}

.calendar__item {
  padding: var(--space-1) var(--space-2);
  border-left: 3px solid var(--materia-ink, var(--color-border));
  background-color: var(--materia-fill, transparent);
  border-radius: var(--radius-sm);
}

.calendar__item strong {
  color: var(--materia-ink, var(--color-text));
}

/* An event is the student's own note, not a class, so it keeps the neutral
 * treatment it had -- otherwise a parcial would borrow whichever colour the
 * day happened to set and read as a class. */
.calendar__item--event {
  border-left-color: var(--color-accent);
  background-color: var(--color-accent-subtle);
}

.calendar__item--event strong {
  color: var(--color-text);
}

.entry {
  border-left: 3px solid var(--materia-ink, var(--color-border));
}

.entry__materia {
  color: var(--materia-ink, var(--color-text));
}

.materia-dot {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: var(--radius-full);
  background-color: var(--materia-ink, var(--color-accent));
}

.entry__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.entry__actions form {
  margin: 0;
}

/* --- The swatches ---
 *
 * Eight submit buttons in one form. A <select> would put a menu, a scroll and
 * a save between the student and a change whose entire result is visible the
 * instant it lands.
 */

.swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
}

.swatch {
  width: 1.125rem;
  height: 1.125rem;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius-full);
  cursor: pointer;
  transition:
    transform var(--transition-fast) var(--ease-spring),
    box-shadow var(--transition-fast) var(--ease-out);
}

.swatch:hover {
  transform: scale(1.2);
}

/* The chosen one gets a ring rather than a tick: a checkmark inside an 18px
 * circle is illegible, and the ring reads at a glance without covering the
 * colour it is marking. */
.swatch[aria-pressed="true"] {
  box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-text);
}

.swatch--reset {
  background-color: var(--color-surface);
  border-color: var(--color-border-strong);
}

.swatch--violeta {
  background-color: var(--color-materia-violeta);
}

.swatch--azul {
  background-color: var(--color-materia-azul);
}

.swatch--verde {
  background-color: var(--color-materia-verde);
}

.swatch--ambar {
  background-color: var(--color-materia-ambar);
}

.swatch--rosa {
  background-color: var(--color-materia-rosa);
}

.swatch--cyan {
  background-color: var(--color-materia-cyan);
}

.swatch--naranja {
  background-color: var(--color-materia-naranja);
}

.swatch--lila {
  background-color: var(--color-materia-lila);
}


/* --- Showing a password ---
 * The control sits inside the field's own box rather than beside it, so the
 * input keeps the full width every other field on the form has. */

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

.field__row .field__input,
.field__row input {
  /* Room for the button, so a long password does not run underneath it. */
  padding-right: 2.5rem;
}

.field__peek {
  position: absolute;
  right: var(--space-1);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  color: var(--color-text-muted);
  background: none;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition:
    color var(--transition-fast) var(--ease-out),
    background-color var(--transition-fast) var(--ease-out);
}

.field__peek:hover {
  color: var(--color-text);
  background-color: var(--color-surface);
}

.field__peek[aria-pressed="true"] {
  color: var(--color-accent);
}

/* An author `display` beats the browser's own `[hidden] { display: none }`,
 * so without this the button is painted while it is still marked hidden --
 * visible, unwired, and doing nothing when pressed. Which is precisely the
 * failure the `hidden` attribute is there to prevent. */
.field__peek[hidden] {
  display: none;
}
/* --- The per-materia menu ---
 * A <details> anchored to the right of a materia's name. See
 * templates/components/materia_menu.html for why it is not a scripted popover. */

.materia-menu {
  position: relative;
  margin-left: auto;
}

.materia-menu__trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  color: var(--color-text-muted);
  border-radius: var(--radius-md);
  cursor: pointer;
  list-style: none;
  transition:
    background-color var(--transition-fast) var(--ease-out),
    color var(--transition-fast) var(--ease-out);
}

.materia-menu__trigger::-webkit-details-marker {
  display: none;
}

.materia-menu__trigger:hover,
.materia-menu[open] .materia-menu__trigger {
  background-color: var(--color-surface);
  color: var(--color-text);
}

.materia-menu__panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + var(--space-1));
  /* Anchored to its right edge: the trigger sits at the right of the row, and
   * a panel opening rightward from there would run off the screen. */
  right: 0;
  min-width: 12rem;
  padding: var(--space-2);
  background-color: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  animation: account-open var(--transition-base) var(--ease-out);
}

.materia-menu__label {
  margin: 0 0 var(--space-1);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.materia-menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 2rem;
  margin-top: var(--space-2);
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-text);
  text-decoration: none;
  border-radius: var(--radius-md);
  border-top: 1px solid var(--color-border);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  transition: background-color var(--transition-fast) var(--ease-out);
}

.materia-menu__item--danger {
  color: var(--color-danger);
}

.materia-menu__item--danger:hover {
  background-color: var(--color-danger-subtle);
}

/* The entry head now carries the menu, so it has to lay out as a row with the
 * trigger pushed to the end. */
.entry__head {
  align-items: center;
}

/* --- The way back ---
 * An arrow above the title, where every other application on a phone puts it,
 * instead of a sentence at the foot of the page. */

.back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 1.75rem;
  margin-bottom: var(--space-2);
  padding: 0 var(--space-2) 0 var(--space-1);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-muted);
  text-decoration: none;
  border-radius: var(--radius-md);
  transition:
    background-color var(--transition-fast) var(--ease-out),
    color var(--transition-fast) var(--ease-out);
}

.back-link:hover {
  background-color: var(--color-surface);
  color: var(--color-text);
}

.back-link:hover .icon {
  transform: translateX(-2px);
}

.back-link .icon {
  transition: transform var(--transition-fast) var(--ease-out);
}

/* On a narrow screen the arrow carries it alone -- the label is still in the
 * markup for assistive technology, just not taking a line of a phone. */
.back-link__label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (min-width: 48rem) {
  .back-link__label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    clip-path: none;
  }
}

/* The term picker applies itself once scripting is available, so the submit
 * button beside it has nothing left to do. It stays in the markup -- without
 * scripting it is the only way to send the form. */
@media (scripting: enabled) {
  [data-autosubmit-fallback] {
    display: none;
  }
}

/* --- Hoy ---
 * The one part of the app read while crossing a hall. Hour on the left in a
 * fixed column so the eye finds the next one without reading names, the room
 * in bold because that is the fact you actually need. */

.today-list {
  margin: 0 0 var(--space-6);
  padding: 0;
  list-style: none;
}

.today {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--materia-ink, var(--color-border));
  border-radius: var(--radius-sm);
  background-color: var(--materia-fill, transparent);
}

.today + .today {
  margin-top: var(--space-1);
}

.today--now {
  box-shadow: 0 0 0 1px var(--materia-ink, var(--color-accent));
}

.today__time {
  display: flex;
  flex-direction: column;
  min-width: 3rem;
  font-weight: var(--font-weight-bold);
  font-variant-numeric: tabular-nums;
  line-height: var(--line-height-tight);
  color: var(--materia-ink, var(--color-text));
}

.today__end {
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-normal);
  opacity: 0.7;
}

.today__what {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.today__materia {
  font-weight: var(--font-weight-medium);
  color: var(--materia-ink, var(--color-text));
}

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

/* --- Ojo con esto ---
 * Exceptions only, so this never renders as a normal state. Styled as a list
 * of warnings rather than a stack of alert boxes: three alert boxes in a row
 * is a wall, and the point is that each one is a separate thing to fix. */

.alert-list {
  margin: 0 0 var(--space-6);
  padding: 0;
  list-style: none;
}

.alert-list__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-sm);
  background-color: var(--color-accent-subtle);
  font-size: var(--font-size-sm);
}

.alert-list__item + .alert-list__item {
  margin-top: var(--space-1);
}

/* Already costing a cursada rather than about to. The two read differently on
 * purpose: one is a warning and the other is a report. */
.alert-list__item--asistencia {
  border-left-color: var(--color-danger-solid);
  background-color: var(--color-danger-subtle);
  color: var(--color-danger);
}

.alert-list__text {
  flex: 1;
  min-width: 12rem;
/* Anotar algo sobre una clase concreta, desde la clase misma. Se mantiene
 * discreto hasta que el puntero está en la fila: es una acción secundaria en
 * una lista cuya función principal es leerse. */
.calendar__note-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  margin-left: var(--space-1);
  vertical-align: middle;
  color: var(--materia-ink, var(--color-text-muted));
  border-radius: var(--radius-full);
  opacity: 0.55;
  transition:
    opacity var(--transition-fast) var(--ease-out),
    background-color var(--transition-fast) var(--ease-out);
}

.calendar__item:hover .calendar__note-add,
.calendar__note-add:focus-visible {
  opacity: 1;
  background-color: color-mix(in srgb, var(--materia-ink) 15%, transparent);
}

/* On a touch screen there is no hover to reveal it, so it simply stays
 * visible -- a control nobody can uncover is a control that is not there. */
@media (hover: none) {
  .calendar__note-add {
    opacity: 1;
  }
}

/* --- The term as a matrix ---
 *
 * Weeks down, days across, copied from the spreadsheet students already keep
 * by hand. A real <table> rather than a grid of divs: it is tabular data, and
 * a screen reader needs the row and column headers to be able to say "Semana
 * 5, Martes" when it lands on a cell.
 */

.term-grid__scroll {
  /* The matrix scrolls sideways inside its own box; the page never does. Six
   * columns of readable cells do not fit a phone, and they should not have to. */
  overflow-x: auto;
  margin-bottom: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.term-grid {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-xs);
  /* Dates and totals line up column to column instead of jittering by digit. */
  font-variant-numeric: tabular-nums;
}

.term-grid th,
.term-grid td {
  padding: var(--space-1);
  text-align: left;
  vertical-align: top;
  border: 1px solid var(--color-border);
}

.term-grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: var(--space-2) var(--space-1);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-inverted);
  /* The header row is the one band of solid brand colour on the screen, which
   * is what the original spreadsheet does with its orange. */
  background: var(--gradient-brand);
  border-color: transparent;
  white-space: nowrap;
}

.term-grid__corner {
  left: 0;
  z-index: 3;
}

/* The week column stays put while the days scroll under it. Without this,
 * scrolling to Friday loses which week you are looking at -- which is the one
 * thing a cell cannot tell you on its own. */
.term-grid__week,
.term-grid__corner,
.term-grid tfoot th {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: 5.5rem;
  background-color: var(--color-surface);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}

.term-grid__week-label {
  color: var(--color-text-muted);
}

.term-grid__row:nth-child(even) .term-grid__cell {
  /* A quiet band every other row. Twenty rows of identical cells is a place to
   * lose your line. */
  background-color: color-mix(in srgb, var(--color-surface) 55%, transparent);
}

/* Where you are, in the spreadsheet's own idiom -- its current week is the one
 * painted a colour you cannot miss. */
.term-grid__row--current .term-grid__week {
  background: var(--gradient-brand);
  color: var(--color-text-inverted);
}

.term-grid__row--current .term-grid__week-label {
  color: inherit;
  font-weight: var(--font-weight-bold);
}

.term-grid__now {
  display: block;
  font-size: var(--font-size-2xs);
  opacity: 0.85;
}

.term-grid__row--current .term-grid__cell {
  background-color: var(--color-accent-subtle);
}

.term-grid__cell {
  min-width: 8.5rem;
}

.term-grid__cell--outside {
  background-image: repeating-linear-gradient(
    -45deg,
    transparent 0 6px,
    color-mix(in srgb, var(--color-border) 45%, transparent) 6px 7px
  );
}

.term-grid__cell--today {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* --- One class inside a cell --- */

.term-class {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-1);
  border-left: 3px solid var(--materia-ink, var(--color-border));
  border-radius: var(--radius-sm);
  background-color: var(--materia-fill, transparent);
}

.term-class + .term-class {
  margin-top: 2px;
}

.term-class__name {
  flex: 1;
  min-width: 0;
  color: var(--materia-ink, var(--color-text));
  font-weight: var(--font-weight-medium);
  /* Two lines at most: a long materia name must not make one row twice the
   * height of every other. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* A class the student has not answered about yet shows its three options
 * faintly; answering one makes that one the only thing left to read. */
.term-class__mark {
  display: flex;
  gap: 1px;
  margin: 0;
}

.term-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.125rem;
  height: 1.125rem;
  padding: 0;
  font: inherit;
  font-size: var(--font-size-2xs);
  line-height: 1;
  color: var(--color-text-muted);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  opacity: 0.45;
  transition:
    opacity var(--transition-fast) var(--ease-out),
    background-color var(--transition-fast) var(--ease-out),
    color var(--transition-fast) var(--ease-out);
}

.term-class:hover .term-mark,
.term-class:focus-within .term-mark {
  opacity: 1;
}

.term-mark:hover {
  background-color: var(--color-surface-raised);
  color: var(--color-text);
}

.term-mark[aria-pressed="true"] {
  opacity: 1;
  color: var(--color-text-inverted);
  background-color: var(--color-accent);
}

.term-mark--media_falta[aria-pressed="true"] {
  background-color: var(--color-text-muted);
}

.term-mark--falta[aria-pressed="true"] {
  background-color: var(--color-danger-solid);
  color: var(--color-on-danger);
}

/* Answered, so the other two step back and the cell reads as one state. */
.term-class[data-status="presente"] .term-mark:not([aria-pressed="true"]),
.term-class[data-status="media_falta"] .term-mark:not([aria-pressed="true"]),
.term-class[data-status="falta"] .term-mark:not([aria-pressed="true"]) {
  opacity: 0;
}

.term-class:hover .term-mark:not([aria-pressed="true"]),
.term-class:focus-within .term-mark:not([aria-pressed="true"]) {
  opacity: 0.45;
}

/* No hover to uncover anything with, so the options simply stay visible. */
@media (hover: none) {
  .term-mark,
  .term-class[data-status] .term-mark:not([aria-pressed="true"]) {
    opacity: 0.7;
  }
}

.term-class__assumed {
  color: var(--color-text-muted);
  font-weight: var(--font-weight-bold);
}

/* --- Notes on a cell --- */

.term-event {
  display: block;
  margin-top: 2px;
  padding: 1px var(--space-1);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  text-decoration: none;
  background-color: var(--color-accent-subtle);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.term-grid__add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.125rem;
  height: 1.125rem;
  margin-top: 2px;
  color: var(--color-text-muted);
  border-radius: var(--radius-sm);
  opacity: 0;
  transition: opacity var(--transition-fast) var(--ease-out);
}

.term-grid__add .icon {
  width: 0.875rem;
  height: 0.875rem;
}

.term-grid__cell:hover .term-grid__add,
.term-grid__add:focus-visible {
  opacity: 1;
}

@media (hover: none) {
  .term-grid__add {
    opacity: 0.6;
  }
}

/* --- The two summary rows --- */

.term-grid tfoot td,
.term-grid tfoot th {
  background-color: var(--color-surface);
  font-size: var(--font-size-2xs);
}

.term-grid__total strong {
  font-size: var(--font-size-sm);
}

.term-grid__total--risk {
  background-color: var(--color-danger-subtle);
  color: var(--color-danger);
}

.term-grid__of,
.term-grid__pct {
  color: var(--color-text-muted);
}

.term-grid__total--risk .term-grid__of,
.term-grid__total--risk .term-grid__pct {
  color: inherit;
}

.term-grid__place {
  color: var(--color-text-muted);
  white-space: nowrap;
}
