/* ═══════════════ FALAQ DASHBOARD — App shell & components ═══════════════ */
@import url('tokens.css');

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%;}
html{-webkit-text-size-adjust:100%;}
body{
  overflow-x:hidden;
  font-family:var(--font);
  background:var(--bg);
  color:var(--ink);
  line-height:1.7;
  -webkit-font-smoothing:antialiased;
  font-variant-ligatures:common-ligatures contextual;
  font-feature-settings:"liga" 1,"clig" 1,"calt" 1,"rlig" 1;
}
img{max-width:100%;display:block;}
a{color:inherit;text-decoration:none;}
button,input,select,textarea{font:inherit;color:inherit;}
[hidden]{display:none !important;}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:6px;}

/* subtle brand glow, fixed so it never scrolls */
body::before{
  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
  background:
    radial-gradient(900px 520px at 92% -8%,rgba(53,221,212,.10),transparent 62%),
    radial-gradient(760px 460px at 4% 104%,rgba(161,249,176,.07),transparent 60%);
}
/* The same glow on white would be a grey smudge — it needs to be both
   weaker and warmer to read as light rather than as dirt. */
:root[data-theme="light"] body::before{
  background:
    radial-gradient(900px 520px at 92% -8%,rgba(14,143,134,.07),transparent 62%),
    radial-gradient(760px 460px at 4% 104%,rgba(18,165,148,.05),transparent 60%);
}

/* ───────────── Scrollbars ───────────── */
*::-webkit-scrollbar{width:10px;height:10px;}
*::-webkit-scrollbar-track{background:transparent;}
*::-webkit-scrollbar-thumb{background:var(--surface-3);border-radius:100px;border:2px solid var(--bg);}
*::-webkit-scrollbar-thumb:hover{background:#2e3849;}

/* ═════════════ LOGIN ═════════════ */
.login{
  position:fixed;inset:0;z-index:60;
  display:grid;place-items:center;padding:24px;
  background:var(--bg);
}
.login-card{
  position:relative;z-index:1;
  width:min(420px,100%);
  background:var(--surface-1);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  padding:38px 32px 32px;
  box-shadow:var(--shadow-2);
}
.login-logo{height:46px;width:auto;margin:0 auto 20px;}
.login-card h1{font-size:1.35rem;font-weight:900;text-align:center;line-height:1.4;}
.login-card .sub{text-align:center;color:var(--ink-3);font-size:.86rem;margin-bottom:26px;}

/* ═════════════ APP SHELL ═════════════ */
.app{position:relative;z-index:1;display:flex;min-height:100vh;min-height:100dvh;}

/* ── Sidebar (right, because dir=rtl) ── */
.sidebar{
  width:var(--sidebar-w);flex:0 0 var(--sidebar-w);
  background:var(--surface-1);
  border-inline-start:1px solid var(--line);
  display:flex;flex-direction:column;
  position:sticky;top:0;height:100vh;
}
.sidebar-head{
  height:var(--topbar-h);flex:0 0 var(--topbar-h);
  display:flex;align-items:center;gap:10px;
  padding:0 20px;border-bottom:1px solid var(--line);
}
/* Scoped rather than bare, and that matters: `.btn-icon` below declares
   `display:inline-grid` at the same specificity and later in the file,
   so a bare `.nav-close{display:none}` lost — which is how the drawer's
   X came to sit in the desktop sidebar doing nothing. */
.sidebar-head .nav-close,
.sidebar-head .nav-collapse{display:none;margin-inline-start:auto;}
.sidebar-head img{height:26px;width:auto;}
.sidebar-head .wordmark{font-weight:900;font-size:1.02rem;letter-spacing:.01em;}
.sidebar-nav{
  flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
  padding:14px 12px;display:flex;flex-direction:column;gap:2px;
}
.nav-section{
  font-size:.68rem;font-weight:700;color:var(--ink-3);
  letter-spacing:.09em;padding:16px 12px 6px;
}
.nav-item{
  display:flex;align-items:center;gap:11px;
  padding:9px 12px;border-radius:var(--r);
  color:var(--ink-2);font-size:.9rem;font-weight:500;
  cursor:pointer;border:0;background:transparent;width:100%;
  text-align:start;transition:background .18s var(--ease),color .18s var(--ease);
}
.nav-item svg{flex:0 0 18px;width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;opacity:.85;}
.nav-item:hover{background:var(--surface-2);color:var(--ink);}
.nav-item.active{background:var(--surface-3);color:var(--ink);font-weight:700;}
.nav-item.active svg{stroke:var(--brand);opacity:1;}
.nav-item .badge{
  margin-inline-start:auto;background:var(--brand);color:var(--ink-on-grad);
  font-size:.66rem;font-weight:900;padding:1px 7px;border-radius:var(--r-pill);
}

/* profile block pinned to sidebar bottom */
.sidebar-user{
  border-top:1px solid var(--line);padding:12px;
  padding-bottom:calc(12px + env(safe-area-inset-bottom));
  display:flex;align-items:center;gap:11px;
}
.sidebar-user .avatar{width:38px;height:38px;border-radius:50%;object-fit:cover;flex:0 0 38px;border:1.5px solid var(--line-accent);}
.sidebar-user .meta{min-width:0;flex:1;}
.sidebar-user .name{font-size:.87rem;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.sidebar-user .role{font-size:.72rem;color:var(--ink-3);}

/* ── Main ── */
.main{flex:1;min-width:0;display:flex;flex-direction:column;}
.topbar{
  height:var(--topbar-h);flex:0 0 var(--topbar-h);
  display:flex;align-items:center;gap:14px;
  padding:0 clamp(16px,2.4vw,30px);
  border-bottom:1px solid var(--line);
  background:color-mix(in srgb,var(--bg) 82%,transparent);
  backdrop-filter:blur(14px);
  position:sticky;top:0;z-index:20;
}
.topbar h2{font-size:1.06rem;font-weight:900;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.topbar .spacer{flex:1;}
.topbar-logo{display:none;height:24px;width:auto;}
.burger{
  display:none;background:transparent;border:0;cursor:pointer;
  padding:8px;margin-inline-start:-8px;border-radius:var(--r-sm);
  flex:0 0 auto;
}
.burger svg{width:24px;height:24px;stroke:var(--ink);fill:none;stroke-width:1.9;stroke-linecap:round;}
.burger:active{background:var(--surface-2);}
.view{
  padding:clamp(14px,2.4vw,30px);flex:1;min-width:0;
  padding-bottom:calc(clamp(14px,2.4vw,30px) + env(safe-area-inset-bottom));
}

/* drawer backdrop — element lives in the DOM so it can fade */
.scrim{
  position:fixed;inset:0;z-index:45;
  background:var(--veil);backdrop-filter:blur(3px);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .28s var(--ease),visibility .28s var(--ease);
}
body.nav-open .scrim{opacity:1;visibility:visible;pointer-events:auto;}
body.nav-open{overflow:hidden;}

/* ── The collapsed sidebar's handle (desktop only) ──
   Parked against the screen edge on the same side the sidebar left
   from, so the way back is where the way out was. It sits outside
   <aside> in the markup because anything inside it slides away with
   it. Enabled in the desktop block far below; on a phone the drawer
   already has a burger and this would be a second, worse one.

   It rides the header row — centred on `--topbar-h`, the same band the
   collapse arrow sits in — rather than the middle of the page. The two
   controls are one toggle seen twice, and a toggle whose halves are
   400px apart vertically reads as two unrelated buttons. Being up there
   also puts it where a sidebar control is looked for; parked at
   mid-height it was easy to miss entirely. */
.nav-peek{
  position:fixed;z-index:46;
  inset-inline-start:0;top:calc(var(--topbar-h) / 2);
  width:30px;height:46px;padding:0;
  display:none;place-items:center;cursor:pointer;
  background:var(--surface-3);color:var(--ink);
  border:1px solid var(--line-accent);border-inline-start:0;
  border-start-end-radius:13px;border-end-end-radius:13px;
  box-shadow:var(--shadow-2);
  /* Physical, because `transform` always is: parked off the screen on
     the sidebar's own side, which is the right under dir=rtl. */
  transform:translateY(-50%) translateX(100%);
  visibility:hidden;
  transition:transform .32s var(--ease),visibility .32s var(--ease),
             background .18s var(--ease),color .18s var(--ease);
}
[dir="ltr"] .nav-peek{transform:translateY(-50%) translateX(-100%);}
.nav-peek svg{
  width:19px;height:19px;stroke:currentColor;fill:none;
  stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;
}
/* The chevron points the way the sidebar will travel — leftward back
   into the page under RTL, the mirror of that under LTR. */
[dir="ltr"] .nav-peek svg{transform:scaleX(-1);}
[dir="ltr"] .nav-collapse svg{transform:scaleX(-1);}
.nav-peek:hover{background:color-mix(in srgb,var(--brand) 22%,var(--surface-3));}
body.nav-collapsed .nav-peek{transform:translateY(-50%);visibility:visible;}

/* ═════════════ PRIMITIVES ═════════════ */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:7px;
  font-weight:700;font-size:.87rem;
  padding:.6em 1.25em;border-radius:var(--r-pill);
  border:0;cursor:pointer;white-space:nowrap;
  transition:transform .3s var(--spring),background .2s,box-shadow .3s var(--ease),opacity .2s;
}
.btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.btn:active{transform:scale(.96);}
.btn:disabled,.btn-icon:disabled{opacity:.5;cursor:not-allowed;}
.btn-primary{background:var(--grad);color:var(--ink-on-grad);}
.btn-primary:hover:not(:disabled){box-shadow:0 10px 26px -10px rgba(53,221,212,.5);}
.btn-ghost{background:var(--surface-2);color:var(--ink);box-shadow:inset 0 0 0 1px var(--line-2);}
.btn-ghost:hover:not(:disabled){background:var(--surface-3);}
.btn-danger{background:transparent;color:var(--danger);box-shadow:inset 0 0 0 1px rgba(255,122,122,.35);}
.btn-danger:hover:not(:disabled){background:rgba(255,122,122,.1);}
.btn-sm{padding:.42em .85em;font-size:.79rem;}
.btn-block{width:100%;}
.btn-icon{
  background:transparent;border:0;cursor:pointer;padding:6px;border-radius:var(--r-sm);
  color:var(--ink-3);display:inline-grid;place-items:center;transition:background .18s,color .18s;
}
.btn-icon svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.btn-icon:hover{background:var(--surface-3);color:var(--ink);}
.btn-icon.danger:hover{color:var(--danger);}

/* Fields */
.field{display:flex;flex-direction:column;gap:6px;margin-bottom:15px;}
.field > label{font-size:.81rem;font-weight:700;color:var(--ink-2);}
.field .hint{font-size:.74rem;color:var(--ink-3);line-height:1.5;}
.input,.select,.textarea{
  background:var(--surface-2);
  border:1px solid var(--line-2);
  border-radius:var(--r);
  padding:.62em .85em;font-size:.88rem;width:100%;
  transition:border-color .2s,box-shadow .2s;
}
.textarea{resize:vertical;min-height:92px;line-height:1.7;}
.select{appearance:none;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236C7A8C' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-size:16px;background-position:left .8em center;padding-inline-start:2.2em;}
.input:focus,.select:focus,.textarea:focus{
  outline:none;border-color:var(--brand-2);box-shadow:0 0 0 3px rgba(53,221,212,.13);
}
.input::placeholder,.textarea::placeholder{color:var(--ink-3);}

/* Per-field validation on the public forms. The message is written into
   the field by JS rather than authored in the markup, so a field only
   ever carries one and it disappears the moment the field is fixed.
   `aria-invalid` is what a screen reader announces; the red border and
   the line under it are for everyone else. */
.input[aria-invalid="true"],.select[aria-invalid="true"],.textarea[aria-invalid="true"]{
  border-color:var(--danger);
}
.input[aria-invalid="true"]:focus,.select[aria-invalid="true"]:focus,
.textarea[aria-invalid="true"]:focus{box-shadow:0 0 0 3px rgba(255,122,122,.14);}
.field-err{font-size:.75rem;font-weight:600;color:var(--danger);line-height:1.5;}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.check{display:flex;align-items:center;gap:9px;cursor:pointer;font-size:.86rem;user-select:none;}

/* Optional half of a form, folded away. The client's complaint about
   the project form was its length, not its fields — everything that is
   not required to save now starts closed. */
details.more{
  border:1px solid var(--line);border-radius:var(--r);
  background:var(--surface-2);padding:0 14px;margin-bottom:15px;
}
details.more > summary{
  cursor:pointer;list-style:none;padding:12px 0;
  font-size:.83rem;font-weight:700;color:var(--ink-2);
  display:flex;align-items:center;gap:8px;
}
details.more > summary::-webkit-details-marker{display:none;}
details.more > summary::after{content:"+";margin-inline-start:auto;color:var(--ink-3);font-weight:900;font-size:1rem;}
details.more[open] > summary::after{content:"−";}
details.more[open]{padding-bottom:2px;}
details.more > summary + *{margin-top:4px;}
.check input{width:17px;height:17px;accent-color:var(--brand);cursor:pointer;flex:0 0 17px;}

/* Card */
.card{
  background:var(--surface-1);border:1px solid var(--line);
  border-radius:var(--r-lg);padding:20px;box-shadow:var(--shadow-1);
}
.card-head{display:flex;align-items:center;gap:12px;margin-bottom:16px;}
.card-head h3{font-size:.98rem;font-weight:900;}
.card-head .spacer{flex:1;}

/* Page header */
.page-head{display:flex;align-items:flex-start;gap:14px;flex-wrap:wrap;margin-bottom:20px;}
.page-head .titles{flex:1;min-width:220px;}
.page-head h1{font-size:1.32rem;font-weight:900;line-height:1.35;}
.page-head p{color:var(--ink-3);font-size:.85rem;}
.page-actions{display:flex;gap:9px;flex-wrap:wrap;}

/* Toolbar */
.toolbar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:16px;}
.search{position:relative;flex:1;min-width:190px;max-width:340px;}
.search input{padding-inline-start:2.3em;}
.search svg{
  position:absolute;inset-inline-start:.8em;top:50%;transform:translateY(-50%);
  width:15px;height:15px;stroke:var(--ink-3);fill:none;stroke-width:2;pointer-events:none;
}

/* Table */
.table-wrap{border:1px solid var(--line);border-radius:var(--r-lg);overflow-x:auto;background:var(--surface-1);}
table{width:100%;border-collapse:collapse;font-size:.86rem;min-width:560px;}
th{
  text-align:start;font-weight:700;font-size:.75rem;color:var(--ink-3);
  padding:11px 14px;background:var(--surface-2);white-space:nowrap;
  border-bottom:1px solid var(--line);letter-spacing:.03em;
}
td{padding:12px 14px;border-bottom:1px solid var(--line);vertical-align:middle;}
tbody tr:last-child td{border-bottom:0;}
tbody tr{transition:background .15s;}
tbody tr:hover{background:var(--surface-2);}
td.actions{white-space:nowrap;text-align:end;}
td .cell-strong{font-weight:700;color:var(--ink);}
td .cell-sub{font-size:.76rem;color:var(--ink-3);}

/* Chips / pills */
.chip{
  display:inline-flex;align-items:center;gap:5px;
  font-size:.72rem;font-weight:700;padding:3px 10px;border-radius:var(--r-pill);
  background:var(--surface-3);color:var(--ink-2);white-space:nowrap;
}
.chip.ok{background:rgba(161,249,176,.13);color:var(--ok);}
.chip.info{background:rgba(53,221,212,.13);color:var(--info);}
.chip.warn{background:rgba(245,200,107,.13);color:var(--warn);}
.chip.danger{background:rgba(255,122,122,.13);color:var(--danger);}

/* Empty & loading */
.empty{
  text-align:center;padding:52px 20px;color:var(--ink-3);
  border:1px dashed var(--line-2);border-radius:var(--r-lg);
}
.empty svg{width:34px;height:34px;stroke:var(--ink-3);fill:none;stroke-width:1.4;margin:0 auto 12px;}
.empty h4{font-size:.95rem;font-weight:700;color:var(--ink-2);margin-bottom:4px;}
.empty p{font-size:.82rem;margin-bottom:16px;}
.skeleton{height:52px;border-radius:var(--r);background:linear-gradient(90deg,var(--surface-1),var(--surface-2),var(--surface-1));background-size:220% 100%;animation:shimmer 1.3s infinite;margin-bottom:8px;}
@keyframes shimmer{0%{background-position:220% 0}100%{background-position:-220% 0}}
.spinner{
  width:15px;height:15px;border-radius:50%;
  border:2px solid rgba(255,255,255,.22);border-top-color:currentColor;
  animation:spin .7s linear infinite;display:inline-block;
}
@keyframes spin{to{transform:rotate(360deg)}}

/* Stat grid */
.stats-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:var(--gap);}
.stat{
  background:var(--surface-1);border:1px solid var(--line);
  border-radius:var(--r-lg);padding:17px 19px;position:relative;overflow:hidden;
}
.stat::after{content:"";position:absolute;inset-block:0;inset-inline-start:0;width:3px;background:var(--grad);opacity:.75;}
.stat .k{font-size:.77rem;color:var(--ink-3);font-weight:600;}
.stat .v{font-size:1.75rem;font-weight:900;line-height:1.25;font-variant-numeric:tabular-nums;}
.stat .d{font-size:.74rem;color:var(--ink-3);}

/* Bar chart (pure CSS, no library) */
.bars{display:flex;flex-direction:column;gap:11px;}
.bar-row{display:grid;grid-template-columns:112px 1fr 52px;gap:11px;align-items:center;font-size:.82rem;}
.bar-track{height:9px;background:var(--surface-3);border-radius:var(--r-pill);overflow:hidden;}
.bar-fill{height:100%;background:var(--grad);border-radius:var(--r-pill);transition:width .6s var(--ease);}
.bar-val{text-align:end;font-variant-numeric:tabular-nums;color:var(--ink-2);font-weight:700;}

/* ═════════════ MODAL ═════════════ */
.modal-back{
  position:fixed;inset:0;z-index:80;
  background:var(--veil-strong);backdrop-filter:blur(6px);
  display:grid;place-items:center;padding:20px;
  animation:fadeIn .18s var(--ease);
}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
.modal{
  background:var(--surface-1);border:1px solid var(--line-2);
  border-radius:var(--r-lg);box-shadow:var(--shadow-2);
  width:min(560px,100%);max-height:88vh;display:flex;flex-direction:column;
  animation:pop .26s var(--spring);
}
.modal.wide{width:min(900px,100%);}
@keyframes pop{from{opacity:0;transform:translateY(14px) scale(.98)}to{opacity:1;transform:none}}
.modal-head{display:flex;align-items:center;gap:12px;padding:18px 22px;border-bottom:1px solid var(--line);}
.modal-head h3{font-size:1rem;font-weight:900;flex:1;}
.modal-body{padding:22px;overflow-y:auto;flex:1;}
.modal-foot{display:flex;gap:10px;justify-content:flex-end;padding:16px 22px;border-top:1px solid var(--line);}

/* ═════════════ TOASTS ═════════════ */
.toasts{position:fixed;inset-block-end:22px;inset-inline-start:22px;z-index:120;display:flex;flex-direction:column;gap:9px;}
.toast{
  background:var(--surface-3);border:1px solid var(--line-2);
  border-radius:var(--r);padding:11px 16px;font-size:.85rem;font-weight:600;
  box-shadow:var(--shadow-2);max-width:340px;
  animation:toastIn .3s var(--spring);
  border-inline-start:3px solid var(--ink-3);
}
.toast.ok{border-inline-start-color:var(--ok);}
.toast.err{border-inline-start-color:var(--danger);}
@keyframes toastIn{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}

/* ═════════════ MODULE-SPECIFIC ═════════════ */

/* Stacked avatars — project teams, task assignees */
.avatars{display:flex;}
.avatars img,.avatars .ini{
  width:26px;height:26px;border-radius:50%;object-fit:cover;
  border:2px solid var(--surface-1);margin-inline-start:-8px;
  display:grid;place-items:center;font-size:.66rem;font-weight:900;background:var(--surface-3);
}
.avatars > :first-child{margin-inline-start:0;}

/* Project detail — content + side column */
.proj-detail{
  display:grid;grid-template-columns:1fr minmax(240px,300px);
  gap:var(--gap);align-items:start;
}
.proj-detail > *{min-width:0;}

/* Project detail — update timeline */
.timeline{display:flex;flex-direction:column;gap:0;}
.tl-item{display:grid;grid-template-columns:26px 1fr;gap:13px;padding-bottom:18px;position:relative;}
.tl-item::before{content:"";position:absolute;inset-inline-start:12px;top:24px;bottom:0;width:1.5px;background:var(--line-2);}
.tl-item:last-child{padding-bottom:0;}
.tl-item:last-child::before{display:none;}
.tl-dot{width:26px;height:26px;border-radius:50%;background:var(--surface-3);display:grid;place-items:center;
  border:1.5px solid var(--line-2);z-index:1;}
.tl-dot svg{width:13px;height:13px;stroke:var(--brand);fill:none;stroke-width:2;}
.tl-body{padding-top:1px;}
.tl-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;}
.tl-head b{font-size:.85rem;}
.tl-head time{font-size:.73rem;color:var(--ink-3);}
.tl-text{font-size:.85rem;color:var(--ink-2);line-height:1.75;white-space:pre-wrap;}

