/* ══════════════════════════════════════════════════════════════════════════
   earl.ee — platform bar (Kanoniker)

   The one bar above every platform surface (DESIGN-SYSTEM.md: "The platform
   bar"). 44px, theme-STABLE on the --night-* constants — on a preview it sits
   above an app whose design the platform does not control and must read as
   *not part of it*. Exactly four zones: wordmark · place switcher ·
   context (previews only) · account. Left is where, right is who — the two
   menus never merge.

   The mark is NOT in this bar, and there is no zone for it. It is gold on an
   ink square and this ground IS ink: the square vanishes and what remains is
   a naked gold chevron sitting beside the switcher's real caret — two carets,
   one of them inert. The wordmark alone is unambiguous, and the browser tab
   already carries the mark four pixels above the bar (DESIGN-SYSTEM.md:
   "Why the mark is not in the bar", trap 4: the mark needs a ground that is
   not its own).

   Canon rules for this file
   - Mirrored byte-identically into every consumer (pre-commit hook, same
     mechanism as tokens.css; tools/sync-design-tokens.sh distributes). A
     consumer may inline it into a <style> in the <head> instead of linking
     it — the byte-equality check runs against the <style> content then
     (DESIGN-SYSTEM.md, "Inlining"). Contains no url(); keep it that way.
   - The bar only. The review dock (.pb-dock, .pb-dock-mark) lives in
     review-dock.css — one consumer renders it, so no other surface pays
     for it.
   - Requires tokens.css to be loaded first: every colour, font, radius,
     motion and focus value is a var(). The bar's own GEOMETRY (44px height,
     the 14px inset, control paddings, type sizes) is the platform-bar spec
     itself, taken at full fidelity from the reference — those literals are
     canon here, not improvisation in a consumer (Rule 01 still binds
     consumers).
   - Menus are native <details>/<summary> — no JavaScript required, which the
     www surface demands ("no JavaScript" is test-pinned there).
   - The caret is a CSS triangle, the onward arrow an inline SVG with
     stroke="currentColor": arrow glyphs (▾ ↗) are outside the shipped font
     subsets and degrade into specks (DESIGN-SYSTEM.md: "Glyphs"). ONE caret
     per bar — it belongs to the place switcher and to nothing else; the
     avatar opens its menu without one.

   Markup contract (rebuild per stack; dynamic parts differ per consumer):

     <header class="platform-bar">
       [zone 1 — the wordmark alone, no mark:]
       <a class="pb-mark" href="https://my.EXAMPLE">
         <span class="pb-wordmark">earl.ee</span>
       </a>
       [zone 2 — the label NAMES THE SURFACE YOU ARE ON (on a preview: the
        app's own name); the menu lists the places you are NOT on, and the
        current place is never among them:]
       <details class="pb-menu pb-switcher">
         <summary><span class="pb-switcher-label">Tools</span>
                  <span class="pb-caret" aria-hidden="true"></span></summary>
         <nav class="pb-menu-list">
           My Projects · Tools · Code · Account · Homepage — minus the current one
         </nav>
       </details>
       [zone 3 — state zone 2 cannot show. Previews: env badge + version.
        my.earl.ee: type and name of the thing the page is about, because
        zone 2 says only "my". Never the app name (zone 2 already said it),
        never a breadcrumb. Empty everywhere else (code, tools, account):]
       [on a preview the badge IS the visitor switch, and then it is a control:]
       <form class="pb-visitor-anchor" method="post" action="…">
         <button class="pb-env-badge" type="submit"
                 aria-pressed="false">preview</button>
         [while it has something to say:]
         <span class="pb-visitor-fact">Deine App sieht … · kein Header</span>
       </form>
       [everywhere else it is a word, with no role, tabindex or aria-pressed:]
       <span class="pb-env-badge">preview</span>
       <span class="pb-version">v1.2.3</span>
       <span class="pb-context"><span class="pb-context-type">Projekt</span> · building-x-website</span>
       <span class="pb-spacer"></span>
       [the onward link sits RIGHT of the spacer, beside the account: it is a
        way out, and ways out are on the right. Word first, arrow after — the
        arrow says "you are leaving", and that is said after the destination:]
       <a class="pb-onward" href="…" title="To the app page"><span class="pb-onward-label">
         To the app page</span>[arrow SVG]</a>                [i18n: preview.onward]
       [signed in:]
       <details class="pb-menu pb-menu--right pb-account">
         <summary class="pb-avatar" title="user@example.org">AD
           <span class="pb-avatar-dot" hidden></span></summary>
         <nav class="pb-menu-list">
           <span class="pb-menu-account">user@example.org</span>
           <span class="pb-menu-note" hidden>Session ends in 0:12</span>
           <a href="…">Account</a>
           <a href="…">Tokens</a>
           <span class="pb-menu-rule"></span>
           <a href="…">Sign out</a>
         </nav>
       </details>
       [signed out:] <a class="pb-signin" href="…">Sign in</a>
     </header>

   Narrow-width drop order (spec order; the px thresholds are this file's
   choice): version → onward label (the arrow stays) → wordmark.

   ON A PREVIEW zone 3 holds two things: the badge (which is also the visitor
   switch) and the version picker. The picker gives way first — the version is
   written at the foot of the app page and the picker is one click away. The
   badge never drops: it says where you are, and on a preview it is also the
   only way in and out of visitor view.

   THE PLACE AND THE ACCOUNT ZONE NEVER DROP — not the place label, not to a
   caret alone. Those two are the answer to *where am I* and *how do I leave*,
   and a bar that has dropped both is a black stripe. The wordmark goes first
   because the URL bar is already saying earl.ee one line above it.
   ════════════════════════════════════════════════════════════════════════ */

