/* KRGXion design tokens and component classes.
   Plain CSS. No Tailwind, no build step.
   Linked before main.css and review.css in both documents.
   Version 1.0 */

/* ============================================================
   FONTS
   --k-sans and --k-mono below have named IBM Plex since the tokens were
   written, with nothing behind the name: an unresolvable family falls through
   to the next entry in the stack without a word, which is why the platform ran
   on system fonts and looked the whole time like it was working.
   `complete/`, not `split/`: split cuts each weight into six unicode-range
   files, and Cyrillic is not optional here -- comments on /r/{token} arrive in
   whatever language the client writes in.
   Upright only, and that is a compromise rather than a fact about the
   product. The markup has no <em> and no <i>, but two rules ask for italic
   in CSS -- .param-hint and .admin-page .admin-share-note in main.css -- and
   with no italic face behind them the browser slants the upright one. The
   system fonts had a real italic installed, so this is a change; it is two
   lines of small grey hint text and is settled by looking at it. Sans carries
   700 for <strong>; Mono is never bolded and stops at 600.
   src is relative to this file on purpose: it survives a change of the static
   mount point, an absolute path is tied to one.
   ============================================================ */

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/IBMPlexSans-Regular.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/IBMPlexSans-Medium.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/IBMPlexSans-SemiBold.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/IBMPlexSans-Bold.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/IBMPlexMono-Regular.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/IBMPlexMono-Medium.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/IBMPlexMono-SemiBold.woff2') format('woff2');
}

:root {
  /* --- Surfaces --- */
  --k-app:        #0b0c0d;   /* app background, center column background */
  --k-panel:      #131518;   /* panels: rail, right panel, grid strip */
  --k-panel-2:    #191c20;   /* secondary surface: meta bar, footer */
  --k-input:      #0f1113;   /* input and select background */
  --k-raised:     #22262c;   /* hover, active tab, icon button */
  --k-raised-2:   #2b3037;   /* active segment, avatar */
  --k-scrim:      rgba(12, 13, 15, .78); /* floating chips over the frame */

  /* --- Borders --- */
  --k-line:       #24272c;   /* block and panel dividers */
  --k-line-soft:  #1f2227;   /* inner hairlines, toolbars */
  --k-border:     #2a2e34;   /* input outline */
  --k-border-2:   #2e3238;   /* button and chip outline */
  --k-border-3:   #3a3f46;   /* outline over an image */

  /* --- Text --- */
  --k-text:       #e6e8ea;   /* primary */
  --k-text-2:     #c0c6cc;   /* secondary, field values */
  --k-text-3:     #8b9299;   /* captions, inactive tabs */
  --k-text-4:     #6d747b;   /* meta, group headings, units */
  --k-text-5:     #5f666d;   /* quietest: counters, placeholder */

  /* --- Accent: primary actions and selection only --- */
  --k-accent:     #5f8f5c;
  --k-accent-txt: #ffffff;
  --k-accent-08:  color-mix(in oklab, var(--k-accent) 8%,  transparent);
  --k-accent-12:  color-mix(in oklab, var(--k-accent) 12%, transparent);
  --k-accent-20:  color-mix(in oklab, var(--k-accent) 20%, transparent);
  --k-accent-50:  color-mix(in oklab, var(--k-accent) 50%, transparent);
  /* An OUTLINE that carries a meaning, not a divider and not a fill: a card
     framed by whose it is. Mixed into --k-line rather than into transparent so
     it lands at the weight of a border on every surface it is drawn on, and so
     it can never come out brighter than the thing it frames. Its amber twin is
     --k-amber-line, and the two are only ever used as a pair -- one meaning per
     colour, one colour per side. */
  --k-accent-line: color-mix(in oklab, var(--k-accent) 45%, var(--k-line));

  /* --- Semantics --- */
  --k-amber:      #d3a04e;   /* ratings, "needs changes", unread */
  --k-amber-txt:  #e8d7b6;
  --k-amber-ink:  #1a1608;   /* text on an amber badge */
  /* The muted amber outline, the pair of --k-accent-line above. Deliberately
     NOT #ffcc4d: that value belongs to .vp-scrub-mark (main.css), the 2x12px
     in/out handles on the scrub bar, and it has no second user. Brightness
     follows the size of an element, not its meaning: a 2px handle has to
     shout to be seen at all, while the 24px .k-pin on the frame
     carries its own shadow and does not. The pin is plain --k-amber, and a
     mark on a frame inherits its author's colour and will be drawn in it. An
     outline in the same value as either would make the loud thing and the
     quiet thing look alike. */
  --k-amber-line: color-mix(in oklab, var(--k-amber) 45%, var(--k-line));
  --k-warn-bar:   #7d6a3a;   /* warning bar on the left */
  --k-warn-txt:   #9a9384;
  --k-green:      var(--k-accent); /* approved = accent: one meaning, one color */
  --k-green-txt:  #8fb9a0;
  --k-red:        #b06a66;   /* destructive only */
  --k-red-txt:    #c98a86;   /* removed lines in a diff */
  --k-idle:       #41464c;   /* empty star, "no response" status */

  /* --- Fonts --- */
  --k-sans: 'IBM Plex Sans', system-ui, sans-serif;
  --k-mono: 'IBM Plex Mono', ui-monospace, Menlo, monospace;

  /* --- Radii --- */
  --k-r-tile:   4px;   /* grid tiles, previews */
  --k-r-ctl:    6px;   /* inputs, selects, ghost buttons */
  --k-r-btn:    7px;   /* primary buttons, segmented groups */
  --k-r-pill:   13px;  /* pill chips */

  /* Multiplier for the size tokens, declared ahead of anything that reads it:
     a token is rewritten as calc(px * --k-scale) when a pass touches it anyway,
     which is cheaper than converting the block in one go later. */
  --k-scale: 1;

  /* --- Heights --- */
  --k-h-topbar: 48px;
  --k-h-toolbar:38px;
  --k-h-ctl:    30px;  /* input, select */
  --k-h-btn:    calc(38px * var(--k-scale));  /* primary in the topbar */
  --k-h-btn-lg: 36px;  /* primary in the dock */
  --k-h-chip:   26px;
  --k-h-chip-sm:22px;
  --k-h-tree:   calc(28px * var(--k-scale));  /* tree row */
  --k-h-seg:    24px;  /* segmented group item */

  /* --- Skeleton column widths --- */
  --k-w-rail:   200px;
  --k-w-panel:  320px;

  /* --- Spacing --- */
  --k-sp-1: 4px;  --k-sp-2: 6px;  --k-sp-3: 8px;
  --k-sp-4: 12px; --k-sp-5: 16px; --k-sp-6: 20px;

  --k-shadow-frame: 0 12px 40px rgba(0,0,0,.5);
  --k-shadow-pop:   0 8px 28px rgba(0,0,0,.55);
}