/* ═════════════ KANBAN BOARD ═════════════
   Columns are stages, colour-coded. Every column sets --stage inline
   from the tokens, and everything inside it — the top rule, the dot,
   the drop highlight, a task card's spine — reads that one variable,
   so re-colouring a stage is one number in tokens.css. */
.board-wrap{
  overflow-x:auto;padding-bottom:12px;margin-inline:-4px;padding-inline:4px;
  -webkit-overflow-scrolling:touch;
  /* Without this a swipe that runs out of board turns into the
     browser's back gesture, which loses the screen. */
  overscroll-behavior-x:contain;
}
/* Snapping fights the drag auto-scroll: every programmatic nudge would
   be yanked back to the nearest column. */
body.k-dragging .board-wrap{scroll-snap-type:none;}
.kanban{
  display:grid;grid-auto-flow:column;grid-auto-columns:minmax(268px,1fr);
  gap:14px;align-items:stretch;min-width:min-content;
}
.k-col{
  background:var(--surface-1);border:1px solid var(--line);
  border-radius:var(--r-lg);display:flex;flex-direction:column;
  overflow:hidden;min-height:150px;scroll-snap-align:start;
}
.k-col-head{
  display:flex;align-items:center;gap:9px;
  padding:12px 15px;border-bottom:1px solid var(--line);
  border-top:3px solid var(--stage);
}
.k-col-head h3{font-size:.87rem;font-weight:900;flex:1;min-width:0;}
.k-col-head .k-dot{width:9px;height:9px;border-radius:50%;background:var(--stage);flex:0 0 9px;}
.k-count{
  font-size:.72rem;font-weight:900;color:var(--ink-3);
  background:var(--surface-2);border-radius:var(--r-pill);padding:1px 9px;
}
.k-drop{
  padding:12px;display:flex;flex-direction:column;gap:11px;flex:1;
  min-height:96px;transition:background .18s var(--ease),box-shadow .18s var(--ease);
}
/* The whole column lights up, not just the strip under the cards —
   the drop is resolved by nearest column, so the feedback has to
   promise the same forgiveness. */
.k-drop.over{
  background:color-mix(in srgb,var(--stage) 14%,transparent);
  box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--stage) 55%,transparent);
}
.k-drop .k-blank{
  font-size:.78rem;color:var(--ink-3);text-align:center;padding:14px 6px;
  border:1px dashed var(--line-2);border-radius:var(--r);
  display:grid;place-items:center;flex:1;min-height:64px;
}

.k-card{
  position:relative;overflow:hidden;
  background:var(--surface-2);border:1px solid var(--line);
  border-radius:var(--r);padding:13px 14px 12px;
  display:flex;flex-direction:column;gap:8px;cursor:grab;
  /* The card claims no gesture: a finger on it still pans the board,
     and the drag is armed by holding still instead (see kanban.js).
     Claiming pan-y here used to mean the columns could only be swiped
     from the gaps between cards, which on a phone is most of nothing.
     `manipulation` keeps the pan and drops only the double-tap zoom. */
  touch-action:manipulation;
  /* A selection or a native image drag starting on the card steals the
     pointer stream and kills the drag mid-gesture. */
  user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;
  transition:border-color .2s var(--ease),transform .25s var(--ease),box-shadow .25s var(--ease);
}
.k-card img{-webkit-user-drag:none;}
/* Hover lift only where there is a real pointer — on touch it sticks
   after the tap and the card looks stuck mid-air. */
@media (hover:hover){
  .k-card:hover{border-color:var(--line-accent);transform:translateY(-1px);}
}
.k-card h4{font-size:.9rem;font-weight:900;line-height:1.5;}
.k-card .excerpt{
  font-size:.79rem;color:var(--ink-3);line-height:1.6;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}

/* ── The one thing the client asked to be unmistakable: project vs task.
   A project wears the brand gradient as a full-width cap and a filled
   badge; a task wears a spine in its own stage colour and an outlined
   badge. Different edge, different side, different badge weight — it
   survives being glanced at from across the room. */
.k-card.is-project{padding-top:16px;background:var(--surface-1);}
.k-card.is-project::before{
  content:"";position:absolute;inset-inline:0;top:0;height:4px;background:var(--grad);
}
.k-card.is-task{padding-inline-start:17px;}
.k-card.is-task::before{
  content:"";position:absolute;inset-block:0;inset-inline-start:0;width:4px;background:var(--stage);
}

.k-top{display:flex;align-items:center;gap:7px;flex-wrap:wrap;}
.k-badge{
  display:inline-flex;align-items:center;gap:5px;
  font-size:.66rem;font-weight:900;padding:2px 9px;border-radius:var(--r-pill);
  letter-spacing:.02em;white-space:nowrap;
}
.k-badge svg{width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;}
.k-badge.project{background:var(--grad);color:var(--ink-on-grad);}
.k-badge.task{background:transparent;color:var(--ink-2);box-shadow:inset 0 0 0 1px var(--line-2);}
.k-parent{
  font-size:.7rem;color:var(--ink-3);font-weight:700;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;
}
.k-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:.72rem;color:var(--ink-3);}
.k-foot .avatars img,.k-foot .avatars .ini{width:22px;height:22px;border:2px solid var(--surface-2);}
.k-card.is-project .k-foot .avatars img,
.k-card.is-project .k-foot .avatars .ini{border-color:var(--surface-1);}
.k-due{display:inline-flex;align-items:center;gap:4px;font-weight:700;}
.k-due svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.k-due.late{color:var(--danger);}
.k-due.soon{color:var(--warn);}

/* Drag feedback. The ghost is a clone under the cursor; the original
   stays in place at low opacity so the column never reflows mid-drag
   and the drop target underneath keeps its geometry. */
.k-card.dragging{opacity:.32;}
.k-ghost{
  position:fixed;z-index:200;pointer-events:none;
  /* inset-0 + transform, because JS writes `transform` on every frame
     and a composited transform is the difference between a ghost that
     tracks the pointer and one that lags behind it. The tilt and lift
     ride the independent rotate/scale properties so they survive.

     `left`, not `inset-inline-start`: the page is RTL, so the logical
     property anchored the ghost's *right* edge to the viewport's right
     edge, and the transform — which is plain clientX/clientY, i.e.
     physical — then pushed it a whole viewport width away from the
     pointer. This origin has to be physical because the number written
     into it is. */
  top:0;left:0;will-change:transform;
  rotate:-1.6deg;scale:1.03;
  box-shadow:var(--shadow-2);opacity:.96;
}
body.k-dragging{cursor:grabbing;user-select:none;}
body.k-dragging .k-card{cursor:grabbing;}

/* ── Moving a card without dragging ──
   Quiet until wanted on a desktop, a first-class button on a phone. */
.k-move{
  display:inline-flex;align-items:center;gap:5px;
  border:1px solid var(--line-2);background:transparent;cursor:pointer;
  color:var(--ink-3);font:inherit;font-size:.7rem;font-weight:700;
  padding:3px 9px;border-radius:var(--r-pill);
  transition:color .18s var(--ease),border-color .18s var(--ease),background .18s var(--ease);
}
.k-move svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.k-move:hover,.k-move:focus-visible{color:var(--ink);border-color:var(--line-accent);background:var(--surface-3);}
@media (hover:hover){
  /* Present but recessive until the card is under the cursor. */
  .k-move{opacity:.5;}
  .k-card:hover .k-move,.k-move:focus-visible{opacity:1;}
}

/* ── Stage pager (phones) ──
   A five- or six-column board is four or five flicks wide. These chips
   make it one tap, and they double as the only view of what is
   off-screen. Shared by إدارة المشاريع and تتبع العملاء. */
.k-pager{display:none;}
.stage-pick{display:flex;flex-direction:column;gap:8px;}
.stage-opt{
  display:flex;align-items:center;gap:11px;text-align:start;
  border:1px solid var(--line);background:var(--surface-2);cursor:pointer;
  border-radius:var(--r);padding:13px 14px;color:var(--ink);font:inherit;
  border-inline-start:4px solid var(--stage);
  transition:background .18s var(--ease),border-color .18s var(--ease);
}
.stage-opt b{font-size:.9rem;font-weight:900;flex:1;}
.stage-opt:hover{background:var(--surface-3);}
.stage-opt.on{border-color:var(--line-accent);background:var(--surface-3);}
/* The pipeline's six stages are not self-explaining the way تخطيط and
   مكتمل are — "بانتظار عرض سعر" and "قيد التفاوض" are a judgement call —
   so its picker carries one line of what each column means. The stage
   name has to stay the big text, hence the column and the flex reset. */
.stage-opt:has(.so-hint){align-items:flex-start;}
.stage-opt:has(.so-hint) b{flex:none;}
.stage-opt .so-hint{
  display:block;width:100%;order:3;
  font-size:.74rem;font-weight:400;color:var(--ink-3);line-height:1.6;margin-top:2px;
}
.stage-opt:has(.so-hint) > .k-dot{margin-top:5px;}
.stage-opt:has(.so-hint) > .chip{order:2;}

/* ═════════════ تتبع العملاء ═════════════
   The pipeline board reuses every kanban primitive above — this is only
   what a *lead* card carries that a project card does not: the channel
   it came through, and the fate of the proposal we sent. */

/* One spine like a task's, because a lead is one kind of thing and needs
   no project/task disambiguation. `.needs` is the whole point of the
   screen: a row that wants chasing today is visible without reading it. */
.k-card.is-lead{padding-inline-start:17px;}
.k-card.is-lead::before{
  content:"";position:absolute;inset-block:0;inset-inline-start:0;width:4px;background:var(--stage);
}
.k-card.is-lead.needs{border-color:var(--line-accent);}

/* The channel badge. Outlined rather than filled: the *stage* is already
   carried by the spine and the column, and a second filled badge would
   compete with it for the same glance. */
.k-badge.chan{
  background:transparent;color:var(--ink-2);box-shadow:inset 0 0 0 1px var(--line-2);
  gap:5px;
}
.k-badge.chan.is-none{color:var(--ink-3);font-weight:700;}
.k-badge.chan .k-dot{width:7px;height:7px;border-radius:50%;background:var(--stage);flex:0 0 7px;}