/* The bar's PUBLIC INTERFACE — its height and its layer, named so a surface can
   reference them instead of copying them.

   These are not new numbers; they are the two literals below with a name. The
   bar keeps laying itself out with its own values. What changes is that a page
   which has to sit *under* the bar can now say so: `.sticky-under-bar` in
   components.css reads --pb-height, and no surface has to know that the bar is
   44px tall.

   The need was measured, not imagined: three surfaces (shape-ui,
   invite-service, introspection-viewer) had independently written
   `position: sticky; top: 0` for their filter field and were all three hidden
   behind the bar, because the bar is sticky at top 0 too and outranks them.
   Three surfaces reaching the same wrong answer is the proof that it was never
   a local question.

   --pb-z is published for completeness and is expressly NOT something a surface
   reaches for: the bar is always the topmost layer, and anything competing with
   it is a bug. `.sticky-under-bar` uses 10. */
:root {
  --pb-height: 44px;
  --pb-z: 100;
}

.platform-bar {
  position: sticky;          /* sticky reserves its height — the page below   */
  top: 0;                    /* starts at 44px by normal flow. Never fixed.   */
  z-index: 100;
  height: 44px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 14px;           /* reference inset — sub-ladder on purpose       */
  background: var(--night-surface);
  overflow: visible;         /* the menus drop below the bar                  */
}

/* A 44px bar whose contents can wrap is the clattering it exists to prevent. */
.platform-bar > * {
  flex: none;
  white-space: nowrap;
}

.platform-bar a {
  text-decoration: none;
}

.platform-bar :focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-control);
}

/* ── zone 1 · wordmark (the mark is not in this bar) ─────────────────────── */

.pb-mark {
  display: inline-flex;
  align-items: center;
  color: var(--night-ink);
}

.pb-wordmark {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--night-ink);
}

/* ── menus (shared) — native <details>, no script ────────────────────────── */

.pb-menu {
  position: relative;
}

.pb-menu > summary {
  list-style: none;
  cursor: pointer;
}
.pb-menu > summary::-webkit-details-marker {
  display: none;
}

.pb-menu-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 180px;
  padding: var(--space-sm) 0;
  background: var(--night-raised);
  border: 1px solid var(--night-line);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-lift);   /* a menu genuinely floats (Rule 04)      */
  display: flex;
  flex-direction: column;
  z-index: 101;
}

.pb-menu--right .pb-menu-list {
  left: auto;
  right: 0;
}

.pb-menu-list a {
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--night-ink);
  padding: 8px 14px;
  transition: background var(--dur-state) ease-out;
}

.pb-menu-list a:hover {
  background: var(--night-line);
}

.pb-menu-rule {
  height: 1px;
  margin: var(--space-sm) 0;
  background: var(--night-line);
}

/* Non-interactive menu lines: the full account address, the expiry note.    */
.pb-menu-account,
.pb-menu-note {
  font: var(--text-meta);
  color: var(--night-muted);
  padding: 4px 14px;
}

.pb-menu-note {
  color: var(--night-fault);
}

/* ── zone 2 · place switcher ─────────────────────────────────────────────── */

/* The ONE flexible child — everything else is flex:none. On a preview this
   button carries the app's own name, and an arbitrarily long name must
   ellipsis rather than push the bar (DESIGN-SYSTEM.md: "the app name is the
   single flexible item"). It NARROWS; it never drops.                       */
.platform-bar > .pb-switcher {
  flex: 0 1 auto;
  min-width: 0;
}

.pb-switcher > summary {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 0;              /* so the label truncates, not the control      */
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--night-ink);
  background: var(--night-raised);
  border: 1px solid var(--night-line);
  border-radius: var(--radius-control);
  padding: 5px 10px;
  transition: border-color var(--dur-state) ease-out;
}

