/* =============================================================================
   Uppercase M e-Memo Automation Platform — demo styles
   Hand-written, zero dependencies, no build step, no network. Palette sampled
   from Uppercase M's own brand palette.
   ========================================================================== */

:root {
  --red: #0033A2;
  --red-bright: #0D4CA3;
  --red-dark: #00247A;
  --grey: #737373;
  --ink: #1F2129;
  --ink-2: #4A4F5C;
  --ink-3: #737373;
  --blush: #E8E8E8;
  --blue: #0369A1;
  /* Two of the deck's workflow-category colours (data.js CATEGORY_COLOR) promoted
     to variables: the flow diagram carries node type in the border, so it needs
     them as colours, not just as per-card accents. */
  --teal: #028090;
  --purple: #5C3D8F;

  --bg: #F7F7F8;
  --surface: #FFFFFF;
  --surface-2: #FBFBFC;
  --line: #E4E5E9;
  --line-2: #EFEFF2;

  --ok: #1A7F4B;
  --ok-bg: #E9F6EF;
  --warn: #B26A00;
  --warn-bg: #FDF3E4;
  --err: #C0261F;
  --err-bg: #FCECEB;
  --info-bg: #EAF3FB;
  --purple-bg: #F5F2FA;

  --r-sm: 6px;
  --r: 10px;
  --r-lg: 14px;
  --shadow-sm: 0 1px 2px rgba(27, 29, 38, .06), 0 1px 3px rgba(27, 29, 38, .04);
  --shadow: 0 2px 6px rgba(27, 29, 38, .06), 0 8px 24px rgba(27, 29, 38, .06);
  --shadow-lg: 0 12px 40px rgba(27, 29, 38, .14);

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
  --thai: "Noto Sans Thai", "IBM Plex Sans Thai", Sarabun, Thonburi, "Leelawadee UI", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* Thai runs get the Thai stack first — Latin UI chrome keeps the system font. */
:lang(th), .th { font-family: var(--thai), var(--sans); line-height: 1.75; }

button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; }
a { color: var(--blue); }

.ico { flex: none; vertical-align: -.15em; }

/* ============================================================ login screen */
#login {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  background:
    radial-gradient(1200px 600px at 15% -10%, var(--blush), transparent 60%),
    radial-gradient(900px 500px at 100% 110%, #DCE7F5, transparent 55%),
    var(--surface);
  z-index: 100;
}

.login-card {
  width: 380px;
  padding: 40px;
  text-align: center;
}

.login-card h1 {
  margin: 22px 0 4px;
  font-size: 22px;
  letter-spacing: -.02em;
}

.login-card p { margin: 0 0 28px; color: var(--ink-3); }

.sso-btn {
  width: 100%;
  padding: 12px 16px;
  border-radius: var(--r);
  background: var(--ink);
  color: #fff;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  transition: transform .12s, background .12s;
}
.sso-btn:hover { background: #000; transform: translateY(-1px); }

.login-note {
  margin-top: 18px;
  font-size: 12px;
  color: var(--ink-3);
}

/* ================================================================== brand */
.logo {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  font-weight: 800;
  letter-spacing: -.03em;
  color: var(--red);
}
.logo .mark { font-size: 26px; }
.logo .dot {
  width: 9px; height: 9px;
  background: #38BDF8;
  border-radius: 0 50% 50% 50%;
  transform: rotate(45deg) translateY(-2px);
  display: inline-block;
}
.logo .sub {
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-3);
  letter-spacing: .04em;
  text-transform: uppercase;
}

/* =================================================================== shell */
#app { display: none; height: 100%; }
#app.on { display: grid; grid-template-columns: 264px 1fr; }

