/* ---------------------------------------------------------------------
   THE TYPE SCALE. Nine base steps, four desktop steps.

     base     11  12.5  13.5  15.5  18  21  28  30  40
     desktop  15.5  19  32  36  46      (inside min-width queries only)

   The diagnosis that produced this redesign named the old screen's twelve
   unrelated sizes -- two of them 0.8px apart, a 12.5px base times whatever
   number fell out -- as the reason it read unfinished. The approved mockup
   carried the same fault: 12 AND 12.5, 15 AND 15.5 AND 16, 14, 17.

   Those five are collapsed here (12->12.5, 14->13.5, 15->15.5, 16->15.5,
   17->18), every move 1px or less. What is NOT collapsed: 19, 32, 36 and 46
   look like one-offs but are the desktop halves of responsive pairs, and
   flattening them would remove a deliberate step rather than an accident.

   A new size needs a reason. If it is within 1px of a step above, it is not
   a reason.

   Enforced by TestEveryFontSizeIsOnTheTypeScale (css_typescale_test.go) as
   of 2026-09-15. Until then this was a comment and nothing else (audit L7),
   and it had already drifted: the table rules added later carried 13, 14,
   16, 17 and 22 -- every one of them within 1px of a step, and two of them
   (14->13.5, 17->18) collapses this very note claims to have made. They are
   collapsed now. Adding a step means adding it to the list above, where the
   next person has to read the reason.
   --------------------------------------------------------------------- */

/* trener -- "Teretana" (direction B), the approved mockup at
   _planning/trener/design/b-teretana-modes.html. One hue in two tones:
   --lime and --coral are FILLS ONLY and byte-identical in both schemes;
   --lime-ink / --coral-ink carry every text and stroke role and collapse
   onto the fill colour itself in dark, so a rule written once looks right
   in both schemes. Scheme switching is prefers-color-scheme only -- no
   picker in this product, and no data-theme override; that was the
   previous design's own mechanism and is not part of this one. System
   font stack only: no CDN, no self-hosted webfonts, and (fault 3) no
   monospace anywhere -- the mockup sets tabular-nums on the same sans
   stack instead of switching families for numerals. */

:root {
  --bg:   #e9ece2; /* page ground */
  --surf: #ffffff; /* card, panel, side block */
  --rais: #dde3d0; /* card header background, a selected calendar cell */
  --hair: #ccd3bd; /* a gap's own divider line -- decorative, no threshold */
  --seam: #d4dac6; /* a seam between siblings in a list -- decorative, no threshold */
  --ink:  #12161b;
  /* The old three-tier system (--ink, --ink-2, --ink-3) is gone. The
     mockup ships exactly two text tiers, and this is the second one --
     with real headroom (7.91:1 on --surf, 6.62:1 on --bg), not tuned to
     just clear the floor the way the old --ink-3 was. Every old --ink-2
     AND --ink-3 consumer now points here directly (pregled.js included,
     since Task 3 -- the transitional var() aliases those eight old names
     needed are gone along with the names themselves). */
  --ink2: #4b525a;
  --edge: #6e7668; /* a control's own boundary -- 3.94:1 on --bg, 4.71:1 on --surf */

  --lime:       #c6f24b; /* FILL ONLY. A lime fill is 1.30:1 on white, so every rule using it as a background also sets border-color: var(--lime-ink) -- see .btn-primary below. */
  --lime-ink:   #4e6808; /* TEXT and STROKE. Same hue (H76), darker tone. */
  --coral:      #ff7f5e; /* FILL ONLY, same rule as --lime. */
  --coral-ink:  #a1321a; /* TEXT and STROKE. Same hue (H12), darker tone. */
  --coral-wash: rgba(161, 50, 26, .10);
  --on-fill:    #0a0c0e; /* text on a lime or coral fill, in both schemes */

  --radius:    10px; /* buttons, inputs, tags -- the mockup's control radius */
  --radius-lg: 14px; /* panels, cards -- the mockup's own */

  --font-ui:  ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-num: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  color-scheme: light;
}

/* System dark mode -- prefers-color-scheme only, see the file banner. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:   #0a0c0e;
    --surf: #14181c;
    --rais: #1e242a;
    --hair: #14181c;
    --seam: #0a0c0e;
    --ink:  #f4f7fa;
    --ink2: #b4bec8;
    --edge: #5c666f; /* 3.05:1 on --surf */
    --lime:      #c6f24b;
    --lime-ink:  #c6f24b; /* collapses onto the fill -- see the note above */
    --coral:     #ff7f5e;
    --coral-ink: #ff7f5e;
    --coral-wash: rgba(255, 127, 94, .14);
    --on-fill:  #0a0c0e;
    color-scheme: dark;
  }
}

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

/* The base: the mockup's own 16px/1.5, not the old system's dense 12.5px
   (a 12.5px base multiplied by whatever number fell out is exactly how
   the old file ended up with twelve sizes and no scale -- see the type
   scale note above .day-date, further down). */
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15.5px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  -webkit-text-size-adjust: 100%;
  /* The sticky-footer column, on EVERY page rather than only the auth
     shell. body.auth had it first, for its own reason (giving .auth-main a
     real "rest of the viewport" box to centre a card inside), and its
     comment said plainly that every other page kept plain block flow. That
     was right until the footer existed: in block flow a short page -- a
     legal document on a wide screen, an empty roster, the acceptance
     screen -- left the footer directly under the content with the
     background running on beneath it, which reads as the page having
     stopped rather than ended.

     min-height twice on purpose: dvh is the correct unit (it excludes the
     phone browser's own collapsing chrome, where vh does not and leaves the
     footer a chrome's height below the fold), and vh above it is the
     fallback for anything that does not know dvh. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

h1 { font-family: var(--font-ui); font-size: 21px; font-weight: 500; letter-spacing: -.02em; margin: 0 0 1rem; }
h2 { font-size: 1rem; font-weight: 600; margin: 0; }
p { margin: 0 0 0.75rem; }

:focus-visible { outline: 3px solid var(--lime-ink); outline-offset: 2px; border-radius: 4px; }

a { color: var(--ink); }

/* Small section label: uppercase + tracking is fine for two or three words;
   anything sentence-length uses .say instead, which is not shouted or
   tracked. */
.lbl {
  display: block;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink2);
  line-height: 1.35;
  margin-bottom: 0.3rem;
}
/* Running text: sentences, hints, captions. */
.say {
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink2);
  line-height: 1.5;
}

/* Inline numeral wrapper: monospace, tabular, tight tracking, for a number
   embedded in an otherwise --font-ui line -- "preostalo 7", not "preostalo"
   itself set in monospace along with it. Mirrors the reference's own .num,
   used the same way inside .say. Colour and size come from the
   surrounding element (.pkg-remaining, .day-count, a gap row's label,
   etc.), not from .num itself. */
.num {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}

/* .tag -- a status label, not an interactive control: 11px, uppercase,
   tracked, with the border driven by currentColor so setting one colour
   sets both text and boundary -- never a fill, so it never needs the
   lime/coral-fill 2px border rule the buttons below do. Bare .tag (no
   modifier) is the neutral status -- "nema paketa" -- at --ink2, already
   proven at 4.5:1 (TestTextMeetsWCAG45); --coral-ink and --lime-ink colour
   it for the states the day screen actually needs to distinguish, already
   proven at the same floor as text roles. */
.tag {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 4px 8px;
  border-radius: 4px; /* the mockup has no tag of its own; 4px is its smallest radius anywhere (the chart bar's own rx) */
  border: 1px solid currentColor;
  color: var(--ink2);
}
.tag-open { color: var(--coral-ink); }       /* a no-show, and a lapsed package */
.tag-settled { color: var(--lime-ink); }     /* an attended session */
/* A live package used to get its own hue (--reserve, purple) -- the new
   system has exactly two hues (lime, coral), both already spoken for by a
   clear positive/negative meaning, and "a package is live" is neither, so
   this demotes to the same neutral --ink2 every other quiet tag uses
   rather than stretching lime or coral to cover a third meaning. */
.tag-reserve { color: var(--ink2); }

/* Header / nav -------------------------------------------------------- */

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: nowrap;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--hair);
  background: var(--bg);
}
/* The wordmark, the mockup's own .wm: 18px/800 with the "er" in
   --lime-ink (5.31:1 on --bg, 15.12:1 in dark -- TestAccentTextMeetsWCAG45
   already proves it). <em> is reset to upright: this is a colour change
   inside a word, not emphasis anyone should read as italic, and <em> is
   what the mockup marks it up with. */
.wm {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-ui);
  font-weight: 800;
  /* px, not rem: kept explicit even though body's base now matches the
     html root's own default, so a future change to either stays
     independent of the other -- see .day-date, further down, for the
     scale these sizes are drawn from. */
  font-size: 18px;
  letter-spacing: -.02em;
  text-decoration: none;
  color: var(--ink);
}
.wm em { font-style: normal; color: var(--lime-ink); }

/* The site footer (layout.gohtml's "site-foot", after every page's own
   </main> except the coach's acceptance screen -- page.HideFooter). Quiet
   on purpose: .say's own 12.5px/--ink2 pairing (already proven at 4.5:1 by
   TestTextMeetsWCAG45), not a fresh size the type-scale banner would have
   to grow for two lines of legal links. --hair for the top rule mirrors
   .app-header's own bottom one, so the two hairlines read as one system
   framing the page rather than two unrelated borders. Links inherit the
   same --ink2 rather than the page's base `a { color: var(--ink); }` --
   a footer link is a quieter thing than running content, on every page
   this renders on. */
.site-foot {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 0.75rem 1rem;
  border-top: 1px solid var(--hair);
  font-size: 12.5px;
  color: var(--ink2);
  /* Belt to .app-main's own flex-grow: if a page ever renders a main that
     does not grow, the footer still sits on the bottom edge rather than
     under the content. */
  margin-top: auto;
}
.site-foot a { color: var(--ink2); }

/* Everything on the right of the wordmark: the tab group and Odjava,
   which travel together so they wrap as one block onto a second row
   rather than splitting the header three ways on a phone. */
.app-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

/* The tab group. The mockup's own .tabs: no container chrome at all --
   the tabs are bare until one of them is current, at which point it alone
   picks up a lime fill. The previous .app-nav here was a segmented
   control with its own border and hairline dividers; that shape is gone
   with it, along with the flex/grid intrinsic-sizing history it carried,
   because a group of four bare tabs has no container to size at all.

   Tabs scroll horizontally rather than wrap: four Croatian tab labels
   plus Odjava do not fit on one 375px line, and a wrapped tab group reads
   as two unrelated rows. Overflow is on the group, so the wordmark and
   Odjava stay put while the tabs slide. */
.tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: 4px;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  /* The padding is the focus ring's room, and the negative margin gives
     it back so nothing moves. `overflow-x: auto` makes this a scroll
     container in BOTH axes -- CSS forces overflow-y to auto the moment
     overflow-x stops being visible -- and a scroll container clips its
     descendants' ink overflow, which is exactly what an outline is.
     Without this, :focus-visible's 3px ring at 2px offset needs 5px
     outside a tab's border box and gets none: a 44px tab in a 44px box
     has its ring sliced off top and bottom, and Kalendar and Postavke
     lose their outer edge too. Fault 2's own rule is that removing an
     underline must not cost the focus ring; neither may adding a
     scrollport. */
  padding: 6px;
  margin: -6px;
}
.tabs::-webkit-scrollbar { display: none; }
/* The 2px transparent border sits on every tab AT REST, so the current
   one gains its outline without moving a single pixel of the row. 13.5px,
   not the mockup's own 14px: the type scale collapses 14 onto 13.5 (see
   the note at the top of this file). */
.tabs a {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  white-space: nowrap;
  min-height: 44px;
  padding: 0 9px;
  border: 2px solid transparent;
  border-radius: 8px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink2);
  text-decoration: none;
}
/* A lime fill is 1.30:1 on --bg on its own, so the border in the darker
   brother of the same hue is what carries the boundary -- the same rule
   every other lime fill in this file follows, and the one
   TestLimeAndCoralFillsCarryTheirBorder checks. */
.tabs a.on {
  background: var(--lime);
  color: var(--on-fill);
  border-color: var(--lime-ink);
}
.tabs a:hover { color: var(--ink); }
.tabs a.on:hover { color: var(--on-fill); }

/* Odjava. The mockup has no logout because a mockup never logs in, so
   this is the one header control with no counterpart to copy. It takes
   the tab's exact shape and size -- so the row reads as one row, not a
   tab group with a button bolted on -- and the --edge boundary every
   other real control in this app carries, so it still reads as a control
   rather than a fifth destination. */
.tab-out {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  white-space: nowrap;
  min-height: 44px;
  padding: 0 9px;
  background: transparent;
  border: 2px solid var(--edge);
  border-radius: 8px;
  font-family: var(--font-ui);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink2);
  cursor: pointer;
}
.tab-out:hover { color: var(--ink); border-color: var(--ink2); }

/* On a phone the wordmark and five controls do not share one line, and
   .tabs' own overflow-x -- measured at 375px -- clips "Postavke" mid-word
   behind Odjava, which reads as damage rather than as "scroll me".
   Dropping the whole right-hand block to a second row does not fix it:
   four tabs plus Odjava measure ~378px against a 343px content box, so
   Postavke is still clipped.
   What fits is splitting the two apart. `display: contents` dissolves the
   .app-actions box below this width, so .tabs and the logout form become
   direct flex children of the header itself: Odjava keeps its place on
   the first row opposite the wordmark, and the tab group -- ~300px of
   four tabs, comfortably inside 343 -- takes the whole second row to
   itself. .tabs keeps its overflow-x as the safety net for a narrower
   viewport or a longer future label. */
@media (max-width: 30rem) {
  .app-header { flex-wrap: wrap; }
  .app-actions { display: contents; }
  .tabs { order: 3; flex: 1 0 100%; }
}

.logout-form { margin: 0; }

.app-main {
  max-width: 32rem;
  margin: 0 auto;
  padding: 1.25rem 1rem 3rem;
  /* Takes the slack so the footer lands on the bottom edge of a short page
     and gets pushed below the fold by a long one. `1 0 auto` rather than
     `1 1 auto`: this must never be allowed to SHRINK below its content,
     which is what would let a long document's own text overflow instead of
     extending the page. */
  flex: 1 0 auto;
  width: 100%;
}
/* 32rem (512px) was right for a phone-only single-column app; past a
   tablet/desktop viewport it crowds the day screen instead. Widened here,
   not made fully fluid, so a wide monitor gets more breathing room without
   turning the timeline into an unreadably long line-length. Nothing below
   this breakpoint changes -- a coach in a gym on a phone sees exactly the
   same layout as before.
   The extra width is for the shell (the day screen, the nav, the panels
   themselves), not for a lone form field: without a cap, .field's own
   width:100% (a plain <p>, not a flex row) and .field-row's flex-grow
   inputs would each stretch to fill the wider shell, turning "07:00" or a
   one-digit capacity into an absurdly wide, sparse-looking box -- the
   "fields ... misaligning" symptom the extra width itself would otherwise
   introduce. Capping both keeps every input a sane reading width
   regardless of how wide .app-main gets. */
@media (min-width: 48rem) {
  .app-main { max-width: 56rem; }
  input, select { max-width: 22rem; }
  .field-row input { max-width: 12rem; }
}

