/* Juntos shared app shell — the navy top bar every module wears.
 *
 * Authored here, copied verbatim into every vanilla app by scripts/generate-ui-css.js —
 * do not edit the copy in an app's public/, it is overwritten on the next sync. El Nido is
 * React and cannot take a copied stylesheet the same way; it mirrors these values in its
 * own component, and the numbers below are the reference.
 *
 * Depends on juntos-tokens.css being linked FIRST (it supplies --navy and the type scale).
 *     <link rel="stylesheet" href="/juntos-tokens.css">
 *     <link rel="stylesheet" href="/juntos-ui.css">
 *     <link rel="stylesheet" href="/juntos-shell.css">
 *
 * WHY THIS IS SHARED RATHER THAN COPIED. Four apps wearing "the same" chrome is exactly
 * the shape that has drifted on this platform before — brand-tokens.css was hand-copied
 * into each app and diverged twice in one week, and sidebar.js carried a "byte-identical"
 * claim in its own docs that turned out to be false by 71 lines. A top bar that is only
 * the same by convention will not stay the same.
 *
 * EVERY CLASS IS PREFIXED `ju-`, same rule as juntos-ui.css: these apps already define
 * .topbar (The Huddle), .sidebar (El Rebaño, La Mesa) and .m-top with their own meanings,
 * and an unprefixed shell would silently restyle pages nobody touched.
 *
 * Nothing here applies until a page opts in by using a class, so adding the file changes
 * no pixels on its own.
 * ───────────────────────────────────────────────────────────────────────────── */

:root {
  /* Read by app CSS that needs to offset against the bar (a sticky page header, a docked
     panel). Overridden below at phone widths, so anything keyed to it follows. */
  --ju-topbar-h: 76px;
}

/* ── The wordmark face ────────────────────────────────────────────────────────
 * Montserrat ExtraBold (weight 800), the brand's own display face. SELF-HOSTED, and it
 * has to be: every app on this platform sets `font-src 'self'`, so a Google Fonts <link>
 * is blocked outright — it would fail silently and fall back to the system stack, which
 * looks like nothing happened rather than like an error. Same reasoning that already put
 * Lucide and Chart.js in each app's own public/.
 *
 * `juntos-montserrat-800.woff2` is a SUBSET: uppercase Latin plus its accents (Ñ for
 * "EL REBAÑO"), digits and a little punctuation — 2,680 bytes, down from the 245KB full
 * face. The wordmark is the only thing that uses it and the wordmark is always uppercase,
 * so lowercase glyphs would be pure payload. If a future app's wordmark needs a character
 * outside that range, re-cut the subset — a missing glyph renders as the fallback face
 * mid-word, which is worse than not using the font at all.
 *
 * `font-display: swap` deliberately: the bar must say which app you are in immediately,
 * even on a bad connection in a church building. A reflow from the fallback to Montserrat
 * is a far smaller cost than an invisible app name.
 * ───────────────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: 'Juntos Display';
  src: url('/juntos-montserrat-800.woff2') format('woff2');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

.ju-topbar {
  position: sticky; top: 0; z-index: 80;
  height: var(--ju-topbar-h);
  background: var(--navy); color: #fff;
  display: flex; align-items: center; gap: 20px;
  padding: 0 22px 0 26px;
}

/* ── Brand lockup ─────────────────────────────────────────────────────────────
 * Each app supplies its own mark (El Rebaño's R, The Huddle's, La Mesa's, El Nido's) as a
 * square white-on-transparent PNG, plus its own wordmark TEXT. The wordmark is text rather
 * than part of the image on purpose: the supplied artwork is a STACKED lockup — mark above
 * word — which cannot be read at 38px in a horizontal bar. Crop the mark out of the
 * official file, and let the type render as type.
 * ───────────────────────────────────────────────────────────────────────────── */
