/* ---------------------------------------------------------------------------
   Ehituspäevik v2 — design tokens and application shell.

   Load order matters and is fixed in index.html:
     vendor/vuetify.min.css  →  vendor/mdi.css  →  app.css
   Everything here is allowed to override Vuetify; nothing here may depend on
   Vuetify class internals beyond the handful of `.v-*` rules at the bottom,
   which are deliberate and annotated.
--------------------------------------------------------------------------- */

/* --- Tokens -------------------------------------------------------------- */
/* Colour, spacing and radius values are fixed by the plan — do not re-pick
   them. The same values are handed to createVuetify() in app.js so components
   and our own CSS never disagree about a colour. */
:root {
  --ground:    #F1F0EB;  /* concrete dust: a warm grey, not a default white */
  --surface:   #FFFFFF;
  --surface-2: #E7E5DE;
  --line:      #D4D1C8;
  --ink:       #1D2023;
  --ink-2:     #55595C;
  --ink-3:     #85888A;
  --accent:    #1B5E8C;  /* surveyor blue: blueprints and instruments */
  --ok:        #2E7150;
  --warn:      #8A6410;
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  --radius: 8px;

  /* Names a value the plan already fixed twice — the amber fill of an incomplete
     day (.mcell.partial) and of its legend chip. Anything else that has to read
     as "attention, not failure" uses the token instead of repeating the hex.
     The two plan-fixed rules keep their literals on purpose: those lines are
     quoted from the plan and should stay diffable against it. */
  --warn-soft: #F3E8CC;

  /* Type scale. Five sizes, three weights — chosen here because the plan does
     not specify one. No web font is loaded: the box has no guaranteed internet
     for the browser, and a font that fails to arrive is worse than a system
     stack that always does.

     The scale exists to separate three things that the old interface rendered
     identically: a section heading, the label of a field, and the value in it.
     Heading = larger + 600. Label = smallest + 500 + uppercase + --ink-2.
     Value = base + 400 + --ink. Numbers = 500 + tabular figures. */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --t-xs:   12px;  --lh-xs:   16px;   /* field labels, chips, dense captions */
  --t-sm:   13px;  --lh-sm:   18px;   /* secondary text, table cells */
  --t-base: 15px;  --lh-base: 22px;   /* body, field values */
  --t-lg:   18px;  --lh-lg:   24px;   /* section and screen headings */
  --t-xl:   22px;  --lh-xl:   28px;   /* the one number a screen is about */
  --w-normal: 400;
  --w-medium: 500;
  --w-bold:   600;

  /* Motion. Every transition in this app must use one of these, so that the
     reduced-motion block below can neutralise all of them in one place. */
  --dur-fast: 90ms;
  --dur:      140ms;
}

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

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--t-base);
  line-height: var(--lh-base);
  font-weight: var(--w-normal);
  color: var(--ink);
  background: var(--ground);
}

/* Digits that sit in columns — month cells, counters, quantities, dates — must
   align. Applied to the whole app rather than opted into per element, because
   the failure mode (one ragged column nobody notices) is silent. Proportional
   figures are not wanted anywhere in this interface. */
body,
input,
textarea,
select,
button {
  font-variant-numeric: tabular-nums;
}

* { box-sizing: border-box; }

/* The shell markup is an in-DOM template, so it exists in the document before
   Vue compiles it. Without this the raw `{{ }}` bindings flash on first paint. */
[v-cloak] { display: none; }

h1, h2, h3 { margin: 0; font-weight: var(--w-bold); }
h1 { font-size: var(--t-lg);  line-height: var(--lh-lg); }
h2 { font-size: var(--t-base); line-height: var(--lh-base); }
h3 { font-size: var(--t-sm);  line-height: var(--lh-sm); }

