/* un-seen — global theme (dark obsidian / neon gold) */
:root {
  --bg-obsidian: #050505;
  --surface-charcoal: #0a0a0a;
  --panel-bg: #0f0f0f;
  --border-subtle: #222222;
  --border-strong: #333333;
  --text-primary: #f0f0f0;
  --text-secondary: #888888;
  --accent-gold: #FFD700;
  --accent-gold-dim: rgba(255, 215, 0, .12);
  --danger: #e63946;
  --ok: #2ecc71;
  --radius: 6px;
  --topbar-h: 58px;
}
* { box-sizing: border-box; }

/* ---------- Never let the page slide sideways ----------
   Chromium showed no overflow at 375px, but Safari on iOS still did. Three reasons it can,
   none of which appear in a desktop test:

     1. iOS inflates text on its own (text auto-sizing) and the inflated text is wider than
        the box that was measured for it.
     2. overflow-x: hidden on <body> alone is unreliable there; the clip has to be on <html>
        too, and `clip` behaves where `hidden` does not.
     3. One long unbreakable string - a file path, an email, an R2 key in the activity log -
        has nothing to wrap at, so it pushes the whole layout wider than the screen.

   Each line below closes one of those. This is a floor, not a substitute for laying things
   out properly. */
html {
  -webkit-text-size-adjust: 100%;          /* 1 */
          text-size-adjust: 100%;
  overflow-x: hidden;                      /* 2 - fallback for older engines */
  overflow-x: clip;
}
html, body { margin: 0; background: var(--bg-obsidian); color: var(--text-primary); font: 14px/1.5 -apple-system, "Segoe UI", Roboto, Inter, sans-serif; overflow-x: hidden; }
body {
  overflow-x: clip;
  width: 100%;
  max-width: 100%;
  overflow-wrap: anywhere;                 /* 3 - long paths and emails wrap instead of pushing */
}
/* Media and form controls stay inside their container. iOS renders date inputs wider than
   the width they are given, which on a 375px screen is enough on its own. */
img, svg, video, canvas, iframe { max-width: 100%; }
input, select, textarea { max-width: 100%; }
#view { max-width: 100%; }
a { color: var(--accent-gold); text-decoration: none; }
h1, h2, h3 { margin: 0 0 12px; font-weight: 600; }
h2 { font-size: 20px; } h3 { font-size: 16px; color: var(--accent-gold); }
[hidden] { display: none !important; }
.muted { color: var(--text-secondary); } .small { font-size: 12px; } .mono { font-family: ui-monospace, Menlo, Consolas, monospace; }

/* Contact Guard chat (tabs/contact-guard.js) — namespaced to avoid colliding with the
   AI-assistant chat's own .msg/.bubble styles, which mean something different. */