/* ------------------------------------------------------------- sidebar */
.side {
  background: var(--surface);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.side-head {
  padding: 16px 16px 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.new-btn {
  margin: 0 12px 14px;
  padding: 9px 12px;
  border-radius: var(--r);
  background: var(--red);
  color: #fff;
  font-weight: 600;
  font-size: 13px;
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
  box-shadow: var(--shadow-sm);
  transition: background .12s;
}
.new-btn:hover { background: var(--red-dark); }

.side-scroll { flex: 1; overflow-y: auto; padding: 0 8px 8px; min-height: 0; }

.side-label {
  padding: 12px 8px 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.proj { margin-bottom: 2px; }

.proj-head {
  width: 100%;
  padding: 6px 8px;
  border-radius: var(--r-sm);
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  text-align: left;
}
.proj-head:hover { background: var(--line-2); }
.proj-head .chev { margin-left: auto; transition: transform .15s; color: var(--ink-3); }
.proj.closed .chev { transform: rotate(-90deg); }
.proj.closed .thread-list { display: none; }

.thread-list { padding: 2px 0 4px 10px; }

.thread {
  width: 100%;
  padding: 7px 9px;
  border-radius: var(--r-sm);
  display: block;
  text-align: left;
  font-size: 13px;
  color: var(--ink-2);
  line-height: 1.35;
  position: relative;
}
.thread:hover { background: var(--line-2); }
.thread.on { background: var(--blush); color: var(--ink); font-weight: 600; }
.thread .meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 3px;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-3);
}
.thread-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.dot-status { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.dot-status.running { background: var(--warn); }
.dot-status.done { background: var(--ok); }
.dot-status.rejected { background: var(--err); }

.side-foot { border-top: 1px solid var(--line); padding: 8px; }

.nav-btn {
  width: 100%;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  text-align: left;
}
.nav-btn:hover { background: var(--line-2); }
.nav-btn.on { background: var(--blush); color: var(--red-dark); font-weight: 600; }
.nav-btn .count {
  margin-left: auto;
  min-width: 19px;
  height: 19px;
  padding: 0 6px;
  border-radius: 10px;
  background: var(--red);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  display: grid;
  place-items: center;
}

/* --------------------------------------------------------------- main */
.main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }

.topbar {
  height: 56px;
  flex: none;
  padding: 0 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 12px;
}

.topbar h2 {
  margin: 0;
  font-size: 14px;
  font-weight: 650;
  letter-spacing: -.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.crumb { color: var(--ink-3); font-size: 13px; }

.spacer { margin-left: auto; }

/* role switcher ---------------------------------------------------------- */
.roles {
  display: flex;
  padding: 3px;
  border-radius: 999px;
  background: var(--line-2);
  gap: 2px;
}
.role-btn {
  padding: 5px 11px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: 6px;
  transition: background .12s;
  white-space: nowrap;
}
.role-btn:hover { background: #fff8; }
.role-btn.on { background: var(--surface); box-shadow: var(--shadow-sm); color: var(--ink); }

.avatar {
  width: 26px; height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 700;
  color: #fff;
  flex: none;
  font-family: var(--thai), var(--sans);
}
.avatar.lg { width: 34px; height: 34px; font-size: 12px; }
.avatar.sm { width: 20px; height: 20px; font-size: 9px; }

.who { display: flex; align-items: center; gap: 9px; }
.who .name { font-size: 13px; font-weight: 600; line-height: 1.2; }
.who .role { font-size: 11px; color: var(--ink-3); line-height: 1.2; }

/* ================================================================ views */
.view { flex: 1; min-height: 0; display: none; }
.view.on { display: flex; }

/* --------------------------------------------------------------- chat */
#view-chat { flex-direction: row; }

.chat-col { flex: 1; display: flex; flex-direction: column; min-width: 0; }

.msgs {
  flex: 1;
  overflow-y: auto;
  padding: 26px 0 8px;
  scroll-behavior: smooth;
}

.msgs-inner { max-width: 780px; margin: 0 auto; padding: 0 24px; }

/* empty state */
.empty {
  height: 100%;
  display: grid;
  place-content: center;
  justify-items: center;
  text-align: center;
  padding: 40px 24px;
  gap: 6px;
}
.empty h3 { margin: 14px 0 2px; font-size: 20px; letter-spacing: -.02em; }
.empty p { margin: 0 0 18px; color: var(--ink-3); max-width: 440px; }

.starter {
  width: 100%;
  max-width: 560px;
  text-align: left;
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  margin-bottom: 9px;
  display: flex;
  gap: 11px;
  align-items: flex-start;
  transition: border-color .12s, box-shadow .12s, transform .12s;
}
.starter:hover { border-color: var(--red); box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.starter .st-icon {
  width: 30px; height: 30px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  color: #fff;
  flex: none;
}
.starter .st-t { display: block; font-weight: 600; font-size: 13px; }
.starter .st-d { display: block; font-size: 12px; color: var(--ink-3); margin-top: 2px; line-height: 1.5; }

/* message rows */
.msg { display: flex; gap: 12px; margin-bottom: 22px; animation: rise .28s ease both; }
@keyframes rise { from { opacity: 0; transform: translateY(6px); } }

.msg.user { justify-content: flex-end; }
.msg.user .bubble {
  background: var(--ink);
  color: #fff;
  padding: 10px 14px;
  border-radius: 14px 14px 4px 14px;
  max-width: 78%;
}

.msg .body { min-width: 0; flex: 1; }

.ai-badge {
  width: 28px; height: 28px;
  border-radius: 8px;
  flex: none;
  display: grid;
  place-items: center;
  /* Flat brand red. A 135° two-stop gradient plus a drop shadow on a 28px
     avatar is the stock AI-assistant mark — it read as decoration because
     it was; nothing about the gradient carried information. */
  background: var(--red);
  color: #fff;
}

.prose { white-space: pre-wrap; }
.prose strong { font-weight: 650; }
.prose code {
  font-family: var(--mono);
  font-size: 12.5px;
  background: var(--line-2);
  padding: 1px 5px;
  border-radius: 4px;
}

.caret {
  display: inline-block;
  width: 7px; height: 15px;
  background: var(--red);
  vertical-align: -2px;
  margin-left: 2px;
  animation: blink .9s steps(2) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* ------------------------------------------------------------ widgets */
.card {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  margin-top: 12px;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.card-head {
  padding: 11px 14px;
  border-bottom: 1px solid var(--line-2);
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ink-3);
  background: var(--surface-2);
}
.card-head .tag { margin-left: auto; text-transform: none; letter-spacing: 0; }
/* A second tag rides along with the first instead of being pushed to the far
   edge — a head that carries both a version and a change count reads as one
   group, not as two unrelated corners. */
.card-head .tag ~ .tag { margin-left: 0; }
.card-body { padding: 14px; }

.tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 650;
  background: var(--line-2);
  color: var(--ink-2);
}
.tag.ok { background: var(--ok-bg); color: var(--ok); }
.tag.warn { background: var(--warn-bg); color: var(--warn); }
.tag.err { background: var(--err-bg); color: var(--err); }
.tag.info { background: var(--info-bg); color: var(--blue); }
/* AI-backed tool mark (doc.summarize, chat.intent) — text, not an icon,
   deliberately: a sparkle here reads as decoration on a catalogue card, not
   information. Reuses --purple/--purple-bg, the hue .cnode.ext already
   carries for "needs something external" — no new colour, one restrained
   mark, the same one on every surface a tool appears (catalogue, canvas
   node, flow-card badge, detail panel). */
.tag.ai { background: var(--purple-bg); color: var(--purple); }

/* tool call strip */
.tool {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  margin-top: 8px;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--surface-2);
  font-size: 12.5px;
  animation: rise .25s ease both;
}
.tool .t-name { font-family: var(--mono); font-size: 11.5px; color: var(--ink-2); font-weight: 600; }
.tool .t-label { color: var(--ink-2); }
.tool .t-res { margin-left: auto; color: var(--ink-3); font-size: 12px; text-align: right; }
.tool .spin {
  width: 13px; height: 13px;
  border: 2px solid var(--line);
  border-top-color: var(--red);
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.tool .done { color: var(--ok); }

/* workflow match */
.match { display: flex; gap: 13px; align-items: flex-start; }
.match .swatch { width: 4px; align-self: stretch; border-radius: 3px; flex: none; }
.match .m-name { font-weight: 650; font-size: 14px; }
.match .m-reason { font-size: 12.5px; color: var(--ink-2); margin-top: 3px; }
.match .m-alt { font-size: 11.5px; color: var(--ink-3); margin-top: 7px; }

.conf {
  margin-left: auto;
  text-align: right;
  flex: none;
}
.conf .pct { font-size: 17px; font-weight: 700; letter-spacing: -.02em; }
.conf .lbl { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-3); }

/* inline form */
.field { margin-bottom: 12px; }
.field:last-child { margin-bottom: 0; }
.field label {
  display: block;
  font-size: 12px;
  font-weight: 650;
  margin-bottom: 5px;
  color: var(--ink-2);
}
.field label .req { color: var(--red); }
.field .hint-th { font-weight: 500; color: var(--ink-3); font-family: var(--thai); }

.inp {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  transition: border-color .12s, box-shadow .12s;
  font-family: var(--thai), var(--sans);
}
.inp:focus {
  outline: none;
  border-color: var(--red);
  box-shadow: 0 0 0 3px rgba(27, 43, 69, .1);
}
textarea.inp { min-height: 130px; resize: vertical; line-height: 1.7; }

.ai-filled {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10.5px;
  font-weight: 650;
  color: var(--red);
  background: var(--blush);
  padding: 1px 6px;
  border-radius: 999px;
  margin-left: 6px;
  vertical-align: 1px;
}

.card-actions {
  display: flex;
  gap: 8px;
  padding: 12px 14px;
  border-top: 1px solid var(--line-2);
  background: var(--surface-2);
}
.card-actions .spacer-l { margin-left: auto; }

.btn {
  padding: 8px 14px;
  border-radius: var(--r-sm);
  font-size: 13px;
  font-weight: 600;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-2);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  transition: background .12s, border-color .12s, transform .1s;
}
.btn:hover { background: var(--line-2); }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--red); border-color: var(--red); color: #fff; }
.btn.primary:hover { background: var(--red-dark); border-color: var(--red-dark); }
.btn.ok { background: var(--ok); border-color: var(--ok); color: #fff; }
.btn.ok:hover { filter: brightness(.94); }
.btn.ghost { border-color: transparent; background: transparent; }
.btn.ghost:hover { background: var(--line-2); }
.btn[disabled] { opacity: .5; pointer-events: none; }

/* upload */
.drop {
  border: 1.5px dashed var(--line);
  border-radius: var(--r);
  padding: 24px;
  text-align: center;
  transition: border-color .15s, background .15s;
}
.drop:hover { border-color: var(--red); background: var(--blush); }
.drop .d-t { font-weight: 600; margin-top: 8px; }
.drop .d-h { font-size: 12px; color: var(--ink-3); margin-top: 2px; }

.file-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  font-size: 13px;
}
.file-row + .file-row { margin-top: 7px; }
.file-row .f-size { margin-left: auto; color: var(--ink-3); font-size: 12px; }
.file-row .f-ico { color: var(--red); }

/* memo preview */
.memo {
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: 22px 24px;
  background: #fff;
  font-family: var(--thai), var(--sans);
}
.memo-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding-bottom: 14px;
  border-bottom: 2px solid var(--ink);
  margin-bottom: 16px;
}
.memo-head .m-title { font-size: 17px; font-weight: 700; letter-spacing: -.01em; }
.memo-head .m-ref { font-family: var(--mono); font-size: 11px; color: var(--ink-3); margin-top: 3px; }
.memo-grid {
  display: grid;
  grid-template-columns: 74px 1fr;
  gap: 5px 12px;
  font-size: 13px;
  margin-bottom: 16px;
}
.memo-grid dt { color: var(--ink-3); font-weight: 600; }
.memo-grid dd { margin: 0; }
.memo-body { font-size: 13.5px; line-height: 1.85; white-space: pre-wrap; }