/* --- Typographic roles --------------------------------------------------- */
.t-label {
  font-size: var(--t-xs);
  line-height: var(--lh-xs);
  font-weight: var(--w-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.t-muted  { color: var(--ink-2); font-size: var(--t-sm); line-height: var(--lh-sm); }
/* --ink-3 on --surface is 3.6:1 — enough for a de-emphasised, non-essential
   marker (the plan uses it for non-working days), not enough for text anyone
   has to read. Do not use this class for values, labels or messages. */
.t-faint  { color: var(--ink-3); font-size: var(--t-sm); line-height: var(--lh-sm); }
.t-num    { font-weight: var(--w-medium); }
.t-figure { font-size: var(--t-xl); line-height: var(--lh-xl); font-weight: var(--w-bold); }

/* --- Interaction states -------------------------------------------------- */
/* Two separate obligations, and they are not the same one:
   hover proves the thing is clickable to a mouse, :focus-visible proves where
   the keyboard is. Anything interactive added by later tasks gets both. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}
/* Vuetify draws its own focus affordance as a translucent overlay, which is
   invisible against our light surfaces. Suppress it and use the ring instead. */
.v-btn:focus-visible,
.v-field:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.pressable {
  cursor: pointer;
  transition: background-color var(--dur-fast) linear,
              border-color var(--dur-fast) linear;
}
.pressable:hover  { background: var(--surface-2); }
.pressable:active { background: var(--line); }

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

/* --- Shell layout -------------------------------------------------------- */
/* One column: a header that stays, and a content region that scrolls.
   The header is not a v-app-bar — see the note at the bottom of this file. */
.shell {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
}

.shell-header {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.shell-brand {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
}
.shell-brand .mdi { font-size: 22px; color: var(--accent); }
/* The title is the way home (owner, 2026-09-25). A bare button reset, so it
   still looks like the title and only the hover/active steps of .pressable
   tell it apart. */
.brand-home {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s1) var(--s2);
  margin-left: calc(-1 * var(--s2));
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-radius: var(--radius);
  cursor: pointer;
}
.brand-home:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The object in the header is a caption now, not a control: flat header fill,
   no hover. */
.object-pill-static { background: var(--surface); cursor: default; }
.shell-title {
  font-size: var(--t-lg);
  line-height: var(--lh-lg);
  font-weight: var(--w-bold);
  letter-spacing: -0.01em;
  white-space: nowrap;
}

/* The selected object is the app's whole context: every screen after this one
   reads from it. It is therefore a control in the header, not a caption. */
.object-pill {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  min-width: 0;
  max-width: 46ch;
  /* Was `--s1 --s3`, which made a 30px-tall target for a gloved thumb on the
     only route from one object to another. Grown through the PADDING rather
     than a min-height because the row is baseline-aligned — extra height on a
     baseline flex container hangs below the text instead of centring it. */
  padding: var(--s2) var(--s3);
  font: inherit;
  text-align: left;
  color: var(--ink);
  /* --ground, not --surface. The header IS --surface, so the old white fill
     left a 1px hairline as the only thing separating this control from the
     title beside it — and a hairline is not an affordance. A recessed fill
     against white reads as a field you can act on, and it gives .pressable's
     hover (--surface-2) and active (--line) somewhere to travel: from --surface
     the hover step was the first visible change of any kind. */
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* The border is the outline of the control, so it is what darkens on hover —
   the fill step alone is a few percent of luminance and is the first thing
   direct sunlight takes away. Element-qualified, like `button.mcell` further
   down, so it can only ever reach the pressable variant and never the
   "Объект не выбран" span. */
button.object-pill:hover { border-color: var(--ink-3); }
/* Focus is louder here than the global 2px ring gives on its own: this control
   sits in a sticky header on a --surface band, and `outline-offset: 2px` puts
   the ring where the header's own bottom border already runs. The inner ring
   redraws the edge of the control itself, the same two-channel trick the month
   cells use to keep focus apart from selection. */
button.object-pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: inset 0 0 0 1px var(--accent);
  border-color: var(--accent);
}
/* Pushed to the far edge rather than tucked against the address: at ≤720px the
   pill spans the header and the chevron then lands where a select's arrow
   lands. `flex: 0 0 auto` keeps it from being squeezed out — the name and the
   address are the parts allowed to ellipsise, never the affordance. Centred
   explicitly because the row is baseline-aligned and an icon glyph has no
   baseline worth sharing with text. */
.object-pill-chev {
  flex: 0 0 auto;
  align-self: center;
  margin-left: auto;
  font-size: 20px;
  color: var(--ink-2);
}
/* "Объект не выбран" is a state the user must read, so it stays at --ink-2;
   --ink-3 would drop it below the contrast floor (see .t-faint). It carries no
   chevron and keeps the flat fill of the header: nothing here is pressable, and
   after the button above grew a real affordance the difference is the point. */
.object-pill[aria-disabled="true"] {
  color: var(--ink-2);
  cursor: default;
  background: var(--surface);
}
.object-pill[aria-disabled="true"]:hover { background: var(--surface); }
.object-pill-name {
  font-weight: var(--w-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.object-pill-addr {
  font-size: var(--t-sm);
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.shell-spacer { flex: 1 1 auto; }

.shell-main {
  flex: 1 1 auto;
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: var(--s5) var(--s4) var(--s6);
}

/* --- Surfaces ------------------------------------------------------------ */
/* A flat sheet of paper with a hairline. No elevation: shadows in this palette
   read as smudges, and depth is not carrying any meaning in this interface. */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4);
}
.panel + .panel { margin-top: var(--s4); }
.panel-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s3);
}

.stack   { display: flex; flex-direction: column; gap: var(--s3); }
.row     { display: flex; align-items: center; gap: var(--s3); }
.hr      { height: 1px; background: var(--line); border: 0; margin: var(--s4) 0; }

/* Placeholder used by the shell until Tasks 3-7 register real screens. */
.screen-stub {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  align-items: flex-start;
  color: var(--ink-2);
}

/* --- Toast --------------------------------------------------------------- */
/* Up to three messages, newest at the bottom, out of the way of the month grid.
   The old interface had a single snackbar, so an error raised while another was
   showing was never read (inventory 2.12); the cap and the queue live in
   store.js. Kept as plain markup so the reduced-motion rule above reaches it. */
.toast-wrap {
  position: fixed;
  left: var(--s4);
  bottom: var(--s4);
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  max-width: min(520px, calc(100vw - 2 * var(--s4)));
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  color: #fff;
  background: var(--ink);
  border-radius: var(--radius);
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
}
.toast.is-error   { background: #8C1D18; }
.toast.is-ok      { background: var(--ok); }
.toast.is-warn    { background: var(--warn); }
.toast-close {
  margin-left: auto;
  padding: 0 var(--s1);
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  opacity: 0.75;
}
.toast-close:hover { opacity: 1; }
/* The global focus ring is --accent, which against a toast's dark background is
   about 1.6:1 — a focus ring nobody can see is worse than none, because it reads
   as compliance. Toasts are the only dark surface in the app, so they get a
   light ring; anything dark added later must do the same. */
.toast :focus-visible {
  outline-color: #FFFFFF;
}

/* --- Vuetify reconciliation ---------------------------------------------- */
/* We keep Vuetify for overlays and form controls and drop its visual opinions:
   Material's font, its elevation, and its uppercase button labels — all three
   fight the flat, paper-like language of the tokens above. This block is the
   entire cost of that decision and is meant to stay this short. */
.v-application,
.v-overlay-container {
  font-family: var(--font) !important;
}
.v-application__wrap { min-height: 100dvh; }

.v-btn {
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--w-medium);
}
.v-card, .v-sheet, .v-menu > .v-overlay__content > .v-list {
  border-radius: var(--radius);
}
.v-list-item-title { font-size: var(--t-base); }

/* --- Controls ------------------------------------------------------------ */
/* A bare icon button — refresh, edit. Square, hairline, no Vuetify: v-btn's
   icon variant brings a 40px ripple target and a border-radius of its own that
   would sit oddly at the end of a 44px list row. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font: inherit;
}
.icon-btn .mdi { font-size: 18px; line-height: 1; }
.icon-btn:disabled { color: var(--ink-3); cursor: default; }
.icon-btn:disabled:hover { background: var(--surface); }

/* --- Objects screen ------------------------------------------------------ */
/* A list to run an eye down, not a stack of posters: one row per object, four
   aligned columns, 44px tall. The old interface gave every object a v-card with
   five stacked caption lines, so four objects filled the screen and comparing
   two contract numbers meant scrolling. */
.obj-table { display: flex; flex-direction: column; }

/* The header strip and the row content share one column template, and the whole
   point of the layout is that the two line up. They are not the same box: the
   row is a flex pair of the object button and a 32px edit button, so the button
   is narrower than the header by (gap + button). The header reserves exactly
   that much on its right, and the two paddings below are the ONLY reason the
   columns agree — change one and change the other. */
:root { --obj-trail: calc(32px + var(--s2)); }

.obj-head,
.obj-open {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 2fr) minmax(0, 8rem) minmax(0, 7rem);
  align-items: center;
  gap: var(--s3);
  min-width: 0;
}
.obj-head {
  /* left padding == .obj-open's; right == .obj-open's plus the trailing button */
  padding: 0 calc(var(--s2) + var(--obj-trail)) var(--s1) var(--s2);
  border-bottom: 1px solid var(--line);
}

