/* core.css — shared responsive layer for The Union of the Rising Sons.
 *
 * Load order in <head>, AFTER each page's inline <style> so these rules win
 * the cascade at equal specificity:
 *     <style> ...page styles... </style>
 *     <link rel="stylesheet" href="core.css?v=1" />   <!-- last in <head> -->
 *   </head>
 * Bump the ?v= query whenever this file changes (same rule as core.js).
 *
 * Scope: this file is presentation only. Most rules live behind the mobile
 * breakpoint below (so desktop layout is untouched); the exceptions are a couple
 * of harmless cross-viewport safety nets and — as of v3 — the cross-viewport
 * BEVEL FINISH on the form layer (controls, buttons, dialog shells). It reuses
 * the page tokens (--cream, --line, --clay, --taupe, --bark) with fallbacks so a
 * page that somehow lacks one still renders.
 *
 * Breakpoint convention (the one place it's defined): MOBILE = max-width 760px.
 * Phones and small tablets in portrait. index.html/seek.html keep their own
 * 1000px column-stacking query; this layer sits below that and handles the
 * "phone shell": the nav, the modal/sheet, and screen-fit guards.
 *
 * v4 — cross-page NAV TRANSITION: a cross-document View Transition (all app
 *      pages opt in via this shared file) plus a short opacity boot fade as a
 *      universal floor, replacing the white flash between top-nav tabs. Both are
 *      reduced-motion-aware. Presentation only; no layout/color change.
 * v3 — cross-viewport BEVEL FINISH on the form layer: recessed inset on text
 *      controls, raised bevel + press on filled action buttons, inset + unified
 *      drop on dialog shells. Tokenized (--bevel-up/-in/-press, --dialog-drop)
 *      near the top, applied just below the safety-nets. Presentation only; no
 *      layout/color change. (First intentional cross-viewport visual rule set.)
 * v2 — top bar wraps to two rows on mobile (wordmark no longer collides with
 *      the nav/actions). Phase 1 foundation:
 *   · .overlay/.dialog centered modal -> bottom sheet on mobile (fixes the
 *     off-screen "New message" window; matches inbox.html's existing .ovl/.sheet).
 *   · single-row scrollable top nav so all destinations stay reachable.
 *   · horizontal-overflow guard so no page needs side-panning.
 *   · larger touch targets + safe-area insets for notched phones.
 */

/* ---- cross-viewport safety nets (cheap, non-visual) ---- */
/* Content media never forces a page wider than the screen. Scoped away from
 * Leaflet, whose tiles must keep their intrinsic size. */
:where(img, svg, video):not(.leaflet-container *) { max-width: 100%; }

/* ======================== NAV TRANSITION — MOVED (v8) ========================
 * The page transition now lives in urs-2035.css, not here.
 *
 * It was written on the assumption, stated in its own comment, that "every app
 * page loads this stylesheet". That was true of the app and false of the
 * product: About, Mission, How it works, Economics, Goals, Explore, Terms,
 * Privacy, Refund and Delete account load urs-2035.css ONLY. A cross-document
 * View Transition needs BOTH documents to opt in, so every step between the app
 * and those ten pages hard-cut while the app itself faded — the exact white
 * flash the rule existed to remove.
 *
 * urs-2035.css is loaded by all thirty real pages and by none of the four email
 * templates, which is precisely the right coverage, so the block lives there. */

/* ============================ BEVEL FINISH (v3) ============================
 * Cross-viewport, intentional (unlike the mobile shell below). A light, warm
 * bevel woven through the FORM layer — the controls people type into, the
 * action buttons they press, and the dialog shells those forms live in — to
 * give edges texture and a clearer boundary without changing layout or color.
 *
 * Mechanism: layered box-shadow. Light reads from the top-left (warm cream
 * highlight), shadow falls to the bottom-right (warm bark). Tokens below so the
 * whole system tunes from one place. Tuned to be felt, not shouted.
 *
 * Cascade notes (why this lands without per-page edits):
 *   · Form CONTROLS set no resting box-shadow on any page, so the recessed
 *     inset applies at rest; each page's :focus ring (which replaces box-shadow)
 *     still wins on focus — resting bevel, focus ring, no conflict.
 *   · Filled BUTTONS: the selector list matches each class at its own
 *     specificity, so equal-specificity rules (.btn.primary, .deploy, .cbtn …)
 *     win on source order. The big soft CTA drop-shadow is intentionally
 *     traded for the beveled look + a tight lift.
 *   · .dialog drop-shadows were inconsistent across pages; this unifies them
 *     and adds the inset. The mobile bottom-sheet keeps its own shadow + bevel
 *     (see the dialog rule inside the breakpoint).
 */
