/* ============================================================================
   components.css - the shapes.

   Every component here exists because Alex described it. The comment on each one
   is his instruction number, so a stranger can check the shape against the
   brief rather than against taste.

   AND IT OBEYS HIS RECORDED UI LAW (builds/crm/docs/UI.md, written 2026-10-19
   after he said screens had become cluttered: "so much to read, no idea what the
   purpose of any of it is"). The twelve rules, applied here:
     1  a work surface carries the work and nothing else
     3  hub rows are nouns, leaf pages hold the verbs
     5  three to seven rows per group, two levels from hub to leaf
     6  the current value is visible on the row
     8  NO instruction text on a screen - the (i) is the only explanation
     9  no button that does nothing; a count is not a control
    10  search at the top of every hub
    11  the same shape at every level
    12  feedback within a moment
   ============================================================================ */

/* ---------------------------------------------------------------- layout */

.shell { min-height: 100vh; display: flex; flex-direction: column; }

.page {
  flex: 1;
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: var(--s4) var(--gutter) calc(var(--tabbar) + var(--s6));
}
@media (min-width: 760px) {
  .page { padding-bottom: var(--s6); }
}

.head {
  display: flex; align-items: center; gap: var(--s3);
  min-height: var(--tap);
  margin-bottom: var(--s4);
}
.head h1 { flex: 1; min-width: 0; }

/* A row of actions. Wraps rather than scrolling sideways, because a sideways
   scroll on a phone hides controls and is always a defect. */
.actions { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; }

/* THREE CONTROLS, ONE ROW, AT EVERY WIDTH.
   Alex, 2026-10-23: "Create template / Upload / New folder / Buttons should all
   fit in one row / 3 side by side". `.actions` wraps, which at 360px put New
   folder on a line of its own and made the hub look like two decisions instead
   of three.
   A grid of three equal columns cannot wrap, so the row is the row. The label
   shrinks before the button does: the text is allowed to go to two lines inside
   a tap-height button rather than being cut off, because a truncated label is a
   button nobody is sure about. */
.trio { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s2); }
.trio > * { min-width: 0; }
.trio .btn {
  width: 100%;
  padding: 0 var(--s2);
  font-size: var(--t-small);
  line-height: 1.15;
  text-align: center;
  white-space: normal;
}
/* The (i) sits under its button rather than beside it, so three buttons plus
   three (i)s still fit the row. */
.trio .cell { display: flex; flex-direction: column; align-items: center; gap: 2px; }

/* A row the person has picked up, waiting to be dropped in a folder. His long
   press: "Long press on any form should allow me to drag it into a folder". */
.row.picked, .folder.target .sq {
  outline: 2px solid var(--go);
  outline-offset: 2px;
}
.pickbar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
  padding: var(--s2) var(--s3);
  margin-bottom: var(--s2);
  border-radius: var(--radius);
  background: var(--go-soft, var(--paper));
  border: 1px solid var(--go);
  font-size: var(--t-small);
}

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

.btn {
  min-height: var(--tap);
  padding: 0 var(--s4);
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  border-radius: var(--radius);
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--t-body);
  font-weight: 560;
  cursor: pointer;
  user-select: none;
  transition: transform .06s ease, background .12s ease, border-color .12s ease;
}
.btn:active { transform: scale(.985); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

/* Instruction 51: accept, continue, confirmed, save - these HAVE COLOUR. */
.btn.go { background: var(--go); border-color: var(--go); color: var(--go-ink); }

/* Instruction 19: cancel is WHITE and sits immediately to the LEFT of save. */
.btn.cancel { background: var(--paper); color: var(--ink); border-color: var(--line-strong); }

/* Instruction 52: cancel subscription, exit, sign out, delete - RED. */
.btn.stop { background: var(--paper); color: var(--stop); border-color: var(--stop); }
.btn.stop.filled { background: var(--stop); color: #fff; border-color: var(--stop); }

.btn.small { min-height: 36px; padding: 0 var(--s3); font-size: var(--t-small); }
.btn.wide { width: 100%; }

/* Instruction 14, verbatim: "text in a bubble with a blue plus in a circle next
   to the text, button text create new client". The plus is a real circle, the
   bubble is a pill, and the whole thing is one tap target. */
.btn-bubble {
  min-height: var(--tap);
  padding: var(--s2) var(--s4) var(--s2) var(--s2);
  display: inline-flex; align-items: center; gap: var(--s3);
  background: var(--paper);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-1);
  color: var(--ink);
  font-size: var(--t-body);
  font-weight: 580;
  cursor: pointer;
}
.btn-bubble .plus {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--go);
  color: var(--go-ink);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 19px; font-weight: 700; line-height: 1;
}
.btn-bubble:active { transform: scale(.985); }

/* ---------------------------------------------------------------- the (i) */

