/* =========================================================================
   Mehrawaran Balkh - design system

   Same design language as the Gulistan Kindergarten build: warm paper tones,
   a crayon-box palette where each colour has one job across the whole app,
   generous corner radii, and personality concentrated in a few deliberate
   places (the sidebar pegs, the stat cubbies) rather than scattered around.

   Two things differ from the kindergarten, both forced by the audience:

   1. RTL is the default and the structure, not a mirror bolted on. Every
      directional value uses a logical property, so the English LTR build
      needs no separate rules. The handful of places CSS has no logical
      equivalent - gradient angles, a couple of transforms - are collected in
      explicit [dir] blocks.
   2. Arabic-script text is set in Vazirmatn, not Segoe UI/Tahoma. Those have
      Arabic letterforms and render Dari's ک and ی wrong; it is a legibility
      bug, not a taste call. Fredoka carries Latin display text only.
   ========================================================================= */

@import url('../fonts/fonts.css');

@font-face {
  font-family: 'Fredoka';
  src: url('../fonts/Fredoka-Variable.woff2') format('woff2-variations'),
       url('../fonts/Fredoka-Variable.woff2') format('woff2');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* --- paper ----------------------------------------------------------
     Taken from the reference dashboard, colours included: a cool near-white
     ground at #F6F8FA, white cards on it, and three pale fills - lavender
     #CFCEFE, yellow #FAE27A, pale blue #C4EAFB - that carry the tiles, the
     chips and the active nav pill.

     The three fills measure 1.51, 1.29 and 1.27:1 on white. None of them can
     be a letterform, and white on them is exactly as bad, so each keeps a
     darkened ink deepened until it clears 4.5:1 on white AND on its own
     tint. The fills are only ever painted behind something, with the near
     black on top - 10.5:1 on the lavender, 12.3 on the yellow.

     Two values are deliberately NOT the reference's. Its muted greys measure
     3.31:1 for an idle nav label, 2.41 for a role caption and 2.27 for a
     chart axis - all under the 4.5 a word owes, on a screen a school office
     reads for six hours a day. They are deepened here to 6.09 and 4.52,
     which is the same grey family two steps down and the only place this
     palette departs from what was asked for. */
  --bg: #f6f8fa;
  --surface: #ffffff;
  --surface-alt: #f4f6f9;
  /* The tint behind a table's header row: the template heads every list in
     its pale blue (sampled as #ECF9FE, which is --sky-soft exactly). Written
     as the token rather than the hex so dark mode, which redefines
     --sky-soft as a translucent tint, gets a dark header rather than a
     bright bar with pale text on it. */
  --table-head: var(--sky-soft);
  --border: #e8ebf0;
  --border-strong: #cfd4dd;
  --fg: #20222a;
  --fg-muted: #5a5e69;
  --fg-subtle: #6e727d;

  /* --- crayon box ----------------------------------------------------
     One colour per section, used consistently everywhere that section
     appears: the sidebar peg, its stat card, its badges.

     Each crayon has TWO values, and the split matters:

       --x        the vivid colour. Decoration only - pegs, icon circles,
                  hover borders, progress bars. Nothing legible sits on it.
       --x-ink    the same hue darkened until it clears 4.5:1 as text.

     The kindergarten build used the vivid value for both, which measures
     2.45:1 for the "excused" chip and 3.15:1 for white on a coral button.
     That was survivable for a handful of staff on one office PC. Here the
     same chips say کامیاب / ناکام on a public page a parent reads on a phone
     in daylight, so the text-bearing variants are darkened. The identity is
     unchanged - coral is still coral, just a deeper ink when it is a word. */
  /* In LIGHT mode the section crayons collapse onto the blue ramp: the four
     names below still mean students / classes / staff everywhere they are
     used, they are simply all blue here. Dark mode restores the real crayons
     further down, which is why the tokens keep their original names - a
     component rule must not have to know which theme it is in.

     The four STATUS crayons underneath (marigold, sky, grass, berry) keep
     their hues in both themes. They are the ones that say کامیاب / ناکام and
     present / absent / late on a public report card, and hue is how a parent
     scans eighteen subjects for the failures without reading every row. */
  --coral: #cfcefe;        /* students - and the primary action colour */
  --coral-ink: #00719c;    /* 5.55:1 with white on it, and as text on white */
  --coral-soft: #f1f0ff;
  --teal: #b9f0e6;         /* classes & structure; the sidebar ground */
  --teal-hover: #a3e9dc;
  --teal-soft: #e8fbf7;
  --teal-deep: #232a3a;
  --marigold: #fae27a;     /* late, and warnings generally */
  --marigold-ink: #856c00;
  --marigold-soft: #fdf6d8;
  --sky: #c4eafb;          /* attendance, "excused" */
  --sky-ink: #0375a8;
  --sky-soft: #ecf9fe;
  --grass: #bdf0d0;        /* success, "present", passed */
  --grass-ink: #177e3d;
  --grass-soft: #e9fbf0;
  --berry: #ffd0d6;        /* fees & money, "absent", failed */
  --berry-ink: #dd001c;
  --berry-soft: #fff0f2;
  --violet: #cfcefe;       /* staff */
  --violet-ink: #00719c;
  --violet-soft: #f1f0ff;

  /* Ink dark enough to read on a marigold fill, for the two controls that
     are deliberately yellow (the portal's search button, the "late" pill). */
  --on-marigold: #20222a;

  /* --- semantic aliases ----------------------------------------------
     Everything that carries text points at an -ink value. */
  --primary: var(--coral-ink);
  --primary-hover: #4340e0;
  --primary-soft: var(--coral-soft);
  --secondary: var(--teal);
  /* Was marigold. The accent drives chrome - the portal's search button, the
     sidebar highlight - and chrome is blue here. Marigold survives as the
     "late" status and nothing else. */
  --accent: #cfcefe;
  --success: var(--grass-ink);
  --success-soft: var(--grass-soft);
  --danger: var(--berry-ink);
  --danger-soft: var(--berry-soft);
  --warning: var(--marigold);
  --warning-soft: var(--marigold-soft);
  --warning-ink: var(--marigold-ink);
  --info: var(--sky-ink);
  --info-soft: var(--sky-soft);

  --on-brand: #ffffff;
  --on-brand-muted: rgba(255, 255, 255, 0.78);

  /* Text sitting ON a filled accent (a primary button, a selected status
     pill, a badge). White works in light mode because the fills are dark
     enough. In dark mode the accents ARE the light colour, so white on them
     drops to about 2:1 and these flip to a dark ink instead - see the dark
     block below. Without this pair, one theme or the other always loses. */
  --on-primary: #ffffff;
  --on-secondary: #20222a;
  --on-success: #ffffff;
  --on-danger: #ffffff;
  --on-info: #ffffff;

  /* --- the outlined field, and the glass surfaces --------------------- */

  /* The field's three states are three tokens rather than three rules, so a
     theme only has to restate the colours. */
  /* Not --border-strong: an input outline is an essential UI component and
     owes 3:1 against the surface behind it. --border-strong measures 1.90 on
     white, which is fine for a card edge and not fine for the box somebody
     has to find and type into. This one measures 3.38. */
  --field-line: #828a9b;
  --field-line-hover: var(--fg-muted);
  --field-line-focus: var(--coral-ink);
  --field-label: var(--fg-muted);
  --field-label-focus: var(--coral-ink);  /* 7.51:1 as text on white */

  /* The two faint washes on the page ground. Far too weak to read as colour;
     they exist so a translucent card has something to be translucent OVER. */
  --wash-a: rgba(207, 206, 254, 0.20);
  --wash-b: rgba(196, 234, 251, 0.18);

  /* Cards are translucent so that ground shows through them. They are
     deliberately NOT backdrop-filtered: a screen like the mark grid can carry
     thirty of these at once, and thirty blur layers is a real cost on the
     kind of machine a school office actually has. Blur is reserved for the
     two places where things genuinely overlap - the sticky bar and modals. */
  --glass: rgba(255, 255, 255, 0.72);
  --glass-strong: rgba(255, 255, 255, 0.88);
  --glass-border: rgba(255, 255, 255, 0.7);
  /* A modal is far more opaque than a card: what sits behind it is a dense
     screen of text, and a form has to be readable through nothing. It is also
     one of only two elements that get a real backdrop blur. */
  --modal-glass: rgba(255, 255, 255, 0.86);
  --glass-highlight: rgba(255, 255, 255, 0.9);

  /* --- shape --------------------------------------------------------- */
  /* The collapsed sidebar and the open one. 78 puts an icon's centre at 39px
     with the sidebar's own padding, which is what keeps the icons from moving
     as the panel opens - see the block at the end of office.css. */
  /* How far the sidebar panel is inset from the window on every side. The
     rail's reserved gutter is derived from it, so moving the panel cannot
     leave the gutter behind. */
  --nav-inset: 14px;
  --rail-open: 252px;

  --panel-shadow: 0 1px 2px rgba(32, 34, 42, 0.03), 0 6px 20px rgba(32, 34, 42, 0.06);
  --r-sm: 12px;
  --r: 20px;
  --r-lg: 28px;
  --r-pill: 999px;
  --shadow-sm: 0 1px 3px rgba(32, 34, 42, 0.05);
  --shadow: 0 4px 14px rgba(32, 34, 42, 0.06);
  --shadow-lg: 0 12px 30px rgba(32, 34, 42, 0.10);

  /* --- type ---------------------------------------------------------- */
  --font-latin: 'Fredoka', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  /* Vazirmatn first for everything, because it carries Latin too and keeps
     mixed Dari/English lines on one baseline. */
  --font: 'Vazirmatn', system-ui, 'Segoe UI', sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', monospace;
  --font-display: var(--font);

  --fs-xs: 12.5px;
  --fs-sm: 13.5px;
  --fs-base: 15.5px;
  --fs-md: 17px;
  --fs-lg: 21px;
  --fs-xl: 26px;
  --fs-2xl: 32px;

  --gap: 16px;
  --pad: 20px;
  --header-h: 62px;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Latin display face for headings in the English build only. In Pashto and
   Dari the headings stay Vazirmatn - Fredoka has no Arabic script at all, so
   using it there would silently fall back and change letterforms mid-page. */
:root[dir='ltr'] {
  --font-display: var(--font-latin);
}

/* Dark mode - same tokens, different values. Every rule below reads colour
   through these custom properties, so redefining the palette here re-skins
   the whole app without touching a single component rule.
   Neutral "space gray" rather than a dimmed version of the warm paper: a
   dark brown reads as a stain, a dark grey reads as a screen. The crayon
   accents stay vivid so it is recognisably the same product. */
:root[data-theme='dark'] {
  --bg: #14161c;
  --panel-shadow: 0 1px 2px rgba(0, 0, 0, 0.45), 0 8px 24px rgba(0, 0, 0, 0.32);
  --surface: #1c1f27;
  --surface-alt: #242832;
  --border: #2f3440;
  --border-strong: #424857;
  --fg: #eef0f4;
  --fg-muted: #b3b8c4;
  /* Measured, not eyeballed: the obvious mid-grey lands near 4.1:1 on
     --surface, under the floor. This clears it at 5.58. */
  --fg-subtle: #979dab;

  /* On a dark ground the vivid crayons already clear 4.5:1, so the -ink
     variants collapse back onto them and the palette stays bright. */
  --coral: #cfcefe;
  --coral-ink: #cfcefe;
  --coral-soft: rgba(207, 206, 254, 0.16);
  --teal: #b9f0e6;
  --teal-hover: #d2f6ef;
  --teal-soft: rgba(185, 240, 230, 0.16);
  --teal-deep: #11131a;
  --marigold: #fae27a;
  --marigold-ink: #fae27a;
  --marigold-soft: rgba(250, 226, 122, 0.16);
  --sky: #c4eafb;
  --sky-ink: #c4eafb;
  --sky-soft: rgba(196, 234, 251, 0.16);
  --grass: #bdf0d0;
  --grass-ink: #bdf0d0;
  --grass-soft: rgba(189, 240, 208, 0.16);
  /* The fail/absent/owed chip sets this as text on an 18% tint of itself,
     which is the tightest pairing in dark mode: 4.92:1 there, against 6.89
     on plain surface. It is the one chip on screen that tells a parent their
     child failed, so it is the one measured hardest. */
  --berry: #ffd0d6;
  --berry-ink: #ffd0d6;
  --berry-soft: rgba(255, 208, 214, 0.16);
  --violet: #cfcefe;
  --violet-ink: #cfcefe;
  --violet-soft: rgba(207, 206, 254, 0.16);

  --primary-hover: #e2e1ff;
  --on-marigold: #241f00;

  /* Dark ink on the light accents. Measures 8.5:1 or better on every fill
     these are used with. */
  --on-primary: #16162e;
  --on-secondary: #062521;
  --on-success: #06240f;
  --on-danger: #2c0308;
  --on-info: #04202e;

  --field-line: rgba(255, 255, 255, 0.24);
  --field-line-hover: rgba(255, 255, 255, 0.42);
  --field-line-focus: #cfcefe;   /* 5.97:1 on the dark card */
  --field-label: rgba(255, 255, 255, 0.62);
  --field-label-focus: #e2e1ff;  /* 6.77:1 */

  --wash-a: rgba(255, 129, 99, 0.08);
  --wash-b: rgba(69, 194, 180, 0.07);
  --glass: rgba(255, 255, 255, 0.055);
  --glass-strong: rgba(255, 255, 255, 0.085);
  --glass-border: rgba(255, 255, 255, 0.13);
  --modal-glass: rgba(34, 35, 40, 0.82);
  --glass-highlight: rgba(255, 255, 255, 0.12);

  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
  --shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 12px 30px rgba(0, 0, 0, 0.55);
}

/* ==========================================================================
   base
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  /* The gradient is what makes the glass legible AS glass. Over a flat fill a
     translucent card is just a slightly different flat fill. The two crayon
     washes are at 0.05-0.07, far too faint to read as colour - they exist so
     the card edges have something to catch. */
  background-color: var(--bg);
  background-image:
    radial-gradient(ellipse 80% 55% at 12% 0%, var(--wash-a) 0%, transparent 60%),
    radial-gradient(ellipse 70% 60% at 92% 100%, var(--wash-b) 0%, transparent 62%);
  background-attachment: fixed;
  color: var(--fg);
  font-family: var(--font);
  font-size: var(--fs-md);
  line-height: 1.55;
  /* Every column of marks, fees and dates lines up without per-table work. */
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  margin: 0 0 0.4em;
  font-family: var(--font-display);
  font-weight: 650;
  line-height: 1.3;
  letter-spacing: -0.01em;
}
h1 { font-size: var(--fs-xl); }
h2 { font-size: var(--fs-lg); }
h3 { font-size: var(--fs-md); }
h4 { font-size: var(--fs-base); }