/* DEEPENED (v12), alongside the elevation ramp in urs-2035.css. The note above
 * says "tuned to be felt, not shouted"; the ask was for it to be shouted a
 * little, so that a member filling in a long form can see where one field ends
 * and the next begins. Same mechanism, same three states, more of each — a
 * field is a deeper well, a button has a firmer edge, a dialog sits further off
 * the page. Nothing here changes layout or colour. */
:root{
  /* raised: filled buttons + dialog shells */
  --bevel-up:    inset 1px 1px 0 rgba(255,253,248,.42),
                 inset -1px -1px 1px rgba(47,38,32,.26),
                 0 2px 4px rgba(47,38,32,.22);
  /* recessed: text fields (a well you type into) */
  --bevel-in:    inset 5px 2px 4px rgba(47,38,32,.20),
                 inset -1px -1px 1px rgba(255,253,248,.70);
  /* pressed: momentary on :active */
  --bevel-press: inset 1px 1px 4px rgba(47,38,32,.36),
                 inset -1px -1px 0 rgba(255,253,248,.16);
  /* one consistent modal drop-shadow, paired with the inset on dialogs */
  --dialog-drop: 0 34px 80px -26px rgba(60,40,20,.64);
}

/* Form controls — recessed wells. Checkboxes/radios stay flat. */
input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]),
select, textarea { box-shadow: var(--bevel-in); }

/* Filled action buttons — raised, beveled edge. Listed at the specificities the
 * pages use so equal-specificity rules win by source order. */
.btn, .btn.primary, .btn.soft, .deploy, .ghost,
.cbtn, .addbtn, .btnclay, .mini, .empty button,
.dfoot .save, .dfoot .cancel, .dfoot2 .save, .dfoot2 .cancel {
  box-shadow: var(--bevel-up);
}
/* Tactile press on the principal submit buttons. */
.btn.primary:active, .btn.soft:active, .deploy:active, .ghost:active,
.cbtn:active, .addbtn:active, .btnclay:active, .mini:active,
.dfoot .save:active, .dfoot .cancel:active,
.dfoot2 .save:active, .dfoot2 .cancel:active {
  box-shadow: var(--bevel-press);
}

/* Dialog shells — the frame a form sits in. Inset bevel + unified drop. */
.dialog { box-shadow: var(--bevel-up), var(--dialog-drop); }

