/* ==========================================================================
   Kaartdijin Oasis — training management portal

   Loaded after style.css and reuses its design tokens, so the portal inherits
   the brand palette and both themes for free. Everything here is portal chrome
   the public site has no use for.
   ========================================================================== */

.portal{min-height:100vh;background:var(--bg);color:var(--text)}

/* ------------------------------- top bar -------------------------------- */
.pnav{
  position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:18px;
  padding:12px clamp(16px,3vw,28px);
  background:color-mix(in srgb,var(--bg) 92%,transparent);
  backdrop-filter:blur(18px) saturate(150%);
  border-bottom:1px solid var(--line);
}
.pnav__brand{display:flex;align-items:center;gap:12px;flex-shrink:0}
/* No plate and no radius — the mark is supplied once per theme instead, and
   the pair is swapped in style.css. */
.pnav__mark{
  height:67px;width:auto;aspect-ratio:600/652;border-radius:0;display:grid;
  background:none;border:0;
}
.pnav__txt{display:flex;flex-direction:column;line-height:1.2}
.pnav__txt b{font-family:var(--font-display);font-size:.98rem}
.pnav__txt span{font-size:.7rem;letter-spacing:.13em;text-transform:uppercase;color:var(--faint)}
.pnav__actions{display:flex;align-items:center;gap:12px;margin-left:auto}
.pnav__who{display:flex;flex-direction:column;line-height:1.25;text-align:right}
.pnav__who b{font-size:.9rem}
.pnav__who span{font-size:.72rem;color:var(--faint)}
.pnav__out{margin:0}

/* -------------------------------- shell --------------------------------- */
.pshell{display:grid;grid-template-columns:248px minmax(0,1fr);gap:0;align-items:start}
.pside{
  /* Its OWN scroll, not the page's. `min-height` let a tall nav grow the
     sidebar past the viewport, so reaching Fees at the bottom meant scrolling
     the whole document and losing the content beside it. A fixed height plus
     overflow-y is what makes the two panes scroll independently; sticky alone
     never could. overscroll-behavior stops a flick at the end of the nav from
     carrying on into the page behind it. */
  position:sticky;top:69px;display:flex;flex-direction:column;gap:4px;
  padding:22px 16px;border-right:1px solid var(--line);
  height:calc(100vh - 69px);overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:thin;scrollbar-color:var(--line-strong) transparent;
}
.pside::-webkit-scrollbar{width:8px}
.pside::-webkit-scrollbar-thumb{background:var(--line-strong);border-radius:0}
.pside::-webkit-scrollbar-track{background:transparent}
.pside__link{
  display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:0;
  font-size:.92rem;font-weight:600;color:var(--muted);transition:.2s var(--ease);
}
.pside__link:hover{background:var(--surface);color:var(--text)}
.pside__link.is-active{background:var(--surface-2);color:var(--text)}
.pside__soon{
  padding:10px 12px;font-size:.86rem;color:var(--faint);opacity:.65;cursor:default;
}
.pmain{padding:clamp(20px,3vw,34px) clamp(16px,3vw,34px) 60px;min-width:0}

/* ------------------------------- headings ------------------------------- */
.phead-row{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:26px}
.phead-row h1{font-size:clamp(1.5rem,3vw,2.1rem);margin-top:8px}

/* --------------------------------- KPIs --------------------------------- */
.pkpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:16px;margin-bottom:26px}
.pkpi{
  padding:22px;border-radius:var(--radius);border:1px solid var(--line);
  background:var(--surface);display:flex;flex-direction:column;gap:4px;
}
.pkpi--attention{border-color:var(--gold-400);box-shadow:0 0 0 1px color-mix(in srgb,var(--gold-400) 30%,transparent)}
.pkpi__num{font-family:var(--font-display);font-size:2.1rem;font-weight:800;line-height:1}
.pkpi--attention .pkpi__num{color:var(--gold-400)}
.pkpi__label{font-weight:650;font-size:.92rem}
.pkpi__note{font-size:.8rem;color:var(--faint)}

/* -------------------------------- cards --------------------------------- */
.pgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:18px}
.pcard{
  padding:24px;border-radius:var(--radius);border:1px solid var(--line);
  background:var(--surface);margin-bottom:18px;
}
.pcard--narrow{max-width:520px}
.pcard h2{font-size:1.12rem;margin-bottom:14px}
/* `.pempty` is the dashed empty-state PANEL, defined further down. This is the
   inline 'this cell has no value' text — a different thing that shared the name
   and silently lost the cascade. Same shape of bug as .ptab. */
.pnone{color:var(--muted);font-size:.94rem;line-height:1.7}
.pnote{margin-top:12px;font-size:.8rem;color:var(--faint);line-height:1.6}

.plist{display:flex;flex-direction:column;gap:2px}
.plist li{
  display:grid;grid-template-columns:1fr auto;gap:2px 14px;align-items:baseline;
  padding:11px 0;border-bottom:1px solid var(--line);
}
.plist li:last-child{border-bottom:0}
.plist b{font-size:.93rem}
.plist span{font-size:.84rem;color:var(--muted);grid-column:1}
.plist time{font-size:.78rem;color:var(--faint);grid-row:1;grid-column:2}
.plist--plain li{grid-template-columns:120px 1fr}
.plist--plain span{grid-column:2;grid-row:1}

/* ------------------------------- messages ------------------------------- */
.pflash{
  padding:12px 16px;border-radius:0;margin-bottom:18px;font-size:.9rem;
  border:1px solid var(--line-strong);background:var(--surface-2);
}
.pflash--success{border-color:var(--teal-400)}
.pflash--error,.pflash--warning{border-color:var(--gold-400);color:var(--text)}

/* -------------------------------- login ---------------------------------
   One bounded card centred in the viewport, not a full-bleed 50/50 split.
   That is the single decision that makes a sign-in read as a product rather
   than as a web page, and it is what the shadcn pattern gets right.

   The brand panel is first in the source and first in the grid, so it sits on
   the left on a desktop and collapses to a slim band above the form on a phone.
   It is aria-hidden: it says nothing the form does not, and a screen reader
   should reach the username field without wading through decoration.
   ----------------------------------------------------------------------- */
.portal--login{display:grid;place-items:center;min-height:100vh;padding:20px}
.plogin{
  display:grid;grid-template-columns:minmax(0,380px) minmax(0,440px);
  width:min(880px,100%);border-radius:0;overflow:hidden;
  border:1px solid var(--line-strong);background:var(--surface);
  box-shadow:0 24px 70px -30px rgba(0,0,0,.55);
}
/* Three zones: mark, headline pushed to the bottom, standing underneath. */
.plogin__aside{
  position:relative;overflow:hidden;display:flex;flex-direction:column;
  gap:20px;color:#fff;padding:clamp(26px,3vw,36px);
  /* Deep navy, not --grad-brand. The logo is a navy K with a red wordmark on a
     grey ring — it was drawn for a dark ground or a white one, and on a mid-blue
     gradient both the K and the wordmark lose their contrast against it. The
     hues are still the brand scale, but the range matters: the mid-navy first
     attempt (#14356b) is the SAME value as --logo-k in the light theme, so the
     K disappeared into its own background. This is the near-black ground the
     site's own dark theme uses, where the artwork was proven to read. The
     client rejected halos and shadows on the logo, so contrast has to come
     from the ground and nothing else. */
  background:linear-gradient(157deg,#050b18 0%,#0a1e42 58%,#12294f 100%);
}
/* The reference's blurred corner orb, in brand azure rather than white: on a
   navy ground white reads as a smudge, a lit blue reads as depth. */
.plogin__orb{
  position:absolute;top:-100px;right:-100px;width:280px;height:280px;
  border-radius:50%;background:rgba(77,140,232,.42);filter:blur(56px);
  pointer-events:none;
}
.plogin__brand{position:relative;display:block;width:190px}
.plogin__brand svg,.plogin__brand .brand-art{width:100%;height:auto}
.plogin__asideInner{position:relative;margin-top:auto}
.plogin__asideInner h2{
  font-size:clamp(1.45rem,2.4vw,1.85rem);line-height:1.15;margin:0 0 14px;color:#fff;
}
.plogin__proof{
  font-size:.8rem;line-height:1.6;opacity:.85;padding-top:14px;
  border-top:1px solid rgba(255,255,255,.22);
}

.plogin__panel{
  padding:clamp(28px,4vw,40px);background:var(--bg-2);
  display:flex;flex-direction:column;gap:20px;
}
.plogin__top{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
.plogin__panel h1{font-size:1.45rem;margin:0}
.plogin__lede{color:var(--muted);font-size:.88rem;margin-top:4px}
.plogin__form{display:flex;flex-direction:column;gap:14px}
.plogin__form .pfield input{
  width:100%;padding:11px 13px;border:1px solid var(--line-strong);border-radius:0;
  background:var(--bg);color:var(--text);font:inherit;font-size:.92rem;
}
.plogin__form .pfield input:focus{
  outline:0;border-color:var(--teal-400);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--teal-400) 22%,transparent);
}
.plogin__pw{position:relative;display:block}
.plogin__pw input{padding-right:44px}
.plogin__eye{
  position:absolute;top:50%;right:6px;transform:translateY(-50%);
  display:grid;place-items:center;width:32px;height:32px;
  background:none;border:0;cursor:pointer;color:var(--muted);
}
.plogin__eye:hover{color:var(--text)}
.plogin__eye svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.6}
.plogin__eye[aria-pressed="true"] svg{color:var(--teal-400)}
.plogin__submit{width:100%;justify-content:center;padding:12px 18px;font-size:.94rem}
.plogin__err{
  display:flex;align-items:flex-start;gap:9px;padding:11px 13px;
  border:1px solid var(--gold-400);background:color-mix(in srgb,var(--gold-400) 12%,transparent);
  font-size:.86rem;line-height:1.5;
}
.plogin__err svg{
  width:17px;height:17px;flex:0 0 17px;margin-top:1px;
  stroke:var(--gold-400);fill:none;stroke-width:1.8;stroke-linecap:round;
}
.plogin__note{margin-top:auto;font-size:.8rem;color:var(--faint);line-height:1.6}
.plogin__note a{color:var(--teal-300);text-decoration:underline;text-underline-offset:2px}
html[data-theme="light"] .plogin__note a{color:var(--teal-600)}