p { margin: 0 0 0.75em; }
p:last-child { margin-bottom: 0; }

a {
  color: var(--primary);
  text-decoration: none;
}
@media (hover: hover) and (pointer: fine) { a:hover { text-decoration: underline; } }

button { font-family: inherit; font-size: inherit; }

/* Latin data inside RTL prose: student IDs, receipt numbers, phone numbers.
   Without an explicit direction the bidi algorithm reorders these and a
   parent reads the wrong ID back over the phone. */
.lat {
  font-family: var(--font-mono);
  direction: ltr;
  unicode-bidi: isolate;
  font-size: 0.92em;
  letter-spacing: 0.01em;
}

/* One focus treatment for everything keyboard-reachable. Staff tab through
   the mark grid all day, so the ring has to be obvious. */
:focus-visible {
  outline: 2.5px solid var(--primary);
  outline-offset: 2px;
}
/* Coral disappears against the deep teal sidebar. */
.nav :focus-visible,
.appbar :focus-visible { outline-color: #fff; }

/* Anchored to the start corner on purpose. Left at its static position, an
   absolutely positioned box with `white-space: nowrap` lands past the edge of
   an RTL page and drags the whole document sideways - horizontal scroll on a
   phone caused by an element nobody can see. */
.sr-only:not(:focus) {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
/* A skip link that stays invisible even when focused is decoration. */
.sr-only:focus {
  position: fixed;
  inset-block-start: 8px;
  inset-inline-start: 8px;
  z-index: 100;
  padding: 10px 16px;
  background: var(--surface);
  color: var(--fg);
  border: 2.5px solid var(--primary);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-lg);
}

/* ==========================================================================
   controls
   ========================================================================== */

label,
.label {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--fg-muted);
  margin-block-end: 6px;
}

