﻿/* ===========================================================================
   ZNetLab — one design system

   Two products were merged into this: a teaching site with a browser
   simulator, and a lab platform that runs real network OS images. They were
   built at different times and looked it — one light and editorial, one dark
   and operational. A person moving between them could tell they were two
   things, which is exactly the wrong impression.

   So there is one palette, one type scale, one set of components, and one
   header. Everything below is defined once here and used everywhere.

   HOW THE MERGE WORKS WITHOUT REWRITING 350 KB OF MARKUP: the teaching site
   was already written against CSS custom properties — 407 uses of var()
   across 22 tokens. So `bridge.css` redefines those 22 tokens in terms of
   the ones below, and the whole site retheme happens without touching a
   single element. That is the entire reason this merge is a day's work
   rather than a month's.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   TWO MODES

   Dark is the base, on bare :root, because this began as an operations tool
   and every component below was drawn against it. Light is an override in
   two places, and it has to be BOTH:

     @media (prefers-color-scheme: light)  — follow the machine, for the
                                             reader who never touches a toggle
     :root[data-theme="light"]             — obey the toggle, which must beat
                                             the machine in either direction

   The media query is guarded with :not([data-theme="dark"]) so that choosing
   dark on a light machine actually sticks. Without that guard the system
   preference would keep winning and the toggle would look broken.

   Only tokens are redefined below. No component is written twice, which is
   the entire reason two modes cost a screenful rather than a second
   stylesheet.
   --------------------------------------------------------------------------- */

:root{
  /* ---- surfaces. Dark, because these are screens people leave open all
     day beside a terminal, and because the lab canvas and the console are
     the two things everything else exists to serve. */
  --bg:#0b1220;
  --panel:#111a2b;
  --panel2:#0e1626;
  --panel3:#132238;
  --line:#1f2c44;
  --line2:#18233a;

  /* ---- ink */
  --ink:#e6edf7;
  --ink2:#93a4bf;
  --ink3:#8c9aad;
  --ink4:#7e899a;

  /* ---- accents. One blue carries every interactive thing; the rest are
     states, not decoration. */
  --accent:#4575c6;
  --accent2:#6d9ae6;
  --accent-ink:#ffffff;
  --ok:#34d399;
  --warn:#fbbf24;
  --bad:#f87171;
  --idle:#64748b;
  --pro:#a78bfa;

  /* ---- AREA COLOURS. One hue per part of the product, so a screen says
     where you are before you read a word: the Accounts tab is blue
     wherever it appears, the image library is violet, watching the server
     is teal, marketing is amber, the host is slate.

     THEY ARE NOT STATE COLOURS AND THEY ARE NEVER USED AS ONE. Green,
     amber and red keep their one meaning — up, worth a look, wrong — and
     they appear on pills, figures and dots. An area colour only ever
     appears on the furniture: an icon chip, a section mark, the rule under
     a heading. Different places, different jobs, so a reader is never
     asked whether an amber chip means "marketing" or "look at this".

     Tints are mixed from these at use, with color-mix, rather than kept as
     another dozen tokens. */
  --area-accounts:#6ea8ff;
  --area-images:#b794f6;
  --area-monitor:#2dd4bf;
  --area-market:#f6ad55;
  --area-server:#94a3b8;
  --area-community:#f472b6;
  /* ---- CABLE COLOURS. A cable is a KIND of thing, like an area is, so it
     takes a hue of its own and never a state colour: what the cable is
     DOING is said by whether each half of it is lit (see .wire.half.off in
     lab/index.html), not by its hue. Where an industry already has a
     colour for a cable — fibre yellow, console grey — that is the one; the
     rest are picked to be told apart at 2px on a dark canvas. */
  --cable-copper:#7f9cc0;
  --cable-crossover:#4fd1c5;
  --cable-fiber:#e3b341;
  --cable-serial:#a78bfa;
  --cable-console:#8b93a7;
  --cable-coax:#b08968;
  --cable-wireless:#34d399;

  /* Tints for the soft-filled notes and badges. Kept as tokens because the
     alpha that reads on dark is not the alpha that reads on white. */
  /* Raised from .09: at that strength a tinted box was white with a
     rumour of colour on it, and the thing the colour was there to say —
     this is fine, this needs a look, this is wrong — had to be read out of
     the words instead. */
  --tint-accent:rgba(75,127,214,.16);
  --tint-ok:rgba(52,211,153,.15);
  --tint-warn:rgba(251,191,36,.15);
  --tint-bad:rgba(248,113,113,.15);
  --tint-pro:rgba(167,139,250,.18);
  --edge-accent:rgba(75,127,214,.32);
  --edge-ok:rgba(52,211,153,.3);
  --edge-warn:rgba(251,191,36,.3);
  --edge-bad:rgba(248,113,113,.3);

  /* A console is dark in both modes. A terminal that turns white is not a
     light theme, it is a broken terminal — nobody reads scrollback on
     paper-white, and every colour scheme for one assumes a dark ground. */
  --term-bg:#05090f;
  --term-ink:#c9d6e8;

  /* ---- type

     POPPINS, which is the typeface the rest of this company's sites are set
     in (softbik.com). Two products in the same hands that are set in two
     different faces read as two companies, and the one a reader arrives
     from is the one that sets the expectation.

     It is a geometric sans with a tall x-height, so it is wider than Inter
     at the same size and sits a shade lower; the body size and line height
     below were left alone because they still read, but anything measured in
     characters rather than pixels is worth looking at twice.

     Weights 300-800 are requested. The fallbacks are deliberately the ones
     softbik names, in its order, so a reader whose font request is blocked
     gets the same second choice on both sites rather than two. */
  --sans:'Poppins','Segoe UI',-apple-system,BlinkMacSystemFont,'Helvetica Neue',Arial,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Consolas,monospace;

  /* ---- geometry. Three radii, not nine — a page with nine corner radii
     looks assembled rather than designed. */
  --r-sm:7px;
  --r:11px;
  --r-lg:16px;
  --maxw:1240px;
  --shadow:0 1px 2px rgba(0,0,0,.28), 0 4px 14px rgba(0,0,0,.22);
  --shadow-l:0 16px 44px rgba(0,0,0,.42);

  /* ---- chrome */
  --header-h:54px;
  --scroll-thumb:#22304a;
  --grid-dot:#1b2740;
  /* The topology canvas. A link has to read against the canvas in both
     modes, and neither ink nor line is right for it: ink is too loud for a
     wire and line is too quiet. */
  --wire:#33507a;
  --node-edge:#3d5478;
}

/* The light palette, written once and applied twice. It is not the dark one
   inverted: on white, shadow does the separating that a border does on dark,
   accents must go DARKER to hold contrast, and the greys need more space
   between them because light surfaces collapse together faster. */
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]){
    --bg:#dae4f2;
    --panel:#ffffff;
    --panel2:#e8eff9;
    --panel3:#d6e1f0;
    --line:#bfcee3;
    --line2:#d3deee;

    --ink:#131c29;
    --ink2:#4d5c72;
    --ink3:#4c5669;
    --ink4:#5e6570;

    --accent:#174190;
    --accent2:#274390;
    --accent-ink:#ffffff;
    --ok:#0d8055;
    --warn:#96610a;
    --bad:#c22f2f;
    --idle:#7e8ca1;
    --pro:#6244cc;

    --area-accounts:#1d4ed8;
    --area-images:#7c3aed;
    --area-monitor:#0d9488;
    --area-market:#b45309;
    --area-server:#475569;
    --area-community:#be185d;
  --cable-copper:#4a6b96;
    --cable-crossover:#0d9488;
    --cable-fiber:#b7791f;
    --cable-serial:#7c3aed;
    --cable-console:#64748b;
    --cable-coax:#8a5a3b;
    --cable-wireless:#0d8055;

    --tint-accent:rgba(23,65,144,.16);
    --tint-ok:rgba(13,128,85,.13);
    --tint-warn:rgba(150,97,10,.14);
    --tint-bad:rgba(194,47,47,.12);
    --tint-pro:rgba(98,68,204,.1);
    --edge-accent:rgba(23,65,144,.26);
    --edge-ok:rgba(13,128,85,.26);
    --edge-warn:rgba(150,97,10,.28);
    --edge-bad:rgba(194,47,47,.24);

    --shadow:0 1px 2px rgba(19,28,41,.06), 0 4px 14px rgba(19,28,41,.07);
    --shadow-l:0 16px 44px rgba(19,28,41,.16);
    --scroll-thumb:#c6d0de;
    --grid-dot:#d6dfeb;
    --wire:#8aa3c4;
    --node-edge:#a9b8cd;  }
}