/* Instruction 11 and 27. His rule 8 says no instruction text on a screen, and
   this is how both hold at once: the explanation exists and is COLLAPSED. The
   page stays empty; the sentence is there the day somebody needs it. */
.info {
  flex: 0 0 auto;
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink-soft);
  font-size: 13px; font-weight: 700; font-style: italic;
  line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  padding: 0;
  /* The visual circle is 22px; the TAP TARGET is 44px, because a 22px target on
     a phone is a missed tap. Rule: never shrink the target to match the paint. */
  position: relative;
}
.info::after {
  content: '';
  position: absolute;
  inset: -11px;
}
.info[aria-expanded="true"] { background: var(--go-soft); border-color: var(--go); color: var(--go); }

.info-pop {
  position: absolute;
  z-index: 60;
  max-width: min(280px, calc(100vw - 32px));
  padding: var(--s3) var(--s4);
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--radius-sm);
  font-size: var(--t-small);
  line-height: 1.4;
  box-shadow: var(--shadow-2);
}
/* The popover inverts against the page, so it reads as a layer above rather
   than another panel. It needs no dark-mode override: --ink and --paper have
   already swapped by the time it is painted. */

/* ---------------------------------------------------------------- rows (his rule 3) */

/* A hub row is a NOUN with its current value and a chevron. No verbs. */
.rows { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.row {
  display: flex; align-items: center; gap: var(--s3);
  min-height: var(--tap);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line);
  background: none; border-left: 0; border-right: 0; border-top: 0;
  width: 100%;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
}
.row:last-child { border-bottom: 0; }
.row .label { flex: 1; min-width: 0; font-weight: 540; }
/* His rule 6: the current value is visible on the row. */
.row .value { color: var(--ink-soft); font-size: var(--t-small); white-space: nowrap; }
.row .chev { color: var(--ink-faint); flex: 0 0 auto; }
.row.stop .label { color: var(--stop); }
/* A row with nothing behind it yet. No chevron (set in app.js), muted, and not
   tappable - UI review pass 1, against his rule 9: "no button that does nothing,
   no label that cannot be acted on." Telling the truth costs one shade of grey. */
.row.soon { cursor: default; }
.row.soon .label { color: var(--ink-soft); }
.row.soon .value { color: var(--ink-faint); font-style: italic; }

.group { margin-bottom: var(--s5); }
.group > h3 {
  font-size: var(--t-small);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-faint);
  margin: 0 0 var(--s2) var(--s1);
  display: flex; align-items: center; gap: var(--s2);
}

/* ---------------------------------------------------------------- square folders */

/* Instruction 16, verbatim: "a list of square folders with text beneath them".
   Instruction 15: each folder opens a new page. */
.folders {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: var(--s4);
}
@media (min-width: 560px) {
  .folders { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
}
.folder {
  display: flex; flex-direction: column; align-items: center; gap: var(--s2);
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--ink);
  min-height: var(--tap);
}
.folder .sq {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius);
  background: var(--paper);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-1);
  display: flex; align-items: center; justify-content: center;
  font-size: 30px;
  color: var(--go);
}
.folder .txt {
  font-size: var(--t-small);
  text-align: center;
  line-height: 1.3;
  /* Text beneath, and it wraps rather than being cut off - a truncated client
     name is a client you cannot find. */
  overflow-wrap: anywhere;
}
.folder:active .sq { transform: scale(.98); }

/* ---------------------------------------------------------------- search + order */

/* His rule 10: search at the top of every hub. Instruction 16 and 17. */
.searchbar { display: flex; gap: var(--s2); align-items: center; margin-bottom: var(--s4); }
.searchbar input {
  flex: 1; min-width: 0;
  min-height: var(--tap);
  padding: 0 var(--s4);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
}
/* Instruction 17: "tiny button next to search bar that says (A-Z change order)". */
.btn-order {
  flex: 0 0 auto;
  min-height: var(--tap);
  padding: 0 var(--s3);
  font-size: var(--t-tiny);
  font-weight: 600;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
}

/* ---------------------------------------------------------------- copy chip */

/* Instruction 20: "a little square next to it that when tapped it copies the
   info to there clipboard, and says in our ui design 'copied' green button". */
.kv { display: flex; align-items: center; gap: var(--s2); padding: var(--s2) 0; }
.kv .k { font-size: var(--t-tiny); color: var(--ink-faint); width: 68px; flex: 0 0 auto; }
/* Instruction 20's "small letters". */
.kv .v { flex: 1; min-width: 0; font-size: var(--t-small); overflow-wrap: anywhere; }
.copy {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px;
  position: relative;
}
.copy::after { content: ''; position: absolute; inset: -7px; }   /* 44px target */
/* The green "copied" state. His rule 12: feedback within a moment, in place. */
.copy.copied {
  background: var(--done-soft);
  border-color: var(--done);
  color: var(--done);
  width: auto; padding: 0 var(--s2); font-size: var(--t-tiny); font-weight: 650;
}

