/* Shared stylesheet. Target: WCAG 2.2 Level AA for project-owned pages.
   Every length is relative so the layout reflows at 320 CSS px and at 200% zoom. */

/* Semantic layer of this repository, not approved identity tokens. The frozen
   primitive layer is `core/css/tokens.css`, which owns the `--bhc-*` names and
   is never edited here. These `--ui-*` aliases name the application roles the
   shared stylesheet already had. Task 040 gives every role that has a direct
   counterpart in the current approved Figma `034B · Foundations` and
   `BHC / Button` its canonical Light value, so this pass is a deliberate
   recolouring rather than a visual-neutral one. No alias takes a Dark value
   here, and no new hex is invented: hover aliases repeat their base role
   because the approved set defines no canonical hover state yet.

   Task 041 adds the canonical Dark half of the same roles, so the set of roles
   is identical in both themes and no role exists in only one of them.

   The rest stay implementation values of this repository and must not be
   documented as approved BHC identity tokens. `--ui-border-control` keeps
   `#6b7280` because the approved set gives no canonical accessible input
   border: Border Default is too faint to carry a control outline alone under
   SC 1.4.11, and Text Secondary is a text role. The warning and focus roles
   keep their values because the approved status colours are a separate
   integration step, and they are unthemed on purpose: every one of them is
   painted on its own light status surface, so it stays internally contrasting
   in Dark and is a deliberate temporary mismatch until the status task runs.

   Error text is the single exception, and it is not a status-system
   integration. `--ui-text-error` turned out to be one name serving two
   different contexts, which no single theme-dependent value can satisfy:
   the error containers paint it on their own light `--ui-surface-error`,
   while `.field__error` paints it as plain text straight onto the themed
   canvas. Theming the shared name would have fixed the second context and
   broken the first, so the role is split instead.

   `--ui-text-error` keeps one repository value `#721c24` and stays unthemed,
   because its remaining consumers always sit on `--ui-surface-error`
   `#fdf2f2` and keep their high contrast in both themes. `.field__error`
   moves to `--ui-text-error-inline`, the only alias this task adds: Light
   keeps `#721c24`, and Dark takes the canonical `status-danger` `#E16767`
   from the current approved Figma, because the Light value would otherwise
   sit on Canvas `#121212` at 1.70:1 and fail SC 1.4.3. `--ui-surface-error`,
   `--ui-border-error` and every warning and info role stay untouched, and no
   Dark error surface is introduced here. The success role was untouched by
   Task 041 too, and moved only in Task 043, below, when it gained consumers.

   Theme resolution contract, mirroring the selector shape of the frozen
   `core/css/tokens.css`:

     no `data-theme` + OS Light                 -> Light, `color-scheme: light`
     no `data-theme` + OS Dark                  -> Dark,  `color-scheme: dark`
     `data-theme="light"` whatever the OS says  -> Light, `color-scheme: light`
     `data-theme="dark"`  whatever the OS says  -> Dark,  `color-scheme: dark`

   `color-scheme` resolves through the same three selectors instead of a single
   global `color-scheme: light dark`, so an explicit `data-theme="light"` keeps
   native controls in the Light scheme even on a Dark OS. `data-theme` is only
   the foundation API here: Task 041 ships no switcher, no persistence and no
   JavaScript, and no template sets it as a user preference.

   Task 043 adds exactly one alias, `--ui-text-success`, and it is the only
   status role this task themes. It arrives now because this milestone is the
   first to have real consumers for it: the positive amount in a Student
   Activity Row and the `Active` participation status in Student More/Profile.
   Both values are confirmed by the current approved Figma — Light `#2F7D4A` in
   Student Home Compact `44:69`, Dark `#57B879` in the representative Student
   Dark `132:752` — so neither is invented and neither theme borrows the
   other's value. `warning`, `info`, `danger` and the remaining status roles are
   deliberately left alone: they still wait for the status-colour task.

   Task 046 adds the two remaining status roles this repository now has real
   consumers for: `--ui-text-danger` for the amount and the applied state of a
   penalty, and `--ui-text-caution` for a serious penalty still waiting for a
   Coordinator or Admin decision. Both appear on Teacher `Мої дії`, Teacher
   Action Detail and the Teacher Home preview.

   The Light values are the canonical ones and are read directly off the
   approved Figma `107:501` — `#B42318` for the penalty amount and its applied
   state, `#9A6100` for the pending state — where `#2F7D4A` and `#6D6A63` are
   the same values this file already carries for success and secondary text.

   The Dark values are canonical too, read off the Figma status variables and
   confirmed by the Dark representative `132:1775`: `status-danger` `#E16767`
   and `status-warning` `#E2A93B`. Neither theme borrows the other's value, and
   `#E16767` is not even a new colour here — it is the dark red this stylesheet
   already carries for `--ui-text-error-inline`.

   Neither Dark value is the frozen identity Gold: that primitive carries brand
   meaning rather than status meaning, and the status roles resolve through this
   semantic layer instead.

   The consumers live in the role stylesheets, not in this file. Role shell
   geometry stays out of the shared layer by the Task 042 contract, so the alias
   layer is declared once here and read from there. */