input[type='text'],
input[type='password'],
input[type='number'],
input[type='date'],
input[type='tel'],
input[type='search'],
select,
textarea {
  width: 100%;
  padding: 11px 13px;
  font-family: inherit;
  font-size: 16px; /* 16px or iOS Safari zooms the page on focus */
  color: var(--fg);
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--r-sm);
  transition: border-color 0.12s var(--ease), box-shadow 0.12s var(--ease);
}

input::placeholder,
textarea::placeholder {
  color: var(--fg-subtle);
  opacity: 0.8;
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3.5px var(--primary-soft);
}

input:disabled,
select:disabled,
textarea:disabled {
  background: var(--surface-alt);
  color: var(--fg-subtle);
  cursor: not-allowed;
}

select {
  /* The chevron is a background image so it can sit on the correct side in
     both directions from one rule. Longhands only - the `background`
     shorthand would reset background-repeat and tile the icon. */
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23a08a5f' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 18px 18px;
  padding-inline-end: 36px;
}
[dir='rtl'] select { background-position: left 11px center; }
[dir='ltr'] select { background-position: right 11px center; }

textarea {
  min-height: 74px;
  resize: vertical;
}

.field { margin-block-end: 15px; }
.hint {
  font-size: var(--fs-xs);
  color: var(--fg-muted);
  margin-block-start: 5px;
}

/* --- the outlined field with a floating label -------------------------- */

/* Every text input, select and textarea in the app. The label starts inside
   the control and floats up into a gap in its own border once the field has
   focus or a value.

   The gap is a real <fieldset>/<legend>, not a label with a background colour
   masking the border. The usual trick needs a flat colour behind it, and the
   surfaces here are translucent - what is behind a field is the page
   gradient, not a solid. A legend cuts an actual hole. The fieldset is
   decoration only (aria-hidden); the <label> is what a screen reader reads.

   Every control carries a single-space placeholder, and that is load-bearing:
   :placeholder-shown is how the CSS knows the field is empty, so none of this
   needs JavaScript holding state. */
.gfield {
  position: relative;
  display: block;
}
.gfield > input,
.gfield > select,
.gfield > textarea {
  width: 100%;
  min-height: 58px;
  padding: 21px 14px 6px;
  border: none;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--fg);
  font: inherit;
  /* Under 16px, iOS Safari zooms the whole page when a field takes focus. */
  font-size: 16px;
}
.gfield > textarea {
  min-height: 92px;
  padding-block-start: 24px;
  resize: vertical;
}
/* The base input:focus rule further up gives every bare control a border and
   a 3.5px halo. Inside a .gfield the fieldset draws the border instead, so
   both of those have to go or the field gets TWO rings: the fieldset's, and a
   second one on the input sitting just outside it. That second ring is easy
   to miss when you are looking at the fieldset, and it is what makes a
   focused field look like it has a smudged double outline. */
.gfield > input:focus,
.gfield > select:focus,
.gfield > textarea:focus {
  outline: none;
  border-color: transparent;
  box-shadow: none;
}
.gfield > input::placeholder,
.gfield > textarea::placeholder { color: transparent; }

/* Chrome paints its own opaque box behind an autofilled field, and on a
   translucent surface that is the one thing that cannot be allowed to happen.

   The usual fix - a very long inset box-shadow in the surface colour - only
   trades one opaque slab for another: it looked like a solid square sitting
   behind the text while every other field on the screen was see-through.

   Chrome ANIMATES that background in, so a transition long enough never to
   arrive leaves the field genuinely transparent. The text colour still has to
   be forced, because -webkit-text-fill-color is not animated. */
.gfield > input:-webkit-autofill, .gfield > input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--fg);
  transition: background-color 100000s ease-in-out 0s;
  caret-color: var(--fg);
}
@media (hover: hover) and (pointer: fine) { .gfield > input:-webkit-autofill:hover {
  -webkit-text-fill-color: var(--fg);
  transition: background-color 100000s ease-in-out 0s;
  caret-color: var(--fg);
} }

.gfield__outline {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  padding-inline-start: 9px;
  border: 1.5px solid var(--field-line);
  border-radius: var(--r-sm);
  pointer-events: none;
  transition: border-color 0.16s var(--ease), box-shadow 0.16s var(--ease);
}
.gfield__outline legend {
  padding: 0;
  max-width: 0.01px; /* no gap until the label floats */
  font-size: 12px;
  visibility: hidden;
  white-space: nowrap;
  transition: max-width 0.14s var(--ease);
}
.gfield__outline legend span { display: inline-block; padding-inline: 6px; }

.gfield__label {
  position: absolute;
  inset-inline-start: 15px;
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  color: var(--field-label);
  font-size: 16px;
  font-weight: 500;
  pointer-events: none;
  white-space: nowrap;
  transition: top 0.14s var(--ease), font-size 0.14s var(--ease), color 0.14s var(--ease);
}
/* A textarea's label belongs at the top of the box, not halfway down it. */
.gfield--area .gfield__label { top: 26px; }