.obj-row {
  display: flex;
  align-items: center;
  gap: var(--s2);
  border-bottom: 1px solid var(--line);
}
.obj-row:last-child { border-bottom: 0; }

.obj-open {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--s2);
  min-height: 44px;
  font: inherit;
  color: inherit;
  text-align: left;
  background: none;
  border: 0;
  border-radius: var(--radius);
}
.obj-name {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-weight: var(--w-medium);
  min-width: 0;
}
.obj-name,
.obj-addr,
.obj-leping {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.obj-addr,
.obj-leping { font-size: var(--t-sm); color: var(--ink-2); }

/* The one thing on this row that is not information but a warning: this card
   will put [VAJA TÄPSUSTADA] on a signature line. --warn, not --accent: it is
   not an action. */
.obj-gap { flex: 0 0 auto; font-size: 16px; color: var(--warn); }

.obj-status {
  justify-self: start;
  padding: 1px var(--s2);
  font-size: var(--t-xs);
  line-height: var(--lh-xs);
  color: var(--ink-2);
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: 999px;
  white-space: nowrap;
}
.obj-status.is-active { color: var(--ok); border-color: var(--ok); background: #DCEBE2; }

/* --- Object card (the form) ---------------------------------------------- */
.card-title {
  font-size: var(--t-lg);
  line-height: var(--lh-lg);
  font-weight: var(--w-bold);
  padding-bottom: var(--s2);
}
.card-lede { margin: 0 0 var(--s4); }

.fgrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s3);
  align-items: start;
}

.fsection {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}
.fsection > h3 {
  margin-bottom: var(--s3);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.fnote { margin: var(--s2) 0 0; }

/* What the generated document will actually say, shown beside the field that
   decides it. The point of this block is that a gap in the card is visible in
   the card: unknown values are marked, and a marking nobody sees until the xlsx
   is open is not a marking. */
.doc-note {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  margin-top: var(--s3);
  padding: var(--s2) var(--s3);
  background: var(--ground);
  border-radius: var(--radius);
}
.doc-note .t-label { flex: 0 0 auto; }
.doc-value {
  min-width: 0;
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
  overflow-wrap: anywhere;
}
.doc-value.is-missing { color: var(--warn); font-weight: var(--w-medium); }

/* --- Month screen -------------------------------------------------------- */
.month-nav {
  display: flex;
  align-items: center;
  gap: var(--s2);
}
.month-label {
  min-width: 11ch;
  text-align: center;
  font-weight: var(--w-bold);
}

/* The counter is the answer to "готово ли к сдаче" — the reason the screen
   exists — so it sits above the grid and is the largest thing on the page. */
.readiness {
  margin-bottom: var(--s4);
  padding: var(--s3) var(--s4);
  background: var(--ground);
  border-radius: var(--radius);
}
.readiness-line {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  margin: 0;
}
.readiness-of { color: var(--ink-2); }
.readiness-done { color: var(--ok); font-size: 20px; align-self: center; }
.readiness-hint { margin: var(--s2) 0 0; }

/* The chips repeat the cell colours and NAME them. That is deliberate: colour
   must never be the only channel carrying the state, and a separate legend box
   would be a second thing to read. One block does both jobs. */
.readiness-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s2);
}
.mchip {
  padding: 1px var(--s2);
  font-size: var(--t-xs);
  line-height: var(--lh-xs);
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  white-space: nowrap;
}
.mchip.is-filled  { color: var(--ok);   border-color: var(--ok);   background: #DCEBE2; }
.mchip.is-partial { color: var(--warn); border-color: var(--warn); background: #F3E8CC; }
.mchip.is-off     { color: var(--ink-2); border-style: dashed; }

.month-dow,
.month-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--s1);
}
.month-dow { margin-bottom: var(--s1); }
.month-dow > span { text-align: center; }

/* Semantic colour is separate from the interface accent: "needs attention" must
   read at a glance without competing with buttons and links. The five rules
   below are fixed by the plan — do not re-pick them. */