@media (max-width:820px){
  /* A slim band on a phone: the brand is still present without costing half
     the screen above the fold, and the headline goes rather than shrink. */
  .plogin{grid-template-columns:1fr}
  .plogin__aside{gap:14px;padding:22px 24px}
  .plogin__asideInner h2{display:none}
  .plogin__proof{border-top:0;padding-top:0}
  .plogin__brand{width:150px}
}

/* ------------------------------ responsive ------------------------------ */
@media (max-width:900px){
  .plogin{grid-template-columns:1fr}
  .plogin__aside{display:none}
}
@media (max-width:820px){
  .pshell{grid-template-columns:1fr}
  .pside{
    position:static;min-height:0;flex-direction:row;flex-wrap:wrap;
    border-right:0;border-bottom:1px solid var(--line);padding:12px 16px;
  }
  .pside__soon{display:none}
  .pnav__who{display:none}
}

/* ------------------------- inbox (P2) ---------------------------------- */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
a.pkpi__num{color:inherit;text-decoration:none;display:inline-block}
a.pkpi__num:hover{text-decoration:underline}

.pside__badge{
  margin-left:auto;background:var(--gold-400);color:#1b1206;
  font-size:.7rem;font-weight:800;border-radius:999px;padding:1px 8px;
}
.pback{margin:0 0 10px;font-size:.85rem}
.pback a{color:var(--muted);text-decoration:none}
.pback a:hover{color:var(--text)}

.pfilters{
  display:flex;gap:10px;flex-wrap:wrap;align-items:center;
  margin:0 0 18px;padding:12px;border:1px solid var(--line);
  border-radius:0;background:var(--surface);
}
.pfilters__search{flex:1 1 240px}
.pfilters input,.pfilters select{
  width:100%;padding:9px 12px;border:1px solid var(--line);border-radius:0;
  background:var(--bg);color:var(--text);font:inherit;font-size:.88rem;
}
.pfilters select{min-width:150px}
.pfilters label{display:block}

.ptable-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:0;background:var(--surface)}
.ptable{width:100%;border-collapse:collapse;min-width:720px;font-size:.88rem}
.ptable th,.ptable td{padding:12px 16px;text-align:left;vertical-align:top}
.ptable thead th{
  font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);border-bottom:1px solid var(--line);font-weight:700;
}
.ptable tbody tr+tr td{border-top:1px solid var(--line)}
.ptable tbody tr:hover{background:color-mix(in srgb,var(--text) 4%,transparent)}
.ptable__main{font-weight:700;color:var(--text);text-decoration:none}
.ptable__main:hover{text-decoration:underline}
.ptable__sub{display:block;color:var(--muted);font-size:.8rem}