.gfield > :is(input, textarea):focus ~ .gfield__label,
.gfield > :is(input, textarea):not(:placeholder-shown) ~ .gfield__label,
.gfield--up .gfield__label {
  top: 1px;
  transform: none;
  font-size: 12px;
  font-weight: 600;
}
.gfield > :is(input, textarea):focus ~ .gfield__outline legend,
.gfield > :is(input, textarea):not(:placeholder-shown) ~ .gfield__outline legend,
.gfield--up .gfield__outline legend {
  max-width: 100%;
}
.gfield > :is(input, select, textarea):focus ~ .gfield__label { color: var(--field-label-focus); }
/* One line, and only one. A glow behind the border reads as a second, blurry
   outline rather than as emphasis, and at this radius it smudges the corners.
   The border alone is a strong enough focus indicator: it goes from a barely
   visible hairline to solid accent and from 1.5px to 2px, and because the
   outline is an absolutely positioned fieldset the width change costs no
   reflow. */
.gfield > :is(input, select, textarea):focus ~ .gfield__outline {
  border-color: var(--field-line-focus);
  border-width: 2px;
}
@media (hover: hover) and (pointer: fine) { .gfield:hover .gfield__outline { border-color: var(--field-line-hover); } }

/* A select always holds a value, so its label is floated from the start -
   :placeholder-shown never applies to one. */
.gfield--select .gfield__label { top: 1px; transform: none; font-size: 12px; font-weight: 600; }
.gfield--select .gfield__outline legend { max-width: 100%; }
.gfield--select > select {
  appearance: none;
  padding-inline-end: 36px;
  cursor: pointer;
}
.gfield--select::after {
  content: '';
  position: absolute;
  inset-inline-end: 15px;
  top: 58%;
  width: 9px;
  height: 9px;
  border-inline-end: 2px solid var(--fg-muted);
  border-block-end: 2px solid var(--fg-muted);
  transform: translateY(-50%) rotate(45deg);
  pointer-events: none;
}
.gfield > select option { background: var(--surface); color: var(--fg); }

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 11px 19px;
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: 650;
  line-height: 1.3;
  color: var(--fg);
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--r-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s var(--ease), border-color 0.12s var(--ease),
    transform 0.12s cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) { .btn:hover:not(:disabled) { background: var(--surface-alt); } }