/* approval cards */
.appr { display: flex; gap: 12px; align-items: flex-start; }
.appr .a-main { min-width: 0; flex: 1; }
.appr .a-name { font-weight: 650; }
.appr .a-step { font-size: 12px; color: var(--ink-3); }
.appr .a-comment {
  margin-top: 9px;
  padding: 9px 12px;
  background: var(--surface-2);
  border-left: 3px solid var(--line);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: 13px;
  font-family: var(--thai), var(--sans);
}

/* audit trail */
.trail { margin-top: 12px; border-top: 1px solid var(--line-2); padding-top: 12px; }
.trail-row {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12.5px;
  padding: 4px 0;
}
.trail-row .t-at { margin-left: auto; color: var(--ink-3); font-size: 11.5px; font-variant-numeric: tabular-nums; }

/* workflow diff */
.diff-row {
  display: flex;
  gap: 11px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line-2);
  align-items: flex-start;
}
.diff-row:last-child { border-bottom: 0; }
.op {
  width: 22px; height: 22px;
  border-radius: 6px;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 800;
  flex: none;
  line-height: 1;
}
.op.add { background: var(--ok-bg); color: var(--ok); }
.op.modify { background: var(--warn-bg); color: var(--warn); }
.op.remove { background: var(--err-bg); color: var(--err); }
.diff-row .d-label { font-weight: 600; font-size: 13px; }
.diff-row .d-detail { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.diff-ba { display: flex; gap: 8px; align-items: center; margin-top: 6px; flex-wrap: wrap; }
.chip {
  font-family: var(--mono);
  font-size: 11px;
  padding: 2px 7px;
  border-radius: 5px;
  background: var(--line-2);
  color: var(--ink-2);
}
.chip.before { background: var(--err-bg); color: var(--err); text-decoration: line-through; }
.chip.after { background: var(--ok-bg); color: var(--ok); }

/* guardrail checklist */
.guards { margin-top: 4px; }
.guard {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 7px 0;
  font-size: 12.5px;
}
.guard .g-ico { flex: none; margin-top: 1px; }
.guard.ok .g-ico { color: var(--ok); }
.guard.warn .g-ico { color: var(--warn); }
.guard.fail .g-ico { color: var(--err); }
.guard.fail .g-label { color: var(--err); }

/* the publish gate refusing an AI-proposed change */
.blocked-banner {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  padding: 13px 15px;
  margin-bottom: 14px;
  border-radius: var(--r);
  background: var(--err-bg);
  border: 1px solid #F3C4C1;
}
.blocked-banner .bb-ico { color: var(--err); flex: none; margin-top: 1px; }
.blocked-banner .bb-title { font-weight: 700; color: var(--err); font-size: 13px; }
.blocked-banner .bb-body { font-size: 12.5px; color: var(--ink-2); margin-top: 4px; }
.guard .g-label { font-weight: 600; }
.guard .g-detail { color: var(--ink-3); }

/* mini flow used inside the diff card */
.mini-flow {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 12px;
  background: var(--surface-2);
  border-radius: var(--r-sm);
  margin-bottom: 12px;
}
.mini-node {
  padding: 5px 9px;
  border-radius: 6px;
  background: #fff;
  border: 1px solid var(--line);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}
.mini-node.changed { border-color: var(--warn); background: var(--warn-bg); color: var(--warn); }
.mini-node.added { border-color: var(--ok); background: var(--ok-bg); color: var(--ok); }
.mini-node.decision { border-radius: 999px; border-style: dashed; }
.mini-arrow { color: var(--ink-3); font-size: 11px; }
/* The compose-plan source chips carry a Thai clause rather than a step name, so
   they must be allowed to wrap where .mini-node deliberately does not. */
.mini-node.src { white-space: normal; }

/* --------------------------------------------- workflow flow diagram widget --
   The same vocabulary the architecture document draws with (.flow / .fnode /
   .farrow, docs/architecture/index.html:356-489), retuned from print mm/pt to
   screen px. The client has already read these diagrams on paper; drawing the
   identical shape in the chat is what makes "the agent just changed your
   process" legible without a workshop. Node type is carried by the border
   colour alone — no icons — exactly as in the document. */
.wfx-name { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 13px; }
.wfx-name .swatch { width: 4px; height: 17px; border-radius: 3px; flex: none; }
.wfx-name .tag { margin-left: auto; }

.flow { padding: 2px 0 0; }

.fnode {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 3px 10px;
  padding: 9px 12px;
  border: 1.2px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
}
/* Thai runs have no inter-word spaces, so a long label has no break opportunity
   and would set the flex item's min-content width to the whole string — that is
   what pushes a card wider than its column. min-width:0 plus an explicit break
   opportunity is what keeps this widget inside the chat. Both text spans need
   it: a decision node's whole question lives in .fn-t. */
.fnode .fn-t { min-width: 0; font-weight: 650; font-size: 13px; overflow-wrap: anywhere; }
.fnode .fn-s {
  min-width: 0;
  font-size: 12px;
  color: var(--ink-3);
  font-family: var(--thai), var(--sans);
  overflow-wrap: anywhere;
}

/* Badges group hard right. The auto margin belongs to the first badge only, so
   two or three of them travel together instead of spreading across the row. */
.fnode .fn-b {
  margin-left: auto;
  white-space: nowrap;
  font-size: 10.5px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 4px;
}
.fnode .fn-chg { margin-left: auto; white-space: nowrap; font-family: var(--thai), var(--sans); }
.fnode .fn-b ~ .fn-b,
.fnode .fn-b ~ .fn-chg { margin-left: 0; }
.fnode .fn-form { color: #fff; background: var(--ink); }
.fnode .fn-tool { font-family: var(--mono); font-weight: 600; background: var(--line-2); color: var(--ink-3); }
.fnode .fn-sla { background: var(--info-bg); color: var(--blue); }
.fnode .fn-rej { padding: 0 6px; color: var(--err); border: 1px dashed var(--err); }
/* The branch qualifier is a Thai clause, not a token, so it wraps alongside the
   description instead of joining the nowrap badge group on the right. */
.fnode .fn-branch {
  min-width: 0;
  font-size: 11px;
  font-weight: 650;
  padding: 1px 7px;
  border-radius: 4px;
  background: var(--warn-bg);
  color: var(--warn);
  overflow-wrap: anywhere;
}

/* Type is not a colour — see the matching note on .cnode.* below; these two
   drawings share one vocabulary and have to change together. Shape survives
   (decision pill, dashed = external), hue does not. */
.fnode.decision {
  border-radius: 999px;
  justify-content: center;
}
.fnode.ext { border-style: dashed; }

/* Task #37 — per-node detail panels. Only fromDraft rows get these (see
   wWorkflowFlow, app.js); every other flow card's .fnode is unaffected. */
.fnode.clickable { cursor: pointer; }
.fnode.clickable:hover { border-color: var(--ink-3); }
.fnode-chevron { color: var(--ink-3); font-size: 13px; flex: none; }
.fnode-detail {
  margin-top: -1px;
  border: 1.2px solid var(--line);
  border-top: 0;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  background: var(--surface-2);
  padding: 10px 12px;
  font-size: 12px;
}

/* Vertical connector. The 9px arrowhead is centred on the 1.2px rule:
   0.6 − 4.5 = −3.9px. */
.farrow {
  height: 16px;
  margin-left: 20px;
  border-left: 1.2px solid var(--ink-3);
  position: relative;
}
.farrow::after {
  content: '';
  position: absolute;
  left: -3.9px;
  bottom: -1px;
  border: 4.5px solid transparent;
  border-top-color: var(--ink-3);
  border-bottom: 0;
}
/* The document positions this label absolutely with nowrap because its labels
   are three English words. Ours are a Thai clause, so the label sits in normal
   flow and the connector grows with it — the annotation wraps down the arrow
   instead of widening the card. */
.farrow.lbl { height: auto; min-height: 16px; }
.farrow.lbl::before {
  content: attr(data-l);
  display: block;
  margin-left: 11px;
  padding: 1px 0 9px;
  font-size: 11px;
  font-weight: 650;
  line-height: 1.5;
  color: var(--ink-3);
  font-family: var(--thai), var(--sans);
  overflow-wrap: anywhere;
}

/* Diff affordance. When the agent re-draws a flow after an adjustment the client
   must see WHICH nodes moved without scrolling back to the change list, so the
   change state lives on the node itself. An inset shadow rather than a ::before
   bar, because it follows border-radius for free — including the 999px decision
   pill. Colours match .op.add/.modify/.remove (above) and .mini-node.changed /
   .added, so add is green everywhere in this product and modify amber. */
.fnode.chg-added,
.fnode.chg-modified,
.fnode.chg-removed { padding-left: 15px; animation: rise .3s ease both; }
.fnode.chg-added { box-shadow: inset 3px 0 0 var(--ok); }
.fnode.chg-modified { box-shadow: inset 3px 0 0 var(--warn); }
.fnode.chg-removed { box-shadow: inset 3px 0 0 var(--err); opacity: .72; }
.fnode.chg-removed .fn-t,
.fnode.chg-removed .fn-s { text-decoration: line-through; }

/* Derived from the nodes actually drawn — a fixed legend naming node types the
   flow does not contain is a small lie, and a client reading carefully spots it. */
.flow-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--line-2);
  font-size: 11.5px;
  color: var(--ink-3);
  font-family: var(--thai), var(--sans);
}
.flow-legend span { display: inline-flex; align-items: center; gap: 6px; }
.flow-legend i { width: 10px; height: 10px; border-radius: 3px; border: 1.2px solid; flex: none; }