.pb-switcher > summary:hover {
  border-color: var(--night-line-strong);
}

.pb-switcher-label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Drawn, never typed (▾ falls back to a speck). Never shrinks with the
   label — a caret compressed to a sliver is the speck it exists to avoid.   */
.pb-caret {
  flex: none;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--night-muted);
}

/* ── zone 3 · context — state zone 2 cannot show ─────────────────────────── */
/* previews: .pb-env-badge + .pb-version · my.earl.ee: .pb-context · else empty */

.pb-env-badge {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--env-preview);
  border-radius: var(--radius-pill);
  padding: 4px 10px;
}

.pb-version {
  font: var(--text-meta);
  color: var(--night-faint);
}

/* PREVIEW ONLY — "show me the page the way my visitors see it". It changes
   what the platform TELLS the app about the viewer, never who may enter:
   sign-in stays required, rights do not move.

   THE BADGE IS THE SWITCH — .pb-env-badge carries aria-pressed on a preview.
   Zone 3 already holds badge and version; a third element pushes the place
   and the account out on a phone, and those two never go. Off is the badge as
   before. On is the same pill inverted (the .help-toggle[open] inversion read
   on a night ground), labelled "preview · besuchersicht", and it is the only
   filled thing in the whole bar. No caret: two states are not a list. The
   state survives a reload — reloading is part of testing, and the pill is
   what remembers it for the eye. Words: i18n preview.visitorView (off),
   preview.visitorOn (on), preview.visitorFact* (the fact).

   As a switch it is a <button type="submit"> inside the anchor <form>, like
   the dock's rows — the state lives in a cookie the router sets, so pressing
   it is a POST, not a change in the browser. A span with role/tabindex would
   submit nothing and answer no key natively; the form gives keyboard, focus
   and semantics for free, and script stays an extra, never a condition. What
   remains is what every button in the canon needs: the UA styles cleared, so
   the pill looks exactly as it did. The hit area grows invisibly to 44px
   below (Rule 07).

   NOT font: inherit (and no letter-spacing/text-transform/line-height
   inherit either): this selector is more specific than .pb-env-badge, so
   those four lines overwrote the type that makes the badge a badge and
   fetched the bar's instead — the pill read as a stray button. Nothing needs
   them: .pb-env-badge sets family, size, weight, tracking and case itself,
   and every author declaration beats the UA's. Only what the UA puts there
   and the badge does not say has to go.                                     */
.pb-env-badge[aria-pressed] {
  cursor: pointer;
  border: 1px solid var(--env-preview);
  appearance: none;                    /* it is a <button>. */
  margin: 0;
}
.pb-env-badge[aria-pressed]:hover { border-color: var(--night-ink); }

.pb-env-badge[aria-pressed="true"] {
  color: var(--night-surface);   /* NOT --ink: that one flips with the theme and
     would turn the pill into an empty light patch in dark mode. The bar is
     theme-stable, so both sides of the inversion must be (Rule 09, the night
     twin). --night-surface is the ground this pill sits on, inverted. */
  background: var(--night-ink);
  border-color: var(--night-ink);
}

/* The anchor the fact hangs from. TWO anchors were needed and one element can
   only have one: the fact starts under the BAR (vertically) and at the BADGE
   (horizontally). So the badge gets a wrapper that is as tall as the bar —
   align-self: stretch — and as wide as the badge. Then "top: 100%" is the
   bottom of the bar and "left: 0" is the left edge of the badge, with no
   magic number that breaks when the pill's padding changes.

   It is a <form method="post">: the switch is a POST, and the anchor is the
   element that was needed anyway. One element, not two. */
.pb-visitor-anchor {
  position: relative;
  align-self: stretch;                 /* as tall as the bar — but stretch */
  display: inline-flex;                /* drops the bar's centring for this */
  align-items: center;                 /* child, so centre the badge again. */
  margin: 0;                           /* it is a <form>. */
}

/* The fact behind the switch: what the app is actually being told. It is
   ALWAYS in the markup of a preview — the answer to "what is my app being
   told" must be available at any moment, not only in the second after
   switching. Only its visibility moves: hover or focus fetches it for as long
   as it is wanted. Zone 3 stays a line of words instead of becoming a status
   bar, and the bar never grows.

   NO SAY-ON-LOAD. It had one (4.5s, then quiet) and it is withdrawn: on a
   preview one reloads constantly, so every single load laid a dark box over
   the customer's page — in the middle of its text on a text page — for an
   answer wanted once or twice a day. Focus covers the moment right after
   switching (see below), hover covers "I want to know now". The animation
   only covered the case where nobody asked.

   No canonical .js file for the bar exists and this does not justify one:
   the whole behaviour is presentation. */