.btn:active:not(:disabled) { transform: scale(0.97); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

.btn--primary {
  /* The reference's own indigo gradient, carrying dark ink rather than
     white: every swatch in that set is a pastel, and white on the darker
     stop is 2.6:1. The ink measures 5.96:1 there and 7.79 on the lighter
     stop, so the label holds all the way across the sweep. */
  background: linear-gradient(135deg, #e2e1ff 0%, #cfcefe 100%);
  border-color: transparent;
  color: #20222a;
  box-shadow: 0 4px 14px rgba(85, 81, 255, 0.28);
}
@media (hover: hover) and (pointer: fine) { .btn--primary:hover:not(:disabled) {
  background: linear-gradient(135deg, #ecebff 0%, #cfcefe 100%);
  border-color: transparent;
} }

.btn--solid {
  background: var(--secondary);
  border-color: var(--secondary);
  color: var(--on-secondary);
}
@media (hover: hover) and (pointer: fine) { .btn--solid:hover:not(:disabled) {
  background: var(--teal-hover);
  border-color: var(--teal-hover);
} }

.btn--ghost { background: transparent; border-color: transparent; }
@media (hover: hover) and (pointer: fine) { .btn--ghost:hover:not(:disabled) { background: var(--surface-alt); } }

.btn--danger {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--on-danger);
}
@media (hover: hover) and (pointer: fine) { .btn--danger:hover:not(:disabled) { opacity: 0.9; } }

/* Sub-navigation tabs (Attendance, Fees, Settings) mark themselves with
   aria-pressed. Keying the active look off that beats an inline style: one
   rule, it follows the theme, and it cannot go stale when a token is
   renamed - which is exactly how the previous inline version broke. */
.btn[aria-pressed='true'] {
  background: var(--secondary);
  border-color: var(--secondary);
  color: var(--on-secondary);
}

/* The active tab needs its own hover, exactly as --primary and --solid do.

   Without it, `.btn:hover` (0-3-0) outranks the rule above (0-2-0) and repaints
   only the BACKGROUND, leaving the pressed white label on --surface-alt cream:
   1.19:1, which is not dim, it is invisible. And it happens under the cursor,
   which is precisely where the user is looking. Measured 7.04:1 after. */
@media (hover: hover) and (pointer: fine) { .btn[aria-pressed='true']:hover:not(:disabled) {
  background: var(--teal-hover);
  border-color: var(--teal-hover);
  color: var(--on-secondary);
} }

.btn--sm { padding: 7px 13px; font-size: 14px; }
.btn--lg { padding: 13px 22px; font-size: 16.5px; }
.btn--block { width: 100%; }

/* A field the server rejected. Set by the modal's error handler and cleared
   the moment the person types, so it marks the box that needs attention
   without becoming a permanent decoration. Colour is not the only signal -
   the message names the field too, and aria-invalid is what a screen reader
   announces. */
input[aria-invalid='true'],
select[aria-invalid='true'],
textarea[aria-invalid='true'] {
  border-color: var(--danger);
  outline: 2px solid var(--danger-soft);
  outline-offset: 0;
}

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

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: var(--r-sm);
  border: 1.5px solid var(--border);
  background: var(--surface);
  color: var(--fg-muted);
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) { .icon-btn:hover { background: var(--surface-alt); color: var(--primary); } }
.icon-btn svg { display: block; }

/* --- segmented language switch ---------------------------------------- */

/* Radios, not buttons: a screen reader announces "1 of 3 selected" and the
   arrow keys work, which they do not for a row of divs. */
.segmented { display: flex; gap: 8px; }
.segmented input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.segmented label {
  flex: 1;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 12px;
  font-size: var(--fs-base);
  font-weight: 650;
  color: var(--fg-muted);
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color 0.12s var(--ease), background-color 0.12s var(--ease), border-color 0.12s var(--ease);
}
.segmented label::before {
  content: '';
  width: 14px;
  height: 14px;
  flex: none;
  border: 1.5px solid var(--border-strong);
  border-radius: 50%;
  transition: background-color 0.12s var(--ease), border-color 0.12s var(--ease), box-shadow 0.12s var(--ease);
}
.segmented input:checked + label {
  color: var(--primary);
  border-color: var(--primary);
  background: var(--primary-soft);
}
.segmented input:checked + label::before {
  border-color: var(--primary);
  border-width: 4px;
}
.segmented input:focus-visible + label {
  outline: 2.5px solid var(--primary);
  outline-offset: 2px;
}

/* --- language toggle (compact, for the bars) -------------------------- */

.langswitch {
  display: flex;
  border: 1.5px solid var(--border);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.langswitch button {
  border: none;
  background: var(--surface);
  padding: 8px 13px;
  font-size: 14.5px;
  font-weight: 700;
  color: var(--fg-muted);
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) { .langswitch button:hover { background: var(--surface-alt); } }
.langswitch button[aria-pressed='true'] {
  background: var(--secondary);
  color: var(--on-secondary);
}

/* ==========================================================================
   surfaces
   ========================================================================== */

/* A panel floats on the ground rather than being drawn onto it: no border,
   a wide radius and a soft shadow. One change here re-shapes every screen in
   the app, because every screen is built from these. */
.card {
  background: var(--surface);
  border: none;
  border-radius: var(--r);
  box-shadow: var(--panel-shadow);
}
.card__head {
  padding: 16px var(--pad);
  border-block-end: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.card__title {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 650;
}
.card__body { padding: var(--pad); }
.card__foot {
  padding: 14px var(--pad);
  border-block-start: 1px solid var(--border);
  background: var(--surface-alt);
  border-end-start-radius: var(--r);
  border-end-end-radius: var(--r);
}

/* --- badges ----------------------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  font-size: var(--fs-sm);
  font-weight: 700;
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  color: var(--fg-muted);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.chip--pass { background: var(--success-soft); color: var(--success); border-color: transparent; }
.chip--fail { background: var(--danger-soft); color: var(--danger); border-color: transparent; }
.chip--warn { background: var(--warning-soft); color: var(--warning-ink); border-color: transparent; }
.chip--info { background: var(--info-soft); color: var(--info); border-color: transparent; }

/* Colour never carries a state on its own - the dot is a second signal for
   anyone who cannot separate the green from the red. */
.chip__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.badge {
  display: inline-block;
  min-width: 24px;
  padding: 2px 9px;
  font-size: var(--fs-xs);
  font-weight: 700;
  text-align: center;
  border-radius: var(--r-pill);
  background: var(--primary);
  color: var(--on-primary);
}

/* --- avatars & the cubby stat cards -----------------------------------
   Shared: the roll sheet and the office report card use these too, so they
   cannot live in portal.css. */

.avatar {
  width: 44px;
  height: 44px;
  flex: none;
  border-radius: 50%;
  background: var(--coral-ink);
  color: var(--on-primary);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-weight: 700;
  overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.photofield {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-block-end: 15px;
}

/* Sized against what is actually stored, not by eye. image.js caps an upload
   at MAX_EDGE = 640px on the long side, so anything up to about 320px on
   screen is still crisp on a 2x phone display - these two are nowhere near
   that ceiling and can grow without going soft.

   --xl is the one the office sees immediately after an upload, and a face at
   84px is a thumbnail rather than a photograph you can check a child against.
   The font-size is raised with it: the initials placeholder is drawn at this
   size too, and left at --fs-xl it would rattle around inside the circle. */
.avatar--xl {
  width: 120px;
  height: 120px;
  font-size: 38px;
  border: 1.5px solid var(--border);
}

.avatar--lg { width: 96px; height: 96px; font-size: 30px; }


/* The cubby-peg stat cards again, so a parent's report card and the office's
   dashboard are visibly the same product. */
.statgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 22px 14px;
  margin-block: 26px 20px;
}
.stat {
  position: relative;
  padding: 20px 16px 16px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
}
/* Same repair as .tile__label in office.css: the selector this rule belonged
   to was removed with the peg it flipped, leaving `[dir='rtl'] .stat__label`
   as the only thing it matched. The label went unstyled in English. */
.stat__label {
  font-size: var(--fs-xs);
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 650;
  margin-block-end: 3px;
}
.stat__value {
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: 650;
  line-height: 1.2;
}
.stat--pass { background: var(--grass-soft); border-color: var(--grass); --peg: var(--grass); }
.stat--pass .stat__value { color: var(--success); }
.stat--fail { background: var(--berry-soft); border-color: var(--berry); --peg: var(--berry); }
.stat--fail .stat__value { color: var(--danger); }

/* A mark below the pass line is tinted AND carries a chip in the result
   column - never colour alone. */
td.mark--fail { color: var(--danger); font-weight: 700; }


/* ==========================================================================
   tables
   ========================================================================== */

/* Every wide table scrolls inside its own box. The page body never scrolls
   sideways - on a phone that is the difference between usable and not. */
/* The template floats a list on a shadow rather than ruling a box around
   it - the same treatment the cards already got. */
.tablewrap {
  overflow-x: auto;
  border: none;
  border-radius: var(--r);
  background: var(--surface);
  box-shadow: var(--panel-shadow);
  -webkit-overflow-scrolling: touch;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-base);
}

thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 13px 15px;
  text-align: start;
  font-size: 14px;
  font-weight: 500;
  color: var(--fg);
  background: var(--table-head);
  border-block-end: 1px solid var(--border);
  white-space: nowrap;
}

tbody td {
  padding: 13px 15px;
  border-block-end: 1px solid var(--border);
  vertical-align: middle;
}
tbody tr:last-child td { border-block-end: none; }
@media (hover: hover) and (pointer: fine) { tbody tr:hover td { background: var(--surface-alt); } }
/* A chosen row takes the brand tint, as the fees and teacher lists do. */
tbody tr[aria-selected='true'] td { background: var(--coral-soft); }

td.num, th.num { text-align: end; }
.table--tight td, .table--tight th { padding: 8px 12px; }

/* ==========================================================================
   layout helpers
   ========================================================================== */

.stack > * + * { margin-block-start: var(--gap); }
.row {
  display: flex;
  align-items: center;
  gap: 11px;
  flex-wrap: wrap;
}
.row--between { justify-content: space-between; }
.row--end { justify-content: flex-end; }
.grow { flex: 1 1 auto; min-width: 0; }

.grid { display: grid; gap: var(--gap); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }

.muted { color: var(--fg-muted); }
.subtle { color: var(--fg-muted); font-size: var(--fs-sm); }
.strong { font-weight: 700; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }

/* --- empty state ------------------------------------------------------ */

.empty {
  text-align: center;
  padding: 46px 20px;
  color: var(--fg-muted);
  font-size: var(--fs-md);
}
.empty__title {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 650;
  color: var(--fg);
  margin-block-end: 4px;
}

.spinner {
  width: 20px;
  height: 20px;
  border: 2.5px solid var(--border);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --- alerts ----------------------------------------------------------- */

.alert {
  display: flex;
  gap: 9px;
  padding: 12px 15px;
  border-radius: var(--r-sm);
  font-size: var(--fs-base);
  font-weight: 600;
}
.alert--error { background: var(--danger-soft); color: var(--danger); }
.alert--warn { background: var(--warning-soft); color: var(--warning-ink); }
.alert--info { background: var(--info-soft); color: var(--info); }
.alert--ok { background: var(--success-soft); color: var(--success); }

/* --- toast ------------------------------------------------------------ */

.toasts {
  position: fixed;
  inset-block-end: 20px;
  inset-inline-start: 50%;
  transform: translateX(var(--toast-shift, -50%));
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}
[dir='rtl'] .toasts { --toast-shift: 50%; }
.toast {
  padding: 11px 22px;
  border-radius: var(--r-sm);
  background: var(--fg);
  color: var(--bg);
  box-shadow: var(--shadow-lg);
  font-size: 14.5px;
  font-weight: 650;
  max-width: 24rem;
  animation: toast-in 0.18s var(--ease);
}
.toast--error { background: var(--danger); color: var(--on-danger); }
.toast--ok { background: var(--success); color: var(--on-success); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(12px); }
}

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

/* The same glass panel as the sign-in card: 28px corners, a translucent
   ground, an inner top highlight and a deep shadow.

   This is one of the two places real backdrop-filter is worth its cost - see
   the note on --glass. A modal is a single element that genuinely overlaps
   the page, so there is one blur layer, not thirty. The alpha is much higher
   here than on the login card (0.82 against 0.42) because what sits behind a
   modal is a dense screen of text, and a form has to be readable through
   nothing. */
dialog.modal {
  border: 1px solid var(--glass-border);
  padding: 0;
  border-radius: 28px;
  background: var(--modal-glass);
  backdrop-filter: blur(20px) saturate(1.25);
  -webkit-backdrop-filter: blur(20px) saturate(1.25);
  color: var(--fg);
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.4),
    inset 0 1px 0 var(--glass-highlight);
  max-width: min(46rem, calc(100vw - 32px));
  width: 100%;
  max-height: calc(100vh - 48px);
  /* The visible height on a phone, not the height with the address bar
     hidden, so the dialog's buttons are never under the browser's bar. */
  max-height: calc(100dvh - 48px);
  overflow: auto;
  overscroll-behavior: contain;
}