/* --------------------------------------------------------- canvas graph --
   2D counterpart to .flow/.fnode above (plan D2/D3, _temp/plans/demo-canvas/
   plan.md) — the same step DSL laid out in rank/column space instead of one
   vertical stack, so procurement's three lanes finally sit apart in space
   rather than sharing a text tag. SVG edges under absolutely-positioned
   .cnode divs — Thai has no inter-word spaces, so HTML text-wrap/clamp
   beats hand-rolled SVG line-breaking, and the .fnode kind-colour
   vocabulary above already exists, so kind borders reuse those exact
   colours rather than a second palette. */
.cv-root { display: flex; flex-direction: column; height: 100%; min-height: 0; }

.cv-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-2);
  flex: none;
}
.cv-head-name { font-weight: 650; font-size: 13px; }
.cv-head .tag:last-child { margin-left: auto; }

.cv-pane { position: relative; flex: 1; min-height: 0; }
/* Dot grid rather than a flat fill — same idea as a design-tool canvas, it
   gives scale/pan a visible reference without competing with node colour. */
.canvas-viewport {
  position: absolute;
  inset: 0;
  overflow: auto;
  background-image: radial-gradient(var(--line) 1px, transparent 1px);
  background-size: 16px 16px;
}
.canvas-world { position: relative; transform-origin: 0 0; }

.cv-edges { position: absolute; top: 0; left: 0; overflow: visible; pointer-events: none; }
.cedge { fill: none; stroke: var(--ink-3); stroke-width: 1.4; }
.cedge.reject { stroke: var(--err); stroke-dasharray: 4 3; }
.cv-arrowhead { fill: var(--ink-3); }
.cv-arrowhead.reject { fill: var(--err); }
/* dasharray bigger than any path this layout produces turns dashoffset
   into a plain reveal-from-start regardless of the path's real length. */
.cedge.cv-draw { stroke-dasharray: 600; stroke-dashoffset: 600; animation: cvDraw .5s ease forwards; }
@keyframes cvDraw { to { stroke-dashoffset: 0; } }

.cedge-label {
  position: absolute;
  /* W1-review polish: the label anchor (mx/my, layoutWorkflow above) sits
     70% of the way down the edge, i.e. close to the target row — the extra
     -5px keeps the pill from reading as flush against that node's top
     edge, without touching the layout math itself. */
  transform: translate(-50%, -50%) translateY(-5px);
  /* An edge label names which way the branch went — it is not a status, so
     it is not a pill. Surface fill (not amber) only so the dotted canvas
     grid doesn't read through the text; grey so it stays subordinate to
     the nodes it sits between. */
  background: var(--surface);
  color: var(--ink-3);
  font-size: 10.5px;
  font-weight: 600;
  padding: 1px 5px;
  border-radius: 3px;
  white-space: nowrap;
  font-family: var(--thai), var(--sans);
}

/* Tool badge sits absolutely (see .cn-top below) rather than as a flex
   sibling above the name — a flex column here left .cn-name only ~1 line
   of the node's height no matter what -webkit-line-clamp said, because the
   badge row's own height came out of the same fixed box. Positioning it out
   of flow instead gives .cn-name the full padded content box to clamp to
   two real lines in, at a NODE_H that still leaves a usable gap to ROW_H. */
.cnode {
  position: absolute;
  padding: 18px 9px 4px;
  border: 1.2px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  overflow: hidden;
}
/* Type is NOT a colour. Every node here already states its type in the
   monospace .cn-tool badge ("approval.request", "notify.push"), so a fill
   per type repeated information the label carried and cost six competing
   hues to do it. Worse, it fought the two overlays that DO carry a rule:
   a removed system step rendered a red inset bar inside a blue-filled card.
   Colour on this canvas now means state and only state — run state
   (.done/.active/.rejected) and diff state (.chg-*), both below.
   Shape still distinguishes type, because shape doesn't compete: the
   decision pill is diagram vocabulary, and dashed = external system. */
.cnode.decision { border-radius: 999px; }
.cnode.decision .cn-name { text-align: center; }
.cnode.ext { border-style: dashed; }
.cnode.ghost { opacity: .65; border-style: dashed; }

.cn-top { position: absolute; top: 5px; left: 9px; right: 9px; display: flex; align-items: center; gap: 6px; line-height: 1; }
.cn-tool { font-size: 9.5px; font-weight: 600; color: var(--ink-3); background: var(--line-2); padding: 0 4px; border-radius: 4px; }
/* Fix 1 — quiet config-fact marks, not a badge cluster: one small outline
   icon each, sitting in normal flow between the tool badge and the (still
   margin-left:auto, still rightmost) reject mark. CC is informational only
   (grey, same hue as .cn-tool) — Threshold carries a real LOA/V6-checked
   rule, so it reuses .cnode.decision's own --warn hue rather than inventing
   a new colour. */
.cn-cc { color: var(--ink-3); flex: none; display: flex; }
.cn-thr { color: var(--warn); flex: none; display: flex; }
/* Same slot, same scale as .cn-tool's own type badge — text, not an icon
   (a sparkle here is exactly the "reads as decoration" case), purple to
   match every other surface the AI mark appears on (tool catalogue,
   flow-card badge, workflow-detail tools list) — one mark, one hue. */
.cn-ai { font-size: 9.5px; font-weight: 600; color: var(--purple); background: var(--purple-bg); padding: 0 4px; border-radius: 4px; flex: none; }
.cn-rej { margin-left: auto; color: var(--err); flex: none; display: flex; }
/* I: OCCURRENCE, not capability — solid disc replaces the outline ✕
   (app.js above, mutually exclusive with .cn-rej) specifically on a
   rejected node. Filled-vs-outline is a shape cue, not a shade of red, so
   it holds up in a still screenshot/photo where the pulse (.cnode.active,
   below) has already stopped and the border-hue gap between active/
   rejected reads as "still just red" from a distance. */
