﻿/* ==========================================================================
   Portal site-specific styling (UI wave 2 - login branding, header title,
   self-hosted brand font). Linked in App.razor AFTER assets/css/app.min.css
   (the Studio ASP theme build output) so these rules can override it.
   Never edit the minified theme files - add overrides here instead.
   ========================================================================== */

/* Self-hosted Inter (weights 400 + 700). CSP is font-src 'self' with no
   external CDNs allowed, so Google Fonts links are blocked - these files were
   downloaded from the Google Fonts css2 API (fonts.gstatic.com, latin subset)
   into wwwroot/assets/fonts/ and are served same-origin. */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('../fonts/inter-400.woff2') format('woff2');
}

@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('../fonts/inter-700.woff2') format('woff2');
}

/* Header title band (top bar, left of the user dropdown). Sits inside
   .app-header .menu (flex:1) - the existing dropdown keeps its ms-auto, so
   this stays left-aligned automatically without any layout changes. */
.app-header-title {
    font-family: 'Inter', -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--bs-emphasis-color, #ebeef4);
    letter-spacing: 0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Public layout background (login / forgot-password / reset-password /
   error - anything under PublicPortalLayout). Full-bleed hero_bg.png with a
   dark tint so the centred card stays readable; tint colour matches the
   theme's own dark-mode body background (#212837), not an unrelated black,
   so it reads as part of the same theme rather than a bolted-on image. */
.public-portal-bg {
    background: linear-gradient(to bottom, rgba(33, 40, 55, 0.60), rgba(20, 24, 34, 0.72)),
                url('../img/hero_bg.png') center center / cover no-repeat #141822;
}

/* Above 1270px the square hero would be width-scaled into a heavy zoom. hero_bg_wide.png is the
   same image with mirrored, blurred, darkened flanks extending it to 3840x1024, so cover scales
   by height instead and the centre keeps its intended size on wide monitors. */
@media (min-width: 1271px) {
    .public-portal-bg {
        background: linear-gradient(to bottom, rgba(33, 40, 55, 0.60), rgba(20, 24, 34, 0.72)),
                    url('../img/hero_bg_wide.png') center center / cover no-repeat #141822;
    }
}

/* Login logo (PublicPortalLayout). The 2027 lockup is a white on transparent
   SVG, sized prominently but capped so it never overflows the 420px column on
   small phones. Soft drop shadow lifts it off the hero background. */
.public-portal-logo {
    width: min(250px, 70%);
    height: auto;
    filter: drop-shadow(0 6px 24px rgba(0, 0, 0, 0.50));
}

/* Field validation visuals. Blazor's EditForm puts an "invalid" class on the
   input itself (not Bootstrap's "is-invalid") and renders each message as
   div.validation-message, so both need styling here. Colours come from the
   theme's own invalid tokens (dark mode swaps in a softer red) with plain
   Bootstrap red fallbacks. */
.form-control.invalid {
    border-color: var(--bs-form-invalid-border-color, #dc3545);
}

.form-control.invalid:focus {
    border-color: var(--bs-form-invalid-border-color, #dc3545);
    box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}

/* Radios and checkboxes need their own rule. Blazor appends "invalid" to
   whatever base class the control was given, and InputRadio carries
   form-check-input rather than form-control, so the compound selector above
   can never match one. Without this a required radio group renders its
   message but shows no highlight at all on the control at fault. */
.form-check-input.invalid {
    border-color: var(--bs-form-invalid-border-color, #dc3545);
}

.form-check-input.invalid:focus {
    border-color: var(--bs-form-invalid-border-color, #dc3545);
    box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}

.validation-message {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.875em;
    text-align: start;
    color: var(--bs-form-invalid-color, #dc3545);
}

/* Slight translucency + blur on the login card so the background reads
   through without hurting text contrast (background is already dimmed by
   the gradient above). */
.public-portal-card {
    background-color: rgba(33, 40, 55, 0.90);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

/* Focus-ring bug (the thick outline on the login heading on page load):
   root cause was <FocusOnNavigate RouteData="routeData" Selector="h1" /> in
   Routes.razor, which programmatically focuses the h1 on every route change
   and the browser draws its default focus outline on that. Fixed at the
   source by removing FocusOnNavigate from Routes.razor (see that file) -
   this matches the Admin site's Routes.razor, which never had it, so no
   heading ever receives programmatic focus and no CSS override is needed
   here. Form-control/button focus rings are untouched - keyboard
   accessibility on inputs and buttons is unaffected. */

/* Brand logo, top left of the app header (UI wave 3). The theme's
   .brand .brand-logo rule carries padding 1.25rem 0 inside the 3.75rem
   header, which squeezed the old 38px raster shield (an RGB png with a
   baked background, hence the non transparent look) down to roughly 20px.
   Swap to the transparent white SVG lockup (logo-sld2027-wide.svg, about
   4:1 aspect), trim the padding, and let it fill the full remaining header
   height; width follows the aspect ratio so it spans most of the 15rem
   brand block. object-fit contain guards the aspect ratio if max-width
   ever binds on a narrow layout.

   Portal UI round A (2026-08-23): Nathan flagged the lockup as very tight
   against the header edges once it was live. The img keeps height 100% of
   this padded box, so bumping the padding both adds visible breathing room
   above and below and, as a direct side effect, renders the logo itself
   slightly smaller, without touching the SVG asset. */
.app-header .brand .brand-logo {
    padding: 0.85rem 0;
}

.app-header .brand .brand-logo img {
    height: 100%;
    width: auto;
    max-width: 100%;
    max-height: none;
    object-fit: contain;
    object-position: left center;
}

/* Content area background (authenticated pages, UI wave 3). The image sits
   in .app-content's own background stack, so it is scoped to the content
   region by construction; the fixed header and sidebar keep their opaque
   theme backgrounds and are never affected. A heavy tint of the theme body
   colour (#212837, the portal is locked to dark via data-bs-theme on the
   html tag) keeps the artwork to a faint texture so text keeps full
   contrast. background-attachment fixed sizes the cover image against the
   viewport instead of the full document height, and the image stays still
   while the body scrolls, so there is no per frame background reposition.
   Cards keep their own opaque backgrounds, which means panel content such
   as the floor plan image never has the texture behind it. Small screens
   keep the flat theme colour: mobile Safari ignores fixed attachment and
   would stretch the cover image across the entire document height. */
@media (min-width: 768px) {
    .app .app-content {
        background:
            linear-gradient(rgba(33, 40, 55, 0.88), rgba(33, 40, 55, 0.88)),
            url('../img/about_bg.png') center center / cover no-repeat;
        background-attachment: fixed;
    }
}

/* Pace loading spinner: disabled for the portal. Blazor Server's websocket circuit never
   satisfies pace's ajax/document monitors, so the spinner sat there spinning forever.
   paceOptions in App.razor stops it starting; this guarantees it can never render. */
.pace,
.pace-progress,
.pace-activity {
    display: none !important;
}

/* Phase 5 messaging: chat bubbles on the thread page. The message list is a
   flex column; own messages hug the right edge in the theme accent colour,
   the other party's hug the left in a neutral tint on the dark card. Message
   text keeps the delegate's typed line breaks via white-space pre-wrap (the
   markup renders plain Blazor-encoded text, never injected HTML), and
   overflow-wrap stops a single long token stretching a bubble past its
   column. */
.portal-chat-thread {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.portal-chat-bubble {
    max-width: 75%;
    padding: 0.625rem 0.875rem;
    border-radius: 1rem;
}

.portal-chat-bubble-own {
    align-self: flex-end;
    background-color: var(--bs-primary);
    color: #fff;
    border-bottom-right-radius: 0.25rem;
}

.portal-chat-bubble-other {
    align-self: flex-start;
    background-color: rgba(255, 255, 255, 0.08);
    border-bottom-left-radius: 0.25rem;
}

.portal-chat-text {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.portal-chat-meta {
    font-size: 0.75rem;
    opacity: 0.6;
    margin-top: 0.25rem;
}

/* Phase 5 messaging: unread-count badge on the sidebar Messages item. The
   theme's .menu-link lays out as a flex row, so margin-left auto pushes the
   badge to the far edge after the menu text. */
.app-sidebar .menu-link .portal-unread-badge {
    margin-left: auto;
}

/* Profiles UI round: sector tag chips (vendors list cards, vendor detail,
   editor preview). Replaces badge bg-secondary, which burned on the dark
   theme. Restyled 2026-08-31 (Nathan's call): the cyan outline read too
   blue, so the chips are now a darker slate fill with only a lean of blue,
   quiet metadata rather than links. Margin is baked in here so the markup
   carries no spacing utilities. */
.portal-tag-chip {
    display: inline-block;
    background: rgba(52, 64, 84, 0.55);
    border: 1px solid rgba(122, 143, 177, 0.28);
    color: #9fadbf;
    border-radius: 50rem;
    padding: 0.125rem 0.625rem;
    margin: 0 0.25rem 0.25rem 0;
    font-size: 0.75rem;
    line-height: 1.4;
    white-space: nowrap;
}

/* Clickable variant (vendors list cards): the chip is a real <button> that
   drives the page's tag filter, so it needs the button resets, a pointer,
   a hover lift, and a z-index above the card's stretched-link (otherwise
   the card link swallows the click and navigates instead of filtering). */
button.portal-tag-chip {
    position: relative;
    z-index: 2;
    cursor: pointer;
    appearance: none;
    font-family: inherit;
}

button.portal-tag-chip:hover {
    border-color: rgba(158, 178, 210, 0.55);
    color: #c4cfdc;
}

/* The compound selector ties button.portal-tag-chip:hover on specificity and
   sits later in the file, so hovering the ACTIVE chip keeps the active look
   instead of dropping back to the plain hover colours. */
button.portal-tag-chip.portal-tag-chip-active,
.portal-tag-chip-active {
    background: rgba(83, 103, 135, 0.7);
    border-color: rgba(158, 178, 210, 0.7);
    color: #dde5ee;
}

/* Vendor booth tiles (2026-08-31): miniature floor-plan booths for the
   vendors list, vendor detail, profile preview and organisation Booths
   card. The base tile is the colourless look; a booth with a custom colour
   gets its fill, text colour and glow inline from BoothTileRow (the hex is
   MapColourRules-normalised before it can reach a style attribute). */
.portal-booth-tiles {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
}

.portal-booth-tile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 3rem;
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
    background: #2f3a4d;
    border: 1px solid rgba(255, 255, 255, 0.16);
    color: #dfe5ec;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.portal-booth-tile-lg {
    min-width: 3.75rem;
    padding: 0.375rem 0.75rem;
    font-size: 0.875rem;
}

/* Status looks for the org's own Booths card (2026-09-02). Booth custom
   colours no longer appear anywhere in the portal; these two say whose the
   booth is, in the floor plan's own vocabulary. Approved: the "yours" solid
   primary. Requested: not theirs yet, so a dashed amber outline in the held
   family over a faint amber wash rather than a fill. */
.portal-booth-tile-approved {
    background: var(--bs-primary, #1f6bff);
    border-color: transparent;
    color: #fff;
}

.portal-booth-tile-requested {
    background: rgba(255, 193, 7, 0.12);
    border: 1px dashed rgba(255, 193, 7, 0.75);
    color: #ffd75e;
}

/* Floor plan + gov round: neutral status chips ("Resolved", "Blocked",
   "Not accepting messages" and friends). Same metrics as .portal-tag-chip
   above so the two read as one family, but a quiet grey outline instead of
   cyan: these are passive state notes, not semantic badges and not tags. */
.portal-status-chip {
    display: inline-block;
    background: transparent;
    border: 1px solid rgba(173, 181, 189, 0.45);
    color: #adb5bd;
    border-radius: 50rem;
    padding: 0.125rem 0.625rem;
    margin: 0 0.25rem 0.25rem 0;
    font-size: 0.75rem;
    line-height: 1.4;
    white-space: nowrap;
}

/* Profiles UI round: the vendor profile rotating gallery. One image at a
   time inside a relative wrapper; the prev/next arrows float over the image
   edges, the caption sits under it and the dot indicators under that. The
   image is capped in height and cover-cropped so mixed aspect ratios rotate
   without the column jumping around. */
.portal-carousel {
    position: relative;
}

.portal-carousel-image {
    width: 100%;
    max-height: 420px;
    object-fit: cover;
    cursor: zoom-in;
    display: block;
}

.portal-carousel-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 2.25rem;
    height: 2.25rem;
    border: 0;
    border-radius: 50%;
    background-color: rgba(20, 24, 34, 0.55);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
}

.portal-carousel-arrow:hover,
.portal-carousel-arrow:focus-visible {
    background-color: rgba(20, 24, 34, 0.85);
}

.portal-carousel-arrow-prev {
    left: 0.5rem;
}

.portal-carousel-arrow-next {
    right: 0.5rem;
}

.portal-carousel-caption {
    margin-top: 0.5rem;
    font-size: 0.875rem;
    opacity: 0.75;
}

.portal-carousel-dots {
    display: flex;
    justify-content: center;
    gap: 0.375rem;
    margin-top: 0.5rem;
}

.portal-carousel-dot {
    width: 0.5rem;
    height: 0.5rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.3);
}

.portal-carousel-dot.active {
    background-color: #6edff6;
}

/* Profiles UI round: the gallery lightbox. A pure Blazor full viewport
   overlay (no Bootstrap JS): fixed inset 0 with a near-black backdrop,
   centred flex, image constrained to the viewport with contain so nothing
   is cropped at full size. z-index 2000 clears the theme's fixed header and
   every Bootstrap layer (toasts top out at 1090). The overlay is position
   fixed and never taller than the viewport, so it needs no internal
   scrolling and no body scroll lock. */
.portal-lightbox {
    position: fixed;
    inset: 0;
    z-index: 2000;
    background-color: rgba(0, 0, 0, 0.85);
    display: flex;
    align-items: center;
    justify-content: center;
}

.portal-lightbox-body {
    margin: 0;
    max-width: 90vw;
    text-align: center;
}

.portal-lightbox-image {
    max-width: 90vw;
    max-height: 85vh;
    object-fit: contain;
    border-radius: 0.375rem;
}

.portal-lightbox-caption {
    margin-top: 0.75rem;
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.9375rem;
}

.portal-lightbox-close {
    position: absolute;
    top: 1rem;
    right: 1rem;
    width: 2.75rem;
    height: 2.75rem;
    border: 0;
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.12);
    color: #fff;
    font-size: 1.125rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

.portal-lightbox-close:hover,
.portal-lightbox-close:focus-visible {
    background-color: rgba(255, 255, 255, 0.25);
}

.portal-lightbox-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 2.75rem;
    height: 2.75rem;
    border: 0;
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.12);
    color: #fff;
    font-size: 1.125rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

.portal-lightbox-arrow:hover,
.portal-lightbox-arrow:focus-visible {
    background-color: rgba(255, 255, 255, 0.25);
}

.portal-lightbox-arrow-prev {
    left: 1rem;
}

.portal-lightbox-arrow-next {
    right: 1rem;
}

/* "You" chip (portal UI round, 2026-08-17). Marks the signed-in delegate's own
   row in the delegate directory and their own organisation's card in the vendor
   directory. Same metrics as .portal-tag-chip and .portal-status-chip above so
   the three read as one family, in gold because neither of those colours is
   free: cyan means "tag" and grey means "passive state". The point of the chip
   is reassurance rather than warning, so the star carries a soft glow and the
   row is otherwise styled exactly like everyone else's - a delegate who opts in
   wants to see their entry come back the way a stranger searching would see it,
   not highlighted into something different. */
.portal-own-chip {
    display: inline-block;
    background: transparent;
    border: 1px solid rgba(255, 193, 7, 0.55);
    color: #ffc94d;
    border-radius: 50rem;
    padding: 0.125rem 0.5rem;
    margin-left: 0.375rem;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
    vertical-align: middle;
}

.portal-own-chip .fa {
    font-size: 0.65rem;
    margin-right: 0.125rem;
    text-shadow: 0 0 6px rgba(255, 201, 77, 0.65);
}

/* News items (portal UI round, 2026-08-17). Each item gets a real header band
   carrying the title and date, plus a 3px left accent bar - the same structural
   device the theme already uses for the active page tab, so it reads as part of
   the system rather than a new invention.

   Consecutive items alternate between two shades of ONE hue (the theme primary
   blue, then a dark navy) rather than two different colours. The alternation is
   there to show where one item ends and the next begins in a list that
   previously ran together; keeping both in the blue family means it separates
   without turning into a stripe pattern that implies the items differ in kind.

   scroll-margin-top clears the fixed 3.75rem app header, so the dashboard's
   /news#news-{id} deep links land the item below the header rather than under
   it. */
.portal-news-card {
    border-left: 3px solid transparent;
    scroll-margin-top: 5rem;
}

.portal-news-header {
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.portal-news-title {
    font-family: 'Inter', -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    font-weight: 700;
    font-size: 1.0625rem;
    line-height: 1.35;
    letter-spacing: 0.01em;
    color: var(--bs-emphasis-color, #ebeef4);
}

/* 0.7 rather than the theme's usual 0.5/0.55 for muted dates: this one sits on a
   tinted gradient at small size, where 0.55 lands around 3.5:1 and misses WCAG AA
   for body text. 0.7 clears it without making the date compete with the title. */
.portal-news-date {
    font-size: 0.8125rem;
    color: rgba(255, 255, 255, 0.7);
    white-space: nowrap;
}

.portal-news-card-bright {
    border-left-color: var(--bs-primary, #0d6efd);
}

.portal-news-card-bright .portal-news-header {
    background: linear-gradient(90deg, rgba(13, 110, 253, 0.16), rgba(13, 110, 253, 0.03));
}

.portal-news-card-deep {
    border-left-color: #1b4b8f;
}

.portal-news-card-deep .portal-news-header {
    background: linear-gradient(90deg, rgba(27, 75, 143, 0.34), rgba(27, 75, 143, 0.06));
}

/* Prominent page tabs. The theme's default nav-tabs are quiet enough to miss
   on the dark background, so any page level tab strip opts in by adding
   portal-page-tabs alongside nav nav-tabs on the ul (Organisation today,
   reusable on any future tabbed page). Links get a larger bolder face and
   more padding, inactive links sit muted with a hover lift, and the active
   link carries a clear 3px accent bar along its bottom edge in the theme
   primary colour. Both states carry the same 3px bottom border so switching
   tabs never shifts the strip's height. */
.portal-page-tabs .nav-link {
    font-size: 1.05rem;
    font-weight: 600;
    padding: 0.75rem 1.25rem;
    color: rgba(255, 255, 255, 0.55);
    border-bottom: 3px solid transparent;
}

.portal-page-tabs .nav-link:hover,
.portal-page-tabs .nav-link:focus-visible {
    color: rgba(255, 255, 255, 0.85);
}

.portal-page-tabs .nav-link.active {
    color: var(--bs-emphasis-color, #ebeef4);
    border-bottom: 3px solid var(--bs-primary, #0d6efd);
}

/* Portal UI round A (2026-08-23): dashboard stat cards. The three top cards
   (event countdown, your organisation, messages) currently render as plain
   .card elements, identical to the four quick-link cards underneath them, so
   nothing on the page tells a delegate these are two different kinds of
   thing. A darker blue diagonal gradient plus a soft outer glow pulls them
   into their own visual group without needing new markup structure; the
   quick-link cards below are left completely alone, so the contrast between
   the two rows is the whole effect. Border and shadow both key off the theme
   primary colour so the glow reads as "part of this theme", not a random
   accent. */
.portal-stat-card {
    background: linear-gradient(135deg, #1c3f73 0%, #10203c 100%);
    border-color: rgba(13, 110, 253, 0.45);
    box-shadow: 0 0 22px rgba(13, 110, 253, 0.22);
}

/* Portal UI round A (2026-08-23): dashboard "Latest Updates" list items.
   Reuses the left-accent-bar-plus-horizontal-gradient device from the full
   /news page cards above (.portal-news-card-bright etc.) so the compact
   dashboard list reads as the same family, but as one flat class rather than
   an alternating bright/deep pair, since this list is a short preview, not
   the full article feed. Border and background reset to 0/none first
   because the markup may still carry Bootstrap's list-group-item classes;
   this class must win regardless of what else is on the element. Hover and
   focus-visible get a brighter version of the same gradient so keyboard and
   mouse users both get clear feedback, and color/text-decoration are reset
   so it still reads correctly if applied to an anchor.

   .portal-news-date (defined above, in the /news section) is reused as-is
   here for the date inside each item rather than duplicated: it already
   reads as a small muted date label at a size and contrast tuned for this
   exact context. */
.portal-news-item {
    display: block;
    border: 0;
    border-left: 3px solid var(--bs-primary, #0d6efd);
    border-radius: 0.25rem;
    padding: 0.75rem 1rem;
    margin-bottom: 0.5rem;
    background: linear-gradient(90deg, rgba(13, 110, 253, 0.10), rgba(13, 110, 253, 0.02));
    color: inherit;
    text-decoration: none;
    transition: background-color 0.15s ease-in-out;
}

.portal-news-item:last-child {
    margin-bottom: 0;
}

.portal-news-item:hover,
.portal-news-item:focus-visible {
    background: linear-gradient(90deg, rgba(13, 110, 253, 0.18), rgba(13, 110, 253, 0.05));
    color: inherit;
}

/* Portal UI round A (2026-08-23): delegate directory selected panel and
   scroll list.

   .portal-selected-panel marks the detail card for whichever delegate is
   currently picked from the list. It borrows the same accent-bar-plus-
   gradient device as the news items above, but on a standalone panel rather
   than a list row it needs a full border and an outer glow as well, so the
   "this is the one you picked" read holds even when the panel is scrolled
   to the top of the viewport with nothing above it for contrast.

   2026-08-31: the fill dropped from a 30 to 50 percent bright-blue wash to a
   deep navy that reads DARKER than the card it sits on. On a small directory
   row the bright wash was fine; on the session booking form it covered a
   whole EditForm and was hard on the eyes (Nathan). The border, accent bar
   and glow are unchanged, they carry the selected read on their own. Shared
   by THREE consumers: the delegate directory selected panel, its org filter
   chip card, and the session booking form. */
.portal-selected-panel {
    border: 1px solid var(--bs-primary, #0d6efd);
    border-left: 4px solid var(--bs-primary, #0d6efd);
    background: linear-gradient(90deg, rgba(8, 44, 108, 0.45), rgba(8, 44, 108, 0.22));
    box-shadow: 0 0 18px rgba(13, 110, 253, 0.32);
}

/* .portal-scroll-list caps the delegate list container so a long directory
   scrolls inside its own box instead of growing the page underneath the
   fixed header. max-height rather than a fixed height so a short list (a
   small org, or a filtered result) simply sizes to its content and never
   shows an empty scrollable void; overflow-y auto only puts a scrollbar on
   screen once the content actually exceeds the cap. */
.portal-scroll-list {
    max-height: 32rem;
    overflow-y: auto;
}

/* Portal UI round A (2026-08-23): the badge note under the profile display
   name field. The rest of the form's helper text uses small muted grey type
   (opacity 0.5 to 0.6), which is exactly wrong here: this note is telling a
   delegate that the text they are about to type is what prints on their
   physical event badge, and a muted footnote is too easy to skip past
   before they type something they would not want on a badge. Styled as a
   small left-accent callout instead, in the theme primary colour, with a
   bold emphasis-coloured label so it reads as a heads-up rather than a
   footnote. */
.portal-badge-note {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.375rem;
    padding: 0.375rem 0.625rem;
    background-color: rgba(13, 110, 253, 0.12);
    border-left: 3px solid var(--bs-primary, #0d6efd);
    border-radius: 0.25rem;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--bs-emphasis-color, #ebeef4);
}

.portal-badge-note .fa {
    color: var(--bs-primary, #0d6efd);
    flex-shrink: 0;
}

/* ==========================================================================
   Live updates round, lane F (2026-09-01): /sessions offering tabs and slot
   request buttons. Append-only block; nothing above this line was touched.

   .portal-offering-tabs is the horizontal strip above the active offering's
   card, one real button per published offering. These deliberately do NOT
   reuse .portal-page-tabs (the quiet underline style on /organisation): the
   brief for this page is tabs that read as unmistakable BUTTONS, so they are
   solid pills in the booth-tile slate family (.portal-booth-tile palette)
   with a filled theme-primary active state and a hover lift. flex-wrap keeps
   the strip usable when offering names outgrow one row on a phone.

   2026-09-02 (Nathan): the resting pill was #2f3a4d, within a hair of the card
   fill (#2a374f), so the strip vanished into the card. The strip is now a dark
   inset track (segmented-control read), resting pills are a step lighter with
   a visible border, and the active pill is a brighter primary gradient with a
   stronger glow. The sessions card and table rules live at the END of this
   file (.portal-sessions-card, .portal-slot-table, .portal-slot-day).
   ========================================================================== */
.portal-offering-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
    width: fit-content;
    max-width: 100%;
    margin-bottom: 1rem;
    padding: 0.375rem;
    border-radius: 50rem;
    background: rgba(0, 0, 0, 0.28);
    border: 1px solid rgba(255, 255, 255, 0.10);
}

.portal-offering-tab {
    appearance: none;
    font-family: inherit;
    cursor: pointer;
    border-radius: 50rem;
    padding: 0.625rem 1.5rem;
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
    background: #3d4d6b;
    border: 1px solid rgba(255, 255, 255, 0.28);
    color: #f2f5f9;
    transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.portal-offering-tab:hover,
.portal-offering-tab:focus-visible {
    background: #4a5d80;
    border-color: rgba(158, 178, 210, 0.7);
    color: #fff;
}

/* The active tab is a filled theme-primary pill with a soft glow, the same
   accent register as .portal-selected-panel, so "which offering am I on" and
   "which slot have I picked" share one visual language. The hover and
   focus-visible states are restated here so pointing at the active tab keeps
   the active look instead of dropping back to the plain hover colours. */
.portal-offering-tab.portal-offering-tab-active,
.portal-offering-tab.portal-offering-tab-active:hover,
.portal-offering-tab.portal-offering-tab-active:focus-visible {
    background: linear-gradient(180deg, #3d8bff 0%, #1f6bff 100%);
    border-color: #5b9cff;
    color: #fff;
    box-shadow: 0 0 18px rgba(31, 107, 255, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

/* Slot rows: the unselected "Request this time" button. Previously
   btn-outline-primary, a ghost outline that read as a label on the dark
   striped table; now a solid subdued slate fill (the same family as the
   tabs' resting state) with a primary-leaning hover, so it is obviously
   pressable before the pointer ever reaches it. The SELECTED state stays
   Bootstrap's btn-primary, untouched, so picked-vs-pickable stays clearly
   distinct. Sits alongside .btn/.btn-sm, which carry the metrics; this
   class carries colour only. */
.btn.portal-slot-request-btn {
    background: #2f3a4d;
    border-color: rgba(255, 255, 255, 0.22);
    color: #dfe5ec;
}

.btn.portal-slot-request-btn:hover,
.btn.portal-slot-request-btn:focus-visible {
    background: var(--bs-primary, #0d6efd);
    border-color: var(--bs-primary, #0d6efd);
    color: #fff;
}

/* Vendor profile description (WYSIWYG description round, 2026-09-01). The description renders as
   sanitised markup inside .portal-profile-description on the /vendors/{id} detail page and the
   /organisation-profile preview. These rules style ONLY the tags the ProfileHtmlRules allowlist
   can emit (p, br, strong/b, em/i, u, ul/ol/li, h4/h5/h6, a, blockquote) - the sanitiser strips
   author styles and classes, so this block is the one place the rendered look is decided. Spacing
   is deliberately tighter than the page defaults: the description sits inside a card column. */
.portal-profile-description p {
    margin-bottom: 0.75rem;
}

.portal-profile-description p:last-child,
.portal-profile-description ul:last-child,
.portal-profile-description ol:last-child,
.portal-profile-description blockquote:last-child {
    margin-bottom: 0;
}

.portal-profile-description ul,
.portal-profile-description ol {
    margin-bottom: 0.75rem;
    padding-left: 1.5rem;
}

.portal-profile-description h4,
.portal-profile-description h5,
.portal-profile-description h6 {
    margin-top: 1rem;
    margin-bottom: 0.5rem;
}

.portal-profile-description h4:first-child,
.portal-profile-description h5:first-child,
.portal-profile-description h6:first-child {
    margin-top: 0;
}

.portal-profile-description blockquote {
    border-left: 3px solid rgba(255, 255, 255, 0.25);
    padding-left: 0.75rem;
    margin: 0 0 0.75rem 0;
    opacity: 0.85;
}

/* ==========================================================================
   Sessions page listing (2026-09-02, Nathan): the offering card and the "Your
   session requests" card faded into the page, and Bootstrap's dark-theme
   stripe (a 5 percent black tint on an already dark card) left slot rows
   indistinguishable. Append-only block.

   .portal-sessions-card sits on BOTH cards: one step lighter than the page
   (#212837) and the default card (#2a374f), with a drop shadow so the listing
   reads as a panel ON the page. The request form's .portal-selected-panel
   (deep navy, shared by three pages) is untouched and still contrasts.

   .portal-slot-table sits on both tables alongside table-striped table-hover:
   odd rows get a light tint and even rows a dark one so BOTH read as rows,
   hover is primary-tinted, and the header is small caps over a stronger rule.

   .portal-slot-day is the per-day h6 inside the slot list: accent bar plus
   small caps so each day is an obvious group boundary.
   ========================================================================== */
.portal-sessions-card {
    background: linear-gradient(180deg, #34435f 0%, #2c394f 100%);
    border: 1px solid rgba(255, 255, 255, 0.14);
    box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
}

.portal-slot-table {
    --bs-table-bg: transparent;
    --bs-table-striped-bg: rgba(255, 255, 255, 0.07);
    --bs-table-hover-bg: rgba(31, 107, 255, 0.16);
    --bs-table-border-color: rgba(255, 255, 255, 0.10);
}

.portal-slot-table > tbody > tr:nth-of-type(even) > * {
    --bs-table-bg-type: rgba(0, 0, 0, 0.14);
}

.portal-slot-table > thead > tr > th {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: rgba(235, 238, 244, 0.65);
    border-bottom: 2px solid rgba(255, 255, 255, 0.18);
}

.portal-slot-table > tbody > tr > td {
    padding-top: 0.55rem;
    padding-bottom: 0.55rem;
}

/* Row edges (Nathan, 2026-09-02, revised the same day): each body row is closed
   on its left AND right by the same thin grey the panels use for their outline
   (.portal-sessions-card border), so rows read as discrete items sitting in
   the panel rather than floating stripes. A blue accent bar was tried first
   and rejected. The row whose slot is currently picked
   (.portal-slot-row-selected, set by Sessions.razor) keeps a primary-tinted
   fill; the tint sets the Bootstrap per-cell variable so it beats the odd/even
   stripe without !important. */
.portal-slot-table > tbody > tr > td:first-child {
    border-left: 1px solid rgba(255, 255, 255, 0.14);
}

.portal-slot-table > tbody > tr > td:last-child {
    border-right: 1px solid rgba(255, 255, 255, 0.14);
}

.portal-slot-table > tbody > tr.portal-slot-row-selected > * {
    --bs-table-bg-type: rgba(31, 107, 255, 0.14);
}

/* When the table sits FLUSH against the card edge (no .card-body padding, as the
   "Your session requests" card does), the card's own border already draws the
   row's side edge, and adding another 1px beside it doubles up. Drop the side
   edges in that placement only; tables inside .card-body keep them. */
.portal-sessions-card > .table-responsive > .portal-slot-table > tbody > tr > td:first-child {
    border-left: 0;
}

.portal-sessions-card > .table-responsive > .portal-slot-table > tbody > tr > td:last-child {
    border-right: 0;
}

.portal-slot-day {
    border-left: 3px solid var(--bs-primary, #1f6bff);
    padding-left: 0.625rem;
    font-size: 0.8125rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--bs-emphasis-color, #ebeef4);
}

/* Confirm modal (schedule round 2026-09-02, D5). The backdrop is focused programmatically on
   open so Escape works straight away; a keyboard-opened dialog would otherwise paint the
   browser's focus ring around the whole viewport. The card inside carries the visible focus. */
.portal-confirm-backdrop:focus {
    outline: none;
}

/* Cost acknowledgement on the session request form (Nathan, 2026-09-02). The
   price sits in an OUTLINED red badge, bold, inline with the checkbox label so
   the figure is read in the sentence that commits to it. Outline rather than a
   fill: a solid red block beside a checkbox reads as an error state. The
   validation message for the box uses the existing .validation-message rule. */
.portal-charge-ack .form-check-label {
    line-height: 1.6;
}

.portal-charge-badge {
    display: inline-block;
    border: 2px solid #fd7e14;
    border-radius: 0.25rem;
    padding: 0 0.45rem;
    margin: 0 0.15rem;
    font-weight: 700;
    color: #ffb36b;
    white-space: nowrap;
}

/* ==========================================================================
   Messages restyle (Nathan, 2026-09-02). Append-only block.

   .portal-thread-list is the inbox: a list-group of anchors, not a table, so
   it is a SIBLING of .portal-slot-table rather than that class reused. Same
   odd/even tints, same primary hover and the same grey row rule, driven
   through Bootstrap's list-group variables so the theme's own hover and
   active rules keep working. The inbox card wears .portal-sessions-card
   (defined above) so the panel treatment matches the sessions listing.

   .portal-thread-avatar is the initials circle beside the other party's name
   on the thread page: a fixed primary-tinted disc so the header has a visual
   anchor without loading anything. .portal-thread-org is the muted
   organisation line under the name; .portal-thread-link is the name and
   organisation link treatment, plain until hover like the vendor card titles.
   ========================================================================== */
.portal-thread-list {
    --bs-list-group-bg: transparent;
    --bs-list-group-border-color: rgba(255, 255, 255, 0.10);
    --bs-list-group-action-hover-bg: rgba(31, 107, 255, 0.16);
    --bs-list-group-action-active-bg: rgba(31, 107, 255, 0.24);
}

.portal-thread-list > .list-group-item:nth-of-type(odd) {
    --bs-list-group-bg: rgba(255, 255, 255, 0.07);
}

.portal-thread-list > .list-group-item:nth-of-type(even) {
    --bs-list-group-bg: rgba(0, 0, 0, 0.14);
}

.portal-thread-avatar {
    flex: 0 0 auto;
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(31, 107, 255, 0.22);
    border: 1px solid rgba(31, 107, 255, 0.55);
    color: var(--bs-emphasis-color, #ebeef4);
    font-weight: 600;
    font-size: 1.125rem;
    letter-spacing: 0.04em;
    user-select: none;
}

.portal-thread-org {
    color: rgba(235, 238, 244, 0.65);
}

.portal-thread-link {
    text-decoration: none;
}

.portal-thread-link:hover {
    text-decoration: underline;
}

/* Powered by Hexlitix, foot of the sidebar (2026-09-03). Reuses the theme's menu-item structure so
   the minified sidebar hides the text and centres the icon like every other item; this only mutes it
   and sizes the icon. */
.portal-powered-by .menu-link {
    opacity: 0.75;
    font-size: 0.8125rem;
}

.portal-powered-by .menu-link:hover,
.portal-powered-by .menu-link:focus-visible {
    opacity: 1;
}

.portal-powered-by .menu-icon img {
    width: 1.125rem;
    height: 1.125rem;
    border-radius: 3px;
    display: inline-block;
    vertical-align: middle;
}