:root[data-theme="light"]{
  --bg:#dae4f2;
  --panel:#ffffff;
  --panel2:#e8eff9;
  --panel3:#d6e1f0;
  --line:#bfcee3;
  --line2:#d3deee;

  --ink:#131c29;
  --ink2:#4d5c72;
  --ink3:#4c5669;
  --ink4:#5e6570;

  --accent:#174190;
  --accent2:#274390;
  --accent-ink:#ffffff;
  --ok:#0d8055;
  --warn:#96610a;
  --bad:#c22f2f;
  --idle:#7e8ca1;
  --pro:#6244cc;

  --area-accounts:#1d4ed8;
  --area-images:#7c3aed;
  --area-monitor:#0d9488;
  --area-market:#b45309;
  --area-server:#475569;
  --area-community:#be185d;
  --cable-copper:#4a6b96;
  --cable-crossover:#0d9488;
  --cable-fiber:#b7791f;
  --cable-serial:#7c3aed;
  --cable-console:#64748b;
  --cable-coax:#8a5a3b;
  --cable-wireless:#0d8055;

  --tint-accent:rgba(23,65,144,.16);
  --tint-ok:rgba(13,128,85,.13);
  --tint-warn:rgba(150,97,10,.14);
  --tint-bad:rgba(194,47,47,.12);
  --tint-pro:rgba(98,68,204,.1);
  --edge-accent:rgba(23,65,144,.26);
  --edge-ok:rgba(13,128,85,.26);
  --edge-warn:rgba(150,97,10,.28);
  --edge-bad:rgba(194,47,47,.24);

  --shadow:0 1px 2px rgba(19,28,41,.06), 0 4px 14px rgba(19,28,41,.07);
  --shadow-l:0 16px 44px rgba(19,28,41,.16);
  --scroll-thumb:#c6d0de;
  --grid-dot:#d6dfeb;
  --wire:#8aa3c4;
  --node-edge:#a9b8cd;}

/* Only the transition of colour, and only on the things that carry it. A
   blanket `transition: all` would drag every layout change with it. */
body,.nb-head,.nb-card,.nb-btn,.nb-note,.nb-menu,.nb-chip{
  transition:background-color .18s ease, color .18s ease, border-color .18s ease;
}
@media (prefers-reduced-motion: reduce){
  body,.nb-head,.nb-card,.nb-btn,.nb-note,.nb-menu,.nb-chip{transition:none}
}

*{box-sizing:border-box}
html,body{height:100%}
/* Pages that scroll as a document grow with their content, so the sticky
   header has the whole page to stick in. A body held at 100% made it stop
   sticking after one screen and scroll away. The full-screen apps (Real
   labs, the simulator) keep the fixed height they lay themselves out in. */
body:not(.nb-app){height:auto;min-height:100%}
body{
  margin:0;
  background:var(--bg);
  color:var(--ink);
  font:15px/1.65 var(--sans);
  -webkit-font-smoothing:antialiased;
}
button,input,select,textarea{font:inherit;color:inherit}
button{cursor:pointer;background:none;border:none}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code,kbd,pre{font-family:var(--mono)}
img{max-width:100%}

::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:6px}
::-webkit-scrollbar-thumb:hover{filter:brightness(1.15)}
::-webkit-scrollbar-track{background:transparent}
/* Tells the browser which way round the page is, so form controls, the
   scrollbar gutter and the caret all follow rather than staying dark. */
:root{color-scheme:dark}
:root[data-theme="light"]{color-scheme:light}
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]){color-scheme:light}
}

/* ==========================================================================
   The shared header. Injected by shell.js into every page of both halves,
   so wherever you are, the way out is in the same place.
   ========================================================================== */
.nb-head{
  position:sticky;top:0;z-index:80;
  display:flex;align-items:center;gap:14px;
  height:var(--header-h);flex:0 0 var(--header-h);
  padding:0 16px;
  /* From the token, NOT a literal. This was hardcoded to a dark rgba, which
     made the header a dark bar with dark text in light mode — the brand and
     every nav link came out at 1.02:1, which is invisible. A browser check
     caught it on three pages; the fourth only passed because another
     stylesheet happened to override it. */
  background:var(--panel);
  border-bottom:1px solid var(--line);
  /* Solid, always: nothing shows through while the page scrolls under it. */
  opacity:1;backdrop-filter:none;-webkit-backdrop-filter:none;
}
/* The blur is worth having, but only where the bar is translucent, and it
   is not — a solid bar with backdrop-filter costs a compositor layer for
   nothing. */
.nb-brand{display:flex;align-items:center;gap:9px;font-weight:700;letter-spacing:-.2px;
          font-size:16px;color:var(--ink)}
.nb-brand:hover{text-decoration:none}
.nb-brand b{color:var(--accent);font-weight:700}

.nb-nav{display:flex;gap:2px;margin-left:6px}
.nb-nav a{
  padding:7px 13px;border-radius:var(--r-sm);
  color:var(--ink2);font-weight:500;font-size:13.5px;white-space:nowrap;
}
.nb-nav a:hover{background:var(--panel2);color:var(--ink);text-decoration:none}
.nb-nav a.on{background:var(--accent);color:var(--accent-ink);font-weight:600}
.nb-nav a .tag{
  display:inline-block;margin-left:6px;padding:1px 6px;border-radius:20px;
  font-size:9.5px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;
  background:rgba(52,211,153,.16);color:var(--ok);
}
.nb-nav a.on .tag{background:rgba(255,255,255,.22);color:var(--accent-ink)}
.nb-nav a .tag.pro{background:rgba(167,139,250,.18);color:var(--pro)}

/* A dropdown in the nav. The trigger and the panel are one hover group, so
   the pointer can cross the gap between them without the panel closing —
   the single commonest way a hover menu is made unusable.

   THE GAP WAS NOT CROSSABLE. The panel is drawn 6px below the trigger, and
   those 6px belonged to nobody: the pointer left the trigger, the group was
   no longer hovered, and the panel was gone before it could be reached. The
   comment above described the intent and the rules did the opposite — the
   menu could be seen opening and never used, which is exactly what a hover
   menu is accused of when it is built wrong.

   `.nb-sub::before` is the bridge. It is part of the panel, so hovering it
   keeps the group hovered, and it covers the 6px the pointer has to cross.
   It exists only while the panel is open, because the panel it hangs off is
   `display:none` the rest of the time. */
.nb-group{position:relative;display:inline-flex}
.nb-nav .caret{
  display:inline-block;width:0;height:0;margin-left:6px;vertical-align:middle;
  border-left:3.5px solid transparent;border-right:3.5px solid transparent;
  border-top:4px solid currentColor;opacity:.65;
  transition:transform .16s ease,opacity .16s ease;
}
.nb-sub{
  position:absolute;left:0;top:calc(100% + 6px);min-width:196px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:6px;box-shadow:var(--shadow-l);z-index:95;
  display:none;flex-direction:column;
}
.nb-sub::before{
  content:'';position:absolute;left:0;right:0;top:-8px;height:8px;
}
/* `.open` is the touch and keyboard path: a screen with no pointer has no
   hover to give, so shell.js opens the panel on the first tap and lets the
   second one follow the link. */
.nb-group:hover .nb-sub,
.nb-group:focus-within .nb-sub,
.nb-group.open .nb-sub{display:flex}
/* The trigger stays lit while its own panel is open, so the panel reads as
   belonging to it rather than floating under the bar. */
.nb-group:hover > a,
.nb-group.open > a{background:var(--panel2);color:var(--ink)}
.nb-group:hover .caret,
.nb-group.open .caret{transform:rotate(180deg);opacity:1}
.nb-sub a{
  padding:7px 11px;border-radius:var(--r-sm);
  font-size:13px;font-weight:500;color:var(--ink2);white-space:nowrap;
}
.nb-sub a:hover{background:var(--panel2);color:var(--ink);text-decoration:none}
.nb-sub .sep{display:block;height:1px;background:var(--line);margin:5px 8px}

.nb-spacer{flex:1}

/* ---------------------------------------------------------------------------
   The header, in the shape the teaching half always had it: wordmark, a row
   of links, a search box, a Contribute menu, the theme switch, and the
   account at the end. That layout was hidden by the merge and replaced with
   a plainer bar; this brings it back as the ONE header, so every page gets
   it rather than only the one page that used to own it.
   --------------------------------------------------------------------------- */
.nb-search{
  display:flex;align-items:center;gap:7px;
  /* 190px was the floor, and at 1180px — an iPad Pro in landscape — the bar
     came out 7px wider than the screen because of it. It shrinks further
     now; the placeholder truncates before the layout breaks. */
  height:34px;padding:0 11px;min-width:150px;max-width:300px;flex:1 1 200px;
  border:1px solid var(--line);border-radius:20px;background:var(--panel2);
}
.nb-search:focus-within{border-color:var(--accent)}
.nb-search svg{flex:0 0 auto;color:var(--ink3)}
.nb-search input{
  flex:1;min-width:0;border:none;background:none;outline:none;
  color:var(--ink);font:13.5px var(--sans);
}
.nb-search input::placeholder{color:var(--ink4)}
.nb-search .x{color:var(--ink3);font-size:15px;line-height:1;padding:0 2px}
.nb-search .x:hover{color:var(--ink)}

/* The Contribute trigger: a link that looks like the others but carries a
   caret, so it is obvious before clicking that something opens. */