/* A negative/needs-attention notice -- coral, the same hue a lapsed
   package or a no-show uses, since an alert here is always something that
   needs the coach's attention. --coral-wash is the mockup's own token for
   exactly this tint, so this no longer needs to mix one from --coral-ink
   itself. */
.alert {
  background: var(--coral-wash);
  border: 1px solid var(--coral-ink);
  border-left-width: 3px;
  border-radius: var(--radius-lg);
  color: var(--ink);
  font-weight: 600;
  padding: 0.85rem 1rem;
  margin: 0 0 1rem;
}

/* .notice -- .alert's own box model (border-left accent, radius, weight,
   padding, margin), copied rather than reused via a shared class because
   this is not something gone wrong. Two consumers now: Postavke's own
   #tipovi confirmation after Ukloni/Vrati iz arhive, telling a coach which
   of obrisano/arhivirano/vraceno the ambiguous "Ukloni" button just did
   (sessionTypeNoticeText, coach_rules.go), rendered inside #tipovi itself
   since the coach lands there via its own redirect and a notice above
   <main> would be scrolled straight past; and page.Notice
   (layout.gohtml), Error's positive twin, at the top of <main> the same
   place .alert renders -- task 6 (email-plan.md)'s "E-pošta je poslana"
   after a successful invite send. Coral would say "something needs
   attention" about either kind of success, so both swap in --rais/--ink2
   instead -- proven at >=4.5:1 against --ink text (TestTextMeetsWCAG45
   covers ink/ink2 on bg/surf/rais), and --rais reads as a raised card
   against #tipovi's own --surf background the same way the day screen's
   card header already does. */
.notice {
  background: var(--rais);
  border-left: 3px solid var(--ink2);
  border-radius: var(--radius-lg);
  color: var(--ink);
  font-weight: 600;
  padding: 0.85rem 1rem;
  margin: 0 0 1rem;
}

/* Forms / panels -------------------------------------------------------
   Panels sit on --surf over the page's --bg ground. The mockup's own
   panel/card (.panel, .rail-b, .big, .card-s) carries no border at all --
   the background-on-background contrast (--surf on --bg) is what
   separates it, at 14px radius, not a hairline. */

.panel {
  background: var(--surf);
  border-radius: var(--radius-lg);
  padding: 1.25rem;
  margin: 0 0 1.25rem;
}
/* A group heading inside a panel: underlined in --ink, the reference's
   convention for "this heads what follows it". */
.panel-heading {
  padding-bottom: 0.4rem;
  margin-bottom: 0.6rem;
  border-bottom: 1px solid var(--ink);
}
.hint {
  font-family: var(--font-ui);
  font-size: 12.5px;
  color: var(--ink2);
  line-height: 1.5;
}

.field, .field-check { margin: 0 0 0.85rem; }

.field-check label {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 44px;
  font-family: var(--font-ui);
  font-size: 12.5px;
  color: var(--ink2);
}

/* Every interactive control boundary (inputs, selects, ghost buttons)
   uses --edge, the token this system reserves for exactly that role --
   3.94:1 on --bg, 4.71:1 on --surf in light; 3.34:1 / 3.05:1 in dark --
   clearing WCAG 1.4.11's 3:1 floor with headroom in both. 2px, and 56px
   tall: the mockup's own .cap input. Decorative dividers keep --hair /
   --seam, a different role at a different (undemanded) threshold. */
input, select {
  width: 100%;
  min-height: 56px;
  padding: 0.6rem 0.75rem;
  font: inherit;
  font-family: var(--font-ui);
  font-size: 15.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--surf);
  border: 2px solid var(--edge);
  border-radius: var(--radius);
}
input:hover, select:hover { border-color: var(--ink); }
input[type="checkbox"] { width: 1.2rem; min-height: 0; flex: 0 0 auto; accent-color: var(--lime-ink); }

/* Wraps a password input on the three first-password screens (accept
   invite, coach accept invite, operator setup) so password-toggle.js has
   somewhere to put its reveal button beside the field it controls. A
   <span>, not a <div>: the markup nests it inside <p class="field">
   alongside the field's own <label>, and a <div> there would be block-level
   content inside a <p> -- invalid HTML that gets reparsed by closing the
   <p> early, breaking .field's own spacing. An inline <span> around a
   width:100% input renders identically to no wrapper at all until
   .has-toggle turns it into a flex row. With the script absent (or
   failing to load) this span holds nothing but the input and is not
   itself a flex container, so the field looks exactly like any other --
   .field-password only turns into a row once .has-toggle is the class the
   script adds the moment it actually creates that button, never shipped
   ahead of it. The gap is 0.5rem, the same value .field-row and .row-gap
   already use for exactly this kind of adjacent-controls spacing, not a
   new one invented for this. The button itself is a plain .btn.btn-ghost
   -- no new colour or height, the 56px control this file already uses
   everywhere else. */
.field-password.has-toggle {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
}
.field-password.has-toggle input { flex: 1 1 auto; min-width: 0; }
.field-password .pw-toggle { flex: 0 0 auto; padding: 0 0.9rem; white-space: nowrap; }

.field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.6rem;
}
.field-row-label {
  flex: 0 0 6rem;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink2);
}
.field-row input { flex: 1 1 6rem; min-width: 0; }

.client-list { list-style: none; margin: 0; padding: 0; }
/* The padding lives on the <a>, not the <li>, so the tap target is the
   whole row rather than just the name's own line box. */
.client-row {
  border-bottom: 1px solid var(--seam);
  font-size: 15.5px; /* px, not rem -- see .wm */
}
/* Fault 2: undecorated at rest, underlined on hover -- the mockup's own
   .rail-b .rn, and the same treatment .client-name already gets on the
   day screen itself. A row here is already a full-width 44px target with
   its own --seam divider, so the underline was carrying no information
   the row's own shape did not. The focus ring is untouched. */
.client-row a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0.5rem 0;
  color: var(--ink);
  text-decoration: none;
}
.client-row a:hover, .client-row a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* Archived clients demote to --ink2, the same treatment an already-
   recorded session gets on the day screen, so the active roster stays
   what draws the eye. */
.client-list-archived .client-row,
.client-list-archived .client-row a { color: var(--ink2); }

/* The client detail page's package list is gone -- option 2
   (paketi-options.html) replaced it with the .tbwrap/.tb table further
   down (Teretana -- Paketi) and its own /ispravi edit page. .package-list,
   .package-row, .package-row-heading, .package-counts and
   .package-row-detail were that list's own rules and had no other
   consumer; .package-action-form and .package-adjust-form stay -- the
   invite form and coach_rules.gohtml's session-type forms still use
   them. */
.package-action-form { display: inline-flex; margin: 0 0.5rem 0.4rem 0; }
.package-adjust-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
}

/* A2: the client detail page's slot picker -- one small form per offered
   slot (each POSTs straight to /klijenti/{id}/rezervacije with the slot's
   own start time in a hidden field), arranged as a wrapping row of buttons
   rather than a list. No new colour: .slot-form's button is .btn-ghost,
   already covered by TestNonTextBoundariesMeetWCAG311. */
.slot-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
.slot-list li { margin: 0; }
.slot-form { display: inline-flex; margin: 0; }
/* Task 6: the client's own picker names every slot's session type and how
   full it is, so .slot-btn stacks that onto two lines inside the same
   .btn-ghost -- no new colour, a full slot reuses .btn:disabled's own
   already-verified ink-3 text/border rather than introducing one. */
.slot-btn { flex-direction: column; align-items: flex-start; height: auto; min-height: 44px; padding: 0.4rem 0.9rem; gap: 0.1rem; }
.slot-time { font-size: 13.5px; font-weight: 600; }
.slot-meta { font-size: 11px; font-weight: 400; }
.package-adjust-form .field-row { flex: 1 1 10rem; margin: 0; }

/* Session-type rows line up across the whole section, whatever the button
   says. .field-row is flex: 1 1 10rem, so the fields GROW to fill whatever
   the button leaves -- which meant the "add" row, whose button was wider
   than the "Spremi" rows above it, pulled its own label and input out of
   line with them.
   Shortening the copy fixed that instance; this fixes the cause. Giving the
   action a fixed basis makes every row's fields start and end at the same x
   regardless of what any button is called, so renaming one later cannot
   quietly break the column again. */
.tt-r .package-adjust-form > .btn { flex: 0 0 7rem; }
/* ...and the same reasoning one step further, now that an existing row
   carries TWO actions (Spremi and Ukloni) while the "add" row carries one.
   A fixed basis per button is no longer enough: the add row's single 7rem
   button left it 7rem plus a gap more room than the rows above, so its
   fields grew wider and its Kapacitet label sat out of line with theirs.
   Reserving the width of the pair keeps all four rows' columns on the same
   x -- 2 x 7rem plus the form's own 0.5rem gap. */
.tt-new .package-adjust-form > .btn { flex: 0 0 14.5rem; }

/* Buttons ---------------------------------------------------------------- */

/* The mockup's own .b: a 2px transparent border sits in the base rule so
   a coloured variant only ever overrides border-color, never adds width --
   no layout shift between a neutral ghost button and a lime-filled one. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: 0 1.1rem;
  font: inherit;
  font-family: var(--font-ui);
  font-size: 15.5px;
  font-weight: 700;
  border: 2px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  /* Fault 2: an <a class="btn"> (Odustani, Otkazano, the day-nav arrows,
     "Svi paketi na Pregledu") inherits the UA's own link underline and
     reads as prose rather than as the control it is. The underline goes;
     the focus ring does not -- :focus-visible's 3px --lime-ink outline is
     a separate property, untouched here, so a keyboard user still sees
     exactly where they are. */
  text-decoration: none;
  transition: filter 120ms ease, border-color 120ms ease, background-color 120ms ease;
}
/* A lime fill is only 1.30:1 against a white card -- the border, in the
   darker --lime-ink brother of the same hue, is what actually carries the
   boundary (6.35:1 on --surf, 4.83:1 on --rais); in dark --lime-ink
   collapses onto --lime itself, so the border is invisible there and the
   fill alone does the job. Every rule below that sets `background:
   var(--lime)` sets `border-color: var(--lime-ink)` in the same rule for
   exactly this reason -- see TestLimeAndCoralFillsCarryTheirBorder. */
.btn-primary {
  background: var(--lime);
  color: var(--on-fill);
  border-color: var(--lime-ink);
}
.btn-primary:hover { filter: brightness(1.06); }
/* Every interactive control boundary uses --edge -- see the note above
   input, select. */
.btn-ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--edge);
}
.btn-ghost:hover { color: var(--ink); border-color: var(--ink2); }
/* A disabled action (no email on file to invite) demotes text to --ink2
   (the one remaining secondary tier, proven at 4.5:1 by
   TestTextMeetsWCAG45) and its border to --edge (proven at 3:1 by
   TestNonTextBoundariesMeetWCAG311) -- the same two tokens every other
   text/boundary pairing in this file uses, not a one-off. */
.btn:disabled {
  color: var(--ink2);
  border-color: var(--edge);
  cursor: not-allowed;
}
/* The merged sign-in page's own Trener/Klijent switch (coach_auth.go,
   login.gohtml, _planning/trener/prijava-plan.md) -- Option 1 of
   design/prijava-options.html. A different shape from /klijenti's own
   .seg, further down this file: that one is a row of separate filter
   chips with a gap between them; this is the mockup's own single
   continuous two-state control, one border around both halves with an
   internal divider, not a row of chips -- a different mockup, a different
   shape, so it gets its own class rather than bending .seg to draw both.
   Built only from tokens this file already has: 2px --edge for the
   container border (3.94:1 / 3.05:1, already proven above input/select's
   own use of it), the 56/48px touch target every other control here uses,
   --lime fill with --lime-ink border on the active side
   (TestLimeAndCoralFillsCarryTheirBorder) -- the mockup's own :checked
   rule fills with --lime alone, but this file's own rule is that a lime
   fill always carries its border in the same declaration (.btn-primary,
   .tabs a.on), so that pairing is added here too, not copied verbatim.
   Real links, not radio buttons (rule 1 of the plan: the switch must work
   with scripts off, and a full navigation -- not a client-side toggle --
   is what lets the choice show up in the URL for rememberRole to read),
   so the active one is marked the way this app already marks "current" --
   aria-current="page" -- not :checked. The 2px transparent border sits on
   every link at rest, .tabs a's own technique (app-header, above), so the
   active link's colour arrives with zero layout shift. */
.role-seg {
  display: flex;
  width: max-content;
  border: 2px solid var(--edge);
  border-radius: var(--radius);
  overflow: hidden;
  margin: 0 0 0.85rem;
}
.role-seg a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: 0 1.375rem;
  font-weight: 700;
  font-size: 15.5px;
  color: var(--ink2);
  text-decoration: none;
  border: 2px solid transparent;
}
.role-seg a + a { border-left-color: var(--edge); }
.role-seg a[aria-current="page"] {
  background: var(--lime);
  color: var(--on-fill);
  border-color: var(--lime-ink);
}
.role-seg a:hover { color: var(--ink); }
.role-seg a[aria-current="page"]:hover { color: var(--on-fill); }
@media (min-width: 48rem) {
  .role-seg a { min-height: 48px; }
}

/* The merged sign-in page's own submit (coach_auth.go, login.gohtml) --
   Option 1 of design/prijava-options.html draws it full width on a phone,
   the one action on the whole screen, and back to its ordinary intrinsic
   width once .app-main gains room past the same 48rem break .btn's own
   min-height already narrows at (below). Scoped to .login-form so no other
   .btn-primary on the site -- the invite-accept form included -- picks
   this up by accident. */
.login-form .btn-primary { width: 100%; }

/* A sign-in screen holds one short form and nothing else, so it should not
   inherit .app-main's full width -- which is 56rem past a tablet and 72rem
   past a desktop, both chosen for the day screen's columns and neither for
   two fields. Left unbounded, the panel stretches to 1152px on a laptop
   with a 22rem input floating at one end of it, which reads as an unfinished
   page rather than a deliberate one.
   
   26rem is the form's own content width, not a new step on the type scale:
   a 22rem input (the `input, select` cap already set above) plus the
   panel's own 2rem of horizontal padding. The cap applies to every
   sign-in-shaped screen -- both roles, the operator, and the operator's
   own password setup -- so they stay one product rather than three
   differently-proportioned pages. It is a max, so a phone is untouched. */
.panel-narrow { max-width: 26rem; }

/* A legal document is the one screen in this app that is pure prose, and it
   must not borrow .panel-narrow. That cap is 26rem because it is a FORM
   width -- a 22rem input plus the panel's padding (see its own comment
   above) -- and prose at that measure runs about 49 characters a line on a
   desktop, against the 60-80 that long text is read at. It also never grew
   past 1280px, because 26rem is a hard cap rather than a proportion.

   52rem, arrived at by measuring rather than by taste. 40rem was tried
   first, on the 60-80 character rule, and read as cramped -- that rule
   assumes body text at the size this app uses for dense screens, not for a
   page of continuous prose. Removing the cap entirely was worse: .app-main
   widens to 72rem past 64rem for the day screen's two-column layout, which
   put this page at about 120 characters a line.

   52rem is the owner's own call, made looking at the page: about 102
   characters at the 15.5px body. Well past the rule's ceiling, and taken
   deliberately -- a contract is scanned for a clause as much as read start
   to finish, and the tables want the room. A phone is untouched: this is a
   max. */
