.grid {
  display: grid;
  gap: 16px;
}

.grid-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.card,
.panel {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper-2);
  box-shadow: var(--shadow);
}

.card {
  padding: 18px 20px;
  animation: rise 0.48s both;
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}

.card:hover {
  box-shadow: var(--shadow-lift);
  transform: translateY(-3px);
}

.card h3 {
  margin: 0 0 8px;
  font-size: 18px;
}

.card p {
  margin: 0;
  color: var(--ink-soft);
  line-height: 1.5;
}

.panel {
  overflow: hidden;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  padding: 16px 20px;
}

/* A heading with its sentence keeps a readable width (HIG, Layout): when the row cannot give it
   12em beside the buttons and status, those move to a row below it, rather than leaving the
   sentence a word per line. A bare title is short and keeps its row. */
.panel-head > div:first-child {
  flex: 1 1 12em;
}

/* A header whose control goes under the title (a filter, a search box): a column, whose title block
   is as tall as its text. In a column the 12em above would be a height. */
.panel-head.panel-head-stack {
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}

.panel-head.panel-head-stack > div:first-child {
  flex: none;
}

.panel-head strong {
  font-size: 17px;
  letter-spacing: 0;
}

/* A panel's note (a count, a page number, what the numbers mean) keeps its line whole: "5 khoản",
   not "5" over "khoản". The title beside it takes the line breaks. */
.panel-note {
  flex-shrink: 0;
  color: var(--ink-soft);
  font-size: 12.5px;
  white-space: nowrap;
}

/* A list's pages: ‹ Trang 1 / 30 ›, as Calendar steps through months (HIG). Chevron buttons fit a
   small phone beside the page number; their names are for VoiceOver and the pointer's tooltip. Each
   is square: as wide as a small button is tall at each size. */
.pager {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px 20px;
}

.pager .btn {
  min-width: 36px;
  padding: 0;
}

@media (min-width: 981px) and (pointer: fine) {
  .pager .btn {
    min-width: 30px;
  }
}

@media (pointer: coarse) {
  .pager .btn {
    min-width: 44px;
  }
}

/* The heading beside it wraps; the button keeps its title whole. */
.panel-head .btn {
  flex-shrink: 0;
  white-space: nowrap;
}

/* On a phone a wide table scrolls sideways inside this box (spec 12 §5.20). The box is a size
   container so that a message standing in for the rows (.table-msg) can be as wide as the part of
   the table on screen, not as wide as the whole table. A soft shadow shows at each edge where more
   of the table lies (HIG, Scroll views): the two covers scroll with the table and hide a shadow once
   that end is in view, so a table that fits shows none. */
.table-wrap {
  position: relative; /* clips what is placed absolutely inside (an .sr-only column title), which would widen the page */
  overflow-x: auto;
  container-type: inline-size;
  background:
    linear-gradient(to right, var(--paper-2) 40%, transparent) left center / 32px 100% no-repeat local,
    linear-gradient(to left, var(--paper-2) 40%, transparent) right center / 32px 100% no-repeat local,
    linear-gradient(to right, var(--scroll-shadow), transparent) left center / 12px 100% no-repeat,
    linear-gradient(to left, var(--scroll-shadow), transparent) right center / 12px 100% no-repeat;
  background-color: var(--paper-2);
}

/* HIG (Layout; Lists and tables): a table whose rows carry buttons (.list-table) keeps them on
   screen, rather than in a last column reached only by scrolling the table sideways; a table of
   records whose columns do not fit a phone (the admin's classes and school years) is drawn the
   same way. As a table, the record's name wraps instead of pushing the buttons out of the box. */
.list-table td:first-child {
  white-space: normal;
}

/* So do the record's other details when the box is tight (a long category or tier in Đoàn's award
   history, in the 680-point column of "Cộng điểm" on a touch iPad held sideways); only the last cell,
   the record's buttons, keeps its line. */
.list-table td:not(:last-child) {
  white-space: normal;
}

/* Up to 1100 points wide (a phone, an iPad held upright, the 13-inch's 1032 points too, a narrow
   window beside the sidebar) the columns do not fit, and the table is a list: each record's fields
   wrap under its name. The column titles stay for screen readers, and the markup gives the table its
   roles (role="table", "row", "cell"), which WebKit drops once a table is not displayed as one. A
   cell that needs its title to make sense (a date) shows it from data-label. */
@media (max-width: 1100px) {
  .list-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .list-table,
  .list-table tbody {
    display: block;
  }

  .list-table tr {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    border-bottom: 1px solid var(--line);
    padding: 12px 16px;
  }

  .list-table tbody tr:last-child {
    border-bottom: 0;
  }

  .list-table td {
    border: 0;
    padding: 0;
    white-space: normal;
  }

  .list-table td:first-child {
    flex: 1 0 100%;
  }

  .list-table td:last-child:not(:first-child) {
    margin-left: auto;
  }

  .list-table td[data-label]::before {
    content: attr(data-label) " ";
  }

  /* An empty column's dash marks a gap in a grid; in a list row it is noise. */
  .list-table td.cell-empty {
    display: none;
  }
}

/* A ranking (.rank-table) in a narrow box (HIG, Layout): the rank, the name and the total fit the
   width; the class and the points by source move under the name (.rank-sub), as their columns
   would push the total off screen. The box, not the screen, is what is narrow: Đoàn's ranking sits
   beside the class averages, so on an iPad or in a Mac window it is as narrow as on a phone. A
   browser without container queries keeps the whole table, which scrolls sideways. In a wide box a
   long name wraps rather than push the total out. */
.rank-sub {
  display: none;
}

.rank-table td:nth-child(2) {
  white-space: normal;
}

@container (max-width: 590px) {
  .rank-table .col-class,
  .rank-table .col-src {
    display: none;
  }

  .rank-table th,
  .rank-table td {
    padding-inline: 8px;
  }

  .rank-table :is(th, td):first-child {
    padding-left: 16px;
  }

  .rank-table :is(th, td):last-child {
    padding-right: 16px;
  }

  .rank-table td:nth-child(2) {
    width: 100%;
  }

  .rank-sub {
    display: block;
    margin-top: 2px;
    color: var(--ink-soft);
    font-size: 13px;
  }
}

/* The school ranking on the Báo cáo page (.rank-list). A wide screen keeps the table, its total in
   bold; a phone draws it as an iOS list (approved b1-bao-cao): the rank, the name over its class
   line and "N điểm" in secondary text, with a separator inset to the name. No chevron: a row opens
   the student's detail as a sheet, not a new page (owner, 08/10/2026). The column
   titles stay for screen readers, and the markup's roles keep the table a table for WebKit. */
.rank-total {
  font-weight: 700;
}

.rank-unit {
  display: none;
}

@media (max-width: 760px) {
  .rank-table.rank-list thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .rank-table.rank-list,
  .rank-table.rank-list tbody {
    display: block;
  }

  .rank-table.rank-list tr {
    display: flex;
    align-items: center;
    min-height: 62px;
    padding: 0 16px;
  }

  /* The separator starts under the name (16 + 18 + 10 points in), as in an iOS list; like a cell's
     border, it takes its point off the top of the row, so the row's content centres under it. */
  .rank-table.rank-list tbody tr + tr {
    box-sizing: border-box;
    padding-top: 1px;
    background: linear-gradient(var(--sep), var(--sep)) 44px 0 / calc(100% - 44px) 1px no-repeat;
  }

  /* The rank sits left of the separator, so it keeps the row's own centre. */
  .rank-table.rank-list tbody tr + tr > td.rank {
    margin-top: -1px;
  }

  .rank-table.rank-list td {
    padding: 0;
    border: 0;
  }

  .rank-table.rank-list td.rank {
    flex: 0 0 18px;
    margin-right: 10px;
    padding: 0;
    color: var(--ink-soft);
    font-size: 15px;
    font-weight: 400;
    text-align: center;
    font-variant-numeric: tabular-nums;
  }

  .rank-table.rank-list td:nth-child(2) {
    flex: 1;
    min-width: 0;
    width: auto;
    padding: 6px 0;
  }

  .rank-table.rank-list .row-link {
    font-size: 17px;
    font-weight: 400;
    line-height: 1.5;
  }

  .rank-table.rank-list .rank-sub {
    display: block;
    margin-top: 0;
    color: var(--ink-soft);
    font-size: 15px;
    line-height: 1.5;
  }

  .rank-table.rank-list td.rank-total {
    margin-left: 8px;
    padding: 0;
    color: var(--ink-soft);
    font-size: 17px;
    font-weight: 400;
    font-variant-numeric: normal;
    white-space: nowrap;
  }

  .rank-table.rank-list .rank-unit {
    display: inline;
  }

  .rank-table.rank-list td[colspan] {
    flex: 1;
  }
}

/* A student's history (.hist-table) in the same narrow box: the activity and its points fit; the
   date and the status move under the activity's name (.hist-sub), the status keeping its pill. */
.hist-sub {
  display: none;
}

@container (max-width: 590px) {
  .hist-table .col-date,
  .hist-table .col-status {
    display: none;
  }

  .hist-table td:first-child {
    width: 100%;
    white-space: normal;
  }

  .hist-sub {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 8px;
    margin-top: 4px;
    color: var(--ink-soft);
    font-size: 13px;
  }
}

/* Pinned to the left of the scrolling box and no wider than what shows: the message is not centred
   off screen or cut at the edge. A browser without container units keeps the full width. */
.table-msg {
  position: sticky;
  left: 0;
  max-width: calc(100cqi - 32px);
  white-space: normal;
}

/* Class progress (Tiến độ theo lớp): a school of 30 classes scrolls the list inside the dialog. The
   title, the sentence under it and the column titles stay on top while it does (owner, 07/10/2026,
   Part 7 d06b): the dialog's body stops scrolling and the table's wrap scrolls instead. The wrap's
   sideways scroll shadows are dropped (this table never scrolls sideways), and the bottom padding the
   body had moves inside the wrap, so the last row still ends where it did. The titles' rule is drawn
   on the cell, because a collapsed table's border stays behind when a cell sticks. */
.modal-body:has(> .cp-table) {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding-bottom: 0;
}

.table-wrap.cp-table {
  flex: 0 1 auto;
  min-height: 0;
  overflow: hidden auto;
  background: var(--paper-2);
}

.cp-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  border-bottom-color: transparent;
  background: var(--paper-2);
}

.cp-table thead th::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: -1px;
  left: 0;
  height: 1px;
  background: var(--line);
}

table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  border-bottom: 1px solid var(--line);
  padding: 13px 20px;
  text-align: left;
  white-space: nowrap;
}

th {
  color: var(--ink-soft);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: uppercase;
}

tbody tr {
  transition: background 0.12s ease;
}

tbody tr:hover {
  background: rgba(20, 33, 61, 0.025);
}

/* A table row (tr.row-click) opens a detail view: the whole row takes the mouse click, with the
   arrow pointer like any control (HIG); the name inside is a real button, drawn as a link, so
   keyboard and screen-reader users reach the same action (audit R21). */
.row-link {
  border: 0;
  background: none;
  padding: 0;
  color: inherit;
  font: inherit;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
}

.row-link:hover {
  text-decoration: underline;
}

/* HIG (Lists and tables): a row that opens another page ends in a disclosure chevron, grey like a
   list's, so a touch screen, with no pointer and no hover, shows which rows can be tapped. A row that
   opens a sheet has none (owner, 08/10/2026). */