.mcell          { border: 1px solid var(--line); background: var(--surface); }
.mcell.filled   { border-color: var(--ok);   background: #DCEBE2; }
.mcell.partial  { border-color: var(--warn); background: #F3E8CC; }
.mcell.offday   { background: var(--ground); color: var(--ink-3); cursor: default; }
.mcell.selected { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Geometry and behaviour, kept out of the block above so the fixed values stay
   readable as the plan wrote them.

   Note what `.mcell.offday` does and does not override: it replaces the
   background but NOT the border-colour, so a Saturday that was actually worked
   still shows its green or amber edge on muted paper. That is the intended
   reading — muted because it is off-schedule, coloured because it is real and
   it will appear in the document. */
.mcell {
  position: relative;   /* anchors .mcell-paint */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s1);
  min-height: 52px;
  padding: var(--s1) var(--s2);
  font: inherit;
  color: inherit;
  text-align: left;
  border-radius: var(--radius);
  transition: background-color var(--dur-fast) linear,
              border-color var(--dur-fast) linear;
}
/* A padding cell of the previous or next month: no paper, no border, nothing to
   click. Same inertness as the old calendar's bare `null` cell. */
.mcell-void { background: none; border-color: transparent; }

.mcell-num  { font-weight: var(--w-medium); }
.mcell-note {
  font-size: var(--t-xs);
  line-height: var(--lh-xs);
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
/* --ink-3 is below the floor for text that has to be read (see .t-faint), and
   an off-schedule day that carries a record has something to read.

   Since every day inside the month became openable, an EMPTY Saturday is a
   press target too — the only way into «Внести день» for weekend work that was
   never dictated to the bot. Its number is now the label of a control, and a
   label nobody can read in sunlight is not a control. So the whole off-schedule
   group moves to --ink-2 and the muting is carried by the paper (--ground
   against the working day's white) and by the hairline border instead. The
   plan-fixed `.mcell.offday` rule above keeps its literal --ink-3 so it stays
   diffable against the plan; this is the override, not a rewrite of it. */
.mcell.offday.has-day,
.mcell.offday.has-day .mcell-note,
button.mcell.offday { color: var(--ink-2); }

/* The non-colour carrier of the state. Hidden while the caption is visible —
   the caption already distinguishes the three states in words — and revealed at
   ≤720px, where the caption does not fit. See the narrow-screen block. */
.mcell-mark { display: none; }

/* THE LANGUAGE MARKER, AND WHY IT IS NOT A COLOUR.

   The three cell colours answer "is this day complete". Russian text in a
   printed field is an orthogonal fault: the day can be perfectly complete and
   still be wrong for a document that goes to a municipality in Estonian. Given
   a fourth colour the two questions would compete, and the one already carried
   by colour is the one the screen exists for.

   So it is a small outlined badge in the top-right corner — present or absent,
   never a shade — and it survives on the narrow layout where the state marks
   (`✓` / `!`) also become visible, because it sits in a corner they do not use.
   The letters are Latin on purpose: `RU` is legible to a foreman who does not
   read Cyrillic, which is exactly the person who needs to notice it. */
.mcell { position: relative; }
.mcell-ru {
  position: absolute;
  top: 2px;
  right: 3px;
  font-size: 9px;
  line-height: 12px;
  font-weight: var(--w-bold);
  letter-spacing: 0.02em;
  padding: 0 3px;
  border-radius: 3px;
  color: var(--warn);
  background: var(--warn-soft);
  border: 1px solid var(--warn);
  pointer-events: none;
}

/* Only the cells rendered as <button> are interactive; the rest are <div> and
   get no hover, no pointer and no tab stop. `button.mcell.offday` needs the
   two-class specificity to win the cursor back from the fixed block above. */
button.mcell { cursor: pointer; }
button.mcell.offday { cursor: pointer; }
button.mcell:hover          { background: var(--surface-2); }
button.mcell.filled:hover   { background: #CBE2D5; }
button.mcell.partial:hover  { background: #ECDDB8; }
/* An off-schedule cell stays muted on hover: at rest its state is carried by
   the BORDER, not the fill (`.mcell.offday` replaces the background but not the
   border-colour), so tinting it here would contradict how it reads when idle.
   Spelled out at three-class specificity rather than left to source order —
   the single-class form tied this to the order of the two rules above it, and
   the next person to sort this block would have silently changed the design. */
button.mcell.offday.filled:hover,
button.mcell.offday.partial:hover,
button.mcell.offday:hover   { background: var(--surface-2); }
button.mcell:active         { background: var(--line); }

/* Keyboard focus must be distinguishable from selection, and both used the same
   2px accent outline: `:focus-visible` is (0,1,0) and `.mcell.selected` is
   (0,2,0), so tabbing onto the day you had already clicked changed nothing on
   screen. Focus wins here on specificity, and carries an inner ring the
   selected state does not have — so the four combinations of
   selected × focused are all told apart. */
button.mcell:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  box-shadow: inset 0 0 0 2px var(--surface), inset 0 0 0 4px var(--accent);
}

.month-foot { margin: var(--s3) 0 0; }

/* A button that reads as a link — used for "Повторить" inside a sentence. */
.linkish {
  padding: 0;
  color: var(--accent);
  background: none;
  border: 0;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

/* --- The month workspace: grid and day panel side by side ---------------- */
/* The plan is explicit that the day is edited BESIDE the month, not in a dialog
   over it. Two columns — and since 2026-09-15 it is the CALENDAR that has the
   ceiling, not the panel (owner: «календарь меньше, а раздел с данными больше»).
   The month grid is navigation: seven columns of numbers and a colour, which
   read fine at ~30rem. The day panel is where the work happens — eight works,
   a crew table, two material lists, photos — and every rem it gets is a line
   that does not wrap. The earlier layout had it the other way round.

   `minmax(0, ...)` on BOTH tracks is what keeps the page from ever scrolling
   sideways. Grid tracks are `auto`-minimum by default, so a long unbroken
   material line or a wide photo would push its column past the viewport and
   take the whole document with it. */
.workspace {
  display: grid;
  grid-template-columns: minmax(0, 30rem) minmax(0, 1fr);
  gap: var(--s4);
  align-items: start;
}
.workspace-grid,
.workspace-side { min-width: 0; }

/* --- Day panel ----------------------------------------------------------- */
/* --- Catalogue screen ---------------------------------------------------- */
/* A row is a line of text plus its labels and its verbs. It wraps rather than
   scrolls sideways: the position text is free-form and can be a whole sentence
   (`Vanad pööningu uksed eemaldamine, asendame tuletõkkeuksega…`), and clipping
   it would hide exactly the part that distinguishes two near-identical rows —
   which is the thing this screen exists to let someone see. */
.cat-list { list-style: none; margin: var(--s3) 0 0; padding: 0; }
.cat-row {
  display: flex;
  align-items: center;
  gap: var(--s2) var(--s3);
  flex-wrap: wrap;
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--line);
}
.cat-text { flex: 1 1 320px; overflow-wrap: anywhere; }
.cat-used { color: var(--ink-2); min-width: 2.5ch; text-align: right; }
/* Hidden means "not suggested", not "gone" — so the row is dimmed, never
   removed. Someone has to be able to find it again to un-hide it. */
.cat-row.is-hidden .cat-text { color: var(--ink-2); text-decoration: line-through; }

.day-panel { overflow-wrap: anywhere; }
.day-date  { color: var(--ink-2); }
.day-hint  { margin: 0; }

/* The head now carries an action as well as a title, and on a narrow screen the
   date, the state chip and the button do not fit on one line. Wrapping is the
   only option that keeps the page from scrolling sideways; the button is given
   the whole second line rather than being squeezed against the chip. */
.day-panel .panel-head { flex-wrap: wrap; row-gap: var(--s2); }
.day-save-top { margin-left: auto; }

.day-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s3);
}

/* Weather is shown as a fact — a line of values on paper — and only becomes a
   set of inputs when someone asks to change it. An empty text field labelled
   "°C" invites a number to be invented; a line reading "12:00 · 4 °C · Kuiv"
   states what is recorded, including that the temperature is not. */
.day-weather-fact {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  padding: var(--s2) var(--s3);
  background: var(--ground);
  border-radius: var(--radius);
}
.weather-edit {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: var(--s2) var(--s3);
  align-items: center;
  margin-top: var(--s3);
}

/* Sections carry the päevik's own numbering (7, 5, 4, 6, 9, 8, 10, 11-12), in
   the order the old dialog used. The numbers are not decoration: they are how
   the foreman and the supervisor refer to parts of the document out loud. */
.dsec {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}
.day-form > .dsec:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.dsec > h3 {
  margin-bottom: var(--s3);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.dsec-empty { margin: 0 0 var(--s3); }

.work-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s2);
  margin-bottom: var(--s3);
  align-items: start;
}
.work-main {
  grid-column: 1;
  grid-row: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 10rem);
  gap: var(--s2);
  min-width: 0;
}
.work-mtr { min-width: 0; }
/* The words that were actually dictated, under the formal phrasing they became.
   --ink-2, not --ink-3: this is text somebody reads to check a translation. */