.legal { max-width: 52rem; margin-inline: auto; }
/* Body text stays at the app's own 15.5px step -- the owner's call of
   2026-09-16, after trying 18px. The leading does NOT go back with it: a
   line this wide needs more of it, not less, because the longer the line
   the harder the eye finds the start of the next one. 1.6 against the
   global 1.5. */
.legal p, .legal li, .legal td, .legal th { line-height: 1.6; }
/* Long-form spacing. The global h2 carries margin: 0, which is right for a
   panel heading sitting directly on its own content and wrong for a
   document: a section title flush against its first sentence reads as one
   block, so a reader scanning for clause 9 has nothing to scan by. Air
   above separates sections; a little below separates the title from what it
   titles. */
/* 21px, the scale's next step above the 18px body below -- the global h2 is
   1rem, which was fine when body text here was 15.5px and became a heading
   SMALLER than the paragraph it titles the moment the body stepped up. */
/* The document title has to outrank its own sections. The global h1 is
   21px at weight 500, so once .legal h2 stepped up to 21px the title became
   the same size as every section heading AND lighter than them -- the page
   read as a list of sections with a label on top rather than one document.
   28px is the scale's next step. */
.legal h1 { font-size: 28px; margin-bottom: 0.5rem; }
.legal h2 { font-size: 21px; margin: 1.75rem 0 0.5rem; }
.legal > p:first-of-type { margin-top: 0; }
.legal ul { margin: 0 0 0.75rem; padding-left: 1.5rem; }
.legal li { margin-bottom: 0.25rem; }
/* A table inside a document scrolls on its own rather than widening the
   page -- the same rule .tbwrap already applies elsewhere, applied here
   because these tables are written into the document body and cannot be
   wrapped in one. */
.legal table { display: block; overflow-x: auto; max-width: 100%; }
/* 56px is a touch target, not a proportion: right for a coach tapping
   one-handed in a gym, oversized for a pointer on a desktop. The mockup's
   own ui-desk shrinks to 48px past the same breakpoint .app-main widens
   at, so phones (and anything narrower than a small tablet) keep the full
   touch target -- this is a sizing change only, no colour, so it needs no
   new contrast proof. .slot-btn (below) is exempted: it stacks two lines
   of text (.slot-time, .slot-meta) inside the same element, and shares
   .btn's own selector specificity, so without this override the desktop
   .btn rule above would win by source order and squeeze that two-line
   button down to a single-line box with no vertical padding. */
@media (min-width: 48rem) {
  .btn { min-height: 48px; padding: 0 0.9rem; font-size: 15.5px; }
  .slot-btn { min-height: 56px; padding: 0.4rem 0.9rem; }
  .login-form .btn-primary { width: auto; }
}

/* Signed-out shell -------------------------------------------------------
   layout.gohtml's AuthShell branch, Option 2 of
   design/prijava-izgled-options.html ("Podjela: obrazac i razgovor"), the
   owner's chosen fix for a diagnosis that applied to five templates at
   once (login.gohtml, operator_login.gohtml, operator_setup.gohtml,
   accept_invite.gohtml, coach_accept_invite.gohtml): with no session,
   layout.gohtml renders no header at all, and .app-main's own
   margin:0 auto centres the MAIN element, not the lone panel inside it --
   so the form sat pinned to the top-left of an otherwise empty column.
   .app-header is reused verbatim for the trimmed bar these five get (just
   the wordmark, no tabs, no Odjava -- see layout.gohtml's own comment);
   everything below is new.

   body.auth's own flex column plus min-height give .auth-main a real
   "everything below the bar" box to centre inside, rather than the
   ordinary block flow app-main relies on (which has no concept of "the
   rest of the viewport" at all). layout.gohtml stamps the class only when
   AuthShell put an .app-header + .auth-main pair on the page -- every
   other screen's BODY keeps the plain block flow it always had, so this
   cannot change the layout of a single authenticated page. */
.auth-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
}
/* The tall-window case the owner raised by name: true centring inside
   flex:1 would use the ENTIRE leftover viewport, so on a very tall window
   (a big monitor, a maximised browser) the card drifts to the exact
   geometric middle of a lot of empty page -- further from the bar with
   every extra inch of height. Past roughly 56rem of viewport height (the
   bar's own ~4.25rem plus the ~52rem a card and its breathing room
   actually use), centring stops growing and settles at a fixed offset
   from the bar instead, so the card stays in the readable upper portion
   no matter how tall the window gets. Below the threshold nothing here
   changes -- the card centres in whatever room there actually is, exactly
   as it does on an ordinary window; a very SHORT window needs no rule of
   its own; it already falls under this same threshold and keeps centring
   in full, just with less air around the card. */
@media (min-height: 56rem) {
  .auth-main { justify-content: flex-start; padding-top: 3.5rem; }
}
/* The column every one of the five screens' own content wraps its title,
   alert and form in -- .panel-narrow's own 26rem, reused rather than
   invented (see its own comment above): a role-seg or an .alert has no
   max-width of its own, so without this they would stretch to whatever
   .auth-main happens to be wide enough to give them. */
.auth-col { width: 100%; max-width: 26rem; }

/* login.gohtml and coach_accept_invite.gohtml's own split: the chat scene
   from landing.gohtml (_chatscene.gohtml, shared, not pasted) beside the
   form, past 64rem -- the same breakpoint the landing hero itself splits
   at. Below it, .chat-wrap is hidden outright (not just visually via
   aria-hidden, which _chatscene.gohtml already sets for the accessibility
   tree): the mockup's own rule for this shape is that a phone gets Option
   1's plain single column, not the scene stacked above or below it and
   pushing the form past the first screen. What's left at that width is
   .auth-split acting as a plain wrapper around .auth-col alone -- no grid,
   no width of its own -- which is byte-for-byte the shell every other auth
   screen already has. */
.auth-split .chat-wrap { display: none; }
@media (min-width: 64rem) {
  /* Two FIXED tracks, not the landing hero's own `1fr` column: with a
     flexible column this grid would stretch across .auth-main's full
     width (up to 72rem past a desktop), leaving the chat pinned to one
     edge and the form to the other with a growing dead zone between them
     -- exactly the "gap at 1280" the mockup does not solve. Two 26rem
     tracks give the grid a FIXED total width of 54.5rem (26 + 2.5 gap +
     26); .auth-main's own align-items:center then centres that whole
     fixed-width block as one piece, the same way it already centres the
     lone .auth-col on every other auth screen -- no separate max-width or
     margin:auto needed here, because a flex item that is not stretched is
     already sized to its own content, and a grid with two fixed tracks
     IS its content. 2.5rem is the landing hero's own column-gap
     (.hero, above), reused for the same reason 26rem is. */
  .auth-split {
    display: grid;
    grid-template-columns: 26rem 26rem;
    column-gap: 2.5rem;
    align-items: center;
  }
  .auth-split .chat-wrap { display: block; }
  /* .split-grid's own right-column pin doesn't apply here: with two equal
     fixed tracks the column itself is already exactly .auth-col's width,
     so no extra margin is needed to keep the two edges from drifting
     apart the way the unequal chat-wrap/copy-wrap pairing in .hero does. */
}

/* Day screen ------------------------------------------------------------ */

.day-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  margin: 0 0 1rem;
}
/* .day-date is exactly the mockup's own .dayhead h1 (28px phone, 36px
   desktop -- see the .day-layout breakpoint, further down, which already
   exists for this same viewport split); .day-count is its own
   .dayhead-sub (15px, --ink2, weight 600). Every size in this file is now
   a step on that one scale: 11 / 12 / 12.5 / 13 / 13.5 / 14 / 15 / 15.5 /
   16 / 17 / 18 / 19 / 21 / 24 / 28 / 30 / 32 / 36 / 40 / 46 / 60 -- taken
   verbatim from the mockup's own CSS, not derived from a ratio. */
.day-date { font-size: 28px; font-weight: 800; letter-spacing: -.03em; margin: 0; } /* px, not rem -- see .wm */
/* "5 treninga": the digit is wrapped in .num by numify() (see coach_clients.go),
   not set whole-line in --font-num -- "treninga" is a Croatian word, not a
   numeral. */
.day-count {
  margin: 0;
  color: var(--ink2);
  font-size: 15.5px; /* px, not rem -- see .wm */
  font-weight: 600;
}

/* A4: previous/next controls plus a conditional "back to today" link,
   between the header and the timeline. The two arrow links always render;
   .day-nav-today (the middle one) is only in the markup at all when
   IsToday is false, so it reads as a distinct affordance for "you have
   navigated away from today" rather than a button that is merely disabled
   on today. */
.day-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 1.25rem;
}

/* Task 3a: the date picker sits beside the arrows and Danas, not below --
   flex-wrap on .day-nav above lets it drop to its own line at 375px rather
   than clipping or forcing the whole nav to scroll. The bare <input
   type="date"> would otherwise inherit the page's global `input { width:
   100% }` (Forms/panels, above) and swallow the rest of the row -- this is
   the one narrow override that field, since a date picker's own field is
   inherently short, never a full-width form. */
.date-picker-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}
.date-picker-form input[type="date"] { width: auto; }

.empty { color: var(--ink2); }

/* Tjedan (Option 1): the client's own week picker ------------------------
   _planning/trener/design/klijent-rezervacija-options.html, the owner's own
   choice. The week nav reuses .day-nav and .date-picker-form verbatim (the
   arrows plus a jump-to-date form, exactly the coach day screen's own
   pattern); .wk-range is the one new piece, a heading-weight label between
   the arrows where .day-nav otherwise has none. */
.wk-range { font-size: 15.5px; font-weight: 700; letter-spacing: -.01em; }

/* The seven-number summary bar: a panel-like strip (--surf on --bg, same
   logic as .panel) holding one cell per weekday. A zero count demotes to
   --ink2 and prints as a dash (the template's own "&ndash;"), the mockup's
   own convention -- a dash reads as "checked, and there is nothing" rather
   than a blank cell a client might mistake for not yet loaded. 18px, not
   the mockup's own 19: the type scale note at the top of this file already
   established that a desktop-only half of a responsive pair collapses onto
   the nearest base step in a context that has no such pair -- here there
   is only one size, so 19 collapses onto .wm's own 18 the same way 17
   collapses onto it elsewhere in this file. 11px for the weekday letters,
   the same collapse (11.5 -> 11) .slot-meta already uses. */
.wk-bar {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
  background: var(--surf);
  border-radius: var(--radius-lg);
  padding: 0.6rem 0.4rem;
  margin: 0 0 0.4rem;
}
.wk-bar-cell { text-align: center; min-width: 0; }
.wk-bar-d { font-size: 11px; font-weight: 800; color: var(--ink2); letter-spacing: .02em; }
.wk-bar-v { font-size: 18px; font-weight: 800; letter-spacing: -.02em; margin-top: 2px; }
.wk-bar-v.wk-bar-zero { color: var(--ink2); font-weight: 700; }
/* Today's own weekday letter picks up the same lime-ink accent the day
   screen's now-marker and month-grid "today" outline already use for
   "this one is different." */
.wk-bar-today .wk-bar-d { color: var(--lime-ink); }
.wk-bar-cap { margin: 0 0 1rem; }

/* One day's own heading, then either its chips or a one-line reason it
   holds none -- the owner's own "the case that matters most": a coach who
   has no availability for days at a time must never leave a client
   guessing why. 18px for the weekday name is the same 17->18 collapse
   .wk-bar-v already uses; the date beside it is plain .say, so it does not
   compete with the name for attention. */
.wk-day { margin: 0 0 1.25rem; }
.wk-day-h {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0 0 0.5rem;
}
.wk-day-n { font-size: 18px; font-weight: 800; letter-spacing: -.02em; }
.wk-day-closed { margin: 0; }

/* A day the COACH is being offered outside their own working hours -- the
   owner's rule of 2026-09-13 that a coach may book manually on any day
   (slotsResult.OffHours, coach_bookings.go). Never rendered on a client's
   Tjedan: for them the day is still shut.

   Dimmed, not disabled: these chips work, and are supposed to. The whole
   row reads one step quieter than a working day, so the coach's own
   schedule still looks like the thing that governs the week and this looks
   like the exception it is. --ink2 on --surf is the same pairing .wk-shut-w
   above already uses, so the contrast test's floor is met by construction
   rather than by a second colour choice. */
.wk-day-off-t { font-size: 12.5px; font-weight: 700; color: var(--ink2); flex: 0 0 auto; text-align: right; }
.wk-day-off .wk-chip { border-style: dashed; }
.wk-day-off .wk-chip-t { color: var(--ink2); }

/* A shut row (fault 1): one or more consecutive closed/past/today days,
   merged by buildWeekRows (client_home.go) into a single line rather than
   repeating an identical empty row once per day -- the mockup's own
   .shut (klijent-rezervacija-options.html, Option 1): a name, a hairline
   that fills the space between, then the reason. Every .wk-shut spans
   every column once .wkcols becomes an actual grid (see .wkcols .wk-shut
   below) -- whether it stands for one day or several: a lone closed day
   sitting beside an open one as its own grid cell was exactly fault 1
   (klijent-rezervacija-options.html has no such state -- Sub/Ned always
   merge together -- so this generalises "spans the row" to every shut
   line, not only a merged run). Below 48rem there is only ever one
   column, so the span is a no-op there either way. */
.wk-shut { display: flex; align-items: center; gap: 0.6rem; padding: 0.6rem 0; margin: 0 0 1.25rem; }
.wk-shut-n { font-size: 13.5px; font-weight: 700; color: var(--ink2); flex: 0 0 auto; } /* 14 -> 13.5, the type scale's own collapse */
.wk-shut-l { flex: 1 1 auto; height: 2px; background: var(--hair); border-radius: 1px; }
.wk-shut-w { font-size: 12.5px; font-weight: 700; color: var(--ink2); flex: 0 0 auto; text-align: right; }

/* closures-plan.md: a closed day (no window on its weekday, or an explicit
   closure) can still hold a booking -- "the display has to say both
   things at once." .wk-chips-shut sits directly under that day's own
   .wk-shut line, reusing .wk-chip's own look below (always the disabled
   shape here -- nothing on a closed day is ever tappable, so there is no
   bookable variant to style). Pulled up slightly against .wk-shut's own
   bottom margin so the two read as one block, not two stacked rows with a
   gap between them. */
.wk-chips-shut { margin: -0.65rem 0 1.25rem; }

/* The chips themselves: two per row on a phone (the mockup's own reason --
   a session type plus its fill count needs more width than a bare time
   would, so a single column of 56px targets reads as a list, not a grid),
   one per row past 48rem where the day sits in a narrower column beside
   the package/upcoming rail (mirrors .wkcols in the mockup's own desktop
   frame). A chip is a <button> (bookable, inside its own <form>) or a
   disabled <button> (every other state) -- never a bare <div>, so a screen
   reader still gets one consistent control per instant. */