tr.row-click > td:last-child > .mini-ic {
  margin-left: 6px;
  color: var(--ink-soft);
}

/* Score tables (approved s47): numbers right-aligned under a right-aligned heading; the chevron has
   its own narrow last column, so it never shifts the digits, and the card's edge ends the list (no
   line under the last row). */
main table :is(th, td).num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

main table :is(th, td).go:last-child {
  width: 28px;
  padding-left: 0;
  padding-right: 8px;
  color: var(--label-3);
  text-align: right;
}

main table td.go > .mini-ic {
  width: 7px;
  height: 12px;
  margin: 0;
  color: inherit;
  vertical-align: middle;
}

main table:is(:has(td.go), .rank-table) tbody tr:last-child > td {
  border-bottom: 0;
}

@media (max-width: 760px) {
  main table :is(th, td).go:last-child {
    width: 22px;
    padding-right: 4px;
  }
}

.badge {
  display: inline-flex;
  align-items: center;
  min-height: 23px;
  border-radius: 7px;
  padding: 2px 9px;
  background: var(--accent-soft);
  color: #7d5a05;
  font-size: 11.5px;
  font-weight: 700;
  /* A short label stays whole: in a table cell that wraps, "Hoạt động" would split in two. */
  white-space: nowrap;
}

/* A name someone typed (an award's tier) can be long: it wraps rather than widen its table. */
.badge.wraps,
.pill.wraps {
  white-space: normal;
}

.badge-info,
.badge.k {
  background: rgba(0, 122, 255, 0.12);
  color: var(--blue-deep);
}

.badge-dark,
.badge.type {
  background: rgba(88, 86, 214, 0.13);
  color: #4f46c7;
}

.badge-solo,
.badge.solo {
  padding: 0;
  background: transparent;
  color: var(--ink-soft);
  font-weight: 500;
}

/* A status is plain coloured text (docs/DESIGN_SYSTEM.md): what is still to come in the accent. */
.badge.solo.is-soon {
  color: var(--blue);
}

.badge.ok {
  background: rgba(52, 199, 89, 0.16);
  color: #187630;
}

.badge.warn {
  background: rgba(255, 159, 10, 0.16);
  color: #805500;
}

/* Compact inline role select used in the admin account table. */
.role-sel {
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--paper-2);
  color: var(--ink);
  padding: 5px 8px;
  font: inherit;
  font-size: 13px;
}

/* No outline: none here. Safari paints no box-shadow on a native select box, so the outline from
   base.css is its only focus ring. */
.role-sel:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.12);
}

/* As approved, a state ("Đang mở", "Bản nháp") is coloured text: no capsule, no dot. */
.pill {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 0;
  font-size: 13px;
  font-weight: 500;
  /* It reads as one label, never split over two lines. */
  white-space: nowrap;
}

.pill-reg {
  color: #9c4f00;
}

.pill-done {
  color: #187630;
}

.pill-absent {
  color: #c70015;
}

.pill-off {
  color: var(--ink-soft);
}

.rank {
  width: 30px;
  color: var(--ink-soft);
  font-weight: 700;
}

.barcell {
  min-width: 140px;
}

@media (max-width: 720px) {
  .barcell {
    display: none;
  }
}

/* The top three are not coloured (approved s47): the order already says it. */
.rank-top {
  color: inherit;
}

.bar {
  height: 7px;
  border-radius: 4px;
  background: var(--line);
  overflow: hidden;
}

.bar i {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--blue), var(--gold));
}

.segmented {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-bottom: 18px;
  border-radius: 10px;
  background: var(--line);
  padding: 2px;
}

/* Inside a field the grid gap and the field's own margin space its parts; the group adds none. */
.field > .segmented {
  margin-bottom: 0;
}

/* In a panel header the header's gap and padding space it, so the header ends as far below it as
   it begins above the title. */
.panel-head > .segmented {
  margin-bottom: 0;
}

.segmented button {
  min-height: 34px;
  border-radius: 8px;
  padding: 0 18px;
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 500;
  transition: background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

/* Full-width variant: buttons share the track equally (used where the segmented
   is stretched to fill its row, e.g. the award history filter and the export
   layout toggle). Compact top-of-page tab segmenteds keep natural widths. */
.segmented.segmented-fill button {
  flex: 1;
  white-space: nowrap;
  /* Shrink with the row instead of pushing past it: without this a button is never narrower than
     its label plus padding, and on a 375px phone the account role filter ran out of its panel
     (the last button cut off, and focusing it scrolled the whole panel sideways). */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* On a small phone a segment is as wide as its title, with less padding, and the room left is
   shared (HIG: a segment's title stays on one line). At 320px four equal segments left "Học sinh"
   43px of the 56px it needs, though the four titles fit the row. A title wraps at a space, rather
   than being cut to "Học …", only when the whole row still cannot hold it. */
@media (max-width: 480px) {
  .segmented.segmented-fill button {
    flex: 1 1 auto;
    padding: 4px 5px;
    white-space: normal;
    line-height: 1.2;
  }
}

/* The selection slides to the item just tapped (slide-thumb.js, owner 08/10/2026): a copy of the
   thumb moves under the items while the control's own thumbs stay hidden, then hands back. */
.segmented {
  position: relative;
}

.slide-thumb {
  position: absolute;
  z-index: 0;
  pointer-events: none;
}

.segmented.is-sliding > button,
.sidebar.is-sliding .nav-link {
  position: relative;
  z-index: 1;
  background: transparent !important;
  box-shadow: none !important;
  transition: none !important;
}

.segmented.is-landing > button,
.sidebar.is-landing .nav-link {
  transition: none !important;
}

.segmented button.is-active,
.segmented button.on {
  background: var(--paper-2);
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}

/* Shared toolbar controls — a search field + an optional filter <select> that
   sit on one row above a list. Used by the Đoàn attendance picker and the Admin
   account list, so they live here (global) rather than in a per-route sheet. */
.att-search {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  background: var(--paper-2);
  color: var(--ink);
  padding: 9px 12px;
  font: inherit;
  font-size: 14px;
}

.att-search:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.12);
}

.att-class {
  min-width: 150px;
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  background: var(--paper-2);
  color: var(--ink);
  padding: 9px 12px;
  font: inherit;
  font-size: 14px;
}

/* Apple-style select pop-up indicator: drop the default browser arrow and use
   an SF Symbols-like up/down chevron (chevron.up.chevron.down) in systemGray —
   the standard macOS pop-up-button / SwiftUI menu-picker affordance. The high
   specificity (select.x) beats the `background:` shorthands on .att-class/.input
   so the chevron isn't reset. Safari also draws a select that keeps its native look at its own
   fixed height, ignoring padding and min-height: the role list stayed 21px tall on a phone. */
select.att-class,
select.input,
select.role-sel {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='17' viewBox='0 0 12 17'%3E%3Cg fill='none' stroke='%238E8E93' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6.2 6 3.2 9 6.2'/%3E%3Cpath d='M3 10.8 6 13.8 9 10.8'/%3E%3C/g%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 11px auto;
}

select.role-sel {
  padding-right: 28px;
  background-position: right 9px center;
}

select.att-class::-ms-expand,
select.input::-ms-expand {
  display: none;
}

@media (prefers-color-scheme: dark) {
  .segmented button.is-active,
  .segmented button.on {
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  }
}

.page-head-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
}

/* A title stays whole; a button that does not fit moves to a new row. */
.page-head-tools .btn {
  white-space: nowrap;
}

.notification-shell {
  position: relative;
}

/* ===== SVG icon set (outline default, filled on active/hover) ===== */
.ic-svg {
  display: block;
}

/* Inline icon sized to sit alongside button/label text. */
.mini-ic {
  display: inline-block;
  width: 14px;
  height: 14px;
  vertical-align: -2px;
  flex-shrink: 0;
}

.ic-o {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ic-f {
  fill: currentColor;
  display: none;
}

.notification-trigger {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 0.5px solid var(--bar-line);
  border-radius: 50%;
  background: var(--bar);
  color: var(--ink);
  line-height: 1;
  transition: 0.18s ease;
}

.notification-trigger .ic-svg {
  width: 22px;
  height: 22px;
}

/* Bell fills in when the panel is open. */
.notification-trigger.is-open .ic-o {
  display: none;
}

.notification-trigger.is-open .ic-f {
  display: block;
}

.notification-trigger:hover,
.notification-trigger.is-open {
  box-shadow: var(--shadow-lift);
  transform: translateY(-1px);
}

/* Centred on the bell's top right corner, so the bell stays recognisable and a three-character
   count still fits beside the screen edge. */
.notification-count {
  position: absolute;
  top: 2px;
  left: calc(50% + 10px);
  transform: translateX(-50%);
  display: none;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  border-radius: 9px;
  padding: 0 4px;
  border: 2px solid var(--paper-2);
  background: var(--bad-fill);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}

.notification-count.is-visible {
  display: inline-flex;
}

.notification-dropdown {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  z-index: 20;
  display: none;
  flex-direction: column;
  width: 370px;
  max-width: calc(100vw - 40px);
  max-height: 72vh;
  border: 0;
  border-radius: 14px;
  background: var(--bar);
  box-shadow: 0 0 0 0.5px var(--bar-line), 0 14px 40px rgba(0, 0, 0, 0.18);
  overflow: hidden;
  transform-origin: top right;
}

/* Opens out of the bell like a menu (approved s11: a solid panel, no outline). */
.notification-dropdown.is-open {
  display: flex;
  animation: rowMenuIn 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* On a narrow phone the buttons move under the title rather than break their own titles. */
.notification-dropdown-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  flex-shrink: 0;
  padding: 15px 18px;
  border-bottom: 1px solid var(--sep);
}

.notification-dropdown-head strong {
  font-size: 15px;
  line-height: 1.5;
  letter-spacing: -0.01em;
}

.notification-head-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
}

.notification-text-button {
  padding: 0;
  white-space: nowrap;
  color: var(--blue);
  font-size: 13px;
  font-weight: 500;
}

.notification-text-button.is-danger {
  color: var(--bad);
}

.notification-text-button:disabled {
  color: var(--ink-soft);
  cursor: default;
}

.notification-list {
  flex: 1 1 auto;
  overflow-y: auto;
}

.notification-item {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 13px 18px;
  border-bottom: 1px solid var(--sep);
}

.notification-item:last-child {
  border-bottom: 0;
}

/* Unread is the blue dot alone (approved s11), not a tinted row. The kind's symbol sits on a grey
   circle in the text colour. */
.notification-icon {
  flex-shrink: 0;
  margin-top: 1px;
  border-radius: 50%;
  background: var(--fill);
  color: var(--ink);
}

.notification-icon .ic-svg {
  width: 20px;
  height: 20px;
}

.notification-content {
  flex: 1;
  min-width: 0;
}

.notification-aside {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.notification-del {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  color: var(--ink-soft);
  font-size: 18px;
  line-height: 1;
  transition: background 0.15s ease, color 0.15s ease;
}

.notification-del .mini-ic {
  width: 13px;
  height: 13px;
  vertical-align: 0;
}

.notification-del:hover {
  color: var(--bad);
  background: rgba(255, 59, 48, 0.12);
}

.notification-content b {
  display: block;
  margin-bottom: 2px;
  font-size: 14px;
  font-weight: 600;
}

.notification-content p {
  margin: 0;
  color: var(--label-2);
  font-size: 13px;
  line-height: 1.4;
}

.notification-content small {
  display: block;
  margin-top: 5px;
  color: var(--label-2);
  font-size: 11.5px;
}

.notification-udot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--blue);
}