.work-raw {
  grid-column: 1;
  grid-row: 2;
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  margin: var(--s1) 0 0;
  min-width: 0;
}
/* Two of these can follow one work — the dictated words and the author line —
   and both were pinned to row 2, so they drew on top of each other (seen
   2026-09-15 on Sillamäe 16.09). The second takes the next row. */
.work-raw + .work-raw { grid-row: 3; }
.work-del { grid-column: 2; grid-row: 1; }

.crew-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 5.5rem 8rem auto;
  gap: var(--s2);
  align-items: start;
  margin-bottom: var(--s3);
}
.crew-role,
.crew-count,
.crew-hours { min-width: 0; }

/* Validation, not failure: the save was refused before it left the browser, so
   it is the warning colour rather than the error one. */
.day-errors {
  margin: 0 0 var(--s4);
  padding: var(--s2) var(--s4);
  border-left: 3px solid var(--warn);
  background: var(--warn-soft);
  border-radius: var(--radius);
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
}
.day-errors > li + li { margin-top: var(--s1); }

/* §13: the day's KTA acts, read-only. One row per act — title, a muted state
   tag, and (owner/admin) the «Открыть» button. The title may wrap; the tag and
   the button never do. */
.act-list { margin: 0; padding: 0; list-style: none; }
.act-row {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s1) 0;
}
.act-row + .act-row { border-top: 1px solid var(--line); }
.act-title { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.act-state, .act-open { flex: 0 0 auto; }

.day-actions {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}

/* Square thumbnails, and under each one the thing no interface has shown until
   now: who sent it. `aspect-ratio` rather than a fixed height so the grid
   reflows with the column instead of cropping to one width. */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: var(--s3) var(--s2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.photo-item {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  min-width: 0;
}
.photo-item img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.photo-by { min-width: 0; }

/* --- The brush ----------------------------------------------------------- */
/* The bar spans both workspace columns: it governs the whole screen, not the
   grid alone, and it must be visible from wherever the eye is. */
.brush-bar {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
  padding: var(--s2) var(--s3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.brush-hint { margin: 0; }

/* A segmented control, not tabs: the leftmost segment is an explicit OFF
   position. Painting is a state you enter on purpose and can see you are in. */
.seg {
  display: flex;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.seg-btn {
  display: flex;
  align-items: center;
  gap: var(--s1);
  padding: var(--s1) var(--s3);
  color: var(--ink-2);
  background: var(--surface);
  border: 0;
  border-right: 1px solid var(--line);
  font: inherit;
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
  cursor: pointer;
}
.seg-btn:last-child { border-right: 0; }
.seg-btn:hover      { background: var(--surface-2); }
.seg-btn.is-on {
  color: var(--surface);
  background: var(--accent);
  font-weight: var(--w-medium);
}
.seg-btn.is-on:hover { background: var(--accent); }
.seg-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.brush-what {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  min-width: 0;
}
.brush-what b {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 28ch;
}
/* What the save will do, in front of the button that does it. */
.brush-preview { color: var(--ink-2); }
.brush-preview.is-wipe {
  color: var(--warn);
  font-weight: var(--w-medium);
}

/* The unsaved-marks guard. A strip, not a modal: the question is about the
   marks on the grid, so the grid has to stay visible while it is answered. */
.brush-guard {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
  margin-top: var(--s1);
  padding: var(--s2) var(--s3);
  color: var(--warn);
  background: #F3E8CC;
  border: 1px solid var(--warn);
  border-radius: var(--radius);
}

.linkish:disabled {
  color: var(--ink-3);
  text-decoration: none;
  cursor: default;
}

/* --- The grid as a painting surface -------------------------------------- */
.month-grid.is-painting { user-select: none; }
.month-grid.is-painting .mcell { cursor: cell; }

/* The mark rides ON TOP of the state colour instead of replacing it: the user
   is painting onto days whose fullness still matters. An inset accent ring
   reads at a glance without repainting the cell, and the ● carries the same
   fact without colour — the marked/unmarked distinction must survive both
   sunlight and colour blindness, exactly like the three states do.

   A disc rather than a tick, deliberately: below 720px the state mark (✓ / !)
   appears in the same cell, and two ticks a few pixels apart would be told
   apart only by colour and position. A disc shares no strokes with either. */
.mcell.paint-on {
  box-shadow: inset 0 0 0 3px var(--accent);
}
button.mcell.paint-off:hover { background: var(--surface-2); }
.mcell-paint {
  position: absolute;
  top: 2px;
  right: var(--s1);
  color: var(--accent);
  font-size: var(--t-xs);
  font-weight: var(--w-bold);
  line-height: 1;
}
/* Out of the brush's reach — an off-schedule day with no record. Hatched rather
   than merely dim, so "cannot be painted" does not read as "not painted yet".

   The cursor has to say it too, and now more than before: the cell is a
   <button> in every mode, so it hovers and focuses like the paintable ones and
   the `cursor: cell` set for the whole painting grid would promise a stroke
   that never lands. It refuses only the BRUSH — clicking it in normal mode
   still opens the day. */
.mcell.paint-block {
  background-image: repeating-linear-gradient(
    45deg, transparent, transparent 5px, var(--line) 5px, var(--line) 6px
  );
}
/* Written through `.month-grid.is-painting` on purpose: that is where
   `cursor: cell` comes from, it is (0,3,0), and a bare `.mcell.paint-block`
   would lose to it in silence. Same reason the off-schedule hover rules above
   are spelled out at full specificity instead of trusting source order. */
.month-grid.is-painting .mcell.paint-block { cursor: not-allowed; }

/* --- Multi-day selection (owner, 2026-10-03) ----------------------------- */
/* Three accent marks share these cells, so each has its own geometry:
     .selected   solid outline OUTSIDE the cell (the day open in the panel)
     .paint-on   solid inset ring + ● (brush mode only)
     .msel       dashed ring INSIDE + translucent tint + ■ (this)
   The tint is an overlay, so the state colour underneath still reads. A day
   can be both open in the panel and multi-selected; outline and ::after do
   not collide. Never shown while painting (the grid drops the class). */
.month-grid.is-selecting { user-select: none; }
.mcell.msel::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 2px dashed var(--accent);
  border-radius: inherit;
  background: rgba(27, 94, 140, 0.14);   /* --accent at 14% */
  pointer-events: none;
}
.mcell-pick {
  position: absolute;
  top: 2px;
  left: 50%;
  transform: translateX(-50%);
  color: var(--accent);
  font-size: 9px;
  line-height: 1;
  pointer-events: none;
}

.select-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
}
.add-list { max-height: 16rem; margin: 0; }
.brush-item.is-chosen { background: var(--surface-2); font-weight: var(--w-medium); }
.brush-item.is-chosen .mdi { color: var(--accent); }
.add-custom { margin-top: 0; }

/* Suggestions from past objects, under the catalogue search (brush panel and the
   add-to-days dialog). The hint sits under the text so a narrow phone keeps both. */
.past-title { margin: var(--s2) 0 0; font-size: var(--t-sm); color: var(--ink-2); }
.past-hint { display: block; font-size: var(--t-xs, 0.75rem); }
.past-list { margin-top: var(--s1); }

/* --- The brush panel ----------------------------------------------------- */
.brush-panel { overflow-wrap: anywhere; }
.brush-title  { margin: 0 0 var(--s1); }
.brush-src    { margin: 0 0 var(--s2); }
.brush-caveat,
.brush-howto  { margin: 0 0 var(--s3); }

.brush-list {
  list-style: none;
  margin: var(--s2) 0;
  padding: 0;
  max-height: 26rem;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.brush-list > li + li { border-top: 1px solid var(--line); }
.brush-item {
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: 100%;
  padding: var(--s2) var(--s3);
  color: inherit;
  background: none;
  border: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.brush-item-title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
}
.brush-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.brush-custom {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s3);
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}
.brush-custom > h3 { margin: 0; }
.brush-custom > .v-input { align-self: stretch; }

/* --- Month-wide operations: fill period, download the document ----------- */
/* One row above the whole month screen, brush bar included. These two act on
   the month as a unit — one writes many days at once, the other turns the month
   into the file that gets submitted — so they sit above everything the month
   screen contains rather than inside it. */
.month-screen > .docs-bar { margin-bottom: var(--s4); }

.docs-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
  padding: var(--s2) var(--s3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.docs-hint { margin: 0; }
/* Progress and result take the whole width so they cannot squeeze the buttons
   into wrapping when the file name is long. */
.docs-note {
  flex: 1 1 100%;
  margin: 0;
}

/* The same strip as the brush's own guard, and deliberately the same shape: it
   asks about the marks on the grid below, so it must not cover them. */
.docs-guard {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
  margin-top: var(--s1);
  padding: var(--s2) var(--s3);
  color: var(--warn);
  background: var(--warn-soft);
  border: 1px solid var(--warn);
  border-radius: var(--radius);
}

/* --- The fill-period dialog ---------------------------------------------- */
/* The consequence block sits directly under the two dates it is computed from,
   above the fields that shape it — a bulk write is read top-down and the
   number must be met before the form, not after it. */
.fill-scope {
  margin: var(--s4) 0;
  padding: var(--s3);
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.fill-scope.is-bad {
  color: var(--warn);
  background: var(--warn-soft);
  border-color: var(--warn);
}
.fill-headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: var(--s1) 0 0;
}
.fill-facts {
  margin: var(--s3) 0 0;
  padding-left: var(--s5);
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
  color: var(--ink-2);
}
.fill-facts > li + li { margin-top: var(--s1); }
/* Amber is "look at this before you press", not "this failed" — the same
   meaning it carries on an incomplete day in the grid. */
.fill-facts > li.is-warn {
  color: var(--warn);
  font-weight: var(--w-medium);
}
.fill-facts > li.is-warn .mdi { margin-right: var(--s1); }
/* What the interface does not know is stated in the same list as what it does,
   and marked as the different kind of statement it is. */
.fill-facts > li.is-unknown {
  color: var(--ink-2);
  font-style: italic;
}
.fill-rule { margin: var(--s3) 0 0; }
.fill-note { margin: var(--s2) 0 0; }

.fill-work,
.fill-crew {
  display: grid;
  gap: var(--s2);
  align-items: start;
  margin-bottom: var(--s2);
}
.fill-work { grid-template-columns: minmax(0, 1fr) minmax(0, 13rem) auto; }
.fill-crew { grid-template-columns: minmax(0, 1fr) minmax(0, 7rem) auto; }
.fill-work > .icon-btn,
.fill-crew > .icon-btn { margin-top: var(--s1); }

.fill-problems {
  margin: var(--s4) 0 0;
  padding: var(--s2) var(--s3) var(--s2) var(--s5);
  color: var(--warn);
  background: var(--warn-soft);
  border: 1px solid var(--warn);
  border-radius: var(--radius);
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
}

/* --- Narrow screens ------------------------------------------------------ */
/* The panel goes UNDER the grid at ~900px. Chosen by content, not by device:
   below this the two columns leave the month cells too narrow to read a date
   and a caption in, and the form's fields start wrapping their own labels. */
@media (max-width: 900px) {
  .workspace { grid-template-columns: minmax(0, 1fr); }
}
/* The plan does not ask for a phone layout, but it does ask that nothing break
   on one. The header wraps rather than producing a horizontally scrolling page. */
@media (max-width: 720px) {
  .shell-header { flex-wrap: wrap; gap: var(--s2); padding: var(--s2) var(--s3); }
  /* Full width on its own row — and that is where the pill reads best now that
     it carries a chevron: `margin-left: auto` parks the arrow on the right edge
     of the screen, which is exactly where a native select puts it. Nothing here
     needs to change for the chevron; this note exists so the next person does
     not "fix" the full-width rule to make room for it. */
  .object-pill  { max-width: 100%; flex: 1 1 100%; }
  .shell-main   { padding: var(--s4) var(--s3) var(--s5); }

  /* Four columns do not fit; the row folds into two lines rather than making
     the page scroll sideways. The column header stops describing anything at
     that point, so it goes away with them. */
  .obj-head { display: none; }
  .obj-open {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: var(--s1);
  }
  .obj-name   { grid-column: 1; }
  .obj-status { grid-column: 2; grid-row: 1; justify-self: end; }
  .obj-addr   { grid-column: 1 / -1; }
  .obj-leping { grid-column: 1 / -1; }
  .fgrid      { grid-template-columns: 1fr; }

  /* The grid stays seven columns wide — a month with fewer is not a month.
     `minmax(0, 1fr)` lets the cells shrink instead of pushing the page into a
     horizontal scroll, and the caption is dropped once there is no room to read
     it.

     THE CAPTION IS REPLACED, NOT JUST REMOVED. Dropping it and keeping only the
     fill would leave colour as the sole encoding of state — on the screen whose
     entire job is encoding state, on the device most likely to be held outdoors
     in sunlight, and #2E7150 against #8A6410 is close to indistinguishable under
     deuteranopia. So the mark takes over: ✓ for a filled day, ! for an
     incomplete one, nothing for an empty one. It is a plain character, not an
     icon-font glyph, so it cannot fail the way the caption's space did.

     The border weight is a second, independent channel: 2px on a day that has
     something, hairline on a day that has nothing. */
  .mcell        { min-height: 46px; padding: var(--s1); }
  .mcell-note   { display: none; }
  .mcell-mark   { display: block; font-weight: var(--w-bold); line-height: 1; }
  .mcell.filled,
  .mcell.partial { border-width: 2px; }
  .month-nav    { width: 100%; justify-content: space-between; }

  /* The day form's multi-field rows stack. A work's description and its MTR,
     and a crew row's role/чел/часов, are each too narrow to use at this width;
     stacking them is the only alternative to a page that scrolls sideways. */
  .work-main    { grid-template-columns: minmax(0, 1fr); }
  .crew-row     { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; }
  .crew-role    { grid-column: 1 / -1; grid-row: 1; }
  .crew-count   { grid-column: 1; grid-row: 2; }
  .crew-hours   { grid-column: 2; grid-row: 2; }
  .crew-del     { grid-column: 3; grid-row: 2; }
  .photo-grid   { grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr)); }

  /* Four segments plus their icons do not fit across a phone. The icons go
     first: they were reinforcement for the words, never the label itself. */
  /* The two month-wide buttons keep their labels — «Заполнить период» and
     «Скачать päevik.xlsx» are the two irreversible-looking things on the
     screen and an icon alone would not say which is which. What gives way is
     the sentence between them and the multi-field rows of the dialog. */
  .docs-hint    { display: none; }
  /* A work's description and its MTR are each too narrow to use side by side
     here, so the description takes the row and the MTR shares the next one with
     the button that removes the line. */
  .fill-work    { grid-template-columns: minmax(0, 1fr) auto; }
  .fill-desc    { grid-column: 1 / -1; }
  .fill-mtr     { grid-column: 1; }
  .fill-crew    { grid-template-columns: minmax(0, 1fr) minmax(0, 6rem) auto; }

  .seg          { width: 100%; }
  .seg-btn      { flex: 1 1 0; justify-content: center; padding: var(--s2) var(--s1); }
  .seg-btn .mdi { display: none; }
  .brush-what b { max-width: 100%; }
}

/* --- Вход -----------------------------------------------------------------
   Завеса, а не диалог: v-dialog закрывается по Esc и по клику мимо, и то и
   другое здесь означало бы «пропусти меня». Закрыть её можно единственным
   способом — войти. */
.login-veil {
  position: fixed;
  inset: 0;
  z-index: 2400;              /* выше шапки и выше тостов Vuetify */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s4);
  background: var(--ground);  /* непрозрачно: интерфейс за ней не подглядывается */
}

.login-panel { width: 100%; max-width: 380px; }

.t-err { color: var(--err, #8C1D18); font-size: var(--t-sm); line-height: var(--lh-sm); }

/* The brush: where the chosen position occurs, all months (2026-09-25). The
   month chips are buttons — they open that month — so they take the chip's look
   and a pointer, and the current month is the filled one. */
.brush-where { margin: var(--s3) 0; }
button.mchip { font: inherit; font-size: var(--t-xs); cursor: pointer; }


/* New dictations not yet reviewed (2026-09-26): a round badge in the lower-right
   corner — the RU badge has the upper one. Colour AND a number, so it survives
   without colour vision: blue new, amber worth a look, red will print wrong. */
.mcell-rv {
  position: absolute;
  bottom: 2px;
  right: 3px;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 7px;
  font-size: 9px;
  line-height: 14px;
  font-weight: var(--w-bold);
  text-align: center;
  color: #fff;
  pointer-events: none;
}
.mcell-rv.rv-ok      { background: var(--accent); }
.mcell-rv.rv-check   { background: var(--warn); }
.mcell-rv.rv-problem { background: #B3261E; }
.mcell-rv.rv-inline  { position: static; display: inline-block; min-width: 10px; height: 10px;
                       line-height: 10px; font-size: 7px; color: transparent; vertical-align: middle; }
.review-strip { margin: var(--s2) 0 var(--s3); }
.review-strip li { margin: var(--s1) 0; }


/* --- Photos and attachments of an act («7. Фото и приложения», 2026-10-09) -- */
/* Three tiles per row, two on a phone. A tile is a button (it opens the viewer)
   with the replace / delete buttons laid over its corner. Those two appear on
   hover and on keyboard focus, and are ALWAYS visible where there is no hover —
   a thumb cannot hover, so hiding them there would hide the feature. */
.kta-photos { border-radius: var(--radius); }
.kta-photos.is-drop { outline: 2px dashed var(--accent); outline-offset: 4px; }
.kta-file-input { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; pointer-events: none; }
.kta-photo-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s3);
  margin: 0;
  padding: 0;
  list-style: none;
}
.kta-photo { position: relative; min-width: 0; }
.kta-photo-open {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  overflow: hidden;
  cursor: zoom-in;
}
.kta-photo-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kta-photo-open img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.kta-photo-actions {
  position: absolute;
  top: var(--s2);
  right: var(--s2);
  display: flex;
  gap: var(--s1);
  opacity: 0;
  transition: opacity 0.12s;
}
.kta-photo:hover .kta-photo-actions,
.kta-photo:focus-within .kta-photo-actions { opacity: 1; }
@media (hover: none) {
  .kta-photo-actions { opacity: 1; }
}
.kta-photo.is-busy .kta-photo-open { opacity: 0.5; }
.kta-photo-spin { position: absolute; top: calc(50% - 12px); left: calc(50% - 12px); }

.kta-viewer-head { display: flex; align-items: center; gap: var(--s2); }
.kta-viewer-stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 12rem;
  padding: 0 var(--s3);
  background: var(--surface-2);
}
.kta-viewer-img {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - 14rem);
  object-fit: contain;
}
.kta-viewer-nav { position: absolute; top: calc(50% - 20px); z-index: 1; }
.kta-viewer-nav.is-prev { left: var(--s3); }
.kta-viewer-nav.is-next { right: var(--s3); }
.kta-viewer-meta { text-align: center; }
.kta-confirm-img { display: block; max-width: 100%; max-height: 40vh; margin: 0 auto; object-fit: contain; }