/* Proposal state, on its own row: it answers a different question from
   everything in .k-top and reads as an afterthought when crowded in. */
.lead-file{margin-top:2px;}
.lead-file .chip{font-size:.7rem;}

/* The next action, with an arrow so it reads as an instruction rather
   than as a description. The icon is baseline-nudged because it sits
   inside a clamped two-line box. */
.k-card.is-lead .excerpt svg{
  width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2.2;
  stroke-linecap:round;stroke-linejoin:round;
  margin-inline-end:4px;vertical-align:-1px;opacity:.7;
}

/* Channel filters. Its own row under the toolbar rather than more chips
   inside it: eight of them wrap the toolbar into three lines and push
   the board below the fold. Scrolls sideways instead.

   Written `.chip-row.chan-row` on purpose. `.chip-row` sets
   `flex-wrap:wrap` and is declared *later* in this file, so a bare
   `.chan-row` lost the tie on equal specificity and the row wrapped onto
   three lines on a phone — the exact thing this rule exists to prevent. */
.chip-row.chan-row{
  margin-bottom:var(--gap);padding-bottom:2px;
  overflow-x:auto;flex-wrap:nowrap;scrollbar-width:none;
}
.chan-row::-webkit-scrollbar{display:none;}
.chan-row .chip-btn{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;}
.chip-btn svg{width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.chip-btn .n{
  font-size:.7rem;font-weight:800;color:var(--ink-3);
  background:var(--surface-2);border-radius:var(--r-pill);padding:0 6px;
}
.chip-btn.on .n{background:var(--surface-1);color:var(--ink-2);}

/* ── The touch log, inside the lead dialog ──
   Separated from the form above it by a real rule: the form is a record
   you are editing, the log is a list of things that already happened,
   and the two must not read as one long scroll of fields. */
.touch-log{margin-top:22px;padding-top:18px;border-top:1px solid var(--line);}
.touch-add{display:flex;gap:9px;flex-wrap:wrap;align-items:flex-end;}
.touch-add .input,.touch-add .select{min-width:0;}
.touch-add > .field{flex:0 0 auto;}
.touch-add .btn{flex:0 0 auto;}

/* Segmented view switch (board ⇄ list) */
.seg{display:inline-flex;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-pill);padding:3px;gap:2px;}
.seg button{
  border:0;background:transparent;cursor:pointer;color:var(--ink-3);
  font-size:.8rem;font-weight:700;padding:.4em 1em;border-radius:var(--r-pill);
  display:inline-flex;align-items:center;gap:6px;transition:background .18s,color .18s;
}
.seg button svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.seg button.on{background:var(--surface-3);color:var(--ink);}

/* Filter chips (الكل / مشاريع / مهام) */
.chip-row{display:flex;gap:7px;flex-wrap:wrap;}
.chip-btn{
  border:1px solid var(--line-2);background:transparent;cursor:pointer;
  color:var(--ink-3);font-size:.78rem;font-weight:700;
  padding:.35em 1em;border-radius:var(--r-pill);transition:all .18s var(--ease);
}
.chip-btn:hover{border-color:var(--line-accent);color:var(--ink-2);}
.chip-btn.on{background:var(--surface-3);color:var(--ink);border-color:var(--line-accent);}

/* ═════════════ TABS (project detail) ═════════════ */
.tabs{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:20px;overflow-x:auto;}
.tab{
  border:0;background:transparent;cursor:pointer;white-space:nowrap;
  color:var(--ink-3);font-size:.87rem;font-weight:700;
  padding:10px 15px;border-bottom:2px solid transparent;margin-bottom:-1px;
  transition:color .18s,border-color .18s;
}
.tab:hover{color:var(--ink-2);}
.tab.active{color:var(--ink);border-bottom-color:var(--brand);}

/* ═════════════ ورشة الحملة ═════════════
   The longest form in the dashboard, so nearly everything here is about
   making its length navigable rather than about decoration. */

/* The list of workshops. Cards rather than a table: what you want to
   know before opening one is how far along it is, and a progress bar is
   not a table cell. */
.ws-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));gap:var(--gap);}
.ws-card{
  display:flex;flex-direction:column;gap:8px;text-align:start;
  background:var(--surface-1);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:17px 18px;cursor:pointer;position:relative;overflow:hidden;
  transition:border-color .2s var(--ease),transform .25s var(--ease);
}
@media (hover:hover){ .ws-card:hover{border-color:var(--line-accent);transform:translateY(-2px);} }
.ws-card:focus-visible{outline:2px solid var(--brand-2);outline-offset:2px;}
.ws-card h4{font-size:.95rem;font-weight:900;line-height:1.5;}
.ws-card .excerpt{
  font-size:.79rem;color:var(--ink-3);line-height:1.65;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
.ws-top{display:flex;align-items:center;gap:7px;}
.ws-pct{font-size:.78rem;font-weight:900;color:var(--ink-2);font-variant-numeric:tabular-nums;}

/* The completion meter. `--pct` is written from JS on the inner element
   only, so the track never moves and the bar can be updated without
   rebuilding the card it sits in. */
.ws-meter{
  display:block;height:5px;border-radius:var(--r-pill);
  background:var(--line-2);overflow:hidden;flex:0 0 auto;
}
.ws-meter i{
  display:block;height:100%;width:var(--pct,0%);border-radius:inherit;background:var(--grad);
  transition:width .45s var(--ease);
}

/* Which of the three documents you are looking at, and how full the
   workshop behind it is — the second half is the reason this bar exists
   on the proposal tabs too. */
.ws-bar{
  display:flex;align-items:center;gap:12px;margin-bottom:var(--gap);
  padding:11px 15px;background:var(--surface-2);
  border:1px solid var(--line);border-radius:var(--r);
}
.ws-bar .ws-meter{width:min(180px,32vw);}
.ws-tag{font-size:.76rem;font-weight:700;color:var(--ink-3);letter-spacing:.02em;}
.ws-lede{
  color:var(--ink-2);font-size:.86rem;line-height:1.9;
  margin-bottom:var(--gap);max-width:74ch;
}

/* Station jump list. Twelve sections is a page you scroll past your own
   answer in, so this is the table of contents — sticky under the topbar
   because it is only useful once you are already deep in the form.
   Buttons rather than anchors: an href would write the hash and the
   router would tear the whole screen down and rebuild it. */
.ws-stops{
  position:sticky;top:calc(var(--topbar-h) + 6px);z-index:4;
  display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;
  padding:9px;margin-bottom:var(--gap);
  background:var(--surface-1);border:1px solid var(--line);border-radius:var(--r-pill);
  box-shadow:var(--shadow-1);
}
.ws-stops::-webkit-scrollbar{display:none;}
.ws-stop{
  display:inline-flex;align-items:center;gap:6px;flex:0 0 auto;
  border:0;background:transparent;cursor:pointer;color:var(--ink-3);
  font:inherit;font-size:.76rem;font-weight:700;white-space:nowrap;
  padding:6px 12px;border-radius:var(--r-pill);
  transition:background .18s,color .18s;
}
.ws-stop b{
  font-size:.7rem;font-weight:900;color:var(--ink-2);
  background:var(--surface-3);border-radius:var(--r-pill);padding:1px 7px;
}
.ws-stop:hover{background:var(--surface-2);color:var(--ink);}

.ws-station{margin-bottom:var(--gap);scroll-margin-top:calc(var(--topbar-h) + 66px);}
.ws-station .card-head h3{display:flex;align-items:center;gap:9px;}
.ws-num{
  display:inline-grid;place-items:center;min-width:24px;height:24px;padding:0 6px;
  border-radius:var(--r-sm);background:var(--surface-3);
  font-size:.75rem;font-weight:900;color:var(--ink-2);
}
.ws-slede{color:var(--ink-3);font-size:.8rem;line-height:1.75;margin-bottom:14px;max-width:70ch;}

/* Two columns of short fields on a desktop, one on a phone; anything
   long (`.wide`) spans both. Without this a station of four one-line
   answers is four full-width rows and the form doubles in length. */
.ws-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:0 16px;}
.ws-fields > .field{margin-bottom:15px;}
.ws-fields > .field.wide{grid-column:1/-1;}

/* ═════════════ MODE PICKER (قالب / حر) ═════════════
   The whole point of the two-way split: the free card must look like
   the fast path, so it is listed first and carries the accent. */
.mode-grid{display:grid;gap:12px;}
.mode-card{
  display:flex;align-items:flex-start;gap:14px;text-align:start;width:100%;
  background:var(--surface-2);border:1px solid var(--line-2);border-radius:var(--r);
  padding:16px 17px;cursor:pointer;
  transition:border-color .2s var(--ease),background .2s var(--ease),transform .25s var(--ease);
}
.mode-card:hover{border-color:var(--line-accent);transform:translateY(-1px);}
.mode-card .ico{
  width:40px;height:40px;flex:0 0 40px;border-radius:var(--r);
  display:grid;place-items:center;background:var(--surface-3);
}
.mode-card .ico svg{width:19px;height:19px;stroke:var(--ink-2);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.mode-card.hot .ico{background:linear-gradient(135deg,rgba(53,221,212,.16),rgba(161,249,176,.1));}
.mode-card.hot .ico svg{stroke:var(--brand);}
.mode-card b{display:block;font-size:.94rem;font-weight:900;margin-bottom:3px;color:var(--ink);}
.mode-card span{display:block;font-size:.8rem;color:var(--ink-3);line-height:1.65;}

/* ═════════════ CALENDAR ═════════════
   Two categories sharing one grid. Every coloured thing in here reads
   --cat, which the module sets inline from --cat-shoot / --cat-event —
   so nothing below knows a hex value, and adding a third calendar later
   is a token and a line in CATS.

   The grid is a stack of `.cal-week` rows rather than one 7×n grid,
   because the phone layout turns the body into a three-row scroll
   window and a row has to be a scroll-snap unit. */
.cal-head{display:flex;align-items:center;gap:11px;flex-wrap:wrap;margin-bottom:14px;}
.cal-nav{
  display:inline-flex;align-items:center;gap:2px;padding:3px;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-pill);
}
.cal-nav .btn-icon{border-radius:50%;}
.cal-title{font-size:1.05rem;font-weight:900;font-variant-numeric:tabular-nums;white-space:nowrap;}
.cal-legend{display:flex;gap:13px;flex-wrap:wrap;font-size:.75rem;font-weight:700;color:var(--ink-3);}
.cal-legend span{display:inline-flex;align-items:center;gap:6px;}
.cal-legend i{width:10px;height:10px;border-radius:3px;background:var(--cat);flex:0 0 10px;}

.cal-grid{
  border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surface-1);overflow:hidden;
}
.cal-dow{display:grid;grid-template-columns:repeat(7,1fr);background:var(--surface-2);border-bottom:1px solid var(--line);}
.cal-dow span{padding:9px 4px;text-align:center;font-size:.74rem;font-weight:800;color:var(--ink-3);}
.cal-week{display:grid;grid-template-columns:repeat(7,1fr);}
.cal-week + .cal-week{border-top:1px solid var(--line);}

.cal-day{position:relative;min-height:114px;padding:6px;display:flex;flex-direction:column;gap:5px;min-width:0;}
.cal-day + .cal-day{border-inline-start:1px solid var(--line);}
.cal-day.out{background:var(--surface-2);}
.cal-day.out .cal-daynum b{opacity:.45;}

/* The whole empty area of a cell is one button, sitting *behind* the
   chips rather than wrapping them: a chip is a button too, and nesting
   interactive elements gives a keyboard nothing it can reach and a
   screen reader nothing it can announce. */
.cal-add{
  position:absolute;inset:0;z-index:0;border:0;padding:0;
  background:transparent;cursor:pointer;border-radius:0;
  transition:background .18s var(--ease);
}
.cal-add:hover{background:color-mix(in srgb,var(--brand) 8%,transparent);}
.cal-add:focus-visible{outline:2px solid var(--brand-2);outline-offset:-3px;}

.cal-daynum{position:relative;z-index:1;pointer-events:none;display:flex;align-items:center;}
.cal-daynum b{
  font-size:.79rem;font-weight:800;color:var(--ink-2);
  font-variant-numeric:tabular-nums;min-width:22px;height:22px;
  display:inline-grid;place-items:center;border-radius:var(--r-pill);
}
.cal-day.today .cal-daynum b{background:var(--grad);color:var(--ink-on-grad);padding-inline:6px;}

.cal-evs{position:relative;z-index:1;display:flex;flex-direction:column;gap:3px;min-width:0;}
.cal-ev{
  display:flex;align-items:center;gap:5px;width:100%;min-width:0;
  text-align:start;font:inherit;font-size:.72rem;font-weight:700;color:var(--ink);
  border:0;border-inline-start:3px solid var(--cat);cursor:pointer;
  padding:3px 7px;border-radius:var(--r-sm);
  background:color-mix(in srgb,var(--cat) 15%,transparent);
  transition:background .16s;
}
.cal-ev:hover{background:color-mix(in srgb,var(--cat) 28%,transparent);}
.cal-ev .t{flex:0 0 auto;font-size:.67rem;color:var(--ink-3);font-variant-numeric:tabular-nums;}
.cal-ev .em{flex:0 0 auto;font-size:.8rem;line-height:1;}
.cal-ev .dot{flex:0 0 6px;width:6px;height:6px;border-radius:50%;background:var(--cat);}
.cal-ev .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cal-more{
  position:relative;z-index:1;align-self:flex-start;
  border:0;background:transparent;cursor:pointer;font:inherit;
  font-size:.68rem;font-weight:700;color:var(--ink-3);
  padding:2px 5px;border-radius:var(--r-sm);
}
.cal-more:hover{color:var(--ink);background:var(--surface-3);}

/* Phones only — see the responsive block. */
.cal-dots{display:none;}

/* ── Cards view, with countdowns ── */
.cal-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:var(--gap);}
.cal-card{
  position:relative;overflow:hidden;display:flex;gap:13px;padding:14px 15px;
  background:var(--surface-1);border:1px solid var(--line);border-radius:var(--r-lg);
  cursor:pointer;box-shadow:var(--shadow-1);
  transition:transform .25s var(--ease),border-color .2s,box-shadow .25s var(--ease);
}
.cal-card::before{content:"";position:absolute;inset-block:0;inset-inline-start:0;width:4px;background:var(--cat);}
.cal-card:hover{transform:translateY(-2px);border-color:var(--line-accent);box-shadow:var(--shadow-2);}
.cal-card:focus-visible{outline:2px solid var(--brand-2);outline-offset:2px;}
/* A finished shoot stays on the board but stops competing with the ones
   that still need doing. */