@media (pointer: coarse) {
  .notification-dropdown {
    border-radius: 20px;
  }

  .notification-dropdown-head strong {
    font-size: 17px;
  }
}

/* Redrawn while it stays open (shared.js syncNotificationShell): no second opening. */
.notification-dropdown.is-open.is-steady {
  animation: none;
}

/* Closing (shared.js playNotificationClose): back into the bell, a little slower than it opened. */
.notification-dropdown.is-open.is-closing {
  animation: rowMenuOut 0.28s ease-in-out forwards;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .notification-dropdown.is-open {
    animation: none;
  }
}

.notification-empty {
  padding: 18px;
  color: var(--label-2);
  font-size: 13px;
  line-height: 1.5;
}

/* A strip across the screen, the toasts centred in it: a toast may take the whole width (16px
   margins). The strip itself lets taps through to the page. */
.toast-root {
  position: fixed;
  z-index: 400;
  right: 16px;
  bottom: 26px;
  left: 16px;
  display: grid;
  justify-items: center;
  gap: 10px;
  pointer-events: none;
}

/* On a phone the toasts stand above the tab bar (layout.css), which stays usable while an error
   waits to be closed (HIG, Tab bars). */
@media (max-width: 760px) {
  .app-root:has(.app-shell) ~ .toast-root {
    bottom: calc(var(--tabbar-height) + var(--tabbar-bottom) + 10px);
  }
}

/* As approved (s13): a solid banner in the bar's colour, the text in the label colour, and a
   coloured round mark that carries the tone: a green check, a blue "i", a red "!". */
.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(560px, calc(100vw - 32px));
  border: 0;
  border-radius: 18px;
  background: var(--bar);
  color: var(--ink);
  padding: 12px 18px;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.5;
  box-shadow: 0 0 0 0.5px var(--bar-line), 0 10px 30px rgba(0, 0, 0, 0.16);
  animation: pop 0.2s backwards;
}

/* An error is told apart by more than colour: its own mark, and a close button, since it stays
   until closed (ui.js). */
.toast-error {
  padding-right: 10px;
}

/* The success check and the error's exclamation mark are drawn (icons.js), white on a disc. */
.toast-mark {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ok-fill);
  color: #fff;
}

.toast-mark .mini-ic {
  width: 15px;
  height: 15px;
  vertical-align: 0;
}

.toast-mark .ic-o {
  stroke-width: 2.6;
}

.toast-error .toast-mark {
  background: var(--sys-red);
}

.toast-error .toast-mark .mini-ic {
  width: 13px;
  height: 13px;
}

/* A note's "i" is drawn by CSS and left out of what a screen reader says (the empty alt text). */
.toast-info::before {
  content: "i" / "";
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--blue-fill);
  color: #fff;
  font: 700 13px/1 Georgia, serif;
}

.toast-text {
  flex: 1;
  min-width: 0;
}

/* An undoable change's toast ends with its action in the accent colour, as a text button (p05:
   "Đã xóa tất cả thông báo · Hoàn tác"). The tap area reaches the toast's edges. */
.toast-action {
  flex-shrink: 0;
  margin: -12px -8px -12px 4px;
  padding: 12px 8px;
  background: none;
  color: var(--blue);
  font: inherit;
  font-weight: 600;
}

.toast-close {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--fill);
  color: var(--label-2);
  font-size: 20px;
  line-height: 1;
}

.toast-close .mini-ic {
  width: 15px;
  height: 15px;
  vertical-align: 0;
}

@media (hover: hover) {
  .toast-close:hover {
    color: var(--ink);
  }
}

@media (pointer: coarse) {
  .toast {
    font-size: 15px;
  }
}

@media (max-width: 760px) {
  .toast {
    border-radius: 22px;
    padding: 14px 18px;
  }

  .toast-error {
    padding-right: 10px;
  }
}

.empty-state {
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  padding: 20px;
  color: var(--ink-soft);
}

/* A panel draws its own edge and has no padding: an empty-state box straight inside it keeps clear of
   that edge, so its dashed line is never drawn on the panel's border (HIG, Layout). */
.panel > .empty-state {
  margin: 18px;
}

/* In a page, "nothing here" is quiet centred text on the card itself, with no box of its own
   (approved s45). */
main .empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 160px;
  border: 0;
  background: var(--card);
  color: var(--label-2);
  text-align: center;
  text-wrap: balance;
}

/* A load that failed (ContentUnavailableView, as approved s15): a grey warning triangle, the title,
   the reason in grey and a tinted "Tải lại" button, centred on the page with no box around them. */
.error-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  border: 0;
  background: none;
  padding: 48px 20px;
  color: var(--ink);
  text-align: center;
  text-wrap: balance;
}

.error-state .es-ic {
  width: 46px;
  height: 46px;
  margin-bottom: 6px;
  color: var(--label-3);
}

.error-state .es-ic .ic-o {
  stroke-width: 1.6;
}

.error-state strong {
  color: var(--ink);
  font-size: 17px;
  font-weight: 700;
}

.error-state p {
  max-width: 36ch;
  margin: 0;
  color: var(--label-2);
}

.error-state .btn {
  width: max-content;
  margin-top: 8px;
}

@media (pointer: coarse) {
  .error-state strong {
    font-size: 20px;
  }
}

@media (max-width: 760px) {
  .error-state .es-ic {
    width: 54px;
    height: 54px;
  }
}

/* A list with nothing in it (ContentUnavailableView, as approved s11/s12): an icon, a title, one
   sentence and the next step, centred where the list would be. */
.cuv {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 48px 24px;
  text-align: center;
  text-wrap: balance;
}

.cuv .cuv-ic {
  width: 44px;
  height: 44px;
  margin-bottom: 6px;
  color: var(--label-3);
}

.cuv .cuv-ic .ic-o {
  stroke-width: 1.8;
}

.cuv strong {
  color: var(--ink);
  font-size: 17px;
  font-weight: 700;
}

.cuv p {
  max-width: 34ch;
  margin: 0;
  color: var(--ink-soft);
  font-size: 13px;
  line-height: 1.4;
}

/* As approved: 12px above a primary next step (a4), 10px above a secondary one (s11). */
.cuv .btn {
  margin-top: 12px;
}

.cuv .btn-ghost {
  margin-top: 10px;
}

@media (max-width: 760px) {
  .cuv {
    padding: 40px 20px 44px;
  }

  .cuv strong {
    font-size: 20px;
  }

  .cuv p {
    font-size: 15px;
  }
}

/* The router's "this page cannot be shown right now" screen (router.js renderProblem). */
.route-problem {
  max-width: 560px;
  margin: 12vh auto;
  padding: 0 16px;
}

.route-problem-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 6px;
}

/* The skeleton-loading system now lives in the shared css/skeleton.css (loaded
   by the app, landing, and super-admin console) so every surface matches. */

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes pop {
  from {
    opacity: 0;
    transform: scale(0.95) translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* A tab's page settles in from part of the way, so a switch never passes through an empty frame. */
@keyframes viewIn {
  from {
    opacity: 0.35;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Screen transition — applied only when the view actually changes */
.view-anim {
  animation: viewIn 0.26s cubic-bezier(0.32, 0.72, 0, 1);
}

/* Form/detail blocks start at the same leading edge as every other tab's content, so the title stays
   put when one switches tabs (HIG, Layout: align to ease scanning), and as approved run to the
   column's right edge like every other block (spec 01 §H3: one edge per column). */
.view-narrow {
  max-width: none;
}

.stagger > * {
  animation: rise 0.5s both;
}

.stagger > *:nth-child(2) {
  animation-delay: 0.05s;
}

.stagger > *:nth-child(3) {
  animation-delay: 0.1s;
}

.stagger > *:nth-child(4) {
  animation-delay: 0.15s;
}

.stagger > *:nth-child(5) {
  animation-delay: 0.2s;
}

.stagger > *:nth-child(6) {
  animation-delay: 0.25s;
}

@media (max-width: 900px) {
  .grid-3 {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 760px) {
  /* Under the large title by the same 20px as any first block (approved s47). */
  .page-head-tools {
    width: 100%;
    margin-top: 20px;
    justify-content: flex-start;
  }

  .notification-dropdown {
    position: fixed;
    top: 70px;
    right: 12px;
    left: 12px;
    width: auto;
    max-width: none;
  }
}

@media (prefers-color-scheme: dark) {
  tbody tr:hover {
    background: rgba(255, 255, 255, 0.04);
  }

  .badge {
    color: var(--gold);
  }

  .badge-info,
  .badge.k {
    color: #5ab0ff;
  }

  .badge.ok {
    color: #5de07e;
  }

  .badge.warn {
    color: #ffb340;
  }

  .badge-dark,
  .badge.type {
    background: rgba(94, 92, 230, 0.26);
    color: #a5a1ff;
  }

  .pill-reg {
    color: #ffb340;
  }

  .pill-done {
    color: #5de07e;
  }

  .pill-absent {
    color: #ff8a80;
  }
}

/* ===== Stat cards (dashboard metrics) ===== */
.stats-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 16px;
  margin-bottom: 26px;
}

/* HIG (Layout): the figures are one group, laid out evenly, never with one left alone on a row (two
   and one on an upright iPad, three and one in a narrower Mac window). Three go three across; four go
   two by two until four fit across. Counted with :has() so the loading blocks (.skel-stat, after a
   hidden status line) take the places of the cards that replace them. */
@media (min-width: 761px) {
  .stats-row:has(> :nth-child(3 of .stat, .skel-stat):nth-last-child(1 of .stat, .skel-stat)) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .stats-row:has(> :nth-child(4 of .stat, .skel-stat):nth-last-child(1 of .stat, .skel-stat)) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1101px) {
  .stats-row:has(> :nth-child(4 of .stat, .skel-stat):nth-last-child(1 of .stat, .skel-stat)) {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.stat {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--paper-2);
  box-shadow: var(--shadow);
  padding: 18px 20px;
}

.stat .k {
  color: var(--ink-soft);
  font-size: 12.5px;
  font-weight: 500;
}

.stat .v {
  margin: 8px 0 2px;
  font-size: 38px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
}

.stat .v small {
  color: var(--ink-soft);
  font-size: 15px;
  font-weight: 400;
}

/* Grey, not blue: blue marks what can be tapped (HIG, Color). */
.stat .sub {
  color: var(--ink-soft);
  font-size: 12px;
  font-weight: 500;
}

.stat .blob {
  position: absolute;
  top: -24px;
  right: -24px;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: rgba(0, 122, 255, 0.06);
}

@media (min-width: 981px) {
  .stat {
    padding: 15px 17px;
  }

  .stat .v {
    font-size: 31px;
  }

  .stat .k {
    font-size: 12px;
  }
}

/* A figure that is a name (a class) rather than a number, a little smaller to fit the card. */
.stat .v-text {
  font-size: 30px;
}

/* HIG (Layout): on a phone the content people came for comes first. The figures are one grouped
   list, a row each, the name and its note on the left and the number on the right, rather than a
   card a third of the screen tall each, which pushed the movements, the history or the tables
   below the first screen. */
@media (max-width: 760px) {
  /* One column on every phone: the wide screens' auto-fit fits two 190px columns from 412pt up
     (iPhone 11, Plus and Pro Max), which broke the list into half-width rows (owner's iPhone 11). */
  .stats-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    overflow: hidden;
    border: 0;
    border-radius: 26px;
    background: var(--paper-2);
    box-shadow: none;
    margin-bottom: 20px;
  }

  .stat {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 12px;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 10px 16px;
  }

  /* Rows are split by a hairline from the text's leading edge, as in a grouped list. */
  .stat + .stat::before {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    left: 16px;
    border-top: 1px solid var(--line);
  }

  .stat .k {
    grid-column: 1;
    grid-row: 1;
    color: var(--ink);
    font-size: 17px;
    font-weight: 400;
    line-height: 1.5;
  }

  .stat .sub {
    grid-column: 1;
    grid-row: 2;
    font-size: 13px;
    line-height: 1.5;
  }

  .stat .v {
    grid-column: 2;
    grid-row: 1 / 3;
    margin: 0;
    font-size: 20px;
    white-space: nowrap;
  }

  .stat .v small {
    font-size: 13px;
  }

  .stat .blob {
    display: none;
  }
}

/* ===== Movement cards ===== */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 16px;
  container: movement-cards / inline-size;
}

/* While the list loads it shows two rows of placeholder cards, or three in one column (approved
   s14, s16): eight are drawn, and those past what the grid's columns fill in two rows are hidden.
   A column is 290 points at least, with 16 between. */
@container movement-cards (max-width: 595px) {
  .skel-card:nth-child(n + 4 of .skel-card) {
    display: none;
  }
}

@container movement-cards (min-width: 596px) and (max-width: 901px) {
  .skel-card:nth-child(n + 5 of .skel-card) {
    display: none;
  }
}

@container movement-cards (min-width: 902px) and (max-width: 1207px) {
  .skel-card:nth-child(n + 7 of .skel-card) {
    display: none;
  }
}

/* A message that stands for the whole list (nothing to show, or the load failed) spans the grid, not
   one card's column (HIG, Layout). */
.cards > .empty-state,
.cards > .cuv,
.cards > .error-state {
  grid-column: 1 / -1;
}

/* As approved (s60): a plain white card, the title and its points first, the kind and grades as
   one grey line, the date and the places under it, and one row of actions on a hairline. */
.mv-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid transparent;
  border-radius: var(--r);
  background: var(--paper-2);
  padding: 18px 18px 0;
  animation: rise 0.5s both;
}

.mv-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.mv-title {
  flex: 1;
  min-width: 0;
}

.mv-card h2 {
  margin: 0;
  color: var(--ink);
  font-size: 17px;
  font-weight: 600;
  line-height: 1.3;
}

.mv-sub {
  margin-top: 3px;
  color: var(--label-2);
  font-size: 14px;
}

.mv-pts {
  flex: none;
  line-height: 1;
  text-align: right;
}

.mv-pts b {
  display: block;
  color: var(--blue);
  font-size: 22px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.mv-pts span {
  color: var(--label-2);
  font-size: 12px;
}

.mv-card .desc {
  margin-top: 10px;
  color: var(--label-2);
  font-size: 15px;
}

.mv-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 12px 0 14px;
  color: var(--label-2);
  font-size: 14px;
}

.mv-meta span {
  display: flex;
  align-items: center;
  gap: 6px;
}

.mv-meta .ic-svg {
  flex: none;
  width: 14px;
  height: 14px;
  margin: 0;
  color: var(--label-3);
}

/* The row of actions: on a touch screen one centred 52pt row; the cards of a row line it up at
   their bottoms. */
.mv-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  margin-top: auto;
  border-top: 0.5px solid var(--sep);
}

.mv-foot .pill {
  font-size: 15px;
}

.mv-foot .pill-reg {
  color: #b25000;
}

.mv-foot .pill-done {
  color: var(--sys-green);
}

.mv-foot .pill-off,
.mv-foot .pill-absent {
  color: var(--label-2);
}

/* Đăng ký is iOS's small filled capsule (30pt) with a 44pt hit area around it; Hủy and Chi tiết
   are plain text buttons. */
.mv-foot [data-register] {
  position: relative;
  min-height: 30px;
  height: 30px;
  padding: 0 14px;
  box-shadow: none;
  font-size: 15px;
  font-weight: 600;
}

.mv-foot [data-register]::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  min-height: var(--hit);
  transform: translateY(-50%);
}