/* Instruction 22: "a seperator line across the screen below the clients info,
   page allows scroll down". */
.sep { height: 1px; background: var(--line); border: 0; margin: var(--s5) calc(var(--gutter) * -1); }

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

/* Instruction 18, and it is the hardest rule in the brief:
   "every button will pull up a pop up window that takes over most of the screen,
    make sure each pop up window doesnt require scrolling, things need to fit all
    on one page"

   So the panel is capped at the viewport and its BODY does not scroll. If content
   would overflow, the answer is to SPLIT THE POP-UP INTO TWO GATES, never to let
   it scroll - and prove-ui.mjs measures scrollHeight against clientHeight to
   prove it. */
.scrim {
  position: fixed; inset: 0;
  background: rgba(16, 24, 40, .55);
  display: flex; align-items: center; justify-content: center;
  padding: var(--gutter);
  z-index: 50;
}
.modal {
  width: 100%;
  max-width: 520px;
  /* "takes over most of the screen" on a phone, comfortable on a desktop */
  max-height: calc(100vh - var(--s7));
  background: var(--paper);
  border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
  display: flex; flex-direction: column;
  overflow: hidden;
}
@media (max-width: 560px) {
  .modal { max-width: none; min-height: 72vh; }
}
.modal header {
  padding: var(--s4);
  display: flex; align-items: center; gap: var(--s3);
  border-bottom: 1px solid var(--line);
}
.modal header h3 { flex: 1; min-width: 0; }
.modal .body {
  padding: var(--s4);
  flex: 1;
  /* DELIBERATELY NOT scrollable. Instruction 18. A pop-up that scrolls makes a
     person hunt for the button; a fitted one tells them the decision is small
     (white paper 11.2). */
  overflow: hidden;
}
.modal footer {
  padding: var(--s4);
  border-top: 1px solid var(--line);
  display: flex; gap: var(--s2); justify-content: flex-end;
}
/* Instruction 19: cancel is immediately to the LEFT of save. The order in the
   markup is cancel then save, so this is the natural order and not a trick. */

/* ---------------------------------------------------------------- choice rows */

/* Instruction 17's order pop-up: four radio rows, fits one screen with room. */
.choice {
  display: flex; align-items: center; gap: var(--s3);
  min-height: var(--tap);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  margin-bottom: var(--s2);
  cursor: pointer;
  background: var(--paper);
  width: 100%;
  text-align: left;
  color: var(--ink);
}
.choice[aria-checked="true"] { border-color: var(--go); background: var(--go-soft); }
.choice .dot {
  width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid var(--line-strong); flex: 0 0 auto;
}
.choice[aria-checked="true"] .dot { border-color: var(--go); background: var(--go); box-shadow: inset 0 0 0 3px var(--paper); }

/* ---------------------------------------------------------------- fields */

.field { margin-bottom: var(--s4); }
.field label {
  font-size: var(--t-small); font-weight: 600; color: var(--ink-soft);
}
/* The label and its (i) side by side, with the (i) as a SIBLING rather than a
   child. walk-live.mjs found the child version in a real browser: it made the
   label's accessible name read "Company name i", because the button's glyph is
   part of the label's text content. Invisible in the source, invisible on screen,
   and only a browser can see it. */
.field-head {
  display: flex; align-items: center; gap: var(--s2);
  margin-bottom: var(--s1);
}
.field input, .field textarea, .field select {
  width: 100%;
  min-height: var(--tap);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  /* 16px prevents iOS zooming the page when a field is focused, which is a
     layout break that looks like a bug. */
  font-size: 16px;
}
.field textarea { min-height: 96px; resize: vertical; }

/* ---------------------------------------------------------------- cards + tabs */

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4);
  margin-bottom: var(--s3);
}
.card h3 { margin-bottom: var(--s1); }
.card .meta { font-size: var(--t-small); color: var(--ink-soft); }

.tag {
  display: inline-flex; align-items: center; gap: var(--s1);
  padding: 2px var(--s2);
  border-radius: var(--radius-pill);
  font-size: var(--t-tiny); font-weight: 650;
  background: var(--sunk); color: var(--ink-soft);
}
.tag.go { background: var(--go-soft); color: var(--go); }
.tag.done { background: var(--done-soft); color: var(--done); }
.tag.stop { background: var(--stop-soft); color: var(--stop); }
.tag.warn { background: var(--warn-soft); color: var(--warn); }

/* Instruction 24: four tabs. 56px bar, fixed at the bottom on a phone where a
   thumb reaches, inline on a desktop. */