@media (max-width: 720px) {
  .kta-photo-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s2); }
  .kta-viewer-img { max-height: calc(100vh - 12rem); }
}

/* --- Import of acts from an xlsx workbook (2026-10-09) -------------------- */
/* Desktop is the target; the table scrolls sideways on a narrow screen rather
   than squeezing its columns. A row that cannot be imported is dimmed, and a
   row's warnings sit directly under it, so the border between the two is
   dropped and they read as one entry. */
.kta-import-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  padding: var(--s5) var(--s4);
  text-align: center;
  background: var(--surface);
  border: 2px dashed var(--line);
  border-radius: var(--radius);
}
.kta-import-drop > .mdi { font-size: 40px; line-height: 1; color: var(--ink-3); }
.kta-import-drop.is-drop { border-color: var(--accent); background: var(--surface-2); }
.kta-import-scroll { overflow-x: auto; }
.kta-import-table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); line-height: var(--lh-sm); }
.kta-import-table th {
  padding: var(--s2);
  font-size: var(--t-xs);
  font-weight: var(--w-medium);
  line-height: var(--lh-xs);
  text-align: left;
  color: var(--ink-2);
  white-space: nowrap;
  border-bottom: 1px solid var(--line);
}
.kta-import-table td { padding: var(--s2); vertical-align: top; border-bottom: 1px solid var(--line); }
.kta-import-table .c-check { width: 1%; padding-right: 0; }
.kta-import-table .c-num { text-align: right; font-variant-numeric: tabular-nums; }
.kta-import-table th.c-num { text-align: right; }
.kta-import-table tr.is-off td { color: var(--ink-2); }
.kta-import-table tr.has-sub td { border-bottom: 0; }
.kta-import-sub td { padding-top: 0; }
.kta-import-warn { display: inline-flex; align-items: center; gap: var(--s1); color: var(--warn); }
.kta-import-warn-list { margin: var(--s1) 0 0; padding: 0; list-style: none; color: var(--warn); }
.kta-import-warn-list li { margin: 2px 0; }
.kta-import-skipped ul { margin: var(--s1) 0 0; padding-left: 1.2em; }
.kta-import-head { font-weight: var(--w-bold); }
.kta-import-summary { font-weight: var(--w-medium); }