.mv-foot .mv-cancel,
.mv-foot .mv-detail {
  background: none;
  font-size: 15px;
  font-weight: 400;
}

.mv-foot .mv-cancel {
  padding: 0 4px;
  color: var(--sys-red);
}

.mv-foot .mv-detail {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  padding: 0;
  color: var(--blue);
}

/* A text button keeps its look under the pointer, as on a Mac (no fill, no fade). */
.mv-foot .mv-cancel:hover,
.mv-foot .mv-detail:hover {
  background: none;
}

@media (max-width: 760px) {
  .mv-card {
    padding: 16px 16px 0;
  }
}

/* A Mac: smaller type, as its lists use, and the actions sit 12px under the hairline and above the
   card's edge. */
@media (min-width: 981px) and (pointer: fine) {
  .mv-card {
    padding: 18px 18px 12px;
  }

  .mv-card h2 {
    font-size: 15px;
  }

  .mv-sub,
  .mv-meta {
    font-size: 12px;
  }

  .mv-pts b {
    font-size: 20px;
  }

  .mv-pts span {
    font-size: 11px;
  }

  .mv-card .desc {
    font-size: 13px;
  }

  .mv-foot {
    min-height: 30px;
    padding-top: 12px;
  }

  .mv-foot .pill,
  .mv-foot .mv-cancel,
  .mv-foot .mv-detail {
    font-size: 13px;
  }

  .mv-foot [data-register] {
    min-height: 28px;
    height: 28px;
    font-size: 13px;
  }
}

@media (prefers-color-scheme: dark) {
  .mv-foot .pill-reg {
    color: #ff9f0a;
  }
}

/* ===== Filters / chips ===== */
.filters {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}

.chip {
  border: 1.5px solid var(--line-strong);
  border-radius: 20px;
  background: var(--paper-2);
  padding: 7px 14px;
  color: var(--ink-soft);
  font-size: 13px;
  font-weight: 500;
  transition: 0.15s ease;
}

/* A filter chip is a single choice: the chosen one is filled, with no check mark (spec 01 §H5, owner
   2026-10-06). The solid fill against an outlined chip tells it apart without relying on hue. */
.chip.on,
.chip.is-active {
  border-color: var(--blue);
  background: var(--hero-fill); /* the deeper blue in dark mode too (approved s47), as a selection, not a button */
  color: #fff;
}

.chip:hover:not(.on):not(.is-active) {
  border-color: var(--ink);
}

.spacer {
  margin-left: auto;
}

@media (min-width: 981px) and (pointer: fine) {
  .chip {
    padding: 5px 12px;
    font-size: 12.5px;
  }
}

/* ===== Class roster (mate list) ===== */
.mate-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 6px;
}

.mate {
  display: flex;
  align-items: center;
  gap: 9px;
  border-radius: 9px;
  background: var(--paper);
  padding: 8px 10px;
  font-size: 13.5px;
}

.mate .dot-i {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  /* The deeper blue in both appearances (approved s61, s64): the system blue that dark mode gives a
     button is too light behind a white initial. */
  background: var(--hero-fill);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
}

.mate.you {
  background: rgba(0, 122, 255, 0.1);
  font-weight: 600;
}

.mate.gap {
  border: 1px dashed var(--line-strong);
  background: transparent;
  color: var(--ink-soft);
}

.mate.gap .dot-i {
  background: var(--line);
  color: var(--ink-soft);
}

/* ===== Inline note / callout ===== */
/* A block, not a flex box: in a flex box every <b> of the sentence became a column of its own. */
.note {
  display: block;
  margin-bottom: 18px;
  border: 1px solid rgba(255, 159, 10, 0.4);
  border-radius: 10px;
  background: rgba(255, 159, 10, 0.12);
  padding: 12px 15px;
  color: #7a5e10;
  font-size: 13px;
  line-height: 1.5;
}

.note.danger {
  border-color: rgba(255, 59, 48, 0.35);
  background: rgba(255, 59, 48, 0.1);
  color: var(--bad);
}

@media (prefers-color-scheme: dark) {
  .note {
    border-color: rgba(255, 159, 10, 0.4);
    background: rgba(255, 159, 10, 0.16);
    color: #ffcf70;
  }
}

/* As approved, a note on a page is a grey fill with secondary text; its warning sign stays orange. */
main .note:not(.danger) {
  border: 0;
  border-radius: 14px;
  background: var(--fill);
  color: var(--label-2);
}

/* The sign is drawn, not text: its stroke is iOS's orange beside the note's own words. */
main .note:not(.danger) .ic-o {
  stroke: #ff9500;
}

@media (min-width: 981px) and (pointer: fine) {
  main .note:not(.danger) {
    border-radius: 10px;
  }
}

/* ===== Two-column content ===== */
.two-col {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 16px;
  align-items: start;
}

@media (max-width: 980px) {
  .two-col {
    grid-template-columns: 1fr;
  }
}

/* ===== Form grid ===== */
.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px 20px;
}

.form-grid .full {
  grid-column: 1 / -1;
}

@media (max-width: 720px) {
  .form-grid {
    grid-template-columns: 1fr;
  }
}

/* ===== Dialogs (spec 01 §H4, §H6) ===== */
/* One shared dialog (#modal inside #overlay, ui.js) comes in four kinds:
   - an alert (.is-alert, from openModal(html, { alert: true })): a question with two answers. It is
     centred, with no ×; title and message are centred and the two buttons share the bottom row;
   - a task sheet: its footer is a .sheet-bar. On a touch screen Hủy moves to the top left and the
     action to the top right; a Mac keeps both at the bottom right;
   - a result: one Xong (.sheet-bar.is-done), at the top right on a touch screen. A Mac shows it as a
     full-width Đóng at the bottom, as in the approved images;
   - a detail sheet: facts, then the one thing to do at the bottom.
   Touch sizes (iPhone and iPad) are the base, a Mac (a mouse) has its own compact block, and a phone's
   width turns every dialog but an alert into a bottom sheet. Nothing is translucent: the page behind
   is dimmed by a solid tint (--dim), never blurred. */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--dim);
}

.overlay.show {
  display: flex;
  animation: fade 0.25s ease-out;
}

/* Closing is quicker than opening, and the backdrop fades out with the dialog. ui.js empties the
   dialog 280ms after it starts closing, so no closing animation runs longer than that. */
.overlay.closing {
  animation: fadeOut 0.22s ease-in forwards;
}

.modal {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 540px;
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 48px);
  overflow: hidden;
  border-radius: 30px;
  background: var(--paper-2);
  box-shadow: 0 0 0 0.5px var(--bar-line), 0 24px 70px rgba(0, 0, 0, 0.28);
  animation: dialogIn 0.34s cubic-bezier(0.32, 0.72, 0, 1);
}

.overlay.closing .modal {
  animation: dialogOut 0.2s cubic-bezier(0.4, 0, 1, 1) forwards;
}

/* An alert settles in from a slightly larger size, as on iOS, and fades out quickly. */
.modal.is-alert {
  max-width: 340px;
  animation-name: alertIn;
  animation-duration: 0.3s;
}

.overlay.closing .modal.is-alert {
  animation-name: alertOut;
  animation-duration: 0.16s;
}

/* The import preview needs more room than an ordinary dialog: the error column is the one thing the
   user has to be able to read, not the one pushed off the edge. */
.modal.modal-wide {
  max-width: 820px;
}