/* Where backdrop-filter is unavailable the panel has to stop being glass
   rather than become a transparent hole over the page behind it. */
@supports not (backdrop-filter: blur(2px)) {
  dialog.modal { background: var(--surface); }
}

dialog.modal::backdrop {
  background: rgba(30, 20, 10, 0.5);
}
:root[data-theme='dark'] dialog.modal::backdrop {
  background: rgba(0, 0, 0, 0.62);
}

/* ==========================================================================
   direction overrides

   The only rules that care which way the page reads: gradients and a couple
   of transforms, which have no logical equivalent in CSS.
   ========================================================================== */

@media print {
  .no-print { display: none !important; }
  body { background: #fff; }

  /* Printed pages always use light, dark-ink-on-white colours regardless of
     the theme on screen. Dark mode is for reading a monitor, not for pouring
     toner over a report card a family keeps. */
  :root[data-theme='dark'] {
    --bg: #ffffff;
    --surface: #ffffff;
    --surface-alt: #e6f4fa;
    --border: #e8ebf0;
    --fg: #20222a;
    --fg-muted: #5a5e69;
    --fg-subtle: #6e727d;
    --coral: #00719c;
    --teal: #157b69;
    --marigold: #856c00;
    --sky: #0375a8;
    --grass: #177e3d;
    --berry: #dd001c;
    --violet: #00719c;
    --coral-soft: #e6f4fa;
    --teal-soft: #e8fbf7;
    --marigold-soft: #fdf6d8;
    --sky-soft: #ecf9fe;
    --grass-soft: #e9fbf0;
    --berry-soft: #fff0f2;
    --warning-ink: #856c00;
  }
}

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

/* The family's portal credential on the student profile. Boxed and dashed so
   it reads as something to copy down rather than another data row. */
.accesscode {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  margin-block-end: 15px;
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--surface-alt);
}
.accesscode .lat {
  letter-spacing: 0.14em;
  color: var(--primary);
}

/* ------------------------------------------------------- light-mode chrome

   Four of the crayons - marigold, sky, grass, berry - keep their hue in both
   themes because they carry status: کامیاب / ناکام, present / absent / late,
   online / offline. But those same four tokens are also used decoratively,
   for the portal's search button, and in light mode that chrome is blue.

   The sidebar icons and the dashboard pegs are deliberately NOT in that list.
   They are how somebody finds Attendance without reading the label, so they
   keep one crayon per section in both themes - which means restating the ones
   whose tokens the palette re-pointed, here and at the end of office.css.

   So the decorative uses are re-pointed here and the meaningful ones are
   Scoped with :not([data-theme='dark']) rather than [data-theme='light'].
   theme.js always writes an explicit attribute, so in the running app the two
   are equivalent - but between first paint and theme.js running there is no
   attribute at all, and the tokens in :root are the light ones. The negative
   form covers that gap; the positive form would leave those few frames with
   light tokens and dark-theme chrome on top of them.

   The extra :root in the selector is what lets one block in app.css win over
   the plain .nav / .tile rules that office.css and portal.css declare later:
   it raises specificity rather than relying on file order. */


/* The portal's search panel used to have a light-mode override here, to keep
   its button from disappearing into the hero photograph behind it. That panel
   is dark in both themes now and carries its own button, so the override only
   out-specified it - a translucent tint where a solid pastel belonged. */

/* ======================================================================
   the timetable grid

   A week of lessons is the one table in this application that is read as a
   SHAPE rather than row by row: a teacher looks for the gap, the office
   looks for the double-booking. So the cells are equal-width tiles and the
   period column is the only thing allowed to be narrow, which keeps every
   day the same size and makes an empty slot visible from across the room.
   ====================================================================== */

table.tt {
  table-layout: fixed;
  min-width: 46rem;
}
table.tt thead th { text-align: center; }
table.tt thead th:first-child { text-align: start; width: 6.5rem; }
table.tt tbody th {
  width: 6.5rem;
  padding: 10px 12px;
  text-align: start;
  vertical-align: middle;
  border-block-end: 1px solid var(--border);
  background: var(--surface-alt);
}
table.tt tbody td {
  padding: 6px;
  vertical-align: stretch;
}
/* The row hover the other tables use would light up a whole week, which
   reads as a selection here rather than as a pointer. */
@media (hover: hover) and (pointer: fine) { table.tt tbody tr:hover td { background: transparent; } }

.tt-period__no {
  font-weight: 700;
  font-size: var(--fs-base);
  line-height: 1.2;
}
.tt-period__time {
  font-size: var(--fs-xs);
  color: var(--fg-subtle);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- a cell ------------------------------------------------------------- */

.tt-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  min-height: 4.6rem;
  padding: 9px 10px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface-alt);
  text-align: start;
  font: inherit;
  color: inherit;
  justify-content: center;
}
button.tt-cell { cursor: pointer; transition: border-color 0.12s var(--ease), background 0.12s var(--ease); }
@media (hover: hover) and (pointer: fine) { button.tt-cell:hover { border-color: var(--accent); background: var(--primary-soft); } }

.tt-cell__subject { font-weight: 700; line-height: 1.25; }
.tt-cell__teacher { font-size: var(--fs-xs); color: var(--fg-muted); }
.tt-cell__room {
  font-size: var(--fs-xs);
  color: var(--fg-subtle);
  font-variant-numeric: tabular-nums;
}

/* A free slot is drawn as an outline rather than left blank: blank reads as
   "the grid stops here", and the whole point of the gap is that something
   could go in it. */
.tt-cell--free {
  border-style: dashed;
  background: transparent;
  align-items: center;
  color: var(--fg-subtle);
}
button.tt-cell--add {
  font-size: 20px;
  font-weight: 400;
  opacity: 0;
}
button.tt-cell--add:focus-visible { opacity: 1; }
@media (hover: hover) and (pointer: fine) { button.tt-cell--add:hover { opacity: 1; } }
/* On a touch screen there is no hover to reveal it with. */
@media (hover: none) {
  button.tt-cell--add { opacity: 0.55; }
}

.tt-row--break th { background: var(--marigold-soft); }
.tt-break {
  text-align: center;
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--warning-ink);
  background: var(--marigold-soft);
  letter-spacing: 0.04em;
}

.tt-clashlist {
  margin: 0;
  padding-inline-start: 18px;
  font-size: var(--fs-sm);
  line-height: 1.7;
}