.cn-rej-mark {
  margin-left: auto; flex: none; display: flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; border-radius: 50%; background: var(--err); color: #fff;
}
/* Same fix .fnode .fn-t uses above: Thai has no break opportunity of its
   own, so overflow-wrap:anywhere supplies one instead of the label
   blowing out the fixed-size node.
   line-height is deliberately NOT set here — it inherits 1.75 from .th
   above. A tighter 1.3 (this element's own font-size/weight otherwise
   matched the badge) measurably drops Thai tone/vowel marks in THIS
   -webkit-line-clamp box whenever a monospace font is also in use
   anywhere on the page (verified by isolating the two variables against
   each other in the browser — a real rendering-engine interaction, not
   a guess); 1.75 is also what the rest of the app already uses for
   every other piece of Thai body text, so this isn't a new number. */
.cn-name {
  font-size: 11.5px;
  font-weight: 650;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Diff overlay — same colours/idiom as .fnode.chg-* above. */
.cnode.chg-added { box-shadow: var(--shadow-sm), inset 3px 0 0 var(--ok); }
.cnode.chg-modified { box-shadow: var(--shadow-sm), inset 3px 0 0 var(--warn); }
.cnode.chg-removed { box-shadow: var(--shadow-sm), inset 3px 0 0 var(--err); opacity: .72; }
.cnode.chg-removed .cn-name { text-decoration: line-through; }

/* Run state — done/active reuse the exact stepper colours and the
   existing @keyframes pulse (app.css above) rather than a second pulse. */
.cnode.done { border-color: var(--ok); background: var(--ok-bg); }
.cnode.active { border-color: var(--red); box-shadow: 0 0 0 4px rgba(27, 43, 69, .13); animation: pulse 1.8s ease-in-out infinite; }
.cnode.rejected { border-color: var(--err); background: var(--err-bg); }

/* Dry-run trace (D4 canvas.trace). */
.cnode.cv-skip { border-style: dashed; border-color: var(--ink-3); opacity: .55; }
.cnode.cv-warn { border-color: var(--warn); box-shadow: 0 0 0 3px rgba(178, 106, 0, .16); }
.cnode.cv-seq-visited { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(3, 105, 161, .16); }

.cnode.cv-enter { animation: cvNodeIn .28s ease both; }
@keyframes cvNodeIn { from { opacity: 0; transform: scale(.85); } }

.cv-zoom {
  position: absolute;
  right: 12px;
  bottom: 12px;
  display: flex;
  gap: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px;
  box-shadow: var(--shadow);
}
.cv-zoom button {
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-2);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.cv-zoom button:hover { background: var(--line-2); }

/* Fixed pane corner rather than cursor-anchored — sidesteps every
   "clipped at the viewport edge" case a cursor-anchored popover would need
   special-casing for. Doubles as the Thai-clamp escape hatch (D5). */
.cv-pop {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 240px;
  max-height: calc(100% - 24px);
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  padding: 12px;
  font-size: 12.5px;
  z-index: 2;
}
.cv-pop-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; font-weight: 650; margin-bottom: 4px; }
.cv-pop-x { border: 0; background: transparent; color: var(--ink-3); cursor: pointer; flex: none; padding: 0; }
.cv-pop-facts { margin: 8px 0 0; }
.cv-pop-facts div { display: flex; gap: 8px; padding: 3px 0; }
.cv-pop-facts dt { color: var(--ink-3); width: 70px; flex: none; }
.cv-pop-facts dd { margin: 0; min-width: 0; word-break: break-word; }

/* Library modal embed (1.8). The modal (.modal above) scrolls as a plain
   block, so .cv-root's height:100% needs an explicit height here rather
   than the rail's flex:1 — internal .canvas-viewport handles pan/scroll
   past that, same as everywhere else the canvas renders. Procurement (the
   headline shot — 3 real lanes instead of one lane tag per step) runs
   tall at full scale, so this is sized to show several ranks at once
   rather than to fit the whole graph unscrolled. */
.wf-canvas { height: min(620px, 66vh); margin-bottom: 4px; }

/* 3.0: the default 620px modal width is fine for the approval/lifecycle
   variants, but starves a 3-lane graph (procurement's world is ~608px
   wide) — cvFitScale (app.js) fits to that width, so a cramped modal is a
   cramped graph. Scoped with :has() to exactly the instance that embeds
   .wf-canvas, so the plain-text modals (openApproval, openWorkflowAction)
   are untouched. */
.modal:has(.wf-canvas) { width: min(1100px, 92vw); }

/* questionnaire — a segmented answer picker. Clicking beats typing when the
   person answering is standing in front of a room, and the pre-selected option
   makes this a confirmation rather than a composition. */
.q-why {
  display: flex;
  gap: 6px;
  align-items: flex-start;
  font-size: 11.5px;
  color: var(--ink-3);
  margin: -1px 0 8px;
}
.q-why .ico { flex: none; margin-top: 3px; }

.q-opts { display: flex; flex-wrap: wrap; gap: 6px; }
.q-opt {
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
  transition: border-color .12s, background .12s, color .12s;
}
.q-opt:hover { border-color: var(--ink-3); background: var(--surface-2); }
.q-opt.on { border-color: var(--red); background: var(--blush); color: var(--red-dark); }

/* a dry-run step the test flagged but did not fail */
.step.warned .bullet { background: var(--warn); border-color: var(--warn); color: #fff; }
.step.warned::before { background: var(--warn); }

/* ---------------------------------------------------------- composer */
.composer { flex: none; padding: 10px 24px 20px; background: linear-gradient(to bottom, transparent, var(--bg) 22%); }
.composer-inner { max-width: 780px; margin: 0 auto; }

.hint {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 9px;
  padding: 8px 12px;
  border-radius: var(--r);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: 12.5px;
  font-weight: 600;
  animation: rise .3s ease both;
}

.box {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  padding: 10px 12px;
  display: flex;
  gap: 10px;
  align-items: flex-end;
  transition: border-color .12s, box-shadow .12s;
}
.box:focus-within { border-color: var(--red); box-shadow: 0 0 0 3px rgba(27, 43, 69, .09), var(--shadow); }

.box textarea {
  flex: 1;
  border: 0;
  outline: 0;
  resize: none;
  background: none;
  max-height: 160px;
  min-height: 24px;
  line-height: 1.55;
  font-family: var(--thai), var(--sans);
}

.send {
  width: 32px; height: 32px;
  border-radius: 8px;
  background: var(--red);
  color: #fff;
  display: grid;
  place-items: center;
  flex: none;
  transition: background .12s, opacity .12s;
}
.send:hover { background: var(--red-dark); }
.send[disabled] { background: var(--line); color: var(--ink-3); pointer-events: none; }

.composer-foot {
  margin-top: 7px;
  font-size: 11px;
  color: var(--ink-3);
  text-align: center;
}

/* ------------------------------------------------------- right panel */
.rail {
  width: clamp(380px, 40vw, 620px);
  flex: none;
  border-left: 1px solid var(--line);
  background: var(--surface);
  overflow-y: auto;
  display: none;
}
/* flex column, not the plain block .rail-sec stack used to be: the canvas
   (.rail-canvas) needs a definite height to hand its own height:100%
   .cv-root (canvas section above), and flex:1 off a column with a definite
   total height (this element already stretches to the view row's height,
   see #view-chat/.view above) is what supplies one. */
.rail.on { display: flex; flex-direction: column; }
.rail-canvas { flex: 1; min-height: 0; }

.rail-sec { padding: 16px; border-bottom: 1px solid var(--line-2); }
.rail-sec:last-child { border-bottom: 0; }

.rail-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  gap: 7px;
}
.rail-title .tag { margin-left: auto; text-transform: none; letter-spacing: 0; }