/* One rhythm in every dialog (spec 01 §H4): 24px sides, the title, 6px, then the blocks 12px apart
   (a field right under the title starts 16px below it), and the buttons 20px under the last line. */
.modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 24px 24px 0;
}

.modal-body > * {
  margin-top: 0;
  margin-bottom: 12px;
}

.modal-body > .dlg-title {
  margin-bottom: 6px;
}

.modal-body > .dlg-title + .field,
.modal-body > .dlg-title + [hidden] + .field {
  margin-top: 16px;
}

.modal-body > :last-child {
  margin-bottom: 0;
}

/* A status line that is still empty (it fills in while the action runs) takes no room. */
.modal-body > p:empty {
  margin: -12px 0 0;
}

.modal-body > p.is-bad {
  color: var(--bad);
}

.modal-body > p.import-error {
  color: var(--ink);
}

/* A dialog's button that cannot be pressed yet fades further than a page's. */
.modal .btn:disabled {
  opacity: 0.4;
}

.modal-body > .promote-step {
  margin-bottom: 8px;
}

.dlg-title {
  margin: 0;
  color: var(--ink);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

/* A detail sheet's title is a large title. */
.dlg-title.is-large {
  font-size: 26px;
}

/* A detail sheet's points: a blue line right under its title. */
.modal-body > .dlg-sub {
  margin-top: -4px;
  color: var(--blue);
  font-size: 15px;
  font-weight: 600;
}

/* A dialog's message keeps the size each dialog was approved with (--p-size and --p-lh on the
   paragraph); a touch alert's message is 15pt whatever the dialog says. */
.modal-body > p {
  color: var(--ink-soft);
  font-size: var(--p-size, 15px);
  line-height: var(--p-lh, 1.45);
}

.modal.is-alert .modal-body > p {
  font-size: 15px;
}

.modal label {
  color: var(--ink);
  font-size: 15px;
  font-weight: 500;
}

/* A dialog's fields are the grouped-list field: card surface, separator hairline, 12pt corners on
   a touch screen and 8pt on a Mac (as approved). */
.modal .input,
.modal select,
.modal textarea {
  min-height: 44px;
  border: 0.5px solid var(--sep);
  border-radius: 12px;
  background: var(--field);
  box-shadow: none;
  color: var(--ink);
}

.modal .input:focus {
  border-color: var(--blue);
  outline: 3px solid color-mix(in srgb, var(--blue) 45%, transparent);
  outline-offset: 0;
}

/* A choice inside a dialog (owner, 2026-10-15; the promote alert's year): one grouped row, its name
   on the left and the chosen value with chevron.up.chevron.down on the right, as iOS draws a menu
   picker in a form and macOS System Settings a pop-up row. The native select lies invisible over
   the whole row, so a tap opens the system's own menu; the row shows what it holds. Not a text
   field: no field border, and a ring only when focus came from the keyboard. */
.picker-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 0 14px 0 16px;
  border-radius: 12px;
  background: var(--fill); /* the grey of the dialog's Hủy: a white field vanished on a white alert */
  text-align: left;
}

.modal .picker-row label {
  flex: none;
  color: var(--ink);
  font-size: 17px;
  font-weight: 400;
}

.picker-value {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  color: var(--ink-soft);
  font-size: 17px;
  white-space: nowrap;
}

.picker-value > span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.picker-value::after {
  content: "";
  flex: none;
  width: 11px;
  height: 16px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='17' viewBox='0 0 12 17'%3E%3Cg fill='none' stroke='%238E8E93' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6.2 6 3.2 9 6.2'/%3E%3Cpath d='M3 10.8 6 13.8 9 10.8'/%3E%3C/g%3E%3C/svg%3E") center / 11px auto no-repeat;
}

.modal .picker-row .picker-native {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-height: 0;
  margin: 0;
  border: 0;
  opacity: 0;
  font-size: 16px; /* iOS zooms into a control under 16px */
}

select.picker-native {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

.picker-row:has(.picker-native:focus-visible) {
  outline: 3px solid color-mix(in srgb, var(--blue) 45%, transparent);
}

/* Opening an alert moves focus onto the dialog box itself (tabindex -1) so a screen reader reads
   it; the box is not a control, so it gets no ring (as approved). Its buttons keep theirs. */
.modal[tabindex="-1"]:focus-visible {
  outline: none;
}

.modal .field-hint,
.modal .hint {
  color: var(--label-2);
}

/* HIG (Alerts): side by side, Hủy on the left and the action on the right; when the two titles do
   not fit side by side they stack, the action on top and Hủy at the bottom. Hủy comes first in the
   markup, and wrap-reverse puts the second line above the first. */
.modal-foot {
  display: flex;
  flex-shrink: 0;
  flex-wrap: wrap-reverse;
  gap: 10px;
  padding: 20px 24px 24px;
}

/* Two buttons share the row equally, but neither is narrower than its title: a title is never broken
   over two lines. A footer's only button keeps the normal wrap, for a long reason it may show. */
.modal-foot > .btn:not(:only-child) {
  flex: 1 1 0;
  min-width: max-content;
  white-space: nowrap;
}

/* A dialog's buttons are capsules: the action filled, Hủy and the like on a grey fill, a destructive
   action filled red, as in the approved alerts. */
.modal-foot > .btn {
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  box-shadow: none;
  font-size: 16px;
  font-weight: 600;
}

.modal-foot > .btn-ghost {
  background: var(--fill);
  color: var(--ink);
}

.modal-foot > .btn-danger {
  background: var(--danger-fill);
  color: #fff;
}

@media (hover: hover) {
  .modal-foot > .btn-ghost:hover {
    background: var(--fill);
    filter: brightness(0.95);
  }

  .modal-foot > .btn-danger:hover {
    background: var(--danger-fill);
    filter: brightness(0.92);
  }
}

/* A secondary button inside a dialog (Tải file mẫu, Thêm mức) is a grey capsule, as in the approved
   sheets. */
.modal-body .btn-ghost {
  border: 0;
  border-radius: 999px;
  background: var(--fill);
  color: var(--ink);
}

@media (hover: hover) {
  .modal-body .btn-ghost:hover {
    background: var(--fill);
    filter: brightness(0.95);
  }
}

/* A movement the student cannot join shows its state where the button would be, centred. */
.modal-foot > .pill {
  margin-inline: auto;
}

/* An alert (touch): the title and the message centred, the buttons a little taller and sharing the
   row at equal width, 22px under the last line. A block that is not a sentence stays left-aligned. */
.modal.is-alert .modal-body {
  padding: 24px 22px 0;
  text-align: center;
}

.modal.is-alert .modal-body > :is(p, .dlg-title) {
  text-wrap: balance;
}

.modal.is-alert .modal-body > :not(p, .dlg-title),
.modal.is-alert .modal-body > .import-error {
  text-align: left;
}

.modal.is-alert .dlg-title {
  font-size: 18px;
  font-weight: 600;
}

.modal.is-alert .modal-foot {
  padding: 22px 16px 16px;
}

.modal.is-alert .modal-foot > .btn {
  flex: 1 1 0;
  min-height: 48px;
  padding: 0 10px;
}

/* An iPad form sheet has no grabber, so a detail sheet there gets a bar with Xong (spec 01 §H4);
   an iPhone sheet is swiped down, and a Mac dialog closes with Esc or a click beside it. */
.modal-foot.form-sheet-only {
  display: none;
}

/* A page form's or panel's buttons, in the dialogs' order (HIG): Hủy or another choice first, the
   action at the end of the row. On a phone they share the row; stacked, when the titles do not fit,
   the action is on top. */
.form-foot {
  display: flex;
  flex-wrap: wrap-reverse;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 16px;
}

/* The create-movement form ends at its grade buttons; 24px keeps Hủy / Lưu apart from the 16px
   field rhythm. (Its old server-check note was a developer's reminder from the prototype; the
   owner removed it on 07/10/2026.) */
#movementForm > .form-foot {
  margin-top: 24px;
}

.form-foot > .btn {
  min-width: max-content;
  white-space: nowrap;
}

@media (max-width: 480px) {
  .form-foot > .btn {
    flex: 1 1 0;
  }
}

/* A field with an error is outlined in red, and the reason is red text right under it (spec 01 §H4). */
.input[aria-invalid="true"],
.check-row[aria-invalid="true"] .check {
  border-color: var(--sys-red);
  box-shadow: 0 0 0 1px var(--sys-red);
}

/* Focus on a field with an error keeps the red outline, with no blue ring over it (approved s10);
   the caret shows where focus is. */
.input[aria-invalid="true"]:not(select):focus,
.modal .input[aria-invalid="true"]:not(select):focus {
  border-color: var(--sys-red);
  box-shadow: 0 0 0 1px var(--sys-red);
  outline: none;
}

.check-row + .f-err {
  margin-top: 8px;
}

.f-err {
  display: block;
  margin: 6px 4px 0;
  color: var(--sys-red);
  font-size: 14px;
  line-height: 1.35;
}

@media (min-width: 981px) and (pointer: fine) {
  .f-err {
    margin: 6px 2px 0;
    font-size: 12px;
  }
}

/* A movement's facts in a detail sheet: one row each, the label on the left and the value on the
   right, on a grey inset group (s61). */
.fact-list {
  overflow: hidden;
  border-radius: 14px;
  background: var(--fill);
}

.fact {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 16px;
  font-size: 16px;
}

.fact + .fact {
  border-top: 0.5px solid var(--sep);
}

.fact > span:first-child {
  flex: 0 0 auto;
  color: var(--ink);
}

.fact > span:last-child {
  min-width: 0;
  color: var(--label-2);
  text-align: right;
}

/* A class's progress in a detail sheet (s61): the count on the right of its label, the bar in the
   accent colour, and the classmates on grey rows, the open places folded into one. */
.cp-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 6px;
  color: var(--ink-soft);
  font-size: 13px;
}

.cp-head b {
  color: var(--ink);
  font-size: 15px;
}

.modal .mate {
  border: 0;
  background: var(--fill);
}

/* The movement detail sheet (s61, s64) keeps the measures it was approved with: 26px sides with the
   large title 4px further out on a phone (2px on an iPad or a Mac), the points 24px above the class's
   progress, whose head is a step larger than the dialog's text, the facts 16px under the classmates,
   and the one button 42px under the facts (40px on a Mac). */
.mv-sheet {
  padding: 32px 26px 0;
}

.mv-sheet > .dlg-title.is-large {
  margin-right: -4px;
  margin-left: -4px;
  line-height: 1.12;
}

.mv-sheet > .dlg-sub {
  margin: -4px -4px 24px;
  line-height: 1.5;
}

/* A movement's description (s61 has none; owner's phone test 07/10/2026) belongs to its heading: 12px
   under the points, on the title's edge, with the approved 24px moved below it. */
.mv-sheet > .dlg-sub:has(+ p) {
  margin-bottom: 12px;
}

.mv-sheet > .dlg-sub + p {
  margin: 0 -4px 24px;
}

.mv-sheet > .class-progress {
  margin-bottom: 16px;
}

.mv-sheet .cp-head {
  font-size: 16px;
}

.mv-sheet .cp-head > span {
  font-size: smaller;
}

.mv-sheet .cp-head b {
  font-size: inherit;
}

.mv-sheet + .modal-foot {
  padding: 42px 26px 24px;
}

/* On a phone Xác nhận đăng ký is the large filled button (52pt); Hủy đăng ký keeps 44pt (as approved). */
@media (max-width: 760px) {
  .mv-sheet + .modal-foot > .btn-clay {
    min-height: 52px;
  }
}