:root {
  color-scheme: light;

  --ui-surface-page: #F7F4EC;
  --ui-surface-input: #FFFFFF;
  --ui-surface-status: #FBF8F0;
  --ui-surface-error: #fdf2f2;
  --ui-surface-warning: #fffbeb;
  --ui-surface-status-danger: #fdf2f2;
  --ui-surface-status-warning: #fffbeb;

  --ui-text: #1A1A1A;
  --ui-text-heading: #1A1A1A;
  --ui-text-muted: #6D6A63;
  --ui-text-on-action: #FFFFFF;
  --ui-text-status: #1A1A1A;
  --ui-text-error: #721c24;
  --ui-text-error-inline: #721c24;
  --ui-text-success: #2F7D4A;
  --ui-text-danger: #B42318;
  --ui-text-caution: #9A6100;
  --ui-text-info: #2E6E9E;
  --ui-text-warning: #7c2d12;
  --ui-text-status-danger: #721c24;
  --ui-text-status-warning: #7c2d12;

  --ui-border-control: #6b7280;
  --ui-border-table: #DED8C9;
  --ui-border-error: #9b1c1c;
  --ui-border-info: #2E6E9E;
  --ui-border-warning: #b45309;
  --ui-border-status-danger: #9b1c1c;
  --ui-border-status-warning: #b45309;

  --ui-link: #1A1A1A;
  --ui-link-hover: #1A1A1A;
  --ui-action-surface: #1A1A1A;
  --ui-action-surface-hover: #1A1A1A;

  --ui-focus-ring: #b45309;
}

/* Forced Dark: wins over `:root` on equal specificity through source order. */
[data-theme="dark"] {
  color-scheme: dark;

  --ui-surface-page: #121212;
  --ui-surface-input: #1A1A1A;
  --ui-surface-status: #222222;
  --ui-surface-status-danger: #2B1E1E;
  --ui-surface-status-warning: #2C281C;

  --ui-text: #F5F1E8;
  --ui-text-heading: #F5F1E8;
  --ui-text-muted: #B8B2A8;
  --ui-text-on-action: #1A1A1A;
  --ui-text-status: #F5F1E8;
  --ui-text-error-inline: #E16767;
  --ui-text-success: #57B879;
  --ui-text-danger: #E16767;
  --ui-text-caution: #E2A93B;
  --ui-text-info: #7FB3D5;
  --ui-text-status-danger: #E16767;
  --ui-text-status-warning: #E2A93B;

  --ui-border-info: #7FB3D5;
  --ui-border-table: #3A3936;
  --ui-border-status-danger: #E16767;
  --ui-border-status-warning: #E2A93B;

  --ui-link: #F5F1E8;
  --ui-link-hover: #F5F1E8;
  --ui-action-surface: #F5F1E8;
  --ui-action-surface-hover: #F5F1E8;
}