.cal-card.past{opacity:.62;}
.cal-card.past:hover{opacity:1;}
.cal-face{
  width:52px;height:52px;flex:0 0 52px;border-radius:var(--r);overflow:hidden;
  display:grid;place-items:center;font-size:1.5rem;line-height:1;
  background:color-mix(in srgb,var(--cat) 15%,var(--surface-2));
}
.cal-face img{width:100%;height:100%;object-fit:cover;}
.cal-face svg{width:22px;height:22px;stroke:var(--cat);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;}
.cal-card-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:5px;}
.cal-card-top{display:flex;align-items:center;gap:7px;flex-wrap:wrap;}
.cal-card h4{font-size:.93rem;font-weight:900;line-height:1.45;}
.cal-when,.cal-where{display:flex;align-items:center;gap:6px;font-size:.76rem;color:var(--ink-3);min-width:0;}
.cal-when svg,.cal-where svg{width:13px;height:13px;flex:0 0 13px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
/* The date wraps rather than truncating: a two-day range spells out two
   full Arabic dates, and "…طوال ا" tells the reader nothing. A location
   is arbitrary text, so that one still clips. */
.cal-when span{line-height:1.6;}
.cal-where span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cal-card-f{display:flex;align-items:center;gap:8px;margin-top:3px;font-size:.72rem;color:var(--ink-3);}
.cal-card-f .avatars img,.cal-card-f .avatars .ini{width:22px;height:22px;}

.cat-pill{
  display:inline-flex;align-items:center;gap:5px;white-space:nowrap;
  font-size:.68rem;font-weight:900;padding:2px 9px;border-radius:var(--r-pill);
  background:color-mix(in srgb,var(--cat) 16%,transparent);color:var(--cat);
}
.cat-pill svg{width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;}

.cal-cd{
  font-size:.7rem;font-weight:800;padding:2px 9px;border-radius:var(--r-pill);
  background:var(--surface-3);color:var(--ink-2);white-space:nowrap;
}
.cal-cd.now{background:color-mix(in srgb,var(--ok) 15%,transparent);color:var(--ok);}
.cal-cd.soon{background:color-mix(in srgb,var(--warn) 16%,transparent);color:var(--warn);}
.cal-cd.past{background:var(--surface-2);color:var(--ink-3);}

/* ── The day, opened on a phone ── */
.day-list{display:flex;flex-direction:column;gap:9px;}
.day-row{
  display:flex;align-items:center;gap:11px;width:100%;text-align:start;font:inherit;
  padding:10px 12px;border-radius:var(--r);cursor:pointer;
  background:var(--surface-2);border:1px solid var(--line);
  border-inline-start:3px solid var(--cat);
  transition:background .16s;
}
.day-row:hover{background:var(--surface-3);}
.day-row .cal-face{width:40px;height:40px;flex-basis:40px;font-size:1.2rem;}
.day-row-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;}
.day-row-b b{font-size:.87rem;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;}
.day-row-b > span{font-size:.74rem;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

/* ── Preview ── */
.ev-view{display:flex;flex-direction:column;gap:17px;}
.ev-hero{display:flex;align-items:center;gap:14px;}
.ev-hero .cal-face{width:60px;height:60px;flex-basis:60px;font-size:1.85rem;}
.ev-label{font-size:.74rem;font-weight:800;color:var(--ink-3);margin-bottom:8px;letter-spacing:.02em;}
.ev-meta{display:grid;grid-template-columns:auto 1fr;gap:9px 14px;font-size:.84rem;align-items:baseline;}
.ev-meta dt{display:flex;align-items:center;gap:6px;color:var(--ink-3);font-weight:700;white-space:nowrap;}
.ev-meta dt svg{width:14px;height:14px;flex:0 0 14px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.ev-meta dd{min-width:0;}
.ev-notes{font-size:.86rem;color:var(--ink-2);line-height:1.85;white-space:pre-wrap;}
.ev-who{display:flex;flex-wrap:wrap;gap:8px;}
.ev-who .who-pill{cursor:default;}

/* ── Assignee pills (editor) ──
   The client asked for admins "as pills with avatars and names that you
   can select" — so the face is the target, not a checkbox beside a
   label. Each is a real toggle button carrying aria-pressed. */
.who-pick{display:flex;flex-wrap:wrap;gap:8px;}
.who-pill{
  display:inline-flex;align-items:center;gap:8px;
  padding:5px;padding-inline-end:13px;border-radius:var(--r-pill);
  background:var(--surface-2);border:1.5px solid var(--line-2);
  font:inherit;font-size:.8rem;font-weight:700;color:var(--ink-2);cursor:pointer;
  transition:border-color .18s,background .18s,color .18s;
}
.who-pill img{width:26px;height:26px;flex:0 0 26px;border-radius:50%;object-fit:cover;}
.who-pill svg{width:14px;height:14px;flex:0 0 14px;stroke:var(--brand);fill:none;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round;display:none;}
.who-pill:hover{border-color:var(--line-accent);color:var(--ink);}
.who-pill.on{border-color:var(--brand);background:color-mix(in srgb,var(--brand) 12%,var(--surface-2));color:var(--ink);}
.who-pill.on svg{display:block;}

/* ── Category picker (editor) ── */
.cat-pick{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
.cat-opt{
  display:flex;align-items:center;gap:11px;text-align:start;width:100%;
  padding:13px;border-radius:var(--r);cursor:pointer;
  background:var(--surface-2);border:1.5px solid var(--line-2);
  transition:border-color .2s var(--ease),background .2s var(--ease),transform .22s var(--ease);
}
.cat-opt:hover{transform:translateY(-1px);border-color:var(--line-accent);}
.cat-opt .ico{
  width:36px;height:36px;flex:0 0 36px;border-radius:var(--r-sm);display:grid;place-items:center;
  background:color-mix(in srgb,var(--cat) 17%,transparent);
}
.cat-opt .ico svg{width:18px;height:18px;stroke:var(--cat);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.cat-opt b{display:block;font-size:.87rem;font-weight:900;color:var(--ink);margin-bottom:2px;}
.cat-opt > span > span{display:block;font-size:.72rem;color:var(--ink-3);line-height:1.6;}
.cat-opt.on{border-color:var(--cat);background:color-mix(in srgb,var(--cat) 10%,var(--surface-2));}

/* ── Emoji picker (editor) ── */
.emoji-pick{display:flex;flex-wrap:wrap;gap:6px;align-items:center;}
.emoji-opt{
  width:38px;height:38px;display:grid;place-items:center;
  font-size:1.15rem;line-height:1;cursor:pointer;
  background:var(--surface-2);border:1.5px solid var(--line-2);border-radius:var(--r-sm);
  transition:border-color .18s,background .18s,transform .2s var(--spring);
}
.emoji-opt:hover{border-color:var(--line-accent);transform:translateY(-1px);}
.emoji-opt.on{border-color:var(--brand);background:color-mix(in srgb,var(--brand) 14%,var(--surface-2));}
.emoji-opt svg{width:14px;height:14px;stroke:var(--ink-3);fill:none;stroke-width:2.2;stroke-linecap:round;}
.emoji-own{width:auto;flex:0 0 132px;text-align:center;}

/* ═════════════ ATTACHMENTS ═════════════ */
.att-list{display:flex;flex-wrap:wrap;gap:8px;}
.att{
  display:inline-flex;align-items:center;gap:8px;max-width:100%;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);
  padding:6px 10px;font-size:.79rem;
}
.att svg{width:14px;height:14px;flex:0 0 14px;stroke:var(--ink-3);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.att .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px;}
.att .sz{color:var(--ink-3);font-size:.72rem;font-variant-numeric:tabular-nums;}
.att img{width:28px;height:28px;border-radius:var(--r-sm);object-fit:cover;flex:0 0 28px;}

/* Drop target shared by attachments and the whiteboard */
.dropzone{
  border:1.5px dashed var(--line-2);border-radius:var(--r-lg);
  padding:22px 18px;text-align:center;color:var(--ink-3);font-size:.83rem;
  transition:border-color .2s,background .2s,color .2s;
}
.dropzone.over{border-color:var(--brand);color:var(--ink-2);background:color-mix(in srgb,var(--brand) 8%,transparent);}
.dropzone b{display:block;font-weight:900;color:var(--ink-2);margin-bottom:3px;font-size:.88rem;}

/* ═════════════ WHITEBOARD ═════════════
   An infinite canvas: `.wb-stage` clips, `.wb-world` carries the whole
   pan/zoom as one transform, and every element inside it is positioned
   in world units and never touched again.

   Two rules run through this whole section:

   • `.wb-world` is `direction:ltr` and everything under it uses
     physical `left`/`top`. A canvas coordinate is not a reading
     direction — the element's offset and the pointer's clientX have to
     name the same axis, or nothing lands where it was dropped. Only
     the *text inside* a block is direction-aware, via dir="auto".

   • Chrome divides by `--k` instead of being redrawn. The world
     publishes its zoom scale there, so an outline, a handle or a
     cursor stays the same size on screen at any zoom without JS
     recomputing a single rect per frame. */
.wb-bar{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin-bottom:10px;
}
.wb-sep{width:1px;align-self:stretch;background:var(--line-2);margin:2px 2px;}
.wb-swatches{display:flex;gap:5px;}
.wb-sw{
  width:20px;height:20px;border-radius:50%;cursor:pointer;
  border:2px solid var(--line-2);padding:0;
  transition:transform .15s var(--spring),border-color .15s var(--ease);
}
.wb-sw:hover{transform:scale(1.14);border-color:var(--ink-2);}
/* The swatch shows the paint, not the theme colour it is named after. */
.wb-sw.c-ink{background:var(--wb-ink);}
.wb-sw.c-mint{background:var(--wb-mint);}
.wb-sw.c-teal{background:var(--wb-teal);}
.wb-sw.c-warn{background:var(--wb-warn);}
.wb-sw.c-danger{background:var(--wb-danger);}
.wb-sw[aria-pressed="true"]{border-color:var(--ink);transform:scale(1.16);}
.wb-peers .chip{white-space:nowrap;}

/* Tool picker. Pressed state rather than a separate active class, so
   the button tells a screen reader what it tells everyone else. */
.wb-tool[aria-pressed="true"]{
  background:var(--surface-3);color:var(--ink);
  box-shadow:inset 0 0 0 1px var(--line-accent);
}
.wb-widths{display:flex;gap:4px;align-items:center;}
.wb-width{
  width:26px;height:26px;padding:0;border-radius:var(--r-sm);cursor:pointer;
  border:1px solid var(--line-2);background:transparent;
  display:grid;place-items:center;
}
.wb-width i{display:block;border-radius:50%;background:var(--ink-2);}
.wb-width[aria-pressed="true"]{background:var(--surface-3);border-color:var(--line-accent);}
.wb-width[aria-pressed="true"] i{background:var(--ink);}

/* The sheet is white in both themes — see the --wb-* block in
   tokens.css for why it does not follow the theme. */
.wb-stage{
  position:relative;overflow:hidden;
  height:clamp(420px,calc(100dvh - 260px),1000px);
  border:1px solid var(--line);border-radius:var(--r-lg);
  /* The dot grid is the paper's texture, and it pans and zooms with the
     board — applyView() writes --gx/--gy/--gs. A tile is 22 world units,
     so at any zoom the dots keep the same spacing as the content. */
  background:
    radial-gradient(circle at 1px 1px,var(--wb-grid) 1px,transparent 0)
      var(--gx,0) var(--gy,0)/var(--gs,22px) var(--gs,22px),
    var(--wb-paper);
  /* The board owns every touch inside it — a pinch here is a zoom, not
     the browser's page zoom, and a drag is a pan, not a scroll. */
  touch-action:none;cursor:default;outline:none;
}
.wb-stage.pannable{cursor:grab;}
.wb-stage.panning{cursor:grabbing;}
.wb-stage.over{border-color:var(--wb-sel);}
/* A crosshair says "this click makes a mark" — the one warning that a
   click will not select what is under it. */
.wb-stage.inking{cursor:crosshair;}

.wb-world{
  position:absolute;top:0;left:0;width:0;height:0;
  transform-origin:0 0;direction:ltr;
}

.wb-item{
  position:absolute;
  /* No transition on left/top: they are written every frame during a
     drag, and an eased element would trail the pointer. */
  border-radius:var(--r-sm);
}
/* No backing tint: a picture with its background removed is a PNG with
   real transparency, and a tint behind it would put a grey rectangle
   back where the background used to be. */
.wb-item img{
  width:100%;height:100%;display:block;object-fit:contain;
  border-radius:var(--r-sm);
  -webkit-user-drag:none;user-select:none;
}

/* The cut-out pass runs on the main thread — a megapixel flood fill is
   a visible beat, so the button says it is working. */
.wb-tool.busy,.wb-bar .btn.busy{opacity:.75;position:relative;}
.wb-bar .btn.busy svg{animation:spin .8s linear infinite;}
.wb-text{
  padding:6px 8px;line-height:1.65;font-weight:600;color:var(--wb-ink);
  white-space:pre-wrap;overflow-wrap:anywhere;border-radius:var(--r-sm);
  outline:none;
}
.wb-text[data-color="mint"]{color:var(--wb-mint);}
.wb-text[data-color="teal"]{color:var(--wb-teal);}
.wb-text[data-color="warn"]{color:var(--wb-warn);}
.wb-text[data-color="danger"]{color:var(--wb-danger);}
.wb-text.empty::before{content:"اكتب…";color:rgba(16,22,32,.4);}
.wb-item.editing .wb-text{
  background:color-mix(in srgb,var(--wb-sel) 8%,var(--wb-paper));
  box-shadow:0 0 0 calc(2px / var(--k,1)) var(--wb-sel);
}

/* ── Link previews ──
   A pasted URL becomes one of two things, and they are opposites: a
   card is an object on the board that happens to point somewhere, and
   a text link is nothing but a link. Both are sized in world units and
   scale with the zoom like everything else on the paper — none of this
   divides by --k, because a card is content, not chrome.

   The card never stores a height: the media box takes its ratio from
   the thumbnail (--ar, written per element by linkcard.js) and the two
   lines under it wrap to whatever the title needs. That is why
   resizing a card moves its width only — see stepResize(). */
.wb-card{
  background:var(--wb-paper);
  border:1px solid var(--wb-line);
  border-radius:12px;overflow:hidden;
  box-shadow:0 1px 3px rgba(16,22,32,.08),0 6px 18px rgba(16,22,32,.06);
  display:flex;flex-direction:column;
}
.wb-card-media{
  position:relative;
  aspect-ratio:var(--ar,1.7778);
  background:color-mix(in srgb,var(--wb-ink) 7%,var(--wb-paper));
  display:grid;place-items:center;overflow:hidden;
}
/* Artwork fills the box; a 32px favicon is centred at a readable size
   instead, because a favicon stretched to card width is a smear. */
.wb-card[data-fit="cover"] .wb-card-media img{width:100%;height:100%;object-fit:cover;}
.wb-card[data-fit="mark"]  .wb-card-media{aspect-ratio:1.7778;}
.wb-card[data-fit="mark"]  .wb-card-media img{
  width:38%;height:38%;object-fit:contain;
  filter:drop-shadow(0 2px 6px rgba(16,22,32,.18));
}
.wb-card-media img{display:block;-webkit-user-drag:none;user-select:none;}
/* No picture at all — the lookup found nothing, or the CDN 404'd after
   the record was written. A chain glyph beats a broken-image icon. */
.wb-card[data-noimg] .wb-card-media{aspect-ratio:2.6;}
.wb-card-fallback svg{
  width:34px;height:34px;stroke:var(--wb-ink);opacity:.32;
  fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
}
/* The play badge. A real button, not decoration: it is the one-tap way
   into the video, and being a button is also what keeps a press on it
   from starting a drag — onDown ignores buttons. */
.wb-play{
  position:absolute;inset:0;margin:auto;
  width:54px;height:54px;padding:0;border:0;border-radius:50%;
  background:rgba(255,255,255,.92);cursor:pointer;
  display:grid;place-items:center;
  box-shadow:0 3px 14px rgba(16,22,32,.32);
  transition:transform .18s var(--spring),background .18s var(--ease);
}
.wb-play svg{width:26px;height:26px;fill:var(--wb-ink);stroke:none;margin-inline-start:3px;}
.wb-play:hover{transform:scale(1.09);background:#fff;}
.wb-card-meta{
  display:flex;align-items:center;gap:8px;
  padding:9px 11px;min-width:0;
}
.wb-card-lines{min-width:0;flex:1;}
.wb-card-title{
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;
  font-size:14.5px;font-weight:800;line-height:1.45;color:var(--wb-ink);
  overflow-wrap:anywhere;
}
.wb-card-site{
  display:block;margin-top:2px;
  font-size:12px;font-weight:600;color:rgba(16,22,32,.52);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.wb-card-go{flex:0 0 auto;color:rgba(16,22,32,.5);}
.wb-card-go:hover{background:color-mix(in srgb,var(--wb-ink) 8%,transparent);color:var(--wb-ink);}
.wb-card-go svg{width:15px;height:15px;}

/* Text mode. Blue and underlined, because the only job it has is to
   look like a link and be clickable. `--wb-teal` is a real blue on
   white — the brand pastels are illegible on the paper. */
.wb-linkbody{display:block;}
.wb-linktext{
  display:block;padding:6px 8px;
  color:var(--wb-teal);text-decoration:underline;text-underline-offset:2px;
  font-size:15px;font-weight:700;line-height:1.6;
  overflow-wrap:anywhere;border-radius:var(--r-sm);
  -webkit-user-drag:none;
}
.wb-linktext:hover{background:color-mix(in srgb,var(--wb-teal) 9%,transparent);}

/* The paste dialog. The preview is `linkBody()` at the card's real
   width, so what it shows is what lands on the paper. */
.linkask-modes{display:flex;gap:8px;margin-bottom:14px;}
.linkask-mode{
  flex:1;display:flex;align-items:center;justify-content:center;gap:7px;
  padding:9px 12px;border:1px solid var(--line-2);border-radius:var(--r);
  cursor:pointer;font-size:.86rem;font-weight:700;color:var(--ink-2);
  transition:border-color .18s var(--ease),background .18s var(--ease);
}
.linkask-mode:has(input:checked){
  border-color:var(--line-accent);background:var(--surface-2);color:var(--ink);
}
.linkask-mode input{accent-color:var(--brand);}
/* The card is white paper, so the preview sits on a slab of the same
   paper rather than on the dialog's surface — otherwise a light-theme
   card is a white box on a white box. */
.linkask-stage{
  display:grid;place-items:center;
  padding:18px;border-radius:var(--r);
  background:color-mix(in srgb,var(--wb-ink) 13%,var(--wb-paper));
  min-height:150px;
}
.linkask-wait{display:flex;align-items:center;gap:9px;color:var(--ink-3);font-size:.86rem;}
.linkask-scale{max-width:100%;}

/* Ink — pencil strokes and arrows. The SVG spans the element's box and
   the geometry is baked into the viewBox, so resizing the box scales
   the drawing with it, stroke included. */
.wb-ink{display:block;width:100%;height:100%;overflow:visible;}
.wb-ink path{fill:none;stroke-linecap:round;stroke-linejoin:round;}
/* Only the stroke is clickable. The bounding box of a diagonal arrow is
   mostly empty space, and making that space swallow clicks would mean a
   long arrow covers everything it crosses. */
.wb-item[data-kind="draw"],.wb-item[data-kind="arrow"]{pointer-events:none;}
.wb-ink .hit{stroke:transparent;pointer-events:stroke;}
.wb-item[data-kind="draw"] .wb-h,.wb-item[data-kind="arrow"] .wb-h{pointer-events:auto;}

/* Selection chrome. The widths divide by the zoom so a 2px ring is
   2px on screen whether the board is at 15% or 400%. */
.wb-item.sel{box-shadow:0 0 0 calc(2px / var(--k,1)) var(--wb-sel);}
.wb-h{
  position:absolute;width:calc(11px / var(--k,1));height:calc(11px / var(--k,1));
  background:var(--wb-paper);border:calc(2px / var(--k,1)) solid var(--wb-sel);
  border-radius:calc(3px / var(--k,1));display:none;z-index:2;
}
.wb-item.one .wb-h{display:block;}
.wb-h.nw{top:calc(-6px / var(--k,1));left:calc(-6px / var(--k,1));cursor:nwse-resize;}
.wb-h.ne{top:calc(-6px / var(--k,1));right:calc(-6px / var(--k,1));cursor:nesw-resize;}
.wb-h.sw{bottom:calc(-6px / var(--k,1));left:calc(-6px / var(--k,1));cursor:nesw-resize;}
.wb-h.se{bottom:calc(-6px / var(--k,1));right:calc(-6px / var(--k,1));cursor:nwse-resize;}

.wb-marquee{
  position:absolute;z-index:5;pointer-events:none;
  border:calc(1.5px / var(--k,1)) dashed var(--wb-sel);
  background:color-mix(in srgb,var(--wb-sel) 12%,transparent);
}

/* The stroke being drawn right now. It lives in one oversized SVG
   pinned to the world's origin so its user units *are* world units —
   no bounding box to compute until the pen comes up. */
.wb-draft{
  position:absolute;left:-20000px;top:-20000px;
  width:40000px;height:40000px;z-index:50;pointer-events:none;
}
.wb-draft path{fill:none;stroke-linecap:round;stroke-linejoin:round;}

/* Other people. Rendered inside the world so a cursor sits on the
   *content* it is pointing at, then counter-scaled so it never grows. */
.wb-cursors{position:absolute;top:0;left:0;z-index:60;pointer-events:none;}
.wb-cursor{
  position:absolute;transform:scale(calc(1 / var(--k,1)));transform-origin:0 0;
  display:flex;align-items:flex-start;gap:2px;white-space:nowrap;
  transition:top .09s linear,left .09s linear;
}
.wb-cursor svg{
  width:19px;height:19px;flex:0 0 19px;
  fill:hsl(var(--hue) 85% 62%);stroke:var(--bg);stroke-width:1.2;
}
.wb-cursor span{
  margin-top:11px;font-size:.68rem;font-weight:800;color:#0B0F16;
  background:hsl(var(--hue) 85% 62%);padding:2px 7px;border-radius:var(--r-pill);
}

.wb-hint{
  position:absolute;inset:0;display:grid;place-content:center;
  text-align:center;color:rgba(16,22,32,.45);font-size:.84rem;pointer-events:none;
  padding:20px;
}
.wb-hint b{display:block;font-weight:900;color:rgba(16,22,32,.7);margin-bottom:5px;font-size:.95rem;}
.wb-legend{margin-top:8px;font-size:.71rem;color:var(--ink-3);line-height:1.9;}

/* Lightbox for a whiteboard picture */
.lightbox{
  position:fixed;inset:0;z-index:130;background:var(--veil-strong);
  display:grid;place-items:center;padding:24px;cursor:zoom-out;
  animation:fadeIn .18s var(--ease);
}
.lightbox img{max-width:100%;max-height:88vh;width:auto;border-radius:var(--r);box-shadow:var(--shadow-2);}

/* Theme switch in the topbar. The button shows the theme it will
   switch *to*, not the one you are in — a moon while you are already
   dark reads as a state badge and gets ignored. */
.theme-toggle svg{width:18px;height:18px;}
.theme-toggle .ico-moon{display:none;}
:root[data-theme="light"] .theme-toggle .ico-sun{display:none;}
:root[data-theme="light"] .theme-toggle .ico-moon{display:block;}

/* الشرح. Carries its label on a desktop and drops to the icon alone on
   a phone, where the topbar has the burger, the title and two other
   controls in it already. */
.help-btn{text-decoration:none;flex:0 0 auto;}
@media (max-width:600px){
  .help-btn span{display:none;}
  .help-btn{padding:.5em;}
}

/* Everything that puts ink ON the brand — the gradient buttons, the
   project badge, the sidebar count — now reads --ink-on-grad, and the
   flip to white happens once, in tokens.css. It used to be only the
   sidebar badge that flipped, on the theory that the gradient stayed
   light enough to carry dark ink; in the light theme --brand and --brand-2
   are both darkened so they work as text, and near-black on them lands
   around 2.4:1. */

/* Permissions grid (admin manager) */
.perm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:9px;}
.perm-item{
  display:flex;align-items:center;gap:9px;padding:9px 12px;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);
  cursor:pointer;font-size:.83rem;transition:border-color .18s,background .18s;
}
.perm-item:hover{border-color:var(--line-2);}
.perm-item:has(input:checked){border-color:var(--line-accent);background:rgba(161,249,176,.055);}

/* A permission that reaches outside the dashboard — it mails a real
   inbox rather than unlocking a screen — so it sits below the grid at
   full width, tinted, and carries its own explanation. */
.perm-item-hot{
  align-items:flex-start;gap:11px;padding:13px 14px;
  border-color:var(--line-accent);
  background:linear-gradient(135deg,rgba(53,221,212,.07),rgba(161,249,176,.05));
}
.perm-item-hot input{margin-top:2px;flex:0 0 auto;}
.perm-item-hot > span{display:flex;flex-direction:column;gap:4px;}
.perm-item-hot b{font-weight:800;}
.perm-item-hot i{font-style:normal;font-size:.76rem;color:var(--ink-3);line-height:1.6;}
.perm-item-hot:has(input:checked){
  border-color:var(--brand-2);
  background:linear-gradient(135deg,rgba(53,221,212,.14),rgba(161,249,176,.08));
}

/* Avatar picker */
.avatar-pick{display:flex;gap:10px;flex-wrap:wrap;}
.avatar-pick label{cursor:pointer;position:relative;}
.avatar-pick input{position:absolute;opacity:0;pointer-events:none;}
.avatar-pick .opt{
  width:50px;height:50px;border-radius:50%;overflow:hidden;
  border:2px solid transparent;transition:border-color .2s,transform .25s var(--spring);
  display:grid;place-items:center;background:var(--surface-2);
}
.avatar-pick input:checked + .opt{border-color:var(--brand);transform:scale(1.06);}
.avatar-pick .opt img{width:100%;height:100%;object-fit:cover;}

/* "Upload" tile — sits at the end of the picker, same footprint as a
   choice but reads as an action. */
.opt-upload{
  width:50px;height:50px;border-radius:50%;cursor:pointer;
  background:var(--surface-2);border:1.5px dashed var(--line-2);color:var(--ink-2);
  display:grid;place-items:center;gap:0;line-height:1;
  transition:border-color .2s,color .2s,background .2s;
}
.opt-upload:hover{border-color:var(--brand);color:var(--brand);background:rgba(161,249,176,.06);}
.opt-upload svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;}

/* ── Avatar cropper ─────────────────────────────────────── */
.cropper{display:flex;flex-direction:column;gap:14px;align-items:center;}
/* 264px is not arbitrary and must not become fluid: cropper.js maps
   window pixels onto source pixels using the same constant. */
.crop-window{
  position:relative;width:264px;height:264px;
  overflow:hidden;border-radius:var(--r);background:var(--bg);
  cursor:grab;touch-action:none;user-select:none;
}
.crop-window.dragging{cursor:grabbing;}
.crop-window img{position:absolute;max-width:none;pointer-events:none;-webkit-user-drag:none;}
/* Circular guide: the stored image stays square, this only previews
   how every avatar slot will mask it — so the hole has to be the
   window's inscribed circle, no smaller. `closest-side` is what makes
   it one: an unsized radial-gradient measures its stops against the
   farthest *corner*, which drew the guide at 1/√2 of the real avatar
   and made every crop look tighter than the one that got saved. */
.crop-ring{
  position:absolute;inset:0;pointer-events:none;border-radius:var(--r);
  box-shadow:0 0 0 999px rgba(16,20,28,.55) inset;
  -webkit-mask:radial-gradient(circle closest-side at 50% 50%, transparent 0 99%, #000 100%);
          mask:radial-gradient(circle closest-side at 50% 50%, transparent 0 99%, #000 100%);
}
.crop-controls{display:flex;align-items:center;gap:12px;width:264px;}
.crop-zoom-ico{color:var(--ink-3);font-size:1rem;font-weight:900;width:12px;text-align:center;}
.crop-controls input[type=range]{flex:1;accent-color:var(--brand);height:22px;cursor:pointer;}

/* Templates gallery */
.tpl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:var(--gap);}
.tpl-card{
  background:var(--surface-1);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:19px;display:flex;flex-direction:column;gap:9px;cursor:pointer;
  transition:border-color .22s,transform .3s var(--ease);
}
.tpl-card:hover{border-color:var(--line-accent);transform:translateY(-2px);}
.tpl-card .ico{
  width:40px;height:40px;border-radius:var(--r);display:grid;place-items:center;
  background:rgba(53,221,212,.1);margin-bottom:3px;
}
.tpl-card .ico svg{width:20px;height:20px;stroke:var(--brand-2);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;}
.tpl-card h4{font-size:.92rem;font-weight:900;}
.tpl-card p{font-size:.79rem;color:var(--ink-3);line-height:1.6;}

/* Document preview (printable) */
.doc{
  background:#fff;color:#12161d;border-radius:var(--r);padding:44px 40px;
  font-size:14px;line-height:1.85;
}
.doc h1{font-size:22px;font-weight:900;margin-bottom:4px;color:#0d1117;}
.doc .doc-sub{color:#5a6472;font-size:12px;margin-bottom:22px;}
.doc h2{font-size:15px;font-weight:900;margin:22px 0 7px;padding-bottom:5px;border-bottom:1.5px solid #d9e0e8;}
.doc p{margin-bottom:9px;white-space:pre-wrap;}
.doc table{width:100%;min-width:0;font-size:13px;margin:10px 0;}
.doc th,.doc td{border:1px solid #d9e0e8;padding:7px 10px;background:none;color:inherit;}
.doc th{background:#f1f5f8;color:#3d4757;font-size:12px;}
.doc tbody tr:hover{background:none;}
.doc .doc-head{display:flex;align-items:center;gap:14px;margin-bottom:22px;padding-bottom:16px;border-bottom:2px solid #0f1620;}
.doc .doc-head img{height:34px;width:auto;}
.doc .kv{display:grid;grid-template-columns:150px 1fr;gap:5px 14px;font-size:13px;}
.doc .kv dt{font-weight:900;color:#3d4757;}
.doc .totals{margin-inline-start:auto;width:min(300px,100%);}
.doc .totals .row{display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid #e5eaf0;}
.doc .totals .row.grand{font-weight:900;font-size:16px;border-bottom:0;border-top:2px solid #0f1620;margin-top:5px;padding-top:9px;}
.doc .sign{display:grid;grid-template-columns:1fr 1fr;gap:34px;margin-top:44px;}
.doc .sign div{border-top:1.5px solid #98a3b1;padding-top:7px;font-size:12px;color:#5a6472;}

/* ── Tax-invoice furniture ──────────────────────────────────
   The document body is deliberately light-on-dark-proof: .doc paints
   its own white background and its own near-black ink, so a tax
   invoice comes out identical whichever theme the admin is using and
   whatever the printer decides about backgrounds. Same reasoning as
   the whiteboard's paper — see the --wb-* note in tokens.css. */

/* Seller and buyer, side by side. An auditor reads this block first. */
.doc .parties{display:grid;grid-template-columns:1fr 1fr;gap:26px;margin-bottom:6px;}
.doc .parties h3{
  font-size:11px;font-weight:900;color:#5a6472;letter-spacing:.04em;
  text-transform:uppercase;margin-bottom:5px;
}
.doc .parties .p-name{font-weight:900;font-size:14px;margin-bottom:2px;}
.doc .parties .p-line{font-size:12px;color:#3d4757;line-height:1.7;}

/* The QR sits in the header beside the number, which is where every
   Saudi receipt puts it and therefore where a phone looks. */
.doc .doc-qr{flex:0 0 auto;line-height:0;}
.doc .doc-qr svg{display:block;border:1px solid #e5eaf0;border-radius:4px;}

/* The type code (388 / 381) next to the Arabic name — the name is for
   the reader, the code is for whatever machine reads it next. */
.doc .doc-code{
  font-size:11px;font-weight:700;color:#5a6472;background:#f1f5f8;
  border-radius:4px;padding:2px 7px;margin-inline-start:8px;vertical-align:middle;
}
.doc .doc-foot{
  margin-top:26px;padding-top:10px;border-top:1px solid #e5eaf0;
  font-size:10.5px;color:#8a94a3;word-break:break-all;
}

/* An unissued document must never be mistakable for an issued one —
   it carries a VAT amount nobody has recorded owing. Banner for the
   reader, diagonal wash for anyone glancing at a printout. */
.doc.is-draft{position:relative;}
.doc .doc-draft{
  background:#fdecec;color:#a12626;border:1px solid #f3bcbc;
  border-radius:6px;padding:8px 12px;margin-bottom:18px;
  font-size:12px;font-weight:900;text-align:center;
}
.doc.is-draft::after{
  content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  background:repeating-linear-gradient(45deg,
    rgba(161,38,38,.045) 0 22px, transparent 22px 44px);
}

/* Live totals under the line editor */
.items-total{margin-top:11px;font-size:.85rem;color:var(--ink-2);}

/* Inline banner — a gap in the number series, or a blocked issue */
.notice{
  display:flex;gap:11px;align-items:flex-start;
  border:1px solid var(--line);border-radius:var(--r);
  padding:13px 15px;font-size:.84rem;line-height:1.75;color:var(--ink-2);
  background:var(--surface-2);
}
.notice svg{width:18px;height:18px;flex:0 0 18px;margin-top:2px;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.notice.warn{border-color:rgba(245,200,107,.4);background:rgba(245,200,107,.08);}
.notice.warn svg{stroke:var(--warn);}
.notice.danger{border-color:rgba(255,122,122,.4);background:rgba(255,122,122,.08);}
.notice.danger svg{stroke:var(--danger);}
.notice b{color:var(--ink-1);}

/* A stat tile that is counting something bad. Overrides the gradient
   spine every other tile carries. */
.stat.is-warn::after{background:var(--warn);opacity:1;}

/* إصدار is the one-way door, so its button is the one green control
   in a row of neutral ones. */
.btn-icon.ok{color:var(--ok);}

/* Line-items editor */
.items-table{width:100%;font-size:.84rem;min-width:0;}
.items-table th{padding:7px 9px;font-size:.72rem;}
.items-table td{padding:5px 4px;border-bottom:0;}
.items-table .input{padding:.42em .6em;font-size:.83rem;}
.items-table tbody tr:hover{background:none;}

/* Calculator */
.calc-grid{display:grid;grid-template-columns:minmax(280px,380px) 1fr;gap:var(--gap);align-items:start;}
.split-rows{display:flex;flex-direction:column;gap:9px;}
.split-row{
  display:grid;grid-template-columns:1fr 92px 34px;gap:9px;align-items:center;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);padding:8px 10px;
}
.payout{
  display:flex;align-items:center;gap:13px;padding:13px 16px;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);margin-bottom:9px;
}
.payout .who{flex:1;font-weight:700;font-size:.9rem;}
.payout .pct{font-size:.76rem;color:var(--ink-3);}
.payout .amt{font-size:1.05rem;font-weight:900;font-variant-numeric:tabular-nums;color:var(--brand);}

/* Public booking form page */
.pub{
  min-height:100vh;min-height:100dvh;display:grid;place-items:center;position:relative;z-index:1;
  padding:clamp(18px,4vw,34px) clamp(14px,3vw,20px);
  padding-bottom:calc(clamp(18px,4vw,34px) + env(safe-area-inset-bottom));
}
.pub-card{width:min(660px,100%);background:var(--surface-1);border:1px solid var(--line);
  border-radius:var(--r-lg);padding:clamp(26px,4vw,42px);box-shadow:var(--shadow-2);}
.pub-card .brand{display:flex;align-items:center;gap:11px;margin-bottom:22px;}
.pub-card .brand img{height:32px;width:auto;}
.pub-card h1{font-size:1.5rem;font-weight:900;line-height:1.4;margin-bottom:6px;}
.pub-card .lede{color:var(--ink-3);font-size:.88rem;margin-bottom:26px;}
.pub-done{text-align:center;padding:34px 10px;}
.pub-done .tick{width:62px;height:62px;border-radius:50%;background:rgba(161,249,176,.12);
  display:grid;place-items:center;margin:0 auto 18px;}
.pub-done .tick svg{width:30px;height:30px;stroke:var(--brand);fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;}
.pub-done h2{font-size:1.2rem;font-weight:900;margin-bottom:7px;}
.pub-done h2:focus{outline:none;}
.pub-done p{color:var(--ink-3);font-size:.88rem;}

/* The way back to the site. It sits on the panel's own lower edge —
   the bar is the base of the card, not a widget floating under the
   text — so the countdown reads as part of the confirmation. */
.pub-back{
  margin-top:26px;padding-top:18px;border-top:1px solid var(--line);
  display:flex;flex-direction:column;align-items:center;gap:12px;
}
.pub-back-note{color:var(--ink-3);font-size:.78rem;}
.pub-bar{
  display:block;width:min(240px,80%);height:4px;border-radius:var(--r-pill);
  background:var(--line-2);overflow:hidden;
}
.pub-bar i{
  display:block;height:100%;border-radius:inherit;background:var(--grad);
  /* RTL: the fill grows from the reading start, which is the right
     edge. transform-origin has no logical keyword, and this is a real
     physical axis — the same exception the canvas code makes. */
  transform:scaleX(0);transform-origin:right center;
}
.pub-bar.run i{animation:pubBack var(--dur,4000ms) linear forwards;}
@keyframes pubBack{to{transform:scaleX(1);}}

/* A date or time field opens from anywhere inside it — picker.js does
   the opening, this says so before the click. */
input[type=date],input[type=time],input[type=datetime-local],input[type=month]{cursor:pointer;}
.pub-note{color:var(--ink-3);font-size:.76rem;text-align:center;margin-top:11px;line-height:1.6;}

/* Section heading inside a long public form. A quiet rule rather than a
   numbered step: four short sections don't need a progress metaphor. */
.step{
  font-size:.78rem;font-weight:700;color:var(--ink-3);letter-spacing:.04em;
  margin:26px 0 12px;padding-bottom:9px;border-bottom:1px solid var(--line);
}
.step:first-child{margin-top:0;}

/* Radio cards (package chooser) */
.pick{display:grid;gap:11px;margin-bottom:4px;}
.pick-card{
  position:relative;display:block;cursor:pointer;
  background:var(--surface-2);border:1px solid var(--line-2);border-radius:var(--r);
  padding:15px 17px;transition:border-color .2s var(--ease),background .2s var(--ease);
}
.pick-card:hover{border-color:var(--line-accent);}
/* The input drives :has() below and stays reachable by keyboard, so it is
   moved out of sight rather than hidden — display:none would drop it from
   the tab order and kill arrow-key selection within the group. */
.pick-card input{position:absolute;opacity:0;width:0;height:0;}
.pick-card:has(input:checked){border-color:var(--brand);background:var(--surface-3);}
.pick-card:has(input:focus-visible){box-shadow:0 0 0 3px rgba(53,221,212,.18);}
.pick-body{display:flex;flex-direction:column;gap:6px;padding-inline-start:26px;}
/* Radio dot, drawn rather than native: the native control cannot be tinted
   to the brand mint in every browser we ship to. */
.pick-body::before{
  content:"";position:absolute;inset-inline-start:17px;top:19px;
  width:15px;height:15px;border-radius:50%;border:1.6px solid var(--line-2);
  transition:border-color .2s,box-shadow .2s;
}
.pick-card:has(input:checked) .pick-body::before{
  border-color:var(--brand);box-shadow:inset 0 0 0 4px var(--brand);
}
.pick-name{font-weight:900;font-size:.95rem;line-height:1.45;}
.pick-desc{color:var(--ink-2);font-size:.83rem;line-height:1.75;}
.pick-rate{color:var(--ink-3);font-size:.76rem;font-weight:700;}
.pick-tag{
  display:inline-block;margin-inline-start:8px;vertical-align:middle;
  font-size:.67rem;font-weight:700;padding:2px 8px;border-radius:var(--r-pill);
  background:rgba(161,249,176,.13);color:var(--brand);
}

/* Jazeel treats */
.jz{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-lg);padding:17px;}
.jz-head{display:flex;align-items:center;gap:14px;margin-bottom:15px;}
/* The wordmark master is near-black; assets/jazeel.webp is the same art
   re-filled white so it reads on this panel. */
.jz-logo{height:34px;width:auto;flex:0 0 auto;opacity:.92;}
.jz-head p{color:var(--ink-2);font-size:.82rem;line-height:1.7;}
.jz-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:11px;}
.treat{
  background:var(--surface-1);border:1px solid var(--line);border-radius:var(--r);
  padding:9px;text-align:center;transition:border-color .2s var(--ease);
}
.treat.on{border-color:var(--brand);}
/* The product shots are square and already flattened onto a warm off-white,
   so they can sit edge to edge and read as one shoot. */
.treat img{
  width:100%;height:auto;aspect-ratio:1;object-fit:cover;
  border-radius:var(--r-sm);display:block;margin-bottom:9px;
}
.treat-name{display:block;font-size:.8rem;font-weight:700;line-height:1.45;margin-bottom:9px;}
.qty{display:flex;align-items:center;justify-content:center;gap:4px;}
.qty-btn{
  width:28px;height:28px;flex:0 0 28px;border-radius:var(--r-sm);cursor:pointer;
  background:var(--surface-3);border:1px solid var(--line-2);color:var(--ink-2);
  font-size:1rem;line-height:1;display:grid;place-items:center;
  transition:background .18s,color .18s,border-color .18s;
}
.qty-btn:hover{background:var(--surface-2);color:var(--ink);border-color:var(--line-accent);}
.qty-n{min-width:26px;font-size:.9rem;font-weight:900;font-variant-numeric:tabular-nums;}

/* ═════════════ الرئيسية ═════════════
   A brand bar, a clock, and two lists. The bar is the only place in the
   dashboard that spends space on the brand, which is the point of it —
   the client asked for a designed strip that says سطر, not another
   card. Everything under it is a doorway, so the rows are buttons and
   they all look pressable. */
.hero-bar{
  position:relative;overflow:hidden;
  display:flex;align-items:center;gap:18px;flex-wrap:wrap;
  border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surface-1);box-shadow:var(--shadow-1);
  padding:22px 24px;margin-bottom:var(--gap);
}
/* The brand wash. A gradient at 7% rather than a tinted surface token:
   it has to read as the same mint→teal on both themes, and the light
   theme's --brand is a different colour by design. */
.hero-bar::before{
  content:"";position:absolute;inset:0;background:var(--grad);
  opacity:.07;pointer-events:none;
}
.hero-bar::after{
  content:"";position:absolute;inset-block:0;inset-inline-start:0;
  width:4px;background:var(--grad);
}
/* The logo art *is* the wordmark — it spells سطر in the brand gradient —
   so the strip carries it once and spends the words on the person
   reading it instead of repeating the name beside its own drawing. */
.hero-brand{display:flex;align-items:center;gap:14px;position:relative;min-width:0;}
.hero-brand img{height:46px;width:auto;flex:0 0 auto;}
.hero-brand div{display:flex;flex-direction:column;gap:3px;min-width:0;}
.hero-brand b{font-size:1.12rem;font-weight:900;line-height:1.3;}
.hero-brand span{font-size:.78rem;color:var(--ink-3);letter-spacing:.02em;}

.hero-clock{margin-inline-start:auto;text-align:end;position:relative;}
/* Tabular numerals, or the whole strip twitches every time a minute
   rolls over from 1 to 11. */
.hc-time{font-size:1.7rem;font-weight:900;line-height:1.2;font-variant-numeric:tabular-nums;}
.hc-date{font-size:.78rem;color:var(--ink-3);}

.home-grid{display:grid;grid-template-columns:1.05fr 1fr;gap:var(--gap);align-items:start;}
.home-list{display:flex;flex-direction:column;gap:8px;}

.home-row{
  display:flex;align-items:center;gap:11px;width:100%;text-align:start;
  padding:11px 13px;border-radius:var(--r);cursor:pointer;
  background:var(--surface-2);border:1px solid transparent;color:var(--ink);
  transition:background .18s var(--ease),border-color .18s var(--ease),transform .18s var(--ease);
}
.home-row:hover{background:var(--surface-3);border-color:var(--line-2);}
.home-row:active{transform:scale(.99);}
/* The stage colour, carried the same way the board carries it: the row
   is handed --stage and nothing in here knows a hex value. */
.hr-spine{
  flex:0 0 3px;align-self:stretch;min-height:26px;
  border-radius:var(--r-pill);background:var(--stage,var(--brand));
}
.hr-ico{flex:0 0 30px;height:30px;border-radius:var(--r-sm);display:grid;place-items:center;background:var(--surface-1);}
.hr-ico svg{width:16px;height:16px;stroke:var(--ink-2);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;}
.home-row.warn .hr-ico svg{stroke:var(--warn);}
.home-row.danger .hr-ico svg{stroke:var(--danger);}
.hr-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;}
/* One line, always. A wrapping title turns a scannable list into a
   paragraph, which is the thing this screen exists to avoid. */
.hr-body b{font-size:.87rem;font-weight:700;line-height:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.hr-sub{font-size:.73rem;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.home-row .chip{flex:0 0 auto;}
.home-row .chip svg{width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2;}

.home-more{font-size:.75rem;color:var(--ink-3);padding:9px 3px 0;}
.home-all{margin-top:12px;}
.home-none{font-size:.82rem;color:var(--ink-3);line-height:1.8;}
/* The empty state inside a card already sits on a border and a
   background — a second dashed box around it reads as a broken panel. */
.home-grid .empty{border:0;padding:26px 12px;}

/* ═════════════ المخطط الزمني ═════════════
   A Gantt chart, right to left, at three zoom levels.

   `--px` (pixels per day) and `--gt-w` (the axis's full width) come
   from JS and are the only two numbers the layout needs: every bar and
   every header cell is placed with `--o` (offset) and `--w` (length),
   both already multiplied out. Nothing in this file does date
   arithmetic and nothing in the module does layout.

   The label column is `position:sticky` at the inline start, which in
   this RTL document is the physical right edge — so the task names
   stay put while the months scroll under them, on a phone as much as
   on a desk. */

.gt-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--gap);}
.gt-card{
  background:var(--surface-1);border:1px solid var(--line);border-radius:var(--r);
  padding:16px 17px;cursor:pointer;display:flex;flex-direction:column;gap:9px;
  transition:border-color .18s var(--ease),transform .18s var(--ease);
}
@media (hover:hover){ .gt-card:hover{border-color:var(--line-accent);transform:translateY(-2px);} }
.gt-card-head{display:flex;align-items:center;gap:9px;}
.gt-card-head h4{font-size:.95rem;font-weight:800;flex:1;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
/* The legend as a colour strip: the fastest read of "what kind of work
   is in here" without opening it. */
.gt-card-strip{display:flex;gap:3px;height:6px;}
.gt-card-strip i{flex:1;border-radius:var(--r-pill);}
.gt-card-foot{display:flex;align-items:center;gap:9px;font-size:.74rem;color:var(--ink-3);}

/* ── Chart head ── */
.gt-top{display:flex;align-items:flex-start;gap:var(--gap);flex-wrap:wrap;margin-bottom:14px;}
.gt-title{flex:1;min-width:220px;}
.gt-title h2{font-family:var(--font-display);font-size:1.4rem;font-weight:900;line-height:1.4;}
.gt-title p{font-size:.85rem;color:var(--ink-2);line-height:1.7;}
.gt-span{display:grid;grid-template-columns:auto auto;gap:3px 12px;font-size:.78rem;align-items:baseline;}
.gt-span dt{color:var(--ink-3);}
.gt-span dd{font-weight:700;}

/* The figure strip from the reference sheets — «٧٤ يوماً · إجمالي
   المشروع». Free text on both halves on purpose: what a project counts
   is not a fixed set of fields. */
.gt-stats{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px;}
.gt-stat{
  flex:1 1 120px;background:var(--surface-1);border:1px solid var(--line);
  border-radius:var(--r);padding:11px 14px;text-align:center;
}
.gt-stat .v{font-size:1.15rem;font-weight:900;line-height:1.4;}
.gt-stat .k{font-size:.72rem;color:var(--ink-3);line-height:1.6;}

/* ── The grid ── */
.gt-wrap{
  /* Its own scroll box in BOTH axes, and the vertical one is not
     incidental: `position:sticky` resolves against the nearest scroll
     port, so the axis header only stays put while the chart is the
     thing being scrolled. Left to the page scroller, a twenty-row plan
     scrolls its own dates off the top and every bar below the fold is
     then sitting over an axis nobody can see. */
  overflow:auto;max-height:min(74vh,760px);overscroll-behavior:contain;
  background:var(--surface-1);border:1px solid var(--line);border-radius:var(--r);
}
.gt-grid{
  --gt-lbl:230px;
  position:relative;width:calc(var(--gt-lbl) + var(--gt-w));min-width:100%;
}

/* One painted layer for the whole chart: the day columns as a repeating
   gradient, the weekends, and today. A cell per day per row is
   thousands of elements on a quarter, and every one of them would be
   rebuilt on a zoom. `to left` starts the repeat at the physical right
   edge, which is where day zero is in an RTL chart. */
.gt-veil{
  position:absolute;inset-block:0;inset-inline-start:var(--gt-lbl);
  width:var(--gt-w);pointer-events:none;z-index:0;
  background:repeating-linear-gradient(to left,
    var(--line) 0 1px, transparent 1px var(--gt-px));
}
.gt-off{
  position:absolute;inset-block:0;inset-inline-start:var(--gt-o);width:var(--gt-len);
  background:var(--surface-3);opacity:.5;
}
.gt-today{
  position:absolute;inset-block:0;inset-inline-start:var(--gt-o);
  width:2px;margin-inline-start:-1px;background:var(--danger);opacity:.85;
}

.gt-head{
  position:sticky;top:0;z-index:4;display:flex;
  background:var(--surface-2);border-bottom:1px solid var(--line-2);
}
.gt-head .gt-lbl{background:var(--surface-2);font-size:.75rem;color:var(--ink-3);
  font-weight:700;align-self:stretch;justify-content:center;}
.gt-bands{width:var(--gt-w);}
.gt-band{display:flex;}
.gt-band span{
  flex:0 0 auto;width:var(--gt-len);box-sizing:border-box;
  border-inline-start:1px solid var(--line);
  padding:4px 2px;text-align:center;overflow:hidden;white-space:nowrap;
}
.gt-band.a span{font-size:.72rem;font-weight:800;color:var(--ink-2);}
.gt-band.b span{font-size:.68rem;color:var(--ink-3);line-height:1.35;}
.gt-band.b i{display:block;font-style:normal;font-size:.6rem;opacity:.75;}
.gt-band.b span.off{color:var(--ink-3);background:var(--surface-3);}

.gt-row{display:flex;border-bottom:1px solid var(--line);min-height:34px;}
.gt-row:last-child{border-bottom:0;}
.gt-grid > .gt-lbl,.gt-row .gt-lbl,.gt-head .gt-lbl{
  flex:0 0 var(--gt-lbl);width:var(--gt-lbl);box-sizing:border-box;
  position:sticky;inset-inline-start:0;z-index:3;
  background:var(--surface-1);border-inline-end:1px solid var(--line-2);
  padding:6px 12px;display:flex;flex-direction:column;justify-content:center;gap:1px;
}
.gt-lbl b{font-size:.82rem;font-weight:700;line-height:1.45;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.gt-lbl .cell-sub{font-size:.68rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.gt-lane{position:relative;flex:0 0 auto;width:var(--gt-w);z-index:1;}

/* A phase heading: a band across the whole chart, whose CONTENT is what
   sticks. Making the band itself sticky puts its text at the far right
   of a chart three screens wide and scrolls it away, which is precisely
   when knowing the phase matters. */
.gt-grouphead{
  background:var(--surface-2);border-block:1px solid var(--line-2);
  position:relative;z-index:3;
}
.gt-gh-in{
  position:sticky;inset-inline-start:0;width:max-content;max-width:100%;
  display:flex;align-items:center;gap:8px;padding:8px 12px;font-size:.78rem;
}
.gt-grouphead b{font-weight:800;color:var(--brand);}

/* ── A bar ── */
.gt-bar{
  position:absolute;inset-inline-start:var(--gt-o);width:var(--gt-len);
  top:50%;transform:translateY(-50%);height:16px;
  padding:0;border:0;background:none;cursor:pointer;
  display:flex;align-items:stretch;
  /* `manipulation`, not `pan-y`: claiming an axis here means the chart
     can only be panned from the gaps between bars. The hold-to-arm in
     the module is what separates a pan from a drag. */
  touch-action:manipulation;user-select:none;-webkit-user-select:none;
}
.gt-fill{
  flex:1;border-radius:5px;background:var(--c,var(--gt-8));
  box-shadow:0 1px 3px rgba(0,0,0,.28);
  transition:filter .15s var(--ease);
}
@media (hover:hover){ .gt-bar:hover .gt-fill{filter:brightness(1.12);} }
.gt-bar:focus-visible{outline:2px solid var(--brand);outline-offset:3px;border-radius:6px;}
.gt-bar.dragging{z-index:5;}
.gt-bar.dragging .gt-fill{filter:brightness(1.2);box-shadow:0 5px 14px -4px rgba(0,0,0,.5);}

/* The resize handles. 12px of grab area on a 16px-tall bar that may be
   30px wide — they overlap the fill rather than sitting beside it,
   because a bar narrower than two handles has to still be draggable. */
.gt-grip{
  position:absolute;inset-block:-4px;width:12px;z-index:2;cursor:ew-resize;
}
.gt-grip{inset-inline-start:-4px;}
.gt-grip.e{inset-inline-start:auto;inset-inline-end:-4px;}

.gt-legend{
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  padding:12px 4px 0;font-size:.75rem;color:var(--ink-2);
}
.gt-legend span{display:inline-flex;align-items:center;gap:6px;}
.gt-legend i{width:14px;height:9px;border-radius:3px;background:var(--ink-3);}
.gt-legend i.off{background:var(--surface-3);border:1px solid var(--line-2);}
.gt-legend i.now{width:3px;height:14px;border-radius:2px;background:var(--danger);}
.gt-note{margin-top:10px;font-size:.75rem;color:var(--ink-3);line-height:1.8;}

/* The colour select, tinted to the colour it names — the row should
   read as the swatch, not as the word for it. */
.gt-colsel{border-inline-start:5px solid var(--c,var(--gt-8));}

/* ── On paper ──
   The printed sheet is a `.doc`, so it already paints its own white and
   its own near-black and comes out identical from either theme — same
   arrangement as the invoices. What is left is to undo the parts of the
   screen chart that only make sense on a screen: the scroller, the
   sticky columns (nothing scrolls on paper, and a sticky element inside
   a rasterised foreignObject is a positioned element with no scroll
   port to stick to) and the dark surfaces. */
.gt-sheet .gt-wrap{overflow:visible;max-height:none;background:none;border:0;border-radius:0;}
.gt-sheet .gt-grid{--gt-lbl:190px;border:1px solid #d9e0e8;border-radius:6px;}
.gt-sheet .gt-head,.gt-sheet .gt-head .gt-lbl,.gt-sheet .gt-grouphead{
  position:static;background:#f1f5f8;border-color:#d9e0e8;color:#3d4757;
}
.gt-sheet .gt-row .gt-lbl{position:static;background:none;border-color:#e5eaf0;}
.gt-sheet .gt-row{border-color:#e5eaf0;min-height:26px;}
.gt-sheet .gt-veil{background:repeating-linear-gradient(to left,
  #eef2f6 0 1px, transparent 1px var(--gt-px));}
.gt-sheet .gt-off{background:#f1f5f8;opacity:1;}
/* Nothing sticks on paper, and `max-width:100%` on a static box inside
   a grid wider than the sheet clips the phase name mid-word. */
.gt-sheet .gt-gh-in{position:static;max-width:none;flex-wrap:wrap;gap:4px 8px;}
/* The weekday under each date is screen affordance. On paper the row
   is 12px tall and the two lines collide; the date alone is enough. */
.gt-sheet .gt-band.b i{display:none;}
.gt-sheet .gt-grouphead b{color:#0f1620;}
.gt-sheet .gt-lbl b{font-size:11px;}
.gt-sheet .gt-lbl .cell-sub,.gt-sheet .gt-band span{color:#5a6472;}
.gt-sheet .gt-bar{height:12px;}
.gt-sheet .gt-fill{box-shadow:none;}
.gt-sheet .gt-legend{color:#3d4757;font-size:10px;padding-top:10px;}
.gt-sheet .gt-note{color:#5a6472;font-size:10px;}
.gt-sheet .gt-stat{background:none;border-color:#d9e0e8;}
.gt-sheet .gt-stat .k{color:#5a6472;}
.gt-sheet .gt-top{margin-bottom:10px;}
.gt-sheet .gt-title h2,.gt-sheet .gt-span dd{color:#0f1620;}
.gt-sheet .gt-grip{display:none;}

/* ═════════════ مخزن الأفكار والروابط ═════════════
   Two views over one collection. Cards for the ideas, because what you
   want off an idea before opening it is its category, its state and how
   much thinking is already in it — none of which is a table cell; and a
   flat row list for the links, because a link is one line of text and a
   picture, and forty of them in a grid is a wall.

   `--idea` is handed down by the card from the category token, so
   nothing below the card knows a colour. */

.idea-filters{margin-bottom:12px;}
.idea-filters:last-of-type{margin-bottom:var(--gap);}
.idea-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;}

.idea-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(272px,1fr));gap:var(--gap);}
.idea-card{
  position:relative;cursor:pointer;text-align:start;
  display:flex;flex-direction:column;gap:9px;
  background:var(--surface-1);border:1px solid var(--line);
  border-radius:var(--r-lg);padding:16px 17px 13px;
  box-shadow:var(--shadow-1);
  transition:transform .22s var(--ease),border-color .22s,box-shadow .22s;
}
/* The category is a spine down the inline start, not a full border: a
   card outlined in its category colour competes with the status chip
   for the same glance. */
.idea-card::before{
  content:"";position:absolute;inset-block:0;inset-inline-start:0;
  width:3px;border-start-start-radius:var(--r-lg);border-end-start-radius:var(--r-lg);
  background:var(--idea);
}
.idea-card:hover{transform:translateY(-2px);border-color:var(--line-2);box-shadow:var(--shadow-2);}
.idea-top{display:flex;align-items:center;gap:8px;}
.idea-kind{
  font-size:.7rem;font-weight:800;letter-spacing:.02em;color:var(--idea);
  display:inline-flex;align-items:center;gap:6px;
}
.idea-kind::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--idea);}
.idea-top .chip{margin-inline-start:auto;}
.idea-title{font-size:.98rem;font-weight:800;line-height:1.55;}
.idea-note{
  color:var(--ink-2);font-size:.83rem;line-height:1.75;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
}
.idea-tags{display:flex;flex-wrap:wrap;gap:5px;}
.idea-tags span{
  font-size:.7rem;font-weight:700;color:var(--ink-3);
  background:var(--surface-2);border-radius:var(--r-pill);padding:1px 8px;
}
.idea-links{display:flex;flex-direction:column;gap:4px;}
.idea-link{
  display:flex;align-items:center;gap:6px;min-width:0;
  font-size:.76rem;color:var(--ink-2);
}
.idea-link svg{
  width:12px;height:12px;flex:0 0 auto;color:var(--idea);
  stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
}
.idea-link b{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.idea-link.more{color:var(--ink-3);font-weight:700;}
.idea-foot{
  display:flex;align-items:center;gap:8px;
  margin-top:2px;padding-top:9px;border-top:1px solid var(--line);
}
.idea-when{font-size:.72rem;color:var(--ink-3);}

/* ── The link list ──
   Also the list inside the idea dialog, at `.compact`. One markup, one
   set of rules: a second rendering of a link row is a second thing to
   keep in step with the first. */
.lk-list{display:flex;flex-direction:column;gap:9px;}
.lk-row{
  display:flex;align-items:flex-start;gap:12px;cursor:pointer;
  background:var(--surface-1);border:1px solid var(--line);
  border-radius:var(--r);padding:10px 12px;
  transition:border-color .18s,background-color .18s;
}
.lk-row:hover{border-color:var(--line-2);background:var(--surface-2);}
.lk-list.compact .lk-row{background:var(--surface-2);cursor:default;}
.lk-thumb{
  flex:0 0 auto;width:92px;aspect-ratio:16/9;overflow:hidden;
  border-radius:var(--r-sm);background:var(--surface-3);
  display:grid;place-items:center;color:var(--idea);
}
.lk-thumb img{width:100%;height:100%;object-fit:cover;display:block;}
.lk-thumb svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.lk-lines{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;}
.lk-title{
  font-size:.88rem;font-weight:700;line-height:1.5;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.lk-site{font-size:.74rem;color:var(--ink-3);}
.lk-note{font-size:.78rem;color:var(--ink-2);line-height:1.6;}
.lk-from{
  display:inline-flex;align-items:center;gap:5px;margin-top:3px;
  font-size:.72rem;font-weight:700;color:var(--idea);
}
.lk-from svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.lk-acts{display:flex;gap:2px;flex:0 0 auto;}
.lk-acts .btn-icon{text-decoration:none;}

/* Inside the dialog the links get their own rule above them, the same
   way the touch log is separated from the lead form. */
.idea-links-box{margin-top:22px;padding-top:18px;border-top:1px solid var(--line);}
.idea-links-box .card-head{margin-bottom:12px;}
.idea-links-box .card-head h4{font-size:.92rem;font-weight:900;flex:1;}

/* ═════════════ RESPONSIVE ═════════════ */

/* ── Tablet landscape and down ── */
@media (max-width:1100px){
  .proj-detail{grid-template-columns:1fr;}
}
@media (max-width:960px){
  .calc-grid{grid-template-columns:1fr;}
  .doc{padding:30px 24px;}
  /* Two lists side by side stop being lists below this — each column is
     narrower than the titles it has to hold on one line. */
  .home-grid{grid-template-columns:1fr;}
}

/* ── Drawer mode: sidebar becomes a hamburger menu ──
   Applies to tablets as well as phones, so the content column keeps a
   comfortable width instead of being squeezed by a permanent sidebar. */
@media (max-width:1024px){
  /* Physical sides, not logical: `transform` is always physical, so an
     inline-end anchor under dir=rtl parked the panel on the left while
     sliding it off to the right. The drawer must sit on the same side as
     the desktop sidebar — right for RTL, left for LTR. */
  .sidebar{
    position:fixed;top:0;bottom:0;right:0;left:auto;z-index:50;
    width:min(300px,86vw);flex-basis:auto;height:100%;
    transform:translateX(100%);
    transition:transform .3s var(--ease),visibility .3s var(--ease);
    visibility:hidden;box-shadow:var(--shadow-2);
    padding-top:env(safe-area-inset-top);
  }
  [dir="ltr"] .sidebar{right:auto;left:0;transform:translateX(-100%);}
  body.nav-open .sidebar{transform:none;visibility:visible;}
  .sidebar-head .nav-close{display:inline-grid;}
  .burger{display:grid;place-items:center;}
  .topbar-logo{display:block;}
  /* bigger hit areas for touch */
  .nav-item{padding:12px 12px;font-size:.94rem;}
  .btn-icon{padding:9px;}
}
/* keep the drawer shut (and scrim gone) once we're back on desktop */
@media (min-width:1025px){
  .scrim{display:none !important;}

  /* ── Collapsing the permanent sidebar ──
     Not the drawer: there is no overlay and no scrim, the column just
     leaves and the view takes the room. One property does both halves
     of that — a negative `margin-inline-start` cancels the sidebar's
     own width, so `.main` grows into it, *and* pushes the panel out
     past the screen edge on the side it lives on. Animating a margin
     relayouts the content column each frame, which is the honest cost
     of the content actually widening; a transform alone would slide
     the panel away and leave a blank column behind it. */
  .sidebar-head .nav-collapse{display:inline-grid;}
  .nav-peek{display:grid;}
  .sidebar{transition:margin-inline-start .32s var(--ease);}
  body.nav-collapsed .sidebar{margin-inline-start:calc(-1 * var(--sidebar-w));}
  /* The handle rides the header row, and the header row already has the
     screen title against that edge. Give the title the tab's width back
     so the two never sit on top of each other. */
  .topbar{transition:padding-inline-start .32s var(--ease);}
  body.nav-collapsed .topbar{padding-inline-start:calc(clamp(16px,2.4vw,30px) + 38px);}
  /* `clip`, not `hidden` or `auto`: it stops the parked sidebar from
     growing the page sideways *without* creating a scroll container,
     which is what the sticky sidebar and sticky topbar inside it need
     in order to keep sticking. */
  .app{overflow-x:clip;}
}

/* ── Phones ── */
@media (max-width:720px){
  .topbar{gap:10px;padding:0 16px;}
  .topbar h2{font-size:1rem;}

  /* One column, and a thumbnail small enough that the title still gets
     most of the row — a 92px picture beside a 40-character title on a
     phone leaves the title two words wide. The filter rows scroll
     rather than wrap for the same reason chan-row does: seven chips
     over three lines push the first card below the fold. */
  .idea-grid{grid-template-columns:1fr;}
  .idea-filters{overflow-x:auto;flex-wrap:nowrap;scrollbar-width:none;padding-bottom:2px;}
  .idea-filters::-webkit-scrollbar{display:none;}
  .idea-filters .chip-btn{flex:0 0 auto;}
  .lk-thumb{width:64px;}
  .lk-row{gap:10px;padding:9px 10px;}

  /* The sticky label column has to give up room before the chart does:
     at 230px it is two thirds of a phone screen and there is nothing
     left to see the bars in. The task name still gets a full line —
     it is the sub-line (duration and dates) that ellipsises, and those
     are on the bar's tooltip and in its editor anyway. */
  .gt-grid{--gt-lbl:132px;}
  .gt-lbl b{font-size:.78rem;}
  .gt-lbl .cell-sub{font-size:.64rem;}
  .gt-stat{flex-basis:96px;padding:9px 10px;}
  .gt-stat .v{font-size:1rem;}
  .gt-legend{gap:11px;font-size:.7rem;}
  .field-row{grid-template-columns:1fr;}
  .page-head{gap:10px;}
  .page-head .titles{min-width:0;flex:1 1 100%;}
  .page-actions{width:100%;}
  .page-actions .btn{flex:1 1 auto;justify-content:center;}
  .search{max-width:none;flex:1 1 100%;}
  .toolbar > .select,.toolbar > .input{flex:1 1 140px;max-width:none !important;}
  .stats-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px;}
  .stat{padding:14px 15px;}
  .stat .v{font-size:1.45rem;}
  /* The clock keeps its line rather than dropping under the wordmark:
     the brand and the time are the two halves of the strip the client
     asked for, and stacking them costs a third of a phone screen. */
  .hero-bar{padding:15px 16px;gap:10px;}
  .hero-brand{gap:11px;}
  .hero-brand img{height:34px;}
  .hero-brand b{font-size:.95rem;}
  .hero-brand span{font-size:.72rem;}
  .hc-time{font-size:1.2rem;}
  .hc-date{font-size:.7rem;}
  .tpl-grid{grid-template-columns:1fr;}
  /* Columns stop stretching and start swiping: a fifth of a phone
     screen each would make every card a word wide. The column is a
     little narrower than the screen on purpose — the sliver of the
     next one is what says "there is more that way". */
  .kanban{grid-auto-columns:min(84vw,330px);align-items:start;}
  /* Snapping only where a column *is* the viewport. On a desktop board
     it just fights free scrolling, and it drags the view back to the
     nearest column every time a drag ends. */
  .board-wrap{scroll-snap-type:x proximity;scroll-padding-inline-start:4px;}
  .k-col{min-height:0;}
  /* A thumb needs a target, and an empty stage is where a card is most
     likely headed. */
  .k-drop{min-height:120px;padding:11px;}
  .k-card{padding:14px 15px 13px;}
  .k-card.is-task,.k-card.is-lead{padding-inline-start:18px;}

  /* ── تتبع العملاء on a phone ──
     Logging a contact is three controls in a row on a desktop and three
     stacked rows here; the date and the channel take half the width each
     because neither needs more, and cramming all three onto one line
     leaves the "what happened" box too narrow to read back. */
  .touch-add{gap:10px;}
  .touch-add > .field{flex:1 1 calc(50% - 5px);}
  .touch-add > .field:nth-child(3){flex:1 1 100%;}
  .touch-add .btn{width:100%;min-height:44px;}

  /* ── ورشة الحملة on a phone ──
     The station jump list is the only affordance that makes a
     twelve-section form usable one-handed, so it stays sticky and
     scrollable and its chips grow to a thumb's height. */
  .ws-stops{padding:7px;gap:5px;top:calc(var(--topbar-h) + 2px);}
  .ws-stop{min-height:40px;font-size:.74rem;padding:6px 11px;}
  .ws-station{scroll-margin-top:calc(var(--topbar-h) + 60px);}
  .ws-grid{grid-template-columns:1fr;}
  /* Wraps rather than squeezes: the tag and the meter are two separate
     readings and a 90px progress bar communicates nothing. */
  .ws-bar{flex-wrap:wrap;gap:9px 12px;}
  .ws-bar .ws-meter{width:100%;order:3;}
  .ws-fields{gap:0;}

  .k-pager{
    display:flex;gap:7px;overflow-x:auto;margin-bottom:12px;
    padding-bottom:4px;scrollbar-width:none;
  }
  .k-pager::-webkit-scrollbar{display:none;}
  .k-pager-btn{
    display:inline-flex;align-items:center;gap:6px;flex:0 0 auto;
    border:1px solid var(--line-2);background:var(--surface-1);cursor:pointer;
    color:var(--ink-2);font:inherit;font-size:.76rem;font-weight:700;
    padding:7px 13px;min-height:44px;border-radius:var(--r-pill);white-space:nowrap;
  }
  .k-pager-btn .k-dot{width:8px;height:8px;border-radius:50%;background:var(--stage);flex:0 0 8px;}
  .k-pager-btn .n{color:var(--ink-3);font-size:.72rem;}
  .k-pager-btn:active{background:var(--surface-3);}
  /* Thumb-sized, and no longer hiding behind a hover it will never get. */
  .k-move{opacity:1;padding:7px 14px;min-height:40px;font-size:.76rem;}
  .k-move svg{width:14px;height:14px;}
  .k-foot{gap:9px;}
  /* ── Calendar on a phone ──
     A seventh of a phone screen is about 50px, which fits a day number
     and nothing else — so the chips are swapped for colour dots and the
     cell as a whole opens the day (calendar.js branches on the same
     breakpoint). And a full month of these rows is taller than the
     screen, so the body becomes a three-row window that scrolls, with
     the current week scrolled into view on paint. The weekday header
     stays outside it: it is the only thing naming the columns. */
  .cal-day{min-height:74px;padding:5px 2px;align-items:center;gap:4px;}
  .cal-daynum{justify-content:center;}
  .cal-daynum b{font-size:.76rem;min-width:20px;height:20px;}
  .cal-evs,.cal-more{display:none;}
  .cal-dots{
    position:relative;z-index:1;pointer-events:none;
    display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:3px;
  }
  .cal-dots i{width:7px;height:7px;border-radius:50%;background:var(--cat);}
  .cal-dots .n{font-size:.6rem;font-weight:800;color:var(--ink-3);}
  .cal-body{
    max-height:236px;overflow-y:auto;overscroll-behavior:contain;
    scroll-snap-type:y proximity;-webkit-overflow-scrolling:touch;
  }
  .cal-week{scroll-snap-align:start;}
  .cal-dow span{font-size:.67rem;padding:7px 2px;}
  .cal-head{gap:8px;}
  .cal-title{font-size:.95rem;}
  .cal-legend{font-size:.7rem;gap:10px;}
  .cal-cards{grid-template-columns:1fr;}
  .cat-pick{grid-template-columns:1fr;}
  /* The pills already wrap; they only need to stay thumb-sized while
     doing it, and the emoji squares grow to a real 42px target. */
  .who-pill{font-size:.78rem;padding-inline-end:14px;}
  .emoji-opt{width:42px;height:42px;}
  .emoji-own{flex:1 1 100%;}

  /* The toolbar is the one thing that must not wrap into three rows on
     a phone — it would push the canvas off the screen entirely. */
  .wb-bar{gap:6px;}
  .wb-bar .btn-sm{padding:.4em .7em;font-size:.74rem;}
  .wb-sep{display:none;}
  .wb-legend{display:none;}
  .wb-stage{height:calc(100dvh - 230px);min-height:340px;}
  /* Thumb-sized handles, since a finger has no hover to find them by. */
  .wb-h{width:calc(18px / var(--k,1));height:calc(18px / var(--k,1));}
  .wb-h.nw{top:calc(-9px / var(--k,1));left:calc(-9px / var(--k,1));}
  .wb-h.ne{top:calc(-9px / var(--k,1));right:calc(-9px / var(--k,1));}
  .wb-h.sw{bottom:calc(-9px / var(--k,1));left:calc(-9px / var(--k,1));}
  .wb-h.se{bottom:calc(-9px / var(--k,1));right:calc(-9px / var(--k,1));}
  .tab{padding:9px 12px;font-size:.83rem;}
  .perm-grid{grid-template-columns:1fr 1fr;}
  .bar-row{grid-template-columns:88px 1fr 46px;gap:9px;font-size:.78rem;}
  .split-row{grid-template-columns:1fr 80px 34px;gap:7px;}
  .card{padding:16px;}
  .card-head{flex-wrap:wrap;}
  .card-head .search{flex:1 1 100%;max-width:none !important;}
  .doc{padding:22px 18px;font-size:13px;}
  .doc .kv{grid-template-columns:1fr;gap:2px 0;}
  .doc .kv dd{margin-bottom:6px;}
  .doc .sign{grid-template-columns:1fr;gap:26px;}
  .doc .totals{width:100%;}
  .doc .parties{grid-template-columns:1fr;gap:16px;}
  /* The header wraps on a phone, so the QR drops to its own line
     rather than squeezing the document number to two characters. */
  .doc .doc-head{flex-wrap:wrap;}
  .doc .doc-qr{order:3;width:100%;}
  .doc .doc-qr svg{margin-inline:auto;}
  .toasts{inset-inline:12px;inset-block-end:calc(12px + env(safe-area-inset-bottom));}
  .toast{max-width:none;}
  /* iOS zooms the page when a focused field is under 16px */
  .input,.select,.textarea{font-size:16px;}
  .items-table .input{font-size:15px;}
  .jz-head{flex-wrap:wrap;gap:10px;}
}

/* One treat per row: three columns on a phone leave "شكولاتة بالفواكه"
   wrapping onto three lines and squeeze the stepper below a thumb-sized
   target. Side by side, the shot stays readable and the buttons stay big. */
@media (max-width:560px){
  .jz-grid{grid-template-columns:1fr;gap:9px;}
  .treat{display:grid;grid-template-columns:66px 1fr auto;align-items:center;gap:12px;text-align:start;}
  .treat img{margin-bottom:0;}
  .treat-name{margin-bottom:0;}
}

@media (max-width:520px){
  .page-head h1{font-size:1.15rem;}
  .login{padding:16px;}
  .login-card{padding:30px 22px 24px;}
  /* modal as a bottom sheet — reachable thumbs, no cramped side gutters */
  .modal-back{padding:0;place-items:end stretch;}
  .modal{
    width:100%;max-height:92vh;
    border-radius:var(--r-lg) var(--r-lg) 0 0;border-bottom:0;
    animation:sheetUp .28s var(--ease);
  }
  .modal.wide{width:100%;}
  @keyframes sheetUp{from{transform:translateY(100%)}to{transform:none}}
  .modal-head{padding:16px;}
  .modal-body{padding:16px;}
  .modal-foot{
    flex-direction:column-reverse;padding:14px 16px;
    padding-bottom:calc(14px + env(safe-area-inset-bottom));
  }
  .modal-foot .btn{width:100%;}
  .perm-grid{grid-template-columns:1fr;}
  .avatar-pick .opt,.opt-upload{width:44px;height:44px;}
  table{font-size:.82rem;}
  th,td{padding:10px 11px;}
  /* the printable doc has no scroll wrapper — shrink instead of overflow */
  .doc table{font-size:11.5px;}
  .doc th,.doc td{padding:5px 7px;}
  .doc h1{font-size:19px;}
  .doc .doc-head{gap:10px;}
  /* Label above value — "الموعد" and a full Arabic date do not fit side
     by side at this width without one of them wrapping to three lines. */
  .ev-meta{grid-template-columns:1fr;gap:2px 0;}
  .ev-meta dd{margin-bottom:8px;}
  .timeline .tl-item{grid-template-columns:22px 1fr;gap:10px;}
  .tl-dot{width:22px;height:22px;}
  .tl-item::before{inset-inline-start:10px;}
  .payout{flex-wrap:wrap;gap:8px 12px;}
}

/* ── Respect reduced-motion ── */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important;
  }
  /* Exempt: the redirect bar is a countdown, not decoration. Snapping
     it to full would claim the page is leaving now and then sit there
     for four seconds. It stays linear, and it stays honest. */
  .pub-bar.run i{animation-duration:var(--dur,4000ms) !important;}
}

/* ═════════════ PRINT ═════════════ */
/* The *fallback* print path. A document normally prints as a picture
   of itself — js/printdoc.js — and this block is what an engine that
   refuses to rasterise a foreignObject falls back to. Keep it. */
@media print{
  body{background:#fff;}
  body::before{display:none;}
  .sidebar,.topbar,.scrim,.modal-head,.modal-foot,.toasts,.no-print{display:none !important;}
  .modal-back{position:static;background:none;backdrop-filter:none;padding:0;display:block;}
  .modal{width:100%;max-height:none;border:0;box-shadow:none;background:#fff;animation:none;}
  .modal-body{overflow:visible;padding:0;}
  .doc{border-radius:0;padding:0;}

  /* Browsers drop backgrounds when printing to save ink. For a tax
     invoice that is wrong twice over: the QR code stops being scannable
     without its white quiet zone, and the "مسودة" banner — the only
     thing distinguishing an unissued document from a real one — would
     print as plain black text. */
  .doc,.doc .doc-qr svg,.doc .doc-draft,.doc th,.doc .doc-code{
    -webkit-print-color-adjust:exact;print-color-adjust:exact;
  }
  .doc .doc-qr svg{border-color:#ccc;}

  /* Never split a table row or the totals across two sheets. */
  .doc tr,.doc .totals,.doc .parties{break-inside:avoid;}
  .doc h2{break-after:avoid;}

  @page{margin:16mm;}
}

/* ---------- The mark, in the theme that can actually show it ---------- */
/* The Satr logo is black type plus a red dot, which is the right artwork on
   paper and invisible on --surface-1. There are two files rather than one
   filtered copy because no single filter takes the black half to paper
   without dragging the red half somewhere it should not go.

   Scoped to the three ON-SCREEN instances only. Every other `assets/logo.png`
   in this codebase is inside a document being printed or photographed —
   templates.js, workshop.js, timeline.js, home.js — and those land on white
   paper whatever the dashboard's theme is, so they keep the dark original. */
:root:not([data-theme="light"]) .login-logo,
:root:not([data-theme="light"]) .sidebar-head img,
:root:not([data-theme="light"]) .topbar-logo{
  content:url("../assets/logo-light.png");
}