.pb-visitor-fact {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  max-width: min(420px, calc(100vw - 28px));   /* never off the edge of the
     viewport — a fact you cannot read is worse than no fact. The box alone
     is not enough: the sentence carries an identifier and is longer than
     420px, so it WRAPS (no nowrap) and long tokens break rather than run out
     of the box. Wrapped, not clipped — an ellipsis in an identifier is half
     an answer again. */
  z-index: 10;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  color: var(--night-ink);
  background: var(--night-raised);
  border: 1px solid var(--night-line);
  border-radius: var(--radius-input);
  padding: 8px 12px;
  overflow-wrap: anywhere;
  box-shadow: var(--shadow-lift);
  opacity: 0;
  transition: opacity 120ms var(--ease);
}

/* Keyed on the ANCHOR, not on the fact: an invisible fact takes no pointer,
   so the badge has to be what you point at. :focus-within also holds after an
   ordinary click — whoever just switched keeps the fact until they click
   elsewhere. That is intended: the moment right after switching is the moment
   the fact is for, and it is now the only thing that shows it unasked. */
.pb-visitor-anchor:hover .pb-visitor-fact,
.pb-visitor-anchor:focus-within .pb-visitor-fact,
.pb-visitor-fact:hover {
  opacity: 1;
}

/* my.earl.ee only — "Projekt · <name>" / "App · <name>". Two tones in one line:
   the type is quiet, the name is the ink. Never the app name of a preview. */
.pb-context {
  flex: none;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--night-ink);
  white-space: nowrap;
}

.pb-context-type {
  color: var(--night-muted);
}

.pb-onward {
  display: inline-flex;
  align-items: center;
  gap: 6px;                            /* label, then arrow — DOM order, no reordering */
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--night-muted);
  transition: color var(--dur-state) ease-out;
}

.pb-onward:hover {
  color: var(--night-ink);
}

.pb-onward svg {
  width: 14px;
  height: 14px;
  display: block;
}

.pb-spacer {
  flex: 1;
}

/* ── zone 4 · account ────────────────────────────────────────────────────── */

.pb-avatar {
  position: relative;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--night-raised);
  border: 1px solid var(--night-line-strong);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--night-muted);
  transition: color var(--dur-state) ease-out,
              border-color var(--dur-state) ease-out;
}

.pb-avatar:hover {
  border-color: var(--night-ink);
  color: var(--night-ink);
}

/* The avatar must never deform from a circle.                               */
.pb-account > .pb-avatar {
  flex: none;
}

/* Session about to expire: --night-fault, because --fault flips with the
   theme and disappears on the theme-stable night ground (the Rule-09 bug
   class the night constants exist to prevent). The menu carries the state
   in words (Rule 13) — the dot is never the only channel.                   */
.pb-avatar-dot {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--night-fault);
  border: 2px solid var(--night-surface);
}

.pb-avatar-dot[hidden],
.pb-menu-note[hidden] {
  display: none;
}

/* Signed out: the state is a word, never an empty avatar.                   */
.pb-signin {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--night-ink);
  background: transparent;
  border: 1px solid var(--night-line-strong);
  border-radius: var(--radius-control);
  padding: 6px 14px;
  transition: border-color var(--dur-state) ease-out;
}

.pb-signin:hover {
  border-color: var(--night-ink);
}

/* ── 44px touch targets on a 28px control (Rule 07): invisible hit-area
      extension — the visual pill stays as specified. ──────────────────────── */

.pb-switcher > summary,
.pb-signin,
.pb-onward {
  position: relative;
}

/* The badge is only a control on a preview — [aria-pressed] is what says so,
   and only then does it need the 44px. As a plain badge it is a word. */
.pb-env-badge[aria-pressed] { position: relative; }

.pb-switcher > summary::after,
.pb-signin::after,
.pb-onward::after,
.pb-avatar::after,
.pb-env-badge[aria-pressed]::after {
  content: "";
  position: absolute;
  inset: -9px;
}

/* ── narrow widths: fixed drop order, nothing wraps ──────────────────────────
      Three steps, and only three: version → onward label → wordmark. The
      place label and the account zone are NOT on this list at any width —
      they answer *where am I* and *how do I leave*, and a bar that has
      dropped both is a black stripe.                                        */

@media (max-width: 900px) {
  .pb-version { display: none; }
}

@media (max-width: 760px) {
  .pb-onward-label { display: none; }   /* the arrow stays, with its title   */
}

/* Zone 1 IS the wordmark now, so the whole link goes — an emptied <a> would
   stay in the tab order and flash a focus ring around nothing.              */
@media (max-width: 460px) {
  .pb-mark { display: none; }
}