/* System Dark: the same values, skipped when Light is explicitly forced. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --ui-surface-page: #121212;
    --ui-surface-input: #1A1A1A;
    --ui-surface-status: #222222;
    --ui-surface-status-danger: #2B1E1E;
    --ui-surface-status-warning: #2C281C;

    --ui-text: #F5F1E8;
    --ui-text-heading: #F5F1E8;
    --ui-text-muted: #B8B2A8;
    --ui-text-on-action: #1A1A1A;
    --ui-text-status: #F5F1E8;
    --ui-text-error-inline: #E16767;
    --ui-text-success: #57B879;
    --ui-text-danger: #E16767;
    --ui-text-caution: #E2A93B;
    --ui-text-info: #7FB3D5;
    --ui-text-status-danger: #E16767;
    --ui-text-status-warning: #E2A93B;

    --ui-border-info: #7FB3D5;
    --ui-border-table: #3A3936;
    --ui-border-status-danger: #E16767;
    --ui-border-status-warning: #E2A93B;

    --ui-link: #F5F1E8;
    --ui-link-hover: #F5F1E8;
    --ui-action-surface: #F5F1E8;
    --ui-action-surface-hover: #F5F1E8;
  }
}

/* This stack is the universal default and is deliberately left alone by
   Task 043. The Reception Display is a second document that loads this file
   without loading `core/fonts/inter.css`, and it must stay visually identical,
   so the UI family is opted into by the application shell rather than imposed
   from here: `core/fonts/inter.css` both ships the face and assigns the frozen
   family primitive, and only the documents that link it are affected.
   `font-synthesis` stays here, because it is a policy of the whole stylesheet
   and not of any one family. */
:root {
  color: var(--ui-text);
  background: var(--ui-surface-page);
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-synthesis: none;
}

* {
  box-sizing: border-box;
}

body {
  min-width: 20rem;
  margin: 0;
  background: var(--ui-surface-page);
  line-height: 1.5;
}

/* Shared content layout foundation (Task 042).

   Canonical breakpoint contract of the current approved handoff
   (`03_Codex/UI_IMPLEMENTATION_CONTRACT.md`), written in `rem` like every
   other length in this file, so a boundary follows the reader's text size
   instead of fighting it. At the browser default of 16px each one is exactly
   its canonical CSS pixel value:

     Compact    below 48rem      below 768
     Tablet     from  48rem      768 to 1023
     Expanded   from  64rem      1024 to 1279
     Desktop    from  80rem      1280 and up

   The legacy 40rem (640px) boundary of this stylesheet is gone as a
   responsive boundary and is not replaced by a second system behind it.

   `--layout-edge` is the shared horizontal edge of the content layer, and it
   is the only thing these boundaries move:

     Compact    1rem     16px   canonical, Figma 44:69
     Tablet     1.5rem   24px   canonical, Figma 39:3 and 62:3
     Expanded   1.5rem   24px   generic implementation default of this
                                repository, restated at its own boundary on
                                purpose. It is not a universal canonical
                                Figma gutter: the approved Expanded frames
                                carry role values (Coordinator 72:3 uses 20,
                                Admin 130:1277 uses 24), not a shared one, so
                                the role shell overrides this default later.
     Desktop    2rem     32px   canonical, Figma 46:134 and 78:3

   `--layout-content-max` is the canonical Desktop content width 70rem
   (1120px), confirmed twice by the approved Figma: Student Desktop 46:134 is
   96 rail + 32 + 1120 + 32 = 1280, and Admin Desktop 78:3 is 256 sidebar +
   32 + 1120 + 32 = 1440. This is the same 70rem the stylesheet already capped
   content at, reassigned to the new contract rather than dropped, so the
   content box stays 1120px wide wherever the container is wide enough.

   The layer is parent-relative: it fills whatever contains it, caps its own
   content box and centres the remainder. No rule here measures the viewport,
   so a future role shell can put this layer inside a workspace beside a rail
   or a sidebar without the container contract being rewritten. Until that
   shell exists the containing block is the body, so the layer simply takes
   the full available width and centres. Role geometry — rails, sidebars,
   bottom navigation, app shell heights — is deliberately absent: none of it
   is declared here as a shared value.

   The layer is a class the application shell opts into, not the bare `main`
   element, because the Reception Display is a second document that loads this
   stylesheet and is explicitly out of this contract. Its screen is a `main`
   with its own approved geometry, so an element-level layer would have given
   it an edge and a cap it must not have. The bare `main` rules below stay
   exactly what Reception already inherited, which is why `margin-inline` is
   declared in both places instead of only in the layer. */
:root {
  --layout-edge: 1rem;
  --layout-content-max: 70rem;
}

.layout-content {
  max-width: calc(var(--layout-content-max) + 2 * var(--layout-edge));
  margin-inline: auto;
  padding-inline: var(--layout-edge);
}

main {
  margin-inline: auto;
  padding-block: 2rem 3rem;
}

main:focus {
  outline: none;
}

.page-header {
  padding-block-start: 1rem;
}