/* ================== A DIALOG NEVER RUNS OFF THE SCREEN (v11) ==================
 * Cross-viewport, and the mobile sheet below is the same idea for phones.
 *
 * REPORTED ON BOYCOTTS: open a campaign and the panel runs off the bottom with
 * no way to scroll it and no way to move it. Measured on the live page at a
 * 675px-tall viewport: the dialog was 720px tall, so its footer — Freeze,
 * Report, Claim, Contact organizer — sat 85px below the fold.
 *
 * WHAT WAS ACTUALLY HAPPENING, because "no scroll" was not quite true. Those
 * pages put `overflow-y:auto` on the OVERLAY and no height bound on the dialog,
 * so the scroll that exists moves the whole dialog — header and footer with it —
 * and it only had 125px of travel. Past that the scroll CHAINED to the page
 * behind: measured, scrollY went to 400 while the dialog sat still. That is
 * exactly what "unmovable, no scroll" feels like — you scroll, the panel inches
 * and stops, and the page starts sliding around underneath it.
 *
 * THE RULE IS THE ONE SEVEN PAGES ALREADY HAD, and the trouble was that they
 * each had their own copy. Thirteen pages define .dialog themselves:
 *
 *   index, posts, profile, seek, showcase, showcase-edit   max-height:90vh + flex column   (right)
 *   coordinator                                            max-height:86vh + overflow:auto (bounded, but scrolls the header away)
 *   requirements                                           bounded only inside a media query
 *   shop, calendar                                         no bound
 *   boycotts, boycott-page, projects                       no bound  ← reported
 *
 * One rule in thirteen places, disagreeing — the same drift as the vendor list
 * (278) and the invoice folder rule (279). So it belongs here, once: core.css
 * loads after every page's inline <style>, so this wins at equal specificity and
 * the per-page copies that already agree change nothing.
 *
 * THE SHAPE: bound the dialog to the overlay, make it a column, and give the
 * BODY the scroll. The header keeps the close button on screen and the footer
 * keeps the actions on screen, however long the text is.
 *
 * `max-height: 100%` AND NOT A dvh CALC. The first attempt was
 * calc(100dvh - 32px), which ignores that each overlay sets its own padding —
 * 40px top and bottom on the boycotts family, 20px on the centred ones. At a
 * 900px window that put the dialog's bottom edge 8px past the fold on five
 * pages: the bound was right about the screen and wrong about the box. 100%
 * resolves against the overlay's CONTENT box, so the padding is already taken
 * off whatever each page chose. Every .overlay here is `position:fixed;
 * inset:0`, so that height is definite and the percentage always resolves.
 *
 * overscroll-behavior stops the chain: scrolling to the end of a dialog must not
 * start scrolling the page behind it. */
.overlay { overscroll-behavior: contain; }