.ju-topbar-brand { display: flex; align-items: center; gap: 11px; color: #fff; text-decoration: none; }
.ju-topbar-mark { width: 38px; height: 38px; display: block; object-fit: contain; flex-shrink: 0; }
/* All caps at weight 800, per the brand. `text-transform` rather than uppercase text in the
   markup, so a screen reader still announces "El Rebaño" and not "E L R E B A Ñ O" — some
   readers spell out an all-caps string. The fallback stack matters: if the woff2 fails to
   load, this still renders as heavy caps rather than collapsing to a serif. */
.ju-topbar-word {
  font-family: 'Juntos Display', 'Montserrat', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-weight: 800; text-transform: uppercase;
  letter-spacing: 0.04em; font-size: 15px; white-space: nowrap;
}

.ju-topbar-tenant {
  font-size: 13px; color: #cbd7db;
  border-left: 1px solid rgba(255,255,255,0.28); padding-left: 20px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ju-topbar-grow { flex: 1; }

/* ── Signed-in user ── */
.ju-topbar-user {
  display: flex; align-items: center; gap: 10px;
  padding-left: 20px; border-left: 1px solid rgba(255,255,255,0.28);
  /* Anchors the account menu. Harmless when sidebar.js has not enhanced the chip. */
  position: relative;
}

/* ── Account menu ──────────────────────────────────────────────────────────────
   The chip is a plain <div> in every page's markup, and sidebar.js UPGRADES it into
   this trigger at runtime rather than the markup being changed in eleven files. That
   also means the no-JS case is the old chip: readable, just not clickable. */
.ju-topbar-user-trigger {
  display: flex; align-items: center; gap: 10px;
  background: none; border: 0; padding: 4px; margin: -4px;
  border-radius: 8px; cursor: pointer; font: inherit; color: inherit;
}
.ju-topbar-user-trigger:hover { background: rgba(255,255,255,0.10); }
.ju-topbar-user-trigger:focus-visible {
  outline: 2px solid #fff; outline-offset: 2px;
}
.ju-topbar-user-caret {
  flex-shrink: 0; opacity: 0.7; transition: transform 0.15s ease;
}
.ju-topbar-user-trigger[aria-expanded="true"] .ju-topbar-user-caret { transform: rotate(180deg); }

.ju-topbar-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 70;
  min-width: 208px; padding: 6px;
  background: var(--surface, #fff); color: var(--text-1, #061d2d);
  border: 1px solid var(--border, rgba(0,0,0,0.1)); border-radius: 10px;
  box-shadow: 0 12px 28px rgba(6,29,45,0.18);
}
/* ── `hidden` has to actually hide ────────────────────────────────────────────────────────
 *
 * The UA rule is `[hidden] { display: none }` at the lowest possible weight, so ANY class rule
 * that sets `display` beats it and the element renders while the markup says it is hidden. That
 * is not theoretical here: it produced a dead "My Work" pill in El Rebano and La Mesa, a case
 * panel whose Overview grid stayed under every other tab, a stale attendance row in The Huddle,
 * and a domain switcher Portal shows to people with one domain. Every one of those is a control
 * or a panel that is visibly there and should not be.
 *
 * `!important` is deliberate and is the only way this wins against a class. It is scoped to the
 * attribute, which nothing styles for appearance, so it cannot collide with a real design rule:
 * an element with `hidden` is being hidden, and that is the whole of it. `.ju-topbar-menu[hidden]`
 * below predates this and stays -- it is harmless and documents its own intent.
 *
 * Toggle visibility with `el.hidden`, not `style.display`, and this rule keeps its promise. */
[hidden] { display: none !important; }

.ju-topbar-menu[hidden] { display: none; }
.ju-topbar-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  min-height: 40px; padding: 0 10px; border-radius: 7px;
  background: none; border: 0; cursor: pointer;
  font-family: inherit; font-size: 13.5px; font-weight: 500; text-align: left;
  color: var(--text-1, #061d2d); text-decoration: none;
}
.ju-topbar-menu-item:hover { background: var(--cream, rgba(0,0,0,0.04)); }
.ju-topbar-menu-item:focus-visible {
  outline: 2px solid var(--teal-2, #00797a); outline-offset: -2px;
}

/* On a phone the name and role are hidden, so the trigger is just the avatar -- the
   caret would crowd it for no information gain. */
@media (max-width: 760px) {
  .ju-topbar-user-caret { display: none; }
}
.ju-topbar-avatar {
  width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center;
  background: linear-gradient(135deg, var(--teal-2), var(--teal));
  color: #fff; font-size: 12px; font-weight: 800; flex-shrink: 0;
  /* line-height:1 so two capitals sit optically centred, not just box-centred. */
  line-height: 1; text-align: center;
}

/* A Directory photo, laid OVER the initials rather than swapping them out. If the person
   has no linked photo the proxy 404s, the <img> removes itself, and the initials are
   already sitting underneath -- no broken-image icon and no layout shift, because the
   photo never occupied its own box. Same treatment El Rebano's care queue already gives
   case avatars.

   The src is ALWAYS the app's own authenticated proxy, never people.photo_url itself.
   That column can hold a private Vercel Blob URL a browser cannot read, and El Nido's
   `img-src 'self' blob: data: https://*.supabase.co` would block the Elvanto CDN host
   outright -- a same-origin path is 'self' and works in all four modules unchanged. */
.ju-topbar-avatar { position: relative; overflow: hidden; }
.ju-topbar-avatar img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; border-radius: 50%;
}

/* COVERING THE FALLBACK IS NOT THE SAME AS REPLACING IT. Everything above assumes the photo
   is opaque, so the gradient and the initials underneath simply never show. A PNG with an
   ALPHA CHANNEL -- a logo, a cut-out portrait -- breaks that assumption: the teal gradient
   and the two capitals show straight THROUGH the picture. Reported by a user who uploaded a
   logo and saw it sitting on a brown disc with their initials behind it.

   `has-photo` is added by the app's own script on the image's LOAD event, never when the
   <img> is merely created -- a 404 or a decode failure must leave the gradient and initials
   exactly as they were, since they are the fallback.

   White, not transparent: a transparent disc would let the navy bar show through, and most
   logos are drawn to sit on a light ground. An opaque photo covers this anyway, so it is
   only ever visible behind an image that asked for it. */
.ju-topbar-avatar.has-photo {
  background: #fff;
  /* The initials are a TEXT NODE on this element, not a child, so there is nothing to
     hide -- only a colour to remove. */
  color: transparent;
}

.ju-topbar-user-text { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.ju-topbar-user-text strong { font-size: 13px; white-space: nowrap; }
.ju-topbar-user-text small { font-size: 11px; color: #bdcbd0; white-space: nowrap; }

/* The EN/ES toggle. Lives in the shared sheet because the control is shared -- La Mesa's
   five per-page copies each styled their own, and they do not match each other.

   It shows the language it will switch TO (juntos-lang.js sets the label), which is the
   opposite of the version everybody misreads.

   BORDERLESS, and quiet on purpose. This was an outlined 800-weight chip, which read as
   the loudest thing in the bar -- a box around two capital letters competes with the
   person's own name beside it, and the bar is not where the eye should stop. It now
   matches La Mesa's public nav buttons: no border, no fill, muted white, normal weight,
   with the surface appearing only on hover. Restraint is the point; do not re-add a
   border to "make it findable."

   It stays 32px tall rather than shrinking to the text. La Mesa's own is about 26px,
   which clears WCAG 2.5.8's 24px floor by two pixels; this one sits beside a 36px avatar
   in an app people use all day, so it keeps a real target while looking like it doesn't. */
.ju-topbar-lang {
  flex-shrink: 0;
  min-width: 40px; height: 32px; padding: 0 10px;
  border: none; border-radius: 8px;
  background: none; color: rgba(255,255,255,0.82);
  font-family: inherit; font-size: 12px; font-weight: 500; letter-spacing: 0.03em;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.ju-topbar-lang:hover { background: rgba(255,255,255,0.14); color: #fff; }
.ju-topbar-lang:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* Actions an app hangs on the right of the bar (sign out, a switcher, a help button).
   Kept deliberately plain — apps style their own contents, this only positions them. */
.ju-topbar-actions { display: flex; align-items: center; gap: 10px; }

/* ── Phone ────────────────────────────────────────────────────────────────────
 * THE TENANT NAME STAYS. An earlier version of this file dropped it here, reasoning that
 * "at this width the bar's whole job is to say which app you are in." That was wrong, and
 * it is worth saying why so nobody re-applies it: this is a MULTI-TENANT platform, the
 * shared operator account holds a membership in every church, and the two tenants' apps
 * are otherwise identical on screen. "Which app" is the one thing you can already tell
 * from the content; "which church" is not.
 *
 * It also hid more than a phone. 760px catches a tablet in portrait and an ordinary
 * half-screen browser window on a laptop, so the name vanished on a desktop that merely
 * was not maximised — which reads as a bug, because it is one.
 *
 * It shrinks and truncates instead. The wordmark is flex-shrink:0 so the app name never
 * clips; the tenant carries `min-width: 0` + ellipsis and is the element that gives way,
 * which is the right order — a truncated church name still tells you which church.
 *
 * The USER's own name/role still drops out, and that asymmetry is deliberate: you know who
 * you are, and it is the least load-bearing text in the bar. The avatar stays.
 * ───────────────────────────────────────────────────────────────────────────── */
@media (max-width: 760px) {
  :root { --ju-topbar-h: 62px; }
  /* WRAPS — but only when what follows the brand genuinely needs its own line.
     Flexbox decides to wrap from an item's flex-BASIS, before any shrinking happens, so
     the basis on .ju-topbar-tenant below is what settles this. A 7rem floor used to force
     a second line unconditionally, which is right for La Mesa (its nav is ~209px and has
     to drop) and wrong for El Rebaño, The Huddle and Portal, whose trailing content is
     just the ES button and a 36px avatar. Measured at 375px: row one came to exactly its
     347px of usable width, so the avatar dropped alone to a second row and took the bar
     from 62px to 94px — for one 36px circle.
     Now the tenant starts at a 4rem basis and grows into whatever is left, so a compact
     trailing cluster stays on row one and a real nav still wraps. Measured after: El
     Rebaño and The Huddle are one 62px row from 360px up, with 107px of tenant at 375px;
     La Mesa still wraps its nav at every phone width AND its tenant gained room, 122px to
     223px, because the tenant now absorbs the slack the spacer used to hold. */
  .ju-topbar {
    height: auto; min-height: var(--ju-topbar-h);
    flex-wrap: wrap; gap: 10px; row-gap: 4px; padding: 8px 14px;
  }
  .ju-topbar-user-text { display: none; }
  .ju-topbar-word { font-size: 13px; }
  /* The app name never clips; the tenant is the one that gives way. */
  .ju-topbar-brand { flex-shrink: 0; }
  .ju-topbar-tenant { font-size: 11px; padding-left: 10px; flex: 1 1 4rem; min-width: 0; }
  /* The tenant does the pushing now, so the spacer would only compete with it for the
     slack. Every one of the 25 topbars across the four apps carries both spans, checked
     rather than assumed, so hiding it here cannot strand a bar with no way to right-align. */
  .ju-topbar-grow { display: none; }
  /* No border-left, and pushed to the right edge. That divider only reads as a divider
     when something sits beside it on the same row: at 320px, where the avatar still does
     drop to a second line, it used to render as a stray vertical rule against nothing.
     margin-left:auto then anchors it under the ES button instead of stranding it at the
     far left, so the wrapped case reads as deliberate rather than broken. */
  .ju-topbar-user { padding-left: 0; border-left: 0; margin-left: auto; }
}

/* ── Light nav rail ───────────────────────────────────────────────────────────
 * Opt in with `class="ju-shell"` on <body>, on a page whose markup follows the admin
 * shell El Rebaño and La Mesa already share:
 *
 *     <header class="ju-topbar"> … </header>
 *     <div class="app-shell">
 *       <aside class="sidebar">
 *         <div class="sidebar-logo">…</div>          <- hidden; the bar carries the brand
 *         <nav class="sidebar-nav">
 *           <div class="sidebar-section">…</div>
 *           <a class="sidebar-link">…</a>
 *         </nav>
 *         <div class="sidebar-footer">…</div>
 *       </aside>
 *       <main class="main">…</main>
 *     </div>
 *
 * WHY THESE SELECTORS ARE NOT `ju-`-PREFIXED, when everything above is. The rule up top
 * is about not restyling markup nobody opted in — and `.sidebar` here is never loose: it
 * only matches inside `.ju-shell`, a class a page has to put on <body> itself. Renaming
 * the structure instead would mean rewriting ten pages' markup, two `sidebar.js` copies
 * and both apps' collapse persistence, to arrive at the same rendering. The Huddle is
 * deliberately NOT a consumer — its rail is `.bottom-nav`/`.bn-btn`, a genuinely
 * different component that doubles as a phone tab bar, and forcing it into this shape
 * would be a rewrite dressed up as sharing.
 *
 * COLLAPSE IS THE APP'S, NOT THIS FILE'S. `sidebar.js` toggles `.sidebar-collapsed` on
 * `.app-shell` and persists it; both apps' own stylesheets carry the collapsed widths.
 * `.app-shell.sidebar-collapsed` (two classes) and `.ju-shell .app-shell` (two classes)
 * tie on specificity, and each app's sheet is linked AFTER this one — so the collapsed
 * state wins, which is exactly the outcome wanted. Do not add `!important` here to
 * "make it robust": that would break collapse in both apps at once.
 * ───────────────────────────────────────────────────────────────────────────── */
.ju-shell .app-shell {
  grid-template-columns: 208px minmax(0, 1fr);
  min-height: calc(100vh - var(--ju-topbar-h));
}
.ju-shell .sidebar {
  background: var(--surface); color: var(--text-1);
  border-right: 1px solid var(--border);
  top: var(--ju-topbar-h); height: calc(100vh - var(--ju-topbar-h));
  padding: 16px 12px 14px;
}
/* The lockup lives in the top bar now, on every page and in every module. */
.ju-shell .sidebar-logo { display: none; }
/* Same reason: the phone brand strip would be a second copy of the bar's own lockup. */
.ju-shell .mobile-topbar { display: none !important; }

.ju-shell .sidebar-nav { display: grid; gap: 4px; }
.ju-shell .sidebar-section {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-3); margin: 14px 0 4px; padding: 0 10px;
}
.ju-shell .sidebar-section:first-child { margin-top: 2px; }
/* 44px floor clears WCAG 2.5.8 with room to spare — these are tapped on a tablet. */
.ju-shell .sidebar-link {
  min-height: 44px; border-radius: 7px; padding: 0 12px;
  display: flex; align-items: center; gap: 11px;
  color: var(--text-1); background: transparent; border: 0;
  font-size: 13.5px; font-weight: 500; text-decoration: none; cursor: pointer;
  width: 100%; text-align: left; font-family: inherit;
}
.ju-shell .sidebar-link:hover { background: var(--cream); }
/* The active row follows the TENANT's configured module colours, the same pair Mesa Hub
   already themes from -- `tenant_modules.config`'s primaryColor/accentColor, resolved by
   Core's getModuleUiBranding.

   NOT tenants.primary_color/accent_color. Those are Juntos Portal's own brand fields, and
   reusing them for another module is a mistake this platform has already shipped and
   reverted once (2026-07-30).

   The literals below are DEFAULTS, not the value: an app that never sets these variables
   renders exactly what it rendered before, so the tokenisation alone changes nothing.

   --ju-rail-ink is set alongside them rather than hardcoded to #fff, because a tenant may
   configure a light colour -- New Jersey's accent is #bad415, and white on that is about
   1.9:1, unreadable. Whoever sets the colours picks the ink to match; see sidebar.js. */
.ju-shell .sidebar-link.active {
  background: linear-gradient(90deg, var(--ju-rail-from, #087b7d), var(--ju-rail-to, #0c8c8e));
  color: var(--ju-rail-ink, #fff); font-weight: 700;
}
.ju-shell .sidebar-link.active svg { color: var(--ju-rail-ink, #fff); }
.ju-shell .sidebar-footer {
  color: var(--text-3); font-size: 10.5px; line-height: 1.6;
  border-top: 1px solid var(--border); padding-top: 12px; margin-top: 12px;
}
.ju-shell .sidebar-footer strong { color: var(--text-2); }

/* ── Phone: the rail's column has to go too ───────────────────────────────────
 * Both apps hide `.sidebar` at 768px and hand navigation to `.mobile-nav`. Neither
 * collapsed the GRID COLUMN with it: `.app-shell { grid-template-columns: 1fr }` inside
 * their own phone media query is one class, and the rail rule that sets 208px is two, so
 * the 208px won at every width. The rail was invisible and its column was still there —
 * on a 375px screen that left an empty 208px gutter and 167px for the content.
 *
 * Measured on El Rebaño's /admin/care/ at 375px, and confirmed against the PREVIOUS rule
 * (`.admin-page .app-shell`) in the same browser: identical 208px + 167px. This is a
 * pre-existing bug in production, not something the move to a shared sheet introduced.
 *
 * 768px, NOT the 760px the bar above uses — this has to fire exactly when `.sidebar`
 * disappears, and 768 is where both apps hide it. A rail-shaped hole between 761 and 768
 * is precisely the failure being fixed.
 * ───────────────────────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  /* Only the expanded case is handled here. The COLLAPSED case cannot be won from this
     file: El Rebaño restates `.admin-page .app-shell.sidebar-collapsed` at three classes in
     a sheet loaded after this one, so any selector here ties at best and loses. `sidebar.js`
     drops the class entirely below this width instead — the right fix anyway, since a
     "collapsed rail" is meaningless when there is no rail. See its own comment. */
  .ju-shell .app-shell { grid-template-columns: minmax(0, 1fr); }
}

/* ── Rail collapse toggle ─────────────────────────────────────────────────────
 * `sidebar.js` (El Rebaño and La Mesa each carry a copy) builds this button at runtime,
 * toggles `.sidebar-collapsed` on `.app-shell`, and persists the choice in localStorage.
 * It is injected as a direct child of `.sidebar`.
 *
 * IT NEEDS STYLING HERE BECAUSE BOTH APPS' OWN RULES WERE WRITTEN FOR THE OLD RAIL, which
 * was teal (El Rebaño) or navy (La Mesa): `color: white` on `rgba(255,255,255,.13)` with a
 * `rgba(255,255,255,.70)` border. Perfectly correct there — and completely invisible on the
 * white rail this file introduced. Restyling it in each app would put the same declarations
 * in two places again, which is the drift this file exists to prevent.
 *
 * Contrast: `--text-2` on `--surface` is comfortably past 4.5:1, and the 1px `--text-3`
 * border clears 1.4.11's 3:1 for a control boundary. Both are token values, so a tenant
 * palette change carries through.
 * ───────────────────────────────────────────────────────────────────────────── */
.ju-shell .sidebar-logo-row {
  display: flex; align-items: center; justify-content: flex-end;
  width: 100%; margin: 0 0 10px; padding: 0;
}
.ju-shell .sidebar-collapse-btn {
  width: 28px; height: 28px; flex-shrink: 0;
  background: var(--surface); color: var(--text-2);
  border: 1px solid var(--text-3); border-radius: 7px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background 0.15s, color 0.15s;
}
.ju-shell .sidebar-collapse-btn:hover { background: var(--cream); color: var(--text-1); }

/* Collapsed: the rail is 58px, so the toggle centres rather than sitting hard right. */
.ju-shell .app-shell.sidebar-collapsed .sidebar-logo-row { justify-content: center; }

/* The rail is hidden below 768px and `.mobile-nav` takes over, so a collapse control there
   would toggle something nobody can see. */
@media (max-width: 768px) {
  .ju-shell .sidebar-logo-row { display: none; }
}

/* ── The workspace switcher (juntos-workspaces.js, 2026-09-16) ────────────────────────────────
   One way to move around Juntos on every staff page: My Work, the Leader overview, then Care, Serving,
   Kids, Groups. A worded button, not an icon, because the people who most need it read words; the menu
   rows are 44px tall. Quiet in the bar like the language toggle, clear once open. */
.ju-ws { position: relative; flex-shrink: 0; }
.ju-ws-button {
  display: inline-flex; align-items: center; gap: 6px;
  height: 36px; padding: 0 12px;
  border: 1px solid rgba(255,255,255,0.28); border-radius: 8px;
  background: none; color: #fff;
  font-family: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.ju-ws-button:hover { background: rgba(255,255,255,0.14); }
.ju-ws-button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.ju-ws-caret { font-size: 11px; opacity: 0.8; }
.ju-ws-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
  min-width: 220px; padding: 6px;
  background: #fff; color: var(--navy, #061d2d);
  border-radius: 12px; box-shadow: 0 12px 32px rgba(15,34,51,0.22);
}
.ju-ws-menu[hidden] { display: none; }
.ju-ws-item {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 44px; padding: 0 12px; border-radius: 8px;
  color: inherit; text-decoration: none; font-size: 15px; font-weight: 600;
}
.ju-ws-item:hover { background: var(--cream, rgba(0,0,0,0.04)); }
.ju-ws-item:focus-visible { outline: 2px solid var(--teal-2, #00797a); outline-offset: -2px; }
.ju-ws-item.is-current { color: var(--teal-2, #00797a); }
.ju-ws-here { font-size: 11px; font-weight: 500; color: var(--muted, #61747a); }
.ju-ws-sep { height: 1px; margin: 6px 4px; background: rgba(15,34,51,0.1); }
@media (max-width: 560px) {
  .ju-ws-button { padding: 0 9px; }
  .ju-ws-menu { position: fixed; left: 12px; right: 12px; top: 64px; min-width: 0; }
}
/* A page may style every link in its top bar (`.ju-topbar-actions a` draws Portal's outlined pills). The menu's rows
   are links too, so they state everything they rely on at a higher specificity rather than inherit a pill. */
.ju-ws .ju-ws-menu .ju-ws-item {
  border: 0; height: auto; line-height: 1.3; opacity: 1; background: none; box-shadow: none;
  color: var(--navy, #061d2d); text-transform: none; letter-spacing: normal;
}
.ju-ws .ju-ws-menu .ju-ws-item:hover { background: var(--cream, rgba(0,0,0,0.04)); }
/* "Opening Care..." (2026-09-17): the moment a row that leaves the page is tapped, the Juntos ground covers it, so a
   second or two in Portal's signed launch never reads as a tap that did nothing. */
.ju-ws-opening {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px;
  background: var(--navy, #061d2d); color: #fff;
  font-family: inherit;
}
.ju-ws-opening-text { margin: 0; font-size: 18px; font-weight: 600; letter-spacing: 0.01em; }
.ju-ws-opening-mark {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--teal-2, #00797a);
  animation: ju-ws-pulse 1s ease-in-out infinite alternate;
}
@keyframes ju-ws-pulse { from { transform: scale(0.7); opacity: 0.6; } to { transform: scale(1.3); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .ju-ws-opening-mark { animation: none; } }
.ju-ws .ju-ws-menu .ju-ws-item.is-current { color: var(--teal-2, #00797a); }