/* Approved transparent UI monogram: no container tile and no recolouring.
   Only the height is set so the intrinsic ratio cannot be distorted. */
.page-header__mark {
  display: block;
  width: auto;
  height: 2.5rem;
  margin-block-end: 0.75rem;
}

/* Both marks are the approved artwork, so the theme picks a file instead of
   recolouring one: no CSS mask, no filter and no new geometry. A media query
   alone would not see an explicit `data-theme`, so the same three-selector
   contract drives the choice and exactly one mark is ever displayed. Both are
   decorative, so the hidden one adds nothing to the accessibility tree. */
.page-header__mark--dark {
  display: none;
}

[data-theme="dark"] .page-header__mark--light {
  display: none;
}

[data-theme="dark"] .page-header__mark--dark {
  display: block;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .page-header__mark--light {
    display: none;
  }

  :root:not([data-theme="light"]) .page-header__mark--dark {
    display: block;
  }
}

nav,
form,
section,
ul,
ol,
p {
  margin-block: 0 1rem;
}

nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  align-items: center;
}

/* Skip link: first focusable element, hidden until focused, above the header. */
.skip-link {
  position: absolute;
  z-index: 20;
  inset-block-start: 0;
  inset-inline-start: 0;
  padding: 0.75rem 1rem;
  transform: translateY(-120%);
  background: var(--ui-action-surface);
  color: var(--ui-text-on-action);
  font-weight: 700;

}

.skip-link:focus {
  transform: none;
}

h1,
h2,
h3,
h4 {
  color: var(--ui-text-heading);
  line-height: 1.15;
  text-wrap: balance;
}

h1 {
  margin: 0 0 1.5rem;
  font-size: clamp(1.75rem, 4vw, 2.5rem);
}

h2 {
  margin: 2rem 0 0.75rem;
  font-size: 1.35rem;
}

h3 {
  margin: 1.5rem 0 0.5rem;
  font-size: 1.1rem;
}

h4 {
  margin: 1.25rem 0 0.5rem;
  font-size: 1rem;
}

p {
  max-width: 70ch;
  overflow-wrap: anywhere;
}

a {
  color: var(--ui-link);
  text-decoration-thickness: from-font;
  text-underline-offset: 0.16em;
}

a:hover {
  color: var(--ui-link-hover);
}

button,
input,
select,
textarea {
  max-width: 100%;
  font: inherit;
}

button,
input:not([type="hidden"]),
select,
textarea {
  min-height: 2.75rem;
  border: 1px solid var(--ui-border-control);
  border-radius: 0.375rem;
}

button {
  min-width: 2.75rem;
  padding: 0.625rem 1rem;
  border-color: var(--ui-action-surface);
  background: var(--ui-action-surface);
  color: var(--ui-text-on-action);
  cursor: pointer;
}

/* A state repaints the whole tuple or none of it. Repainting the background
   alone left the foreground on its normal-state role, so any control that
   overrides one half of the pair — a variant with its own surface, or an `<a>`
   whose colour comes from the link role — hovered into text the colour of its
   own background. Both roles are named here so the pair can never separate. */
button:hover {
  border-color: var(--ui-action-surface-hover);
  background: var(--ui-action-surface-hover);
  color: var(--ui-text-on-action);
}

input:not([type="hidden"]),
select,
textarea {
  width: min(100%, 42rem);
  padding: 0.5rem 0.625rem;
  background: var(--ui-surface-input);
  color: inherit;
}

textarea {
  min-height: 7rem;
  resize: vertical;
}

label {
  display: block;
  margin-block-end: 0.375rem;
  font-weight: 650;
}

/* 24 CSS px control inside a label row that stays a 44 px activation target. */
input[type="checkbox"],
input[type="radio"] {
  width: 1.5rem;
  min-height: 1.5rem;
  margin-inline-end: 0.5rem;
  vertical-align: middle;
}

ul,
ol {
  padding-inline-start: 1.5rem;
}

li + li {
  margin-block-start: 0.75rem;
}

/* Shared accessible form renderer. */
.field {
  margin-block-end: 1.25rem;
}

.field--group {
  padding: 0.75rem 1rem;
  border: 1px solid var(--ui-border-control);
  border-radius: 0.375rem;
}

.field--group legend {
  padding-inline: 0.25rem;
  font-weight: 650;
}