@media (max-width: 700px) {
  .tt-cell { min-height: 4rem; padding: 7px 8px; }
  table.tt tbody th, table.tt thead th:first-child { width: 5rem; }
}

/* Shared scrollbars for pages, sidebars, tables, dialogs and the parent portal. */
:root {
  --scroll-track: transparent;
  --scroll-thumb: #cbd0dc;
  --scroll-thumb-hover: #aab7c8;
}
:root[data-theme='dark'] {
  --scroll-track: transparent;
  --scroll-thumb: #485365;
  --scroll-thumb-hover: #687b90;
}
@media screen {
  * { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) var(--scroll-track); }
  /* Use the rounded inset handle where WebKit styling is supported. */
  @supports selector(::-webkit-scrollbar) {
    * { scrollbar-width: auto; scrollbar-color: auto; }
  }
  ::-webkit-scrollbar { width: 8px; height: 8px; }
  ::-webkit-scrollbar-track { background: var(--scroll-track); border-radius: 10px; }
  ::-webkit-scrollbar-thumb { background: var(--scroll-thumb); background-clip: padding-box; border: 2px solid transparent; border-radius: 10px; }
  @media (hover: hover) and (pointer: fine) { ::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); } }
  ::-webkit-scrollbar-corner { background: transparent; }
  ::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
}
@media (forced-colors: active) {
  * { scrollbar-color: auto; }
}