/* run step timeline */
.steps { position: relative; }
.step {
  display: flex;
  gap: 11px;
  padding-bottom: 15px;
  position: relative;
}
.step:last-child { padding-bottom: 0; }
.step::before {
  content: '';
  position: absolute;
  left: 10px;
  top: 22px;
  bottom: 0;
  width: 2px;
  background: var(--line);
}
.step:last-child::before { display: none; }

.step .bullet {
  width: 21px; height: 21px;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: var(--surface);
  display: grid;
  place-items: center;
  flex: none;
  z-index: 1;
  font-size: 10px;
  font-weight: 700;
  color: var(--ink-3);
}
.step.done .bullet { background: var(--ok); border-color: var(--ok); color: #fff; }
.step.done::before { background: var(--ok); }
.step.active .bullet {
  border-color: var(--red);
  color: var(--red);
  box-shadow: 0 0 0 4px rgba(27, 43, 69, .13);
  animation: pulse 1.8s ease-in-out infinite;
}
@keyframes pulse {
  50% { box-shadow: 0 0 0 7px rgba(27, 43, 69, .06); }
}
.step.rejected .bullet { background: var(--err); border-color: var(--err); color: #fff; }

.step .s-name { font-size: 13px; font-weight: 600; line-height: 1.35; }
.step.pending .s-name { color: var(--ink-3); font-weight: 500; }
.step .s-meta { font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }
.step .s-tool {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--ink-3);
  background: var(--line-2);
  padding: 0 5px;
  border-radius: 4px;
  display: inline-block;
  margin-top: 4px;
}

/* fact rows */
.facts { font-size: 13px; }
.fact { display: flex; gap: 10px; padding: 5px 0; }
.fact dt { color: var(--ink-3); width: 96px; flex: none; }
.fact dd { margin: 0; font-weight: 550; min-width: 0; }

/* ------------------------------------------------- inbox / library views */
.page { flex: 1; overflow-y: auto; padding: 26px 32px 60px; }
.page-inner { max-width: 980px; margin: 0 auto; }

.page h1 { margin: 0 0 4px; font-size: 22px; letter-spacing: -.02em; }
.page .lede { margin: 0 0 26px; color: var(--ink-3); max-width: 620px; }

.list-card {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.list-row {
  display: flex;
  gap: 14px;
  align-items: center;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line-2);
  text-align: left;
  width: 100%;
  transition: background .12s;
}
.list-row:last-child { border-bottom: 0; }
.list-row:hover { background: var(--surface-2); }
.list-row .lr-main { min-width: 0; flex: 1; }
.list-row .lr-title { font-weight: 600; font-size: 13.5px; }
.list-row .lr-meta { font-size: 12px; color: var(--ink-3); margin-top: 2px; display: flex; gap: 10px; flex-wrap: wrap; }
.list-row .lr-amt { font-variant-numeric: tabular-nums; font-weight: 650; }

.empty-row { padding: 40px; text-align: center; color: var(--ink-3); }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(288px, 1fr));
  gap: 14px;
}

/* Flat, hairline border, modest radius. The workflow's identity colour lives on the
   reference label only — a coloured side-tab on every card is decoration, and it makes
   six cards shout at equal volume instead of letting the names do the work. */
.wf-card {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  padding: 16px;
  text-align: left;
  transition: border-color .13s, background .13s;
  position: relative;
}
/* No lift on hover: a card that jumps is decoration, and it fights the eye when
   scanning seven of them. Border and surface carry the affordance instead. */
.wf-card:hover { border-color: var(--ink-3); background: var(--surface-2); }
.wf-card .wf-ref { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--c); }
.wf-card h3 { margin: 5px 0 2px; font-size: 15px; letter-spacing: -.01em; }
.wf-card .wf-th { font-size: 12.5px; color: var(--ink-3); font-family: var(--thai); }
.wf-card .wf-desc { font-size: 12.5px; color: var(--ink-2); margin: 9px 0 12px; min-height: 34px; }
/* row-gap because a status tag now joins the version tag, and at the 288px grid
   minimum above the row would otherwise overflow the card. */
.wf-card .wf-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 6px; font-size: 11.5px; color: var(--ink-3); }

/* Archived definitions stay in the library — it is the audit surface, not just a
   menu — but they must not read as available work. */
.wf-card.dim { opacity: .58; }
.wf-card.dim:hover { opacity: 1; }

.tool-card {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  padding: 14px;
}
.tool-card .tc-head { display: flex; align-items: center; gap: 9px; margin-bottom: 7px; }
.tool-card .tc-id { font-family: var(--mono); font-size: 12px; font-weight: 600; }
.tool-card .tc-sum { font-size: 12.5px; color: var(--ink-2); }
.tool-card .tc-sys { font-size: 11.5px; color: var(--ink-3); margin-top: 8px; display: flex; align-items: center; gap: 6px; }

.kind-core { background: var(--info-bg); color: var(--blue); }
.kind-interaction { background: var(--blush); color: var(--red-dark); }
.kind-integration { background: var(--warn-bg); color: var(--warn); }