@media (min-width: 721px) {
  .mv-sheet > .dlg-title.is-large,
  .mv-sheet > .dlg-sub,
  .mv-sheet > .dlg-sub + p {
    margin-right: -2px;
    margin-left: -2px;
  }
}

@media (min-width: 721px) and (pointer: coarse) {
  .modal:has(> .modal-foot.form-sheet-only) > .modal-body.mv-sheet {
    padding-top: 0;
  }

  .mv-sheet .cp-head {
    font-size: 15px;
  }
}

@media (min-width: 721px) and (pointer: fine) {
  .mv-sheet {
    padding-top: 34px;
  }

  .mv-sheet .cp-head {
    font-size: 13.5px;
  }

  .mv-sheet + .modal-foot {
    padding: 40px 20px 20px;
  }
}

.modal .bar {
  background: var(--fill);
}

.modal .bar i {
  background: var(--blue);
}

/* A class that has not filled its places yet: the bar is orange (Tiến độ theo lớp). */
.modal .bar i.is-short {
  background: var(--gold);
}

/* HIG (Sheets): on an iPhone or iPad a sheet for a task has Hủy at its top left and its action at its
   top right, as text buttons, where the on-screen keyboard never covers them; its large title sits
   right under that bar. An alert keeps its buttons at the bottom, and so does every sheet on a Mac. */
@media (pointer: coarse) {
  .modal-foot.sheet-bar {
    order: -1;
    flex-wrap: nowrap;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 12px 16px 0;
  }

  .modal-foot.sheet-bar > .btn {
    flex: 0 1 auto;
    width: auto;
    min-width: 0;
    min-height: 44px;
    padding: 0 8px;
    border: 0;
    background: none;
    box-shadow: none;
    color: var(--blue);
    font-size: 17px;
    font-weight: 400;
  }

  .modal-foot.sheet-bar > .btn-primary,
  .modal-foot.sheet-bar.is-done > .btn {
    font-weight: 600;
  }

  .modal-foot.sheet-bar > .btn-danger {
    color: var(--bad);
  }

  /* A bar button that cannot be pressed yet is the tertiary label colour, as on iOS. */
  .modal-foot.sheet-bar > .btn:disabled {
    color: var(--label-3);
    opacity: 1;
  }

  /* A result's Xong stands alone at the top right. */
  .modal-foot.sheet-bar.is-done {
    justify-content: flex-end;
  }

  /* On a phone the bar is as tall as a primary button there (52pt), as approved. */
  @media (max-width: 760px) {
    .modal-foot.sheet-bar.is-done > .btn {
      min-height: 52px;
    }
  }

  /* The title sits right under the bar; with no buttons at the bottom the content ends 24px above the
     edge. (A detail sheet's iPad-only bar is counted where it shows, below.) */
  .modal:has(> .modal-foot.sheet-bar:not(.form-sheet-only)) > .modal-body {
    padding-top: 0;
    padding-bottom: 24px;
  }

  .modal:has(> .modal-foot.sheet-bar:not(.form-sheet-only)) > .modal-body:has(> .cp-table) {
    padding-bottom: 0;
  }

  .modal:has(> .modal-foot.sheet-bar:not(.form-sheet-only)) .table-wrap.cp-table {
    padding-bottom: 24px;
  }

  /* A result's or a detail sheet's one button reads its touch title (Xong); a Mac's, its own. */
  .on-mouse {
    display: none;
  }
}

@media (pointer: fine) {
  .on-touch {
    display: none;
  }
}

/* iPad (a touch screen as wide as a Mac): a detail sheet's Xong bar shows, an alert carrying a warning
   block is wider so its title and buttons keep one line, and a large title is iPadOS's size. */
@media (min-width: 721px) and (pointer: coarse) {
  .modal-foot.form-sheet-only {
    display: flex;
  }

  .modal:has(> .modal-foot.form-sheet-only) > .modal-body {
    padding-top: 4px;
  }

  /* An alert with warning blocks is laid out as the Mac's (owner, 18/10/2026, mock n03): 460 wide,
     the title and message left-aligned and wrapping normally, the buttons at the bottom right; the
     touch sizes (text, 48px buttons) stay. */
  .modal.is-alert:has(.import-error) {
    max-width: 460px;
  }

  .modal.is-alert:has(.import-error) .modal-body {
    padding: 24px 24px 0;
    text-align: left;
  }

  .modal.is-alert:has(.import-error) .modal-body > :is(p, .dlg-title) {
    text-wrap: wrap;
  }

  .modal.is-alert:has(.import-error) .modal-foot {
    justify-content: flex-end;
    padding: 22px 24px 24px;
  }

  .modal.is-alert:has(.import-error) .modal-foot > .btn {
    flex: 0 0 auto;
    min-width: 110px;
    padding: 0 22px;
  }

  .dlg-title.is-large {
    font-size: 24px;
  }
}

/* A Mac (a mouse and a window wider than a phone): a compact dialog, macOS's sizes, every dialog's
   buttons at the bottom right, never narrower than 96px. An alert is a Mac dialog too: left-aligned. */
@media (min-width: 721px) and (pointer: fine) {
  .modal {
    max-width: 460px;
    border-radius: 22px;
  }

  .modal .input,
  .modal select,
  .modal textarea {
    min-height: 32px;
    border-radius: 8px;
  }

  .picker-row {
    min-height: 36px;
    padding: 0 10px 0 12px;
    border-radius: 8px;
  }

  .modal .picker-row label,
  .picker-value {
    font-size: 13px;
  }

  .modal.is-alert {
    max-width: 460px;
  }

  .modal.modal-wide {
    max-width: 760px;
  }

  .modal.is-alert .modal-body {
    padding: 24px 24px 0;
    text-align: left;
  }

  /* A Mac's alert is left-aligned, so its lines break the usual way, not balanced as a centred
     touch alert's are ("2026-2027" broke at its dash on a short first line, Part 7 m17e). */
  .modal.is-alert .modal-body > :is(p, .dlg-title) {
    text-wrap: wrap;
  }

  .dlg-title,
  .modal.is-alert .dlg-title {
    font-size: 17px;
    font-weight: 700;
  }

  .dlg-title.is-large {
    font-size: 20px;
  }

  .modal-body > .dlg-sub,
  .modal label {
    font-size: 13px;
  }

  .modal-body > p,
  .modal.is-alert .modal-body > p {
    font-size: var(--p-size, 13px);
  }

  .modal-foot,
  .modal.is-alert .modal-foot {
    justify-content: flex-end;
    padding: 20px 24px 24px;
  }

  .modal-foot > .btn {
    min-height: 34px;
    font-size: 14px;
  }

  .modal-foot > .btn:not(:only-child),
  .modal.is-alert .modal-foot > .btn {
    flex: 0 0 auto;
    min-width: 96px;
    min-height: 34px;
    padding: 0 18px;
  }

  .fact-list {
    border-radius: 10px;
  }

  .fact {
    padding: 8px 14px;
    font-size: 13px;
  }
}

@keyframes fadeOut {
  to {
    opacity: 0;
  }
}