.wk-chips { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
.wk-chips li { margin: 0; }
.wk-chips form { margin: 0; }
.wk-chip {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 2px;
  width: 100%;
  min-height: 56px;
  padding: 0.5rem 0.7rem;
  font: inherit;
  text-align: left;
  cursor: pointer;
  background: var(--surf);
  color: var(--ink);
  border: 2px solid var(--edge);
  border-radius: var(--radius);
}
.wk-chip:hover { border-color: var(--ink2); }
.wk-chip:disabled { cursor: default; }
.wk-chip-t { font-size: 18px; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; }
.wk-chip-m { font-size: 11px; font-weight: 650; color: var(--ink2); line-height: 1.25; }
/* Muted: past the coach's own notice, or genuinely full -- real and
   offered, just not tappable right now.
   
   Until 2026-09-14 this state differed from a bookable chip by exactly one
   thing: the time dropping from --ink to --ink2. Same white fill, same 2px
   --edge outline. A client scanning a week could not see which slots they
   could actually take, which is the single question that screen exists to
   answer.
   
   The fill alone cannot fix it -- measured, --rais against --surf is
   1.31:1 in light and 1.14:1 in dark, far too close to read as a
   difference. So the OUTLINE carries it: a free slot keeps the raised card
   (white, 2px --edge, a boundary proven at 3:1) and a taken one loses the
   border entirely and settles onto --rais. Card versus no card is legible
   in both schemes in a way two nearly identical fills are not, and it says
   the right thing -- a bookable slot is a control, a full one is a
   statement.
   
   Text stays --ink2, which is 6.02:1 on --rais in light and 8.31:1 in
   dark: recessed, never dimmed below the floor. */
.wk-chip-off {
  background: var(--rais);
  border-color: transparent;
}
.wk-chip-off .wk-chip-t { color: var(--ink2); }
.wk-chip-off:hover { border-color: transparent; }
/* Coral border only, never a coral fill (a coral fill is 2.49:1 on white,
   nowhere near the 3:1 non-text floor on its own -- fault 3's own rule):
   this client holds no package for this type, the one state that gets its
   own colour rather than just muting to --ink2. */
.wk-chip-no { border-color: var(--coral-ink); }
.wk-chip-no .wk-chip-t { color: var(--ink2); }
.wk-chip-no .wk-chip-m { color: var(--coral-ink); }
.wk-chip-no:disabled { cursor: default; }
/* The client's own booking at this instant: lime fill, same border-color
   pairing every lime fill in this file carries (see .btn-primary's own
   comment -- TestLimeAndCoralFillsCarryTheirBorder enforces it). */
.wk-chip-mine { background: var(--lime); border-color: var(--lime-ink); color: var(--on-fill); }
.wk-chip-mine .wk-chip-t, .wk-chip-mine .wk-chip-m { color: var(--on-fill); }

/* The coach's own reuse of a bookable chip (templates/_weekbooking.gohtml,
   .Coach's own branch): a <select> ahead of the chip's own button, for
   the one slot genuinely ambiguous between more than one package type
   (weekChipView.TypeOptions) -- every other input, select global rule
   already styles it, this only tightens its bottom margin so it reads
   as part of the same control group as the button under it, not a
   separate field. */
.wk-chip-tip { margin: 0 0 0.4rem; }

/* Gap 1 (Option 1's own desktop frame): the day list is a grid of
   day-columns past 48rem, not a stack of full-width days -- .wkcols
   mirrors the mockup's own .ui-desk .wkcols. The mockup hardcodes
   repeat(5, minmax(0,1fr)) because its own example never shows more than
   five open days at once; this app's coach can work six or seven, so the
   column count is computed once per week in Go (OpenDayCount,
   client_home.go -- how many of the seven days actually hold chips) and
   selects one of the seven .wkcols-N classes below, N from 1 to 7 (never
   0 -- OpenDayCount is floored at 1). A CSS custom property set inline
   (style="--wk-cols: N") was tried first and rejected: this app's CSP is
   style-src 'self' with no 'unsafe-inline' (server.go's csp constant),
   which blocks an inline style="" attribute exactly like it blocks an
   inline <script> -- silently, so a screenshot at the seed data's own
   five open days would have looked identical whether or not the fix
   worked at all, and only a six-or-seven-day coach (var() had silently
   fallen back to its default of 5) would have shown the bug. A class is
   what every other per-instance value in this file already is.
   Whichever N is picked, every open day gets its own column, always in
   one row, never wrapped -- repeat(auto-fit, minmax(9rem,...)) was tried
   before that and rejected too, because auto-fit decides the column
   count from the CONTAINER's width alone, the same count on every row
   regardless of how many cells that row actually has content for; a
   sixth open day then falls into a second row of that same column
   count, alone in its own first cell, looking orphaned rather than
   sharing the row. A fixed, computed column count cannot do that: there
   are never more columns than there are open days, so there is never a
   second row to fall into. The floor of minmax(0, ...), not the
   mockup's own 9rem minimum, is what makes six or seven columns fit at
   all on a real viewport rather than forcing the row to overflow --
   .wk-day-n's and .wk-chip-m's own overflow-wrap (below) are what keep a
   narrow column's own weekday name and type/fill text legible once it
   does, confirmed by screenshot at 1024 and 1280 with a seven-day
   coach's own week (measure.js alone cannot see this -- it proves
   nothing overflows, not that the six or seven columns are still
   readable). A merged shut run, or a lone shut day (fault 1 -- see
   .wk-shut above) spans every column instead of sitting in one, the
   same way the mockup's own .wk-end does. Below 48rem .wkcols stays a
   plain block: each day (or shut row) is its own full-width block, one
   after another, exactly as before. */
.wkcols { display: block; }
@media (min-width: 48rem) {
  .wkcols {
    display: grid;
    gap: 0.75rem;
    align-items: start;
  }
  .wkcols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
  .wkcols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .wkcols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .wkcols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .wkcols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .wkcols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .wkcols-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .wkcols .wk-shut { grid-column: 1 / -1; }
  .wkcols .wk-day, .wkcols .wk-shut { margin: 0; }
  .wk-chips { grid-template-columns: 1fr; }
  /* .wk-chips-shut spans every column exactly like .wk-shut above, rather
     than being squeezed into a single narrow day-column beside it -- the
     same reasoning .wkcols .wk-shut's own comment already gives, applied
     to the chip strip a closed-with-bookings day now also carries. A
     wider row can afford more than the mobile default's single column, so
     this widens it back out rather than inheriting the 1fr rule above. */
  .wkcols .wk-chips-shut {
    grid-column: 1 / -1;
    grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
    margin: -0.65rem 0 1.25rem;
  }
  /* Fault 3: a day heading is name-then-date stacked (never side by side)
     past this breakpoint, the mockup's own .ui-desk .wkcols .dsec-h
     block/.c rule. min-height alone does not make every column's
     heading the same height -- it only sets a floor -- unless the name
     itself is guaranteed to wrap the same way regardless of column
     width, which is what .wk-day-n's own overflow-wrap does below: a
     Croatian weekday name is one word, so without it "Ponedjeljak"
     would overflow a narrow (six- or seven-column) cell instead of
     wrapping onto a second line the way plain word-wrap only ever
     breaks at a space. min-height is sized for that worst case -- two
     name lines at 18px/1.5 line-height (54px) plus the date line
     (.wk-day-d, .say's own 12.5px/1.5 plus its 2px margin, ~21px) plus
     this element's own 0.5rem bottom padding (8px) -- so every column's
     heading is the same height whether its own name happens to wrap or
     not, and every column's first chip starts at the same y regardless
     of which weekday it is. */
  .wkcols .wk-day-h { display: block; min-height: 5.2rem; }
  .wkcols .wk-day-n { display: block; overflow-wrap: anywhere; }
  .wkcols .wk-day-d { display: block; margin: 2px 0 0; }
  /* A narrow column (six or seven open days) needs its own type/fill text
     to break wherever it must, the mockup's own .ui-desk .wkcols .chip .m
     rule -- never truncated or forced to overflow the chip. */
  .wk-chip-m { overflow-wrap: anywhere; }
}

/* "Sljedeći tjedan: N slobodnih termina" closes the week grid. It had
   width and nothing else, so it sat directly against the last chip above
   it and against the rules footer below -- three unrelated things in a
   row with no space to tell them apart. The chips are a set to scan, this
   is one action, and the footer is a standing notice; each needs a gap
   saying the previous one has ended. The grid's own row gap is 0.6rem, so
   anything less than roughly double that reads as another chip. */
.wk-more { width: 100%; margin-top: 1.25rem; }

/* Gap 4: Option 1's own closing sentence -- the booking-notice rule, the
   cancellation-notice rule, and each active package's own expiry, always
   visible under the week rather than only inside the confirm step. The
   mockup's own .rules: a left rule in --edge (a control's own boundary
   colour, already proven at 3:1 -- this is a decorative accent, not a
   control, but the same token already cleared the higher bar), --ink2 text
   a notch heavier than plain .say so it still reads as a standing notice
   rather than a caption. */
.rules {
  /* 1.25rem, matching .wk-more above: this footer follows either the
     button or the grid directly, and 0.75rem left it crowding whichever
     it was. */
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink2);
  margin: 1.25rem 0 0;
  padding-left: 0.75rem;
  border-left: 2px solid var(--edge);
}

/* The coach's own booking widget (#rezervacija-panel, coach_client_detail
   .gohtml), promoted into a native <dialog> by booking-dialog.js (loaded
   on that page alone). showModal() already supplies centering, a
   backdrop, the focus trap, Escape-to-close and an inert background --
   only the box itself and its backdrop's own colour need styling here.
   With JavaScript off, or the browser missing real <dialog> support,
   none of this ever applies: the same markup stays a plain <div> exactly
   where the "Nova rezervacija" section already had it.

   Background is --bg, the page ground, not --surf: the wk-bar strip and
   the chips inside are themselves --surf (see .wk-bar / .wk-chip further
   up), same as on the client's own week view where they sit on the page.
   --surf-on-surf here would erase that strip and leave the chips reading
   only by their border. */
dialog.wk-dialog {
  background: var(--bg);
  color: var(--ink);
  border: none;
  border-radius: var(--radius-lg);
  padding: 1.25rem;
  width: min(90vw, 64rem);
  max-height: 85vh;
  overflow-y: auto;
}
dialog.wk-dialog::backdrop { background: rgba(0, 0, 0, 0.5); }
.wk-dialog-close-row { display: flex; justify-content: flex-end; margin-bottom: 0.75rem; }

/* Gap 2: the right rail. Below 64rem (the same breakpoint .day-layout
   already uses for its own main/sidebar split) this is a single column,
   and .rail/.col-main simply stack in source order -- the rail first, so
   Paket and Nadolazeći termini sit above the booking grid on a phone,
   exactly as the mockup's own phone frame has them. Past 64rem the two
   sit side by side; grid-column placement puts the rail on the right
   without changing that source order (and so without changing the order
   a screen reader hears them in either). */
.client-layout { display: block; }
@media (min-width: 64rem) {
  .client-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 316px;
    gap: 2rem;
    align-items: start;
  }
  .client-layout .rail { grid-column: 2; grid-row: 1; }
  .client-layout .col-main { grid-column: 1; grid-row: 1; }
  /* Fault 4: Nedavni termini used to sit outside .client-layout entirely,
     so past this breakpoint it ran under the rail as well as the week
     grid -- its own left AND right edges matched nothing above it. Placed
     inside the grid instead (col-recent, client_home.gohtml), pinned to
     the same column .col-main occupies; no grid-row is set, so the
     ordinary auto-placement cursor lands it in the next free row of that
     column (row 2) whether .col-main rendered this time or not (an
     archived client has no booking panel at all). Below this breakpoint
     .client-layout is a plain block and col-recent is just another
     full-width child, same as before. */
  .client-layout .col-recent { grid-column: 1; }
}

/* Paket: each active package's own name and expiry on the left, its own
   remaining count on the right -- big, since it is the one number this
   whole rail exists to answer. The mockup's own .pk/.pk-v/.pk-k; 16 -> 15.5
   and 11.5 -> 11 are the type scale's own collapses, 28 is already a base
   step. */
.pk { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; padding: 0.5rem 0; }
.pk + .pk { border-top: 2px solid var(--seam); }
.pk-n { font-size: 15.5px; font-weight: 750; letter-spacing: -.01em; }
.pk-v { font-size: 28px; font-weight: 800; letter-spacing: -.05em; line-height: 1; flex: 0 0 auto; text-align: right; }
/* Coral at 3 or fewer -- the same rule .cnt-v.low (the coach's day screen)
   and .tb .rem.low (their package table) already carry, finally applied to
   the client's own rail. The landing page promises "brojka pocrveni prije
   nego sto klijent ostane bez ijednog" about this exact number, and until
   2026-09-15 this was the one place it never did (audit M6). .pk-k inherits
   nothing from this: the word "preostalo" beside the figure stays --ink2,
   so the colour marks the number rather than shouting the whole block. */
.pk-v.low { color: var(--coral-ink); }
.pk-v.low .pk-k { color: var(--ink2); }
.pk-k { display: block; font-size: 11px; font-weight: 600; color: var(--ink2); margin-top: 3px; }

/* Nadolazeći termini: the same row shape, an Otkaži button in place of
   the count -- .btn-warn (coral-ink outline, already this file's own
   cancel colour) is the mockup's own .b-no, not a new button style. */
.mine { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.5rem 0; }
.mine + .mine { border-top: 2px solid var(--seam); }
.mine-t { font-size: 15.5px; font-weight: 750; letter-spacing: -.01em; }

/* Task 2 (Teretana): a session is a CARD, not a table row -- the mockup's
   own .day (_planning/trener/design/b-teretana-modes.html), which drops
   the shared time-gutter/spine entirely: each card carries its own time in
   its own header, so there is no longer one column every row (session,
   gap, now-marker) has to line up against. An <ol> still, for the
   position/count an assistive technology can announce; the 0.75rem gap
   between children is the real separation between two cards sitting
   directly on --bg, not a hairline. */