/* section heading inside pages */
.sec-h {
  margin: 30px 0 12px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.sec-h:first-child { margin-top: 0; }

/* ------------------------------------------------------------- modal */
.scrim {
  position: fixed;
  inset: 0;
  background: rgba(27, 29, 38, .42);
  display: none;
  place-items: center;
  z-index: 60;
  padding: 24px;
  animation: fade .16s ease both;
}
.scrim.on { display: grid; }
@keyframes fade { from { opacity: 0; } }

.modal {
  width: 620px;
  max-width: 100%;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  animation: pop .2s cubic-bezier(.2, .9, .3, 1.2) both;
}
@keyframes pop { from { opacity: 0; transform: scale(.97) translateY(8px); } }

.modal-head {
  padding: 18px 20px;
  border-bottom: 1px solid var(--line-2);
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.modal-head h3 { margin: 0; font-size: 16px; letter-spacing: -.01em; }
.modal-head .mh-sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; font-family: var(--mono); }
.modal-body { padding: 20px; }
.modal-foot {
  padding: 14px 20px;
  border-top: 1px solid var(--line-2);
  background: var(--surface-2);
  display: flex;
  gap: 9px;
  align-items: center;
}
/* spacer-l was only ever defined for .card-actions, so the two spacers already
   sitting in modal feet were no-ops. Both footers now behave as their markup
   always intended. */
.modal-foot .spacer-l { margin-left: auto; }

/* --------------------------------------------------------- toast */
.toasts {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 80;
  display: flex;
  flex-direction: column;
  gap: 9px;
  align-items: flex-end;
}
.toast {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 15px;
  border-radius: var(--r);
  background: var(--ink);
  color: #fff;
  box-shadow: var(--shadow-lg);
  font-size: 13px;
  max-width: 340px;
  animation: slideIn .26s cubic-bezier(.2, .9, .3, 1.1) both;
}
@keyframes slideIn { from { opacity: 0; transform: translateX(16px); } }
.toast .t-title { font-weight: 650; }
.toast .t-sub { color: #ffffffb0; font-size: 12px; margin-top: 1px; }
.toast.leaving { animation: slideOut .2s ease both; }
@keyframes slideOut { to { opacity: 0; transform: translateX(16px); } }

/* -------------------------------------------------------- utilities */
.mono { font-family: var(--mono); font-size: 12px; }
.muted { color: var(--ink-3); }
.small { font-size: 12px; }
.strong { font-weight: 650; }
.row { display: flex; align-items: center; gap: 8px; }
.stack { display: flex; flex-direction: column; gap: 8px; }
.nowrap { white-space: nowrap; }
.mt0 { margin-top: 0; } .mt8 { margin-top: 8px; } .mt12 { margin-top: 12px; } .mt16 { margin-top: 16px; }
.mb0 { margin-bottom: 0; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: #0000001f; border-radius: 6px; border: 3px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: #00000033; background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ======================================================================
   Master data (spec §8) — routing-lookup/budget-check widgets (W4) and the
   master data view (W5). Appended; no existing selector above is modified.
   ========================================================================== */

/* Generic data-grid table. Each usage sets --cols inline (e.g.
   style="--cols:60px 1fr 90px") for its own column widths — one grid
   recipe, many tables. .mtable-wrap scrolls internally so the PAGE never
   gains a horizontal scrollbar on a wide table (routing/allocation). */
.mtable-wrap { overflow-x: auto; }
.mtable {
  display: grid;
  grid-template-columns: var(--cols);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--surface);
  min-width: 640px;
  font-size: 12.5px;
}
.mt-h {
  background: var(--surface-2);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--ink-3);
  padding: 8px 10px;
}
.mt-c {
  padding: 8px 10px;
  border-top: 1px solid var(--line-2);
  display: flex;
  align-items: center;
  min-width: 0;
}
.mt-c.num { justify-content: flex-end; text-align: right; font-variant-numeric: tabular-nums; }
.mt-c.warn { background: var(--warn-bg); color: var(--warn); }
.mt-row-click { cursor: pointer; }
.mt-row-click:hover .mt-c { background: var(--surface-2); }

.src-badge {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 1px 5px;
  white-space: nowrap;
}
/* C7: a row the admin authored by answering questions, sitting in a table
   where every other row cites an Excel cell. It has to read as a different
   KIND of origin, not a different shade of the same one — so Thai face
   rather than mono, and the one green in this table that means "this is
   yours". Colour carries the rule here; nothing else in the column is
   coloured. */
.src-qn {
  font-family: var(--thai), var(--sans);
  color: var(--ok);
  border-color: var(--ok);
}
/* A conditional gate naming its own condition, subordinate to the role it
   qualifies. */
.chain-note { color: var(--ink-3); font-size: 10.5px; }

/* Approval chain, both in the routing-lookup widget and the master view's
   routing table — same chip shape, so a client sees one visual language for
   "who approves" everywhere it renders. */
.chain { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12.5px; }
.chain-step {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
}
.chain-arrow { color: var(--ink-3); }
.chain-par { padding: 0 6px; }

/* 2-col math grid (label / amount), amounts right-aligned tabular-nums —
   reuses .lr-amt's convention rather than a new number class. */
.bc-math {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 5px 12px;
  font-size: 13px;
  margin-top: 10px;
}
.bc-math .bc-total { font-weight: 700; border-top: 1px solid var(--line-2); padding-top: 5px; margin-top: 2px; }
.bc-math .lr-amt { text-align: right; }

/* The matched-row strip in wRoutingLookup's phase 2. */
.ml-hit {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  background: var(--surface-2);
  border-left: 3px solid var(--ok);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  margin-bottom: 10px;
}
.ml-phase1 { display: flex; align-items: center; gap: 8px; padding: 8px 2px; font-size: 12.5px; color: var(--ink-2); }

/* Pill pair for the master view's e-Memo / e-Budget sub-tabs. */
.mst-tabs { display: inline-flex; gap: 4px; padding: 3px; background: var(--surface-2); border: 1px solid var(--line-2); border-radius: 999px; margin-bottom: 18px; }
.mst-tabs button {
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 650;
  color: var(--ink-2);
  background: transparent;
  border: none;
}
.mst-tabs button.on { background: var(--surface); box-shadow: var(--shadow-sm); color: var(--ink); }

/* Phase-negative allocation cell (BG-03's deliberate soft-warning row). */
.mt-warn { background: var(--warn-bg); color: var(--warn); }

/* Small inline usage bar for the allocation table row. */
.bar { width: 72px; height: 6px; border-radius: 3px; background: var(--line-2); overflow: hidden; display: inline-block; }
.bar-fill { height: 100%; background: var(--purple); }
.bar-fill.warn { background: var(--warn); }

/* 12-bar monthly view (allocation row modal): track height = plan, solid
   fill = actual (months already closed), hatched top segment = committed
   (reserve-on-submit sitting in the current month), current month ringed. */
.month-bars { display: flex; align-items: flex-end; gap: 5px; height: 90px; margin-top: 10px; }
.month-bars .mb-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 0; }
.month-bars .mb-track { width: 100%; height: 64px; display: flex; flex-direction: column-reverse; background: var(--line-2); border-radius: 3px; overflow: hidden; }
.month-bars .mb-plan { width: 100%; background: var(--line); }
.month-bars .mb-actual { width: 100%; background: var(--purple); }
.month-bars .mb-committed { width: 100%; background: repeating-linear-gradient(45deg, var(--warn), var(--warn) 3px, var(--warn-bg) 3px, var(--warn-bg) 6px); }
.month-bars .mb-lbl { font-size: 9.5px; color: var(--ink-3); }
.month-bars .mb-col.current .mb-track { box-shadow: 0 0 0 2px var(--red) inset; }

/* Small avatar+grade pair used in the org/ladder strip (§5.2 1b). */
.grade-chip { display: inline-flex; align-items: center; gap: 5px; }
.grade-chip .avatar { width: 18px; height: 18px; font-size: 9px; }

/* ======================================================================
   Creation-flow W2 — revision-engine chips. Quiet affordances by contract
   (§5.6): text buttons, thin border, no fills, no per-chip icons. Used both
   standalone (the composer's suggestion strip, #chips) and inside a card
   body (wReviseHelp's menu, the clarify variant's candidate list).
   ========================================================================== */
.chip-row { display: flex; flex-wrap: wrap; gap: 6px; }
#chips { display: none; margin-bottom: 9px; }
#chips.on { display: flex; }
.chip-btn {
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  font-size: 12px;
  transition: border-color .12s, color .12s;
}
.chip-btn:hover { border-color: var(--ink-3); color: var(--ink); }

/* Test-plan cases as rows rather than stacked blocks. Two fit either way;
   a workflow with three decision points generates eight and the stacked form
   becomes a wall. The row states the path and the value, so the caret is for
   detail rather than for essentials. */
.tp-table { border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; }
.tp-row {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  border-bottom: 1px solid var(--line-2);
}
.tp-row:last-child { border-bottom: 0; }
.tp-head {
  display: flex; align-items: center; gap: 8px; padding: 9px 10px;
  background: none; border: 0; cursor: pointer; text-align: left; font: inherit; min-width: 0;
}
.tp-head:hover { background: var(--surface-2); }
.tp-caret { color: var(--ink-3); flex: none; display: flex; transition: transform .15s ease; }
.tp-row.open .tp-caret { transform: rotate(90deg); }
.tp-label { font-weight: 600; font-size: 12.5px; overflow-wrap: anywhere; }
.tp-branch { flex: none; }
.tp-val { padding: 6px 10px 6px 0; }
.tp-val .inp { max-width: 130px; }
.tp-detail {
  grid-column: 1 / -1; padding: 0 10px 9px 30px;
  font-size: 11.5px; color: var(--ink-2);
}
.tp-status { flex: none; }
.tp-verdict { font-size: 11.5px; font-weight: 600; margin-bottom: 6px; }
.tp-steps { display: flex; flex-direction: column; gap: 4px; }
.tp-step { display: flex; gap: 7px; align-items: flex-start; font-size: 11px; line-height: 1.5; }
.tp-step-n { flex: none; width: 13px; text-align: center; color: var(--ok); font-weight: 700; }
.tp-step.skip { opacity: .55; }
.tp-step.skip .tp-step-n { color: var(--ink-3); }
.tp-step.warn .tp-step-n { color: var(--warn); }
.tp-finding { margin-top: 6px; font-size: 11px; padding: 5px 8px; border-radius: 5px; }
.tp-finding.warn { background: var(--warn-bg); color: var(--warn); }
.tp-finding.info { background: var(--info-bg); color: var(--info); }