/* Homework cards are shared by staff and the family portal. */
.homework-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: 20px; }
.homework-actions { margin-bottom: 18px; }
.homework-card { border: 1px solid var(--border); border-radius: 18px; background: var(--surface); overflow: hidden; }
.homework-card .card__head { background: #e5f7fe; padding: 18px 22px; }
.homework-card:nth-child(3n+2) .card__head { background: #eeecff; }
.homework-card:nth-child(3n) .card__head { background: #fff4d2; }
.homework-card .card__body { padding: 22px; }
.homework-card .card__title { font-size: 16px; }
.homework-instructions { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 14px; line-height: 1.8; margin-block: 14px; }
.homework-subject { font-size: 12px; font-weight: 600; }
.homework-grid > article:not(.card) { padding: 22px; border-top: 4px solid #b7e9fb; }
:root[data-theme='dark'] .homework-card .card__head { background: #293f4b; }
:root[data-theme='dark'] .homework-card:nth-child(3n+2) .card__head { background: #39354e; }
:root[data-theme='dark'] .homework-card:nth-child(3n) .card__head { background: #48412c; }

/* Compact homework review, using the same cyan and lavender accents as the office. */
dialog.modal:has(.homework-review) { width: 720px; max-width: calc(100vw - 2rem) !important; background: var(--surface); }
.modal:has(.homework-review) .modal__head { padding: 20px 24px 12px; }
.modal:has(.homework-review) .modal__head h3 { font-size: 18px; }
.modal:has(.homework-review) .modal__head > button { width: 36px; min-width: 36px; height: 36px; padding: 0; }
.modal:has(.homework-review) .modal__body { padding: 0 24px 20px; }
.homework-review-title { font-size: 14px; color: var(--fg-muted); line-height: 1.6; margin-bottom: 18px; overflow-wrap: anywhere; }
.homework-checklist { border: 1px solid var(--border); border-radius: 16px; overflow: hidden; }
.homework-student { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 13px 16px; border-bottom: 1px solid var(--border); }
.homework-student:last-child { border-bottom: 0; }
.homework-student:nth-child(even) { background: var(--surface-alt); }
.homework-student-info { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.homework-student-name { font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
.homework-student-status { font-size: 11px; color: var(--fg-muted); }
.homework-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; flex: 0 0 224px; }
.homework-choice { display: flex; align-items: center; justify-content: center; min-height: 36px; padding: 7px 10px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); color: var(--fg); font: inherit; font-size: 12px; cursor: pointer; }
@media (hover: hover) and (pointer: fine) { .homework-choice:hover { border-color: #aaa5e7; } }
.homework-choice[aria-pressed='true'][data-status='done'] { background: #bfeefe; border-color: #9bd9ed; color: #254a58; }
.homework-choice[aria-pressed='true'][data-status='not_done'] { background: #dedaff; border-color: #c8c4ff; color: #45406a; }
.homework-choice:focus-visible { outline: 2px solid #8b81d5; outline-offset: 2px; }
:root[data-theme='dark'] .homework-choice[aria-pressed='true'][data-status='done'] { background: #293f4b; color: #bfeefe; border-color: #527386; }
:root[data-theme='dark'] .homework-choice[aria-pressed='true'][data-status='not_done'] { background: #39354e; color: #dedaff; border-color: #71688d; }
@media (max-width: 520px) {
  .homework-student { align-items: stretch; flex-direction: column; gap: 10px; }
  .homework-choices { flex-basis: auto; }
  .modal:has(.homework-review) .modal__body { padding-inline: 16px; }
}

/* Two copies of the logo. The default Lemar Maktab logo is navy, which
   vanishes on a dark ground, so where the ground can be dark the page
   carries both: the logo as drawn (--on-light) and /branding/logo?on=dark
   (--on-dark), which is a light copy of the default logo - or the school's
   own uploaded logo, unchanged. By default the theme decides which shows;
   office.css and sidebar-light.css decide it for the sidebar. */
.brand-logo.brand-logo--on-dark { display: none; }
:root[data-theme='dark'] .brand-logo.brand-logo--on-light { display: none; }
:root[data-theme='dark'] .brand-logo.brand-logo--on-dark { display: block; }

/* The light / dark switch (the user's design), built by Theme.switchEl() in
   theme.js: a 70 x 35 pill, white with an orange sun in light mode, slate
   with a white moon in dark, and a knob that bounces between the ends. The
   design placed its icons for a pill twice this size; here they are scaled
   and positioned to sit inside it. */
.theme-switch {
  position: relative;
  display: inline-block;
  flex: none;
  margin: 0;
  cursor: pointer;
  /* The knob's travel is drawn left to right; keep it so in Pashto and Dari
     too, or the knob would start on the wrong side of the pill. */
  direction: ltr;
}
.theme-switch .theme-switch__input {
  position: absolute;
  opacity: 0;
  cursor: pointer;
  height: 0;
  width: 0;
  margin: 0;
}
.theme-switch__slot {
  display: block;
  font-size: 10px;
  position: relative;
  height: 3.5em;
  width: 7em;
  border: 0 solid transparent;
  border-radius: 10em;
  background-color: white;
  /* A hairline so the white pill does not vanish on a white card. */
  box-shadow: 0 0 0 1px var(--border, #e2e7f2);
  transition: background-color 250ms;
  overflow: hidden;
}
.theme-switch__input:checked ~ .theme-switch__slot { background-color: #374151; box-shadow: none; }
.theme-switch__button {
  display: block;
  transform: translate(0.3em, 0.25em);
  position: absolute;
  height: 3em;
  width: 3em;
  border-radius: 50%;
  background-color: #fff1cc;
  box-shadow: inset 0 0 0 0.75em #ffb703;
  transition: background-color 250ms, border-color 250ms, transform 500ms cubic-bezier(.26,2,.46,.71);
}
.theme-switch__input:checked ~ .theme-switch__slot .theme-switch__button {
  background-color: #485367;
  box-shadow: inset 0 0 0 0.75em white;
  transform: translate(3.65em, 0.25em);
}
.theme-switch__sunwrap, .theme-switch__moonwrap {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  height: 1.8em;
  width: 1.8em;
  transform-origin: 50% 50%;
}
.theme-switch__sun, .theme-switch__moon { display: block; height: 100%; width: 100%; }
.theme-switch__sun { color: #ffb703; }
.theme-switch__moon { color: white; }
.theme-switch__sunwrap {
  opacity: 1;
  transform: translate(4.4em, 0.85em) rotate(15deg);
  transition: opacity 150ms, transform 500ms cubic-bezier(.26,2,.46,.71);
}
.theme-switch__input:checked ~ .theme-switch__slot .theme-switch__sunwrap {
  opacity: 0;
  transform: translate(5em, 0.85em) rotate(0deg);
}
.theme-switch__moonwrap {
  opacity: 0;
  transform: translate(2.4em, 0.85em) rotate(0deg);
  transition: opacity 150ms, transform 500ms cubic-bezier(.26,2.5,.46,.71);
}
.theme-switch__input:checked ~ .theme-switch__slot .theme-switch__moonwrap {
  opacity: 1;
  transform: translate(0.85em, 0.85em) rotate(-15deg);
}
/* A ring for keyboard users, who would otherwise see no sign of focus. */
.theme-switch__input:focus-visible ~ .theme-switch__slot { outline: 2px solid var(--focus-ring, #00719c); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .theme-switch__slot, .theme-switch__button, .theme-switch__sunwrap, .theme-switch__moonwrap { transition: none; }
}

/* ---------- language menu (I18n.menu in i18n.js) ----------
   A pill with a globe and the current language. Opening it turns the globe a
   half turn, flips the chevron, and brings the list in with a quick fade and
   scale from the button's edge; the options then rise in one after another
   and the current one shows its tick. Everything keys off [data-open]. */
.langmenu { position: relative; display: inline-block; flex: none; }
.langmenu-host { display: inline-flex; }
.langmenu__btn {
  display: inline-flex; align-items: center; gap: 8px;
  height: 38px; padding: 0 12px 0 10px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--fg);
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: border-color .2s, box-shadow .2s, background-color .2s;
}
@media (hover: hover) and (pointer: fine) { .langmenu__btn:hover { border-color: var(--border-strong); box-shadow: 0 4px 14px -6px rgb(16 24 40 / .25); } }
.langmenu__btn:focus-visible { outline: 2px solid var(--focus-ring, var(--primary)); outline-offset: 2px; }
.langmenu__globe { width: 18px; height: 18px; flex: none; color: var(--primary); transition: transform .7s cubic-bezier(.34, 1.56, .64, 1); }
.langmenu[data-open] .langmenu__globe { transform: rotate(180deg); }
@media (hover: hover) and (pointer: fine) { .langmenu__btn:hover .langmenu__globe { transform: rotate(180deg); } }
.langmenu__chev { width: 16px; height: 16px; flex: none; color: var(--fg-muted); transition: transform .3s cubic-bezier(.2, .8, .2, 1); }
.langmenu[data-open] .langmenu__chev { transform: rotate(180deg); }
.langmenu--up .langmenu__chev { transform: rotate(180deg); }
.langmenu--up[data-open] .langmenu__chev { transform: rotate(0deg); }

.langmenu__list {
  position: absolute; z-index: 60; inset-inline-start: 0; top: calc(100% + 8px);
  min-width: max(100%, 168px); margin: 0; padding: 8px; list-style: none;
  /* Room between the choices, so the highlighted one sits clear of its
     neighbours and of the panel's edge. */
  display: grid; gap: 4px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 18px 40px -14px rgb(16 24 40 / .3), 0 2px 6px rgb(16 24 40 / .08);
  opacity: 0; visibility: hidden;
  transform: translateY(-6px) scale(.95); transform-origin: top center;
  transition: opacity .16s ease, transform .2s ease, visibility 0s linear .2s;
}
.langmenu--up .langmenu__list { top: auto; bottom: calc(100% + 8px); transform: translateY(6px) scale(.95); transform-origin: bottom center; }
.langmenu[data-open] .langmenu__list {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .18s ease, transform .32s cubic-bezier(.2, .9, .3, 1.25), visibility 0s;
}
.langmenu__list li {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 14px; border-radius: 10px;
  color: var(--fg); font-size: 14px; font-weight: 500; cursor: pointer;
  opacity: 0; transform: translateY(6px);
  transition: background-color .15s, color .15s, opacity .2s ease, transform .28s cubic-bezier(.2, .9, .3, 1.2);
}
.langmenu[data-open] .langmenu__list li {
  opacity: 1; transform: none;
  transition-delay: 0s, 0s, calc(var(--i) * 50ms + 60ms), calc(var(--i) * 50ms + 60ms);
}
.langmenu__list li:focus { background: var(--tint-3, var(--surface-alt)); outline: none; }
@media (hover: hover) and (pointer: fine) { .langmenu__list li:hover { background: var(--tint-3, var(--surface-alt)); outline: none; } }
.langmenu__list li:focus-visible { box-shadow: inset 0 0 0 2px var(--focus-ring, var(--primary)); }
.langmenu__list li[aria-selected='true'] { color: var(--primary); font-weight: 700; }
.langmenu__check { width: 16px; height: 16px; flex: none; opacity: 0; transform: scale(.4) rotate(-20deg); transition: opacity .2s, transform .3s cubic-bezier(.34, 1.56, .64, 1); }
.langmenu__list li[aria-selected='true'] .langmenu__check { opacity: 1; transform: none; transition-delay: .2s; }

/* In the sidebar: the full width of the column, opening upwards. */
.langmenu--nav { display: block; }
.langmenu--nav .langmenu__btn { width: 100%; justify-content: flex-start; }
.langmenu--nav .langmenu__chev { margin-inline-start: auto; }

@media (prefers-reduced-motion: reduce) {
  .langmenu__globe, .langmenu__chev, .langmenu__list, .langmenu__list li, .langmenu__check { transition: none !important; }
}

/* ======================================================================
   On a phone (2026-10-07)

   The few platform-level details that make the app feel installed rather
   than like a website in a browser. Each one is here for a reason:

   - No grey flash over a tapped control; buttons give their own press
     feedback (.btn:active above).
   - No 300ms wait for a possible double-tap zoom on controls.
   - Holding a finger on a button or tab must not select its label. Text
     that is content - names, receipt numbers - stays selectable.
   - iPhones zoom the whole page into any field whose text is under 16px
     and leave it zoomed. On touch screens every field is 16px; desktop
     keeps its smaller sizes. Zoom itself is never disabled.
   - Hover styles live behind (hover: hover) and (pointer: fine)
     throughout the stylesheets, so a tapped button does not stay stuck in
     its hover colour.
   ====================================================================== */
html { -webkit-tap-highlight-color: transparent; }
:where(button, a, summary, label, select, [role='button'], [role='tab'], input[type='checkbox'], input[type='radio']) {
  touch-action: manipulation;
}
:where(button, .btn, [role='button'], [role='tab'], .chip, summary) {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
@media (pointer: coarse) {
  :root body :is(input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='color']), select, textarea) {
    font-size: 16px !important;
  }
}