.timeline {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Visually hidden but still announced. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Task 2 (Teretana): a session is a CARD -- its own --surf surface, its
   own corners, holding its own header and every attendee's own seat.
   Nothing about a "row" or a shared gutter column is left; see the
   mockup's own .card-s. */
.row-session {
  background: var(--surf);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
/* The card's own header: a raised strip (--rais, the mockup's own
   .card-h) carrying the SESSION's facts -- when, how long, what kind, how
   full -- never a person's own. .row-time and .row-body are the two
   groups inside it (time+duration on the left, type+fill pushed right by
   .row-body's own margin-left: auto), the mockup's four flat children
   collapsed into two so each half keeps its own token name. */
.card-h {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  padding: 0.75rem 1.1rem;
  background: var(--rais);
}
.row-time { display: flex; align-items: baseline; gap: 0.4rem; }
/* Fault 3's own numeral: display-size, tight tracking, no monospace (the
   same sans stack as everything else, tabular-nums doing the alignment
   work) -- exactly the mockup's own .card-h .hh. Unchanged in size from
   before Task 2; what changes is that it now sits in a compact header
   strip rather than being the row's own dominant column, while the
   client's name below (.client-name) is what the eye actually lands on
   first in the spacious --surf area under it. */
.time-value {
  font-family: var(--font-ui);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -.04em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* "60 min": the digits are wrapped in .num in the template, "min" stays
   --font-ui. 13.5px, not the old 11px -- the mockup's own .hm, now that
   this sits beside the clock in a header rather than stacked under it
   with no room to spare. */
.time-duration {
  font-size: 13.5px;
  color: var(--ink2);
}

/* The session's type and how full it is, pushed to the header's own right
   edge: quiet context (--ink2), not a competing headline -- fault 3's
   "clock is secondary" logic extended to the type name too, since neither
   is what a coach is scanning for. The occupied/capacity fraction's own
   digits (wrapped in .num already) go back to full --ink, the mockup's
   own .ht b, so "2/3" still reads at a glance against the quieter type
   name beside it. */
.row-body {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.3rem 0.5rem;
  margin-left: auto;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ink2);
}
.session-type { color: inherit; font-weight: inherit; font-size: inherit; }
.session-fill { color: inherit; font-weight: inherit; font-size: inherit; }
.session-fill .num { color: var(--ink); }

/* One session's attendees, each its own seat: a 2px seam between them
   (var(--seam), the same token this file already reserves for "a seam
   between siblings sitting inside a --surf card") -- but only from the
   SECOND seat on (the mockup's own ".seat + .seat"), so a group session
   with six names reads as six distinct people at 375px, not a run-on
   paragraph, without doubling the header's own bottom padding into an odd
   gap above the first seat. */
.attendee-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.attendee-row { display: flex; flex-direction: column; gap: 0.6rem; padding: 1rem 1.1rem 1.1rem; }
.attendee-row + .attendee-row { border-top: 2px solid var(--seam); }
/* The seat's own top line: identity on the left, the countdown on the
   right -- side by side at every width the mockup itself uses this rule
   at (it has no ui-desk override either), so a coach never has to scroll
   past the count to find the name or vice versa. */
.seat-h { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.seat-id { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
/* Fault 3's actual reversal: 21px, up from 18px -- still short of the
   clock's own 30px (see .time-value above), because the clock has moved
   into a small quiet header strip and no longer needs beating on raw
   size; the name is now the largest thing in the spacious --surf area a
   coach's eye actually lands in. Never demoted by .is-recorded, below --
   see that rule's own comment for fault 2's full fix. */
.client-name { font-size: 21px; font-weight: 750; color: var(--ink); text-decoration: none; letter-spacing: -.02em; } /* px, not rem -- see .wm */
.client-name:hover, .client-name:focus-visible { text-decoration: underline; text-underline-offset: 3px; }

/* "vrijedi do 08.12.2026.": the date (dots included -- it is one token) is
   wrapped in .num in the template, "vrijedi do" stays --font-ui. 13.5px,
   the mockup's own .seat-e -- up from the old 11px now that it sits under
   a bigger name with room to spare. */
.pkg-expiry {
  color: var(--ink2);
  font-size: 13.5px;
  font-weight: 600;
  margin: 0;
}

/* Task 2's own hero numeral (requirement 4): the reason a coach opens this
   screen between sets is not the session, it is whether this client has
   sessions left -- so the remaining count is the single largest figure on
   the whole card, bigger even than the clock (.time-value, 30px). Full
   --ink strength always (never demoted by .is-recorded, same as
   .client-name, and for the same reason: this is the person's own data,
   not the session's), turning --coral-ink at 3 or fewer so a package
   running out draws the eye without a coach having to read the number. */
.cnt { text-align: right; flex: 0 0 auto; line-height: 1; }
.cnt-v {
  display: block;
  font-size: 40px;
  font-weight: 800;
  letter-spacing: -.05em;
  color: var(--ink);
}
.cnt-v.low { color: var(--coral-ink); }
.cnt-k {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink2);
  margin-top: 0.25rem;
}

/* .row-heading / .attendee-heading / .pkg-status / .pkg-remaining are
   gone as of Task 5. They were the PRE-Task-2 row markup, kept alive one
   task longer only because landing.gohtml's own hero still mocked that
   older day screen verbatim; the hero now renders the card markup above
   (.card-h / .seat-h / .seat-id / .cnt) and nothing in this app uses the
   four old names any more. */

.outcome-form { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* Requirement 5: "Dolazak" as wide as "Nedolazak" and "Otkazano"
   together, not a stylistic balance of three equal thirds -- a coach taps
   this one-handed nine times out of ten (the mockup's own reasoning, see
   its "1. Dnevni prikaz" note), so it needs to be the easiest target to
   find and hit, not merely the leftmost.
   A flex-grow ratio on a shared flex-basis of 0 (2:1:1) was tried first
   and measured wrong: "Nedolazak" and "Otkazano" are long enough Croatian
   words that their own min-content width already exceeds a 1-share slice
   at 375px, so flex-wrap moved Otkazano onto its own full-width second
   line ALONE -- wider than Dolazak, the opposite of this requirement.
   flex-basis: 100% on the primary button sidesteps that: with wrap, a
   100%-basis item always claims its own line, pushing Nedolazak and
   Otkazano onto the next one to split 50/50 between them -- so "as wide
   as the other two together" is exactly what renders, by construction,
   regardless of label width, at every viewport this screen supports. */
.outcome-form .btn-primary { flex: 1 1 100%; }
.outcome-form .btn-warn,
.outcome-form .btn-ghost { flex: 1 1 0; min-width: 0; }

/* Fault 3: the confirmation pages' own action row.
   These pages (coach_cancel_confirm, client_cancel_confirm,
   client_booking_confirm) used to borrow .outcome-form. That class is the
   DAY SCREEN's three-button row and nothing else: its whole job is the
   `flex: 1 1 100%` above, which makes "Dolazak" claim a line of its own
   so it ends up as wide as Nedolazak and Otkazano together. Borrowed here
   it forced the primary button full width, pushed Odustani onto a second
   full-width line under it, and -- on the coach's cancel page, which has
   a third form -- made that form's question paragraph a flex SIBLING of
   its button, so the override sat half-width and right-aligned across the
   sentence introducing it.
   A confirmation is a different shape: one decision, one way back. The
   primary grows to fill what is left after Odustani, both on one line,
   wrapping only when the two genuinely do not fit. */
.confirm-acts { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 0.75rem; }
.confirm-acts .btn-primary { flex: 1 1 12rem; }
.confirm-acts .btn-ghost { flex: 0 1 auto; }

/* The override -- "the client actually did give notice in time" -- is a
   second, secondary decision, not a third button in the row above. It
   gets the seam this file uses everywhere else to separate two things
   that are not the same thing, its question on its own line ABOVE the
   button rather than beside it, and the button sized to its own label. */
.override {
  margin: 1.25rem 0 0;
  padding: 1.25rem 0 0;
  border-top: 2px solid var(--seam);
}
.override-q { margin: 0 0 0.75rem; max-width: 62ch; }

/* Requirement 5's other button: a no-show costs the client a session
   without them showing up, worth flagging the moment a coach glances at
   the row, so it gets --coral-ink rather than the neutral .btn-ghost
   "Otkazano" (a cancellation) uses -- transparent fill, coral-ink text
   and border (already proven at >=4.5:1 on every ground this screen uses,
   well past the 3:1 a border needs), a wash on hover the same way
   .btn-ghost darkens its own border. Never a coral FILL: that is
   .btn-primary's own lime-fill trick, reserved for the one action this
   screen actually wants to look approvable. */
.btn-warn {
  background: transparent;
  color: var(--coral-ink);
  border-color: var(--coral-ink);
}
.btn-warn:hover { background: var(--coral-wash); }

/* Fault 6: the recorded outcome, and this REVERSES an earlier decision
   recorded in this same place.
   A previous task was told the recorded state read "as loud as a pending
   Dolazak" and demoted it to the small bordered .tag. That instruction
   overrode the approved mockup, the owner has now looked at both screens
   side by side and chosen the mockup, so the mockup's .rec is what ships:
   a full-width filled block occupying exactly the space the three action
   buttons it replaces would have taken. Nothing about "it is louder than
   a tag" is an accident -- a settled seat is a fact the coach wants to
   read at a glance down a day, and the block reading at button size is
   how the row keeps its rhythm whether it is pending or done.
   What does NOT come back is the other half of that earlier instruction,
   which was right and stands: the SESSION's own facts (its clock, its
   fill count) may settle to --ink2 once every seat is recorded, but the
   PERSON's never do. .client-name and .cnt-v are not touched by
   .is-recorded anywhere in this file -- a coach scanning a recorded row
   still reads who it was and what they have left at full strength.
   Fill + border in the same rule, as every lime and coral fill in this
   file does (TestLimeAndCoralFillsCarryTheirBorder). 15.5px, not the
   mockup's own 16px: the type scale collapses 16 onto 15.5 (see the note
   at the top of this file). The 18px glyph IS a step and is kept. */
.rec {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 56px;
  border: 2px solid transparent;
  border-radius: var(--radius);
  padding: 0 1rem;
  font-size: 15.5px;
  font-weight: 750;
}
.rec-go { background: var(--lime); color: var(--on-fill); border-color: var(--lime-ink); }
.rec-no { background: var(--coral); color: var(--on-fill); border-color: var(--coral-ink); }
/* The fourth outcome the mockup has no variant for: a cancellation made
   in time costs the client nothing, so it is neither the lime nor the
   coral statement. Same block, same size, the neutral --edge boundary
   every other quiet control here carries -- rather than stretching either
   hue to cover a third meaning. See outcomeRecClass in coach_clients.go. */
.rec-off { background: transparent; color: var(--ink2); border-color: var(--edge); }
.rec .gl { font-size: 18px; line-height: 1; }
/* A session that simply happened: still `booked`, its time passed, nobody
   cancelled it. Quieter than every .rec state beside it, on purpose --
   those record a decision somebody made (attended, cancelled late,
   cancelled in time), and this records that nobody made one. --ink2 on
   --surf is the pairing .say already proves at 4.5:1 in both schemes, so it
   needs no colour of its own; 12.5px is an existing scale step. */
.held { margin: 0; font-size: 12.5px; font-weight: 700; color: var(--ink2); }
/* 56px is a touch target on a phone and oversized for a pointer -- the
   same trade .btn makes at the same breakpoint, so a recorded block and
   the button row it stands in for keep matching heights. */
@media (min-width: 48rem) { .rec { min-height: 48px; } }

.row-session.is-recorded { color: var(--ink2); }
/* .session-type / .session-fill need no rule here: .row-body already
   holds them at --ink2 unconditionally (above) -- only the clock and the
   fill count's own digits (.session-fill .num) are ever full --ink, so
   only those two need an explicit demotion once every seat is settled. */
.row-session.is-recorded .time-value,
.row-session.is-recorded .session-fill .num { color: var(--ink2); }

/* Requirement 3: a gap is real empty space whose height grows with its
   length, not a row with a label -- kept from before Task 2 (the size
   tiers already matched the mockup's own gap-1/gap-2 heights), only the
   row's own shape changes now that there is no shared gutter column left
   to align a label against (see .timeline, above): a centred label with a
   hairline running to the card on either side, the mockup's own .gap. */
.row-gap {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  /* Fault 8: the mockup's .gap insets its hairlines 18px so they stop
     short of the card edges above and below rather than running the full
     measure, and keys height off the tier instead of padding. */
  height: 40px;
  padding: 0 18px;
}
.row-gap::before, .row-gap::after {
  content: "";
  flex: 1;
  height: 2px;
  background: var(--hair);
  border-radius: 1px;
}
/* The mockup names two gap heights, .gap-1 at 56px and .gap-2 at 88px;
   gapSizeClass (day_timeline.go) has three tiers, because a sub-hour gap
   is a real case the mockup's own example day happens not to contain. The
   two tiers the mockup DOES name land on its exact heights; the third,
   shorter than either, is the bare .row-gap above. */
.row-gap.gap-medium { height: 56px; }
.row-gap.gap-long { height: 88px; }
/* "1 h 30 slobodno": each number is wrapped in .num by numify() (see
   coach_clients.go), "h" and "slobodno" stay --font-ui. */
.row-gap span {
  color: var(--ink2);
  font-size: 12.5px;
  font-weight: 600; /* fault 8: the mockup's .gap span is 600, not the 400 this inherited */
  white-space: nowrap;
}

/* The now-marker: structure-only, and (fault 3's own colour, kept) lime --
   the mockup's own .now-p/.now-l. The pill is a real lime FILL, so it
   carries the same 2px --lime-ink border every lime fill does (invisible
   in dark, where --lime-ink collapses onto --lime); the line across the
   rest of the row is the darker brother alone, same as the mockup's
   .now-l. No animation. */
.row-now {
  display: flex;
  align-items: center;
  gap: 0;
  height: 58px; /* the mockup's own .now */
}
/* The pill is rounded on its LEFT only and carries no right border: it
   runs straight into the line, so the two read as one marker rather than
   a badge with a rule next to it. The border shorthand still names
   --lime-ink, which is what carries this fill's boundary on a light
   ground (1.30:1 on its own) and what
   TestLimeAndCoralFillsCarryTheirBorder checks for. */
.row-now .now-label {
  background: var(--lime);
  color: var(--on-fill);
  border: 2px solid var(--lime-ink);
  border-right: 0;
  border-radius: 8px 0 0 8px;
  padding: 5px 13px 5px 11px;
  font-weight: 800;
  font-size: 13.5px; /* the mockup's 14px, on this file's scale */
  font-family: var(--font-ui);
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.row-now::after {
  content: "";
  flex: 1;
  height: 4px;
  background: var(--lime-ink);
}

/* Task 3a: the month grid ------------------------------------------------
   A <details> so the calendar is reachable without JavaScript and without
   competing with the timeline for the coach's first glance -- closed by
   default on every viewport, exactly the "day is the content, month is
   context" ordering the sidebar itself follows (task 3b). */
.month-grid-wrap { margin: 0 0 1.25rem; }
.month-grid-summary {
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink2);
  padding: 0.5rem 0;
}
.month-grid { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }
.month-grid caption {
  text-align: left;
  font-weight: 600;
  margin-bottom: 0.5rem;
  color: var(--ink);
}
.month-grid th {
  padding: 0.3rem;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ink2); /* 4.5:1 on both grounds -- see :root's own comment */
}
.month-grid td {
  padding: 0.2rem;
  border: 1px solid var(--seam);
  text-align: center;
}
.month-grid td.cal-pad { border-color: transparent; }
.month-grid td a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.1rem;
  min-height: 2.75rem;
  color: var(--ink);
  text-decoration: none;
}
.month-grid td a:hover, .month-grid td a:focus-visible { background: var(--rais); }
.cal-day-count { font-size: 11px; color: var(--ink2); }
/* Today: an outline in --lime-ink, the same structure colour the now-
   marker uses, so "today" reads as a fact about the calendar's own
   structure rather than a status needing --coral-ink/--lime-ink-as-tag.
   Selected: a filled background rather than a second outline, so the two
   states (today, the day actually being viewed) never look identical when
   they coincide. */
.month-grid td.cal-today { outline: 2px solid var(--lime-ink); outline-offset: -2px; }
.month-grid td.cal-selected { background: var(--rais); }

/* Task 3b: the day screen's own layout, day plus sidebar -------------------
   Column by default (phone and tablet both): the day view always renders
   first in source order and the sidebar always follows it, so "the day view
   comes first, always" holds with no CSS at all below this breakpoint --
   including at 768px, deliberately: a sidebar beside the day at that width
   would crowd the timeline, the owner's own original complaint. Only past a
   genuinely wide viewport do the two sit side by side, at which point
   .app-main itself widens too (below) so the day column keeps a sane
   reading width rather than the sidebar simply stealing space from it. */
.day-layout { display: flex; flex-direction: column; gap: 1.25rem; }
.day-sidebar { display: flex; flex-direction: column; }
@media (min-width: 64rem) {
  .app-main { max-width: 72rem; }
  .day-layout { flex-direction: row; align-items: flex-start; }
  .day-main { flex: 2 1 0; min-width: 0; }
  .day-sidebar { flex: 1 1 18rem; min-width: 18rem; }
  /* The mockup's own ui-desk step for .dayhead h1 / .card-h .hh, plus its
     own ui-desk step for .cnt-v -- the one number on this screen that
     stays the biggest thing on the card at any width, just a notch
     smaller once the card itself has more room either side of it. */
  .day-date { font-size: 36px; }
  .time-value { font-size: 32px; }
  .cnt-v { font-size: 36px; }
}

.settings-list { list-style: none; margin: 0 0 0.85rem; padding: 0; font-size: 12.5px; }
.settings-list li { padding: 0.3rem 0; border-bottom: 1px solid var(--seam); }
.settings-list li:last-child { border-bottom: none; }

/* Task 3c used to merge .outcome-form's three buttons into one bordered
   segmented control past 48rem. Dropped: the mockup's own .acts keeps
   three separate, gapped buttons at every width, and merging them here
   forced every .btn inside the group to `border-color: transparent`,
   which would silently strip the 2px --lime-ink border a lime-filled
   .btn-primary needs the moment one lands in this group. .outcome-form's
   own base rule (further up: display:flex; flex-wrap:wrap; gap:0.5rem)
   already matches the mockup's .acts and needs no desktop override at
   all -- Task 2's own width rule on its children (also further up:
   flex: 1 1 100% / 1 1 0) is likewise the same rule at every width, on
   purpose: requirement 5 holds at 375, 768 and 1280px alike. */
/* A seat never reflows to a row at any width -- the mockup's own .seat
   has no ui-desk override either, and requirement 8 (group sessions
   readable at 375px, not sprawling at 1280px) is best served by the same
   simple stack (.seat-h, then the controls or the outcome label full
   width) everywhere. That is what .attendee-row's own base rule already
   does, so there is nothing to write here: a 48rem block turning the seat
   horizontal, and the .day-main override that existed only to defeat it
   on the real screen, both went out with Task 5 -- the landing hero was
   the last consumer of that reflow and no longer mocks the row markup. */

/* No narrow-viewport override of the gutter width: .app-main's 1rem side
   padding (32px) plus the 5.25rem time column and 1rem column gap (100px)
   leaves 188px for the body column even at a 320px viewport -- still
   comfortable for a client's name, package status and the outcome
   buttons. */

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}

/* Pregled (Teretana task 3) ----------------------------------------------- */

/* "Paketi koji ističu": a .client-row that also carries a package's own
   detail line underneath the client's name, in .say's own quieter
   register -- .client-row's own <a> is display:flex (a full-width row on
   its own), so this second line simply falls onto the next line inside
   the same <li>, no new layout primitive needed. */
.client-row .say { display: block; padding: 0 0 0.6rem; }

/* "Nedavni termini" is a log, not a list of links. Every other
   .client-row holds an <a> whose own 44px min-height and 0.5rem padding
   give the row its vertical rhythm; this one holds a date and the
   outcome recorded against it, and nothing else -- so it inherited no
   padding at all. The tag ended up flush against the row's own --seam
   divider with the next date flush under it, which read as the outcome
   belonging to the date below rather than the one above. Date left,
   outcome right on one line where the column is wide enough, wrapping
   to two on a phone, with the row's own padding restored so a pair
   reads as one record. */
.client-row-log {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem 1rem;
  min-height: 44px;
  padding: 0.6rem 0;
}
.client-row-log .say { padding: 0; }

/* The "see all of these on Pregled" link that closes a sidebar panel.
   Its own button border sat a default paragraph margin away from the
   last row's --seam divider, close enough that the two lines read as
   one doubled rule rather than as a list that had ended and an action
   that followed it. The list needs to finish before the action starts.
   */
.list-more { margin: 1rem 0 0; }

/* Fixed-height wrapper so Chart.js has real pixels to measure the moment
   pregled.js clears its own [hidden] -- a canvas with no drawn content
   would otherwise report a 0-height box and never lay out at all.
   [hidden] itself needs no rule here: the HTML spec's own default
   (display: none) is enough, and pregled.js only ever clears the
   attribute after Chart.js has something to draw -- so a coach with
   JavaScript off, or a coach whose chart.umd.js request simply fails,
   sees the .figures table below and nothing else, never an empty box. */
.chart-wrap { height: 220px; margin: 0 0 1rem; }
.chart-wrap canvas { width: 100%; height: 100%; }

/* Every number a chart shows, printed as text too (five things that will
   bite you, item 5) -- .figures-wrap's own scroll is this file's usual
   guard for anything that can outgrow a phone's width, same idea as the
   timeline's own gutter. */
.figures-wrap { overflow-x: auto; }
.figures { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.figures th, .figures td {
  text-align: left;
  padding: 0.45rem 0.75rem 0.45rem 0;
  border-bottom: 1px solid var(--seam);
  white-space: nowrap;
}
.figures th {
  color: var(--ink2); /* 4.5:1 on both grounds -- see :root's own comment */
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .05em;
  text-transform: uppercase;
}

/* Fault 9: the overview's own row of big numbers, the mockup's .bigs /
   .big / .big-v / .big-k / .big-d. "Brojka je krupna jer je brojka ono
   što se gleda" -- the number is what the coach opened this page for, so
   it opens the page. This replaces .overview-big-figure, which was the
   same idea applied to one figure only, and applied to it in a panel at
   the very BOTTOM of the page, under two charts and two tables.
   Two columns on a phone and three past a tablet: the mockup's own grid
   is 2/4, and this page has three tiles' worth of data (see the note in
   overview.gohtml for why not four).
   .big-v.hi colours the hero figure --lime-ink -- text only, no fill, so
   it needs no border and is already proven at 4.5:1 on --surf
   (TestAccentTextMeetsWCAG45). 40/46 is an already-approved pair in the
   type scale (see the file banner), bumping at the same breakpoint
   .day-date and .cnt-v do -- not a new size. */
.bigs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
  margin: 0 0 1.25rem;
}
/* An odd last tile spans the row rather than sitting alone beside a gap
   -- three tiles in a two-column grid is exactly this case at phone
   width. */
.big:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (min-width: 48rem) {
  .bigs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .big:last-child:nth-child(odd) { grid-column: auto; }
}
.big {
  background: var(--surf);
  border-radius: var(--radius-lg);
  padding: 1.25rem;
}
.big-v {
  font-size: 40px;
  font-weight: 800;
  letter-spacing: -.05em;
  line-height: 1;
  margin: 0;
}
.big-v.hi { color: var(--lime-ink); }
.big-k { font-size: 13.5px; font-weight: 650; color: var(--ink2); margin: 0.5rem 0 0; }
.big-d { font-size: 12.5px; color: var(--ink2); margin: 0.15rem 0 0; }
@media (min-width: 64rem) {
  .big-v { font-size: 46px; }
}

/* Teretana Task 4: shared page heading ------------------------------------
   "Klijenti", "Novi klijent", "Postavke" -- every page that is not the day
   screen (which keeps its own .day-header/.day-date/.day-count, unchanged)
   gets this same title-plus-subheading pair, so a coach moving between
   pages sees one heading convention, not the day screen's own plus a
   second invented one. Sizes are the exact .day-date/.day-count steps
   (already on the approved scale -- see the file banner), not new ones. */
.ph { margin: 0 0 1.25rem; }
.ph h1 { font-size: 28px; font-weight: 800; letter-spacing: -.03em; margin: 0; } /* px, not rem -- see .wm */
.ph-sub { margin: 0.3rem 0 0; color: var(--ink2); font-size: 15.5px; font-weight: 600; }
@media (min-width: 64rem) {
  .ph h1 { font-size: 36px; } /* same breakpoint/step as .day-date */
}

/* Teretana Task 4: Klijenti -- "Tablica" ----------------------------------
   The owner's chosen layout (_planning/trener/design/klijenti-options.html,
   option 2): a sortable, filterable, searchable table over one store query
   (store.CoachScope.Roster) instead of the old two-list-plus-a-form page.
   Every control here is a real GET link or a GET form -- no JavaScript, so
   sorting, filtering and searching all keep working under the CSP and with
   scripts off, like the rest of this app. */

/* The toolbar above the table: filter chips, the "+ Novi klijent" button
   (which now just links to its own route, A4's own change) and the search
   form. Phone-first order (chips, then the full-width button, then the
   search form below); the desktop query re-orders and lets the button
   settle at the trailing edge via margin-left: auto, mirroring the
   mockup's own .bar. */
.bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin: 0.25rem 0 1rem; }
.bar .seg { order: 1; }
.bar .btn-primary { order: 2; flex: 1 1 100%; }
.bar .roster-search { order: 3; flex: 1 1 100%; }
@media (min-width: 48rem) {
  .bar .btn-primary { order: 3; flex: 0 0 auto; margin-left: auto; }
  .bar .roster-search { order: 2; flex: 0 1 20rem; }
}