/* ============================================================
   BASE
   ============================================================ */

/* The standard properties, never `::-webkit-scrollbar`: the pseudo-element path
   makes the bars permanently visible on macOS and takes width away from columns
   whose sizes are measured against overlay behaviour.
   `*` AND NOT `html`, and the history of this rule is the reason. On `html` it
   dressed the root scroller and nothing else -- `scrollbar-width` does not
   inherit -- so the public page, the one document that HAS a root scroller,
   came out thin while every panel inside every room kept a system bar. The
   point was never the root: it was the panels. `scrollbar-color` did inherit
   and did reach them, which is exactly what made the miss hard to see, and the
   item was ticked off once on that half-result before a live pass caught it.
   The universal selector is what reaches them. A list of the scrollers was
   refused in its place: there are twenty-five of them today and the list goes
   stale on the next room that gets one.
   Measured cost, and it is a gain: an inner scroller's gutter goes 15px -> 11px,
   so every scrolling panel hands 4px back to its content.
   The two places that HIDE their bars are untouched by the widening.
   `.m-bin-crumbs` and `.m-chips` declare `scrollbar-width: none` at (0,1,0)
   against this rule's (0,0,0) and win on weight, whatever the source order --
   their hiding never rested on non-inheritance, so widening the selector cannot
   take it away. Verified on the stand, not reasoned: both still compute
   `none`. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--k-border-3) transparent;
}

body {
  margin: 0;
  background: var(--k-app);
  color: var(--k-text);
  font: 400 13px/1.4 var(--k-sans);
  -webkit-font-smoothing: antialiased;
}
a { color: var(--k-accent); text-decoration: none; }
a:hover { color: color-mix(in oklab, var(--k-accent) 80%, #fff); }

/* A control does not inherit a family from its parent -- the UA hands it one of
   its own -- so the platform's face stops at the border of every button, field
   and select without this line.
   LONGHAND, NEVER `font: inherit`: the shorthand carries size, weight and
   line-height as well and would move the geometry of every control at once.
   Only the face is in question here. Specificity (0,0,1): anything already
   dressed by a class stays dressed. */
button, input, select, textarea { font-family: inherit; }

/* All numeric values are mono: values, seed, dimensions, file names, IDs, time */
.k-num, .k-file, .k-id { font-family: var(--k-mono); font-variant-numeric: tabular-nums; }

/* ============================================================
   SCREEN SKELETON
   topbar → rail | center (viewer + strip) | right panel
   ============================================================ */

.k-app      { height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
.k-body     { flex: 1; display: flex; min-height: 0; }
.k-rail     { width: var(--k-w-rail); flex: none; background: var(--k-panel);
              border-right: 1px solid var(--k-line); display: flex; flex-direction: column; }
.k-center   { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--k-app); }
.k-side     { width: var(--k-w-panel); flex: none; background: var(--k-panel);
              border-left: 1px solid var(--k-line); display: flex; flex-direction: column; }

/* Panel body is elastic. One real block inside gets flex:1 and absorbs
   the slack — do not add empty spacers. */
.k-side-body { flex: 1; min-height: 0; overflow: hidden;
               padding: var(--k-sp-4); display: flex; flex-direction: column; gap: var(--k-sp-4); }
.k-grow      { flex: 1; min-height: 0; }

/* Contextual dock at the panel footer: "what you are acting on right now" */
.k-dock { flex: none; padding: 10px 12px 12px;
          background: color-mix(in oklab, var(--k-accent) 10%, var(--k-panel-2));
          border-top: 1px solid var(--k-accent-50);
          display: flex; flex-direction: column; gap: var(--k-sp-3); }
.k-dock-title { font: 600 10px var(--k-sans); letter-spacing: .08em;
                text-transform: uppercase; color: var(--k-green-txt); }

/* The heading of a section inside a panel -- "Projects" over the tree. Same
   face as .k-dock-title on purpose and deliberately NOT its colour: the dock's
   green says "the thing you are acting on right now", and a heading that names
   a list is not that. The form is copied, the value is not. */
.k-panel-title { font: 600 10px var(--k-sans); letter-spacing: .08em;
                 text-transform: uppercase; color: var(--k-text-4); }

/* ============================================================
   TOPBAR
   ============================================================ */

.k-topbar { height: var(--k-h-topbar); flex: none; display: flex; align-items: center;
            gap: var(--k-sp-6); padding: 0 14px;
            background: var(--k-panel); border-bottom: 1px solid var(--k-line); }
.k-logo   { display: flex; align-items: center; gap: 9px;
            font: 600 13px var(--k-sans); letter-spacing: .01em; }