/* ====================================================== Viriyah underwriting
   Viriyah CI: blue #0033A2, gold #FCAF17 (sampled from viriyah.co.th). */
:root { --gold: #FCAF17; --gold-bg: #FFF6E0; }
.logo .dot { background: var(--gold); }
.srcb { display: inline-block; font-size: 10.5px; line-height: 1.5; padding: 0 6px; border-radius: 4px;
  white-space: nowrap; font-family: var(--mono); border: 1px solid var(--line); color: var(--ink-3); background: var(--surface-2); }
.srcb.xl { color: #0B5A2E; background: var(--ok-bg); border-color: #CDEBD9; }
.srcb.calc { color: var(--red); background: var(--info-bg); border-color: #CFE0F3; }
.srcb.syn { color: #7A5A00; background: var(--gold-bg); border-color: #F6DDA0; }
.vgrp + .vgrp { margin-top: 14px; }
.vgrp-h { font-size: 11.5px; font-weight: 600; color: var(--ink-3); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 6px; }
.vrow { display: grid; grid-template-columns: 150px 1fr auto; gap: 10px; align-items: baseline; padding: 6px 0; border-top: 1px solid var(--line-2); }
.vrow:first-child { border-top: 0; }
.vk { color: var(--ink-3); font-size: 13px; }
.vs { justify-self: end; }
.vt { width: 100%; border-collapse: collapse; font-size: 13px; }
.vt th { text-align: left; font-weight: 600; font-size: 11.5px; color: var(--ink-3); padding: 6px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
.vt td { padding: 7px 8px; border-bottom: 1px solid var(--line-2); vertical-align: top; }
.vt .r { text-align: right; white-space: nowrap; }
.vt tr.pick td { background: var(--info-bg); }
.vt tr.skip td { color: var(--ink-3); }
.vt tr.skip td:nth-child(2) { text-decoration: line-through; text-decoration-color: rgba(0,0,0,.25); }
.stagger > *, .stagger tbody > tr { animation: rise .35s both; animation-delay: calc(var(--i, 0) * 110ms + 80ms); }
.pick-table tbody tr { cursor: pointer; }
.pick-table tbody tr:hover td { background: var(--surface-2); }
.pick-table tbody tr.on td { background: var(--info-bg); }
.radio { display: inline-block; width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid var(--line); vertical-align: -2px; }
tr.on .radio { border: 4px solid var(--red); }
.verdict { display: flex; gap: 8px; align-items: flex-start; padding: 10px 12px; border-radius: var(--r-sm); font-size: 13px; }
.verdict.ok { background: var(--ok-bg); color: var(--ok); }
.verdict.warn { background: var(--warn-bg); color: var(--warn); }
.verdict.err { background: var(--err-bg); color: var(--err); }
.verdict.info { background: var(--info-bg); color: var(--red); }
.calc { border: 1px solid var(--line); border-radius: var(--r-sm); padding: 6px 12px; font-size: 13px; }
.calc > div { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; }
.calc > div.tot { border-top: 1px solid var(--line); margin-top: 4px; padding-top: 8px; font-weight: 600; }
.try { display: flex; gap: 10px; align-items: center; font-size: 13px; flex-wrap: wrap; }
.try .inp { width: 130px; }
.qpair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.qdoc { border: 1px solid var(--line); border-radius: var(--r-sm); padding: 10px 12px; background: var(--surface); }
.qpair.anim .qdoc { animation: rise .4s both; }
.qdoc.old { background: var(--surface-2); color: var(--ink-2); }
.qdoc.new { border-color: var(--red); box-shadow: 0 0 0 3px rgba(0, 51, 162, .08); }
.qpair.anim .qdoc.new { animation-delay: .35s; }
.qdoc-h { display: flex; justify-content: space-between; align-items: center; font-weight: 600; margin-bottom: 6px; }
.qrow { display: grid; grid-template-columns: 110px 1fr; gap: 8px; font-size: 12.5px; padding: 4px 0; border-top: 1px solid var(--line-2); }
.qrow > span:first-child { color: var(--ink-3); }
.qrow.diff { background: var(--gold-bg); margin: 0 -6px; padding: 4px 6px; border-radius: 4px; }
.track { display: flex; flex-direction: column; gap: 2px; }
.trk { display: grid; grid-template-columns: 24px 44px 1fr; gap: 8px; align-items: center; padding: 5px 0; opacity: .25; transition: opacity .4s; }
.trk.on { opacity: 1; }
.trk-dot { width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; background: var(--line-2); color: var(--ink-3); transition: background .4s, color .4s; }
.trk.on .trk-dot { background: var(--ok); color: #fff; }
table.mst { font-size: 12.5px; }
.list-card + .list-card { margin-top: 14px; }
@media (max-width: 720px) {
  .vrow { grid-template-columns: 1fr; gap: 2px; }
  .vs { justify-self: start; }
  .qpair { grid-template-columns: 1fr; }
  .card-body { overflow-x: auto; }
}
@media (prefers-reduced-motion: reduce) { .stagger > *, .stagger tbody > tr, .qdoc { animation: none; } }
.src-legend { margin-bottom: 12px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.rail { width: clamp(340px, 30vw, 470px); }
.cn-name { line-height: 1.4; margin-top: 3px; }
.cnode { padding-top: 24px; }
.cn-name { margin-top: 0; line-height: 1.35; }

/* ============================================ Underwriting steps in chat
   Each step card is a .fx.step block (uw-steps.css holds the step styles). The
   step brings its own Fabric avatar, so the message has no ai-badge. */
.msg.uwstep { display: block; }
.fx.step { display: block; min-width: 0; background: transparent; grid-template-columns: none;
  --r: #0033A2; --r-d: #00247A; --r-t: #E6ECF8; --sel: #E6ECF8; --bl: #0033A2; --bl-t: #E6ECF8; --sys: #0033A2; }
.fx.step .ev { margin: 0 auto 12px; width: max-content; }
.fx.step .ai { max-width: 100%; }
.fx.step.enter > * { animation: rise .4s both; }
.fx.step.enter > *:nth-child(2) { animation-delay: .12s; }
.fx.step.enter > *:nth-child(3) { animation-delay: .24s; }
.fx.step.enter .ai .c > * { animation: rise .4s both; }
.fx.step.enter .ai .c > *:nth-child(2) { animation-delay: .2s; }
.fx.step.enter .ai .c > *:nth-child(3) { animation-delay: .35s; }
.fx.step.enter .ai .c > *:nth-child(4) { animation-delay: .5s; }
.fx.step.enter .ai .c > *:nth-child(n+5) { animation-delay: .65s; }
.fx .cs-ed { width: 100%; border: 1px solid var(--bd); border-radius: 6px; background: #fff; font: inherit; color: var(--tx); }
.fx .cs-ed:focus { outline: 2px solid var(--r); outline-offset: 0; }
.fx .cs-ed.cs-chg { border-color: var(--am); background: var(--am-t); }
@media (prefers-reduced-motion: reduce) { .fx.step.enter > *, .fx.step.enter .ai .c > * { animation: none; } }
/* The underwriting steps carry wide tables (pricing bars, Monitoring pivots):
   give the chat more width and let a table scroll inside its card rather than clip. */
.msgs-inner, .composer-inner { max-width: 1100px; }
.rail { width: clamp(320px, 26vw, 470px); }
.fx.step .ai, .fx.step .ai > .c { min-width: 0; }
.fx.step .tw { overflow-x: auto; }