.pchip{
  display:inline-block;padding:3px 10px;border-radius:999px;
  font-size:.72rem;font-weight:700;border:1px solid var(--line);white-space:nowrap;
}
.pchip--new{border-color:var(--gold-400);color:var(--gold-400)}
.pchip--in_progress{border-color:var(--teal-400);color:var(--teal-400)}
.pchip--converted{background:var(--teal-400);border-color:transparent;color:#06201c}
.pchip--closed{color:var(--muted)}

.ppager{display:flex;gap:12px;align-items:center;justify-content:center;margin:18px 0;font-size:.85rem;color:var(--muted)}

.pdl{display:grid;grid-template-columns:auto 1fr;gap:6px 18px;margin:0 0 14px;font-size:.88rem}
.pdl dt{color:var(--muted)}
.pdl dd{margin:0;font-weight:600;overflow-wrap:anywhere}
.pquote{
  margin:0;padding:14px 16px;border-left:3px solid var(--gold-400);
  border-radius:0 12px 12px 0;background:var(--bg);font-size:.9rem;line-height:1.6;
}
.pform{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin:0 0 14px}
.pform label{flex:1 1 200px;font-size:.78rem;color:var(--muted);display:block}
.pform select,.pform textarea{
  width:100%;margin-top:5px;padding:9px 12px;border:1px solid var(--line);
  border-radius:0;background:var(--bg);color:var(--text);font:inherit;font-size:.88rem;
}
.pform--note{margin-top:16px}
.pform--note label{flex:1 1 100%}

/* The native option list is drawn by the OS, so it does not inherit the
   select's colours — without this it is white-on-white in dark mode. */
.pfilters option,.pform option{background:var(--bg);color:var(--text)}
.pfilters select:focus,.pfilters input:focus,
.pform select:focus,.pform textarea:focus{outline:2px solid var(--teal-400);outline-offset:1px}

/* Below 700px a five-column table can only scroll sideways, which hides the
   status — the one column an admin scans for. Stack each row as a card. */
@media (max-width:700px){
  .ptable-wrap{overflow-x:visible;border:0;border-radius:0;background:none}
  .ptable{min-width:0;display:block}
  .ptable thead{display:none}
  .ptable tbody,.ptable tr,.ptable td{display:block;width:100%}
  .ptable tr{
    border:1px solid var(--line);border-radius:0;background:var(--surface);
    padding:6px 4px;margin-bottom:12px;
  }
  .ptable tbody tr:hover{background:var(--surface)}
  .ptable tbody tr+tr td{border-top:0}
  .ptable td{
    display:flex;gap:14px;align-items:baseline;justify-content:space-between;
    padding:7px 14px;text-align:right;
  }
  .ptable td+td{border-top:1px solid var(--line)}
  .ptable td::before{
    content:attr(data-label);flex:0 0 auto;text-align:left;
    font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;
    color:var(--muted);font-weight:700;
  }
  .ptable td{flex-wrap:wrap}
  .ptable__main{flex:1 1 auto;text-align:right}
  .ptable__sub{flex:1 1 100%;text-align:right}
  .pfilters__search{flex:1 1 100%}
}

/* ------------------------ notifications (P2.1) -------------------------- */
.pbell{position:relative}
.pbell__btn{position:relative}
.pbell__count{
  position:absolute;top:-4px;right:-4px;min-width:18px;height:18px;padding:0 5px;
  display:flex;align-items:center;justify-content:center;
  background:var(--gold-400);color:#1b1206;border-radius:999px;
  font-size:.68rem;font-weight:800;line-height:1;
}
.pbell__count[hidden]{display:none}
.pbell__panel{
  position:absolute;top:calc(100% + 10px);right:0;width:min(340px,calc(100vw - 32px));
  /* --surface is translucent by design; a dropdown floating over KPI tiles has
     to be opaque or the numbers behind it read straight through. */
  background:var(--bg);border:1px solid var(--line-strong);border-radius:0;
  box-shadow:0 24px 60px -20px rgba(0,0,0,.55);z-index:60;overflow:hidden;
}
.pbell__panel[hidden]{display:none}
.pbell__panel header{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:12px 16px;border-bottom:1px solid var(--line);font-size:.85rem;
}
.pbell__panel header a{color:var(--teal-400);text-decoration:none;font-size:.8rem}
.pbell__panel ul{list-style:none;margin:0;padding:0;max-height:min(60vh,380px);overflow-y:auto}
.pbell__panel li+li{border-top:1px solid var(--line)}
.pbell__panel li a{display:block;padding:11px 16px;text-decoration:none;color:var(--text)}
.pbell__panel li a:hover{background:var(--surface-2)}
.pbell__panel li b{display:block;font-size:.85rem}
.pbell__panel li span{display:block;color:var(--muted);font-size:.78rem;margin:2px 0 4px}
.pbell__panel li time{color:var(--muted);font-size:.72rem}
.pbell__panel li.is-unread{border-left:3px solid var(--gold-400)}
.pbell__empty{padding:16px;color:var(--muted);font-size:.85rem}

.pdesk{
  display:flex;gap:16px;align-items:center;flex-wrap:wrap;justify-content:space-between;
  margin:0 0 18px;padding:14px 18px;border:1px dashed var(--line-strong);
  border-radius:0;background:var(--surface);
}
.pdesk p{margin:0;font-size:.86rem;color:var(--muted)}
.pdesk b{color:var(--text)}

.pnotes{list-style:none;margin:0;padding:0;border:1px solid var(--line);border-radius:0;background:var(--surface);overflow:hidden}
.pnotes li+li{border-top:1px solid var(--line)}
.pnote-row{display:grid;grid-template-columns:auto 1fr auto;gap:14px;align-items:baseline;padding:14px 18px}
.pnote-row.is-unread{background:color-mix(in srgb,var(--gold-400) 8%,transparent)}
.pnote-row__kind{
  font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  padding:3px 9px;border-radius:999px;border:1px solid var(--line);color:var(--muted);white-space:nowrap;
}
.pnote-row__kind--enquiry{border-color:var(--gold-400);color:var(--gold-400)}
.pnote-row__kind--application{border-color:var(--teal-400);color:var(--teal-400)}
.pnote-row__main a{color:var(--text);text-decoration:none}
.pnote-row__main a:hover{text-decoration:underline}
.pnote-row__main b{display:block;font-size:.9rem}
.pnote-row__main span{display:block;color:var(--muted);font-size:.82rem;margin-top:2px}
.pnote-row time{color:var(--muted);font-size:.75rem;white-space:nowrap}

@media (max-width:700px){
  .pnote-row{grid-template-columns:1fr;gap:6px}
  .pnote-row time{justify-self:start}
}

/* --------------------------- people (P3) -------------------------------- */
.phead-row__actions{display:flex;gap:10px;flex-wrap:wrap}
.pkpis--tight .pkpi{padding:14px 16px}
.pkpis--tight .pkpi__num{font-size:1.6rem}

.phandover{
  display:grid;grid-template-columns:1.1fr 1fr;gap:24px;align-items:center;
  margin:0 0 22px;padding:20px 24px;border-radius:0;
  border:1px solid var(--gold-400);
  background:color-mix(in srgb,var(--gold-400) 10%,var(--bg));
}
.phandover h2{margin:8px 0 8px;font-size:1.15rem}
.phandover p{margin:0;font-size:.86rem;color:var(--muted);line-height:1.6}
.phandover__creds{display:grid;grid-template-columns:auto 1fr;gap:8px 16px;margin:0;font-size:.86rem}
.phandover__creds dt{color:var(--muted)}
.phandover__creds dd{margin:0;overflow-wrap:anywhere}
.phandover code{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.95rem;
  background:var(--bg);border:1px solid var(--line);border-radius:0;padding:3px 8px;
}
.phandover__pw{font-size:1.05rem;letter-spacing:.04em;font-weight:700}
/* Third grid child, so it lands under the copy — keep it button-sized rather
   than letting it stretch the full column. */
.phandover .js-copy{margin-top:14px;justify-self:start;width:max-content}

.pcard--form{max-width:640px}
.pstack{display:flex;flex-direction:column;gap:14px;margin:0 0 12px}
.pfield{display:block;font-size:.78rem;color:var(--muted)}
.pfield span{display:block;margin-bottom:5px}
.pfield input,.pfield select{
  width:100%;padding:10px 13px;border:1px solid var(--line);border-radius:0;
  background:var(--bg);color:var(--text);font:inherit;font-size:.9rem;
}
.pfield small{display:block;margin-top:5px;font-size:.75rem}
/* The generic width:100% above is for text inputs; a checkbox stretched to the
   full column lands at the far right of the row, miles from its label. */
.pfield input[type="checkbox"],.pfield input[type="radio"]{width:auto;margin:0}
/* CheckboxSelectMultiple renders nested <div>s, not a <ul> — the only child
   div inside a .pfield is that widget, so this is unambiguous. */
.pfield > div{display:flex;flex-wrap:wrap;gap:8px 10px;margin-top:2px}
.pfield > div > div{margin:0}
.pfield > div label{
  display:inline-flex;align-items:center;gap:7px;cursor:pointer;
  padding:7px 13px;border:1px solid var(--line);border-radius:999px;
  font-size:.82rem;color:var(--text);transition:background .2s,border-color .2s,color .2s;
}
.pfield > div label:hover{border-color:var(--line-strong)}
.pfield > div div:has(input:checked) label{
  border-color:transparent;background:var(--grad-brand);color:#fff;font-weight:650;
}
/* A tick-box group is a <fieldset>, not a <label>: a label would claim the
   first checkbox, so clicking the group's own title would toggle whoever is
   first in the list. The browser's default border, padding and legend indent
   have to go or that one field reads as a different form. */
.pfield--choices{border:0;padding:0;margin:0;min-width:0}
.pfield--choices legend{display:block;padding:0;margin-bottom:5px;font-size:.78rem;color:var(--muted)}
.pfield__err{display:block;margin-top:5px;color:var(--gold-400);font-style:normal;font-size:.78rem}
.pform__actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.paccess{display:flex;gap:10px;flex-wrap:wrap;margin:14px 0 0}
.ptable code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.82rem}

@media (max-width:820px){
  .phandover{grid-template-columns:1fr;gap:16px}
}

@media (max-width:700px){
  /* At this width the action buttons steal half the row and squeeze the
     explanation into a four-word-wide column. Stack instead. */
  .phead-row{flex-direction:column;align-items:stretch;gap:14px}
  .phead-row__actions .btn{flex:1 1 auto;justify-content:center}
  .pkpis--tight{grid-template-columns:1fr 1fr;gap:12px}
  .pkpis--tight .pkpi{padding:12px 14px}
  .pkpis--tight .pkpi__label{font-size:.76rem}
}