/* iPad and Mac: a sheet fades in and rises a little. */
@keyframes dialogIn {
  from {
    opacity: 0;
    transform: translateY(18px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes dialogOut {
  to {
    opacity: 0;
    transform: translateY(10px) scale(0.98);
  }
}

@keyframes alertIn {
  from {
    opacity: 0;
    transform: scale(1.1);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes alertOut {
  to {
    opacity: 0;
    transform: scale(0.97);
  }
}

/* iPhone: a sheet slides up from the bottom edge, and back down. The closing slide has no start, so a
   sheet dragged part of the way down (ui.js) goes on from where the finger let go. */
@keyframes sheetUp {
  from {
    transform: translateY(100%);
  }
  to {
    transform: none;
  }
}

@keyframes sheetDown {
  to {
    transform: translateY(100%);
  }
}

/* A phone: every dialog but an alert is a bottom sheet with a grabber, dragged down to close it
   (ui.js). An alert stays a centred card. */
@media (max-width: 720px) {
  .overlay {
    align-items: flex-end;
    padding: 0;
  }

  .modal {
    max-width: none;
    max-height: calc(100vh - 54px);
    max-height: calc(100dvh - 54px);
    padding-bottom: env(safe-area-inset-bottom);
    border-radius: 38px 38px 0 0;
    animation: sheetUp 0.42s cubic-bezier(0.32, 0.72, 0, 1);
  }

  .overlay.closing .modal {
    animation: sheetDown 0.26s cubic-bezier(0.4, 0, 1, 1) forwards;
  }

  .modal::before {
    content: "";
    position: absolute;
    top: 15px;
    left: 50%;
    z-index: 5;
    width: 36px;
    height: 5px;
    border-radius: 3px;
    background: var(--grabber);
    transform: translateX(-50%);
  }

  .overlay.has-alert {
    align-items: center;
    padding: 0 28px;
  }

  .modal.is-alert {
    max-width: 340px;
    max-height: calc(100vh - 80px);
    max-height: calc(100dvh - 80px);
    padding-bottom: 0;
    border-radius: 30px;
    animation: alertIn 0.3s cubic-bezier(0.32, 0.72, 0, 1);
  }

  .overlay.closing .modal.is-alert {
    animation: alertOut 0.16s ease-in forwards;
  }

  .modal.is-alert::before {
    display: none;
  }
}

@media (max-width: 720px) and (pointer: coarse) {
  .modal-foot.sheet-bar {
    padding: 10px 14px 0;
  }

  /* The sheet's action is the phone's tall button (52pt, as approved), so Hủy beside it sits 4pt
     lower and the title starts where the bar ends. */
  .modal-foot.sheet-bar > .btn-primary {
    min-height: 52px;
  }
}

/* Reduce Motion: no sliding or scaling, only a short fade in and a quicker fade out (spec 01 §H6). These
   win over the global rule below, which cuts every other animation to nothing. */
@media (prefers-reduced-motion: reduce) {
  .overlay.show,
  .modal,
  .modal.is-alert {
    animation: fade 0.2s ease-out !important;
  }

  .overlay.closing,
  .overlay.closing .modal,
  .overlay.closing .modal.is-alert {
    animation: fadeOut 0.15s ease-in forwards !important;
  }
}

/* ===== Definition list (detail pages + modals: student profile, drill-down) ===== */
.d-list {
  border-top: 1px solid var(--line);
}

/* The header's border already separates them: one hairline, not two. A list first in its card keeps
   its hairline under the card's edge, as approved (s44, a movement's detail). */
.panel-head + .d-list {
  border-top: 0;
}

.d-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 1px solid var(--line);
  padding: 12px 20px;
  font-size: 14px;
}

.d-row:last-child {
  border-bottom: none;
}

/* The label keeps its one line, leaving the value at least 5em; the value takes the rest and wraps. */
.d-k {
  flex: 0 0 auto;
  max-width: calc(100% - 5em);
  color: var(--ink-soft);
}

.d-v {
  min-width: 0;
  font-weight: 500;
  text-align: right;
}

/* ===== Student detail modal (doan/admin drill-down, s20) ===== */
/* An iOS inset-grouped sheet: the page colour behind white groups, a plain header with a grey
   avatar, the three numbers as one group, then a small grey heading above each list. Đóng stays at
   the bottom on every screen, as in the approved images. */
.modal:has(> .sd-head) {
  background: var(--paper);
}

.sd-head {
  padding: 28px 24px 16px;
  text-align: center;
  text-wrap: balance;
}

.sd-ava {
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  margin: 0 auto 10px;
  border-radius: 50%;
  background: linear-gradient(var(--av-top), var(--av-bottom));
  color: #fff;
  font-size: 23px;
  font-weight: 700;
}

.sd-name {
  margin: 0;
  color: var(--ink);
  font-size: 24px;
  font-weight: 700;
  line-height: 1.12;
}

.sd-class {
  margin-top: 2px;
  color: var(--label-2);
  font-size: 13px;
}

.sd-stats {
  display: flex;
  flex-shrink: 0;
  margin: 0 16px;
  overflow: hidden;
  border-radius: 14px;
  background: var(--paper-2);
}

.sd-stats > div {
  flex: 1;
  padding: 16px 8px;
  text-align: center;
}

.sd-stats > div + div {
  border-left: 0.5px solid var(--sep);
}

.sd-v {
  color: var(--ink);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.sd-k {
  margin-top: 2px;
  color: var(--label-2);
  font-size: 12px;
}

.modal-body.sd-body {
  padding: 0 16px 8px;
}

.modal-body.sd-body > * {
  margin-bottom: 0;
}

.sd-sec {
  margin: 0;
  padding: 20px 16px 6px;
  color: var(--label-2);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.sd-body .d-list,
.sd-body .sd-awards,
.sd-body .sd-empty {
  overflow: hidden;
  border: 0;
  border-radius: 14px;
  background: var(--paper-2);
}

.sd-body .d-row {
  padding: 12px 16px;
  border-bottom: 0.5px solid var(--sep);
  font-size: 16px;
}

.sd-body .d-row:last-child {
  border-bottom: 0;
}

.sd-body .d-k {
  color: var(--ink);
}

.sd-body .d-v {
  color: var(--label-2);
}

/* The total reads as the sum of the rows above it: its label in the ink, its number bold in the rows' grey. */
.sd-body .sd-total .d-k {
  font-weight: 600;
}

.sd-body .sd-total .d-v {
  font-weight: 700;
}

.sd-body .sd-empty {
  padding: 12px 16px;
  color: var(--label-2);
  font-size: 15px;
  line-height: 1.5;
}

.sd-awards td {
  border-color: var(--sep);
}

.sd-awards b {
  font-weight: 500;
}

.sd-sub {
  color: var(--ink-soft);
  font-size: 12px;
}

/* An award's points read like the breakdown above (owner, Part 7 s20, 08/10/2026): the rows' grey and
   weight, so on the whole sheet only the total stands out. */
.sd-pts {
  color: var(--label-2);
  font-weight: 500;
  text-align: right;
  white-space: nowrap;
}

.modal:has(> .sd-head) .modal-foot {
  padding: 18px 26px 24px;
}

@media (min-width: 721px) and (pointer: fine) {
  .sd-name {
    font-size: 20px;
  }

  .sd-class {
    font-size: 13px;
  }

  .sd-stats,
  .sd-body .d-list,
  .sd-body .sd-awards,
  .sd-body .sd-empty {
    border-radius: 10px;
  }

  /* A Mac's group is shorter, with its three numbers centred in it: the approved image sat them low,
     which the owner saw as off (Part 7 d13, 07/10/2026). */
  .sd-stats > div {
    padding-top: 10px;
    padding-bottom: 10px;
  }

  .modal:has(> .sd-head) .modal-foot {
    padding: 16px 20px 20px;
  }

  .sd-sec {
    font-size: 11px;
  }

  .sd-body .d-row {
    padding: 9px 16px;
    font-size: 13px;
  }

  .sd-body .sd-empty {
    font-size: 13px;
  }
}

/* ===== Floating notification bell ===== */
/* Centred in the bar that spans the top of a scrolled page (.main-edge, layout.css). */
.notification-fab {
  position: fixed;
  top: 14px;
  right: 30px;
  z-index: 150;
}

.notification-fab .notification-trigger {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  box-shadow: var(--lift);
}

.notification-fab .notification-trigger:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-lift);
}

.notification-fab .notification-dropdown {
  top: calc(100% + 12px);
}

@media (max-width: 760px) {
  .notification-fab {
    top: 14px;
    right: 14px;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}

/* macOS / laptop high-density desktop — mirrors the prototype's @media
   (min-width:981px) block so panels, inputs, buttons, segmented controls and
   tables render at the same compact size as the design. Placed last among the
   global stylesheets so these overrides win the cascade. Mouse and trackpad
   only: an iPad held sideways is as wide but keeps the touch sizes (HIG). */
@media (min-width: 981px) and (pointer: fine) {
  .panel-head {
    padding: 13px 18px;
  }

  .panel-head strong {
    font-size: 15px;
  }

  .input,
  .field input,
  .field textarea {
    padding: 8px 11px;
    font-size: 13.5px;
    border-radius: 7px;
  }

  .btn {
    min-height: 0;
    padding: 8px 14px;
    font-size: 13.5px;
    border-radius: 7px;
  }

  .btn-primary {
    padding: 9px 16px;
  }

  .btn-sm {
    min-height: 30px;
    padding: 5px 11px;
    font-size: 12.5px;
  }

  .segmented button {
    min-height: 30px;
    padding: 6px 14px;
    font-size: 13px;
  }

  th {
    padding: 9px 16px;
    font-size: 11px;
  }

  td {
    padding: 9px 16px;
    font-size: 13px;
  }
}

/* Touch screens, at any width: every control is at least 44px tall (HIG). Text and icon buttons get
   an invisible hit area instead (base.css, --hit). */
@media (pointer: coarse) {
  .btn-sm,
  .segmented button,
  .chip,
  .att-search,
  .att-class,
  .role-sel {
    min-height: 44px;
  }
}

/* Settings-style lists (spec 01 §H5): one row per item, a tinted tile, the value and a chevron, as
   in the phone's Cài đặt list and account page. The tiles' colours hold 4.5:1 under white. */
.menu-head {
  margin: 22px 16px 6px;
  color: var(--ink-soft);
  font-size: 13px;
  font-weight: 400;
}

.menu-group {
  overflow: hidden;
  border-radius: 12px;
  background: var(--paper-2);
}

.menu-group + .menu-group {
  margin-top: 22px;
}

.menu-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 44px;
  padding: 8px 16px;
  color: var(--ink);
  font-size: 17px;
  text-align: left;
}

.menu-row + .menu-row::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 16px;
  height: 1px;
  background: var(--line);
}

.menu-row:has(.menu-tile) + .menu-row::before {
  left: 58px;
}

/* Pressed, the row darkens like a table cell. Hover only with a pointer: on a touch screen it
   would stay on the row under the last tap (here, the row that slid under the finger). */
button.menu-row:active {
  background: rgba(0, 0, 0, 0.08);
}

@media (hover: hover) {
  button.menu-row:hover {
    background: rgba(0, 0, 0, 0.04);
  }
}

.menu-row.is-danger {
  color: var(--bad);
}

.menu-tile {
  display: grid;
  flex: none;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 7px;
  background: #636366;
  color: #fff;
}

.menu-tile .ic-svg {
  width: 19px;
  height: 19px;
}

.menu-tile.tone-red {
  background: #d70015;
}

.menu-tile.tone-orange {
  background: #b25000;
}

.menu-tile.tone-indigo {
  background: #5856d6;
}

.menu-tile.tone-green {
  background: #187630;
}

/* The label keeps its width; a long value (the school's name) is what gets shortened. The account
   row has no value, so its name shortens instead. */
.menu-text {
  display: flex;
  flex: 1 0 auto;
  flex-direction: column;
}

.menu-account .menu-text {
  flex: 1 1 auto;
  min-width: 0;
}

.menu-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.menu-sub {
  color: var(--ink-soft);
  font-size: 15px;
}

.menu-value {
  min-width: 0;
  overflow: hidden;
  color: var(--ink-soft);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.menu-chev {
  display: flex;
  color: var(--ink-soft);
}

.menu-chev .ic-svg {
  width: 16px;
  height: 16px;
}

.menu-account {
  min-height: 76px;
}

.menu-account .menu-label {
  font-size: 20px;
  font-weight: 600;
}

.menu-ava {
  display: grid;
  flex: none;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: #636366;
  color: #fff;
  font-size: 22px;
  font-weight: 600;
}

@media (prefers-color-scheme: dark) {
  button.menu-row:active {
    background: rgba(255, 255, 255, 0.1);
  }
}

@media (prefers-color-scheme: dark) and (hover: hover) {
  button.menu-row:hover {
    background: rgba(255, 255, 255, 0.06);
  }
}

/* ===== Page structure as approved (spec 01 §H3, step 3d) =====
   Cards have no border and no shadow, and one corner radius per platform: 26pt on an iPhone, 22pt
   on an iPad, 14pt on a Mac. A section's title sits above its card, outside it, as in iOS lists and
   macOS forms (.sec-wrap > .sec-title + .panel). The overview's titles are large (.big); every other
   page's are small (.small). */
.panel,
.stats-row {
  border: 0;
  border-radius: 14px;
  box-shadow: none;
}

.panel-head {
  border-bottom: 0;
}

/* A figure's card: no frame, no shadow, no decoration. */
.stat {
  border-color: transparent;
  box-shadow: none;
}

.stat .blob {
  display: none;
}

.sec-wrap {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.sec-wrap > .panel {
  flex: 1;
}

.sec-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 22px 2px 8px;
}

.sec-title strong {
  color: var(--ink);
  font-weight: 700;
}

.sec-title.big strong {
  font-size: 17px;
}

.sec-title.small {
  margin: 20px 2px 6px;
}

.sec-title.small strong {
  font-size: 13px;
  font-weight: 600;
}

.sec-title .panel-note {
  font-size: 13px;
}

/* A section title is its name only, as a page head is its large title only: the line under it is
   kept in the markup for a later change of mind. */
.sec-title p {
  display: none;
}

/* A state that is the normal one ("Hoạt động", "Đang mở") is not shown: only the exception is (a
   locked account reads "Đã khóa"). It stays for a screen reader. */
.badge.ok {
  position: absolute;
  width: 1px;
  height: 1px;
  min-height: 0;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (pointer: coarse) {
  .panel,
  .stats-row {
    border-radius: 22px;
  }

  .sec-title.small strong {
    font-size: 15px;
  }

  .sec-title .panel-note {
    font-size: 15px;
  }
}

@media (max-width: 760px) {
  .panel,
  .stats-row {
    border-radius: 26px;
  }

  .sec-title {
    margin: 22px 4px 8px;
  }

  .sec-title.big strong {
    font-size: 22px;
  }

  .sec-title.small {
    margin: 22px 16px 6px;
  }

  /* A page that opens with a section: its title starts 20px under the large title, like every
     other first block (the head's own margin). */
  .page-head + div > .sec-wrap:first-child > .sec-title:first-child,
  .page-head + .sec-wrap > .sec-title:first-child {
    margin-top: 0;
  }

  .sec-title.small strong {
    color: var(--ink-soft);
    font-size: 13px;
    font-weight: 400;
  }

  .sec-title .panel-note {
    font-size: 13px;
  }

  .stat + .stat::before {
    border-top-color: var(--sep);
  }
}

/* ===== Lists and their actions as approved (spec 01 §H3, step 3d) =====
   Column titles are sentence case in the secondary label colour, as in a macOS table. */
th {
  color: var(--label-2);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}

/* A row's actions behind a round "…" button (ui.js toggleRowMenu). */
.more-btn {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--fill);
  color: var(--ink);
}

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

/* The menu: a solid card over the page, the same as the account menu. */
.row-menu {
  position: fixed;
  z-index: 300;
  min-width: 220px;
  max-width: min(320px, calc(100vw - 16px));
  padding: 5px;
  border-radius: 12px;
  background: var(--bar);
  box-shadow: 0 0 0 0.5px var(--bar-line), 0 12px 34px rgba(0, 0, 0, 0.2);
  text-align: left;
  white-space: normal;
  transform-origin: top right;
  animation: rowMenuIn 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.row-menu[hidden] {
  display: none;
}

/* Closing (ui.js playRowMenuClose): back toward the button, a little slower than it opened, the
   same as the bell's panel. */
.row-menu.is-closing {
  animation: rowMenuOut 0.28s ease-in-out forwards;
  pointer-events: none;
}

.row-menu.is-up {
  transform-origin: bottom right;
}

@keyframes rowMenuIn {
  from {
    opacity: 0;
    transform: scale(0.94);
  }
}

@keyframes rowMenuOut {
  to {
    opacity: 0;
    transform: scale(0.94);
  }
}

.row-menu [role="menuitem"] {
  display: block;
  width: 100%;
  padding: 6px 10px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 400;
  text-align: left;
}

.row-menu [role="menuitem"].danger {
  color: var(--sys-red);
}

.row-menu [role="menuitem"]:disabled {
  color: var(--label-3);
  cursor: default;
}

.row-menu [role="menuitem"]:not(:disabled):is(:hover, :focus-visible) {
  outline: none;
  background: var(--blue-fill);
  color: #fff;
}

.row-menu-sep {
  height: 0.5px;
  margin: 5px 10px;
  background: var(--sep);
}

.row-menu-head,
.row-menu-note {
  margin: 0;
  padding: 4px 10px 2px;
  color: var(--label-2);
  font-size: 11px;
  font-weight: 600;
}

.row-menu-note {
  padding-bottom: 6px;
  font-weight: 400;
}

/* A search that found nothing (ContentUnavailableView.search): the query stays in the field and
   the list gives way to a centred message. */
.no-res {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 56px 20px;
  color: var(--label-2);
  font-size: 13px;
  text-align: center;
  white-space: normal;
  text-wrap: balance;
}

.no-res .ic-svg {
  width: 44px;
  height: 44px;
  margin-bottom: 6px;
  color: var(--label-3);
}

.no-res .ic-o {
  stroke-width: 1.7;
}

.no-res b {
  color: var(--ink);
  font-size: 17px;
  font-weight: 700;
}

/* A text button in a section's title (iPad and Mac) and a link under a list (iPhone). */
.sec-link,
.list-link button {
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--blue);
  font: inherit;
  cursor: pointer;
}

/* A section's add button (Cộng điểm's "Thêm hạng mục", approved s48): plain accent text with its
   plus, on the header's baseline; the plus sits on the text's middle. */
.sec-title:has(> .sec-add) {
  align-items: baseline;
}

.sec-add {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  font-size: 15px;
  font-weight: 400;
  line-height: 23px;
}

.sec-add .mini-ic {
  align-self: center;
  width: 14px;
  height: 14px;
}

@media (max-width: 760px) {
  .sec-add {
    align-items: center;
    gap: 8px;
    line-height: 1.5;
  }

  .sec-add .mini-ic {
    width: 13px;
    height: 13px;
  }
}

@media (min-width: 981px) and (pointer: fine) {
  .sec-add {
    gap: 4px;
    font-size: 13px;
    line-height: 20px;
  }

  .sec-add .mini-ic {
    width: 12px;
    height: 12px;
  }
}

.list-link {
  display: none;
  justify-content: center;
  margin-top: 26px;
}

.list-link button {
  min-height: 44px;
  font-size: 17px;
}

@media (pointer: coarse) {
  .more-btn {
    width: 44px;
    height: 44px;
  }

  .row-menu {
    min-width: 250px;
    padding: 8px;
    border-radius: 18px;
  }

  .row-menu [role="menuitem"] {
    padding: 11px 12px;
    border-radius: 11px;
    font-size: 16px;
  }

  .row-menu [role="menuitem"]:not(:disabled):is(:hover, :focus-visible) {
    background: var(--sel);
    color: inherit;
  }

  .row-menu [role="menuitem"].danger:not(:disabled):is(:hover, :focus-visible) {
    color: var(--sys-red);
  }

  .row-menu-head,
  .row-menu-note {
    font-size: 13px;
  }

  .no-res {
    font-size: 15px;
  }

  .no-res b {
    font-size: 20px;
  }
}

@media (max-width: 760px) {
  .list-link {
    display: flex;
  }

  .no-res .ic-svg {
    width: 52px;
    height: 52px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .row-menu {
    animation: none;
  }
}

/* ===== Controls as approved (step 3d) =====
   Every button, segmented control and search field is a capsule. The secondary button is a blue
   tint with no outline; the primary one is filled. */
.btn,
.btn-sm {
  border-radius: 999px;
}

.btn-primary,
.btn-clay {
  border: 0;
  background: var(--blue-fill);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 4px 14px color-mix(in srgb, var(--blue) 26%, transparent);
}

.btn-ghost {
  border: 0;
  background: var(--sel-tint);
  color: var(--blue);
}

/* Only where there is a pointer: a tap on a touch screen leaves :hover on, tinting the button after it. */
@media (hover: hover) {
  .btn-ghost:hover {
    border: 0;
    background: color-mix(in srgb, var(--sel-tint) 70%, var(--blue) 8%);
  }
}

/* A page's toolbar beside the title (iPad, Mac): solid capsules; its one primary action is filled.
   On a phone the buttons sit under the title and keep the plain tinted look (approved s47). */
@media (min-width: 761px) {
  .page-head-tools .btn:not(.btn-clay):not(.btn-primary) {
    border: 0.5px solid var(--bar-line);
    background: var(--bar);
    box-shadow: var(--lift);
    color: var(--ink);
  }

  .page-head-tools .btn-clay,
  .page-head-tools .btn-primary {
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.14);
  }
}

.segmented {
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background: var(--fill);
}

.segmented button {
  border-radius: 999px;
}

.segmented button.is-active,
.segmented button.on {
  background: var(--thumb);
  box-shadow: 0 2px 7px rgba(0, 0, 0, 0.12), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
  font-weight: 600;
}

main .att-search,
main input.input[placeholder^="Tìm"],
main input[type="search"] {
  border: 0;
  border-radius: 999px;
  background: var(--fill);
  box-shadow: none;
  padding-left: 18px;
}

main .att-search:focus,
main input.input[placeholder^="Tìm"]:focus,
main input[type="search"]:focus {
  outline: 3px solid color-mix(in srgb, var(--blue) 45%, transparent);
  outline-offset: 0;
  box-shadow: none;
}

@media (min-width: 981px) and (pointer: fine) {
  .page-head-tools .btn {
    min-height: 32px;
    height: 32px;
    padding: 0 14px;
  }
}

@media (min-width: 761px) and (pointer: coarse) {
  .page-head-tools .btn {
    min-height: 44px;
    height: 44px;
    padding: 0 18px;
    font-size: 17px;
  }
}

/* A toolbar button that opens a menu ("Xuất ⌄", approved s20): a plain capsule with the title in medium
   weight and a small chevron after it. The menu is the row menu (toggleRowMenu). */
.page-head-tools .btn.tool-menu-btn {
  gap: 4px;
  border-width: 1px;
  font-weight: 500;
}

.tool-menu-btn .menu-chev {
  width: 10px;
  height: 10px;
}

.tool-menu-btn .menu-chev .ic-o {
  stroke-width: 1.6;
}

@media (min-width: 981px) and (pointer: fine) {
  .page-head-tools .btn.tool-menu-btn {
    font-size: 13px;
  }
}

/* The phone's share button in the top bar, beside the bell (21pt, as approved). */
.mtop-add .ic-svg {
  width: 21px;
  height: 21px;
}

.mtop-add .ic-svg .ic-o {
  stroke-width: 2;
}

@media (max-width: 760px) {
  .btn-clay,
  .btn-primary {
    min-height: 52px;
    font-size: 17px;
  }

  /* A page whose action moved to the top bar (+) shows no buttons under its title. */
  .page-head.has-bar-action .page-head-tools {
    display: none;
  }

  /* A list's search field and filter are as wide as the list; only its title is inset. */
  .panel-head.panel-head-stack {
    padding-left: 0;
    padding-right: 0;
  }

  .panel-head.panel-head-stack > div:first-child {
    padding: 0 20px;
  }

  main .segmented {
    display: flex;
    width: 100%;
  }

  /* On iPhone equal segments fill the row (spec 01 §H3). */
  main .segmented:not(.segmented-fill) button {
    flex: 1 1 0;
    min-width: 0;
  }

  main .segmented button,
  .modal .segmented button {
    min-height: 34px;
  }
}

/* A tag in a table cell ("Bạn", "Đã khóa", a kind) is plain secondary text at the cell's size. */
main td .badge.solo,
main td .badge.type {
  padding: 0;
  border: 0;
  background: none;
  color: var(--label-2);
  font-size: inherit;
  font-weight: 400;
}

/* ===== Notices and semester controls (docs/specs/13) =====
   A notice card (.ov-notice): an orange tile as in the settings lists, the title and a grey line, an
   action as the page's one filled button. The overview's (admin.css) and the semester reminders on
   the admin, Đoàn and student pages share it. */
.ov-notice {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 0;
  padding: 14px 16px;
  animation: rise 0.5s backwards;
}

.ov-notice-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ov-notice-text strong {
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
}

.ov-notice-text span {
  color: var(--ink-soft);
  font-size: 12.5px;
}

/* The semester reminders (mock n02, n07, n14) keep these sizes on every screen, the tile centred on
   the text, as approved. */
.ov-notice.sem-notice {
  align-items: center;
  padding: 14px 16px;
}

.ov-notice.sem-notice .ov-notice-text strong {
  font-size: 14px;
}

.ov-notice.sem-notice .ov-notice-text span {
  font-size: 12.5px;
}

@media (prefers-reduced-motion: reduce) {
  .ov-notice {
    animation: fade 0.2s backwards;
  }
}

/* Segmented controls side by side (Toàn trường / Theo lớp and the semester), stacked full width on a
   phone, their segments sharing the row as .segmented-fill's do. */
.seg-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 12px;
  align-items: center;
}

.seg-row .segmented {
  margin: 0;
}

@media (max-width: 760px) {
  .seg-row {
    flex-direction: column;
    align-items: stretch;
  }

  .seg-row .segmented {
    width: 100%;
  }

  main .seg-row .segmented button {
    flex: 1;
    white-space: nowrap;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

@media (max-width: 480px) {
  main .seg-row .segmented button {
    flex: 1 1 auto;
    padding: 4px 5px;
    white-space: normal;
    line-height: 1.2;
  }
}

/* Two fields on one row (the export's Năm học and Học kỳ), one under the other on a phone. */
.field-pair {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}

.field-pair .field {
  flex: 0 1 300px;
  min-width: 0;
}

@media (max-width: 760px) {
  .field-pair .field {
    flex: 1 1 100%;
  }
}

/* A choice that cannot be made (Học kỳ I once locked, mock n11) is dimmed. */
.check[disabled] {
  opacity: 0.4;
}

.full > .field-hint {
  display: block;
  margin-top: 7px;
}