/* The Aktivni/Arhivirani/Svi filter: a row of real links (never buttons --
   each one is its own GET, not a script-driven toggle), sized like this
   file's other 56px/48px controls (.btn's own two-step target) so it reads
   as part of the same control family rather than a smaller, second one.
   [aria-current="page"] is the active filter's own lime fill, carrying the
   2px --lime-ink border every lime fill in this file does (see .btn-primary,
   above) -- TestLimeAndCoralFillsCarryTheirBorder checks this rule
   specifically. */
.seg { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.seg a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: 0 1rem;
  border: 2px solid var(--edge);
  border-radius: var(--radius);
  color: var(--ink2);
  text-decoration: none;
  font-weight: 700;
  font-size: 15.5px; /* px, not rem -- see .wm */
}
.seg a:hover, .seg a:focus-visible { color: var(--ink); border-color: var(--ink); }
.seg a[aria-current="page"] { background: var(--lime); color: var(--on-fill); border-color: var(--lime-ink); }
@media (min-width: 48rem) { .seg a { min-height: 48px; } }

/* The search form: a plain GET, composing with whatever filter/sort is
   already active via three hidden fields (SearchFilter/SearchSort/SearchDir
   in the Go view) -- see coach_clients_roster.go's own comment on why this
   never needed a query-string library beyond net/url. */
.roster-search { display: flex; gap: 0.5rem; margin: 0; }
.roster-search input[type="search"] { flex: 1 1 auto; }

/* The table itself: a raised header (--rais, the same surface the day
   screen's own .card-h uses for a session's facts) that sticks while the
   body scrolls past it, a real --surf card underneath. */
/* overflow-x: auto, not hidden. The radius needs the box to clip, but
   `hidden` clips a too-wide table *and refuses to scroll to the rest of
   it* -- measured at 375px, a 366px table inside a 343px box, with 23px
   of a client's own name simply unreachable. The six c-* columns above
   already collapse on a phone; what still overflows is a single long
   unbroken name or address, which is a person's actual name and cannot
   be shortened away. overflow-y stays hidden so no vertical scrollbar
   appears and the corners still clip. */
.tbwrap { background: var(--surf); border-radius: var(--radius-lg); overflow-x: auto; overflow-y: hidden; }
.tb { width: 100%; border-collapse: collapse; font-size: 15.5px; }
/* padding lives on the th itself (a plain-text header, the package
   table's own kind, needs it directly); a sortable header's <a>
   (roster table) covers the same box with a negative margin so its own
   whole cell stays the click target, not just the text inside the
   padding. */
.tb th {
  background: var(--rais);
  text-align: left;
  padding: 11px 14px;
}
.tb th a {
  display: block;
  margin: -11px -14px;
  padding: 11px 14px;
  color: var(--ink);
  text-decoration: none;
  font: inherit;
  font-weight: 700;
}
.tb th[aria-sort] a { text-decoration: underline; text-decoration-color: currentColor; text-underline-offset: 3px; }
/* The sorted column's own direction, drawn where the coach is already
   looking -- in the header they just clicked. aria-sort (on the th, the
   element that actually carries the columnheader role) says the same
   thing to a screen reader, so this arrow is aria-hidden rather than
   duplicated into the accessible name. It points the way the column
   reads down the page: descending, largest first, points down. No new
   type size -- it inherits the header's own, at the weight the rest of
   the header already has. */
.tb th .sort-ar { margin-left: 0.35em; }
.tb th.tb-num .sort-ar { margin-left: 0.3em; }
.tb th.tb-num, .tb td.tb-num { text-align: right; }
.tb td { padding: 13px 14px; border-top: 2px solid var(--seam); vertical-align: middle; }
.tb tbody tr:first-child td { border-top: 0; }
/* "Ime": the name is the largest thing in its own cell -- the same
   requirement 2 logic the day screen's .client-name follows (a coach
   thinks in people first), just at a size that fits a table row rather
   than a card. */
.tb .nm { font-size: 18px; font-weight: 750; letter-spacing: -.01em; color: var(--ink); text-decoration: none; }
.tb .nm:hover, .tb .nm:focus-visible { text-decoration: underline; text-underline-offset: 3px; }
/* .sub is the phone layout's second line under the name -- hidden on
   desktop, where .c-tip/.c-next carry the same facts in their own columns
   instead (see the breakpoint below). */
.tb .sub { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink2); margin-top: 3px; }
/* .warn/.dim keep their own colour inside .sub (the package table's phone
   line needs the identical expiry/naplata colouring its desktop columns
   get, not a paler copy), but not their standalone size or weight -- both
   would read as a random mid-sentence size bump inside one small line. */
.tb .sub .warn, .tb .sub .dim { font-size: inherit; font-weight: inherit; }
/* "Preostalo": the mockup's own large, bold figure -- .cnt-v's own logic
   (coach_day.gohtml) in a table cell: the number a coach scans this column
   for is the loudest thing in it, coral at 3 or fewer. */
.tb .rem { font-size: 21px; font-weight: 800; letter-spacing: -.03em; color: var(--ink); }
.tb .rem.low { color: var(--coral-ink); }
/* A dead package's own Preostalo (option 2, paketi-options.html): expired
   or exhausted demotes to --ink2 even when sessions are technically still
   left (an expired package with 2 remaining is not "3 or fewer and
   urgent", it is over) -- buildPackageRowView (coach_packages.go) only
   ever sets .low on a live package, so .off and .low never collide on the
   same cell. */
.tb .rem.off { color: var(--ink2); }
.tb .dim { color: var(--ink2); font-size: 13.5px; font-weight: 600; }
.tb .warn { color: var(--coral-ink); font-weight: 700; font-size: 13.5px; }
.tb tr.arch .nm { color: var(--ink2); }
.tb-cap { margin-top: 0.6rem; }

/* Teretana -- Paketi (option 2, paketi-options.html): the client detail
   page's own package table reuses .tbwrap/.tb wholesale (the roster
   table's own component, coach_clients.gohtml) rather than inventing a
   second one -- same raised header, same seam rule, same phone/desktop
   column split. .tb-tp is this table's own "Ime" analogue: the package's
   type name is the largest thing in its own cell, .tb .nm's own logic at
   the same size, just not a link (a package has no page of its own to
   link to from here -- "Ispravi", the one link this row does carry, has
   its own .go treatment below). */
.tb .tb-tp { display: block; font-size: 18px; font-weight: 750; letter-spacing: -.01em; color: var(--ink); }
/* A dead row's own type name demotes the same way a package's own .p1-dead
   title does elsewhere in this app -- .tb tr.arch .nm's identical logic,
   just keyed to "dead" (a row status, not an archived client) rather than
   reusing that name for an unrelated meaning. */