@media (max-width:560px){
  /* The bell added a third control to the right-hand side; below this width the
     brand lockup and the actions no longer both fit, so the brand loses its
     strapline and the mark carries the identity. */
  .pnav{gap:10px;padding:10px 14px}
  .pnav__brand{min-width:0}
  .pnav__txt span{display:none}
  .pnav__txt b{font-size:.9rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .pnav__actions{gap:8px}
}

/* --------------------------- training (P4) ------------------------------ */
.pchip--planned{border-color:var(--line-strong);color:var(--muted)}
.pchip--running{border-color:var(--teal-400);color:var(--teal-400)}
.pchip--finished{background:var(--teal-400);border-color:transparent;color:#06201c}
.pchip--cancelled{color:var(--muted);text-decoration:line-through}
.pchip--present{background:var(--teal-400);border-color:transparent;color:#06201c}
.pchip--late{border-color:var(--gold-400);color:var(--gold-400)}
.pchip--absent{border-color:var(--gold-400);background:color-mix(in srgb,var(--gold-400) 18%,transparent);color:var(--text)}
.pchip--excused{color:var(--muted)}

tr.is-muted td{opacity:.55}
.ptable__actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.plink{
  background:none;border:0;padding:0;font:inherit;font-size:.82rem;
  color:var(--muted);cursor:pointer;text-decoration:underline;
}
.plink:hover{color:var(--text)}
.pinline{display:flex;gap:8px;align-items:center}
.pinline select{
  padding:6px 10px;border:1px solid var(--line);border-radius:9px;
  background:var(--bg);color:var(--text);font:inherit;font-size:.82rem;
}
.pinline option{background:var(--bg);color:var(--text)}

.pregister{list-style:none;margin:0 0 18px;padding:0;border:1px solid var(--line);border-radius:0;overflow:hidden}
.pregister li{
  display:flex;gap:16px;align-items:center;justify-content:space-between;
  flex-wrap:wrap;padding:12px 16px;
}
.pregister li+li{border-top:1px solid var(--line)}
.pregister__who b{display:block;font-size:.92rem}
.pregister__who span{font-size:.78rem;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.pregister__states{display:flex;gap:8px;flex-wrap:wrap}
.pstate{position:relative}
.pstate input{position:absolute;opacity:0;width:0;height:0}
.pstate span{
  display:inline-block;padding:7px 14px;border-radius:999px;cursor:pointer;
  border:1px solid var(--line);font-size:.8rem;color:var(--muted);
  transition:background .2s,color .2s,border-color .2s;
}
.pstate input:checked+span{background:var(--grad-brand);border-color:transparent;color:#fff;font-weight:650}
.pstate input:focus-visible+span{outline:2px solid var(--teal-400);outline-offset:2px}
.pstate span:hover{border-color:var(--line-strong);color:var(--text)}

@media (max-width:640px){
  .pregister li{flex-direction:column;align-items:stretch;gap:10px}
  .pregister__states{display:grid;grid-template-columns:1fr 1fr;gap:8px}
  .pstate span{display:block;text-align:center}
}

/* -------------------------- documents (P5) ------------------------------ */
.pchip--missing{border-color:var(--line-strong);color:var(--muted)}
.pchip--submitted{border-color:var(--gold-400);color:var(--gold-400)}
.pchip--verified{background:var(--teal-400);border-color:transparent;color:#06201c}
.pchip--rejected{border-color:var(--gold-400);background:color-mix(in srgb,var(--gold-400) 18%,transparent);color:var(--text)}
.pchip--expired{border-color:var(--gold-400);color:var(--gold-400);text-decoration:line-through}

.pinline__note{
  padding:6px 10px;border:1px solid var(--line);border-radius:9px;
  background:var(--bg);color:var(--text);font:inherit;font-size:.8rem;min-width:150px;
}
.pform input[type="file"],.pfield input[type="file"]{
  width:100%;margin-top:5px;padding:9px 12px;border:1px dashed var(--line-strong);
  border-radius:0;background:var(--bg);color:var(--text);font:inherit;font-size:.84rem;
}
.pform input[type="date"]{
  padding:9px 12px;border:1px solid var(--line);border-radius:0;
  background:var(--bg);color:var(--text);font:inherit;font-size:.88rem;
}
.pfield textarea{
  width:100%;margin-top:5px;padding:10px 13px;border:1px solid var(--line);
  border-radius:0;background:var(--bg);color:var(--text);font:inherit;font-size:.9rem;
}

.preqs{display:grid;gap:16px}
.preq__head{display:flex;gap:16px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap}
.preq__head h2{margin:0}
.preq__head .pnote{margin:4px 0 0}
.preq .pform{margin-top:14px}

/* ----------------------------- exams (P6) ------------------------------- */
.pmark{
  width:88px;padding:7px 10px;border:1px solid var(--line);border-radius:9px;
  background:var(--bg);color:var(--text);font:inherit;font-size:.88rem;text-align:right;
}
.pfeedback{
  width:100%;min-width:160px;padding:7px 10px;border:1px solid var(--line);
  border-radius:9px;background:var(--bg);color:var(--text);font:inherit;font-size:.84rem;
}
.pcheck input{width:18px;height:18px;accent-color:var(--teal-400);cursor:pointer}
.pexam{
  display:flex;gap:20px;align-items:flex-start;justify-content:space-between;
  flex-wrap:wrap;padding:14px 0;
}
.pexam+.pexam{border-top:1px solid var(--line)}
.pexam b{display:block;font-size:.95rem}
.pexam .pform{margin:0;flex:0 1 320px}

@media (max-width:700px){
  .pexam{flex-direction:column;gap:12px}
  .pexam .pform{flex:1 1 100%}
  .pmark{width:100%}
}

/* ------------------------------ modal ---------------------------------- */
.pmodal{
  position:fixed;inset:0;z-index:900;display:grid;place-items:center;padding:20px;
  background:rgba(2,4,9,.7);backdrop-filter:blur(8px);
  opacity:0;visibility:hidden;transition:opacity .28s var(--ease),visibility .28s;
}
.pmodal.is-open{opacity:1;visibility:visible}
.pmodal__box{
  width:min(640px,100%);max-height:88vh;overflow-y:auto;
  background:var(--bg-2,var(--bg));border:1px solid var(--line-strong);
  border-radius:var(--radius);box-shadow:0 40px 90px -30px rgba(0,0,0,.7);
  transform:translateY(20px) scale(.98);transition:transform .32s var(--ease);
}
.pmodal.is-open .pmodal__box{transform:none}
.pmodal__head{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:20px 24px;border-bottom:1px solid var(--line);
  position:sticky;top:0;background:inherit;z-index:1;
}
.pmodal__head h2{margin:0;font-size:1.15rem}
.pmodal__body{padding:22px 24px 26px}
.pmodal__close{
  flex-shrink:0;width:34px;height:34px;border-radius:11px;cursor:pointer;
  display:grid;place-items:center;font-size:1.05rem;line-height:1;
  background:var(--surface);border:1px solid var(--line);color:var(--muted);
}
.pmodal__close:hover{border-color:var(--line-strong);color:var(--text)}
@media (max-width:640px){
  .pmodal{padding:0;place-items:end stretch}
  .pmodal__box{max-height:92vh;border-radius:var(--radius) var(--radius) 0 0}
}
.phandover__actions{grid-column:1 / -1;display:flex;gap:10px;flex-wrap:wrap;margin-top:4px}
@media (max-width:820px){.phandover__actions{grid-column:auto}}
a.pnav__who{text-decoration:none;color:inherit;border-radius:0;padding:4px 8px;transition:background .2s}
a.pnav__who:hover{background:var(--surface-2)}

/* --------------------------- website editing ---------------------------- */
.ptabs{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 18px}
.ptab{
  padding:8px 15px;border-radius:999px;border:1px solid var(--line);
  background:var(--surface);color:var(--muted);text-decoration:none;font-size:.86rem;
  transition:border-color .25s,color .25s,background .25s;
}
.ptab:hover{border-color:var(--line-strong);color:var(--text)}
.ptab.is-active{background:var(--grad-brand);border-color:transparent;color:#fff;font-weight:650}
.ptab--out{margin-left:auto}
.pcard--wide{max-width:860px}
.pfield__req{font-style:normal;color:var(--gold-400);font-size:.68rem;letter-spacing:.04em}


/* ---- Website imagery upload -------------------------------------------
   The file input sits under a thumbnail of whatever is already attached. A
   file input renders no current value of its own, so without the thumbnail an
   entry that already has a picture is indistinguishable from one that does
   not, and the safe move becomes "re-upload every time". */
.pfield--image .pfield__preview{
  display:block;width:132px;height:88px;border-radius:0;overflow:hidden;
  border:1px solid var(--line);background:var(--surface-2);margin:2px 0 8px;
}
.pfield--image .pfield__preview img{width:100%;height:100%;object-fit:cover;display:block}
.pfield--image input[type="file"]{
  font-size:.86rem;color:var(--muted);padding:9px 10px;border-radius:0;
  border:1px dashed var(--line-strong);background:var(--surface-2);width:100%;
}
.pfield--image input[type="file"]::file-selector-button{
  margin-right:10px;padding:6px 13px;border-radius:99px;cursor:pointer;
  border:1px solid var(--line-strong);background:var(--surface);color:var(--text);
  font-family:inherit;font-size:.84rem;font-weight:600;
}
.pfield--image input[type="file"]::file-selector-button:hover{border-color:var(--teal-400)}


/* ---- Admission details on the person page ------------------------------
   A definition list, not a table: eight label/value pairs with no rows to
   compare across. The grid keeps labels and values aligned in two columns and
   collapses to one on a narrow screen, where a 50/50 split would leave a
   passport number wrapping mid-number. */
.pdetail{
  margin:18px 0 4px;padding:16px 18px;border:1px solid var(--line);
  border-radius:0;background:var(--surface-2);
}
.pdetail__head{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.pdetail__head h3{margin:0;font-size:1rem;font-family:var(--font-display)}
.pdetail__grid{
  display:grid;grid-template-columns:minmax(0,180px) minmax(0,1fr);
  gap:8px 18px;margin:0;font-size:.9rem;
}
.pdetail__grid dt{color:var(--faint);font-size:.82rem}
.pdetail__grid dd{margin:0;color:var(--text)}
@media (max-width:640px){
  .pdetail__grid{grid-template-columns:1fr;gap:2px 0}
  .pdetail__grid dd{margin-bottom:8px}
}

/* ---- Person profile card (people) --------------------------------------
   The student profile: a photograph card beside the admission particulars.

   The card is the one place in the portal with an image, so it is also the one
   place the square-corner rule has to be argued rather than assumed. It holds:
   a brand band, a photograph that straddles it, the name, and three numbers.
   The photograph is a square tile, not the circle the client's other panel
   uses — every card, chip and field on this site has been squared off on their
   instruction, and one circle would read as an accident. */
.pprofile{
  display:grid;grid-template-columns:minmax(0,300px) minmax(0,1fr);
  gap:18px;align-items:start;margin-bottom:18px;
}
.pprofile__card{padding:0;overflow:hidden}
.pprofile__banner{height:88px;background:var(--grad-brand);opacity:.9}
/* Negative margin pulls the tile up over the band, which is what gives the
   card a front rather than a stack of blocks. */
/* position + z-index, or the band paints over the tile: both are children of
   the same card and the band comes second in the stacking order without it. */
.pprofile__figure{
  position:relative;z-index:1;margin:-52px 0 0;padding:0 24px;display:flex;
}
.pprofile__photo{
  width:104px;height:104px;border-radius:0;object-fit:cover;display:block;
  border:3px solid var(--surface);background:var(--surface-2);
  box-shadow:0 8px 22px rgba(0,0,0,.28);
}
.pprofile__photo--empty{
  display:grid;place-items:center;font-family:var(--font-display);
  font-size:1.5rem;font-weight:800;color:var(--text);letter-spacing:.02em;
}
.pprofile__body{padding:14px 24px 4px}
.pprofile__name{margin:0;font-size:1.15rem;font-family:var(--font-display)}
.pprofile__role{margin:2px 0 0;color:var(--muted);font-size:.85rem;font-weight:600}
.pprofile__hint{margin-top:8px}
.pprofile__stats{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  border-top:1px solid var(--line);margin-top:14px;
}
.pprofile__stats > div{
  padding:14px 8px;text-align:center;border-left:1px solid var(--line);
}
.pprofile__stats > div:first-child{border-left:0}
.pprofile__num{
  display:block;font-family:var(--font-display);font-size:1.35rem;font-weight:800;
  line-height:1.1;
}
.pprofile__num small{font-size:.8rem;font-weight:700;color:var(--faint)}
.pprofile__lab{display:block;margin-top:3px;font-size:.7rem;color:var(--faint);
  text-transform:uppercase;letter-spacing:.07em}
.pprofile__details .pdetail__head{margin-bottom:16px}
.pprofile__details .pdetail__head h2{margin:0}

/* Two columns of short fields. A passport number and a date of birth are both
   narrow; one per row would make the panel twice as tall as the card beside it
   and push the save button below the fold. */
.pfields{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  gap:14px 16px;margin-bottom:16px;
}
@media (max-width:900px){
  .pprofile{grid-template-columns:1fr}
  .pprofile__card{max-width:360px}
}

/* ---- Payslip -----------------------------------------------------------
   The one page in the portal designed to leave it. Everything that is chrome
   — sidebar, header, buttons, the void form — is dropped at print time, so
   what comes out of the printer is the slip and nothing else. */
.pvoid{
  padding:12px 16px;margin-bottom:16px;border:1px solid var(--gold-400);
  border-radius:0;background:var(--surface-2);font-size:.9rem;
}
.payslip{
  border:1px solid var(--line);background:var(--surface);padding:26px 28px;
  max-width:860px;
}
.payslip__head{
  display:flex;justify-content:space-between;align-items:flex-start;gap:24px;
  padding-bottom:18px;border-bottom:1px solid var(--line);
}
.payslip__org{margin:0 0 4px;font-size:1.1rem;font-family:var(--font-display)}
.payslip__head p{margin:0;font-size:.8rem;color:var(--muted)}
.payslip__title{text-align:right;display:flex;flex-direction:column;gap:2px;align-items:flex-end}
.payslip__title span{font-size:.72rem;letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
.payslip__title b{font-family:var(--font-display);font-size:1.05rem}
.payslip__who{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:14px 22px;margin:18px 0 24px;font-size:.88rem;
}
.payslip__who dt{color:var(--faint);font-size:.74rem;text-transform:uppercase;letter-spacing:.06em}
.payslip__who dd{margin:2px 0 0;font-weight:600;overflow-wrap:anywhere}
.payslip__cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:24px}
.payslip__cols h3{
  margin:0 0 8px;font-size:.76rem;letter-spacing:.08em;text-transform:uppercase;
  color:var(--faint);
}
.payslip__t{width:100%;border-collapse:collapse;font-size:.9rem}
.payslip__t td{padding:7px 0;border-bottom:1px solid var(--line)}
.payslip__t td:last-child{text-align:right;font-variant-numeric:tabular-nums}
.payslip__sum td{font-weight:700;border-bottom:0;border-top:1px solid var(--line-strong)}
.payslip__none{color:var(--faint)}
.payslip__net{
  display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  margin:24px 0 0;padding:14px 16px;background:var(--surface-2);
  border:1px solid var(--line-strong);
}
.payslip__net span{font-size:.76rem;letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.payslip__net b{font-family:var(--font-display);font-size:1.5rem;font-variant-numeric:tabular-nums}
.payslip__note{margin:14px 0 0;font-size:.85rem;color:var(--muted)}
.payslip__foot{
  display:flex;flex-direction:column;gap:3px;margin-top:22px;padding-top:14px;
  border-top:1px solid var(--line);font-size:.75rem;color:var(--faint);
}

@media print{
  /* Print in ink on paper, not in the viewer's dark mode: a payslip printed
     white-on-black empties a toner cartridge and is unreadable when faxed to
     a bank, which is still how a salary certificate travels here. */
  .pnav, .pside, .pnoprint, .pflash{display:none!important}
  .pshell, .pmain{display:block;padding:0;margin:0}
  body{background:#fff!important;color:#000!important}
  .payslip{
    border:0;padding:0;max-width:none;background:#fff!important;color:#000!important;
  }
  .payslip *{color:#000!important}
  .payslip__net{background:#f2f2f2!important;border-color:#999!important}
  .payslip__t td, .payslip__sum td, .payslip__head, .payslip__foot{border-color:#bbb!important}
}
.pdetail__sub{margin:16px 0 8px;font-size:.8rem;letter-spacing:.07em;text-transform:uppercase;color:var(--faint)}

/* ---- Person record tabs ------------------------------------------------
   `precord`, not `ptab`: .ptab and .ptabs were already taken by the website
   editor's pill links further up this file, so the panels came out as 999px
   stadiums with a second border. Component prefixes are cheap; a collision
   found by eye on a client's screen is not.

   The record opens on a read-only summary; editing is the last tab, reached
   deliberately. Looking somebody up should not be able to end in a save. */
.precord{min-width:0}
.precord__tabs{
  display:flex;flex-wrap:wrap;gap:2px;margin-bottom:14px;
  border-bottom:1px solid var(--line);
}
.precord__tab{
  appearance:none;border:0;background:none;font:inherit;font-size:.88rem;
  font-weight:650;color:var(--muted);padding:9px 14px;cursor:pointer;
  border-bottom:2px solid transparent;margin-bottom:-1px;
  transition:color .18s var(--ease),border-color .18s var(--ease);
}
.precord__tab:hover{color:var(--text)}
.precord__tab.is-active{color:var(--text);border-bottom-color:var(--teal-400)}
/* Editing is set apart, so it is never hit on the way to Activity. */
.precord__tab--edit{margin-left:auto;color:var(--faint)}
.precord__tab--edit.is-active{color:var(--text)}
.precord__n{
  display:inline-block;min-width:17px;padding:0 5px;margin-left:5px;
  background:var(--gold-400);color:#1b1206;border-radius:999px;
  font-size:.68rem;font-weight:800;text-align:center;
}
/* Explicit, like .pbell__panel[hidden] above. The UA's [hidden] rule is the
   weakest thing in the cascade and any component rule that touches display
   silently beats it — which is how a "hidden" panel stays on screen. */
.precord__panel[hidden]{display:none}
/* Without JavaScript nothing is hidden and every panel stacks; portal.js adds
   .is-tabbed, and only then does the spacing between stacked panels come off. */
.precord:not(.is-tabbed) .precord__panel{margin-bottom:18px}
.precord__panel .pgrid{margin-bottom:18px}
.precord__panel > .pcard:not(:first-child){margin-top:18px}

/* The summary itself: label above value, wrapping into as many columns as fit.
   Not a two-column dl — a passport number and an IBAN both need their own
   line, and label-beside-value wastes half the width on eight short labels. */
.psum{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:14px 22px;margin:0 0 16px;
}
.psum dt{color:var(--faint);font-size:.72rem;letter-spacing:.06em;text-transform:uppercase}
.psum dd{margin:3px 0 0;font-weight:600;font-size:.92rem;overflow-wrap:anywhere}
@media (max-width:520px){
  .precord__tabs{gap:0}
  .precord__tab{padding:9px 10px;font-size:.82rem}
  .precord__tab--edit{margin-left:0}
}

/* ========================================================================
   Refinement pass (2026-09-02)

   Design decisions ported from the shadcn/21st admin patterns the client
   liked — a header band on the table, mid-aligned rows at 16px, identity
   cells carrying an avatar, a toolbar attached to the table it filters, and
   numerals that line up. None of the CODE is portable: those are React +
   Radix + Tailwind and this is Django templates with hand-written CSS. The
   decisions are what transfer.
   ===================================================================== */

/* ---- Controls ----------------------------------------------------------
   The portal was borrowing the MARKETING site's buttons: 99px pills, a
   34px glow, a 3px hover lift and a shine sweep. On a landing page that is
   the point; on a screen with nine of them around a data table it reads as
   a brochure, and it broke the squared-corner rule everywhere else obeys.
   Quiet, square, compact — the button is not the thing you came to look at. */
.pmain .btn,.pnav .btn,.plogin .btn{
  border-radius:0;padding:9px 16px;font-size:.86rem;font-weight:650;
  box-shadow:none;letter-spacing:0;
  transition:background .18s var(--ease),border-color .18s var(--ease),color .18s var(--ease);
}
.pmain .btn--sm,.pnav .btn--sm,.plogin .btn--sm{padding:7px 13px;font-size:.82rem}
.pmain .btn:hover,.pnav .btn:hover,.plogin .btn:hover{transform:none;box-shadow:none}
.pmain .btn:active,.pnav .btn:active,.plogin .btn:active{transform:none}
/* The sweep is a hero-button flourish. It has no business on "Save role". */
.pmain .btn--primary::after,.pnav .btn--primary::after,.plogin .btn--primary::after{content:none}
.pmain .btn--primary,.pnav .btn--primary,.plogin .btn--primary{background:var(--teal-500);border-color:var(--teal-500)}
.pmain .btn--primary:hover,.pnav .btn--primary:hover,.plogin .btn--primary:hover{background:var(--teal-400);border-color:var(--teal-400)}
.pmain .btn--ghost:hover,.pnav .btn--ghost:hover{background:var(--surface-2);border-color:var(--line-strong);color:var(--text)}

/* Keyboard focus was visible on filter inputs and nowhere else. An office
   tabs through these forms all day. */
.pmain :is(button,a,input,select,textarea,[tabindex]):focus-visible,
.pside__link:focus-visible,.pnav .btn:focus-visible{
  outline:2px solid var(--teal-400);outline-offset:2px;border-radius:0;
}

/* ---- Table -------------------------------------------------------------
   Three changes, all from the reference: a tinted header band so the head
   reads as chrome rather than a first row; middle alignment, because a
   two-line name cell beside single-line cells left every other column
   floating at the top; and 15px rows, which is the difference between a
   spreadsheet and a product. */
.ptable thead th{
  background:var(--surface-2);
  border-bottom:1px solid var(--line-strong);
  position:sticky;top:0;z-index:1;
}
.ptable th,.ptable td{padding:14px 16px;vertical-align:middle}
.ptable tbody tr{transition:background .12s var(--ease)}
.ptable tbody tr:hover{background:color-mix(in srgb,var(--teal-400) 7%,transparent)}
/* Figures line up under each other or they cannot be compared at a glance. */
.ptable__num{text-align:right;font-variant-numeric:tabular-nums}
.ptable td:last-child:has(.btn){text-align:right;white-space:nowrap}

/* An identity cell: square avatar, name, and one line of context. Square
   because every card, chip and field here was squared off on the client's
   instruction — the reference uses a circle, and one circle would read as
   an accident. */
.pwho{display:flex;align-items:center;gap:11px;min-width:0}
.pwho__av{
  width:36px;height:36px;flex:0 0 36px;overflow:hidden;
  display:grid;place-items:center;background:var(--surface-2);
  border:1px solid var(--line);color:var(--muted);
  font-size:.72rem;font-weight:800;letter-spacing:.02em;
}
.pwho__av img{width:100%;height:100%;object-fit:cover;display:block}
.pwho__t{min-width:0}

/* ---- Toolbar attached to its table -------------------------------------
   The filters were a floating box 18px above the table they filter. Joined
   into one board, they read as a control surface for what is beneath. */
.pfilters:has(+ .ptable-wrap){margin-bottom:0;border-bottom:0}
.pfilters + .ptable-wrap{border-top:0}
.pfilters{padding:12px 14px;gap:10px;background:var(--surface)}
.pfilters input,.pfilters select{padding:8px 11px;font-size:.86rem}

/* ---- Counters ----------------------------------------------------------
   Tabular numerals and a quieter label. The number is the message; the
   caption underneath is not competing for the same weight. */
.pkpi{padding:18px 20px;gap:5px}
.pkpi__num{font-size:1.85rem;font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.pkpi__label{font-size:.78rem;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted)}
.pkpi__note{font-size:.78rem}

/* ---- Empty states ------------------------------------------------------
   A bare sentence on a blank page looks like something failed to load. */
.pempty{
  padding:36px 24px;border:1px dashed var(--line-strong);
  background:var(--surface);text-align:center;color:var(--muted);
}

/* Counter tiles are a fixed size, not a share of the row. Two of them
   stretched across a 1400px screen made a count of 15 look like a headline. */
.pkpis{grid-template-columns:repeat(auto-fit,minmax(190px,220px));justify-content:start;gap:14px}

/* Status chips: tinted fill, matching border, coloured text — the treatment
   the reference uses. A solid block repeated down fifteen rows shouts; the
   status matters most when it is NOT the usual one. */
.pchip{padding:3px 11px;font-weight:700;font-size:.71rem;letter-spacing:.02em}
.pchip--converted{
  background:color-mix(in srgb,var(--teal-400) 16%,transparent);
  border-color:color-mix(in srgb,var(--teal-400) 45%,transparent);
  color:var(--teal-300);
}
html[data-theme="light"] .pchip--converted{color:var(--teal-600)}
.pchip--new{
  background:color-mix(in srgb,var(--gold-400) 14%,transparent);
  border-color:color-mix(in srgb,var(--gold-400) 45%,transparent);
}
.pchip--closed{background:var(--surface-2);border-color:var(--line)}

/* Header actions stay on one line where there is room; the description
   beside them was pushing a second button onto its own row. */
@media (min-width:820px){.phead-row__actions{flex-wrap:nowrap}}

/* ---- Record sections ---------------------------------------------------
   Ported from the shadcn Card/CardHeader split: a titled band with a hairline
   under it, then the body. The summary panel was one flat slab where the
   second group's heading — "ACCOUNT" — read as debris floating in the middle
   of the first group's data, because it was an h3 styled as a micro-label
   while the group above it had an h2. Both are sections now, and they say so. */
.psec{border:1px solid var(--line);background:var(--surface)}
.psec + .psec{margin-top:16px}
.psec__head{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:14px 20px;border-bottom:1px solid var(--line);background:var(--surface-2);
}
.psec__head h2,.psec__head h3{
  margin:0;font-size:.95rem;font-family:var(--font-display);letter-spacing:0;
}
.psec__head .chip{margin-left:auto}
.psec__body{padding:18px 20px}
.psec__body > :last-child{margin-bottom:0}

/* Field rows read as a record rather than floating text: a hairline above
   each row of the grid, so the eye can follow a column down. */
.psum{gap:0;margin:0}
.psum > div{padding:11px 0;border-top:1px solid var(--line);min-width:0}
.psum > div:first-child{border-top:0}
@media (min-width:520px){
  .psum{column-gap:26px}
  /* Every cell in the top ROW loses its rule, not just the first child. */
  .psum > div{border-top:1px solid var(--line)}
}
.psum dd{margin:4px 0 0;font-size:.94rem}

/* The status chip in a section band borrows the website's .chip, which is a
   solid gradient pill — at 15px it read as a call-to-action sitting where a
   label belongs. Quietened to the portal's own chip treatment. */
.psec__head .chip{
  background:none;border:1px solid var(--line-strong);color:var(--muted);
  padding:3px 11px;font-size:.71rem;font-weight:700;border-radius:999px;
  box-shadow:none;letter-spacing:.02em;
}
.psec__head .chip.is-active{
  background:color-mix(in srgb,var(--teal-400) 16%,transparent);
  border-color:color-mix(in srgb,var(--teal-400) 45%,transparent);
  color:var(--teal-300);
}
html[data-theme="light"] .psec__head .chip.is-active{color:var(--teal-600)}

/* A 22px drop shadow under the avatar was doing 2014 material-design work.
   A hairline and the card's own ground are enough to lift it off the band. */
.pprofile__photo{box-shadow:0 2px 8px rgba(0,0,0,.14)}

/* ========================================================================
   Portal palette (2026-09-02)

   Scoped to `body.portal`. The public site keeps every colour the client has
   already signed off — this changes nothing they have approved.

   The structural lesson came from a real fintech theme on 21st: an admin tool
   needs (a) OPAQUE surfaces with a real border, not a translucent wash of the
   page colour, (b) near-neutral greys, and (c) the sidebar on its own surface
   token rather than sharing the page's.

   What our tokens did wrong:
     --surface was rgba(255,255,255,.045) — every card was a tint of the navy
     behind it, so nothing had an edge and eight stacked panels read as one
     grey field. On a landing page over a photograph that is the effect you
     want. On a data screen it is why it looked unfinished.

   The brand hues are untouched: --gold-400 is still the #B53C40 from
   branding.docx, and the blues are the same scale. Only the SUBSTRATE moved.
   ===================================================================== */
body.portal{
  --bg:#0b1220;                 /* lifted off near-black: less consumer-app */
  --bg-2:#111a2b;
  --surface:#131c2e;            /* opaque, so a card is an object */
  --surface-2:#1a2437;
  --line:#26314a;
  --line-strong:#38455f;
  --text:#eaeff7;
  --muted:#9aa6bd;              /* contrast step, not "whatever looked grey" */
  --faint:#6c7891;
  /* The sidebar is its own plane. It was sharing --bg, which is why it read
     as an undifferentiated column rather than navigation. */
  --side:#0e1626;
  --side-line:#1e293d;
}
html[data-theme="light"] body.portal{
  --bg:#f6f8fb;
  --bg-2:#ffffff;
  --surface:#ffffff;
  --surface-2:#f1f4f9;
  --line:#e2e7f0;
  --line-strong:#c8d1e0;
  --text:#0d1526;
  --muted:#556072;
  --faint:#8b95a8;
  --side:#ffffff;
  --side-line:#e2e7f0;
}

/* ---- Sidebar -----------------------------------------------------------
   Fourteen links in one undifferentiated column, with a glyph set that mixed
   emoji (🔔 ✉ ✓) with geometric shapes (◧ ▤ ◍ ◈ ❖) — two different visual
   languages, rendered by two different font stacks, at two different weights.
   That reads as unfinished more than any colour choice does.

   Grouped under quiet section labels, on its own surface, with a left accent
   bar marking the active item. Groups follow the order of the day's work:
   what needs attention, who is here, what is running, then money and setup. */
.pside{background:var(--side);border-right:1px solid var(--side-line)}
.pside__group{
  padding:16px 20px 6px;font-size:.66rem;font-weight:800;letter-spacing:.11em;
  text-transform:uppercase;color:var(--faint);
}
.pside__group:first-child{padding-top:8px}
.pside__link{
  position:relative;gap:11px;padding:9px 20px;font-size:.9rem;
  border-left:2px solid transparent;border-radius:0;
}
.pside__link:hover{background:var(--surface-2)}
.pside__link.is-active{
  background:var(--surface-2);border-left-color:var(--teal-400);
  color:var(--text);font-weight:700;
}
/* One icon language: 18px inline SVG, currentColor, 1.6 stroke. No emoji. */
.pside__ico{
  width:18px;height:18px;flex:0 0 18px;stroke:currentColor;fill:none;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;opacity:.75;
}
.pside__link.is-active .pside__ico{opacity:1;color:var(--teal-400)}

/* The header icons share the sidebar's icon rule, so they inherit the same
   grid, stroke and colour. Full opacity here — a 20px control has no
   surrounding text to be quieter than. */
.pnav .pside__ico{opacity:1}

/* A textarea in a two-column field grid needs the whole row, or it is a
   three-line box beside a one-line one. */
.pfield--wide{grid-column:1 / -1}

/* ---- Lead outreach -----------------------------------------------------
   Composed messages, handed to the admin's own mail client or WhatsApp. */
.pout{display:grid;gap:12px}
.pout__row{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:12px 14px;border:1px solid var(--line);background:var(--surface-2);
}
.pout__label{font-weight:700;font-size:.88rem;margin-right:auto}
.pout__pre{
  grid-column:1 / -1;margin:0;padding:12px 14px;border:1px solid var(--line);
  background:var(--bg);font:inherit;font-size:.84rem;line-height:1.6;
  color:var(--muted);white-space:pre-wrap;max-height:190px;overflow:auto;
}

/* One entry in a lead's history. The kind is the thing the eye scans for, so
   it gets the strip of colour, not the date. */
.plog{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.plog li{
  padding:12px 0 12px 14px;border-top:1px solid var(--line);
  border-left:2px solid var(--line-strong);
}
.plog li:first-child{border-top:0}
.plog li.is-call{border-left-color:var(--teal-400)}
.plog li.is-whatsapp{border-left-color:#25d366}
.plog li.is-email{border-left-color:var(--violet-400)}
.plog li.is-meeting{border-left-color:var(--gold-400)}
.plog__top{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.plog__kind{font-weight:800;font-size:.76rem;letter-spacing:.06em;text-transform:uppercase}
.plog__who{color:var(--faint);font-size:.8rem}
.plog__when{margin-left:auto;color:var(--faint);font-size:.78rem}
.plog__body{margin:5px 0 0;font-size:.9rem;line-height:1.6}
.pdue{
  display:inline-block;padding:2px 9px;border-radius:999px;font-size:.71rem;font-weight:800;
  background:color-mix(in srgb,var(--gold-400) 18%,transparent);
  border:1px solid color-mix(in srgb,var(--gold-400) 50%,transparent);color:var(--gold-300);
}
html[data-theme="light"] .pdue{color:var(--gold-500)}

/* ---- Pipeline chain ----------------------------------------------------
   Enquiry → admission → student account, with where this one has reached.
   A status word alone never answered "and then what"; three steps do. */
.pchain{list-style:none;margin:0 0 16px;padding:0;display:grid;gap:0}
.pchain li{
  position:relative;padding:12px 0 12px 30px;border-left:2px solid var(--line);
}
.pchain li:last-child{border-left-color:transparent}
.pchain li::before{
  content:"";position:absolute;left:-6px;top:16px;width:10px;height:10px;
  background:var(--bg-2);border:2px solid var(--line-strong);border-radius:50%;
}
.pchain li.is-done{border-left-color:var(--teal-400)}
.pchain li.is-done::before{background:var(--teal-400);border-color:var(--teal-400)}
.pchain__step{display:block;font-weight:700;font-size:.92rem}
.pchain__note{display:block;margin-top:2px;font-size:.83rem;color:var(--muted)}

/* ---------------------------------------------------------------- fees --
   .pledger, not .pfee — `.pfeedback` already exists further up this file and
   a prefix that overlaps an existing class is how `.ptab` shipped broken. */
.pledger__plan{list-style:none;margin:0;padding:0;display:grid;gap:4px}
.pledger__plan li{font-size:.82rem;line-height:1.5}
.pledger__plan li b{font-weight:650;margin-right:6px}
.pledger__plan li span{color:var(--faint);margin-left:6px}

.pledger__series{padding:14px 0;border-bottom:1px solid var(--line)}
.pledger__series:last-child{border-bottom:0;padding-bottom:0}
.pledger__series-head{margin-bottom:10px}
.pledger__series-head b{display:block;font-size:.92rem}
.pledger__series-fields{
  display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end;
}
.pledger__series-fields .pfield{flex:1 1 140px;min-width:0}

/* ------------------------------------------- ledger, statement, receipt --
   Print rules first: the shell already goes away via .pnoprint in the block
   further up this file. These force black on white, because a statement
   printed in dark mode empties a toner cartridge and is unreadable by the
   time it reaches anybody. */
.pledger__rec{font-size:.8rem;line-height:1.5}
.pledger__rec a{font-weight:650}
.pledger__rec--void{opacity:.55;text-decoration:line-through}
.pledger__pay{display:flex;flex-wrap:wrap;gap:6px;align-items:center;justify-content:flex-end}
.pledger__pay input,.pledger__pay select{
  padding:6px 9px;border:1px solid var(--line);border-radius:0;
  background:var(--bg);color:var(--text);font:inherit;font-size:.82rem;
}
.pledger__pay input[type="number"]{width:96px}
.pledger__pay input[type="text"]{width:88px}
.pledger__pay input[type="date"]{width:140px}
.pledger__docs{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}

.pstmt,.preceipt{
  padding:clamp(20px,3vw,34px);border:1px solid var(--line);
  background:var(--surface);max-width:900px;
}
.pstmt__head,.preceipt__head{
  display:flex;align-items:flex-start;justify-content:space-between;gap:20px;
  padding-bottom:18px;border-bottom:1px solid var(--line);margin-bottom:22px;
}
.pstmt__brand{width:74px;flex-shrink:0}
.pstmt__title{text-align:right}
.pstmt__title h2{font-size:1.3rem;letter-spacing:.02em;text-transform:uppercase}
.pstmt__title span{font-size:.74rem;color:var(--faint);letter-spacing:.12em;text-transform:uppercase}
.pstmt__label{
  font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--faint);margin:0 0 8px;font-weight:700;
}
.pstmt__name{display:block;font-size:1.1rem;margin-bottom:6px}
.pstmt__cols{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
  gap:24px;margin-bottom:26px;font-size:.86rem;line-height:1.7;
}
.pstmt__right{text-align:right}
.pstmt__table{width:100%;border-collapse:collapse;margin-bottom:20px;font-size:.86rem}
.pstmt__table th{
  text-align:left;padding:10px 12px;background:var(--surface-2);
  font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
}
.pstmt__table td{padding:12px;border-bottom:1px solid var(--line);vertical-align:middle}
.pstmt__ref{display:block;font-size:.72rem;color:var(--faint);letter-spacing:.06em}
.pstmt__totals{display:grid;gap:6px;margin:0 0 12px;font-size:.88rem}
.pstmt__totals > div{display:flex;justify-content:space-between;gap:20px}
.pstmt__totals dt{color:var(--muted)}
.pstmt__totals dd{margin:0;font-weight:650}
.pstmt__paid{color:var(--teal-400)}
.pstmt__net{
  display:flex;justify-content:space-between;align-items:center;gap:20px;
  padding:16px 20px;background:var(--bg);border:1px solid var(--line-strong);
}
.pstmt__net span{font-size:.76rem;letter-spacing:.12em;text-transform:uppercase;font-weight:700}
.pstmt__net b{font-family:var(--font-display);font-size:1.5rem;color:var(--gold-400)}
.pstmt__foot{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
  gap:24px;margin-top:26px;
}
.pstmt__terms p{font-size:.8rem;line-height:1.7;color:var(--muted)}
.pstmt__sign{align-self:end;text-align:right}
.pstmt__sign span{display:block;font-size:.7rem;color:var(--faint);letter-spacing:.1em;text-transform:uppercase}
.pstmt__sign b{display:block;padding-top:8px;border-top:1px solid var(--line-strong);
  font-size:.78rem;letter-spacing:.14em;text-transform:uppercase}

/* ------------------------------------------------------------- ID card --
   Square, like everything else here. The client's other system uses a round
   avatar; one circle in a squared-off product reads as an accident. */
.pidcard{
  width:85.6mm;height:54mm;border:1px solid var(--line-strong);
  background:var(--surface);display:flex;flex-direction:column;overflow:hidden;
}
.pidcard__band{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:5px 8px;background:var(--bg);border-bottom:1px solid var(--line);
}
.pidcard__brand{width:26px}
.pidcard__band span{font-size:.5rem;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.pidcard__body{display:flex;gap:8px;padding:8px;flex:1;min-height:0}
.pidcard__photo{
  width:20mm;height:26mm;flex-shrink:0;background:var(--surface-2);
  border:1px solid var(--line);display:grid;place-items:center;overflow:hidden;
}
.pidcard__photo img{width:100%;height:100%;object-fit:cover;object-position:center top}
.pidcard__photo span{font-family:var(--font-display);font-size:1.4rem;color:var(--muted)}
.pidcard__who{min-width:0;flex:1}
.pidcard__who b{display:block;font-size:.82rem;line-height:1.2}
.pidcard__who > span{display:block;font-size:.58rem;color:var(--muted);margin-bottom:5px}
.pidcard__who dl{display:grid;gap:2px;margin:0}
.pidcard__who dl > div{display:flex;gap:5px;font-size:.55rem}
.pidcard__who dt{color:var(--faint);min-width:34px}
.pidcard__who dd{margin:0;font-weight:650}
.pidcard__qr{width:16mm;flex-shrink:0;align-self:flex-end}
.pidcard__qr svg{width:100%;height:auto;display:block}

@media print{
  .pstmt,.preceipt,.pidcard{
    border:0;background:#fff;color:#000;max-width:none;box-shadow:none;
  }
  .pstmt__net{background:#fff;border:1px solid #000}
  .pstmt__net b,.pstmt__paid{color:#000}
  .pstmt__table th{background:#fff;color:#000;border-bottom:1px solid #000}
  .pstmt__table td{border-bottom:1px solid #bbb}
  .pidcard{page-break-inside:avoid}
}

/* ------------------------------------------------ dashboard charts --------
   The categorical slots, in FIXED order — a hue belongs to a status, never to
   its rank, so filtering one out must not repaint the survivors. Values are
   the validated default set, checked with the dataviz validator against this
   portal's own card surface: every gate passes in both themes (worst adjacent
   CVD dE 8.4 dark / 9.1 light, normal-vision 19.8 / 22.9).

   Light mode throws a contrast WARN on slots 3 and 4, which is why every
   segment on this dashboard carries a direct label. That is the required
   relief, not a nicety. */
body.portal{
  --viz-1:#3987e5;   /* blue    */
  --viz-2:#d95926;   /* orange  */
  --viz-3:#199e70;   /* aqua    */
  --viz-4:#c98500;   /* yellow  */
  --viz-grid:rgba(255,255,255,.10);
}
body.portal[data-theme="light"],
html[data-theme="light"] body.portal{
  --viz-1:#2a78d6; --viz-2:#eb6834; --viz-3:#1baf7a; --viz-4:#eda100;
  --viz-grid:rgba(9,16,32,.12);
}

/* KPI tiles gain an icon tile and a tinted wash, the one thing worth taking
   from the reference stat cards. */
.pkpi--viz{position:relative;padding-top:20px}
.pkpi__ico{
  position:absolute;top:16px;right:16px;width:34px;height:34px;
  display:grid;place-items:center;border:1px solid var(--line);
}
.pkpi__ico svg{width:19px;height:19px;fill:none;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round}
.pkpi__ico--1{color:var(--viz-1)}
.pkpi__ico--2{color:var(--viz-2)}
.pkpi__ico--3{color:var(--viz-3)}
.pkpi__ico--4{color:var(--gold-400)}
.pkpi__ico--5{color:var(--viz-1)}
.pkpi__ico svg{stroke:currentColor}
.pkpi__num[data-figure]{transition:none}

/* Masked figures. Not display:none — the box must not reflow when the office
   toggles it, or the whole page jumps. */
.is-masked [data-figure]{
  color:transparent;position:relative;user-select:none;
}
.is-masked [data-figure]::after{
  content:"••••";position:absolute;left:0;top:0;color:var(--muted);
  letter-spacing:.08em;
}

.pviz{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
  gap:18px;margin-bottom:26px;
}
.pviz__card{margin-bottom:0}
.pviz__card--wide{grid-column:span 1}
@media (min-width:1100px){.pviz__card--wide{grid-column:span 2}}

/* Donut. Arcs rather than a dashed stroke, so the 2px gap between segments is
   real geometry instead of a dash array fighting the segment lengths. */
.pviz__ring{display:flex;gap:22px;align-items:center;flex-wrap:wrap}
.pviz__ring svg{width:150px;height:150px;flex-shrink:0}
.pviz__track{fill:none;stroke:var(--viz-grid);stroke-width:14}
.pviz__arc{fill:none;stroke-width:14;stroke-linecap:butt}
.pviz__ring-label{
  text-anchor:middle;font-size:9px;letter-spacing:.16em;
  fill:var(--faint);font-weight:700;
}
.pviz__ring-num{
  text-anchor:middle;font-size:26px;font-weight:800;fill:var(--text);
  font-family:var(--font-display);
}
.pviz__legend{list-style:none;margin:0;padding:0;display:grid;gap:9px;min-width:0}
.pviz__legend li{display:grid;grid-template-columns:10px 1fr;gap:4px 9px;align-items:center}
.pviz__legend i{width:10px;height:10px;display:block;grid-row:span 2}
.pviz__legend b{font-size:.88rem}
.pviz__legend span{font-size:.78rem;color:var(--muted);grid-column:2}

/* Funnel */
.pfunnel{list-style:none;margin:0 0 4px;padding:0;display:grid;gap:14px}
.pfunnel__row{display:flex;justify-content:space-between;align-items:baseline;gap:12px}
.pfunnel__row b{font-size:.9rem}
.pfunnel__row span{font-family:var(--font-display);font-size:1.05rem;font-weight:700}
.pfunnel__track{height:9px;background:var(--viz-grid);margin-top:5px}
.pfunnel__bar{height:100%;background:var(--viz-1);min-width:2px}
.pfunnel__kept{display:block;margin-top:4px;font-size:.74rem;color:var(--faint)}

/* Line plot. Thin marks, one hairline baseline, no gridlines — the numbers on
   the axis do that job and a grid competes with two-pixel lines. */
.pviz__key{list-style:none;display:flex;gap:16px;margin:0 0 12px;padding:0}
.pviz__key li{display:flex;align-items:center;gap:7px;font-size:.8rem;color:var(--muted)}
.pviz__key i{width:10px;height:10px;display:block}
.pviz__plot{width:100%;height:130px;display:block;overflow:visible}
.pviz__axis{stroke:var(--viz-grid);stroke-width:1}
.pviz__line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;
  vector-effect:non-scaling-stroke}
.pviz__dot{stroke:var(--surface);stroke-width:2;vector-effect:non-scaling-stroke}
.pviz__xaxis{
  list-style:none;display:flex;justify-content:space-between;
  margin:6px 2px 0;padding:0;font-size:.72rem;color:var(--faint);
}

/* Bars. 4px rounded data-end anchored to the baseline is the one radius this
   stylesheet allows itself — it is a mark cap, not a rounded rectangle. */
.pbars{display:flex;align-items:flex-end;gap:6px;height:150px;margin-bottom:8px}
.pbars__col{flex:1;display:flex;flex-direction:column;justify-content:flex-end;
  align-items:center;gap:6px;height:100%;min-width:0}
.pbars__bar{
  width:100%;background:var(--viz-3);min-height:2px;
  border-radius:2px 2px 0 0;transition:background .15s var(--ease);
}
.pbars__col:hover .pbars__bar{background:var(--viz-1)}
.pbars__col span{font-size:.68rem;color:var(--faint)}