.nb-ddbtn{
  display:inline-flex;align-items:center;gap:6px;
  padding:7px 13px;border-radius:var(--r-sm);
  color:var(--ink2);font-weight:500;font-size:13.5px;white-space:nowrap;
}
.nb-ddbtn:hover{background:var(--panel2);color:var(--ink)}

/* The account, at the end of the bar: face, first name, caret. */
.nb-av-btn{
  display:flex;align-items:center;gap:8px;
  padding:3px 10px 3px 3px;border-radius:22px;
  background:var(--panel2);border:1px solid var(--line);
}
.nb-av-btn:hover{border-color:var(--accent)}
.nb-av-btn .nm{font-size:13px;font-weight:600;color:var(--ink);max-width:96px;
               overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.nb-av-btn img,.nb-av-btn .init{
  width:28px;height:28px;border-radius:50%;flex:0 0 28px;display:block;object-fit:cover;
}
.nb-av-btn .init{display:grid;place-items:center;color:#fff;font-weight:700;font-size:11.5px}

/* ---------------------------------------------------------------------------
   SIGNING IN, WHEREVER YOU ARE

   Signed out, the header's account control is not a drawer of your things —
   there are none yet — it is one focused task. So it opens as the same
   centred dialog the rest of the product uses, with the same card the login
   page and the site's own sign-in modal use: Poppins, the navy, small
   corners. Three doors that look like three products is how one product
   stops feeling like one.

   The drawer stays for a signed-in account, which IS a panel of your things.
   --------------------------------------------------------------------------- */
.nb-drawer.as-modal .nb-drawer-panel{
  /* position:relative, NOT static — and this is the whole bug it fixes.
     The backdrop is position:absolute, so it paints in the positioned phase;
     a static panel paints in the in-flow phase, which is UNDERNEATH it. The
     card was visible through the translucent dim and completely unclickable:
     every click on the email field, the password field and the button landed
     on the backdrop instead. */
  position:relative;z-index:1;
  width:min(400px,100%);margin:auto;
  border:1px solid var(--sb-line, var(--line));border-left:1px solid var(--sb-line, var(--line));
  border-radius:5px;transform:none;box-shadow:0 24px 60px rgba(4,9,17,.45);
  background:var(--sb-card, var(--panel));max-height:calc(100vh - 40px);
}
.nb-drawer.as-modal{display:flex;align-items:center;justify-content:center;padding:20px;overflow:auto}
.nb-drawer.as-modal .nb-drawer-back{background:rgba(10,16,22,.55)}
.nb-drawer.as-modal.on .nb-drawer-back{opacity:1}

/* The sign-in card — the same values as login.html. */
.nb-signin{
  --sb-brand:#174190; --sb-brand-d:#274390;
  --sb-ink:#2c2d3f;   --sb-ink2:#5b5f70; --sb-ink3:#868686;
  --sb-card:#ffffff;  --sb-line:#e3e7ee; --sb-field:#fbfcfe;
  font-family:var(--sans);
  color:var(--sb-ink);
}
/* Dark only when the machine asks for it, or the toggle says so. Written
   without the media query first, this painted a dark card on a light page
   for everybody who had never touched the theme switch — `data-theme` is
   ABSENT in that case, and :not([data-theme="light"]) matches absent. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .nb-signin{
    --sb-brand:#4b7fd6; --sb-brand-d:#6d9ae6;
    --sb-ink:#e8ecf4;   --sb-ink2:#aab3c4; --sb-ink3:#8a93a6;
    --sb-card:#1a1d27;  --sb-line:#2a2f3c; --sb-field:#151822;
  }
}
:root[data-theme="dark"] .nb-signin{
  --sb-brand:#4b7fd6; --sb-brand-d:#6d9ae6;
  --sb-ink:#e8ecf4;   --sb-ink2:#aab3c4; --sb-ink3:#8a93a6;
  --sb-card:#1a1d27;  --sb-line:#2a2f3c; --sb-field:#151822;
}
.nb-signin h2{margin:0 0 6px;font-size:26px;font-weight:600;letter-spacing:-.2px;color:var(--sb-ink)}
.nb-signin .sub{color:var(--sb-ink3);font-size:14px;margin:0 0 22px;line-height:1.75;font-weight:300}
.nb-signin label{display:block;font-size:12px;letter-spacing:.2px;color:var(--sb-ink2);
                 font-weight:500;margin:16px 0 6px}
.nb-signin input{display:block;width:100%;padding:12px 14px;border:1px solid var(--sb-line);
                 border-radius:4px;background:var(--sb-field);color:var(--sb-ink);
                 font:400 14.5px/1.5 var(--sans)}
.nb-signin input:focus{outline:none;border-color:var(--sb-brand);
                       box-shadow:0 0 0 3px rgba(23,65,144,.10)}
.nb-signin input::placeholder{color:var(--sb-ink3);font-weight:300}
.nb-signin .go{width:100%;margin-top:24px;padding:13px;border:none;border-radius:4px;
               background:var(--sb-brand);color:#fff;cursor:pointer;
               font:600 14.5px/1 var(--sans);letter-spacing:.2px}
.nb-signin .go:hover{background:var(--sb-brand-d)}
.nb-signin .go:disabled{opacity:.6;cursor:not-allowed}
.nb-signin .alt{margin-top:18px;text-align:center;font-size:13.5px;color:var(--sb-ink3);font-weight:300}
.nb-signin .alt a{color:var(--sb-brand);cursor:pointer;font-weight:500}
.nb-signin .why{margin-top:20px;padding-top:16px;border-top:1px solid var(--sb-line);
                font-size:13px;color:var(--sb-ink3);line-height:1.8;font-weight:300}
.nb-signin .why p{margin:0 0 8px}
.nb-signin .why p:last-child{margin-bottom:0}
.nb-signin .why a{color:var(--sb-brand);cursor:pointer;font-weight:500}
.nb-signin .err{color:#a52020;background:rgba(192,38,38,.07);border-left:3px solid #c02626;
                border-radius:4px;padding:11px 13px;font-size:13px;margin-top:14px;line-height:1.7}
.nb-signin .mark{display:flex;align-items:center;gap:10px;margin-bottom:20px}
.nb-signin .mark b{font-size:20px;font-weight:700;letter-spacing:-.2px;color:var(--sb-ink)}
.nb-signin .mark b i{font-style:normal;color:var(--sb-brand)}

/* Notifications. The button is only drawn for somebody who could have any,
   so a signed-out reader is not shown a bell that can never ring. */
.nb-bellwrap{position:relative;display:none}
.nb-bellwrap.on{display:inline-flex}
/* Ask — labelled, unlike the icon buttons beside it. Support is the one
   control somebody looks for when they are already stuck, and a glyph they
   have to decode is the wrong thing to hand them at that moment. */
.nb-ask{
  display:flex;align-items:center;gap:6px;height:34px;padding:0 12px 0 10px;
  border-radius:18px;color:var(--ink2);font:500 13px/1 var(--sans);
  border:1px solid var(--line);background:var(--panel);flex:0 0 auto;
}
.nb-ask:hover{border-color:var(--accent);color:var(--accent)}
@media(max-width:760px){ .nb-ask span{display:none} .nb-ask{padding:0 9px} }

.nb-bell{
  width:34px;height:34px;border-radius:50%;
  display:grid;place-items:center;color:var(--ink2);position:relative;
}
.nb-bell:hover{background:var(--panel2);color:var(--ink)}

/* ------------------------------------------------------- the assistant
   A panel, not a page: it sits over whatever the reader was doing, so the
   thing they are asking about stays on screen behind it. */
.nb-ai{
  position:fixed;right:18px;bottom:18px;z-index:130;
  width:min(420px,calc(100vw - 36px));height:min(560px,calc(100vh - 90px));
  background:var(--panel);border:1px solid var(--line);border-radius:14px;
  box-shadow:0 24px 60px rgba(4,9,17,.45);
  display:none;flex-direction:column;overflow:hidden;
}
.nb-ai.on{display:flex}
.nb-ai-head{display:flex;align-items:baseline;gap:9px;padding:13px 12px 13px 16px;
  border-bottom:1px solid var(--line);background:var(--panel3)}
.nb-ai-head b{font-size:14px}
.nb-ai-sub{flex:1;font:400 11px/1.4 var(--mono);color:var(--ink3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:help}
.nb-ai-x{width:26px;height:24px;border-radius:6px;color:var(--ink3);font-size:13px}
.nb-ai-x:hover{background:var(--panel2);color:var(--ink)}
.nb-ai-body{flex:1;overflow-y:auto;padding:14px 16px;display:flex;
  flex-direction:column;gap:12px}
.nb-ai-hi p{margin:0 0 14px;font-size:13px;line-height:1.7;color:var(--ink2)}
.nb-ai-sug{display:block;width:100%;text-align:left;margin-bottom:7px;padding:9px 12px;
  border:1px solid var(--line);border-radius:9px;background:var(--panel2);
  font-size:12.5px;color:var(--ink2)}
.nb-ai-sug:hover{border-color:var(--accent);color:var(--ink)}
.nb-ai-you{align-self:flex-end;max-width:88%;background:var(--accent);color:var(--accent-ink);
  padding:9px 13px;border-radius:14px 14px 4px 14px;font-size:13px;line-height:1.6}
.nb-ai-bot{max-width:96%;font-size:13px;line-height:1.7;color:var(--ink2)}
.nb-ai-bot.quoted{background:var(--panel2);border:1px solid var(--line);
  border-radius:4px 14px 14px 14px;padding:11px 13px}
/* An answer from a person is marked as one — the reader should never have
   to guess whether they are reading a colleague or an index. */
.nb-ai-bot.human{border-color:var(--edge-ok);background:var(--tint-ok)}
.nb-ai-bot.human .nb-ai-t{color:var(--ok)}
.nb-ai-t{display:block;color:var(--ink);font-size:12.5px;margin-bottom:6px}
.nb-ai-src{margin-top:8px;font-size:11.5px;color:var(--ink3)}
.nb-ai-src a{color:var(--accent)}
.nb-ai-warn{margin-top:8px;font-size:11.5px;color:var(--warn);line-height:1.6}
.nb-ai-ok{margin-top:9px;font-size:12px;color:var(--ok);line-height:1.6}
.nb-ai-tkt{margin-top:10px;padding:7px 12px;border-radius:8px;font-size:12.5px;
  border:1px solid var(--line);background:var(--panel2);color:var(--ink2)}
.nb-ai-tkt:hover{border-color:var(--accent);color:var(--accent)}
.nb-ai-foot{display:flex;gap:8px;padding:11px 12px;border-top:1px solid var(--line);
  background:var(--panel2)}
.nb-ai-foot input{flex:1;min-width:0;padding:9px 12px;border-radius:9px;
  border:1px solid var(--line);background:var(--panel);color:var(--ink);
  font:400 13px/1.4 var(--sans);outline:none}
.nb-ai-foot input:focus{border-color:var(--accent)}
.nb-ai-go{padding:0 15px;border-radius:9px;background:var(--accent);color:var(--accent-ink);
  font:600 13px/1 var(--sans)}
.nb-ai-go:hover{filter:brightness(1.08)}
.nb-bell .dot{
  position:absolute;top:6px;right:7px;width:8px;height:8px;border-radius:50%;
  background:var(--accent);border:2px solid var(--panel);
}
.nb-notes{
  position:absolute;right:0;top:calc(100% + 8px);width:min(340px,92vw);
  max-height:420px;overflow:auto;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:6px;box-shadow:var(--shadow-l);z-index:95;display:none;
}
.nb-notes.on{display:block}
.nb-notes .hd{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:8px 10px 10px;border-bottom:1px solid var(--line);margin-bottom:6px;
}
.nb-notes .hd b{font-size:13px}
.nb-notes .hd button{font-size:11.5px;font-weight:600;color:var(--accent)}
.nb-notes a{
  display:flex;gap:9px;align-items:flex-start;
  padding:9px 10px;border-radius:var(--r-sm);font-size:13px;color:var(--ink2);line-height:1.5;
}
.nb-notes a:hover{background:var(--panel2);color:var(--ink);text-decoration:none}
.nb-notes a.unread{background:var(--panel2)}
.nb-notes a b{color:var(--ink)}
.nb-notes a .sub{display:block;font-size:11.5px;color:var(--ink4);margin-top:2px}
.nb-notes .empty{padding:22px 14px;text-align:center;font-size:12.5px;color:var(--ink3);line-height:1.7}

/* The narrow-screen menu button. Hidden on a wide screen, where the links
   have room to sit in the bar. */
.nb-burger{display:none}

/* Search and Contribute live in the bar on a wide screen and inside the
   burger menu on a narrow one. Both copies are drawn into the page; these
   keep the menu ones hidden until the width that needs them. */
.nb-navsearch{display:none;margin:2px 0 6px}
.nb-navonly{display:none}
.nb-navsearch input{
  width:100%;height:38px;padding:0 13px;
  border:1px solid var(--line);border-radius:20px;background:var(--panel2);
  color:var(--ink);font:400 14px/1 var(--sans);
}
.nb-navsearch input::placeholder{color:var(--ink4)}
.nb-navsearch input:focus{border-color:var(--accent);outline:none}

/* Below 1080 the bar cannot hold the links AND the search box: measured,
   the full set needs about 1198px at a point where 1024 is all there is.
   So that is where the nav folds into the burger — and the search folds in
   with it, rather than being dropped. An iPad in landscape had no search at
   all before this. */
@media(max-width:1080px){
  .nb-search{display:none}
  .nb-burger{display:inline-flex}
  .nb-av-btn .nm{display:none}
  .nb-nav .nb-navsearch{display:flex}
}
/* Contribute still fits in the bar at tablet width; below 900 it moves into
   the menu, where .nb-navonly is its stand-in. */
@media(max-width:900px){
  /* The whole group, not just its trigger. A zero-width flex item is still
     a flex item and still costs a gap on both sides — with five of them in
     this bar that was 30px of nothing, and the 6px that pushed a 320px
     phone sideways on the lab page. */
  .nb-head > .nb-group{display:none}
  .nb-ddbtn{display:none}
  .nb-nav .nb-navonly{display:block}
}

/* --------------------------------------------------------------- phones
   Below 560px the bar still overflowed — measured at 93px past the edge of
   a 375px screen, which is what put a horizontal scrollbar on every page
   of the site. The nav had already collapsed to a burger; what had not
   been dealt with is that eight fixed-width controls plus a 14px gap
   between each cannot fit whatever you do to the nav.

   So the gap and the padding come down, and the two controls that have
   somewhere else to live go: Contribute is in the burger menu, and the
   brand shortens to the mark. What stays is what somebody on a phone
   actually reaches for — the menu, Ask, notifications, the theme, and
   their account. */
@media(max-width:560px){
  .nb-head{ gap:6px; padding:0 10px; }
  .nb-head > *{ min-width:0; }
  .nb-brand span{ display:none; }          /* the mark alone; it still links home */
  .nb-brand{ flex:0 0 auto; }
  .nb-ddbtn{ display:none; }
  .nb-nav{ left:6px; right:6px; }
  .nb-menu{ right:6px; left:6px; width:auto; }

  /* min-width:0 above lets a flex item shrink below its content, which is
     what keeps the bar from overflowing — but it applies to the icon
     buttons too, and the bell was being squeezed to a 10px sliver of a
     34px icon. The controls that are a fixed-size target opt back out. */
  .nb-brand, .nb-burger, .nb-ask, .nb-bellwrap, .nb-theme, .nb-chip{
    flex:0 0 auto;
  }

  /* The panel hangs off the bell, which sits near the right edge — so its
     340px width was drawn leftwards, 71px past the edge of the screen.
     Fixed to the viewport instead: on a phone this is a sheet, not a
     dropdown, and it no longer depends on where its anchor happens to be. */
  .nb-notes{
    position:fixed; left:8px; right:8px; width:auto;
    top:calc(var(--header-h) + 6px);
    max-height:calc(100vh - var(--header-h) - 18px);
  }
}
/* Very narrow — a 320px phone, or a split view. Nothing is dropped here:
   the bell used to be, via `.nb-bellwrap{display:none}`, which never once
   took effect — `.nb-bellwrap.on` (0,2,0) outranks it (0,1,0), so the rule
   had been dead the whole time while looking deliberate. */
@media(max-width:400px){
  .nb-head{ gap:4px; }
  .nb-ask span{ display:none; }
}
/* 320px, with the lab's connection indicator adopted into the bar on top of
   the six controls. Everything above already gave up what it could; what is
   left is the spacing itself. */
@media(max-width:360px){
  .nb-head{ gap:3px; padding:0 7px; }
}

/* The rest of the small-screen work is at the FOOT of this file. Overrides
   have to come after what they override, and several of these do — a
   badge's 12px floor loses to the 10.5px rule two hundred lines below if
   it is written here. */

.nb-chip{
  display:flex;align-items:center;gap:8px;
  padding:4px 11px 4px 4px;border-radius:22px;
  background:var(--panel2);border:1px solid var(--line);
}
.nb-chip:hover{border-color:var(--accent)}
.nb-chip.signin{background:var(--accent);border-color:var(--accent)}
.nb-av{width:26px;height:26px;border-radius:50%;flex:0 0 26px;display:grid;place-items:center;
       background:linear-gradient(135deg,#274390,#4b7fd6);color:#fff;
       font-weight:700;font-size:11.5px}
.nb-chip.signin .nb-av{background:var(--accent-ink);color:var(--accent)}
/* NOWRAP, because the two lines in this chip are each one thing. "Sign in"
   broke across two lines the day the typeface changed — Poppins is wider
   than what was here before at the same size — and a header that is one
   pixel short of fitting a word is a header that will be one pixel short of
   fitting somebody's name. A long name is cut with an ellipsis instead of
   growing the header or folding it. */
.nb-who{text-align:left;line-height:1.25;min-width:0;max-width:170px}
.nb-who b{display:block;font-size:12.5px;font-weight:600;
          white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nb-who span{display:block;font-size:10px;color:var(--ink3);text-transform:uppercase;
             letter-spacing:.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nb-chip.signin .nb-who b{color:var(--accent-ink)}
.nb-chip.signin .nb-who span{color:rgba(5,32,52,.72)}

.nb-menu{
  position:absolute;right:16px;top:calc(var(--header-h) + 6px);width:240px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:6px;box-shadow:var(--shadow-l);z-index:90;display:none;
}
.nb-menu.on{display:block}
.nb-menu .hd{padding:9px 11px 11px;border-bottom:1px solid var(--line);margin-bottom:6px}
.nb-menu .hd b{display:block;font-size:13px}
.nb-menu .hd span{font-size:11.5px;color:var(--ink3)}
.nb-menu button,.nb-menu a{
  display:block;width:100%;text-align:left;padding:8px 11px;border-radius:var(--r-sm);
  font-size:13px;color:var(--ink2);
}
.nb-menu button:hover,.nb-menu a:hover{background:var(--panel2);color:var(--ink);text-decoration:none}
.nb-menu button.danger:hover{color:var(--bad)}

/* The burger. Desktop never sees it; below 900px it is the only nav. */
.nb-burger{display:none;padding:7px 10px;border-radius:var(--r-sm);color:var(--ink2)}
.nb-burger:hover{background:var(--panel2);color:var(--ink)}
@media(max-width:1080px){
  .nb-nav{
    display:none;position:absolute;left:8px;right:8px;top:calc(var(--header-h) + 6px);
    flex-direction:column;gap:2px;background:var(--panel);border:1px solid var(--line);
    border-radius:var(--r);padding:8px;box-shadow:var(--shadow-l);z-index:85;
    /* Laid open, every section and its sub-links together run past the
       bottom of a short phone — measured 694px against a 667px screen, with
       the last links simply unreachable. The menu scrolls on its own so the
       end of it is always gettable, whatever the screen height. */
    max-height:calc(100vh - var(--header-h) - 18px);
    overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
  }
  .nb-nav.open{display:flex}
  .nb-burger{display:block}
  /* No hover on a touch screen, so the dropdown is simply laid open as an
     indented group. A menu that needs a pointer to reveal it is a menu that
     does not exist on a phone.

     Scoped to .nb-nav, and that scoping is the whole point: unscoped, these
     rules also caught the Contribute group, which lives in the BAR rather
     than in the nav. `.nb-group > a` (0,1,1) outranked `.nb-ddbtn{display:none}`
     (0,1,0), so the trigger stayed visible, and `.nb-sub{display:flex}`
     then laid its submenu open inside the header — 419x224px of links
     sitting in a 56px bar. */
  .nb-nav .nb-group{display:block;width:100%}
  .nb-nav .nb-group > a{display:block}
  .nb-nav .caret{display:none}
  .nb-nav .nb-sub{
    position:static;display:flex;min-width:0;
    background:none;border:none;box-shadow:none;padding:0 0 4px 14px;
  }
  /* No gap to bridge when the panel is laid open under its trigger — and the
     bridge would be 8px of invisible nothing sitting across the bottom of
     that trigger, swallowing the tap that was meant for it. */
  .nb-nav .nb-sub::before{display:none}
  .nb-nav .nb-sub a{font-size:12.5px;color:var(--ink3)}
  .nb-nav .nb-sub .sep{display:none}

}

/* Tablet and down. These are about a bar that is genuinely short of room,
   which starts below 900 — not at 1080, where only the nav had to fold. */
@media(max-width:900px){
  .nb-who{display:none}
  .nb-chip{padding:4px}
  /* The lab's connection indicator is adopted into this header, and at
     126px it was the thing pushing the page 16px wider than a phone. The
     coloured dot carries the meaning — connected, connecting, offline — so
     the words go and the dot stays. */
  .nb-head #conn{padding:5px;gap:0}
  .nb-head #conn b,
  .nb-head #conn .mode{display:none}
  .nb-head{gap:8px;padding:0 10px}
}

/* ==========================================================================
   Components, shared by both halves
   ========================================================================== */
.nb-btn{
  display:inline-flex;align-items:center;gap:7px;
  padding:8px 15px;border-radius:var(--r-sm);
  background:var(--panel2);border:1px solid var(--line);color:var(--ink);
  font-size:13.5px;font-weight:500;
}
.nb-btn:hover{border-color:var(--accent);color:var(--accent);text-decoration:none}
.nb-btn.pri{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);font-weight:600}
.nb-btn.pri:hover{background:var(--accent2);border-color:var(--accent2);color:var(--accent-ink)}
.nb-btn.danger:hover{border-color:var(--bad);color:var(--bad)}
.nb-btn.lg{padding:11px 22px;font-size:15px;border-radius:var(--r)}
.nb-btn.sm{padding:5px 10px;font-size:12.5px;border-radius:6px}
.nb-btn:disabled{opacity:.4;cursor:not-allowed}

.nb-card{
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:20px;margin-bottom:18px;
}
.nb-card h3{margin:0 0 12px;font-size:15px;font-weight:600}

.nb-badge{
  display:inline-block;padding:2px 9px;border-radius:20px;
  font-size:10.5px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;
}
.nb-badge.free{background:rgba(52,211,153,.16);color:var(--ok)}
.nb-badge.pro{background:rgba(167,139,250,.18);color:var(--pro)}
.nb-badge.enterprise{background:rgba(99,102,241,.22);color:#a5b4fc}
.nb-badge.admin{background:rgba(23,65,144,.16);color:var(--accent)}
.nb-badge.soon{background:rgba(100,116,139,.24);color:var(--ink2)}

.nb-note{
  padding:12px 15px;border-radius:var(--r-sm);
  font-size:13px;line-height:1.65;margin:0 0 14px;
}
/* Tinted from tokens rather than literal rgba, so the same four notes work
   on both grounds. The text colour is the state colour itself in light mode
   and a lightened form of it on dark — a pale mint on white is invisible,
   and a deep green on near-black is too. */
.nb-note.info{background:var(--tint-accent);border:1px solid var(--edge-accent);color:var(--ink)}
.nb-note.ok  {background:var(--tint-ok);   border:1px solid var(--edge-ok);    color:#a6e8cd}
.nb-note.warn{background:var(--tint-warn); border:1px solid var(--edge-warn);  color:#f3d38a}
.nb-note.bad {background:var(--tint-bad);  border:1px solid var(--edge-bad);   color:#f5aeae}
.nb-note code{background:rgba(0,0,0,.32);padding:1px 5px;border-radius:4px;font-size:.9em}

:root[data-theme="light"] .nb-note.info,
:root[data-theme="light"] .nb-note.ok,
:root[data-theme="light"] .nb-note.warn,
:root[data-theme="light"] .nb-note.bad{color:var(--ink2)}
:root[data-theme="light"] .nb-note code{background:rgba(19,28,41,.07)}
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]) .nb-note.info,
  :root:not([data-theme="dark"]) .nb-note.ok,
  :root:not([data-theme="dark"]) .nb-note.warn,
  :root:not([data-theme="dark"]) .nb-note.bad{color:var(--ink2)}
  :root:not([data-theme="dark"]) .nb-note code{background:rgba(19,28,41,.07)}
}

/* The theme switch itself. Two states, one control, and the icon shows what
   you will GET rather than what you have — a moon while light means "go
   dark", which is the reading people expect. */
.nb-theme{
  display:grid;place-items:center;width:34px;height:34px;flex:0 0 34px;
  border-radius:var(--r-sm);color:var(--ink2);
  border:1px solid transparent;
}
.nb-theme:hover{background:var(--panel2);color:var(--ink);border-color:var(--line)}
.nb-theme .sun{display:none}
.nb-theme .moon{display:block}
:root[data-theme="light"] .nb-theme .sun{display:block}
:root[data-theme="light"] .nb-theme .moon{display:none}
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]) .nb-theme .sun{display:block}
  :root:not([data-theme="dark"]) .nb-theme .moon{display:none}
}

.nb-wrap{max-width:var(--maxw);margin:0 auto;padding:34px 24px 70px}
.nb-lead{color:var(--ink2);font-size:15px;line-height:1.75;max-width:70ch;margin:0 0 22px}

.nb-grid{display:grid;gap:16px}
.nb-grid.c2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
.nb-grid.c3{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
.nb-grid.c4{grid-template-columns:repeat(auto-fit,minmax(190px,1fr))}

.nb-mono{font-family:var(--mono);font-size:12px;color:var(--ink2);word-break:break-all}
.nb-dim{color:var(--ink3)}

/* ==========================================================================
   Pane handles

   The seam is 5px wide but grabbable over 11 — a handle you have to aim at
   is a handle people give up on. The extra reach comes from a pseudo
   element so it costs no layout, which matters because these sit between
   panes whose sizes are the whole point.
   ========================================================================== */
.nb-split{
  position:relative;flex:0 0 5px;background:var(--line);
  transition:background .12s;touch-action:none;
}
.nb-split::after{
  content:'';position:absolute;
  /* the invisible reach, three pixels past the seam on each side */
  inset:-3px;
}
.nb-split-x{cursor:col-resize;width:5px;align-self:stretch}
.nb-split-y{cursor:row-resize;height:5px;width:100%}
.nb-split:hover,.nb-split.on,.nb-split:focus-visible{background:var(--accent);outline:none}

/* A grip, so the seam reads as something you can take hold of rather than
   as a border that happens to be thick. */
.nb-split::before{
  content:'';position:absolute;background:var(--ink4);border-radius:2px;
  opacity:0;transition:opacity .12s;
}
.nb-split-x::before{width:1px;height:22px;left:2px;top:50%;margin-top:-11px;
  box-shadow:2px 0 0 var(--ink4)}
.nb-split-y::before{height:1px;width:22px;top:2px;left:50%;margin-left:-11px;
  box-shadow:0 2px 0 var(--ink4)}
.nb-split:hover::before{opacity:.9}
.nb-split:hover::before,.nb-split.on::before{background:var(--accent-ink);box-shadow:none}

/* While dragging, the cursor is owned by the document. Without this it
   changes shape on every element the pointer crosses, and the drag selects
   all the text it passes over. */
body.nb-resizing{user-select:none;-webkit-user-select:none}
body.nb-resizing-x,body.nb-resizing-x *{cursor:col-resize !important}
body.nb-resizing-y,body.nb-resizing-y *{cursor:row-resize !important}
/* An iframe or canvas would otherwise swallow the pointer mid-drag. */
body.nb-resizing iframe,body.nb-resizing canvas{pointer-events:none}

/* Stacked on a phone, so there is no seam to drag. */
@media(max-width:900px){
  .nb-split{display:none}
}

/* ==========================================================================
   The image drawer — opens over any page from the header
   ========================================================================== */
.nb-count{
  position:absolute;top:2px;right:2px;min-width:15px;height:15px;padding:0 4px;
  border-radius:9px;background:var(--accent);color:var(--accent-ink);
  font:700 9.5px/15px var(--sans);font-style:normal;text-align:center;
}
#nbImages{position:relative}

.nb-drawer{position:fixed;inset:0;z-index:120;pointer-events:none}
.nb-drawer.on{pointer-events:auto}
.nb-drawer-back{
  position:absolute;inset:0;background:rgba(4,9,17,.5);
  opacity:0;transition:opacity .18s;
}
.nb-drawer.on .nb-drawer-back{opacity:1}
.nb-drawer-panel{
  position:absolute;top:0;right:0;bottom:0;width:min(430px,92vw);
  background:var(--panel);border-left:1px solid var(--line);
  box-shadow:var(--shadow-l);display:flex;flex-direction:column;
  /* Off-screen rather than hidden, so it slides rather than appears —
     a panel that pops in gives no sense of where it came from. */
  transform:translateX(100%);transition:transform .2s ease;
}
.nb-drawer.on .nb-drawer-panel{transform:none}
@media (prefers-reduced-motion: reduce){
  .nb-drawer-panel,.nb-drawer-back{transition:none}
}
.nb-drawer-panel > header{
  display:flex;align-items:center;justify-content:space-between;
  padding:15px 18px;border-bottom:1px solid var(--line);flex:0 0 auto;
}
.nb-drawer-panel > header b{font-size:15px}
/* The signed-out dialog keeps its close button and loses everything else the
   header draws: the card has its own heading, so a rule and 15px of padding
   above it only looked like a mistake. The button is pushed to the right of
   a header that now has no title in it. */
.nb-drawer-panel > header.bare{
  border-bottom:0;padding:10px 12px 0;justify-content:flex-end;min-height:0;
}
.nb-drawer-x{font-size:22px;line-height:1;color:var(--ink3);padding:0 4px}
.nb-drawer-x:hover{color:var(--ink)}
.nb-drawer-body{flex:1;overflow:auto;padding:18px}

.nb-drawer-grp{
  font-size:10.5px;letter-spacing:.9px;text-transform:uppercase;
  color:var(--ink3);font-weight:600;margin:20px 0 9px;
}
.nb-drawer-grp:first-child{margin-top:0}

.nb-img{
  border:1px solid var(--line);border-radius:var(--r-sm);
  padding:11px 13px;margin-bottom:8px;background:var(--panel2);
}
.nb-img-hd{display:flex;justify-content:space-between;align-items:center;gap:10px}
.nb-img-hd b{font-size:13px}
.nb-img-sub{font-size:11.5px;color:var(--ink3);margin-top:4px;line-height:1.6}
.nb-img-path{font-family:var(--mono);font-size:10.5px;color:var(--ink4);
             margin-top:5px;word-break:break-all}
.nb-img-err{font-size:11.5px;color:var(--bad);margin-top:6px;line-height:1.5}

/* ===========================================================================
   SMALL SCREENS

   Last in the file on purpose. These are overrides, and an override written
   above the rule it overrides simply loses — which is exactly what happened
   the first time: a 12px floor for badges, written two hundred lines above
   the 10.5px rule, matched and was then thrown away.

   Phone-first fixes only. The desktop layout above is dense because a lab
   platform on a monitor should be; none of this reaches it.
   =========================================================================== */

/* ------------------------------------------------- wide things on narrow
   A five-column table and an eighty-character command are both wider than a
   phone, and neither can be narrowed without destroying it. So they scroll
   INSIDE themselves rather than dragging the page sideways — which is what
   was happening, and why the guide had a scrollbar under every paragraph.

   `display:block` is what makes a table scrollable. It costs the table its
   automatic column sizing, which on a 375px screen was never going to
   survive anyway. */
@media(max-width:900px){
  table{
    display:block; width:100%; max-width:100%;
    overflow-x:auto; -webkit-overflow-scrolling:touch;
  }
  pre{ overflow-x:auto; -webkit-overflow-scrolling:touch; }
  /* A long path, URL or image filename must break rather than push the
     column out past the screen. */
  code, .nb-mono, .mono, .nb-img-path{ overflow-wrap:anywhere; word-break:break-word; }
  img, svg, video, canvas{ max-width:100%; height:auto; }
}

/* ------------------------------------------------------------- text size
   Labels, badges and table headings sit at 10.5–11.5px. Fine on a monitor a
   foot away; too small on a phone at arm's length in daylight. A 12px floor
   on small screens only. Body copy is already 13.5px+ and is left alone —
   scaling everything would reflow every page for no gain. */
@media(max-width:560px){
  .nb-badge, .nb-badge.free, .nb-badge.pro, .nb-badge.enterprise,
  .nb-badge.admin, .nb-badge.soon,
  .nb-who span, .nb-menu .hd span, .nb-img-sub, .nb-img-err, .nb-img-path,
  th, .grp, .xs, .pill, .tag, .note, .sub, .lbl, figcaption, small{
    font-size:12px;
  }
  code, .mono, .nb-mono{ font-size:12px; }
}

/* --------------------------------------------------------- touch targets
   Measured at 26–31px, against the 44px a thumb actually needs. Keyed to
   the POINTER, not the width: an iPad in landscape is 1194px wide and still
   a touch screen, and a narrow laptop window is neither. */
@media (pointer: coarse){
  .nb-head button, .nb-head a.nb-brand, .nb-burger, .nb-theme, .nb-bell,
  .nb-chip, .nb-ask, .nb-ddbtn{ min-height:40px; min-width:40px; }
  /* The brand collapses to its mark on a phone; without this it is a 26px
     target, which is a link most thumbs miss. */
  .nb-brand, .nb-chip{ display:flex; align-items:center; justify-content:center; }
  .nb-nav a, .nb-menu a, .nb-menu button, .nb-sub a{
    min-height:44px; display:flex; align-items:center;
  }
  /* Below 16px, iOS Safari zooms the page in when an input takes focus —
     and then leaves it zoomed, with the layout shifted sideways. */
  input, select, textarea{ font-size:16px; }
}

/* ------------------------------------------------------- the last 3 pixels
   A fixed, full-viewport overlay — the lab's sign-in gate, `inset:0` — is
   sized against the initial containing block, which includes the scrollbar
   gutter. It therefore measures a few pixels wider than the content area and
   makes the whole page report itself horizontally scrollable: on a phone,
   a sideways rubber-band on the sign-in screen with nothing to see there.

   `clip` and not `hidden` on purpose. `hidden` would turn the body into a
   scroll container, and the sticky header would stop sticking. `clip` is
   special-cased so the other axis stays `visible`. This covers that
   accounting only — every real overflow found was fixed where it happened,
   not hidden here. */
html, body{ max-width:100%; overflow-x:clip; }

/* ---------------------------------------------------------------- touch size
   A finger is about 9 mm across; a mouse cursor is one pixel. The shared
   controls were sized for the cursor — .nb-btn at 8px padding is roughly a 33px
   target, against the 44px every platform's own guidance asks for. On a phone
   that is the difference between pressing a button and pressing near it.

   Keyed on `pointer:coarse`, not on width: a narrow browser window on a desktop
   still has a mouse and should keep the tighter layout it was designed for, and
   a 13in touch laptop is wide and still needs the room. `min-height` rather
   than more padding, so nothing already big enough grows and no layout that
   depends on the padding shifts. */
@media (pointer:coarse){
  .nb-btn, .btn, button, [role="button"], .nb-chip,
  .nb-ask, .nb-bell, .nb-av-btn, .nb-ddbtn{ min-height:44px; }
  /* An icon-only control needs the width too, or it is a 44px-tall sliver. */
  .nb-btn.icon-only, .btn.icon-only, .nb-ask, .nb-bell, .nb-av-btn{ min-width:44px; }
  /* Fields: the same target, and 16px text — anything smaller makes iOS zoom the
     page in when the field is focused, which leaves the layout scrolled sideways
     and is the commonest mobile-form complaint there is. */
  .nb-in, input, select, textarea{ min-height:44px; font-size:16px; }
  /* Links in running text are not buttons and must not be padded into one.
     Links in NAVIGATION are, and they are what a thumb aims at. */
  .nb-nav a, .nb-menu a, .nb-sub a, .nb-foot-links a{
    min-height:44px; display:flex; align-items:center;
  }
  /* Rows of small controls get room between them, so a near-miss is a miss
     rather than the wrong button. */
  .filters, .row-btns{ gap:10px; }
}

/* --------------------------------------------------- header spacing, phones
   Down here for the reason this file has already learned once: the tablet
   rule `.nb-head{gap:8px;padding:0 10px}` in the max-width:900px block sits
   BELOW the phone blocks, and at equal specificity later wins — so all three
   phone rules were being quietly overridden by the tablet one, and a 320px
   screen was still spacing its header as if it had 900. */
@media(max-width:560px){ .nb-head{ gap:6px; padding:0 10px; } }
@media(max-width:400px){ .nb-head{ gap:4px; } }
@media(max-width:360px){ .nb-head{ gap:3px; padding:0 7px; } }

/* header menu entries for signed-in users or admins stay hidden until paint() allows them */
.nb-head [hidden]{display:none !important}

/* the account panel's Real labs shortcuts */
.nb-acgrid{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}

/* the account panel's "create an account" form (administrators) */
.nb-nuform{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:10px;padding:12px;margin:4px 0 8px;background:var(--bg2,transparent)}
.nb-nuform label{font-size:11px;font-weight:700;color:var(--ink3,#667);text-transform:uppercase;letter-spacing:.04em;margin:8px 0 3px}
.nb-nuform label:first-child{margin-top:0}
.nb-nuform input,.nb-nuform select{width:100%;box-sizing:border-box;padding:7px 9px;border:1px solid var(--line);border-radius:7px;background:var(--card);color:var(--ink);font:inherit;font-size:13px}
.nb-nurow{display:flex;gap:8px}
.nb-nurow > div{flex:1;display:flex;flex-direction:column}

/* Edit profile: labelled fields, and a two-up row for appearance + language */
.nb-flabel{display:block;font-size:11px;font-weight:700;color:var(--ink3);text-transform:uppercase;letter-spacing:.04em;margin:10px 0 4px}
.nb-frow{display:flex;gap:8px}
.nb-frow > div{flex:1;min-width:0}
select.nb-in{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink);font:inherit}

/* the language globe in the header */
.nb-langwrap{position:relative;display:inline-flex}
.nb-langbtn{display:inline-flex;align-items:center;gap:4px;width:auto!important;padding:0 8px!important}
.nb-langbtn b{font-size:11px;font-weight:700;letter-spacing:.03em}
.nb-langmenu{display:none;position:absolute;right:0;top:calc(100% + 8px);width:260px;max-height:70vh;overflow:auto;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow-l,0 16px 40px rgba(0,0,0,.3));padding:6px;z-index:1200}
.nb-langmenu.open{display:block}
.nb-langmenu button{display:flex;justify-content:space-between;align-items:center;width:100%;padding:7px 10px;border:none;border-radius:7px;
  background:none;color:var(--ink);font:inherit;font-size:13px;cursor:pointer;text-align:left}
.nb-langmenu button:hover{background:var(--panel2)}
.nb-langmenu button.on{background:var(--accent);color:var(--accent-ink)}
.nb-langmenu .en{font-size:11px;color:var(--ink3)}
.nb-langmenu button.on .en{color:inherit;opacity:.8}
.nb-lang-note{font-size:11px;color:var(--ink3);padding:8px 10px 4px;border-top:1px solid var(--line);margin-top:4px;line-height:1.4}

/* ==========================================================================
   The shared footer. Injected by shell.js at the end of <body> on every page
   that has no footer of its own: copyright, the legal pages, and the
   trademark notice. Tokens only, so it follows the theme like the header.
   ========================================================================== */
.nb-foot{
  margin-top:48px;
  background:var(--panel);
  border-top:1px solid var(--line);
  color:var(--ink3);
  font-size:11.5px;line-height:1.7;
}
.nb-foot-in{max-width:var(--maxw);margin:0 auto;padding:34px 24px 28px}
.nb-foot-links{margin-bottom:6px}
.nb-foot a{color:var(--ink3);text-decoration:underline;text-underline-offset:2px}
.nb-foot a:hover{color:var(--accent)}
.nb-foot-tm{max-width:none}
/* The columns. Four across on a desktop, two on a tablet and one on a phone —
   a footer that keeps four columns at 380px is four columns of one word. */
/* The brand block is wider than a link column, which is what makes the row
   read as "who this is" followed by "where to go" rather than four equal
   lists. */
.nb-foot-cols{
  display:grid;grid-template-columns:1.6fr repeat(3,minmax(0,1fr));
  gap:22px 28px;margin-bottom:26px;
}
@media (max-width:860px){.nb-foot-cols{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:460px){.nb-foot-cols{grid-template-columns:1fr}}
.nb-foot-mark{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:700;
  letter-spacing:-.4px;color:var(--ink);margin-bottom:10px}
.nb-foot-mark svg{flex:none;border-radius:7px}
.nb-foot-mark span{font-weight:400;color:var(--ink3)}
.nb-foot-blurb{line-height:1.7;max-width:40ch;margin-bottom:10px}
.nb-foot-pills{display:flex;gap:6px;flex-wrap:wrap}
/* A bare .pill carries no background — only its state variants do — so in
   the footer it rendered as bold text with padding and read as nothing.
   These are claims about the product, not states, so they get a quiet chip
   of their own rather than borrowing a status colour. */
.nb-foot-social{display:flex;gap:10px;margin-top:14px}
.nb-foot-social a{
  display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;
  border:1px solid var(--line);border-radius:8px;color:var(--ink3);text-decoration:none;
}
/* THE BRANDS' OWN COLOURS. Each mark takes the colour its owner uses, which
   is how people recognise them at 18px without reading a label.

   Two of them are black, and this theme is dark by default — a black GitHub
   mark on a dark footer is an empty box. So those two are defined as white
   here and flipped back to black only where the page is light, the same way
   every other colour in this file is handled. The three that are already
   vivid stay as they are in both. */
.nb-foot-social a.soc-github  {color:#ffffff}
.nb-foot-social a.soc-x       {color:#ffffff}
.nb-foot-social a.soc-linkedin{color:#0a66c2}
.nb-foot-social a.soc-youtube {color:#ff0000}
.nb-foot-social a.soc-discord {color:#5865f2}
/* Colour belongs to the mark; the frame stays the footer's. */
.nb-foot-social a:hover{border-color:currentColor}
.nb-foot-pills .pill{
  font-size:11px;font-weight:600;background:var(--panel);border:1px solid var(--line);
  color:var(--ink3);padding:4px 11px;border-radius:20px;
}
/* Measured off the teaching half's footer so the two match rather than merely
   resemble each other: headings in sentence case, dark and bold, not the
   uppercase grey label this used; links 12.5px, no underline until hover. */
.nb-foot-col h4{
  margin:0 0 10px;font-size:14px;font-weight:700;letter-spacing:-.1px;
  text-transform:none;color:var(--ink);
}
.nb-foot-col a{display:block;margin-bottom:8px;text-decoration:none;font-size:12.5px;color:var(--ink3)}
.nb-foot-col a:hover{text-decoration:underline;color:var(--accent)}
.nb-foot-about{
  padding-top:18px;border-top:1px solid var(--line);margin-bottom:12px;
}

/* ====================================================================
   Shared components

   These were defined inside the lab page, which meant the accounts console
   and the company portal rendered the same markup with none of the styling
   — cards with no card, stats as bare numbers. They are components of the
   product, not of one page, so they live here and every page gets them.
   ==================================================================== */
.msg{padding:10px 12px;border-radius:8px;font-size:12.5px;line-height:1.6;margin-bottom:12px}
.msg.info{background:var(--tint-accent);border:1px solid var(--edge-accent);color:var(--ink2)}
.msg.warn{background:var(--tint-warn);border:1px solid var(--edge-warn);color:var(--ink2)}
.msg.bad {background:var(--tint-bad);border:1px solid var(--edge-bad);color:var(--ink2)}
.msg.ok  {background:var(--tint-ok);border:1px solid var(--edge-ok);color:var(--ink2)}
.msg code{font-family:var(--mono);font-size:11.5px;background:rgba(0,0,0,.3);
          padding:1px 5px;border-radius:4px;display:inline-block}

.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px;
      margin-bottom:20px}
.card h3{margin:0 0 12px;font-size:14px;font-weight:600;display:flex;
         justify-content:space-between;align-items:center;gap:10px}

table.tbl{width:100%;border-collapse:collapse;font-size:12.5px}
table.tbl th{text-align:left;padding:8px 10px;color:var(--ink3);font-size:11px;
             letter-spacing:.5px;text-transform:uppercase;border-bottom:1px solid var(--line)}
table.tbl td{padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}
table.tbl tr:last-child td{border-bottom:none}

.pill{display:inline-block;padding:2px 8px;border-radius:20px;font-size:11px;font-weight:600}
/* A STATUS PILL SAYS ITS STATE TWICE: in the word and in the colour, and
   the colour is on the word as well as behind it. Mixed from the tokens
   rather than written as rgba of the dark palette, which is what these
   were — so in daylight the text was one green and the wash behind it was
   another. */
.pill.ready,.pill.ok{background:color-mix(in srgb,var(--ok) 18%,transparent);
  border:1px solid color-mix(in srgb,var(--ok) 38%,transparent);color:var(--ok)}
.pill.pending{background:color-mix(in srgb,var(--warn) 18%,transparent);
  border:1px solid color-mix(in srgb,var(--warn) 38%,transparent);color:var(--warn)}
.pill.error,.pill.no{background:color-mix(in srgb,var(--bad) 18%,transparent);
  border:1px solid color-mix(in srgb,var(--bad) 38%,transparent);color:var(--bad)}
.pill.admin{background:var(--tint-pro);color:var(--pro)}
.pill.user{background:rgba(75,127,214,.15);color:var(--accent)}

.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;
       margin-bottom:22px}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:11px;padding:14px 16px}
.stat .n{font-size:24px;font-weight:700;line-height:1.2}
.stat .l{font-size:11px;letter-spacing:.6px;text-transform:uppercase;color:var(--ink3);
         font-weight:600;margin-top:3px}
.stat .sub{font-size:11.5px;color:var(--ink3);margin-top:6px}
/* THREE TONES, AND EVERY ONE OF THEM MEANS SOMETHING.

   Green is "this is up, this is in use, this is fine". Amber is "look at
   this before it bites". Red is "this is wrong now". A figure that is
   merely a count — how many accounts, how many saved labs — stays in the
   ink colour, because a number painted for decoration teaches the reader
   that the colours on this screen do not mean anything, and then the red
   one does not get read either. */
.stat.warn .n{color:var(--warn)} .stat.ok .n{color:var(--ok)} .stat.bad .n{color:var(--bad)}

.meter{height:5px;border-radius:4px;background:var(--panel2);overflow:hidden;margin-top:9px}
.meter i{display:block;height:100%;background:var(--accent);transition:width .3s}
.meter i.warn{background:var(--warn)} .meter i.bad{background:var(--bad)}

.mono{font-family:var(--mono);font-size:11.5px;color:var(--ink2);word-break:break-all}
/* A quiet line under a control or a table: used by every portal screen, and
   previously only defined as `.prop .note`, so it was unstyled anywhere a
   property row was not involved. */
.note{font-size:11.5px;color:var(--ink3);line-height:1.6}

/* ====================================================================
   Monitoring — shared by the site portal (Real labs → Admin) and the
   company portal, because one implementation of a screen is the only
   way two of them stay the same.
   ==================================================================== */
/* ---- monitoring. One measure per card: the number now, the shape behind
   it. The chart is an SVG with a non-scaling stroke, so one viewBox works
   at any width and the line stays 2px. */
.mongrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;
         margin-bottom:22px}
.moncard{background:var(--panel);border:1px solid var(--line);border-radius:11px;
         padding:14px 16px 10px;min-width:0}
.moncard .monh{font-size:11px;letter-spacing:.6px;text-transform:uppercase;color:var(--ink3);
               font-weight:600}
.moncard .monv{font-size:26px;font-weight:700;line-height:1.25;margin-top:2px}
.moncard .monu{font-size:13px;font-weight:600;color:var(--ink3);margin-left:3px}
.moncard .monago{font-size:11px;font-weight:600;color:var(--ink3);margin-left:8px}
.moncard .monsub{font-size:11.5px;color:var(--ink3);margin-top:2px}
.monchart{display:block;width:100%;height:46px;margin-top:10px;cursor:crosshair;overflow:visible}
.monalert{display:flex;gap:10px;align-items:flex-start;margin:7px 0;font-size:12.5px;
          line-height:1.55}
.monalert .pill{flex:0 0 auto}
.montbl td{vertical-align:middle}
/* A bar whose length is the only thing carrying the value, with the number
   at its end rather than on every row of a legend. */
.monbarwrap{position:relative;height:18px;background:var(--panel2);border-radius:5px;
            overflow:hidden;min-width:80px}
.monbarwrap .monbar{display:block;height:100%;background:var(--accent);border-radius:5px;
                    transition:width .4s ease}
.monbarwrap .monbarv{position:absolute;right:6px;top:0;line-height:18px;font-size:11px;
                     font-weight:600;font-family:var(--mono,monospace);color:var(--ink)}


/* The two marks that are black in the brands' own guidelines, put back where
   the page is light. Declared after everything else so it wins without an
   !important, and under both ways this theme becomes light. */
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]) .nb-foot-social a.soc-github{color:#181717}
  :root:not([data-theme="dark"]) .nb-foot-social a.soc-x{color:#000000}
}
:root[data-theme="light"] .nb-foot-social a.soc-github{color:#181717}
:root[data-theme="light"] .nb-foot-social a.soc-x{color:#000000}


/* ======================================================================== */
/*  NARROW SCREENS, FOR EVERY PAGE                                          */
/*                                                                          */
/*  This file is loaded by all fifteen pages, so what is written here is     */
/*  written once. The header, the nav and the footer already had their       */
/*  breakpoints above; what did not was the CONTENT — and the content is     */
/*  mostly four shapes repeated: a card, a table, a row of buttons and a     */
/*  form. Those four are the whole of this block.                            */
/*                                                                          */
/*  THE TABLE IS THE HARD ONE and worth saying why. A six-column table on a  */
/*  360px screen has two possible behaviours: wrap every cell until the rows */
/*  are unreadable, or push the page sideways so the whole layout drifts and */
/*  nothing lines up. Neither is right. The third way is to let the TABLE    */
/*  scroll inside its card while the page stays put, which needs two things  */
/*  that have to agree: a scroll container (the card) and a table that will  */
/*  not shrink below the width its columns need (min-width). One without the */
/*  other does nothing, which is why they are next to each other here.       */
/* ======================================================================== */

@media(max-width:900px){
  /* A card's padding is a desktop's margin. On a phone it is the difference
     between a readable column and a gutter. */
  .card{padding:15px}
  .card h3{font-size:13.5px}
  /* Rows of buttons and filters wrap instead of overflowing. These are the two
     shapes every screen in the product ends a section with. */
  .row{flex-wrap:wrap}
  .pills,.portal{flex-wrap:wrap}
}

@media(max-width:760px){
  /* The table scrolls, the page does not. See the note above. */
  .card{overflow-x:auto}
  table.tbl{font-size:12px;min-width:540px}
  table.tbl th,table.tbl td{padding:7px 8px}
  /* A form is one column here, whatever it was. An input next to its label is
     a desktop's luxury; below this it is two cramped columns.

     Scoped to a card rather than every label on the page: a bare `label` rule
     would also straighten out the ones used inline — a checkbox and its words,
     a hint beside a field — and turn each of them into its own row somewhere
     nobody asked for one. */
  .card label{display:block}
  input,select,textarea{max-width:100%}
  /* Anything that was laid out as a fixed two- or three-across grid becomes
     one column. Named rather than global, so a deliberate two-across layout
     that still fits is left alone. */
  .grid,.grid2,.grid3,.cols,.two,.three{grid-template-columns:1fr !important}
}

@media(max-width:560px){
  /* The last of the horizontal luxuries. A button that was sized to its text
     takes the row instead, because two half-width buttons on a 320px screen
     are two buttons with one word each. */
  .row > .btn{flex:1 1 auto}
  .card{padding:13px;border-radius:10px}
  table.tbl{min-width:460px}
  h1{font-size:21px}
  h2{font-size:17px}
}

/* A finger needs a target, and there is no hover to explain anything. Applies
   whatever the width, because a tablet in landscape is wide AND touched. */
@media (pointer:coarse){
  .btn{min-height:36px}
  input,select{min-height:36px}
  table.tbl td,table.tbl th{padding-top:10px;padding-bottom:10px}
}