.tb tr.dead .tb-tp { color: var(--ink2); font-weight: 700; }
/* The note, when a package has one: a third muted line in the same cell,
   between the type name and the phone-only .sub summary.
   overflow-wrap: anywhere is naplata-options.html's own recipe (its local
   CSS carries this on .tb-note already; this shared rule did not, because
   every OTHER table's own note was short enough never to need it) --
   naplata's own note lines combine an e-mail address with a tier, or a
   client count with a billing date and a proration note, and an e-mail's
   "marko@fitnesmarko.hr" has no space to break on at all. Without this, a
   375px column has to grow to fit that one unbroken run whole, which
   measured out to roughly DOUBLE the overflow naplata-options.html itself
   measured and accepted (~179px against a documented ~70-90px) before
   this was added. Harmless everywhere else: it only changes where a line
   breaks once content already does not fit its column, never a cell that
   already fits. */
.tb .tb-note { display: block; margin-top: 2px; overflow-wrap: anywhere; }
/* "/ 10": the total a live package's own Preostalo is measured against,
   in the same cell but deliberately quieter than .rem -- the coach reads
   the big number first. */
.tb .of { font-size: 13.5px; font-weight: 600; color: var(--ink2); }
/* "Ispravi": the one link a package row carries now, in --lime-ink (the
   mockup's own action colour, already proven at 4.5:1 -- TestAccentTextMeetsWCAG45)
   rather than --ink, so it reads as the row's one available action, not
   as more data. */
.tb .go { font-size: 15.5px; font-weight: 750; color: var(--lime-ink); text-decoration: none; white-space: nowrap; }
.tb .go:hover, .tb .go:focus-visible { text-decoration: underline; text-underline-offset: 3px; }

/* Phone: the table does not fit five columns (the mockup's own "što
   košta"), so Tip and Sljedeći termin move under the name as .sub and their
   own columns disappear; Preostalo and Vrijedi do stay, since those are the
   two figures a coach actually scans this table for between sessions. The
   package table's own three extra columns (Iskorišteno, Vrijedi do,
   Naplata) fold under the type name the identical way, into the same .sub
   line -- one phone rule for both tables rather than a second copy of it.
   The operator's own Treneri list (operator_coaches.gohtml) reuses .c-email
   for the identical reason: E-pošta already reads under the name as .sub,
   so its own column would just repeat it until there is room to spare. */
.tb .c-tip, .tb .c-next, .tb .c-used, .tb .c-until, .tb .c-paid, .tb .c-email,
.tb .c-clients, .tb .c-money, .tb .c-arrived { display: none; }
@media (min-width: 48rem) {
  .tb .c-tip, .tb .c-next, .tb .c-used, .tb .c-until, .tb .c-paid, .tb .c-email,
  .tb .c-clients, .tb .c-money, .tb .c-arrived { display: table-cell; }
  .tb .sub { display: none; }
  .tb { font-size: 15.5px; }
}

/* Interes (operator_interest.gohtml) -- the checkbox column. Narrow and
   centred at every width, unlike every c-* column above: a control an
   operator needs to reach is not something this table folds away on a
   phone the way it folds a merely informational column into .sub.
   input[type="checkbox"] already carries a 2px solid var(--edge) border
   from the shared "input, select" rule (nothing here overrides it), which
   is what clears the 3:1 non-text floor a control's own boundary needs --
   TestNonTextBoundariesMeetWCAG311 checks that rule generically, not this
   column specifically, because nothing about a checkbox's OWN contrast
   differs from every other input this file already proves compliant. */
.tb .c-sel { width: 2.75rem; text-align: center; padding-left: 10px; padding-right: 10px; }
/* The header cell starts empty (no dead "select all" control shipped for a
   click that could not do anything without a script) -- interest-select.js
   is what actually puts a checkbox in here, the moment it can honour one. */
#sel-all-cell { vertical-align: middle; }

/* .sel-count is interest-select.js's own live "(N)" suffix on each submit
   button -- hidden by default (both in markup, via the [hidden] attribute,
   and functionally: with the script absent nothing ever unhides it), so a
   no-script reader sees a plain "Označi odabrane kontaktiranima" with
   nothing dangling after it. */
.sel-count { font-weight: 700; }

/* The bulk-mark-all-shown form is one line, one button -- no chips or
   search box beside it, so it does not need .bar's own flex-ordering
   rules (those only matter once a primary button has to share a row with
   a search form). */
.bulk-mark-form { margin: 0 0 0.75rem; }

.interest-actions { margin-top: 1rem; }

/* Naplata (operator_billing.gohtml) -- naplata-options.html Option 1,
   "Tri stupca za iznos". Osnovica, PDV and Klijenti each get their own
   column (.c-money / .c-clients, the same hide-below-48rem shape .c-email
   already uses above) once there is room; below that they do not vanish,
   they fold into a phone-only note in the column that stays -- the exact
   trade .sub/.c-tip already makes for the roster table, applied to money
   instead of a name. .money-fold carries Osnovica+PDV under Ukupno's own
   <strong>; .clients-fold carries the client count as a prefix on the
   period cell's own note, in the words the hidden Klijenti column would
   have shown as a number. Both are plain inline text, not a block like
   .sub, because each sits inside a .tb-note line that already reads as
   one sentence -- "5 klijenata · obračun 1. rujna 2026." -- not as its
   own paragraph. */
.tb .money-fold, .tb .clients-fold { display: inline; }
@media (min-width: 48rem) {
  .tb .money-fold, .tb .clients-fold { display: none; }
}
/* .money-fold's own text is nothing BUT short words and numbers already
   separated by ordinary spaces ("Osnovica 20,00 € · PDV 25 % 5,00 €"),
   with the one run that must never break (an amount and its € symbol)
   already glued by formatEuroCents' own non-breaking space, not by
   omitting a space here -- so it never needed the .tb-note parent's
   overflow-wrap: anywhere (above), which exists for the Trener cell's own
   unbroken e-mail address. Inheriting it anyway let the line-breaking
   algorithm break "Osnovica" itself ("Osnovic"/"a") rather than wrapping
   at the ordinary space right after it -- normal restores "break only at
   a space," which is all this line ever needed. */
.tb .money-fold { overflow-wrap: normal; }

/* The Izdano column carries the chip and nothing else.
   
   It briefly held a disabled checkbox beside the chip as well, on the
   reasoning that a checkbox reads as state. The owner removed it: the chip
   already says "Izdano" or "Nije izdano" in words and in colour, so the
   checkbox restated it while also being the one element on the page a
   person would try to click -- and it could never respond, since this page
   carries no script (CSP's script-src 'self', and a template-scanning test
   fails on an inline one). The button in .act-cell, in its own column and
   its own form, is the only control that acts. */

/* .act-cell: the button, now alone in its own column. max-width keeps the
   longer "Poništi izdano" label from stretching the button across a column
   sized for the shorter "Označi izdano" -- .stcell's own reasoning, kept
   for the one child it now actually has. */
.act-cell { max-width: 11em; }
.act-cell form { margin: 0; max-width: 100%; }
.act-cell .btn { white-space: normal; text-align: center; width: 100%; }

/* Teretana -- Ispravi paket (option 2's own edit page) -------------------
   The one place a package's own total, expiry and paid flag are changed
   now, replacing the old per-row forms. .crumb is a plain back-link at
   44px (a touch target, not styled as a button); .facts is the read-only
   box above the form -- Iskorišteno/Preostalo/Vrijedi do/Napomena --
   giving the coach the numbers the form's own inputs need without
   repeating them as a second, editable copy of the same field (fault 1's
   "the expiry stood twice" is exactly what this box does not reintroduce:
   .facts shows it, the field below edits it, never both as inputs). */
.crumb {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: 0 0 0.5rem;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ink2);
  text-decoration: none;
}
.crumb:hover, .crumb:focus-visible { color: var(--ink); }
.facts {
  border: 2px solid var(--edge);
  border-radius: var(--radius-lg);
  padding: 0.8rem 1rem;
  margin: 0 0 1.1rem;
}
.facts div {
  display: flex;
  justify-content: space-between;
  gap: 0.9rem;
  padding: 0.3rem 0;
  font-size: 13.5px;
  font-weight: 650;
  color: var(--ink2);
}
.facts div b { color: var(--ink); font-weight: 800; }

/* Teretana Task 4: Postavke -- "Odjeljci" ---------------------------------
   The owner's chosen layout (_planning/trener/design/postavke-options.html,
   option A): three real sections (Pravila, Radni sati, Tipovi treninga),
   each its own card with its own Spremi, plus an anchor nav to jump between
   them -- a sticky left column on desktop, a horizontal chip row on phone.
   No server change: every section still posts to the same route it always
   did (handleRulesSave still reads "samo_sati" to tell the working-hours
   form's own POST apart from the rules form's), this is presentation only. */
.pg2 { display: block; }
@media (min-width: 64rem) {
  /* Same breakpoint .day-layout uses for its own sidebar, and the same
     reasoning: a coach's viewport has to actually be wide enough to hold a
     226px rail beside a real form before this is worth doing at all. */
  .pg2 { display: grid; grid-template-columns: 14rem minmax(0, 1fr); gap: 1.5rem; align-items: start; }
}
/* padding/negative margin for the same reason .tabs has it: this row is
   a horizontal scrollport on a phone, and a scrollport clips the focus
   ring of the chips inside it. */
.anav-w { margin: -6px -6px calc(1rem - 6px); overflow-x: auto; padding: 6px; }
.anav { display: flex; gap: 0.5rem; }
.anav a {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  min-height: 56px;
  padding: 0 1rem;
  border: 2px solid var(--edge);
  border-radius: var(--radius);
  color: var(--ink2);
  text-decoration: none;
  font-weight: 700;
  font-size: 15.5px; /* px, not rem -- see .wm */
}
.anav a:hover, .anav a:focus-visible { color: var(--ink); border-color: var(--ink); }
/* Fault 4's second half: the section the reader is currently scrolled to.
   sections.js sets aria-current="location" -- "location" and not "page",
   because the target is a section WITHIN this page, not another page --
   and this is what makes that visible. Same lime fill + --lime-ink border
   .seg a[aria-current="page"] uses for the roster's own current filter,
   so "this is the one you are on" looks the same everywhere in this app.
   The attribute is the only hook: with no JavaScript it is never set,
   nothing here matches, and the nav renders exactly as it did before. */
.anav a[aria-current] {
  background: var(--lime);
  color: var(--on-fill);
  border-color: var(--lime-ink);
}
.anav a[aria-current]:hover { color: var(--on-fill); border-color: var(--lime-ink); }
@media (min-width: 64rem) {
  .anav-w { overflow: visible; margin: 0; padding: 0; }
  .anav { flex-direction: column; gap: 0.4rem; }
  .anav a { min-height: 46px; }
}
/* Fault 4's first half: the menu must stay put while the right column
   scrolls. `position: sticky` used to sit on .anav, which does not work
   and never did -- a sticky box cannot travel outside its own parent's
   CONTENT box, and .anav's parent .anav-w is a grid item sized to its
   content, so .anav had exactly zero room to move in. The sticky must be
   on the grid ITEM: its containing block is then the grid AREA, which
   spans the full height of the row, so the nav has the whole column to
   travel down. `align-self: start` keeps the item itself content-height
   (the grid's own `align-items: start` already does this, and it is
   repeated here so the rule survives a change to the container).

   This rule must stay OUTSIDE the breakpoint above and carry its own
   media condition: it shares .anav-w's selector with the plain rule
   further up, and a same-specificity tie is broken by source order, so a
   later plain rule would otherwise win over an earlier media-queried one
   even while its condition holds. */
@media (min-width: 64rem) {
  .anav-w { position: sticky; top: 1rem; align-self: start; }
}
/* An anchor jump lands the section's own top flush with the viewport's
   top edge; the offset gives it the same 1rem of air the sticky nav
   keeps. No smooth-scroll is introduced anywhere: an anchor jump stays
   the instant jump it already was, so there is no new motion for a
   prefers-reduced-motion reader to have to opt out of. */
.sec { scroll-margin-top: 1rem; }

.sec { background: var(--surf); border-radius: var(--radius-lg); padding: 1.25rem; margin: 0 0 1rem; }
.sec > h2 { font-size: 19px; font-weight: 800; letter-spacing: -.02em; margin: 0; }
.sec > .hint { margin: 0.3rem 0 1rem; max-width: 62ch; }

/* A labelled sub-block inside a section -- today only Pretplata's
   read-only "Podaci za naplatu". It was a bare <p class="say">, which
   carries a bottom margin and no top one, so the label sat flush under
   whichever line the section had just finished and read as a continuation
   of it rather than as the start of something new. A heading needs room
   above it more than below: that space is what says the previous thing
   has ended.

   Its trailing .hint gets the same treatment for the same reason -- a
   note about the block reads as part of the block only if it is nearer
   to it than to whatever follows. */
.sec-sub { margin: 1.75rem 0 0.6rem; }

/* Zatvoreni dani -- its own section since 2026-09-14.
   
   The new-closure form had labels beside their fields (.field-row), which
   works for a short numeric field and not for these: the note was squeezed
   into a few characters with its placeholder cut mid-word, and the submit
   button ended up pressed against it. Labels go above instead, the two
   dates read as the one pair they are, and the note gets the width it
   needs. */
.cl-dates { display: flex; flex-wrap: wrap; gap: 0 1.25rem; }
.cl-dates .field { flex: 0 1 auto; }
.cl-new { margin: 1.25rem 0 0; }
.cl-new .field { margin: 0 0 0.75rem; }

/* A closure that already exists: the dates are the row's subject and the
   note qualifies them, so the note sits under rather than beside. Remove
   stays right on a wide row and wraps under on a phone, like every other
   .tt-r action. */
.cl-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem 1rem; justify-content: space-between; }
.cl-what { min-width: 0; }
.cl-when { margin: 0; font-size: 18px; font-weight: 700; }
.cl-note { margin: 0.15rem 0 0; color: var(--ink2); font-size: 13.5px; font-weight: 600; }
.cl-row form { margin: 0; }
.sec-sub + .tbwrap, .sec-sub + .empty { margin-top: 0; }
.sec-sub-note { margin: 0.75rem 0 0; }
/* Each section's own Spremi and its fine print, separated from the fields
   above it the same way a card's own seam elsewhere in this file is. */
.sec-f {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 2px solid var(--seam);
}
.sec-f .fine { font-size: 12.5px; color: var(--ink2); }

/* Pravila's three number fields: one column on phone, three across on a
   wide enough screen -- .field's own width:100% (a plain <p>) already caps
   at 22rem past 48rem (Forms/panels, above), so this only needs to lay the
   three out side by side, not also constrain their width again. */
