/* ===========================================================================
   NexusSec Labs — portal
   Standalone by design: the portal must render even if the marketing site's
   stylesheet is mid-deploy. Same tokens, same Deep Field canvas, same measured
   enterprise type scale (H1 ~56px / weight 500 / -.025em) as the main site.
   =========================================================================== */

:root {
  --canvas:#070c16; --panel:#101a2b; --panel-2:#16223a; --cream:#e6ecf5;
  --steel:#7ea3f3; --brass:#c9a227; --brass-bright:#e0b93f; --info:#0ea5b7;
  --ok:#35d07f; --warn:#f5a524; --bad:#ff8a80;
  --t2:#a9b7c6; --t3:#93a2b5;
  --hair:rgba(147,172,209,.16); --hair2:rgba(147,172,209,.30);
  --mono:"DM Mono",ui-monospace,monospace; --sans:"Manrope",system-ui,sans-serif;
  --r:4px;
}
* { box-sizing:border-box; }
html { background:var(--canvas); -webkit-text-size-adjust:100%; }

.p-body {
  margin:0; font-family:var(--sans); color:var(--cream); background:var(--canvas);
  font-size:16px; line-height:1.65; overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
}
.p-body::before {
  content:""; position:fixed; inset:0; z-index:-2; pointer-events:none;
  background:
    radial-gradient(1100px 720px at 82% -6%, rgba(126,163,243,.18), transparent 62%),
    radial-gradient(900px 620px at -8% 92%, rgba(14,165,183,.10), transparent 66%),
    var(--canvas);
}
.p-body::after {
  content:""; position:fixed; inset:0; z-index:-1; pointer-events:none;
  background-image:
    linear-gradient(rgba(160,190,235,.075) 1px,transparent 1px),
    linear-gradient(90deg,rgba(160,190,235,.075) 1px,transparent 1px),
    linear-gradient(rgba(160,190,235,.028) 1px,transparent 1px),
    linear-gradient(90deg,rgba(160,190,235,.028) 1px,transparent 1px);
  background-size:96px 96px,96px 96px,16px 16px,16px 16px;
  -webkit-mask-image:radial-gradient(1200px 900px at 78% 2%,#000,transparent 76%);
          mask-image:radial-gradient(1200px 900px at 78% 2%,#000,transparent 76%);
}
@media (max-width:620px){
  .p-body::after{
    background-image:
      linear-gradient(rgba(160,190,235,.07) 1px,transparent 1px),
      linear-gradient(90deg,rgba(160,190,235,.07) 1px,transparent 1px);
    background-size:64px 64px,64px 64px;
  }
}

a { color:var(--steel); text-decoration:none; }
a:hover { color:var(--brass); }
h1,h2,h3 { font-weight:500; letter-spacing:-.025em; line-height:1.06; margin:0 0 14px; }
h1 { font-size:clamp(32px,4vw,52px); }
h2 { font-size:clamp(22px,2.4vw,30px); }
h3 { font-size:17px; font-weight:600; letter-spacing:-.022em; line-height:1.25; }
code { font:13px var(--mono); background:rgba(126,163,243,.12); padding:2px 7px;
       border-radius:3px; color:var(--cream); }

/* ---------- brand ---------- */
.p-brand { display:inline-flex; align-items:center; gap:9px; font-size:19px;
  font-weight:700; letter-spacing:-.04em; color:var(--cream); }
.p-brand > span:nth-of-type(1) > span { color:var(--steel); }
.p-brand b { font:9px var(--mono); letter-spacing:.12em; align-self:flex-end;
  padding-bottom:2px; color:var(--t2); border:1px solid var(--hair2);
  border-radius:2px; padding:2px 5px; font-weight:400; }
.p-mark { width:26px; height:26px; display:inline-flex; gap:2px; align-items:center;
  transform:skewY(-24deg); overflow:hidden; }
.p-mark i { display:block; width:6px; height:17px; background:var(--cream);
  transition:height .28s cubic-bezier(.2,.7,.3,1), background .28s; }
.p-mark i:nth-child(2){ height:24px; background:var(--steel); }
.p-mark i:nth-child(3){ height:11px; }
@media (prefers-reduced-motion:no-preference){
  /* Stagger baked into the keyframes, fill-mode none — see styles.css for why:
     with `backwards` the logo sits at 8% height whenever the timeline is
     throttled, which is a broken logo rather than an un-animated one. */
  .p-mark i:nth-child(1){ animation:p-rise-1 .9s cubic-bezier(.2,.75,.3,1); }
  .p-mark i:nth-child(2){ animation:p-rise-2 .9s cubic-bezier(.2,.75,.3,1); }
  .p-mark i:nth-child(3){ animation:p-rise-3 .9s cubic-bezier(.2,.75,.3,1); }
  @keyframes p-rise-1{0%{transform:scaleY(.08);opacity:0}42%{transform:scaleY(1.14);opacity:1}69%{transform:scaleY(1);opacity:1}}
  @keyframes p-rise-2{0%,12%{transform:scaleY(.08);opacity:0}54%{transform:scaleY(1.14);opacity:1}81%{transform:scaleY(1);opacity:1}}
  @keyframes p-rise-3{0%,24%{transform:scaleY(.08);opacity:0}66%{transform:scaleY(1.14);opacity:1}93%{transform:scaleY(1);opacity:1}}
  .p-brand:hover .p-mark i:nth-child(1){height:24px}
  .p-brand:hover .p-mark i:nth-child(2){height:11px;background:var(--brass)}
  .p-brand:hover .p-mark i:nth-child(3){height:20px}
}

/* ---------- standalone pages (login, demo) ---------- */
.p-wrap { max-width:520px; margin:0 auto; padding:56px 20px 70px;
  min-height:100vh; display:flex; flex-direction:column; gap:26px; }
.p-wrap-wide { max-width:1040px; }
.p-split { display:grid; grid-template-columns:1fr 1fr; gap:38px; align-items:start; }
@media (max-width:860px){ .p-split{ grid-template-columns:1fr; gap:26px; } }

.p-eyebrow { font:10px var(--mono); letter-spacing:.11em; text-transform:uppercase;
  color:var(--t3); margin:0 0 14px; display:flex; align-items:center; gap:9px; }
.p-eyebrow s { text-decoration:none; color:var(--brass); }
.p-eyebrow s.p-live { color:var(--ok); }
@media (prefers-reduced-motion:no-preference){
  .p-eyebrow s.p-live{ animation:p-blink 2.4s ease-in-out infinite; }
  @keyframes p-blink{0%,100%{opacity:1}50%{opacity:.35}}
}
.p-lede { color:var(--t2); font-size:16px; line-height:1.75; margin:0 0 20px; }
.p-pitch h1 em { font-style:normal; color:var(--steel); }
.p-list { list-style:none; margin:22px 0 0; padding:0; display:grid; gap:12px; }
.p-list li { color:var(--t2); font-size:14.5px; line-height:1.6;
  padding-left:20px; position:relative; }
.p-list li::before { content:"/"; position:absolute; left:0; color:var(--steel); }
.p-list b { color:var(--cream); font-weight:600; }

/* ---------- cards ---------- */
.p-card { background:var(--panel); border:1px solid var(--hair); border-radius:var(--r);
  padding:30px 28px; box-shadow:0 22px 60px rgba(2,5,12,.55); }
@media (max-width:520px){ .p-card{ padding:24px 20px; } }
.p-ok { border-color:rgba(53,208,127,.35); }

.p-facts { margin:22px 0 0; display:grid; gap:13px; }
.p-facts div { display:flex; justify-content:space-between; gap:16px;
  border-top:1px solid var(--hair); padding-top:11px; }
.p-facts dt { font:9px var(--mono); letter-spacing:.09em; text-transform:uppercase;
  color:var(--t3); margin:0; }
.p-facts dd { margin:0; font:12px var(--mono); color:var(--cream); text-align:right; }

/* ---------- forms ---------- */
.p-form h1, .p-form h2 { margin-bottom:18px; }
.p-field { display:block; margin-bottom:18px; }
.p-field > span { display:block; font:9px var(--mono); letter-spacing:.09em;
  text-transform:uppercase; color:var(--t3); margin-bottom:8px; }
.p-field > span i { font-style:normal; text-transform:none; letter-spacing:0;
  color:var(--t3); opacity:.7; }
.p-field input, .p-field select {
  display:block; width:100%; min-height:48px; padding:11px 13px;
  color:var(--cream); background:var(--panel-2);
  border:1px solid rgba(230,236,245,.42); border-radius:3px;
  font:15px var(--sans); outline:0; appearance:none;
  transition:border-color .2s, box-shadow .2s;
}
.p-field select {
  background-image:linear-gradient(45deg,transparent 50%,var(--steel) 50%),
                   linear-gradient(135deg,var(--steel) 50%,transparent 50%);
  background-position:calc(100% - 17px) 22px, calc(100% - 12px) 22px;
  background-size:5px 5px,5px 5px; background-repeat:no-repeat; padding-right:38px;
}
.p-field select option { background:var(--panel); color:var(--cream); }
.p-field input:focus, .p-field select:focus {
  border-color:var(--steel); box-shadow:0 0 0 3px rgba(126,163,243,.22);
}
.p-field input::placeholder { color:#8097b6; }
.p-field em { display:block; font-style:normal; font-size:12px; color:var(--t3);
  margin-top:7px; line-height:1.5; }

/* The bot trap. It MUST be a class and never an inline style attribute: the
   portal's Content-Security-Policy sets style-src without 'unsafe-inline', so
   the browser drops every style="" it is given. register.php used to hide this
   field inline, which meant the browser showed it — real people filled in the
   visible "Website" box and the form rejected them as automated. */
.hp-field { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

/* ---------- spacing utilities ----------
   Same reason: under this CSP a spacing tweak has to be a class or it does
   not happen at all. Anything that needs margin gets one of these. */
.p-gap   { margin-top:18px; }
.p-gap-s { margin-top:10px; }
.p-gap-l { margin-top:24px; }
.p-plain { font-style:normal; text-transform:none; letter-spacing:0; }

/* ---------- buttons ---------- */
.p-btn { display:inline-flex; align-items:center; justify-content:center; gap:9px;
  min-height:48px; padding:0 22px; border-radius:3px; border:1px solid transparent;
  font:600 13px var(--sans); letter-spacing:.01em; cursor:pointer;
  transition:background .2s, border-color .2s, color .2s, transform .2s; }
.p-btn-primary { background:var(--brass); color:#221a02; }
.p-btn-primary:hover { background:var(--brass-bright); color:#221a02; transform:translateY(-2px); }
.p-btn-outline { background:transparent; color:var(--cream); border-color:var(--hair2); }
.p-btn-outline:hover { border-color:var(--steel); color:var(--steel); }
.p-btn-full { width:100%; margin-top:6px; }
.p-btn-sm { min-height:40px; padding:0 15px; font-size:12px; }
.p-btn:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible,
button:focus-visible { outline:2px solid var(--steel); outline-offset:3px; }

/* ---------- messages ---------- */
.p-errors { border:1px solid rgba(255,138,128,.4); background:rgba(255,138,128,.09);
  border-radius:3px; padding:13px 15px; margin:0 0 18px; }
.p-errors p { margin:0 0 6px; color:#ffb3ac; font-size:13.5px; line-height:1.55; }
.p-errors p:last-child { margin:0; }
.p-notice { border-left:3px solid var(--steel); background:rgba(126,163,243,.10);
  padding:11px 15px; margin:0 0 18px; font-size:13.5px; color:var(--t2); border-radius:0 3px 3px 0; }
.p-flash { margin-bottom:26px; }
.p-warn { border-left:3px solid var(--warn); background:rgba(245,165,36,.10);
  padding:11px 15px; margin:18px 0 0; font-size:13px; color:#f3c777; border-radius:0 3px 3px 0; }
.p-fine { font-size:12.5px; color:var(--t3); line-height:1.65; margin:16px 0 0; }
.p-note { font-size:13px; color:var(--t2); margin:16px 0 0; }
.p-aside { font-size:13px; color:var(--t3); line-height:1.7; margin:0;
  border-top:1px solid var(--hair); padding-top:18px; }
.p-back { margin:auto 0 0; font:10px var(--mono); letter-spacing:.07em;
  text-transform:uppercase; }
.p-back a { color:var(--t3); }

/* ═══════════ dashboard ═══════════ */
.p-app { display:flex; flex-direction:column; min-height:100vh; }
.p-header { position:sticky; top:0; z-index:20; display:flex; align-items:center;
  gap:24px; padding:14px clamp(16px,4vw,40px);
  background:rgba(7,12,22,.86); -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
  border-bottom:1px solid var(--hair); flex-wrap:wrap; }
.p-nav { display:flex; gap:20px; margin-right:auto; flex-wrap:wrap; }
.p-nav a { font:10px var(--mono); letter-spacing:.08em; text-transform:uppercase;
  color:var(--t2); min-height:34px; display:inline-flex; align-items:center; }
.p-nav a:hover { color:var(--cream); }
.p-header-end { display:flex; align-items:center; gap:16px; }
.p-signout { font:10px var(--mono); letter-spacing:.07em; text-transform:uppercase;
  color:var(--t3); }
@media (max-width:760px){
  .p-header { gap:12px; }
  .p-nav { order:3; width:100%; gap:14px; margin:0; }
  .p-header-end { margin-left:auto; }
}

.p-main { flex:1; width:100%; max-width:1200px; margin:0 auto;
  padding:38px clamp(16px,4vw,40px) 60px; }
.p-hello { margin-bottom:34px; }
.p-hello h1 { margin-bottom:10px; }
.p-section { margin-top:52px; scroll-margin-top:90px; }
.p-h2 { display:flex; align-items:baseline; gap:12px; flex-wrap:wrap;
  border-bottom:1px solid var(--hair); padding-bottom:13px; margin-bottom:22px; }
.p-count { font:9px var(--mono); font-style:normal; letter-spacing:.09em;
  text-transform:uppercase; color:var(--steel); }
.p-section-lede { color:var(--t2); font-size:14.5px; margin:-8px 0 22px; max-width:70ch; }
.p-empty { color:var(--t3); font-size:14px; padding:26px 0; margin:0; }
.p-upsell { margin:20px 0 0; padding:14px 17px; border:1px dashed var(--hair2);
  border-radius:3px; font-size:13.5px; color:var(--t2); }

.p-lease { display:flex; align-items:center; justify-content:space-between;
  gap:26px; flex-wrap:wrap; border-color:rgba(53,208,127,.3); }
.p-lease h2 { margin-bottom:8px; }
.p-lease p { margin:0; color:var(--t2); font-size:14px; }

.p-grid { display:grid; gap:14px; }
.p-grid-4 { grid-template-columns:repeat(4,1fr); }
.p-grid-3 { grid-template-columns:repeat(3,1fr); }
@media (max-width:980px){ .p-grid-4{grid-template-columns:repeat(2,1fr)} .p-grid-3{grid-template-columns:repeat(2,1fr)} }
@media (max-width:620px){ .p-grid-4,.p-grid-3{grid-template-columns:1fr} }

.p-tile { background:var(--panel); border:1px solid var(--hair); border-radius:var(--r);
  padding:18px 18px 16px; }
.p-tile span { display:block; font:9px var(--mono); letter-spacing:.09em;
  text-transform:uppercase; color:var(--t3); margin-bottom:9px; }
.p-tile b { display:block; font-size:21px; font-weight:500; letter-spacing:-.03em; }
.p-tile em { display:block; font-style:normal; font-size:12.5px; color:var(--t3);
  margin-top:7px; line-height:1.5; }
.p-status-demo { color:var(--steel); } .p-status-active { color:var(--ok); }
.p-status-pending { color:var(--warn); } .p-status-suspended,
.p-status-expired { color:var(--bad); }

/* labs */
.p-lab { background:var(--panel); border:1px solid var(--hair); border-radius:var(--r);
  padding:20px; display:flex; flex-direction:column;
  transition:border-color .22s, transform .22s; }
.p-lab:hover { border-color:var(--hair2); transform:translateY(-2px); }
.p-lab-exam { border-color:rgba(201,162,39,.28); }
.p-lab-top { display:flex; justify-content:space-between; align-items:center;
  gap:10px; margin:0 0 12px; }
.p-lab-top span { font:9px var(--mono); letter-spacing:.09em; text-transform:uppercase;
  color:var(--t3); }
.p-level { font-style:normal; font:9px var(--mono); letter-spacing:.06em;
  text-transform:uppercase; padding:3px 8px; border-radius:2px;
  border:1px solid var(--hair2); color:var(--t2); }
.p-level-associate { color:var(--ok); border-color:rgba(53,208,127,.35); }
.p-level-professional { color:var(--steel); border-color:rgba(126,163,243,.35); }
.p-level-expert { color:var(--warn); border-color:rgba(245,165,36,.35); }
.p-level-exam { color:var(--brass); border-color:rgba(201,162,39,.4); }
.p-lab h3 { margin:0 0 9px; }
.p-lab-sum { color:var(--t2); font-size:13.5px; line-height:1.65; margin:0 0 14px; flex:1; }
.p-lab-meta { list-style:none; display:flex; flex-wrap:wrap; gap:6px;
  margin:0 0 16px; padding:0; }
.p-lab-meta li { font:9px var(--mono); letter-spacing:.05em; text-transform:uppercase;
  color:var(--t3); border:1px solid var(--hair); border-radius:2px; padding:4px 8px; }

/* table */
.p-tablewrap { overflow-x:auto; -webkit-overflow-scrolling:touch; }
.p-table { width:100%; border-collapse:collapse; font-size:14px; min-width:600px; }
.p-table th { text-align:left; font:9px var(--mono); letter-spacing:.09em;
  text-transform:uppercase; color:var(--t3); font-weight:400;
  padding:10px 12px; border-bottom:1px solid var(--hair2); }
.p-table td { padding:14px 12px; border-bottom:1px solid var(--hair); vertical-align:middle; }
.p-table td b { font-weight:600; display:block; letter-spacing:-.02em; }
.p-table td span { display:block; font:9px var(--mono); letter-spacing:.05em;
  text-transform:uppercase; color:var(--t3); margin-top:4px; }
.p-mono { font:11.5px var(--mono); color:var(--t2); letter-spacing:.03em; }
.p-pill { display:inline-block; font:9px var(--mono); letter-spacing:.07em;
  text-transform:uppercase; padding:4px 9px; border-radius:2px; }
.p-pill-ok   { background:rgba(53,208,127,.13); color:var(--ok);   border:1px solid rgba(53,208,127,.32); }
.p-pill-hold { background:rgba(245,165,36,.12); color:var(--warn); border:1px solid rgba(245,165,36,.32); }
.p-pill-res  { background:rgba(126,163,243,.12);color:var(--steel);border:1px solid rgba(126,163,243,.32); }
.p-pill-done { background:rgba(147,172,209,.10);color:var(--t2);   border:1px solid var(--hair2); }
.p-pill-off  { background:transparent;          color:var(--t3);   border:1px solid var(--hair); }
.p-score { display:inline-block; margin-left:8px; font:11px var(--mono); color:var(--cream); }

@media (max-width:620px){
  .p-table { min-width:0; font-size:13px; }
  .p-table thead { position:absolute; left:-9999px; }
  .p-table tr { display:grid; grid-template-columns:1fr auto; gap:5px 12px;
    padding:15px 0; border-bottom:1px solid var(--hair); }
  .p-table td { border:0; padding:0; }
  .p-table td:nth-child(2) { grid-column:1/-1; }
}

.p-footer { border-top:1px solid var(--hair); padding:24px clamp(16px,4vw,40px) 34px;
  display:flex; justify-content:space-between; gap:14px; flex-wrap:wrap;
  font-size:12.5px; color:var(--t3); }
.p-footer p { margin:0; }
.p-footer b { color:var(--t2); font-weight:500; }

@media (prefers-contrast:more){
  .p-body::before, .p-body::after { display:none; }
  .p-body { background:#05080f; }
}

/* An outline button's border IS the control — WCAG 1.4.11 wants 3:1 on it.
   --hair2 (.30 alpha) measured 1.82:1 on the panel: fine for a decorative card
   edge, not fine for something you are meant to find and press. Card borders
   stay as they are; only the interactive boundary is lifted. */
.p-btn-outline { border-color: rgba(147,172,209,.55); }
.p-btn-outline:hover { border-color: var(--steel); }

/* ═══════════════════════════════════════════════════════════════════════
   DASHBOARD v2
   Built under one hard constraint: portal/.htaccess sends
   script-src 'none'. There is no JavaScript on this page and there cannot
   be one, so every interactive affordance below — the catalogue filter,
   the meters, the status rails — is CSS only. Where a value has to come
   from PHP it arrives as a CLASS, never as a style attribute, because
   style-src has no 'unsafe-inline' either and inline styles are dropped.
   ═══════════════════════════════════════════════════════════════════════ */

/* ---------- meters ----------
   Width comes from a bucket class, not an inline style. 5% steps are
   finer than the eye reads at this size and keep the rule count sane. */
.p-meter { height:5px; border-radius:99px; background:rgba(147,172,209,.15);
  overflow:hidden; margin-top:10px; }
.p-meter i { display:block; height:100%; border-radius:99px; background:var(--ok);
  transition:width .5s cubic-bezier(.2,.7,.3,1); }
.p-meter-warn i { background:var(--warn); }
.p-meter-low  i { background:var(--bad); }
.p-meter-steel i { background:var(--steel); }
.p-w-0 { width:0%; }
.p-w-5 { width:5%; }
.p-w-10 { width:10%; }
.p-w-15 { width:15%; }
.p-w-20 { width:20%; }
.p-w-25 { width:25%; }
.p-w-30 { width:30%; }
.p-w-35 { width:35%; }
.p-w-40 { width:40%; }
.p-w-45 { width:45%; }
.p-w-50 { width:50%; }
.p-w-55 { width:55%; }
.p-w-60 { width:60%; }
.p-w-65 { width:65%; }
.p-w-70 { width:70%; }
.p-w-75 { width:75%; }
.p-w-80 { width:80%; }
.p-w-85 { width:85%; }
.p-w-90 { width:90%; }
.p-w-95 { width:95%; }
.p-w-100 { width:100%; }

/* ---------- lease panel ----------
   The old version said "You have N hours left" off TIMESTAMPDIFF(HOUR), which
   floors: a lease with 59 minutes to run announced "0 hours left" and read as
   already dead. It now counts in minutes and renders the largest honest unit. */
.p-lease { display:grid; grid-template-columns:1fr auto; gap:26px 34px;
  align-items:center; border-color:rgba(53,208,127,.3); }
.p-lease h2 { margin-bottom:8px; }
.p-lease p { margin:0; color:var(--t2); font-size:14px; }
.p-lease-bar { grid-column:1/-1; }
.p-lease-bar .p-meter { margin-top:0; }
.p-lease-scale { display:flex; justify-content:space-between; margin:7px 0 0;
  font:9px var(--mono); letter-spacing:.07em; text-transform:uppercase; color:var(--t3); }
@media (max-width:620px){
  .p-lease { grid-template-columns:1fr; }
  .p-lease .p-btn { width:100%; }
}

/* ---------- plan tiles ---------- */
.p-tile { position:relative; overflow:hidden; }
.p-tile-accent::before { content:""; position:absolute; inset:0 auto 0 0; width:2px;
  background:var(--steel); }
.p-tile-accent.p-tile-ok::before   { background:var(--ok); }
.p-tile-accent.p-tile-warn::before { background:var(--warn); }
.p-tile-accent.p-tile-bad::before  { background:var(--bad); }

/* ---------- catalogue filter ----------
   A radio group, no script. The inputs sit before the chips and the grid so a
   plain sibling combinator can reach both: :checked styles its own chip, and
   :checked hides every card that is not the chosen vendor. Screen readers get
   a real radio group; keyboards get arrow-key selection for free. */
.p-vf { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
.p-chips { display:flex; flex-wrap:wrap; gap:8px; margin:0 0 20px; }
.p-chips label { display:inline-flex; align-items:center; gap:7px; cursor:pointer;
  min-height:34px; padding:0 13px; border-radius:99px;
  border:1px solid rgba(147,172,209,.35); background:transparent;
  font:10px var(--mono); letter-spacing:.08em; text-transform:uppercase;
  color:var(--t2); transition:border-color .2s, color .2s, background .2s; }
.p-chips label:hover { border-color:var(--steel); color:var(--cream); }
.p-chips label i { font-style:normal; opacity:.6; }
.p-chips label u { text-decoration:none; width:7px; height:7px; border-radius:50%;
  background:var(--vendor,var(--t3)); }

/* ---------- lab cards ---------- */
.p-lab { position:relative; }
.p-lab::before { content:""; position:absolute; inset:0 0 auto 0; height:2px;
  background:var(--vendor,var(--hair2)); opacity:.85;
  border-radius:var(--r) var(--r) 0 0; }
.p-lab-top span { display:inline-flex; align-items:center; gap:7px; }
.p-lab-top span::before { content:""; width:7px; height:7px; border-radius:50%;
  background:var(--vendor,var(--t3)); flex:none; }
.p-lab-flag { font:9px var(--mono); letter-spacing:.07em; text-transform:uppercase;
  color:var(--ok); border:1px solid rgba(53,208,127,.35); border-radius:2px;
  padding:3px 7px; align-self:flex-start; margin:0 0 12px; }

/* Vendor identity. One custom property per card drives the top rule, the dot in
   the header and the dot in its filter chip, so a new vendor is one rule. */
[data-v="cisco"]      { --vendor:#1ba0d7; }
[data-v="fortinet"]   { --vendor:#ee3124; }
[data-v="sophos"]     { --vendor:#0f7dc2; }
[data-v="paloalto"]   { --vendor:#fa582d; }
[data-v="juniper"]    { --vendor:#84b424; }
[data-v="checkpoint"] { --vendor:#e4002b; }
[data-v="pfsense"]    { --vendor:#c33a31; }
[data-v="mixed"]      { --vendor:#7ea3f3; }

/* ---------- request status rail ----------
   A booking moves pending -> confirmed -> running -> completed. A single pill
   showed where it was but not how far along, which is the thing a customer
   waiting on a slot actually wants to see. */
.p-rail { display:flex; align-items:center; gap:4px; margin-top:7px; }
.p-rail i { display:block; width:15px; height:3px; border-radius:99px;
  background:rgba(147,172,209,.18); }
.p-rail i.on { background:var(--steel); }
.p-rail i.done { background:var(--ok); }
.p-rail i.off { background:rgba(147,172,209,.12); }

/* ---------- empty states ---------- */
.p-empty { color:var(--t3); font-size:14px; padding:30px 22px; margin:0;
  border:1px dashed var(--hair2); border-radius:var(--r); text-align:center; }
.p-empty b { display:block; color:var(--cream); font-weight:500; font-size:16px;
  letter-spacing:-.02em; margin-bottom:7px; }
.p-empty a { text-decoration:underline; text-underline-offset:3px; }

/* ---------- summary strip ---------- */
.p-strip { display:flex; flex-wrap:wrap; gap:0; margin:0 0 34px;
  border:1px solid var(--hair); border-radius:var(--r); background:var(--panel);
  overflow:hidden; }
.p-strip div { flex:1 1 150px; padding:15px 18px; border-right:1px solid var(--hair); }
.p-strip div:last-child { border-right:0; }
.p-strip span { display:block; font:9px var(--mono); letter-spacing:.09em;
  text-transform:uppercase; color:var(--t3); margin-bottom:6px; }
.p-strip b { font-size:19px; font-weight:500; letter-spacing:-.03em; }
.p-strip b.ok { color:var(--ok); } .p-strip b.warn { color:var(--warn); }
.p-strip b.dim { color:var(--t3); }
@media (max-width:620px){ .p-strip div { flex-basis:50%; } }

/* ---------- catalogue filter wiring ----------
   One pair of rules per vendor: the first lights that vendor's chip, the second
   hides every card that is not that vendor. Plain sibling combinators, so the
   radios must stay BEFORE .p-chips and #labgrid in the markup.

   ADDING A VENDOR: add its slug to this list and to the [data-v=] colour block
   above. A vendor the DB has but this file does not still gets a working chip —
   it just will not filter — so the page degrades quietly rather than breaking.
   Generated for: all cisco fortinet sophos paloalto juniper checkpoint pfsense mixed */
.p-vf:checked ~ .p-chips label { }
#vf-all:checked ~ .p-chips label[for="vf-all"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-all:focus-visible ~ .p-chips label[for="vf-all"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-cisco:checked ~ .p-chips label[for="vf-cisco"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-cisco:focus-visible ~ .p-chips label[for="vf-cisco"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-fortinet:checked ~ .p-chips label[for="vf-fortinet"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-fortinet:focus-visible ~ .p-chips label[for="vf-fortinet"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-sophos:checked ~ .p-chips label[for="vf-sophos"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-sophos:focus-visible ~ .p-chips label[for="vf-sophos"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-paloalto:checked ~ .p-chips label[for="vf-paloalto"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-paloalto:focus-visible ~ .p-chips label[for="vf-paloalto"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-juniper:checked ~ .p-chips label[for="vf-juniper"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-juniper:focus-visible ~ .p-chips label[for="vf-juniper"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-checkpoint:checked ~ .p-chips label[for="vf-checkpoint"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-checkpoint:focus-visible ~ .p-chips label[for="vf-checkpoint"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-pfsense:checked ~ .p-chips label[for="vf-pfsense"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-pfsense:focus-visible ~ .p-chips label[for="vf-pfsense"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-mixed:checked ~ .p-chips label[for="vf-mixed"] { border-color:var(--steel); color:var(--cream);
  background:rgba(126,163,243,.13); }
#vf-mixed:focus-visible ~ .p-chips label[for="vf-mixed"] { outline:2px solid var(--steel); outline-offset:3px; }
#vf-cisco:checked ~ #labgrid .p-lab:not([data-v="cisco"]) { display:none; }
#vf-fortinet:checked ~ #labgrid .p-lab:not([data-v="fortinet"]) { display:none; }
#vf-sophos:checked ~ #labgrid .p-lab:not([data-v="sophos"]) { display:none; }
#vf-paloalto:checked ~ #labgrid .p-lab:not([data-v="paloalto"]) { display:none; }
#vf-juniper:checked ~ #labgrid .p-lab:not([data-v="juniper"]) { display:none; }
#vf-checkpoint:checked ~ #labgrid .p-lab:not([data-v="checkpoint"]) { display:none; }
#vf-pfsense:checked ~ #labgrid .p-lab:not([data-v="pfsense"]) { display:none; }
#vf-mixed:checked ~ #labgrid .p-lab:not([data-v="mixed"]) { display:none; }

/* ═══════════ LEARNING TRACKS ═══════════
   A track is an ordered path, so it is an <ol> of steps rather than a grid of
   cards. The number in the gutter is the point: a grid cannot show that lab 3
   comes after lab 2, and the flat catalogue it replaces looked identical on
   day one and day three however much you had actually finished. */
.p-trackgrid { display:grid; grid-template-columns:1fr 1fr; gap:20px; align-items:start; }
@media (max-width:900px){ .p-trackgrid { grid-template-columns:1fr; } }

.p-track { background:var(--panel); border:1px solid var(--hair);
  border-radius:var(--r); padding:22px 22px 8px; }
.p-track-head h3 { font-size:20px; font-weight:500; letter-spacing:-.03em; margin:0 0 6px; }
.p-track-blurb { color:var(--t3); font-size:13px; line-height:1.6; margin:0 0 4px; max-width:46ch; }
.p-track .p-meter { margin:14px 0 4px; }

.p-steps { list-style:none; margin:0; padding:0; counter-reset:none; }
.p-step { display:grid; grid-template-columns:auto 1fr auto; gap:14px;
  align-items:start; padding:16px 0; border-top:1px solid var(--hair); }
.p-step:first-child { border-top:0; }

/* The gutter marker carries the state, so the row does not need a colour wash
   that would fight the vendor accent elsewhere on the page. */
.p-step-n { display:grid; place-items:center; width:26px; height:26px; border-radius:50%;
  font:11px var(--mono); color:var(--t3);
  border:1px solid var(--hair2); background:transparent; flex:none; }
.p-step.is-next .p-step-n { border-color:var(--brass); color:var(--brass); }
.p-step.is-open .p-step-n { border-color:var(--steel); color:var(--steel); }
.p-step.is-done .p-step-n { border-color:rgba(53,208,127,.5); color:var(--ok);
  background:rgba(53,208,127,.10); }

.p-step-body h4 { font-size:14.5px; font-weight:600; letter-spacing:-.02em;
  line-height:1.3; margin:2px 0 5px; color:var(--cream); }
.p-step-body p { color:var(--t2); font-size:13px; line-height:1.6; margin:0 0 9px; }
.p-step.is-done .p-step-body h4 { color:var(--t2); }
.p-step-body .p-lab-meta { margin:0; }
.p-next-flag { color:var(--brass) !important; border-color:rgba(201,162,39,.45) !important; }

.p-step-act { align-self:center; }
.p-step-act form { margin:0; }
.p-step-state { margin:0; }

@media (max-width:520px){
  .p-step { grid-template-columns:auto 1fr; }
  .p-step-act { grid-column:2; margin-top:2px; }
}

/* A completed lab in the flat grid dims rather than disappears — it is still
   worth re-reading, it just should not compete with what you have not done. */
.p-lab-done { opacity:.72; }
.p-lab-done:hover { opacity:1; }

/* ---------- demo scope panel ----------
   Sits on the request form so the boundary of the free tier is read BEFORE
   signing up, not discovered on the dashboard afterwards. */
.p-scope { margin:26px 0 0; padding:18px 20px; border:1px solid var(--hair);
  border-radius:var(--r); background:rgba(126,163,243,.05); }
.p-scope-h { font:9px var(--mono); letter-spacing:.09em; text-transform:uppercase;
  color:var(--steel); margin:0 0 14px; }
.p-scope-list { margin:0; display:grid; gap:11px; }
.p-scope-list div { display:grid; grid-template-columns:88px 1fr; gap:12px; align-items:baseline; }
.p-scope-list dt { margin:0; font:9px var(--mono); letter-spacing:.08em;
  text-transform:uppercase; color:var(--t3); }
.p-scope-list dd { margin:0; font-size:13px; line-height:1.6; color:var(--t2); }
.p-scope-note { margin:15px 0 0; padding-top:13px; border-top:1px solid var(--hair);
  font-size:12.5px; line-height:1.6; color:var(--t3); }
@media (max-width:520px){
  .p-scope-list div { grid-template-columns:1fr; gap:3px; }
}

/* optgroup needs its own colours — the browser default is a white background
   that is unreadable against the dark select. */
.p-field select optgroup { background:var(--panel); color:var(--t3);
  font:11px var(--mono); font-style:normal; letter-spacing:.06em; }
.p-field select optgroup option { color:var(--cream); font:15px var(--sans); }

/* ---------- track completion badge ---------- */
.p-track-head { display:flex; justify-content:space-between; align-items:flex-start; gap:14px; }
.p-badge { flex:none; margin:2px 0 0; padding:5px 11px; border-radius:99px;
  font:9px var(--mono); letter-spacing:.08em; text-transform:uppercase;
  color:var(--ok); border:1px solid rgba(53,208,127,.4);
  background:rgba(53,208,127,.10); display:inline-flex; align-items:center; gap:6px; }
.p-badge s { text-decoration:none; }

/* ---------- topology path ----------
   Monospace and dimmed: it is a filename to find on the lab server, not prose. */
.p-path { font:10px var(--mono); color:var(--t3); letter-spacing:.02em;
  margin:8px 0 0; word-break:break-all; opacity:.75; }

/* "12 / 40" — the allowance is context, not the headline number. */
.p-of { font-style:normal; font-size:14px; color:var(--t3); font-weight:400; }

/* ===========================================================================
   Auth split layout  (login.php, register.php)
   ---------------------------------------------------------------------------
   Form on the left, animated brand panel on the right.

   NO JAVASCRIPT ANYWHERE. The portal's .htaccess sends script-src 'none', and
   that is a deliberate security posture on the pages that handle credentials —
   the animation is therefore pure CSS. For the same reason there are no inline
   style attributes: style-src has no 'unsafe-inline', so anything inline is
   silently dropped by the browser and the element renders unstyled.

   Only transform and opacity are animated, so the whole thing stays on the
   compositor and never triggers layout or paint. No images, no extra requests.
   =========================================================================== */

.p-auth {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  /* align-items and gap are set EXPLICITLY because portal.css already defines a
     different .p-split grid used by demo.php. These pages used to reuse that
     class name, which quietly inherited align-items:start and collapsed the art
     panel to the height of its own text. Never rely on the absence of an
     inherited value here. */
  align-items: stretch;
  gap: 0;
  min-height: 100vh;
  min-height: 100dvh;
}
.p-auth-main {
  display: flex; flex-direction: column; justify-content: center;
  padding: 48px clamp(20px, 5vw, 72px);
  max-width: 620px; width: 100%; margin: 0 auto;
}
.p-auth .p-brand { align-self: flex-start; margin-bottom: 34px; }

/* The form sheds its card border here — the split itself is the frame. */
.p-auth .p-card {
  background: transparent; border: 0; padding: 0; border-radius: 0;
}
.p-auth-foot {
  margin-top: 34px; padding-top: 20px; border-top: 1px solid var(--hair);
  font: 11px var(--mono); letter-spacing: .04em; color: var(--t3);
}
.p-auth-foot a { color: var(--t2); }
.p-auth-foot a:hover { color: var(--brass); }

/* ---- the animated panel ---- */
.p-auth-art {
  position: relative; overflow: hidden;
  background: linear-gradient(155deg, #0d1728 0%, #101d33 45%, #0a1220 100%);
  border-left: 1px solid var(--hair);
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: clamp(32px, 4vw, 60px);
  isolation: isolate;
}

/* Dot field. One gradient, no elements, no requests. */
.p-art-dots {
  position: absolute; inset: -10%; z-index: 0; pointer-events: none;
  background-image: radial-gradient(circle, rgba(126,163,243,.55) 1.1px, transparent 1.2px);
  background-size: 20px 20px;
  -webkit-mask-image: radial-gradient(58% 58% at 62% 38%, #000 22%, transparent 72%);
          mask-image: radial-gradient(58% 58% at 62% 38%, #000 22%, transparent 72%);
  opacity: .5;
}

/* Radar sweep: one rotating conic gradient. */
.p-art-sweep {
  position: absolute; z-index: 1; pointer-events: none;
  width: 128%; aspect-ratio: 1; left: 62%; top: 38%;
  translate: -50% -50%;
  border-radius: 50%;
  /* A long, soft tail rather than a hard wedge: several low-alpha stops
     fading over 110deg read as a sweep, one hard stop reads as a triangle. */
  background: conic-gradient(from 0deg,
      rgba(126,163,243,.22) 0deg,
      rgba(126,163,243,.13) 18deg,
      rgba(126,163,243,.06) 45deg,
      rgba(126,163,243,.02) 80deg,
      transparent 110deg, transparent 360deg);
  filter: blur(6px);
  -webkit-mask-image: radial-gradient(circle, #000 8%, rgba(0,0,0,.55) 42%, transparent 70%);
          mask-image: radial-gradient(circle, #000 8%, rgba(0,0,0,.55) 42%, transparent 70%);
  animation: p-sweep 7s linear infinite;
}
@keyframes p-sweep { to { rotate: 360deg; } }

/* Three rings pushing outward, staggered. */
.p-art-ring {
  position: absolute; z-index: 1; pointer-events: none;
  left: 62%; top: 38%; width: 30%; aspect-ratio: 1;
  translate: -50% -50%;
  border: 1px solid rgba(126,163,243,.42); border-radius: 50%;
  opacity: 0;
  animation: p-ring 6s cubic-bezier(.22,.61,.36,1) infinite;
}
.p-art-ring:nth-child(3) { animation-delay: 2s; }
.p-art-ring:nth-child(4) { animation-delay: 4s; }
@keyframes p-ring {
  0%   { scale: .35; opacity: 0; }
  12%  { opacity: .85; }
  100% { scale: 2.9; opacity: 0; }
}

/* The core the sweep turns around. */
.p-art-core {
  position: absolute; z-index: 2; pointer-events: none;
  left: 62%; top: 38%; width: 9px; height: 9px; translate: -50% -50%;
  border-radius: 50%; background: var(--brass-bright);
  box-shadow: 0 0 0 5px rgba(224,185,63,.14), 0 0 22px 5px rgba(224,185,63,.45);
  animation: p-core 3.4s ease-in-out infinite;
}
@keyframes p-core { 50% { opacity: .55; scale: .82; } }

.p-art-copy { position: relative; z-index: 3; max-width: 34ch; }
.p-art-copy h2 {
  font-size: clamp(26px, 2.6vw, 38px); line-height: 1.1;
  margin: 0 0 14px; color: var(--cream);
}
.p-art-copy h2 em { font-style: normal; color: var(--steel); }
.p-art-copy p { margin: 0; font-size: 13px; line-height: 1.8; color: var(--t2); }
.p-art-meta {
  display: flex; flex-wrap: wrap; gap: 8px 20px; margin-top: 26px;
  padding-top: 18px; border-top: 1px solid var(--hair);
  font: 10px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--t3);
}
.p-art-meta b { color: var(--steel); font-weight: 500; }

/* ---- narrow screens: the art is decoration, so it goes ---- */
@media (max-width: 900px) {
  .p-auth { grid-template-columns: 1fr; min-height: 0; }
  .p-auth-art { display: none; }
  .p-auth-main { padding: 40px 20px 60px; max-width: 560px; }
}

/* ---- motion sensitivity: freeze, do not remove ---- */
@media (prefers-reduced-motion: reduce) {
  .p-art-sweep, .p-art-ring, .p-art-core { animation: none; }
  .p-art-ring { opacity: .3; }
  .p-art-ring:nth-child(3) { scale: 1.7; }
  .p-art-ring:nth-child(4) { scale: 2.4; }
}

/* ---------------------------------------------------------------------------
   Brand loader — same component as the marketing site, restated against the
   portal's own tokens because this stylesheet is standalone by design.

   Stagger is baked into three keyframes with no delay and no fill-mode, so if
   the timeline never advances (throttled tab, low-power mode, webview) the bars
   rest at their normal heights and you get the static mark, not a sliver.
   --------------------------------------------------------------------------- */
.nsl-loader {
  display:inline-flex; align-items:center; flex:none;
  gap:var(--nsl-load-gap,2px); height:var(--nsl-load-h,28px);
  transform:skewY(-24deg);
}
.nsl-loader i {
  display:block; width:var(--nsl-load-w,7px); border-radius:1px;
  background:currentColor; transform-origin:bottom center;
}
.nsl-loader i:nth-child(1) { height:64%; }
.nsl-loader i:nth-child(2) { height:93%; background:var(--steel); }
.nsl-loader i:nth-child(3) { height:43%; }

.nsl-loader--sm { --nsl-load-h:15px; --nsl-load-w:4px;  --nsl-load-gap:1.5px; }
.nsl-loader--lg { --nsl-load-h:44px; --nsl-load-w:11px; --nsl-load-gap:3px; }

@media (prefers-reduced-motion:no-preference) {
  .nsl-loader i:nth-child(1) { animation:nsl-load-1 1.15s cubic-bezier(.4,0,.6,1) infinite; }
  .nsl-loader i:nth-child(2) { animation:nsl-load-2 1.15s cubic-bezier(.4,0,.6,1) infinite; }
  .nsl-loader i:nth-child(3) { animation:nsl-load-3 1.15s cubic-bezier(.4,0,.6,1) infinite; }
  @keyframes nsl-load-1 { 0%,100%{transform:scaleY(1)} 16%{transform:scaleY(.32)} 42%{transform:scaleY(1.16)} }
  @keyframes nsl-load-2 { 0%,100%{transform:scaleY(1)} 33%{transform:scaleY(.32)} 59%{transform:scaleY(1.16)} }
  @keyframes nsl-load-3 { 0%,100%{transform:scaleY(1)} 50%{transform:scaleY(.32)} 76%{transform:scaleY(1.16)} }
}
@media (prefers-reduced-motion:reduce) {
  .nsl-loader { animation:nsl-load-fade 1.6s ease-in-out infinite; }
  @keyframes nsl-load-fade { 0%,100%{opacity:1} 50%{opacity:.45} }
}

.nsl-loading {
  display:grid; place-items:center; gap:16px;
  min-height:170px; padding:40px 0; text-align:center;
}
.nsl-loading p {
  margin:0; font:10px var(--mono); text-transform:uppercase;
  letter-spacing:.14em; color:var(--t3);
}

.p-btn.is-busy { pointer-events:none; gap:12px; }
.p-btn .nsl-loader i { background:currentColor; }

/* Demo form — access-method choice (WebUI vs VPN) */
.p-access{border:0;margin:0 0 18px;padding:0;}
.p-access > span{display:block;font:9px var(--mono);letter-spacing:.09em;text-transform:uppercase;color:var(--t3);margin-bottom:9px;}
.p-access-opt{display:block;position:relative;padding:13px 14px 13px 42px;margin:0 0 9px;border:1px solid rgba(174,198,248,.2);border-radius:9px;cursor:pointer;transition:border-color .15s,background .15s;}
.p-access-opt:hover{border-color:rgba(126,163,243,.55);background:rgba(126,163,243,.06);}
.p-access-opt input{position:absolute;left:15px;top:16px;accent-color:#7ea3f3;width:16px;height:16px;}
.p-access-opt b{display:block;font-size:.92rem;color:#fff;font-weight:650;}
.p-access-sub{display:block;font-size:.76rem;color:var(--p-muted,#8fa3c0);margin-top:2px;}
.p-access-opt:has(input:checked){border-color:#7ea3f3;background:rgba(126,163,243,.1);}

/* ===== Admin console ===== */
.p-admin{background:var(--ink,#0b1115);}
.p-topbar{display:flex;align-items:center;gap:20px;flex-wrap:wrap;padding:13px clamp(16px,4vw,40px);border-bottom:1px solid rgba(174,198,248,.16);background:rgba(9,14,19,.92);position:sticky;top:0;z-index:20;backdrop-filter:blur(12px);}
.p-admin-tag{font:600 8px/1 var(--mono);letter-spacing:.16em;color:#0b1115;background:#f0c040;padding:3px 6px;border-radius:4px;margin-left:8px;vertical-align:middle;}
.p-top-actions{margin-left:auto;display:flex;gap:8px;}
.p-admin-main{max-width:1180px;margin:0 auto;padding:clamp(20px,4vw,40px);}
.p-admin-main section{margin-bottom:44px;scroll-margin-top:80px;}
.p-admin-h1{margin:4px 0 22px;color:#fff;font-size:clamp(26px,4vw,38px);letter-spacing:-.04em;}
.p-admin-h2{margin:0 0 16px;color:#fff;font-size:18px;letter-spacing:-.02em;display:flex;align-items:center;gap:10px;}
.p-count{font:500 11px/1 var(--mono);color:var(--t3,#8fa3c0);border:1px solid rgba(174,198,248,.2);border-radius:20px;padding:3px 9px;}
.p-flash{margin:0 0 22px;padding:12px 15px;border-radius:8px;font-size:13px;border:1px solid;}
.p-flash--ok{border-color:rgba(121,228,179,.4);background:rgba(121,228,179,.08);color:#b9f3d7;}
.p-flash--err{border-color:rgba(238,92,92,.45);background:rgba(238,92,92,.08);color:#ffc2c2;}
.p-stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(118px,1fr));gap:12px;}
.p-stat{border:1px solid rgba(174,198,248,.18);border-radius:10px;background:rgba(255,255,255,.02);padding:16px 15px;}
.p-stat b{display:block;font-size:26px;color:#fff;letter-spacing:-.03em;line-height:1;}
.p-stat span{display:block;margin-top:7px;font:500 9px/1.2 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--t3,#8fa3c0);}
.p-stat--warn{border-color:rgba(240,180,64,.5);background:rgba(240,180,64,.08);}
.p-stat--warn b{color:#f5c869;}
.p-table-wrap{overflow-x:auto;border:1px solid rgba(174,198,248,.14);border-radius:10px;}
.p-table{width:100%;border-collapse:collapse;font-size:12.5px;min-width:720px;}
.p-table th{text-align:left;padding:11px 13px;font:600 9px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--t3,#8fa3c0);background:rgba(255,255,255,.025);border-bottom:1px solid rgba(174,198,248,.16);}
.p-table td{padding:10px 13px;border-bottom:1px solid rgba(174,198,248,.08);color:var(--paper,#e6ecf5);vertical-align:middle;}
.p-table tr:last-child td{border-bottom:0;}
.p-table tr:hover td{background:rgba(126,163,243,.04);}
.p-mono{font:500 11.5px/1.4 var(--mono);color:#c8d4e6;}
.p-dim{color:var(--t3,#8fa3c0);}
.p-inline-form{display:inline-block;margin:0;}
.p-inline-form select{min-height:0;padding:5px 8px;font-size:12px;background:var(--panel-2,#141d29);color:#fff;border:1px solid rgba(174,198,248,.24);border-radius:5px;appearance:auto;}
.p-row-actions{display:flex;gap:6px;flex-wrap:wrap;}
.p-btn-xs{padding:5px 10px;font-size:11px;border-radius:6px;min-height:0;}
.p-badge{display:inline-block;font:600 9px/1 var(--mono);letter-spacing:.04em;color:#9db4d6;border:1px solid rgba(174,198,248,.3);border-radius:20px;padding:2px 7px;text-transform:uppercase;}
.p-badge--warn{color:#f5c869;border-color:rgba(240,180,64,.45);}
.p-badge--vpn{color:#c58bff;border-color:rgba(160,110,255,.5);}
.p-yes{color:#79e4b3;}.p-no{color:#ff9d9d;}
.p-pill{display:inline-block;font:600 10px/1 var(--mono);padding:3px 9px;border-radius:20px;text-transform:capitalize;}
.p-pill--leased{color:#7ea3f3;background:rgba(126,163,243,.14);}
.p-pill--free{color:#79e4b3;background:rgba(121,228,179,.14);}
.p-pill--retired,.p-pill--held{color:#ff9d9d;background:rgba(238,92,92,.12);}
.p-health{list-style:none;margin:0;padding:0;border:1px solid rgba(174,198,248,.14);border-radius:10px;overflow:hidden;}
.p-health li{display:flex;justify-content:space-between;gap:16px;padding:12px 16px;border-bottom:1px solid rgba(174,198,248,.08);font-size:13px;}
.p-health li:last-child{border-bottom:0;}
.p-health span{color:var(--t3,#8fa3c0);}
.p-health b{color:#fff;font-weight:600;text-align:right;}
.p-nav-admin{color:#f0c040 !important;font-weight:600;}
.p-vpn-up{display:flex;gap:5px;align-items:center;margin-top:5px;}
.p-vpn-up input[type=file]{font-size:10px;max-width:120px;color:var(--t3,#8fa3c0);}
.p-vpn-up input[type=file]::file-selector-button{font-size:10px;padding:3px 7px;margin-right:5px;border-radius:5px;border:1px solid rgba(174,198,248,.3);background:var(--panel-2,#141d29);color:#c8d4e6;cursor:pointer;}
/* vLabs 5-bar logo mark in portal headers (replaces the old 3-bar mark) */
.p-mark-img{display:inline-block;vertical-align:middle;height:24px;width:auto;}
.p-brand:hover .p-mark-img{filter:brightness(1.1);}
/* Plan request / upgrade cards */
.p-plan-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;}
.p-plan-opt{position:relative;display:block;border:1px solid rgba(174,198,248,.2);border-radius:11px;padding:18px 18px 18px 44px;cursor:pointer;transition:border-color .15s,background .15s;}
.p-plan-opt:hover{border-color:rgba(126,163,243,.5);background:rgba(126,163,243,.05);}
.p-plan-opt input{position:absolute;left:16px;top:20px;accent-color:#7ea3f3;width:16px;height:16px;}
.p-plan-opt:has(input:checked){border-color:#7ea3f3;background:rgba(126,163,243,.1);}
.p-plan-body b{display:block;color:#fff;font-size:15px;letter-spacing:-.02em;}
.p-plan-price{display:block;margin:6px 0 8px;color:#7ea3f3;font-size:17px;font-weight:700;}
.p-plan-price i{font-style:normal;font-size:12px;color:var(--t3,#8fa3c0);font-weight:500;}
.p-plan-blurb{display:block;color:var(--t3,#8fa3c0);font-size:12.5px;line-height:1.5;}
.p-upgrade-cta{display:inline-block;margin-left:12px;font:600 10px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:#0b1115;background:#7ea3f3;padding:7px 12px;border-radius:6px;vertical-align:middle;}
.p-upgrade-cta:hover{filter:brightness(1.08);}

/* ===== Telemetry ===== */
.p-live-dot{font:600 11px/1 var(--mono);color:#79e4b3;letter-spacing:.04em;margin-right:6px;}
.p-live-dot.p-live-stale{color:#f5c869;}
.p-tel-gauges{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:14px;}
.p-gauge{border:1px solid rgba(174,198,248,.18);border-radius:12px;background:rgba(255,255,255,.02);padding:18px 18px 16px;}
.p-gauge-top{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:11px;}
.p-gauge-top span{font:600 9px/1 var(--mono);letter-spacing:.09em;text-transform:uppercase;color:var(--t3,#8fa3c0);}
.p-gauge-top b{font-size:24px;color:#fff;letter-spacing:-.03em;}
.p-gauge .p-meter{height:9px;border-radius:6px;background:rgba(174,198,248,.12);overflow:hidden;}
.p-gauge .p-meter i{display:block;height:100%;width:0;border-radius:6px;transition:width .5s cubic-bezier(.3,.7,.3,1),background .3s;}
.p-meter i.p-w-green{background:linear-gradient(90deg,#4fd1a0,#79e4b3);}
.p-meter i.p-w-amber{background:linear-gradient(90deg,#e0a83e,#f5c869);}
.p-meter i.p-w-red{background:linear-gradient(90deg,#e05c5c,#ff9d9d);}
.p-gauge-sub{margin:9px 0 0;font:500 11px/1.4 var(--mono);color:var(--t3,#8fa3c0);}
.p-svc b{font-size:15px !important;}
.p-svc-up b{color:#79e4b3;}
.p-svc-down b{color:#ff9d9d;}
.p-svc em{display:block;margin-top:6px;font:500 10px/1.3 var(--mono);color:var(--t3,#8fa3c0);font-style:normal;}

/* ═══════════ CSP-SAFE LAYOUT UTILITIES ═══════════
   The portal is served with Content-Security-Policy: style-src 'self' and NO
   'unsafe-inline', so the browser silently DROPS every style="" attribute.
   Twenty-five of them were doing real layout work across account.php,
   plan-request.php, admin.php, telemetry.php and index.php — none of it was
   reaching the page. These classes replace them.
   Anything that needs styling in the portal must be a class. There is no
   inline-style escape hatch here, and adding one would weaken the CSP that
   makes the rest of the policy worth having.
   (The HTML EMAIL template in _lib.php is the one legitimate exception —
   mail clients have no external stylesheet and CSP does not apply to them.) */
.p-flush      { padding-left:0; padding-right:0; }
.p-flush-b    { padding-left:0; padding-right:0; border-bottom:0; }
.p-noborder   { border:0; }
.p-pad-md     { padding:22px 24px; margin-bottom:26px; }
.p-pad-lg     { padding:24px 26px; }
.p-pad-xl     { padding:30px 32px; }
.p-w-640      { max-width:640px; }
.p-w-860      { max-width:860px; }
.p-measure    { max-width:60ch; margin:0 0 26px; }
.p-mt-12      { margin-top:12px; }
.p-mt-14      { margin-top:14px; }
.p-mt-16      { margin-top:16px; }
.p-mt-20      { margin-top:20px; }
.p-mt-22      { margin-top:22px; }
.p-mt-24      { margin-top:24px; }
.p-mt-30      { margin-top:30px; }
.p-mb-18      { margin-bottom:18px; }
.p-fs-12      { font-size:12px; }
.p-self-start { align-self:start; }
.p-title-sm   { margin:0 0 6px; color:#fff; font-size:17px; letter-spacing:-.02em; }
.p-sub-sm     { margin:0 0 18px; font-size:13px; }
.p-tag-steel  { color:#7ea3f3; border-color:rgba(126,163,243,.5); }
.p-tag-mint   { color:#79e4b3; border-color:rgba(121,228,179,.4); }

/* ---------- skip link ----------
   Keyboard and screen-reader users land on the header nav on every page load
   and have to tab past the whole of it to reach the content. The marketing
   pages have had a skip link since launch; the portal — the part people
   actually use repeatedly — had none.
   Off-screen until focused, then pinned top-left over everything. */
.p-skip {
  position:absolute; left:-9999px; top:0; z-index:100;
  background:var(--brass); color:#221a02; font:600 13px var(--sans);
  padding:12px 18px; border-radius:0 0 4px 0; text-decoration:none;
}
.p-skip:focus {
  left:0; outline:2px solid var(--cream); outline-offset:2px; color:#221a02;
}

/* ---------- first-run steps ----------
   Shown inside the live-lease card. EVE-NG on this build cannot be deep-linked
   to a specific lab (its /lab route takes no path parameter), so the button can
   only drop someone in the file browser. These three lines say the part the
   link cannot: which folder is yours and what to do when you get there. */
.p-firstrun { grid-column:1/-1; list-style:none; counter-reset:fr; margin:18px 0 0; padding:16px 0 0;
  border-top:1px solid var(--hair); display:grid; gap:9px; }
.p-firstrun li { counter-increment:fr; position:relative; padding-left:28px;
  font-size:13px; line-height:1.6; color:var(--t2); }
.p-firstrun li::before { content:counter(fr); position:absolute; left:0; top:1px;
  width:18px; height:18px; border-radius:50%; display:grid; place-items:center;
  font:10px var(--mono); color:var(--steel); border:1px solid rgba(126,163,243,.45); }
.p-firstrun b { color:var(--cream); font-weight:600; }

/* ---------- certificate maker ---------- */
.p-grid-2 { grid-template-columns:repeat(2,1fr); }
@media (max-width:760px){ .p-grid-2{ grid-template-columns:1fr; } }
.p-btn-ghost { background:transparent; color:var(--t2); border-color:var(--hair2); }
.p-btn-ghost:hover { color:var(--bad); border-color:rgba(255,138,128,.5); }
/* security panel: remediation column reads as prose, not as data */
.p-fix { font-size:12.5px; color:var(--t3); line-height:1.55; max-width:46ch; }