.cg-box { border: 1px solid var(--border-subtle); border-radius: var(--radius); height: 420px; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.cg-msg { max-width: 80%; padding: 8px 12px; border-radius: 10px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle); align-self: flex-start; }
.cg-msg.mine { align-self: flex-end; background: var(--accent-gold-dim); border-color: rgba(255,215,0,.3); }
.cg-meta { margin-bottom: 3px; }
.cg-attachment a { color: var(--accent-gold); text-decoration: underline; }
.cg-held { color: var(--text-secondary); font-style: italic; }
.cg-release { background: none; border: none; color: var(--accent-gold); text-decoration: underline; cursor: pointer; font-size: inherit; padding: 0; }
.cg-brief-panel { margin-top: 14px; padding: 14px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: var(--radius); }
.cg-brief-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.cg-brief-head h3 { margin: 0; }
.cg-brief-body { margin-top: 12px; }
.cg-brief-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.cg-brief-edit textarea { min-height: 120px; }
.cg-brief-files { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.cg-brief-file { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 10px 12px; background: #111; border: 1px solid var(--border-subtle); border-radius: var(--radius); }
.cg-brief-file-info { min-width: 0; }
.cg-brief-file-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cg-brief-upload { margin-top: 12px; }
.cg-brief-upload input[type=file] { flex: 1; width: auto; min-width: 240px; }
.gold { color: var(--accent-gold); } .red { color: var(--danger); } .green { color: var(--ok); }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.spread { justify-content: space-between; }
.stack { display: flex; flex-direction: column; gap: 10px; }

/* When installed as an app the page runs under the notch and the home indicator, so the
   bar and the page bottom are padded by whatever the device reports as unsafe. */
body { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }

/* ---------- Top bar + tabs ---------- */
.topbar { position: sticky; top: 0; z-index: 50; height: calc(var(--topbar-h) + env(safe-area-inset-top)); padding-top: env(safe-area-inset-top); display: flex; align-items: center; gap: 18px; padding-left: 24px; padding-right: 24px; background: var(--surface-charcoal); border-bottom: 1px solid var(--border-subtle); }
.brand { font-weight: 800; letter-spacing: 3px; color: var(--accent-gold); font-size: 15px; white-space: nowrap; }
.brand small { color: var(--text-secondary); letter-spacing: 0; font-weight: 400; margin-left: 8px; }
.tabs { display: flex; gap: 2px; flex: 1; overflow-x: auto; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tab { padding: 7px 9px; border-radius: var(--radius); color: var(--text-secondary); cursor: pointer; white-space: nowrap; border: 1px solid transparent; }
.tab:hover { color: var(--text-primary); }
.tab.active { color: var(--accent-gold); background: var(--accent-gold-dim); border-color: rgba(255,215,0,.35); }
.bell { position: relative; cursor: pointer; padding: 6px 10px; border-radius: var(--radius); border: 1px solid var(--border-subtle); }
.bell .dot { position: absolute; top: -4px; right: -4px; background: var(--danger); color: #fff; font-size: 10px; min-width: 16px; height: 16px; border-radius: 8px; display: flex; align-items: center; justify-content: center; padding: 0 4px; }
.userchip { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent-gold); color: #000; font-weight: 700; display: flex; align-items: center; justify-content: center; font-size: 13px; }
.rolebadge { font-size: 10px; text-transform: uppercase; letter-spacing: 1px; padding: 2px 6px; border-radius: 10px; border: 1px solid var(--border-strong); color: var(--text-secondary); }
.rolebadge.admin { color: var(--accent-gold); border-color: var(--accent-gold); }
.rolebadge.editor { color: #7dd3fc; border-color: #7dd3fc; }
.rolebadge.client { color: #c4b5fd; border-color: #c4b5fd; }

main { padding: 24px; max-width: 1600px; margin: 0 auto; }
.tab-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }

/* ---------- Buttons / inputs ---------- */
.btn { background: var(--accent-gold); color: #000; border: 1px solid var(--accent-gold); padding: 8px 14px; border-radius: var(--radius); font-weight: 600; cursor: pointer; font-size: 13px; }
.btn:hover { filter: brightness(1.1); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-outline { background: transparent; color: var(--text-primary); border-color: var(--border-strong); font-weight: 500; }
.btn-outline:hover { border-color: var(--accent-gold); color: var(--accent-gold); filter: none; }
.btn-danger { background: transparent; color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: #fff; }
.btn-sm { padding: 3px 9px; font-size: 12px; }
input, select, textarea { width: 100%; background: #111; color: var(--text-primary); border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 9px 11px; font: inherit; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent-gold); }
input[type=date] { color-scheme: dark; }
input[type=checkbox] { width: auto; accent-color: var(--accent-gold); }
label { display: block; font-size: 12px; color: var(--text-secondary); margin-bottom: 4px; text-transform: uppercase; letter-spacing: .5px; }
.field { margin-bottom: 12px; }
.check { display: flex; align-items: center; gap: 8px; text-transform: none; letter-spacing: 0; font-size: 13px; color: var(--text-primary); cursor: pointer; }

/* ---------- Pill filters ---------- */
.pills { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.pill { padding: 5px 12px; border-radius: 20px; border: 1px solid var(--border-strong); color: var(--text-secondary); cursor: pointer; font-size: 12px; user-select: none; background: transparent; }
.pill:hover { border-color: var(--accent-gold); color: var(--text-primary); }
.pill.active { background: var(--accent-gold); color: #000; border-color: var(--accent-gold); font-weight: 600; }
.filterbar { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end; padding: 12px 14px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: var(--radius); margin-bottom: 16px; }
.filterbar .group { display: flex; flex-direction: column; gap: 6px; }
.datefilter { display: flex; gap: 6px; align-items: center; }
.datefilter input { width: 150px; padding: 5px 8px; font-size: 12px; }

/* ---------- Cards / grid ---------- */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
/* My Profile: edit form beside the summary card. This was an inline style, which beat the
   phone media query and forced two columns onto a 390px screen. */
.profile-grid { grid-template-columns: minmax(280px, 480px) 1fr; }
.profile-grid .row > div { min-width: 0; }
/* Support Tickets: list on the left, conversation on the right. Same inline-style problem. */
.ticket-grid { grid-template-columns: minmax(280px, 380px) 1fr; align-items: start; }
.pending-large-files { background: var(--surface-charcoal); border: 1px solid var(--accent-gold); border-radius: var(--radius); padding: 14px; margin-bottom: 16px; }
.pending-large-files h3 { color: var(--accent-gold); font-size: 14px; }
.signed-in-devices { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border-subtle); }
.card { background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: var(--radius); padding: 14px; }
.card:hover { border-color: var(--border-strong); }
.cockpit-widget[draggable="true"] { cursor: grab; }
.cockpit-widget[draggable="true"] button, .cockpit-widget[draggable="true"] select,
.cockpit-widget[draggable="true"] a { cursor: pointer; }
.cockpit-widget.is-dragging { opacity: .55; cursor: grabbing; }
.cockpit-widget-controls { display: flex; align-items: center; gap: 6px; }
.cockpit-widget-action { padding: 4px 8px; font-size: 12px; }
.cockpit-chat-message { padding: 7px 0; border-bottom: 1px solid var(--border-subtle); overflow-wrap: anywhere; }
.cockpit-chat-message:last-child { border-bottom: 0; }
.card .title { font-weight: 600; font-size: 15px; }
.card .actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.status { display: inline-block; padding: 2px 9px; border-radius: 10px; font-size: 11px; border: 1px solid var(--border-strong); color: var(--text-secondary); white-space: nowrap; }
.status.gold { border-color: var(--accent-gold); color: var(--accent-gold); }
.status.red { border-color: var(--danger); color: var(--danger); }
.status.green { border-color: var(--ok); color: var(--ok); }
.status.blue { border-color: #7dd3fc; color: #7dd3fc; }
.empty { padding: 40px; text-align: center; color: var(--text-secondary); border: 1px dashed var(--border-strong); border-radius: var(--radius); }
.list { display: flex; flex-direction: column; gap: 8px; }
.list-item { display: flex; justify-content: space-between; gap: 12px; align-items: center; padding: 12px 14px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: var(--radius); cursor: pointer; flex-wrap: wrap; }
.list-item:hover { border-color: var(--accent-gold); }
.list-item.selected { border-color: var(--accent-gold); background: var(--accent-gold-dim); }

/* ---------- Kanban ---------- */
.kanban { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: minmax(150px, auto); gap: 12px; }
.kcol { background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: var(--radius); padding: 10px; min-height: 160px; }
.kcol.over { border-color: var(--accent-gold); background: var(--accent-gold-dim); }
.kcol h4 { margin: 0 0 10px; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-secondary); display: flex; justify-content: space-between; }
.kcard { background: #111; border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 10px; margin-bottom: 8px; cursor: grab; }
.kcard:hover, .kcard.selected { border-color: var(--accent-gold); }
.kcard .sub { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
.kcard[draggable=false] { cursor: pointer; }

/* Stage picker. Drag-and-drop does not exist on touch devices, so this button is the only
   way to move a project on a phone - and a faster way than dragging on a desktop too. */
.kcard-move {
  display: block; width: 100%; margin-top: 8px; padding: 6px 8px;
  background: transparent; color: var(--text-secondary);
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  font: inherit; font-size: 12px; text-align: center; cursor: pointer;
}
.kcard-move:hover { color: var(--accent-gold); border-color: var(--accent-gold); }
@media (max-width: 900px) {
  /* Comfortable thumb target, and prominent because it is the ONLY way to move a card here. */
  .kcard-move { min-height: 44px; font-size: 14px; color: var(--accent-gold); border-color: var(--accent-gold); }
}

/* ---------- Universal header ---------- */
.universal-header { display: flex; flex-wrap: wrap; overflow-x: hidden; padding: 15px 30px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: var(--radius); align-items: center; gap: 20px 32px; margin-top: 20px; margin-bottom: 0; }
.uh-col { display: flex; flex-direction: column; }
.uh-label { font-size: 10px; color: var(--text-secondary); text-transform: uppercase; margin-bottom: 4px; letter-spacing: 1px; }
.uh-col strong { color: var(--text-primary); font-size: 16px; }
.uh-col .gold { color: var(--accent-gold); }
.uh-col.link { cursor: pointer; }
.uh-col.link:hover strong { color: var(--accent-gold); }
.co-pilot-pill { background: #333; padding: 2px 8px; border-radius: 12px; font-size: 12px; color: #fff; cursor: pointer; margin-right: 4px; display: inline-block; }
.co-pilot-pill:hover { background: var(--accent-gold); color: #000; }
.uh-actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- Timeline ---------- */
.timeline-container { padding: 20px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-top: none; }
.timeline { list-style: none; padding-left: 10px; margin: 0; border-left: 2px solid var(--border-subtle); }
.timeline li { margin-bottom: 15px; position: relative; padding-left: 20px; }
.timeline li span.dot { position: absolute; left: -26px; top: 4px; background: var(--bg-obsidian); border: 2px solid var(--accent-gold); border-radius: 50%; width: 10px; height: 10px; display: block; }
.timeline .t-title { font-size: 14px; font-weight: bold; }
.timeline .t-meta { font-size: 12px; color: var(--text-secondary); }
.timeline p { font-size: 13px; margin: 5px 0 0; }

/* ---------- Split-pane file viewer ---------- */
.breadcrumb { padding: 10px 15px; background: #111; color: var(--text-secondary); font-family: ui-monospace, Menlo, monospace; border: 1px solid var(--border-strong); border-bottom: none; border-radius: 4px 4px 0 0; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.breadcrumb .crumb { cursor: pointer; } .breadcrumb .crumb:hover { color: var(--accent-gold); }
.breadcrumb .crumb.current { color: var(--text-primary); cursor: default; }
.breadcrumb .tools { margin-left: auto; display: flex; gap: 6px; }
.split-pane { display: flex; border: 1px solid var(--border-strong); height: 500px; border-radius: 0 0 4px 4px; overflow: hidden; }
.split-list { width: 30%; min-width: 220px; border-right: 1px solid var(--border-strong); background: var(--panel-bg); overflow-y: auto; padding: 10px; }
.split-item { padding: 10px; border-bottom: 1px solid var(--border-subtle); cursor: pointer; display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.split-item:hover, .split-item.selected { background: var(--accent-gold-dim); }
.split-item .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.split-item .ico { margin-right: 10px; } .split-item.folder .ico { color: var(--accent-gold); }
.split-preview { width: 70%; background: var(--surface-charcoal); display: flex; justify-content: center; align-items: center; color: #555; position: relative; }
.split-preview iframe, .split-preview img { width: 100%; height: 100%; border: 0; object-fit: contain; }
.split-preview .ptools { position: absolute; top: 8px; right: 8px; display: flex; gap: 6px; }

/* ---------- Modal / name card / toast ---------- */
.overlay { position: fixed; inset: 0; background: rgba(0,0,0,.7); display: flex; align-items: center; justify-content: center; z-index: 100; padding: 16px; }
.modal { background: var(--surface-charcoal); border: 1px solid var(--border-strong); border-radius: 8px; width: 100%; max-width: 520px; max-height: 92vh; overflow-y: auto; padding: 22px; }
.modal.wide { max-width: 960px; }
.modal h3 { margin-bottom: 16px; }
.modal .foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
.namecard { position: fixed; z-index: 120; background: #111; border: 1px solid var(--accent-gold); border-radius: 8px; padding: 14px 16px; min-width: 240px; box-shadow: 0 10px 40px rgba(0,0,0,.6); }
.namecard .nm { font-weight: 700; font-size: 15px; }
.namecard .links { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.namecard .links a, .namecard .links button { display: inline-flex; align-items: center; gap: 4px; }
#toasts { position: fixed; bottom: 20px; right: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
.toast { background: #111; border: 1px solid var(--accent-gold); border-left-width: 4px; padding: 10px 14px; border-radius: var(--radius); min-width: 240px; animation: fade .2s; }
.toast.err { border-color: var(--danger); }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } }

/* ---------- Login ---------- */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
.login { width: 100%; max-width: 400px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: 10px; padding: 32px; }
.login .brand { display: block; text-align: center; margin-bottom: 8px; font-size: 18px; }
.demo-otp { background: var(--accent-gold-dim); border: 1px dashed var(--accent-gold); padding: 10px; border-radius: var(--radius); text-align: center; margin-bottom: 12px; }
.demo-otp b { font-size: 24px; letter-spacing: 6px; color: var(--accent-gold); }

/* ---------- Chat (tickets) ---------- */
.chat { display: flex; flex-direction: column; gap: 10px; max-height: 420px; overflow-y: auto; padding: 4px; }
.msg { max-width: 80%; padding: 10px 12px; border-radius: 10px; background: #151515; border: 1px solid var(--border-subtle); }
.msg.me { align-self: flex-end; background: var(--accent-gold-dim); border-color: rgba(255,215,0,.3); }
.msg .who { font-size: 11px; color: var(--text-secondary); margin-bottom: 3px; }

/* ---------- CMS split ---------- */
.cms-split { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.preview-card { background: var(--surface-charcoal); border: 1px solid var(--border-strong); border-radius: 8px; overflow: hidden; }
.preview-card img { width: 100%; height: 240px; object-fit: cover; background: #111; display: block; }
.preview-card .body { padding: 16px; }
.switch { position: relative; width: 40px; height: 22px; background: #333; border-radius: 11px; cursor: pointer; display: inline-block; vertical-align: middle; }
.switch::after { content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; background: #888; border-radius: 50%; transition: .15s; }
.switch.on { background: var(--accent-gold); } .switch.on::after { left: 21px; background: #000; }

/* ---------- Table (audit) ---------- */
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border-subtle); vertical-align: top; }
th { color: var(--text-secondary); font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: .5px; }
.table-wrap { overflow-x: auto; border: 1px solid var(--border-subtle); border-radius: var(--radius); }

/* ---------- Phone menu button (hidden on desktop) ---------- */
.nav-menu-button { display: none; width: 38px; height: 34px; flex: 0 0 auto; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--border-subtle); border-radius: var(--radius); cursor: pointer; padding: 0; }
.nav-menu-bars, .nav-menu-bars::before, .nav-menu-bars::after { display: block; width: 17px; height: 2px; background: var(--accent-gold); border-radius: 2px; transition: transform .18s ease, opacity .18s ease; }
.nav-menu-bars { position: relative; }
.nav-menu-bars::before, .nav-menu-bars::after { content: ''; position: absolute; left: 0; }
.nav-menu-bars::before { top: -5px; } .nav-menu-bars::after { top: 5px; }
body.nav-menu-open .nav-menu-bars { background: transparent; }
body.nav-menu-open .nav-menu-bars::before { transform: translateY(5px) rotate(45deg); }
body.nav-menu-open .nav-menu-bars::after { transform: translateY(-5px) rotate(-45deg); }
.nav-menu-backdrop { display: none; }
.nav-menu-logout { display: none; }

/* ---------- Compact navigation ----------
   The tab strip scrolls sideways with no visible scrollbar, so a tab that does not fit is
   a tab nobody knows exists. Rather than guess a screen width, app.js measures the strip
   and adds .nav-compact whenever the row would overflow (and always on a phone) - then the
   tabs become the drop-down sheet, where every one of them is reachable. */
body.nav-compact .topbar { gap: 10px; padding-left: 12px; padding-right: 12px; }
body.nav-compact .nav-menu-button { display: flex; }
body.nav-compact .brand { font-size: 13px; letter-spacing: 1.5px; }
body.nav-compact .brand small { display: none; }
body.nav-compact .topbar-logout-button { display: none; }        /* moved into the sheet */
body.nav-compact .userchip #me-name, body.nav-compact .userchip .rolebadge { display: none; }
body.nav-compact .userchip { margin-left: auto; }
body.nav-compact .bell { padding: 6px 8px; }

body.nav-compact .tabs { position: fixed; top: calc(var(--topbar-h) + env(safe-area-inset-top)); left: 0; right: 0; z-index: 49;
  flex-direction: column; gap: 2px; overflow-y: auto; overflow-x: hidden;
  max-height: calc(100vh - var(--topbar-h) - env(safe-area-inset-top)); padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
  background: var(--surface-charcoal); border-bottom: 1px solid var(--border-subtle);
  box-shadow: 0 12px 24px rgba(0,0,0,.55);
  opacity: 0; visibility: hidden; transform: translateY(-10px); pointer-events: none; transition: opacity .16s ease, transform .16s ease, visibility .16s; }
body.nav-compact.nav-menu-open .tabs { opacity: 1; visibility: visible; transform: none; pointer-events: auto; }
body.nav-compact .tab { padding: 13px 14px; border-radius: var(--radius); }   /* finger-sized targets */
body.nav-compact .nav-menu-logout { display: block; margin-top: 6px; border-top: 1px solid var(--border-subtle); border-radius: 0 0 var(--radius) var(--radius); color: var(--danger); }
body.nav-compact .nav-menu-backdrop { display: block; position: fixed; inset: calc(var(--topbar-h) + env(safe-area-inset-top)) 0 0 0; z-index: 48; background: rgba(0,0,0,.5); }

/* ---------- Responsive ----------
   Measured on a real 390x844 phone, the old top bar demanded 555px, which left the tab
   strip 0px wide: all nine tabs were invisible until the screen was rotated. Below 900px
   the tabs now live in a drop-down sheet and the bar keeps only brand, bell and avatar. */
@media (max-width: 900px) {
  main { padding: 14px 14px calc(14px + env(safe-area-inset-bottom)); }

  /* One column beats a squeezed grid on a phone. Empty stages also collapse to a thin
     header strip — six 160px empty boxes is a lot of scrolling to reach the work. */
  .kanban { grid-template-columns: 1fr; grid-auto-rows: auto; }
  .kcol { min-height: 0; }
  .grid { grid-template-columns: 1fr; }
  .cms-split { grid-template-columns: 1fr; }
  .universal-header { padding: 14px 16px; gap: 12px 18px; }

  /* The date filter's two fixed 150px inputs plus a button needed 379px inside a 362px
     bar — the only thing still pushing the page sideways. Let it wrap and share the row. */
  .filterbar { padding: 10px; gap: 12px; }
  .filterbar .group { width: 100%; min-width: 0; }
  .datefilter { flex-wrap: wrap; width: 100%; }
  .datefilter input { flex: 1 1 130px; width: auto; min-width: 0; }
  .grid > * { min-width: 0; }
  .profile-grid, .ticket-grid { grid-template-columns: 1fr; }

  /* Stacked panes need real height or the file list is a 146px slit. */
  .split-pane { flex-direction: column; height: auto; }
  .split-list, .split-preview { width: 100%; }
  .split-list { max-height: none; min-height: 45vh; }
  .split-preview { height: 55vh; }
  .detail-timeline { max-height: 50vh; }

  /* Quote line items: six columns cannot fit, so each row becomes its own card. */
  table.items, table.items thead, table.items tbody, table.items tr, table.items td { display: block; width: 100%; }
  table.items thead { display: none; }
  table.items tr { border: 1px solid var(--border-subtle); border-radius: var(--radius); padding: 10px; margin-bottom: 10px; background: var(--panel-bg); }
  table.items td { border: none; padding: 4px 0; }
  table.items td[data-label]::before { content: attr(data-label); display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--text-secondary); margin-bottom: 3px; }
  table.items td[data-label='']::before { display: none; }
  table.items td.r { text-align: left; }
  table.items input { width: 100%; }
}

/* Very small phones: drop the wordmark to a monogram so nothing is ever clipped. */
@media (max-width: 380px) {
  /* All three selectors, because `body.nav-compact .brand` (13px) and `.login .brand`
     (18px) are both MORE specific than a bare `.brand`, and a media query adds no
     specificity of its own. Without them the wordmark never collapsed and the page read
     "the monogram plus the full wordmark stacked together" on every phone under 380px - the monogram sitting in front of the
     full name it was supposed to replace. */
  .brand, body.nav-compact .brand, .login .brand { font-size: 0; letter-spacing: 0; }
  .brand::before { content: 'UD'; font-size: 15px; letter-spacing: 2px; }
  .login .brand::before { font-size: 18px; }
}


/* ---------- Dashboard detail: timeline (left) | folders (right), independent heights ---------- */
.detail-split { display: grid; grid-template-columns: minmax(260px, 340px) 1fr; gap: 16px; align-items: start; margin-top: 0; }
.detail-timeline { background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-top: none; border-radius: 0 0 var(--radius) var(--radius); max-height: 560px; overflow-y: auto; }
.detail-timeline .timeline-container { border: none; }
.detail-files { min-width: 0; }
.detail-files .breadcrumb { border-radius: var(--radius) var(--radius) 0 0; }
@media (max-width: 1000px) { .detail-split { grid-template-columns: 1fr; } .detail-timeline { max-height: 300px; border-radius: var(--radius); border-top: 1px solid var(--border-subtle); } }

/* ---------- Upload progress toast ---------- */
.toast.progress { min-width: 280px; }
.toast.progress .bar { height: 5px; background: #222; border-radius: 3px; margin-top: 7px; overflow: hidden; }
.toast.progress .bar i { display: block; height: 100%; background: var(--accent-gold); width: 0; transition: width .15s linear; }
.toast.ok { border-color: var(--ok); } .toast.ok .bar i { background: var(--ok); }

/* ---------- Name card icons ---------- */
.nc-ico { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 50%; border: 1px solid var(--border-strong); color: var(--text-primary); }
.nc-ico:hover { border-color: var(--accent-gold); color: var(--accent-gold); background: var(--accent-gold-dim); }
.nc-ico.wa:hover { border-color: #25D366; color: #25D366; background: rgba(37,211,102,.12); }
.nc-counts { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.nc-counts .status { cursor: pointer; } .nc-counts .status:hover { filter: brightness(1.25); }

/* ---------- Forms / modals ---------- */
.modal.full { max-width: 1200px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* Three fields side by side on a desktop form, stacked on a phone where three date inputs
   in a row would each be too narrow to read. */
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
@media (max-width: 640px) { .grid3 { grid-template-columns: 1fr; } }
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.row.nowrap { flex-wrap: nowrap; }
.danger-outline { color: var(--danger); border-color: var(--danger); }
.danger-outline:hover { background: var(--danger); color: #fff; }
@media (max-width: 760px) { .grid2, .grid4 { grid-template-columns: 1fr; } }

/* ---------- Quote line items ---------- */
table.items { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
table.items th { font-size: 10px; }
table.items td { padding: 5px 4px; border-bottom: 1px solid var(--border-subtle); vertical-align: top; }
table.items input { padding: 6px 8px; font-size: 13px; }
table.items input.desc { margin-top: 4px; font-size: 12px; color: var(--text-secondary); }
table.items td.r, table.items th.r { text-align: right; }
.totalbar { display: flex; justify-content: space-between; align-items: center; padding: 10px 14px; background: #111; border: 1px solid var(--border-strong); border-radius: var(--radius); margin-top: 12px; }
.totalbar strong { font-size: 20px; }

/* ---------- Quote template editor ---------- */
.tpl-split { display: grid; grid-template-columns: minmax(320px, 420px) 1fr; gap: 18px; align-items: start; }
.tpl-form { max-height: 66vh; overflow-y: auto; padding-right: 8px; }
.tpl-prev { position: sticky; top: 0; }
#tpl-preview { background: #555; padding: 14px; border-radius: var(--radius); max-height: 62vh; overflow: auto; }
@media (max-width: 1000px) { .tpl-split { grid-template-columns: 1fr; } }

/* A4 preview sheet (mirrors what the PDF draws) */
.a4 { background: #fff; color: #333; width: 100%; max-width: 720px; margin: 0 auto; padding: 30px 34px; box-sizing: border-box; font-family: Helvetica, Arial, sans-serif; font-size: 12px; box-shadow: 0 4px 15px rgba(0,0,0,.3); }
.a4 .r { text-align: right; }
.a4-head { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 2px solid var(--ink, #050505); padding-bottom: 14px; }
.a4-logo { font-size: 22px; font-weight: 900; letter-spacing: -.5px; text-transform: uppercase; color: var(--ink, #050505); border-bottom: 3px solid var(--acc, #FFD700); display: inline-block; padding-bottom: 2px; }
.a4-tag { font-size: 10px; letter-spacing: 2px; color: #777; margin-top: 4px; }
.a4-co { font-size: 11px; color: #555; text-align: right; line-height: 1.5; }
.a4-meta { display: flex; justify-content: space-between; margin: 22px 0; }
.a4-lbl { font-size: 10px; font-weight: bold; color: #888; text-transform: uppercase; margin-bottom: 4px; }
.a4-title { font-size: 26px; font-weight: bold; color: var(--ink, #050505); text-transform: uppercase; }
.a4-tbl { width: 100%; border-collapse: collapse; margin-bottom: 14px; }
.a4-tbl th { background: var(--ink, #050505); color: #fff; font-size: 10px; text-transform: uppercase; padding: 8px 10px; text-align: left; }
.a4-tbl td { padding: 8px 10px; border-bottom: 1px solid #ddd; font-size: 11px; }
.a4-tbl .d { font-size: 10px; color: #666; }
.a4-tot { width: 55%; margin-left: auto; }
.a4-tot div { display: flex; justify-content: space-between; padding: 6px 10px; border-bottom: 1px solid #eee; font-size: 11px; }
.a4-tot div.g { font-size: 14px; font-weight: bold; background: #f9f9f9; border-bottom: 2px solid var(--ink, #050505); }
.a4-block { margin-top: 18px; font-size: 10.5px; color: #666; line-height: 1.5; }
.a4-disc { margin-top: 14px; padding: 10px 12px; background: #fffae1; border: 1px solid var(--acc, #FFD700); font-size: 10.5px; color: #5a4a00; }
.a4-foot { margin-top: 26px; text-align: center; font-size: 10.5px; color: #999; border-top: 1px solid #eee; padding-top: 12px; }


/* ---------- In-app file viewer ---------- */
.filemodal { height: 72vh; background: #0a0a0a; border: 1px solid var(--border-strong); border-radius: var(--radius); overflow: hidden; display: flex; align-items: center; justify-content: center; }
.filemodal iframe { width: 100%; height: 100%; border: 0; }
.filemodal img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* ---------- Inline person name -> opens the name card ---------- */
.user-link { cursor: pointer; border-bottom: 1px dotted var(--text-secondary); }
.user-link:hover { color: var(--accent-gold); border-bottom-color: var(--accent-gold); }

/* ---------- Client progress bar (replaces the Kanban for clients) ---------- */
.progress-track { display: flex; align-items: flex-start; gap: 0; margin: 10px 0 26px; padding: 26px 10px 10px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: var(--radius); overflow-x: auto; }
.pstep { flex: 1 1 0; min-width: 110px; text-align: center; position: relative; }
.pstep .bar { height: 6px; background: #1e1e1e; border-radius: 3px; margin: 0 2px 14px; overflow: hidden; }
.pstep .bar i { display: block; height: 100%; width: 0; background: var(--accent-gold); border-radius: 3px; transition: width .9s cubic-bezier(.4,0,.2,1); }
.pstep.done .bar i { width: 100%; }
.pstep.current .bar i { width: 60%; animation: pulse 1.8s ease-in-out infinite; }
.pstep .dot { position: absolute; top: -19px; left: 50%; transform: translateX(-50%); width: 13px; height: 13px; border-radius: 50%; background: var(--bg-obsidian); border: 2px solid #333; }
.pstep.done .dot { border-color: var(--accent-gold); background: var(--accent-gold); }
.pstep.current .dot { border-color: var(--accent-gold); box-shadow: 0 0 0 5px var(--accent-gold-dim); animation: ring 1.8s ease-in-out infinite; }
.pstep .lbl { font-size: 12px; color: var(--text-secondary); padding: 0 4px; }
.pstep.done .lbl { color: var(--text-primary); }
.pstep.current .lbl { color: var(--accent-gold); font-weight: 700; }
.pstep .sub { font-size: 10px; color: var(--text-secondary); margin-top: 3px; text-transform: uppercase; letter-spacing: .5px; }
.pstep.stuck .bar i { background: var(--danger); } .pstep.stuck .dot { border-color: var(--danger); } .pstep.stuck .lbl { color: var(--danger); }
@keyframes pulse { 0%,100% { width: 45%; } 50% { width: 75%; } }
@keyframes ring { 0%,100% { box-shadow: 0 0 0 4px var(--accent-gold-dim); } 50% { box-shadow: 0 0 0 9px rgba(255,215,0,.05); } }

/* ---------- Audit: readable vs developer view ---------- */
.log-line { line-height: 1.5; }
.log-line b { color: var(--text-primary); font-weight: 600; }
.log-who { color: var(--accent-gold); }
tr.dev td { font-family: ui-monospace, Menlo, monospace; font-size: 12px; }


/* ---------- Version chip + release notes ----------
   Quiet by default and loud only when there is genuinely something new: the version sits in
   the header as plain small text, and the gold dot is the only thing that draws the eye. */
.app-version {
  margin-left: 8px; padding: 2px 8px; position: relative;
  background: transparent; color: var(--text-secondary);
  border: 1px solid var(--border-strong); border-radius: 999px;
  font: inherit; font-size: 11px; letter-spacing: 0.5px; cursor: pointer;
}
.app-version:hover { color: var(--accent-gold); border-color: var(--accent-gold); }
.ver-dot {
  display: inline-block; width: 6px; height: 6px; margin-left: 5px; vertical-align: middle;
  background: var(--accent-gold); border-radius: 50%;
}
.release { padding: 12px 0; border-bottom: 1px solid var(--border-strong); }
.release:last-child { border-bottom: 0; }
.release-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.release-notes { margin: 0; padding-left: 18px; }
.release-notes li { margin: 4px 0; line-height: 1.5; }
@media (max-width: 900px) {
  /* The header is tight on a phone; the number still fits, the hover state does not matter. */
  .app-version { margin-left: 6px; padding: 2px 6px; }
}


/* ---------- Tab groups ----------
   Wide screen: the label is hidden and a thin rule separates one group from the next, so the
   strip still reads as a single row. Phone: the sheet is vertical and has room, so the label
   is shown and does the work the divider does on desktop. */
.tab-group-label {
  align-self: center; margin: 0 2px 0 8px; padding-left: 10px;
  border-left: 1px solid var(--border-strong);
  height: 20px; width: 0; overflow: hidden; text-indent: -9999px;
}
.tab-group-label[data-first] { border-left: 0; margin-left: 0; padding-left: 0; }
@media (max-width: 900px) {
  .tab-group-label {
    display: block; width: auto; height: auto; text-indent: 0; overflow: visible;
    margin: 10px 0 2px; padding: 0 11px; border-left: 0;
    /* align-self:center from the desktop rule would centre these in the vertical sheet;
       the padding matches .tab so a label sits directly above the items it names. */
    align-self: stretch; text-align: left;
    font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase;
    color: var(--text-secondary);
  }
  .tab-group-label[data-first] { margin-top: 0; }
}

/* The "you already have this client" panel in the lead conversion dialog. Gold-edged
   because it changes what the button is about to do, and that must not be skim-read. */
.matchbox {
  padding: 10px 12px; margin-bottom: 14px;
  background: var(--accent-gold-dim); border: 1px solid rgba(255,215,0,.35);
  border-radius: var(--radius);
}

/* ---------- Money ----------
   .cost-cell is the admin-only cost column in the quote editor. Gold-tinted so it never
   reads as part of the client-facing quote. It is never rendered for anyone else. */
.cost-cell { background: var(--accent-gold-dim); }
.cost-cell input { border-color: rgba(255,215,0,.35); }
.fin-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 18px; }
.fin-card { background: var(--surface-charcoal); border: 1px solid var(--border-subtle); border-radius: var(--radius); padding: 14px 16px; }
.fin-card .k { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-secondary); }
.fin-card .v { font-size: 22px; font-weight: 700; margin-top: 4px; }
.fin-card.gold .v { color: var(--accent-gold); }
.fin-card.green .v { color: var(--ok); }
.fin-card.red .v { color: var(--danger); }
.fin-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.fin-table th, .fin-table td { padding: 8px 10px; border-bottom: 1px solid var(--border-subtle); text-align: right; white-space: nowrap; }
.fin-table th:first-child, .fin-table td:first-child { text-align: left; }
.fin-table th { color: var(--text-secondary); font-weight: 500; font-size: 11px; letter-spacing: .5px; text-transform: uppercase; }
.fin-table th.l, .fin-table td.l { text-align: left; }
.fin-table tr.total td { font-weight: 700; border-top: 2px solid var(--border-strong); }
.fin-wrap { overflow-x: auto; }
/* Client balance pane on the project view */
.balance { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 12px 0; }
.balance div { background: #111; border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 10px 12px; }
.balance .k { font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: .5px; }
.balance .v { font-size: 18px; font-weight: 700; margin-top: 2px; }
.balance .remaining .v { color: var(--accent-gold); }
.balance .paid .v { color: var(--ok); }
@media (max-width: 900px) { .balance { grid-template-columns: 1fr; } }

/* ---------- Assistant ----------
   A conversation is easier to follow when the two voices differ in more than colour, so the
   client's own words sit right and tight, and the answer sits left and wide enough to read. */
.chatlog { height: min(58vh, 520px); overflow-y: auto; padding: 12px; background: var(--surface-charcoal);
  border: 1px solid var(--border-subtle); border-radius: var(--radius); display: flex; flex-direction: column; gap: 10px; }
.msg { display: flex; }
.msg.user { justify-content: flex-end; }
.msg .bubble { max-width: 78%; padding: 9px 12px; border-radius: 12px; font-size: 13.5px; line-height: 1.55; white-space: normal; }
.msg.user .bubble { background: var(--accent-gold-dim); border: 1px solid rgba(255,215,0,.35); color: var(--text-primary); border-bottom-right-radius: 4px; }
.msg.assistant .bubble { background: var(--surface-raised, #1a1a1a); border: 1px solid var(--border-subtle); color: var(--text-primary); border-bottom-left-radius: 4px; }
.chatbar { display: flex; gap: 8px; margin-top: 10px; }
.chatbar input { flex: 1; min-width: 0; }
@media (max-width: 760px) { .chatlog { height: 52vh; } .msg .bubble { max-width: 88%; } }

/* ---------- Photos ----------
   A grid that stays square whatever the photo's shape, because a ragged grid is harder to
   scan than a tidy one, and the viewer is the part people judge: black, edge to edge, with
   the picture centred and everything else out of the way. */
.photogrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.photo { margin: 0; cursor: pointer; background: var(--surface-charcoal); border: 1px solid var(--border-subtle);
  border-radius: var(--radius); overflow: hidden; transition: border-color .15s ease, transform .15s ease; }
.photo:hover { border-color: var(--accent-gold); transform: translateY(-2px); }
.photo img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; background: #0d0d0d; }
.photo figcaption { padding: 6px 8px; font-size: 11px; color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ai-dot { color: var(--accent-gold); margin-left: 4px; }
/* A file that claims to be an image but will not decode: say so quietly rather than showing
   a browser's broken-image icon. */
.photo.unreadable img { background: repeating-linear-gradient(45deg, #141414, #141414 8px, #191919 8px, #191919 16px); }
.photo.unreadable::after { content: 'cannot preview'; display: block; margin-top: -34px; margin-bottom: 16px;
  text-align: center; font-size: 10px; color: var(--text-secondary); position: relative; }

.pv-wrap { position: relative; display: flex; align-items: center; justify-content: center;
  background: #000; border-radius: var(--radius); height: min(70vh, 640px); overflow: hidden; }
.pv-wrap img { max-width: 100%; max-height: 100%; object-fit: contain; transition: transform .12s ease; }
.pv-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 44px; height: 64px;
  background: rgba(0,0,0,.55); color: #fff; border: 1px solid var(--border-subtle); border-radius: var(--radius);
  font-size: 30px; line-height: 1; cursor: pointer; }
.pv-nav:hover { background: rgba(0,0,0,.8); border-color: var(--accent-gold); }
.pv-nav.prev { left: 8px; } .pv-nav.next { right: 8px; }
.pv-meta { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; flex-wrap: wrap; margin-top: 12px; }
.pv-ai { margin-top: 6px; padding: 8px 10px; background: var(--surface-charcoal); border: 1px solid var(--border-subtle);
  border-radius: var(--radius); font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.6; }
@media (max-width: 760px) {
  .photogrid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 6px; }
  .pv-wrap { height: 58vh; }
  .pv-nav { width: 38px; height: 52px; font-size: 24px; }
}

/* ---------- Contact icons, shown in the open ----------
   Small enough to sit inside a card or beside a name without changing the layout, big enough
   to hit with a thumb. A person with no phone keeps the buttons, dimmed, so rows stay even. */
.contact-row { display: inline-flex; gap: 6px; margin-top: 8px; }
.contact-row.contact-sm .nc-ico { width: 26px; height: 26px; }
.contact-row.contact-sm .nc-ico svg { width: 13px; height: 13px; }
.nc-ico.off { opacity: .3; cursor: not-allowed; }
.nc-ico.off:hover { border-color: var(--border-strong); color: var(--text-primary); background: none; }
.uh-person { display: inline-flex; align-items: center; gap: 6px; margin: 0 8px 4px 0; }
.uh-person .contact-row { margin-top: 0; }

/* A QR needs a white quiet zone around it or phone cameras struggle on a dark page. */
.qrbox { display: flex; justify-content: center; padding: 16px; background: #fff; border-radius: var(--radius); }
.qrbox img, .qrbox canvas { display: block; }

/* The QR drawn straight onto a client's app card. White plate on purpose: a camera reads a
   dark-on-light code far more reliably than the inverse, and every phone camera expects it
   that way round. Sized so two cards still sit side by side on a laptop. */
.qr-inline { display: flex; justify-content: center; margin: 12px auto 8px; padding: 10px;
  background: #fff; border-radius: var(--radius); width: max-content; }
.qr-inline img, .qr-inline canvas { display: block; }

/* Descriptions differ in length, so without this the codes sit at six different heights and
   the row reads as a mess rather than a set. The text block absorbs the difference; the QR
   and the button below it line up across every card. */
/* Every card the same shape, whatever the text says.
   Descriptions here run from one line to four, and left alone that puts the codes and the
   buttons at four different heights - the row stops reading as a set and starts reading as a
   mistake. So each text block is given a fixed number of lines and clipped: the title one,
   the model list two, the description three. Below that everything is identical by
   construction, not by luck, at any window width. */
.app-card { display: flex; flex-direction: column; }
.app-card .app-card-top { flex: 0 0 auto; }
.app-card .title,
.app-card .app-meta,
.app-card .app-desc { overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; }
.app-card .title    { -webkit-line-clamp: 1; }
.app-card .app-meta { -webkit-line-clamp: 2; min-height: 2.6em; }
.app-card .app-desc { -webkit-line-clamp: 3; min-height: 4.1em; }
.app-card .qr-inline { margin-top: auto; }
.app-card .actions { margin-top: 10px; justify-content: center; }

/* The app catalog list. align-items:flex-start so the tick sits by the app name and not
   halfway down a two-line entry; the dimmed rows are the ones already in the library. */
.catalog-row { align-items: flex-start; gap: 10px; padding: 9px 2px; border-bottom: 1px solid var(--border-subtle); }
.catalog-row:last-child { border-bottom: 0; }
.catalog-row input { margin-top: 3px; flex: none; }
.catalog-row.is-have { opacity: .55; }
.catalog-row .status { margin-left: 6px; }

/* A picture inside a Project info block: shown small enough to scan the page, opened full
   size in a new tab. max-height stops a tall rack photo owning the whole screen. */
.infoblock .infoimg { display: block; margin-top: 8px; }
.infoblock .infoimg img { max-width: 100%; max-height: 260px; border-radius: var(--radius); border: 1px solid var(--border-subtle); }

/* ---------- No connection ----------
   Fixed to the BOTTOM, not the top. The top already carries the tab bar and the notch, and
   a bar that pushes those down moves everything the person was about to tap. The bottom is
   empty, the bar sits above the home indicator, and nothing on the page shifts. */
.offline-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: #2a1a00; color: #ffcf4d;
  border-top: 1px solid #5a3d00;
  font-size: 13px; text-align: center;
}
.offline-dot {
  width: 8px; height: 8px; border-radius: 50%; background: #ffcf4d; flex: none;
  animation: offline-pulse 1.6s ease-in-out infinite;
}
@keyframes offline-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .offline-dot { animation: none; } }

/* ---------- Get the app ----------
   The floating button that offers the Android app. Sits above the offline bar's z-index
   band but below modals, and lifts out of the way when the offline bar is showing so the
   two never overlap on a phone. */
.getapp {
  position: fixed; z-index: 850;
  right: calc(16px + env(safe-area-inset-right));
  bottom: calc(16px + env(safe-area-inset-bottom));
  display: inline-flex; align-items: center; gap: 9px;
  padding: 11px 16px;
  background: var(--accent-gold); color: #050505;
  border: 0; border-radius: 999px;
  font: inherit; font-weight: 600; font-size: 13px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, .55);
  cursor: pointer;
  animation: getapp-in .35s ease both;
}
.getapp:active { transform: scale(.97); }
.getapp svg { width: 17px; height: 17px; flex: none; }
.is-offline .getapp { bottom: calc(66px + env(safe-area-inset-bottom)); }
@keyframes getapp-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .getapp { animation: none; } }

/* The small x that dismisses it. Inside the pill, so the button stays one target on a
   phone and there is no second floating thing on the screen. */
.getapp .getapp-x {
  margin: -4px -6px -4px 2px; padding: 4px 6px;
  border: 0; background: none; color: rgba(5, 5, 5, .55);
  font: inherit; font-size: 15px; line-height: 1; cursor: pointer;
}
.getapp .getapp-x:hover { color: #050505; }

/* ---------- The assistant, floating ----------
   Bottom LEFT, because "Get the app" already owns bottom right and two bubbles on the same
   corner of a phone is a guessing game. Below the offline bar's band so it never covers a
   message about the connection. */
.chatfab {
  position: fixed; z-index: 840;
  left: calc(16px + env(safe-area-inset-left));
  bottom: calc(16px + env(safe-area-inset-bottom));
  width: 52px; height: 52px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel-bg); color: var(--accent-gold);
  border: 1px solid var(--border-strong);
  box-shadow: 0 6px 22px rgba(0, 0, 0, .55);
  cursor: pointer;
}
.chatfab:hover { border-color: var(--accent-gold); }
.chatfab svg { width: 23px; height: 23px; }
.is-offline .chatfab { bottom: calc(66px + env(safe-area-inset-bottom)); }

.chatpanel {
  position: fixed; z-index: 845;
  left: calc(16px + env(safe-area-inset-left));
  bottom: calc(16px + env(safe-area-inset-bottom));
  width: min(420px, calc(100vw - 32px));
  height: min(560px, calc(100vh - 100px));
  display: flex; flex-direction: column;
  background: var(--surface-charcoal);
  border: 1px solid var(--border-strong); border-radius: 12px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .7);
  overflow: hidden;
}
/* On a phone it is not a panel floating over the page, it is the page - a 420px card on a
   390px screen is just a worse full screen. */
@media (max-width: 520px) {
  .chatpanel { left: 0; right: 0; bottom: 0; top: 0; width: 100%; height: 100%; border: 0; border-radius: 0; }
}
.chatpanel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px; border-bottom: 1px solid var(--border-subtle); flex: none;
}
.chatpanel-x {
  border: 0; background: none; color: var(--text-secondary);
  font-size: 22px; line-height: 1; cursor: pointer; padding: 0 4px;
}
.chatpanel-x:hover { color: var(--text-primary); }
.chatpanel-body { flex: 1; overflow-y: auto; padding: 14px; }
/* The chat tab was written to fill a page, so its own heading and width rules are dropped
   here - the panel already says "Assistant" in its bar. */
.chatpanel-body .tab-head h2 { display: none; }
.chatpanel-body .tab-head { margin-bottom: 8px; }
.chatpanel-body .chatlog { max-height: none; }

/* ---------- A client's project, open on the page ----------
   Each project is a full card rather than a summary that has to be clicked. The row of
   actions wraps on a phone instead of scrolling sideways. */
.client-project .client-actions { flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.client-project .detail-split { border-top: 1px solid var(--border-subtle); padding-top: 12px; }
@media (max-width: 760px) {
  .client-project .client-actions .btn { flex: 1 1 100%; }
}

/* ---------- Apps for this project ----------
   A horizontal strip that scrolls inside itself rather than widening the card. On a phone
   the chips become full-width rows, because a sideways scroll people cannot see is the same
   as hiding the third app. */
.apps-pane { border-top: 1px solid var(--border-subtle); margin-top: 14px; padding-top: 12px; }
.apps-pane h3 { font-size: 13px; margin: 0 0 10px; }
.apps-strip { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.app-chip {
  flex: 0 0 auto; min-width: 170px; max-width: 240px;
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  padding: 10px 12px;
  background: var(--panel-bg); border: 1px solid var(--border-subtle); border-radius: var(--radius);
}
.app-chip-name { font-weight: 600; }
@media (max-width: 620px) {
  .apps-strip { flex-direction: column; overflow-x: visible; }
  .app-chip { min-width: 0; max-width: none; width: 100%; }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════
   ARCHITECTURAL DRAFTING LAYER
   ═══════════════════════════════════════════════════════════════════════════════════════

   One 45-degree corner cut, on the same corner of every frame, repeated until it reads as
   a system rather than a decoration. Angles carry the FRAMES; information stays on straight
   lines, so tables, inputs and body text are deliberately left square.

   Everything below derives from --cut. Change that one number and the whole app restyles.

   ── Four decisions taken here, and why ────────────────────────────────────────────────

   1. THE 1px EDGE IS DRAWN WITH drop-shadow, NOT A WRAPPER.
      clip-path erases borders, and the usual fix is to wrap every framed element in a
      gradient-padded parent. Counted against this codebase that meant 76 wrappers across
      18 files - 22 cards, 16 list items, 14 empty states, 11 filter bars - each one a
      chance to break a layout that works today. filter: drop-shadow() traces the CLIPPED
      outline, which border and outset box-shadow do not, so four stacked 1px shadows give
      the same hairline on the diagonal with zero markup. .k-edge is kept below for anyone
      who wants the gradient version on a specific element.

   2. FOCUS RINGS USE INSET box-shadow.
      clip-path clips outline AND outset box-shadow. The brief's other suggestion - put the
      ring on an inner element - has nowhere to go inside a <button>, and there are 63 of
      them. An inset ring is painted inside the box, so the clip cannot eat it.

   3. THE NOTIFICATION BELL IS NOT CUT.
      Its unread badge is positioned top:-4px right:-4px, i.e. OUTSIDE the box, on exactly
      the corner the cut removes. Cutting the bell deletes the badge. The badge itself
      carries the language instead, so the corner still reads as part of the system.

   4. SMALL THINGS USE --cut-sm, NOT --cut.
      A badge is 22px tall. A 34px diagonal on a 22px box is not a corner, it is a slice
      through the whole element, and the text goes with it. Chips, badges and pills are
      wired to --cut-sm so --cut can be pushed to 34px without breaking them.

   ── RTL ───────────────────────────────────────────────────────────────────────────────
   The mirrored rules are written and correct, but nothing in this portal sets dir="rtl"
   today - there is no Arabic mode to switch into. They are here so that the day Arabic is
   added, the geometry flips on its own instead of having to be found and redone.
   ═══════════════════════════════════════════════════════════════════════════════════════ */

:root {
  --cut: 20px;          /* the whole system. 6px = subtle, 34px = loud. */
  --cut-sm: 8px;        /* chips, badges, pills - anything under ~34px tall */
  --cut-lg: 34px;       /* the largest frames, where a bigger bite reads better */
  --edge: #2e2e2e;      /* the hairline that replaces `border` on a cut element */
  --edge-lit: var(--accent-gold);
}

/* ---------- the cut itself ---------- */
.k      { clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%); }
[dir=rtl] .k { clip-path: polygon(var(--cut) 0, 100% 0, 100% 100%, 0 100%, 0 var(--cut)); }

/* Double cut, for avatars and thumbnails: reads as a plate rather than a frame. */
.k-plate { clip-path: polygon(0 0, calc(100% - 11px) 0, 100% 11px, 100% 100%, 11px 100%, 0 calc(100% - 11px)); }

/* The gradient-edge version, for anywhere a lit border is wanted. Needs `> .in`. */
.k-edge { background: linear-gradient(135deg, #3a3a3a, #1c1c1c 60%); padding: 1px; }
.k-edge, .k-edge > .in { clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%); }
[dir=rtl] .k-edge, [dir=rtl] .k-edge > .in { clip-path: polygon(var(--cut) 0, 100% 0, 100% 100%, 0 100%, 0 var(--cut)); }
.k-edge > .in { background: var(--panel-bg); }

/* The hairline. Four 1px shadows follow the clipped shape, including the diagonal. */
.k-line { filter: drop-shadow(1px 0 0 var(--edge)) drop-shadow(-1px 0 0 var(--edge))
                  drop-shadow(0 1px 0 var(--edge)) drop-shadow(0 -1px 0 var(--edge)); }
.k-line-gold { filter: drop-shadow(1px 0 0 var(--edge-lit)) drop-shadow(-1px 0 0 var(--edge-lit))
                       drop-shadow(0 1px 0 var(--edge-lit)) drop-shadow(0 -1px 0 var(--edge-lit)); }

/* ---------- frames: cut, and given their edge back ---------- */
.card, .kcard, .kcol, .list-item, .modal, .filterbar, .empty, .login, .namecard, .toast,
.universal-header, .timeline-container, .split-pane, .msg, .infoblock, .demo-otp,
.pending-large-files, .progress-track, .table-wrap, .fin-card, .balance,
.a4-disc, .dropzone {
  border-radius: 0;
  clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%);
  filter: drop-shadow(1px 0 0 var(--edge)) drop-shadow(-1px 0 0 var(--edge))
          drop-shadow(0 1px 0 var(--edge)) drop-shadow(0 -1px 0 var(--edge));
  border-color: transparent;      /* the drop-shadow is the edge now */
}
[dir=rtl] .card, [dir=rtl] .kcard, [dir=rtl] .kcol, [dir=rtl] .list-item, [dir=rtl] .modal,
[dir=rtl] .filterbar, [dir=rtl] .empty, [dir=rtl] .login, [dir=rtl] .namecard, [dir=rtl] .toast,
[dir=rtl] .universal-header, [dir=rtl] .timeline-container, [dir=rtl] .split-pane, [dir=rtl] .msg,
[dir=rtl] .infoblock, [dir=rtl] .demo-otp, [dir=rtl] .pending-large-files, [dir=rtl] .progress-track,
[dir=rtl] .table-wrap, [dir=rtl] .fin-card, [dir=rtl] .balance,
[dir=rtl] .a4-disc, [dir=rtl] .dropzone {
  clip-path: polygon(var(--cut) 0, 100% 0, 100% 100%, 0 100%, 0 var(--cut));
}

/* Hover and selection light the edge instead of changing a border colour. */
.card:hover, .list-item:hover, .kcard:hover {
  filter: drop-shadow(1px 0 0 var(--edge-lit)) drop-shadow(-1px 0 0 var(--edge-lit))
          drop-shadow(0 1px 0 var(--edge-lit)) drop-shadow(0 -1px 0 var(--edge-lit));
}
.list-item.selected, .namecard, .toast, .pending-large-files {
  filter: drop-shadow(1px 0 0 var(--edge-lit)) drop-shadow(-1px 0 0 var(--edge-lit))
          drop-shadow(0 1px 0 var(--edge-lit)) drop-shadow(0 -1px 0 var(--edge-lit));
}
.toast.err { filter: drop-shadow(1px 0 0 var(--danger)) drop-shadow(-1px 0 0 var(--danger))
                     drop-shadow(0 1px 0 var(--danger)) drop-shadow(0 -1px 0 var(--danger)); }

/* A QR CODE IS NEVER CUT.
   It is not a frame, it is data. The top-right corner holds a finder pattern and the quiet
   zone around it is part of the specification - take a 20px bite out of that and some phone
   cameras stop reading it. The card around it carries the language instead. Same reasoning
   as tables and inputs: angles on the frames, never on the information. */
.qr-inline { filter: none; clip-path: none; border-radius: 0; }

/* ---------- small things: the same cut, scaled so it cannot eat the label ---------- */
.status, .pill, .rolebadge, .co-pilot-pill, .bell .dot, .tab.active, .vchip, .app-version {
  border-radius: 0;
  clip-path: polygon(0 0, calc(100% - var(--cut-sm)) 0, 100% var(--cut-sm), 100% 100%, 0 100%);
}
[dir=rtl] .status, [dir=rtl] .pill, [dir=rtl] .rolebadge, [dir=rtl] .co-pilot-pill,
[dir=rtl] .bell .dot, [dir=rtl] .tab.active, [dir=rtl] .vchip, [dir=rtl] .app-version {
  clip-path: polygon(var(--cut-sm) 0, 100% 0, 100% 100%, 0 100%, 0 var(--cut-sm));
}
.bell { border-radius: 0; }            /* squared, but NOT cut - see decision 3 above */

/* A clipped 1px border does not vanish cleanly: it leaves a fragment at the point of the
   nose and a hairline stub where the diagonal meets the top edge - the stray dots and
   ticks visible on every outline button and status chip. The border has to go entirely,
   and the edge comes back as a drop-shadow that follows the clipped shape. */
.status, .pill, .rolebadge, .co-pilot-pill, .vchip,
.btn-outline, .btn-danger { border-color: transparent !important; }

/* AND the element must be OPAQUE before any of that works.
   drop-shadow follows the alpha of what it is given, not the box. On a transparent chip the
   only opaque pixels are the letters, so the "edge" comes out as a coloured halo around the
   text - which is exactly what it did the first time. A near-black fill, one step off the
   card behind it, makes the box the shape being outlined. */
.status, .pill, .rolebadge, .co-pilot-pill, .vchip, .btn-outline, .btn-danger {
  background: #0d0d0d;
}
.pill.active { background: var(--accent-gold); }
.btn-outline:hover { background: #131313; }
.btn-danger:hover { background: var(--danger); }

.status  { filter: drop-shadow(1px 0 0 var(--edge)) drop-shadow(-1px 0 0 var(--edge))
                   drop-shadow(0 1px 0 var(--edge)) drop-shadow(0 -1px 0 var(--edge)); }
.status.gold  { filter: drop-shadow(1px 0 0 var(--accent-gold)) drop-shadow(-1px 0 0 var(--accent-gold))
                        drop-shadow(0 1px 0 var(--accent-gold)) drop-shadow(0 -1px 0 var(--accent-gold)); }
.status.red   { filter: drop-shadow(1px 0 0 var(--danger)) drop-shadow(-1px 0 0 var(--danger))
                        drop-shadow(0 1px 0 var(--danger)) drop-shadow(0 -1px 0 var(--danger)); }
.status.green { filter: drop-shadow(1px 0 0 var(--ok)) drop-shadow(-1px 0 0 var(--ok))
                        drop-shadow(0 1px 0 var(--ok)) drop-shadow(0 -1px 0 var(--ok)); }
.status.blue  { filter: drop-shadow(1px 0 0 #7dd3fc) drop-shadow(-1px 0 0 #7dd3fc)
                        drop-shadow(0 1px 0 #7dd3fc) drop-shadow(0 -1px 0 #7dd3fc); }
.pill, .rolebadge, .co-pilot-pill, .vchip,
.btn-outline { filter: drop-shadow(1px 0 0 var(--border-strong)) drop-shadow(-1px 0 0 var(--border-strong))
                       drop-shadow(0 1px 0 var(--border-strong)) drop-shadow(0 -1px 0 var(--border-strong)); }
.rolebadge.admin  { filter: drop-shadow(1px 0 0 var(--accent-gold)) drop-shadow(-1px 0 0 var(--accent-gold))
                            drop-shadow(0 1px 0 var(--accent-gold)) drop-shadow(0 -1px 0 var(--accent-gold)); }
.rolebadge.editor { filter: drop-shadow(1px 0 0 #7dd3fc) drop-shadow(-1px 0 0 #7dd3fc)
                            drop-shadow(0 1px 0 #7dd3fc) drop-shadow(0 -1px 0 #7dd3fc); }
.rolebadge.client { filter: drop-shadow(1px 0 0 #c4b5fd) drop-shadow(-1px 0 0 #c4b5fd)
                            drop-shadow(0 1px 0 #c4b5fd) drop-shadow(0 -1px 0 #c4b5fd); }
.btn-danger  { filter: drop-shadow(1px 0 0 var(--danger)) drop-shadow(-1px 0 0 var(--danger))
                       drop-shadow(0 1px 0 var(--danger)) drop-shadow(0 -1px 0 var(--danger)); }
.pill:hover, .btn-outline:hover, .pill.active,
.status:hover { filter: drop-shadow(1px 0 0 var(--accent-gold)) drop-shadow(-1px 0 0 var(--accent-gold))
                        drop-shadow(0 1px 0 var(--accent-gold)) drop-shadow(0 -1px 0 var(--accent-gold)); }
.pill.active, .btn { border-color: transparent; }
.btn-danger:hover { filter: none; }    /* fills solid red - an edge on top of it is noise */
.avatar { border-radius: 0; clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 9px, 100% 100%, 9px 100%, 0 calc(100% - 9px)); }

/* ---------- buttons: an angled nose, not a corner bite ---------- */
.btn {
  border-radius: 0;
  clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 50%, calc(100% - 9px) 100%, 0 100%);
  padding-right: 22px;
}
[dir=rtl] .btn {
  clip-path: polygon(9px 0, 100% 0, 100% 100%, 9px 100%, 0 50%);
  padding-left: 22px; padding-right: 14px;
}
.btn-sm { padding: 3px 9px; padding-right: 17px; }
[dir=rtl] .btn-sm { padding-left: 17px; padding-right: 9px; }

/* ---------- focus: inset, because clip-path eats everything else ---------- */
.btn:focus-visible, .pill:focus-visible, .tab:focus-visible, .status:focus-visible,
.list-item:focus-visible, .card:focus-visible, a:focus-visible, [data-a]:focus-visible,
[data-action]:focus-visible, [data-tab]:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent-gold);
}
.btn:focus-visible { box-shadow: inset 0 0 0 2px #000; }   /* gold button, dark ring */
/* Inputs are never cut, so they keep a real outline. */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent-gold); outline-offset: 1px;
}

/* ---------- two voices ----------
   Mono uppercase for anything that LABELS something. The normal stack for anything that
   SAYS something. The eye learns the difference in about four seconds. */
.uh-label, .pstep .sub, .k, label, .breadcrumb, .app-meta, .lgn, .rolebadge, .vchip {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}
.uh-label, .pstep .sub, label, .rolebadge {
  text-transform: uppercase; letter-spacing: 1.4px; font-size: 10px; color: var(--text-secondary);
}
.fin-card .k, .balance .k, .billing-sum .k {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 1.6px; font-size: 9.5px; color: var(--text-secondary);
}

/* Section title: a faint sheet number, then a rule that fades out. */
h4.sheet { font: 700 10px/1 ui-monospace, Menlo, monospace; text-transform: uppercase;
  letter-spacing: 2px; color: var(--accent-gold); display: flex; align-items: center;
  gap: 10px; margin: 22px 0 10px; }
h4.sheet .num { color: #4a4a4a; }
h4.sheet i { flex: 1; height: 1px; background: linear-gradient(90deg, rgba(255,215,0,.35), transparent); }
[dir=rtl] h4.sheet i { background: linear-gradient(270deg, rgba(255,215,0,.35), transparent); }

/* ---------- stat tiles: cut on the BOTTOM-right, with a sheet number ----------
   The one deliberate exception to "always the same corner". A figure is an endpoint, not a
   frame around something else, so it is marked differently on purpose. */
.fin-card, .balance {
  position: relative;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%);
}
[dir=rtl] .fin-card, [dir=rtl] .balance {
  clip-path: polygon(0 0, 100% 0, 100% 100%, var(--cut) 100%, 0 calc(100% - var(--cut)));
}
/* The sheet number counts itself. A hand-written data-sheet="01" on every tile would be
   markup to add, keep in order, and get wrong the first time a tile is inserted in the
   middle; a counter cannot drift. */
.fin-cards, .billing-sum { counter-reset: sheet; }
.fin-card, .balance { counter-increment: sheet; }
.fin-card::after, .balance::after {
  content: "0" counter(sheet); position: absolute; top: 8px; right: 12px;
  font: 700 9.5px/1 ui-monospace, Menlo, monospace; letter-spacing: 1.5px; color: #3a3a3a;
}
[dir=rtl] .fin-card::after, [dir=rtl] .balance::after { right: auto; left: 12px; }

/* ---------- section titles become sheet headings, with no markup change ----------
   Every h3 inside the working area gets the drafting treatment: a faint number that counts
   itself, the title in mono, and a rule that fades out to the right. Done in CSS so no tab
   file has to be touched and nothing can fall out of step. */

#view { counter-reset: sheetno; }
#view h3 {
  counter-increment: sheetno;
  display: flex; align-items: center; gap: 10px;
  font: 700 10px/1 ui-monospace, "SF Mono", Menlo, monospace;
  text-transform: uppercase; letter-spacing: 2px; color: var(--accent-gold);
}
#view h3::before { content: "0" counter(sheetno); color: #4a4a4a; font-weight: 700; }
#view h3::after { content: ""; flex: 1; height: 1px;
  background: linear-gradient(90deg, rgba(255,215,0,.35), transparent); }
[dir=rtl] #view h3::after { background: linear-gradient(270deg, rgba(255,215,0,.35), transparent); }
/* Titles that are a NAME rather than a section - a card's heading, a modal's - keep the
   normal voice. A person's name set in 10px mono caps reads as a part number. */
#view .card h3, #view .modal h3, #view h3.title, #view .namecard h3 {
  counter-increment: none; display: block; font: 600 15px/1.3 inherit;
  text-transform: none; letter-spacing: 0; color: var(--text-primary);
}
#view .card h3::before, #view .modal h3::before, #view h3.title::before, #view .namecard h3::before,
#view .card h3::after, #view .modal h3::after, #view h3.title::after, #view .namecard h3::after { content: none; }

/* ---------- progress steps: diamonds on a connector line ---------- */
.pstep .dot {
  border-radius: 0; width: 11px; height: 11px; display: block;
  transform: translateX(-50%) rotate(45deg); background: var(--bg-obsidian);
  border: 1px solid #2e2e2e; top: -18px;
}
.pstep.done .dot, .pstep.current .dot { border-color: var(--accent-gold); background: var(--accent-gold); }
.pstep.current .dot { box-shadow: 0 0 0 4px var(--accent-gold-dim); animation: none; }
.pstep.stuck .dot { border-color: var(--danger); background: var(--danger); }
.pstep .bar { border-radius: 0; height: 1px; background: #2e2e2e; }
.pstep .bar i { border-radius: 0; }

/* ---------- the drafting grid ---------- */
main {
  background-image:
    linear-gradient(rgba(255,215,0,.028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,215,0,.028) 1px, transparent 1px);
  background-size: 30px 30px;
}

/* ---------- definition lists become drafting schedules ----------
   No card, no fill. A 1px rule under each row, the term in mono on one side and the value
   on the other. This is the "information travels on straight lines" half of the system. */
.schedule { display: block; }
.schedule > div { display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; padding: 9px 0; border-bottom: 1px solid #171717; }
.schedule > div:last-child { border-bottom: 0; }
.schedule .k { font-family: ui-monospace, Menlo, monospace; text-transform: uppercase;
  letter-spacing: 1.6px; font-size: 9.5px; color: var(--text-secondary); }
.schedule .v { color: var(--text-primary); text-align: right; }
[dir=rtl] .schedule .v { text-align: left; }

/* ---------- cover image: a shallow diagonal, with a gold line riding it ----------
   No screen in the portal has a cover image yet. This is the geometry ready for the day one
   is added, and it is inert until a .cov exists. */
.cov-wrap { position: relative; }
.cov { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 52px), 0 100%); overflow: hidden; }
.cov-line { position: absolute; left: 0; bottom: 0; width: 100%; height: 52px; pointer-events: none; }
.cov-photo { position: absolute; bottom: -26px; left: 24px; z-index: 3; background: var(--accent-gold); padding: 2px; }
[dir=rtl] .cov-photo { left: auto; right: 24px; }

/* ---------- small screens ----------
   A 20px bite out of a 320px card is proportionate. Out of a 150px one it is a wedge. */
@media (max-width: 560px) {
  :root { --cut: 14px; --cut-sm: 6px; }
  main { background-size: 24px 24px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ANIMATED LOGIN — digit glow → verifying ring → access granted
   ═══════════════════════════════════════════════════════════════════════════ */

/* Ambient cursor-following light */
.login-light {
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background: radial-gradient(600px circle at var(--mx,50%) var(--my,40%),
    rgba(255,215,0,.07) 0%, transparent 70%);
}
/* Grain texture */
.login-noise {
  position: fixed; inset: 0; pointer-events: none; z-index: 0; opacity: .4;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
  background-size: 200px;
}

/* Card */
.login-wrap { position: relative; min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px; }
.login-card {
  position: relative; z-index: 1;
  width: 100%; max-width: 420px;
  background: rgba(12,12,12,.92); backdrop-filter: blur(18px);
  border: 1px solid rgba(255,215,0,.12);
  border-radius: 16px; padding: 40px 36px 36px;
  box-shadow: 0 0 0 1px rgba(255,215,0,.04), 0 32px 80px rgba(0,0,0,.7);
  /* grain on the card itself */
  overflow: hidden;
}
.login-card::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E");
  background-size: 180px; mix-blend-mode: overlay; opacity: .25;
}
.login-brand { display:block; text-align:center; font-size:18px; letter-spacing:3px; margin-bottom:4px; }
.login-sub { text-align:center; margin:0 0 28px; }
.login-heading { font-size:1.25rem; font-weight:600; margin:0 0 20px; }
.login-err { margin:16px 0 0; min-height:20px; }
.login-step-enter { animation:login-step-enter .18s ease-out both; }
@keyframes login-step-enter { from { opacity:0; transform:translateY(5px); } to { opacity:1; transform:translateY(0); } }
@media (prefers-reduced-motion: reduce) { .login-step-enter { animation:none; } }

/* Floating-label field */
.login-field-wrap { position:relative; margin-bottom:20px; }
.login-field {
  width:100%; padding:16px 14px 6px; background:rgba(255,255,255,.04);
  border:1px solid var(--border-subtle); border-radius:8px;
  color:var(--text-primary); font-size:1rem; transition:border-color .2s, box-shadow .2s;
  box-sizing:border-box;
}
.login-field:focus { outline:none; border-color:var(--accent-gold); box-shadow:0 0 0 3px rgba(255,215,0,.12); }
.login-label {
  position:absolute; left:14px; top:50%; transform:translateY(-50%);
  font-size:.95rem; color:var(--text-secondary); pointer-events:none;
  transition:all .18s ease;
}
.login-field:focus + .login-label,
.login-field:not(:placeholder-shown) + .login-label {
  top:8px; transform:none; font-size:.7rem; color:var(--accent-gold);
}
.login-btn { width:100%; margin-top:4px; }
.login-btn-sm { width:100%; margin-top:10px; font-size:.85rem; padding:8px; }

/* OTP digit boxes */
.otp-row { display:flex; align-items:center; gap:8px; justify-content:center; margin:0 0 20px; }
.otp-digit {
  width:48px; height:56px; text-align:center; font-size:1.4rem; font-weight:600;
  background:rgba(255,255,255,.05); border:2px solid var(--border-subtle);
  border-radius:10px; color:var(--text-primary); caret-color:var(--accent-gold);
  transition:border-color .15s, box-shadow .15s, background .15s;
  font-variant-numeric: tabular-nums;
}
.otp-digit:focus {
  outline:none; border-color:var(--accent-gold);
  box-shadow:0 0 0 3px rgba(255,215,0,.18), 0 0 14px rgba(255,215,0,.1);
  background:rgba(255,215,0,.05);
}
.otp-digit.filled { border-color:rgba(255,215,0,.4); }
.otp-sep { color:var(--text-secondary); font-size:1.2rem; user-select:none; }

/* Verifying ring */
#login-step-verify { text-align:center; padding:8px 0; }
.verify-ring {
  position:relative; width:80px; height:80px; margin:20px auto;
}
.ring-svg { width:80px; height:80px; transform:rotate(-90deg); }
.ring-track { fill:none; stroke:rgba(255,255,255,.08); stroke-width:6; }
.ring-progress {
  fill:none; stroke:var(--accent-gold); stroke-width:6;
  stroke-linecap:round;
  stroke-dasharray:213.6; stroke-dashoffset:213.6;
  animation:ring-fill 1.8s ease-out forwards;
}
@keyframes ring-fill {
  to { stroke-dashoffset:0; }
}
.ring-label {
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-size:.85rem; font-weight:600; color:var(--text-primary); font-variant-numeric:tabular-nums;
}

/* Access granted */
#login-step-granted { text-align:center; padding:8px 0; }
.access-granted-text { animation:fade-up .4s ease both; }
.granted-icon { position:relative; width:80px; height:80px; margin:20px auto 12px; }
.granted-glow-ring {
  fill:none; stroke:rgba(34,197,94,.4); stroke-width:3;
  animation:glow-pulse 1.2s ease-out both;
}
@keyframes glow-pulse {
  0%   { stroke-width:3; opacity:0; r:16; }
  50%  { stroke-width:8; opacity:.6; r:28; }
  100% { stroke-width:3; opacity:.3; r:26; }
}
.granted-fill {
  fill:rgba(34,197,94,.9);
  animation:pop-in .35s cubic-bezier(.34,1.56,.64,1) .1s both;
}
.granted-check {
  fill:none; stroke:#fff; stroke-width:3.5; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:34; stroke-dashoffset:34;
  animation:draw-check .35s ease .3s forwards;
}
@keyframes draw-check { to { stroke-dashoffset:0; } }
.granted-label {
  display:block; margin-top:6px; font-size:.65rem; letter-spacing:.18em;
  color:rgba(34,197,94,.9); font-weight:700;
  animation:fade-up .3s ease .55s both;
}
.login-card.state-granted { border-color:rgba(34,197,94,.3); box-shadow:0 0 0 1px rgba(34,197,94,.08), 0 0 60px rgba(34,197,94,.08), 0 32px 80px rgba(0,0,0,.7); transition:border-color .4s, box-shadow .4s; }

@keyframes pop-in  { from{transform:scale(.5);opacity:0} to{transform:scale(1);opacity:1} }
@keyframes fade-up { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:none} }