.tabs {
  position: fixed; left: 0; right: 0; bottom: 0;
  height: var(--tabbar);
  background: var(--paper);
  border-top: 1px solid var(--line);
  display: flex;
  padding-bottom: env(safe-area-inset-bottom);
  z-index: 40;
}
.tab {
  flex: 1;
  border: 0; background: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  font-size: var(--t-tiny); font-weight: 600;
  color: var(--ink-faint);
  cursor: pointer;
  min-height: var(--tabbar);
}
.tab[aria-selected="true"] { color: var(--go); }
.tab .ico { font-size: 18px; line-height: 1; }
/* Instruction 35 and white paper 10: the AI tab is VISIBLE when ungated, because
   a hidden paid feature sells nothing. */
.tab.locked .ico::after { content: ' \1F512'; font-size: 11px; }

@media (min-width: 760px) {
  .tabs { position: sticky; top: 0; bottom: auto; border-top: 0; border-bottom: 1px solid var(--line); height: var(--tap); }
  .tab { flex-direction: row; gap: var(--s2); min-height: var(--tap); font-size: var(--t-small); }
}

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

/* His rule 12: feedback within a moment. */
.toast {
  position: fixed;
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--tabbar) + var(--s4));
  z-index: 70;
  padding: var(--s3) var(--s4);
  background: var(--ink); color: var(--paper);
  border-radius: var(--radius-pill);
  font-size: var(--t-small); font-weight: 560;
  box-shadow: var(--shadow-2);
  max-width: calc(100vw - 32px);
}
.toast.bad { background: var(--stop); }

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

/* His rule 9: no label that cannot be acted on. An empty state names the ONE
   thing to do and is itself the control. */
.empty {
  text-align: center;
  padding: var(--s6) var(--s4);
  color: var(--ink-soft);
}
.empty h3 { color: var(--ink); margin-bottom: var(--s2); }
.empty .btn-bubble, .empty .btn { margin-top: var(--s4); }

/* ---------------------------------------------------------------- pass 3 */

/* UI review pass 3. These replace inline styles that were set in JavaScript -
   a spacer <div> with an inline height, and an iframe with a fixed 420px height.
   Both break at a size nobody tested: a spacer does not collapse when the thing
   above it is absent, and a fixed iframe height clips on a short phone in
   landscape. */

/* The action bar at the top of a hub carries its own space. */
.page > .actions { margin-bottom: var(--s4); }

/* Instruction 23's preview widget. A viewport-relative height so it is
   proportionate on a tall phone AND on a short laptop, with a floor so it is
   never too short to show a job card, and a cap so it never pushes the rest of
   the client's page off the screen. */
.portal-frame {
  width: 100%;
  height: clamp(280px, 52vh, 560px);
  border: 0;
  display: block;
  background: var(--sunk);
}

/* ---------------------------------------------------------------- the chat box
   Chunk 23. "the same as any other AI": a transcript that grows downwards, a
   box at the bottom, a paperclip, a send button.

   The log is capped in HEIGHT rather than allowed to grow the page, so the
   composer never scrolls off the bottom of a phone - which is the one thing
   that makes a chat box unusable on the device Alex actually opens this on. */
.card.chat { padding: 0; overflow: hidden; }
.chat-log {
  max-height: 46vh;
  min-height: 9rem;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.chat-msg { max-width: 86%; padding: 0.5rem 0.7rem; border-radius: 0.75rem; }
.chat-msg p { margin: 0; }
.chat-msg.mine { align-self: flex-end; background: var(--accent-soft, #eef); }
.chat-msg.theirs { align-self: flex-start; background: var(--panel-2, #f4f4f5); }
.chat-msg.thinking p { opacity: 0.6; }
/* A tool turn is evidence, not conversation, so it reads as a quiet line down
   the middle rather than as somebody speaking. */
.chat-did {
  align-self: center;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  opacity: 0.85;
}
.chat-bar {
  display: flex;
  align-items: flex-end;
  gap: 0.4rem;
  padding: 0.6rem;
  border-top: 1px solid var(--line, #e4e4e7);
}
.chat-input {
  flex: 1 1 auto;
  min-width: 0;
  resize: none;
  font: inherit;
  padding: 0.5rem;
  border-radius: 0.6rem;
  border: 1px solid var(--line, #e4e4e7);
  background: var(--bg, #fff);
  color: inherit;
}
/* 44px on both, because these are the two controls a thumb has to hit. */
.chat-bar .btn { min-height: 44px; min-width: 44px; touch-action: manipulation; }
.chat-bar .btn.clip { flex: 0 0 auto; }
.hidden-file { display: none; }
@media (max-width: 420px) {
  .chat-log { max-height: 40vh; }
  .chat-bar .btn.clip { padding-left: 0.5rem; padding-right: 0.5rem; }
}