.grid3 { display: grid; grid-template-columns: 1fr; gap: 0 1rem; }
@media (min-width: 48rem) {
  .grid3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Radni sati: one row per day. A definition-list shape (name, then its own
   hours and type toggles) rather than the old flat field-row, so a day with
   no hours set ("Slobodan dan") reads as a real sentence instead of two
   empty inputs. */
.dg { display: flex; flex-direction: column; gap: 0.6rem; }
.dg-r {
  background: var(--bg);
  border-radius: var(--radius);
  padding: 0.85rem 0.9rem 0.9rem;
}
.dg-r .dn { display: block; font-size: 15.5px; font-weight: 750; margin-bottom: 0.5rem; }
.dg-r .hrs { display: flex; gap: 0.6rem; align-items: center; }
.dg-r .hrs input { min-height: 48px; }
.dg-r .tgs { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.6rem; }
@media (min-width: 48rem) {
  .dg-r {
    background: transparent;
    padding: 0.6rem 0;
    border-top: 2px solid var(--seam);
    display: grid;
    grid-template-columns: 8rem 14rem minmax(0, 1fr);
    gap: 1rem;
    align-items: center;
  }
  .dg-r:first-child { border-top: 0; padding-top: 0; }
  .dg-r .dn { margin-bottom: 0; }
  .dg-r .hrs input { max-width: 6rem; }
  /* Both of these belong to the stacked phone layout and have to be undone
     once the row becomes a grid, exactly as .dn's own margin above already
     is. Without them the type pills sat 6.8px below the time inputs (the
     0.6rem stack gap, plus half of it again from align-items:center
     measuring the wrapper's margin box) and stood 4px shorter -- so the
     row read as two bands at slightly different heights rather than one.
     Measured, not guessed: the offset is 0 at 768px and 1280px now. */
  .dg-r .tgs { margin-top: 0; }
  .dg-r .tg span { min-height: 48px; }
}

/* A session-type toggle pill: a checkbox styled as a chip, the same lime
   fill + --lime-ink border every "this is currently on" control in this
   file uses, and the input itself stays in the DOM (a real checkbox,
   opacity:0 over the whole pill) so this stays a native, JS-free control --
   the pill is just the label's own paint job, not a replacement widget. */
.tg { position: relative; display: inline-flex; cursor: pointer; }
.tg input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.tg span {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.9rem;
  border: 2px solid var(--edge);
  border-radius: var(--radius);
  color: var(--ink2);
  font-size: 13.5px;
  font-weight: 700;
}
.tg input:checked + span { background: var(--lime); color: var(--on-fill); border-color: var(--lime-ink); }
.tg input:focus-visible + span { outline: 3px solid var(--lime-ink); outline-offset: 2px; }

/* Tipovi treninga: each existing type keeps its own inline Spremi (the
   route is per-type, unchanged); the "add a new one" row sits below its own
   seam, exactly like the Postavke page's other "add new" rows already did
   before this task. */
/* .tt-r's only child is .package-adjust-form, already its own flex row
   (Forms/panels, above) -- this just adds the seam and spacing between
   rows, the flex layout itself is not duplicated here. */
.tt { display: flex; flex-direction: column; gap: 0.75rem; }
.tt-r { border-top: 2px solid var(--seam); padding-top: 0.75rem; }
.tt-r:first-child { border-top: 0; padding-top: 0; }
.tt-new { margin-top: 1rem; padding-top: 1rem; border-top: 2px solid var(--seam); }
/* An archived row's own tag, above its Naziv/Kapacitet form -- bare .tag
   (neutral --ink2) is enough on its own to read "archived" at a glance,
   the same signal ArchivedTag gives a client row (coach_clients.gohtml);
   no second colour cue is layered on top of it, since a text label a
   screen reader announces is the part doing the actual work here, not a
   tint that would only ever reach a sighted reader anyway. */
.tt-tag { margin: 0 0 0.5rem; }
/* The row's own Naziv/Kapacitet values dim to --ink2 once archived, the
   same treatment .tb tr.arch .nm already gives an archived client's name
   (Klijenti table, above) -- a second, weaker cue layered under the .tag
   itself, so the row still reads as archived even at a glance that lands
   on the input text before the tag above it. */
.tt-r-archived input { color: var(--ink2); }
/* Ukloni/Vrati iz arhive sits below the Naziv/Kapacitet form as its own
   small form, not squeezed into .package-adjust-form's own flex row (that
   row's button already carries a fixed 7rem basis for "Spremi", and a
   second, differently-labelled button competing for the same row would
   just reopen the alignment bug .tt-r .package-adjust-form > .btn's own
   comment already fixed once). */
/* .tt-action is gone: the row's second action is now a submit button
   inside the row's own form (coach_rules.gohtml), so it sits on the same
   flex line as Spremi instead of wrapping onto a row of its own. */

/* Landing page (Teretana landing plan) -----------------------------------
   The public page at /. It is the one screen in this app with no signed-in
   header above it, so its own wordmark line stands in for one -- .wm and
   .tag are the real header components (Header / nav, above), reused
   as-is rather than re-derived under landing-only names.

   Design is Option 1 ("Poruke"), Varijanta A of
   _planning/trener/design/landing-options.html: the hook is the coach's
   own message thread, not a rendering of the day screen. Every rule below
   still reuses only the tokens already proven at 4.5:1 text / 3:1 non-text
   by css_contrast_test.go's generic (selector-agnostic) checks --
   TestTextMeetsWCAG45 covers --ink/--ink2 on --bg/--surf/--rais for any
   rule that pairs them, TestOnFillMeetsWCAG45 covers --on-fill on --lime,
   TestLimeAndCoralFillsCarryTheirBorder covers any `background:
   var(--lime)` rule -- so the chat bubbles below need no new test of
   their own, only the existing tokens.

   Two new shape values do appear, on the chat bubbles: 16px on three
   corners, 4px on the "tail" corner. Neither is a font size, so the type
   scale note at the top of this file does not apply to them -- they are
   taken doslovno (literally) from the mockup's own already-approved
   system (b-teretana-modes.html), the same way --radius/--radius-lg were.

   Sizes are px, not rem, kept explicit for the same reason .wm gives. */

.landing-brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin: 0 0 1.5rem;
}

/* Hero: the scene, then the argument, then the one CTA -- in that order
   in the markup, so a phone reads chat, then headline, then form. Past
   64rem (the same breakpoint the old hero split on) the two pieces sit
   side by side instead, chat-wrap first in both the markup and the grid,
   so the DOM order never changes, only its rendering. */
.hero {
  display: block;
  margin: 0 0 2.5rem;
}
.hero .copy-wrap { margin-top: 1.75rem; }

/* The chat-wrap's own cap keeps a bubble at something like a phone's own
   measure once the single column gets wide (768px, still one column --
   the two-column grid below does not start until 64rem) -- 26rem is not
   invented for this: it is .panel-narrow's own width, reused here for the
   same reason, "a narrow column on a wide screen still reads like the
   product". */
.chat-wrap { max-width: 26rem; }
.chat {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0 0 1.75rem;
  padding: 0;
  list-style: none;
}
.msg {
  max-width: 86%;
  padding: 0.6rem 0.85rem;
  border-radius: 16px;
  font-size: 15.5px;
  font-weight: 500;
  line-height: 1.4;
}
.msg-in {
  align-self: flex-start;
  background: var(--surf);
  color: var(--ink);
  border-bottom-left-radius: 4px;
}
/* The coach's own messages: muted, --rais/--ink2, the same pairing
   TestTextMeetsWCAG45 already proves at 4.5:1 in both schemes for any
   selector that uses it, not just this one. */
.msg-out {
  align-self: flex-end;
  background: var(--rais);
  color: var(--ink2);
  border-bottom-right-radius: 4px;
  text-align: right;
}
.msg-time {
  display: block;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink2);
  margin-top: 0.25rem;
}
/* The one accent on the page: the product itself, answering inside the
   scene. A lime fill with the lime-ink border every fill in this file
   carries in the same rule (TestLimeAndCoralFillsCarryTheirBorder), text
   in --on-fill -- the same pairing .btn-primary uses, already proven at
   4.5:1 by TestOnFillMeetsWCAG45. */
.msg-app {
  align-self: flex-end;
  background: var(--lime);
  color: var(--on-fill);
  border: 2px solid var(--lime-ink);
  border-bottom-right-radius: 4px;
  font-weight: 750;
}

/* Teretana's own display voice, the mockup's own h-hook: heavy (800),
   tight (-.04em) and set solid, the same treatment .day-date and .ph h1
   already carry inside the product. The sizes are the approved scale's
   own 30 / 36 / 46 triple (see the file banner), NOT the mockup demo's
   own 28 / 36, which is not a step in this system. */
.hero-title {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -.04em;
  line-height: 1.06;
  margin: 0 0 1rem;
  color: var(--ink);
}
/* The one word the headline turns on. Not italic -- the weight and size are
   already doing the shouting, and an italic at 800 in this face just looks
   like a different font. See landing.gohtml for why coral rather than the
   brand lime. */
.hero-title em { font-style: normal; color: var(--coral-ink); }
.hero-lede {
  font-family: var(--font-ui);
  font-size: 18px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink2);
  margin: 0 0 1.25rem;
  max-width: 48ch;
}
/* The lede's own <code>trener</code>, straight from the mockup: the
   wordmark set lowercase, distinguished from the running sentence around
   it the way an identifier would be -- but never in an actual monospace
   face. This file's own rule (the banner at the top: "no monospace
   anywhere") is that --font-num is the same --font-ui stack under a
   different name, tabular-nums doing the distinguishing instead of a
   font swap; a bare <code> would otherwise silently reach for the UA's
   default monospace, so this pins it back to the one sans stack. */
.hero-lede code {
  font-family: var(--font-ui);
  font-weight: 600;
  color: var(--ink);
}

/* The form: bare fields on the page ground at phone width, exactly
   Varijanta A's own phone frame (.ui-phone .cap has no card at all) --
   the card only appears once there is a second column beside it to set
   it apart from, at the same 64rem the hero itself splits at.

   The same 26rem cap as .chat-wrap, for the same reason: below 64rem this
   is a single column as wide as .app-main gets (up to 56rem), and an
   uncapped field would stretch far past the chat bubbles sitting above
   it -- two different measures reading as two unrelated pieces rather
   than one hero. At 64rem+ the grid column itself is already 26rem, so
   this is a no-op there, not a second, conflicting constraint. */
/* No max-width. The card is as wide as the copy column, so both of its
   edges line up with the headline and the lede directly above it -- that
   is the alignment the owner asked for, and a 26rem cap inside a wider
   column cannot produce it however the contents are padded. */
.signup {
  margin: 0 0 1.25rem;
  background: var(--surf);
  border-radius: var(--radius-lg);
  padding: 1.25rem;
}
/* scroll-margin-top lets an in-page "#email" anchor -- the features
   section's own closing "Zanima me" -- land on the whole card, kicker and
   helper text included, rather than on this field alone with everything
   above it scrolled past the top of the viewport. Measured against the
   real rendered page (a CDP getBoundingClientRect diff between this
   input and .signup itself), not copied from the mockup's own demo
   frame: the gap between the card's own top and the input's is 125.9px
   (7.87rem) at 390px and 102.6px (6.42rem) at 1280px -- 390px is the
   binding case, so 8.5rem, with a little headroom, covers both; the
   mockup's own 8.5rem was tuned to its own gallery-page layout, not this
   one, and only agrees with this by coincidence. */
.signup input { max-width: none; scroll-margin-top: 8.5rem; }
.signup input::placeholder { color: var(--ink2); }
.signup .btn { width: 100%; }
.signup .hint { margin-top: 0.75rem; }
/* The form names the act before it asks for anything, so an email field
   on a product page is not mistaken for the usual thing. */
.signup-kicker {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink2);
  margin: 0 0 0.25rem;
}
.signup-title { font-size: 21px; font-weight: 800; letter-spacing: -.02em; margin: 0 0 0.4rem; }
/* No measure of its own. 34ch was set while the card was capped at 26rem
   and sized to that; once the card grew to the width of the copy column
   the cap survived and broke this one sentence early, with the rest of
   the card's width sitting empty beside it. One short line inside a box
   that already constrains it needs no second constraint. */
.signup-say { color: var(--ink2); margin: 0 0 1rem; }

@media (min-width: 48rem) {
  .hero-title { font-size: 36px; }
}
@media (min-width: 64rem) {
  .hero {
    display: grid;
    grid-template-columns: 26rem minmax(0, 1fr);
    column-gap: 2.5rem;
    align-items: start;
  }
  .hero .copy-wrap { margin-top: 0; }
  .hero-title { font-size: 46px; }
  .hero-lede { font-size: 19px; }
  /* The field recessed one level further than the card it sits on
     (--bg, not --surf), so it still reads as a field rather than as the
     card's own background showing through. */
  .signup input { background: var(--bg); }
}

/* Landing page -- features section (below the hero) ----------------------
   _planning/trener/design/naslovnica-znacajke-v2.html: the owner's
   revised Opcija 1 ("Naizmjenično"), with the section heading, the
   per-row "radi već danas" chip and the "Dolazi kasnije" box all struck.
   Seven rows alternate a real screen beside a paragraph; one sentence and
   one full-width "Zanima me" close the section instead.

   Every .feat-shot borrows real class names wholesale from the template
   named in landing.gohtml's own comment on that row, so it renders with
   this file's real rules rather than a second, parallel copy of them --
   no new selector here changes how a day, a roster, a package, a rules
   panel or a week grid actually looks. What IS new is only the scaffold
   around them (.feat, .feat-row, .feat-copy, .feat-lede, .feat-cta) and
   the divider above the section. Both are already-approved tokens: the
   divider reuses --edge on --bg, the exact pair
   TestChartFillsUseRealTokensMeetWCAG311 already proves clears 3:1 in
   both schemes (it was previously only exercised by pregled.js's chart
   fills; this is the same pair, not a new one to test); .feat-copy's
   13.5px/15.5px text is the approved scale's own steps, not new sizes.

   Column width: 26rem, not the mockup's own 22rem. The mockup measured
   22rem against its own fixed 1020px demo frame; here it reuses .hero's
   own grid column instead of adding a second, slightly different one --
   and the extra room matters once real components with their own
   internal breakpoints (.tb's 48rem column split, .bigs' 48rem 2-to-3
   column jump) sit inside it at real desktop widths a static demo frame
   never actually reaches. A narrower column still would not break either
   -- .tbwrap already scrolls its own overflow horizontally
   (TestWideContentScrollsInsteadOfBeingClipped) -- but 26rem needs it
   less. */
.feat {
  margin: 2.5rem 0 0;
  padding-top: 1.75rem;
  border-top: 2px solid var(--edge);
}
.feat-row {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  margin: 0 0 2rem;
}
/* Belt-and-braces alongside `inert` (landing.gohtml): a browser that for
   any reason ignored `inert` itself would still refuse every click and
   hover inside a mocked screen. `inert` alone already removes the
   subtree from the tab order and the accessibility tree; this rule only
   guards the pointer path. */
.feat-shot { pointer-events: none; }
.feat-copy h2 {
  font-size: 15.5px;
  font-weight: 750;
  color: var(--ink);
  letter-spacing: -.01em;
}
.feat-copy p {
  font-size: 13.5px;
  color: var(--ink2);
  line-height: 1.5;
  margin: 0.4rem 0 0;
}
.feat-lede {
  font-size: 13.5px;
  color: var(--ink2);
  line-height: 1.5;
  margin: 0 0 1.5rem;
  max-width: 52ch;
}
/* Same fix as .hero-lede code above: without this, the bare <code> here
   would fall back to the UA's own monospace font, which the file banner's
   "no monospace anywhere" rule forbids. */
.feat-lede code { font-family: var(--font-ui); font-weight: 600; color: var(--ink); }
.feat-cta { margin: 2rem 0 0.5rem; }
/* Full width, matching .signup .btn above -- the owner's own instruction
   for this button: not a default-width button, the one action at the
   foot of a long page. */
.feat-cta .btn { width: 100%; }

@media (min-width: 64rem) {
  .feat-row {
    display: grid;
    grid-template-columns: 26rem minmax(0, 1fr);
    gap: 2rem;
    align-items: center;
  }
  /* Naizmjenično: every other row swaps which side the shot sits on
     without moving it in the markup -- landing.gohtml's own row order
     (and so its reading order below 64rem, and a screen reader's order at
     any width, since `order` is visual-only) never changes. */
  .feat-row-r .feat-shot { order: 2; }
  .feat-row-r .feat-copy { order: 1; }
}