.dialog {
  max-height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* The three body classes this codebase has accumulated for the same region:
 * .dbody (boycotts, boycott-page), .dbody2 (the core.js cardOverlay pages),
 * .dbd (coordinator). Named together so none of them is the one that was
 * forgotten — which is how this started. */
.dialog > .dbody,
.dialog > .dbody2,
.dialog > .dbd {
  flex: 1 1 auto;
  min-height: 0;                  /* without this a flex child refuses to shrink */
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Head and foot hold their size, so neither is what gets squeezed. */
.dialog > .dhead, .dialog > .dhead2, .dialog > .dhd,
.dialog > .dfoot, .dialog > .dfoot2, .dialog > .dft, .dialog > .sheet-foot {
  flex: 0 0 auto;
}

/* ============================ MOBILE ============================ */
@media (max-width: 760px) {

  /* No accidental sideways panning. overflow-x:clip (not hidden) so it never
   * turns <body> into a scroll container — keeps position:sticky topbars working. */
  html, body { overflow-x: clip; }

  /* ---- top bar: two rows on a phone ----
   * One row can't hold the wordmark, six nav links, and the right-side actions
   * without them colliding. Let the bar wrap: logo + actions share the top row,
   * and the nav drops to its own full-width, swipeable row beneath. The order:
   * properties (not DOM order) decide which items pack onto the first line. */
  .top, .topbar {
    flex-wrap: wrap;
    gap: 10px;
    padding: 10px 14px;
  }
  /* Row 1, left: logo can shrink, and the wordmark ellipsizes only if a screen
   * is truly narrow — so it never pushes into the actions. */
  .logo { order: 1; flex: 1 1 auto; min-width: 0; }
  .logo b {
    font-size: 15px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Row 1, right: the actions stay pinned to the far end of the top row. */
  .top .right, .topbar .right { order: 2; margin-left: auto; }

  /* Row 2: the full-width swipeable nav. flex-basis:100% forces the wrap. */
  .top .nav, .topbar .nav {
    order: 3;
    flex: 1 1 100%;
    flex-wrap: nowrap;                /* keep groups on ONE row so the row can scroll */
    margin-left: 0;
    gap: 2px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;            /* Firefox */
    scroll-snap-type: x proximity;
  }
  /* Pill groups + their links must not wrap or shrink, or they stack inside
   * the bar instead of overflowing past its edge for the parent to scroll. */
  .top .navgrp, .topbar .navgrp { flex-wrap: nowrap; flex: 0 0 auto; }
  .top .navgrp a, .topbar .navgrp a { flex: 0 0 auto; }
  .top .nav::-webkit-scrollbar,
  .topbar .nav::-webkit-scrollbar { display: none; }   /* WebKit/Blink */
  .top .nav a, .topbar .nav a {
    white-space: nowrap;
    padding: 8px 11px;
    scroll-snap-align: start;
  }

  /* ---- the headline fix: centered modal -> bottom sheet ----
   * The .overlay/.dialog system (driven by core.js cardOverlay and used on
   * index/profile/seek/requirements/projects/calendar/coordinator) centered a dialog that
   * could be taller than the phone, pushing its top — and the close button —
   * above the viewport. Anchor it to the bottom and let it own the height. */
  .overlay {
    place-items: end stretch;        /* bottom-anchored, full width */
    padding: 0;
  }
  .dialog {
    width: 100%;
    max-width: none;
    max-height: 92dvh;               /* dvh so the keyboard can't bury it */
    border-radius: 16px 16px 0 0;
    box-shadow: var(--bevel-up), 0 -12px 44px -20px rgba(60, 40, 20, .55);
    animation: uors-sheet-up .22s ease;
  }
  @keyframes uors-sheet-up {
    from { transform: translateY(14px); opacity: .65; }
    to   { transform: none;             opacity: 1;   }
  }

  /* Header stays pinned so the close button is always one tap away. */
  .dhead2 {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--cream, #fffdf8);
    border-radius: 16px 16px 0 0;
  }
  /* Generous tap target for close, on either modal system. */
  .dhead2 .x, .sheet-head .x { min-width: 40px; min-height: 40px; }

  /* ---- chat inside the sheet: fill it, keep the composer above the keyboard ---- */
  /* .dbody2.chat-body hard-codes height:62vh on desktop; on a phone let the
   * sheet bound the height and have the body flex to fill. */
  .dbody2.chat-body {
    height: auto;
    max-height: none;
    flex: 1 1 auto;
    min-height: 0;
  }
  .chat-scroll { min-height: 0; }    /* allow the scroll region to shrink */
  .chat-compose {
    position: sticky;
    bottom: 0;
    background: var(--cream, #fffdf8);
    padding-bottom: calc(8px + env(safe-area-inset-bottom));
  }

  /* Sheet footers / composers clear the home indicator on notched phones. */
  .sheet-foot { padding-bottom: calc(11px + env(safe-area-inset-bottom)); }
}

/* Respect reduced-motion: no slide-up. */
@media (max-width: 760px) and (prefers-reduced-motion: reduce) {
  .dialog { animation: none; }
}

/* ---- the Requirements bell ----
 * core.js mounts it; the markup lives in each page's nav. The bell is HIDDEN when
 * the count is zero, so "present" already means "there is something to answer" —
 * it therefore renders at full strength always. An earlier version keyed its
 * colour off `.nav a.on`, which tied "are there notifications" to "is this the
 * current tab" — two unrelated facts, and wrong on every page but one.
 *
 * The [hidden] rule is load-bearing: `[hidden]{display:none}` is a USER-AGENT
 * rule, and the author `display` above outranks it, so without this the badge
 * shows permanently. It is one attribute selector more specific, so it wins with
 * no !important. Do not remove it as redundant.
 */
.nav a .bell{display:inline-flex;align-items:center;gap:3px;margin-left:6px;vertical-align:baseline;}
.nav a .bell[hidden]{display:none;}
.nav a .bell .bico{display:none;}
.nav a .bell i{font-style:normal;font-size:11px;font-weight:700;line-height:1;min-width:16px;height:16px;
  padding:0 4px;border-radius:9px;background:var(--clay);color:#fff;display:inline-flex;
  align-items:center;justify-content:center;font-variant-numeric:tabular-nums;}
.nav a .bell::before{content:"\1F514";font-size:11.5px;line-height:1;}

/* ============================================================
   SIBLING-SITE LOCKUP  (core.css v9)
   ============================================================
   The URS lockup is the heptagon mark beside bold serif caps, a red rule under
   the word, and a small uppercase line under that. unionsmarket.org's masthead
   is the canonical drawing of it; this is the same lockup, rendered inside the
   app so a member sees the SAME mark in both places.

   It lives here rather than in each page because it is now used in three
   surfaces and would otherwise drift. The one copy that cannot read this file
   is the myprofiles.site Worker, which server-renders under a CSP with no
   shared stylesheet — that copy carries a comment pointing back here.

   The mark is served as the real PNG, never redrawn. Redrawing it is how two
   "identical" marks end up slightly different. */

.urs-sitelink{display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:11px 15px;margin:0 0 16px;
  background:var(--cream,#fffdf8);border:1px solid var(--line,#e7dcc9);border-radius:13px;
  text-decoration:none;color:inherit;transition:border-color .15s,transform .15s;}
a.urs-sitelink:hover{border-color:var(--clay,#c75d3a);transform:translateY(-1px);}
.urs-sitelink .go{margin-left:auto;font-size:12.5px;font-weight:600;color:var(--taupe,#9a8a78);
  white-space:nowrap;}
a.urs-sitelink:hover .go{color:var(--clay,#c75d3a);}
.urs-sitelink .note{font-size:12.5px;color:var(--taupe,#9a8a78);line-height:1.4;
  flex:1 1 200px;min-width:0;}

.urs-lock{display:flex;align-items:center;gap:10px;flex:none;color:var(--bark,#2f2620);
  text-decoration:none;}
.urs-lock img{width:34px;height:34px;display:block;border-radius:8px;}
.urs-lockw{display:flex;flex-direction:column;line-height:1;min-width:0;}
.urs-lockw .wd{font-family:var(--fdisplay,'Fraunces',Georgia,serif);font-weight:700;font-size:16px;
  letter-spacing:.055em;text-transform:uppercase;color:var(--bark,#2f2620);white-space:nowrap;}
/* The trademark symbol rides the WORDMARK only — never the <title>, og:title or
   a link preview, where it reads as noise. Only a mark that is actually claimed
   gets one; the flag is per-site, not decoration. */
.urs-lockw .wd .tm{font-size:.5em;font-weight:600;letter-spacing:0;top:-.55em;margin-left:.06em;}
.urs-lockw .bar{display:block;height:3px;background:var(--red,#c1272d);border-radius:2px;margin-top:4px;}
.urs-lockw .sub{font-size:9px;font-weight:600;letter-spacing:.15em;text-transform:uppercase;
  color:var(--taupe,#9a8a78);margin-top:4px;white-space:nowrap;}

/* Small: the note is what goes, not the mark. */
@media(max-width:560px){
  .urs-sitelink{gap:10px;padding:10px 12px;}
  .urs-sitelink .note{display:none;}
  .urs-lockw .wd{font-size:15px;}
}

/* ============================================================
   ACTING SUBJECT — the "My" group while you are inside a business
   or a network (core.css v10)
   ============================================================
   The tunnel has to be visible. A member acting as a business and not knowing
   it is the failure this whole mechanism exists to prevent, so the group is
   tinted, the label is the subject's NAME rather than the word "My", and the
   way out sits in the same group as the way around. */
.navgrp.my.acting{background:#f3e7dc;border-color:#dcc4ab;}
.navgrp.my.acting .acting-lbl{
  color:var(--clay-deep,#a84a2c);max-width:15ch;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;text-transform:none;letter-spacing:.01em;font-size:12px;}
.navgrp.my.acting a{color:var(--clay-deep,#a84a2c);}
.navgrp.my.acting a:hover{color:var(--clay,#c75d3a);}
.navgrp.my .acting-exit{
  font-family:inherit;font-size:12px;font-weight:700;cursor:pointer;
  border:1px solid #dcc4ab;background:#fff;color:var(--clay-deep,#a84a2c);
  border-radius:8px;padding:5px 10px;margin-left:4px;}
.navgrp.my .acting-exit:hover{border-color:var(--clay,#c75d3a);background:#fffaf5;}