.k-logo-mark { width: 17px; height: 17px; border-radius: var(--k-r-tile); background: var(--k-accent); }
.k-tabs   { display: flex; gap: 2px; }
.k-tab    { padding: 6px 11px; border-radius: var(--k-r-ctl);
            font-size: 12.5px; color: var(--k-text-3); cursor: pointer; }
.k-tab:hover      { background: var(--k-raised); color: var(--k-text-2); }
.k-tab[aria-current="page"] { background: var(--k-raised); color: #f2f4f6; font-weight: 500; }
.k-sep    { width: 1px; height: 20px; background: var(--k-border); }
.k-spacer { flex: 1; }

/* ============================================================
   USER MENU
   Belongs to the topbar and is placed by that adjacency, not because
   anything else wants it -- the public page has no such control.
   ============================================================ */

/* The positioning context for the dropdown. flex: none so the bar's spacer,
   and not this, is what gives way when the window narrows. */
.k-usermenu { position: relative; flex: none; }

/* --k-raised-2 is the surface the token block already names for an avatar.
   Deliberately NOT the accent: the accent means "primary action or selection",
   and a picture of who you are is neither. */
.k-avatar { width: 32px; height: 32px; display: flex; align-items: center;
            justify-content: center; border: 0; border-radius: 16px;
            background: var(--k-raised-2); color: var(--k-text);
            font: 600 12px var(--k-sans); cursor: pointer; }
.k-avatar:hover,
.k-avatar[aria-expanded="true"] { background: var(--k-raised); }

/* display:none carried explicitly because the rule below sets display:flex,
   which beats the user agent's own [hidden] rule -- without this the menu is
   open the moment it exists. */
.k-menu[hidden] { display: none; }
.k-menu { position: absolute; top: calc(100% + var(--k-sp-2)); right: 0; z-index: 900;
          min-width: 168px; display: flex; flex-direction: column;
          padding: var(--k-sp-1); border-radius: var(--k-r-ctl);
          background: var(--k-panel-2); border: 1px solid var(--k-border-2);
          box-shadow: var(--k-shadow-pop); }

/* One rule for both genres: Profile is an <a> and Log out is a <button>, and
   the row must not read as two different things. text-align matters only to the
   button, whose UA default is centred. */
.k-menu-item { display: flex; align-items: center; height: var(--k-h-ctl);
               padding: 0 10px; border: 0; border-radius: var(--k-r-tile);
               background: transparent; color: var(--k-text-2);
               font: 400 12.5px var(--k-sans); text-align: left;
               text-decoration: none; cursor: pointer; }
.k-menu-item:hover { background: var(--k-raised); color: var(--k-text); }

/* ============================================================
   BUTTONS
   ============================================================ */

.k-btn { height: var(--k-h-btn); display: inline-flex; align-items: center; justify-content: center;
         gap: var(--k-sp-3); padding: 0 14px; border: 0; border-radius: var(--k-r-btn);
         background: var(--k-accent); color: var(--k-accent-txt);
         font: 600 12.5px var(--k-sans); cursor: pointer; }
.k-btn:hover  { background: color-mix(in oklab, var(--k-accent) 88%, #fff); }
.k-btn:active { background: color-mix(in oklab, var(--k-accent) 88%, #000); }
.k-btn--lg    { height: var(--k-h-btn-lg); font-size: 13px; width: 100%; }

/* Icon-only: the label has left the screen, so the width follows the height
   instead of the text and the box comes out square. Declared here and not in a
   room's stylesheet because the grid toolbar needs the same button. A button
   with no visible label still needs aria-label and title in the markup. */
.k-btn--icon  { width: var(--k-h-btn); padding: 0; }

/* Counter inside primary: ×1, ×4, 6 */
.k-btn-count { height: 22px; display: inline-flex; align-items: center; padding: 0 8px;
               border-radius: 5px; background: rgba(255,255,255,.2);
               font: 500 11px var(--k-mono); }

.k-btn--ghost { background: transparent; color: var(--k-text-2);
                border: 1px solid var(--k-border-2); font-weight: 400; font-size: 12px; }
.k-btn--ghost:hover { background: var(--k-raised); color: var(--k-text); }

.k-btn--ok     { background: var(--k-green); }
.k-btn--danger { background: transparent; color: var(--k-red);
                 border: 1px solid color-mix(in oklab, var(--k-red) 35%, var(--k-line)); }

/* Text action in the toolbar, and since stage 5 the trash rows' Restore and
   Delete, where a bordered button does not physically fit: the rail's working
   minimum is 12% of the gallery panel, about 120px, and two boxes do not go
   into what is left of it after the thumbnail.
   The reset is here because the component was drawn against a mock where the
   element was not a button, and declared only size, colour and cursor. Any
   emitter that IS a button therefore keeps the UA family, a 2px outset
   border, the ButtonFace fill, 1px 6px of padding and centred text -- all
   measured, all visible. The reset undoes exactly those; it costs nothing on
   an emitter that is not a button, so it does not go stale when one appears.
   Same family of miss as .k-seg > * in stage 3a.
   font-family is in the list to stop a button's own UA family beating
   inheritance; it is not a change of typeface. */
.k-link   { appearance: none; -webkit-appearance: none; border: 0; background: transparent;
            padding: 0; text-align: left; font-family: var(--k-sans);
            font-size: 11.5px; color: var(--k-text-3); cursor: pointer; }
.k-link:hover { color: var(--k-text); }
.k-link--danger { color: var(--k-red); }
/* Must come after .k-link:hover, not before it: both are (0,2,0), so the tie
   is broken by source order, and without this rule the destructive action
   turned --k-text under the cursor -- the one moment it has to read as
   destructive. 88% is the lift .k-btn:hover already uses, not a new number. */
.k-link--danger:hover { color: color-mix(in oklab, var(--k-red) 88%, #fff); }

/* ============================================================
   INPUTS, SELECTS, SEGMENTS
   ============================================================ */

.k-label { font: 500 9.5px var(--k-sans); letter-spacing: .09em;
           text-transform: uppercase; color: var(--k-text-4); }
.k-field-row { display: flex; flex-direction: column; gap: 5px; }

.k-input, .k-select {
  height: var(--k-h-ctl); box-sizing: border-box; width: 100%;
  padding: 0 9px; background: var(--k-input);
  border: 1px solid var(--k-border); border-radius: var(--k-r-ctl);
  color: var(--k-text-2); font: 400 12px var(--k-mono);
  appearance: none; outline: none;
}
.k-input:focus, .k-select:focus { border-color: var(--k-accent-50); }
.k-input::placeholder { color: var(--k-text-5); }

/* Select: the chevron is drawn as a shape, not an SVG */
.k-select-wrap { position: relative; }
.k-select-wrap::after {
  content: ''; position: absolute; right: 10px; top: 50%; width: 6px; height: 6px;
  margin-top: -5px; pointer-events: none;
  border-right: 1.5px solid var(--k-text-3); border-bottom: 1.5px solid var(--k-text-3);
  transform: rotate(45deg);
}

.k-textarea { box-sizing: border-box; width: 100%; padding: 9px 10px; resize: none;
              background: var(--k-input); border: 1px solid var(--k-border);
              border-radius: var(--k-r-ctl); color: var(--k-text-2);
              font: 400 11.5px/1.6 var(--k-sans); outline: none; }
.k-textarea:focus { border-color: var(--k-accent-50); }

.k-seg { display: flex; gap: 2px; padding: 2px; background: var(--k-input);
         border: 1px solid var(--k-border); border-radius: var(--k-r-ctl); }
/* border and background are reset for the same reason .k-tab, .k-menu-item,
   .k-caret-btn and .k-tree-btn reset them: the segment's children are
   <button>s, and a button arrives with a 2px outset border and an #efefef face
   from the user agent. The rule was written against the mock's <div>s and the
   reset was a miss, not a decision. */
.k-seg > * { flex: 1; height: var(--k-h-seg); display: flex; align-items: center;
             justify-content: center; border: 0; background: transparent;
             border-radius: var(--k-r-tile);
             font-family: var(--k-sans);
             font-size: 11.5px; color: var(--k-text-3); cursor: pointer; }
.k-seg > [aria-selected="true"] { background: var(--k-raised-2); color: #f0f2f4; font-weight: 500; }

.k-toggle { width: 30px; height: 17px; border-radius: 9px; background: var(--k-border);
            position: relative; cursor: pointer; }
.k-toggle::after { content: ''; position: absolute; top: 2px; left: 2px;
                   width: 13px; height: 13px; border-radius: 7px; background: var(--k-text-3);
                   transition: left .12s ease, background .12s ease; }
/* TWO WAYS OF READING ONE STATE, and the second is what made this component
   wearable at all. It was drawn for the mock's <div role="switch"> keeping its
   state in aria-checked; every switch the platform actually renders is a native
   <input type="checkbox"> keeping it in :checked, because that is what the form
   collects by name. `:checked +` is added rather than the component being
   rewritten around it -- the geometry, the colours and the knob are the same
   ones, and only the question "is it on" is asked a second way.
   NOT the fork that declined .k-check (§16): that one would have had to be
   re-expressed end to end against the other mechanism. This is one selector,
   and it matches nothing except immediately before a .k-toggle. */
.k-toggle[aria-checked="true"],
:checked + .k-toggle { background: var(--k-accent); }
.k-toggle[aria-checked="true"]::after,
:checked + .k-toggle::after { left: 15px; background: #fff; }

.k-slider { flex: 1; height: 3px; border-radius: 2px; background: var(--k-border); position: relative; }
.k-slider-fill { position: absolute; inset: 0 auto 0 0; background: var(--k-accent); border-radius: 2px; }
.k-slider-knob { position: absolute; top: -4px; width: 11px; height: 11px; margin-left: -5px;
                 border-radius: 6px; background: #dfe3e7; }

/* 16px SINCE LIVE ACCEPTANCE, up from 13. Two things came out of it: the square
   was hard to hit, and the box was small enough that the tick inside it was
   near the limit of reading as one. The other half of the fix -- a hit area
   wider than the square -- is on the wrapper below.
   The same 16 .grid-checkbox already is, so the platform's two checkboxes now
   share a footprint and differ only in shape: a circle means "picked" in the
   grids, a square is an ordinary tick. Radius and border width are untouched. */
.k-check { width: 16px; height: 16px; box-sizing: border-box; border-radius: 3px;
           border: 1px solid var(--k-border-3); background: var(--k-input);
           display: flex; align-items: center; justify-content: center; }
/* TWO WAYS OF READING ONE STATE, the same addition .k-toggle above took and for
   the same reason. The mock keeps the state in aria-checked on a <div>; the
   resolve box in the Review room is a native <input type="checkbox"> keeping it
   in :checked, because the click, the Space key and the screen reader come free
   with that -- and because the room's handler both reads `.checked` and writes
   it back when the server refuses. `:checked +` is added rather than the
   component rewritten around it: the geometry, the border and the tick are the
   same ones, and only the question "is it ticked" is asked a second way. It
   matches nothing except immediately after a checked input.
   This does not reopen §16. What was declined there was wearing .k-check for
   the grid tile's ROUND selector lying over a picture, which needed different
   geometry and its own backdrop. A square in a row on a panel is what this
   component already is. */
.k-check[aria-checked="true"],
:checked + .k-check { background: var(--k-accent); border-color: var(--k-accent); }
/* THE TICK IS AN L OF TWO BORDERS TURNED 45 DEGREES, and it scaled with the box:
   6x3 -> 8x4 keeps the 2:1 the two arms had, and the stroke went 1.5 -> 2 because
   the proportion asks for 1.85. Read the stroke against the SHORT arm, not
   against the box: what makes the shape read as a tick rather than a smear is
   the inner corner left over, and that is the short arm minus the stroke. It was
   1.5px at 13 and is 2px now -- the one number to keep an eye on if this is ever
   resized again. 2px is also .grid-checkbox's own stroke-width at the same 16,
   and two checkboxes of one size should not draw at two weights. */
.k-check[aria-checked="true"]::after,
:checked + .k-check::after {
  content: ''; width: 8px; height: 4px; margin-top: -2.5px;
  border-left: 2px solid #fff; border-bottom: 2px solid #fff; transform: rotate(-45deg);
}

/* WHAT MAKES .k-check ABOVE ACTUALLY WORK, and the reason it is here rather
   than in a room: the square is a <span> and cannot be focused, ticked or read
   by a screen reader, so a native <input type="checkbox"> lies over it at full
   size and fully transparent. The click and the Space key stay the browser's
   own, the value stays where a handler expects it, and a handler that writes
   `.checked` back -- to roll a refused change out, say -- still moves the box.
   That last part is why the state cannot simply live in aria-checked on the
   span instead.
   The component and its wrapper are one thing in two rules, the same pairing
   .k-select and .k-select-wrap already use: worn apart, neither half is a
   control.
   THE ORDER OF THE TWO CHILDREN IS PART OF THE CONTRACT. `:checked +` matches
   nothing except immediately after a checked input, so the input comes first
   and the square second, with nothing between them.
   flex: none because a box of a fixed size is never the thing that should give
   way in a row -- the neighbour carrying text is. Without it the wrapper is an
   ordinary flex item with a content basis, and an auto basis shrinks before a
   zero one (§10), so the box would be exactly what collapsed. */
.k-check-wrap { position: relative; display: inline-flex; flex: none; }
/* THE NEGATIVE INSET IS THE POINT OF THIS RULE. DO NOT "FIX" IT BACK TO ZERO.
   The hit area is deliberately bigger than the square it sits on: -4px on each
   side of a 16px box makes it 24x24. Live acceptance found the box hard to hit
   precisely because the two used to be the same size -- a miss by one pixel was
   a miss of the control.
   It costs nothing anywhere else. The input is absolutely positioned, so the
   larger box is outside the flow and moves no neighbour; it is fully
   transparent, so it paints nothing; and the wrapper does NOT clip it, having no
   overflow of its own. What the extra 4px eats into is the padding of whatever
   row the wrapper stands in, which is why it has to be checked per surface
   rather than assumed -- measured at 4px of clearance to the numbered disc in
   the Review comment row and 6px to the thumbnail in the History list row.
   width/height are gone on purpose: a percentage width would beat the left and
   right pair and hold the box at the square's size, which is the bug this rule
   exists to avoid. */
.k-check-wrap input[type="checkbox"] {
  position: absolute; inset: -4px;
  margin: 0; opacity: 0; cursor: pointer;
}
/* The focus ring is drawn on the square, because the element that takes focus
   is the invisible one. */
.k-check-wrap input[type="checkbox"]:focus-visible + .k-check {
  outline: 2px solid var(--k-accent); outline-offset: 2px;
}

/* ============================================================
   CHIPS AND BADGES
   ============================================================ */

.k-chip { height: var(--k-h-chip); display: inline-flex; align-items: center; gap: 7px;
          padding: 0 10px; border-radius: var(--k-r-pill);
          background: var(--k-input); border: 1px solid var(--k-border-2);
          font: 400 11px var(--k-mono); color: var(--k-text-3); }
.k-chip--sm     { height: var(--k-h-chip-sm); padding: 0 9px; border-radius: 11px; }
.k-chip--accent { background: var(--k-accent-20); border-color: var(--k-accent-50); color: var(--k-green-txt); }
.k-chip--amber  { background: rgba(211,160,78,.16); border-color: rgba(211,160,78,.45); color: var(--k-amber-txt); }
/* Chip over the frame */
.k-chip--over   { background: var(--k-scrim); border-color: var(--k-border-3);
                  color: var(--k-text-2); border-radius: 5px; }

.k-dot { width: 6px; height: 6px; border-radius: 3px; background: var(--k-idle); }
.k-dot--live { background: var(--k-green); }
.k-dot--work { background: var(--k-amber); }

.k-note { display: flex; gap: 7px; padding: 7px 9px; border-radius: 5px;
          background: #1a1c1f; border-left: 2px solid var(--k-warn-bar);
          font: 400 11px/1.45 var(--k-sans); color: var(--k-warn-txt); }

/* ============================================================
   TREE
   ============================================================ */

.k-tree      { display: flex; flex-direction: column; gap: 1px; padding: 0 var(--k-sp-3); }
.k-tree-row  { height: var(--k-h-tree); display: flex; align-items: center;
               justify-content: space-between; gap: var(--k-sp-2);
               padding: 0 var(--k-sp-3); border-radius: 5px;
               font-size: 13px; color: var(--k-text-2); cursor: pointer; }
.k-tree-row:hover { background: var(--k-raised); }
.k-tree-row[aria-current="true"] {
  background: color-mix(in oklab, var(--k-accent) 22%, transparent);
  color: #fff; font-weight: 500; box-shadow: inset 2px 0 0 var(--k-accent);
}
.k-tree-row--l2 { padding-left: 20px; color: var(--k-text-3); }
/* ONE INDENT PER LEVEL, and it is this padding. The margin-left that used to
   sit beside it made every step the sum of two values, which on a 200px rail is
   half the width of a name -- and the deeper the bin, the worse. The border is
   the level marker; it does not need a margin to be read. */
.k-tree-kids    { border-left: 1px solid #282c32; padding-left: var(--k-sp-4);
                  display: flex; flex-direction: column; gap: 1px; }
/* flex: none, or the count shrinks before the label does: the label has
   min-width: 0 and gives way on purpose, and a number that ellipsises is not a
   number any more. */
.k-tree-count   { flex: none; font: 400 10.5px var(--k-mono); color: var(--k-text-5); }
.k-caret        { width: 5px; height: 5px; margin-top: -3px; transform: rotate(45deg);
                  border-right: 1.5px solid var(--k-text-3); border-bottom: 1.5px solid var(--k-text-3); }

/* Both of the above are <ul>, and the two declarations were written without
   one: a marker and a block margin arrive from the user agent otherwise, and a
   bullet turns up beside every row. The horizontal padding is already there
   and is the tree's own inset from the rail edge -- only the list defaults go. */
.k-tree, .k-tree-kids { list-style: none; margin-block: 0; }

/* THE CLICK TARGET IS THE BUTTON, NOT THE CARET, and they are two elements for
   that reason alone. .k-caret is a 5x5 figure drawn from two borders -- it is a
   shape, and 5x5 is not something a hand aims at. The button around it carries
   the hit area, the cursor and the aria-expanded state; the figure carries
   nothing but which way it points. Making the button itself the figure would
   tie the size of the target to the size of the arrow. */
.k-caret-btn { width: 16px; height: 16px; flex: none; display: flex;
               align-items: center; justify-content: center; padding: 0;
               border: 0; background: transparent; cursor: pointer; }

/* Both positions are stated here rather than leaning on the base rule's
   transform. The direction of the caret IS the open/closed state, and taking
   half of it from a value written for something else is how it silently ends
   up pointing the wrong way: -45deg is right, 45deg is down, and 135deg -- the
   value this looked like it wanted -- is left. margin-top is re-measured per
   position for the same reason: the -3px above was set for the figure sitting
   directly in a row, and in here the flex centring already does that work. */
.k-caret-btn[aria-expanded="false"] .k-caret { margin-top: 0;    transform: rotate(-45deg); }
.k-caret-btn[aria-expanded="true"]  .k-caret { margin-top: -1px; transform: rotate(45deg); }

/* A row with nothing to toggle: holds the caret column, draws nothing. Two
   kinds of row want it -- a bin with no children, and a section root, which
   always has children but never collapses. Without it their labels start one
   caret to the left of every other row's and the levels stop lining up.
   Deliberately not a <button> in the markup -- a button that does nothing is
   still a stop on the way to one that does. */
.k-caret-btn--empty { cursor: default; }

/* flex: 1 and min-width: 0 together, and the second one is not cosmetic: a
   flex child's automatic minimum is its own content, so without it the label
   refuses to shrink below the folder name, the ellipsis never appears and the
   row overflows instead (30-infra.md, section 10). */
.k-tree-label { flex: 1; min-width: 0;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Row actions stay out of the way until the row is hovered -- a tree is read
   far more often than it is edited. The caret is not one of these: it is how
   the shape of the tree is read, not an edit affordance, and it is always on. */
.k-tree-btn { flex: none; opacity: 0; padding: 0 3px; border: 0;
              background: transparent; color: var(--k-text-4);
              font-family: var(--k-sans);
              font-size: 13px; line-height: 1; cursor: pointer; }
.k-tree-row:hover .k-tree-btn   { opacity: 1; }
.k-tree-btn:hover               { color: var(--k-accent); }
.k-tree-btn--danger:hover       { color: var(--k-red); }

/* TWO DROP CUES, because there are two questions. --drop is the ordinary one:
   the file changes bin, or the bin changes parent. --drop-transfer is the
   Media room's editing mode, where a drop on a PROJECT row opens the
   Copy/Move dialog and changes what a file BELONGS to, generated output
   included -- the one gesture no other path offers. Amber against accent
   because the two are not the same promise, and a single cue for both would
   say they were. */
.k-tree-row--drop          { background: var(--k-raised);
                             outline: 1px dashed var(--k-accent); outline-offset: -1px; }
.k-tree-row--drop-transfer { background: var(--k-raised);
                             outline: 1px dashed var(--k-amber);  outline-offset: -1px; }

.k-tree-empty { margin: 0; padding: var(--k-sp-3);
                font-size: 12px; color: var(--k-text-4); }

/* ============================================================
   FILE GRID
   The strip has a fixed height; its inner box must be at least
   rows*row + gaps + padding, otherwise the last row is clipped.
   ============================================================ */

.k-strip        { flex: none; background: var(--k-panel); border-top: 1px solid var(--k-line);
                  display: flex; flex-direction: column; }
.k-toolbar      { height: var(--k-h-toolbar); flex: none; display: flex; align-items: center;
                  gap: 10px; padding: 0 var(--k-sp-5); border-bottom: 1px solid var(--k-line-soft); }
.k-grid         { flex: 1; min-height: 0; overflow-y: auto; padding: var(--k-sp-4) var(--k-sp-5);
                  display: grid; gap: var(--k-sp-3); align-content: start;
                  grid-template-columns: repeat(var(--k-cols, 5), 1fr);
                  grid-auto-rows: var(--k-tile-h, 104px); }

.k-tile         { position: relative; border-radius: var(--k-r-tile); overflow: hidden;
                  background: var(--k-input); box-shadow: 0 0 0 1px #23272c; cursor: pointer; }
.k-tile-img     { position: absolute; inset: 0; background-size: cover; background-position: center; }
.k-tile[aria-selected="true"] { box-shadow: 0 0 0 2px var(--k-accent); }
.k-tile-bar     { position: absolute; inset: auto 0 0 0; height: 19px; display: flex;
                  align-items: center; gap: 5px; padding: 0 var(--k-sp-2);
                  background: linear-gradient(to top, rgba(8,9,10,.9), rgba(8,9,10,.5)); }
.k-tile-name    { flex: 1; min-width: 0; font: 400 9px var(--k-mono); color: #c8cdd2;
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Reference number / A-B slot / delivery order */
.k-tile-badge   { position: absolute; top: 5px; left: 5px; width: 18px; height: 18px;
                  border-radius: 9px; background: var(--k-accent); color: #fff;
                  font: 600 10.5px var(--k-mono); display: flex; align-items: center;
                  justify-content: center; box-shadow: 0 1px 5px rgba(0,0,0,.5); }
.k-tile-check   { position: absolute; top: 6px; left: 6px; width: 16px; height: 16px;
                  box-sizing: border-box; border-radius: var(--k-r-tile);
                  border: 1px solid #5b6169; background: rgba(12,13,15,.7);
                  display: flex; align-items: center; justify-content: center; }

/* ============================================================
   VIEWER
   ============================================================ */

.k-viewer     { flex: 1; min-height: 0; display: flex; align-items: center;
                justify-content: center; padding: var(--k-sp-6) 24px; position: relative; }
.k-viewer img { max-width: 100%; max-height: 100%; object-fit: contain;
                border-radius: 3px; box-shadow: var(--k-shadow-frame); }
.k-over-tl { position: absolute; top: 24px; left: 34px; display: flex; gap: var(--k-sp-2); }
.k-over-tr { position: absolute; top: 24px; right: 34px; display: flex; gap: var(--k-sp-2); }

/* Comment marker on the frame. Keep the comment card in the corner
   opposite the marker -- that rule is for an OVERLAY layout, where the card
   lies on top of the picture; it does not apply on the client's review page,
   whose thread is a column beside the frame and cannot cover the mark.
   The modifier is --own, not --done: green here says "the studio wrote this",
   the same distinction the comment card's border carries. Resolvedness is not
   what it means and is not even reachable on the public page -- resolved_at is
   absent from the public schema. */
.k-pin { position: absolute; width: 24px; height: 24px;
         border-radius: 12px 12px 12px 2px; background: var(--k-amber);
         color: var(--k-amber-ink); font: 600 11.5px var(--k-mono);
         display: flex; align-items: center; justify-content: center;
         box-shadow: 0 2px 10px rgba(0,0,0,.6); }
.k-pin--own { background: var(--k-green); color: #0b1409; }

/* ============================================================
   COMMENTS
   The card of one comment and the thread it sits in. It came out of
   review.css so that both documents can reach it; the room still draws its
   own from main.css, so until that is replaced the only renderer is
   publicReview.js.
   Placed against .k-pin above rather than anywhere else: the mark on the
   frame and the card that names it are two halves of one signal, and --own
   means the same thing in both for the same reason.
   Modifiers are written compound -- .k-comment.k-comment--own, not
   .k-comment--own on its own -- and that is not redundancy. A lone modifier
   class is one step lower in specificity, which would hand the reply's border
   and geometry to source order, and would let .k-comment:last-child beat
   .k-comment--reply on margin-bottom -- a reply that is genuinely the last
   child, which the room's branch produces and this page's does not. Do not
   "simplify" them.
   ============================================================ */

/* The only scrolling part: the composer has to stay reachable without
   scrolling back down past the whole thread.
   THE CONSUMER OWES THIS A FLEX COLUMN WITH A DEFINITE HEIGHT. `flex: 1` and
   `min-height: 0` scroll nothing by themselves -- they only stop the item
   refusing to shrink below its own content. On the client's page that column
   is .lightbox-side; whatever mounts the list next has to give it the same,
   or the list grows instead of scrolling. */
.k-comment-list {
  list-style: none;
  margin: 0;
  padding: 10px 16px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* A CARD, darker than the panel it sits on. The separator rule it replaces made
   a thread read as one block of text with lines through it; a reply is a unit,
   and it is bounded like one. */
/* WHOSE REPLY IT IS, SAID ONCE, IN COLOUR: the frame and the numbered disc take
   it, the fill does not. Both kinds of card are the same weight on the page and
   only the outline differs -- amber for the client, green for the studio.
   What this replaces: a green bar down the left edge AND the word "STUDIO"
   beside the name. Three marks for one boolean, two of them repeating the
   third. */
.k-comment {
  padding: 10px 12px;
  margin-bottom: 8px;
  border: 1px solid var(--k-amber-line);
  border-radius: 10px;
  background: #0f1116;
}

.k-comment.k-comment--own {
  border-color: var(--k-accent-line);
}

.k-comment:last-child {
  margin-bottom: 0;
}

/* --- the branch under a root ------------------------------------------------
   Indented INSIDE the root's own card, which grows downwards to hold it. No
   scroller of its own: the panel already scrolls, and a second bar inside it
   would hide the end of the thread from the client looking for it. */
.k-comment-replies {
  margin: 8px 0 0 12px;
  padding-left: 10px;
  /* The one line that says "these belong to the card above". Neutral, not the
     authorship pair: a branch can hold both sides at once, and colouring the
     rail either way would name a side for the whole of it. */
  border-left: 1px solid var(--k-line);
}

/* A reply is the SAME card with its chrome taken off -- a modifier, not a
   second kind of thing. The frame belongs to the root; repeating it here would
   draw a box inside a box and make the branch read as a list of new remarks.
   Authorship still shows, on the fill rather than the border, because the
   border is what was just removed. */
.k-comment.k-comment--reply {
  margin-bottom: 6px;
  padding: 8px 10px;
  border: none;
  border-radius: 8px;
  background: #14161c;
}

.k-comment.k-comment--reply.k-comment--own {
  background: color-mix(in oklab, var(--k-accent) 12%, #14161c);
}

.k-comment.k-comment--reply:last-of-type {
  margin-bottom: 0;
}

/* Reply, on a root and only on a root. Quiet: it sits on every root card in the
   thread, and a filled button repeated eight times down a column would be the
   loudest thing on the page. */
.k-comment-reply {
  margin-top: 8px;
  border: none;
  background: transparent;
  padding: 0;
  color: var(--k-text-3);
  font: 400 11px var(--k-sans);
  cursor: pointer;
}

.k-comment-reply:hover {
  color: var(--k-amber);
  text-decoration: underline;
}

/* ONE LINE, and it stays one: the disc, the plaque and the time are fixed, so
   the name is the only thing that can give -- see .k-comment-who. Measured at
   the panel's narrowest (380px -> 321px of header): disc 20 + plaque 89 + the
   longest date 62 + three 8px gaps leaves 127px for the name before it starts
   truncating, and nothing wraps at any width. */
.k-comment-meta {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 8px;
  font-size: 11px;
  color: #6b7280;
  margin-bottom: 5px;
}

/* The number the client reads out loud, in a disc that carries the author's
   colour. No "#" in front: the disc is what makes it a label. */
.k-comment-seq {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font: 600 11px var(--k-mono);
  background: var(--k-amber);
  color: var(--k-amber-ink);
}

.k-comment.k-comment--own .k-comment-seq {
  background: var(--k-accent);
  color: var(--k-accent-txt);
}

/* The one element in the row that may shrink, because it is the only one whose
   meaning survives being cut. min-width: 0 is what actually permits it -- a
   flex item will not go below its content width without it, and the row would
   overflow instead of ellipsising. */
.k-comment-who {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  font-weight: 500;
  color: #e6e6e6;
}

/* A LABEL. It reads the timecode and says nothing about being pressable,
   because the card around it is what takes the click now -- an outline, a
   pointer or a hover here would each advertise a 90px target sitting inside
   the real one, and the client would aim at the small thing.
   The fill and the mono stay: this is a value, and a value on this page is set
   in mono on a dark plate. NEUTRAL in colour, deliberately -- green now means
   "the studio wrote this" on the card around it, and a green timecode inside
   an amber card would be a second voice in a signal meant to be single. */
.k-comment-tc {
  flex: 0 0 auto;
  border-radius: 6px;
  padding: 2px 8px;
  background: var(--k-app);
  font: 400 11px var(--k-mono);
  color: var(--k-text-2);
}

/* When it was written, pushed to the end of the row by the margin rather than
   by a spacer element: there is nothing between it and the plaque to mark. */
.k-comment-when {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: 11px;
  color: #6b7280;
}

.k-comment-body {
  font-size: 13px;
  color: #e6e6e6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.k-comment-empty {
  color: #6b7280;
  font-size: 13px;
  padding: 8px 0;
}

/* ============================================================
   RATING
   ============================================================ */

.k-stars      { display: flex; gap: 1px; font-size: 14px; letter-spacing: 1px; color: var(--k-idle); }
.k-stars-on   { color: var(--k-amber); }

/* ============================================================
   DIFF
   ============================================================ */

.k-diff-row   { display: grid; grid-template-columns: 26px 1fr 1fr; border-bottom: 1px solid #1c1f23; }
.k-diff-gutter{ display: flex; align-items: flex-start; justify-content: center; padding-top: 11px;
                font: 600 12px var(--k-mono); color: var(--k-text-3); }
.k-diff-cell  { padding: 10px 12px; font: 400 12px/1.65 var(--k-sans); color: var(--k-text-2); }
.k-diff-cell + .k-diff-cell { border-left: 1px solid var(--k-line); }
.k-diff-row--del { background: rgba(176,106,102,.07); }
.k-diff-row--add { background: rgba(95,143,92,.08); }
.k-del { color: var(--k-red-txt);   background: rgba(176,106,102,.14); border-radius: 2px; padding: 0 2px; }
.k-add { color: var(--k-green-txt); background: rgba(95,143,92,.16);  border-radius: 2px; padding: 0 2px; }

/* ============================================================
   PUBLIC PAGE /r/{token}
   Isolated: its own review.html / publicReview.js / review.css.
   Same tokens, lower density, no jargon.
   ============================================================ */

.k-pub            { --k-h-topbar: 56px; }
.k-pub-head       { padding: 0 40px; }
.k-pub-section    { display: flex; flex-direction: column; gap: 11px; }
.k-pub-section-hd { display: flex; align-items: baseline; gap: 11px;
                    font: 600 11px var(--k-sans); letter-spacing: .12em;
                    text-transform: uppercase; color: #aab2b9; }
.k-pub-section-hd::after { content: ''; flex: 1; height: 1px; background: var(--k-line-soft); }
.k-pub-grid       { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--k-sp-4); }
.k-pub-card       { display: flex; flex-direction: column; gap: var(--k-sp-2); }
.k-pub-card-img   { position: relative; aspect-ratio: 16/9; border-radius: 5px;
                    background-size: cover; background-position: center;
                    box-shadow: 0 0 0 1px #23272c; }
.k-pub-card-name  { font: 400 10px var(--k-mono); color: var(--k-text-3);
                    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.k-play           { position: absolute; left: 50%; top: 50%; width: 30px; height: 30px;
                    margin: -15px 0 0 -15px; border-radius: 16px;
                    background: rgba(12,13,15,.62); border: 1px solid rgba(255,255,255,.5);
                    display: flex; align-items: center; justify-content: center; }
.k-play::after    { content: ''; margin-left: 3px; width: 0; height: 0;
                    border-left: 9px solid #fff; border-top: 6px solid transparent;
                    border-bottom: 6px solid transparent; }