.field__check-label {
  display: flex;
  align-items: center;
  min-height: 2.75rem;
  margin-block-end: 0;
  gap: 0.5rem;
}

.field__required {
  font-weight: 400;
}

.field__help {
  margin: 0 0 0.375rem;
  color: var(--ui-text-muted);
  font-size: 0.9375rem;
}

.field__error {
  margin: 0 0 0.375rem;
  color: var(--ui-text-error-inline);
  font-weight: 650;
}

.error-summary {
  margin: 0 0 1.5rem;
  padding: 0.75rem 1rem;
  border: 2px solid var(--ui-border-error);
  border-radius: 0.375rem;
  background: var(--ui-surface-error);
  color: var(--ui-text-error);
}

.error-summary__title {
  margin: 0 0 0.5rem;
  color: inherit;
  font-size: 1.1rem;
}

.error-summary__list {
  margin: 0;
}

.error-summary__list a {
  color: var(--ui-text-error);
}

/* Data tables keep native semantics; only the wrapper scrolls. */
.table-scroll {
  max-width: 100%;
  margin-block-end: 1rem;
  overflow-x: auto;
}

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

caption {
  margin-block-end: 0.5rem;
  font-weight: 650;
  text-align: start;
}

th,
td {
  padding: 0.625rem;
  border-block-end: 1px solid var(--ui-border-table);
  text-align: start;
  vertical-align: top;
  overflow-wrap: anywhere;
}

th {
  font-weight: 700;
}

.errorlist,
[role="alert"]:not(.error-summary) {
  margin: 0.5rem 0;
  padding: 0.625rem 0.75rem;
  border-inline-start: 0.25rem solid var(--ui-border-error);
  background: var(--ui-surface-error);
  color: var(--ui-text-error);
  font-weight: 600;
}

.errorlist {
  list-style: none;
  padding-inline-start: 0.75rem;
}

.offline-notice,
.offline-message {
  padding: 0.625rem 0.75rem;
  border-inline-start: 0.25rem solid var(--ui-border-warning);
  background: var(--ui-surface-warning);
  color: var(--ui-text-warning);
  font-weight: 600;
}

/* The live regions stay in the DOM while empty and take no visual space. */
.offline-notice:empty,
.offline-message:empty {
  margin: 0;
  padding: 0;
  border: 0;
}

.offline-message {
  margin: 0 0 1rem;
}

.status-badge {
  display: inline-block;
  padding: 0.2rem 0.55rem;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: var(--ui-surface-status);
  color: var(--ui-text-status);
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1.4;
}

/* Pagination links are standalone controls, so they carry a 44 px target. */
.pagination a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding-inline: 0.75rem;
}

/* One focus indicator, offset onto the page background so its 3:1 contrast does
   not depend on the colour of the control it surrounds. */
:is(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--ui-focus-ring);
  outline-offset: 3px;
}

/* The privileged shell carries an approved accent of its own, so it may tint
   the indicator — and only tint it. Width, style and offset stay the ones
   above, the semantic role stays declared once, and the accent arrives through
   the shell's own token layer rather than as a brand value assigned to a role
   here. A shell that names no accent falls back to the shared role, so this
   costs the other shells nothing. */
.privileged-shell
  :is(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline-color: var(--privileged-focus-ring, var(--ui-focus-ring));
}

/* Compact only: the tighter vertical rhythm and the full-width controls this
   stylesheet already had, moved off the legacy 640px boundary onto the
   canonical Compact bound. Range syntax keeps 48rem one shared boundary
   instead of pairing it with an approximate 47.9375rem twin. */
@media (width < 48rem) {
  main {
    padding-block: 1.25rem 2rem;
  }

  button,
  input:not([type="hidden"], [type="checkbox"], [type="radio"]),
  select,
  textarea {
    width: 100%;
  }
}

/* Tablet and up: canonical 24px shared edge. */
@media (width >= 48rem) {
  :root {
    --layout-edge: 1.5rem;
  }
}

/* Expanded: the same 24px, declared at its own boundary as this repository's
   generic implementation default and not as a canonical Figma value. The
   declaration site is what the future role shell overrides per role. */
@media (width >= 64rem) {
  :root {
    --layout-edge: 1.5rem;
  }
}

/* Desktop: canonical 32px shared edge around the canonical 1120px content. */
@media (width >= 80rem) {
  :root {
    --layout-edge: 2rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
