/**
 * CertFlow Portal Shell — shared by every CertFlow portal.
 * All classes use the .certflow-portal-* prefix.
 *
 * ===== WHY EVERY RULE IS SCOPED TO .certflow-portal-shell =====
 *
 * public.css and portal.css BOTH define .certflow-portal-* rules, and both load
 * on a staff portal page (portal.css is enqueued with ['certflow-public'], so it
 * comes second). public.css still owns the organization portal until phase 3.
 *
 * Scoping on the bare .certflow-portal root would therefore break in both
 * directions at once:
 *   - a shell rule at the SAME specificity as its public.css counterpart wins by
 *     order and restyles the organization portal, which this change must not touch;
 *   - a shell rule at LOWER specificity (public.css writes many of its rules as
 *     `.certflow-portal .certflow-portal-X`, i.e. (0,2,0)) silently loses on the
 *     lecturer portal, and the symptom is a component that looks right on one
 *     portal and wrong on the other.
 *
 * `.certflow-portal-shell` is an opt-in second class that only the rebuilt
 * surfaces carry. It lifts every rule here to at least (0,2,0) — enough to beat
 * public.css's bare (0,1,0) rules and to tie-and-win against its (0,2,0) ones —
 * while making it impossible for any of this to reach a portal that has not
 * opted in. Rules that must outrank a public.css selector with an element in it
 * (e.g. `.certflow-portal .certflow-portal-nav a`, (0,2,1)) are written one level
 * deeper on purpose; those carry a comment.
 *
 * NOTE: where public.css declares a property this file does not, the public.css
 * value still applies regardless of specificity. Properties reset explicitly
 * below for that reason are marked "cascade reset".
 */

.certflow-portal-shell {
    /* Themed at runtime by Assets::enqueuePortalShell(); fallbacks match WhiteLabel's default. */
    --cf-accent: #1a5f7a;
    --cf-accent-rgb: 26, 95, 122;
    --cf-content-bg: #f8f9fb;

    /* Two-tier radius scale. Containers 6, controls 4, chips 3. No 8/10/12px. */
    --cf-r-container: 6px;
    --cf-r-control: 4px;
    --cf-r-chip: 3px;

    color: #1a1a2e;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

/* ===== Theme isolation =====
   Layer 1 uses :where() so it carries ZERO specificity — every
   .certflow-portal-* component rule beats it without !important.
   This is the client-dashboard approach; public.css's (0,1,1) version
   only won by cascade order and could lose to a theme's (0,2,0) rule. */
:where(.certflow-portal-shell) button,
:where(.certflow-portal-shell) input,
:where(.certflow-portal-shell) select,
:where(.certflow-portal-shell) textarea {
    -webkit-appearance: none;
    appearance: none;
    background: none;
    border: none;
    box-shadow: none;
    border-radius: 0;
    margin: 0;
    padding: 0;
    color: inherit;
    font: inherit;
    text-transform: none;
    letter-spacing: normal;
}

:where(.certflow-portal-shell) select { -webkit-appearance: menulist; appearance: menulist; }
:where(.certflow-portal-shell) button { cursor: pointer; }
:where(.certflow-portal-shell) a { text-decoration: none; box-shadow: none; border: 0; color: var(--cf-accent); }

/* Layer 2: element resets public.css has and client-dashboard.css lacks.
   The table reset is mandatory — staff portals are table surfaces. */
.certflow-portal-shell *,
.certflow-portal-shell *::before,
.certflow-portal-shell *::after { box-sizing: border-box; }

.certflow-portal-shell table,
.certflow-portal-shell thead,
.certflow-portal-shell tbody,
.certflow-portal-shell tfoot,
.certflow-portal-shell tr,
.certflow-portal-shell th,
.certflow-portal-shell td {
    margin: 0;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    color: inherit;
    vertical-align: middle;
    border-collapse: collapse;
    border-spacing: 0;
    box-shadow: none;
    outline: none;
}

.certflow-portal-shell h1,
.certflow-portal-shell h2,
.certflow-portal-shell h3,
.certflow-portal-shell h4,
.certflow-portal-shell h5,
.certflow-portal-shell h6 { margin: 0; padding: 0; border: none; line-height: 1.3; }

.certflow-portal-shell p { margin: 0; padding: 0; }
.certflow-portal-shell ul,
.certflow-portal-shell ol,
.certflow-portal-shell li { margin: 0; padding: 0; list-style: none; }
.certflow-portal-shell img { max-width: 100%; height: auto; }

/* Layer 3: interactive-state neutraliser.
   !important is safe here because CertFlow never animates these itself.
   Host themes (militra-lt's design-tokens.css is the known case) animate
   button:hover with a transform, which made bare portal controls jump. */
.certflow-portal-shell a:hover,
.certflow-portal-shell a:focus,
.certflow-portal-shell button:hover,
.certflow-portal-shell button:focus,
.certflow-portal-shell button:active {
    transform: none !important;
    animation: none !important;
    text-decoration: none;
}

/* Layer 4: host themes that add a border on button:hover grow borderless
   buttons by 1px and shift the row.
   ⛔ This is an ALLOWLIST of controls that are genuinely borderless, NOT a
   `:not(.certflow-portal-btn)` catch-all. The catch-all form was (0,3,1) and
   therefore outranked every component's own (0,2,0)/(0,3,0) rule, so it stripped
   the border off filter pills, page links and the attendance tick on hover and
   focus — the pills rendered inconsistently depending on which one the pointer
   or focus happened to be on. Failing safe matters more than covering every
   control here: a new bordered button is unaffected by default, and a new bare
   one just needs adding to this list. */
.certflow-portal-shell .certflow-portal-modal-close:hover,
.certflow-portal-shell .certflow-portal-modal-close:focus,
.certflow-portal-shell .certflow-portal-notice-close:hover,
.certflow-portal-shell .certflow-portal-notice-close:focus,
.certflow-portal-shell .certflow-portal-notice-action:hover,
.certflow-portal-shell .certflow-portal-notice-action:focus,
.certflow-portal-shell .certflow-portal-search-clear:hover,
.certflow-portal-shell .certflow-portal-search-clear:focus { border: 0; }

/* Host themes routinely put a transition on `button`/`a`. Combined with the
   shell's own colour changes that produced visible lag on the filter pills —
   the pressed state faded in over the theme's duration/easing rather than
   switching. The shell animates nothing on these elements itself. */
.certflow-portal-shell a,
.certflow-portal-shell button { transition: none; }

/* Focus visibility — restored globally, not in 2 places out of 5. */
.certflow-portal-shell :focus-visible {
    outline: 2px solid var(--cf-accent);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .certflow-portal-shell *,
    .certflow-portal-shell *::before,
    .certflow-portal-shell *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* ===== Header ===== */
.certflow-portal-shell .certflow-portal-header {
    display: flex;
    align-items: center;
    justify-content: flex-start;   /* cascade reset: public.css says space-between */
    gap: 14px;
    margin: 0;                      /* cascade reset: public.css adds margin-bottom */
    padding: 16px 20px;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: var(--cf-r-container) var(--cf-r-container) 0 0;
    border-bottom: none;
    flex-wrap: wrap;
}
.certflow-portal-shell .certflow-portal-avatar {
    width: 44px; height: 44px; border-radius: 50%;
    background: var(--cf-accent); color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-weight: 600; font-size: 15px; flex: 0 0 44px;
    overflow: hidden;
}
.certflow-portal-shell .certflow-portal-avatar img { width: 100%; height: 100%; object-fit: cover; }
.certflow-portal-shell .certflow-portal-header-identity { display: block; min-width: 0; }
.certflow-portal-shell .certflow-portal-title { font-size: 17px; font-weight: 600; color: #1a1a2e; margin: 0; }
.certflow-portal-shell .certflow-portal-subtitle { font-size: 13px; color: #6b7280; margin: 2px 0 0; }
.certflow-portal-shell .certflow-portal-header-summary { margin-left: auto; font-size: 13px; color: #6b7280; }
.certflow-portal-shell .certflow-portal-header-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ===== Tabs =====
   Styling hangs off the class, NOT the element selector. public.css put all
   nav styling on `.certflow-portal-nav a`, so converting a tab to a <button>
   silently stripped it. */
.certflow-portal-shell .certflow-portal-nav {
    display: flex; gap: 4px; flex-wrap: wrap;
    background: #fff; padding: 0 20px; margin: 0;   /* cascade reset: public.css adds margin-bottom */
    border: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;
    border-radius: 0;
}
/* One level deeper than the component alone: public.css's counterpart is
   `.certflow-portal .certflow-portal-nav a` at (0,2,1), which a (0,2,0) rule
   would lose to. This is (0,3,0). */
.certflow-portal-shell .certflow-portal-nav .certflow-portal-nav-item {
    font-size: 14px; padding: 11px 14px; color: #6b7280;
    background: none;
    border: none; border-bottom: 2px solid transparent; border-radius: 0;
    display: inline-flex; align-items: center; gap: 7px;
    min-height: 44px; white-space: nowrap;
    box-shadow: none;
}
.certflow-portal-shell .certflow-portal-nav .certflow-portal-nav-item:hover {
    color: #1a1a2e; background: none;
}
.certflow-portal-shell .certflow-portal-nav .certflow-portal-nav-item[aria-current="page"] {
    color: #1a1a2e; font-weight: 600; background: none;
    border-bottom-color: var(--cf-accent);
    box-shadow: none;
}
.certflow-portal-shell .certflow-portal-nav-count {
    font-size: 12px; color: #9ca3af; background: #f3f4f6;
    border-radius: var(--cf-r-chip); padding: 1px 6px;
    min-width: 0;
}

/* ===== Content card ===== */
.certflow-portal-shell .certflow-portal-card {
    background: var(--cf-content-bg);
    border: 1px solid #e5e7eb; border-top: none;
    border-radius: 0 0 var(--cf-r-container) var(--cf-r-container);
    margin: 0;                      /* cascade reset: public.css adds margin-bottom */
    padding: 16px 20px;
    box-shadow: none;
}
.certflow-portal-shell .certflow-portal-filter-bar {
    display: flex; align-items: center; gap: 10px;
    flex-wrap: wrap; margin-bottom: 12px;
}
.certflow-portal-shell .certflow-portal-filter-bar-spacer { flex: 1 1 auto; }

/* ===== Breadcrumb ===== */
.certflow-portal-shell .certflow-portal-breadcrumb {
    display: flex; align-items: center; gap: 6px;
    font-size: 13px; color: #6b7280; margin-bottom: 12px;
}
.certflow-portal-shell .certflow-portal-breadcrumb .current { color: #1a1a2e; font-weight: 600; }

/* ===== Standalone message card =====
   The logged-out "please log in" prompt on every portal. Rendered without a
   header/nav around it, so it carries its own container chrome. */
.certflow-portal-shell .certflow-portal-message {
    background: #fff; border: 1px solid #e5e7eb;
    border-radius: var(--cf-r-container);
    padding: 56px 32px; text-align: center;
    max-width: 480px; margin: 56px auto;
}
.certflow-portal-shell .certflow-portal-message h2 { font-size: 18px; margin-bottom: 8px; }
.certflow-portal-shell .certflow-portal-message p { font-size: 14px; color: #6b7280; margin-bottom: 18px; }

/* ===== Utility ===== */
.certflow-portal-hidden { display: none !important; }

/* ===== Table ===== */
.certflow-portal-shell .certflow-portal-table-responsive {
    overflow-x: auto; background: #fff;
    border: 1px solid #e5e7eb; border-radius: var(--cf-r-container);
}
.certflow-portal-shell .certflow-portal-table { width: 100%; margin: 0; }
.certflow-portal-shell .certflow-portal-table th {
    background: #f3f4f6; color: #6b7280;
    text-transform: uppercase; letter-spacing: .4px;
    font-size: 11px; font-weight: 600; text-align: left;
    padding: 9px 12px; white-space: nowrap;
    border: none;
}
.certflow-portal-shell .certflow-portal-table td {
    padding: 11px 12px; font-size: 14px; color: #374151;
    border: none; border-top: 1px solid #f1f2f4;
}
.certflow-portal-shell .certflow-portal-table tbody tr:hover { background: #fafbfc; }

/* Sortable headers. Only emitted for columns the server can actually sort —
   a non-functional sort arrow is worse than none. */
.certflow-portal-shell .certflow-portal-table th[aria-sort] { cursor: pointer; }
.certflow-portal-shell .certflow-portal-table th[aria-sort]::after { content: ' \2195'; opacity: .45; }
.certflow-portal-shell .certflow-portal-table th[aria-sort="ascending"]::after { content: ' \2191'; opacity: 1; }
.certflow-portal-shell .certflow-portal-table th[aria-sort="descending"]::after { content: ' \2193'; opacity: 1; }

/* ===== Pagination ===== */
.certflow-portal-shell .certflow-portal-pagination {
    display: flex; align-items: center; gap: 10px;
    flex-wrap: wrap; margin-top: 12px; font-size: 13px; color: #6b7280;
}
.certflow-portal-shell .certflow-portal-pagination-links { display: flex; gap: 4px; margin-left: auto; }
.certflow-portal-shell .certflow-portal-page-link {
    min-width: 34px; min-height: 34px; padding: 5px 9px;
    border: 1px solid #d1d5db; border-radius: var(--cf-r-control);
    background: #fff; color: #374151; font-size: 13px;
}
.certflow-portal-shell .certflow-portal-page-link[aria-current="page"] {
    background: var(--cf-accent); border-color: var(--cf-accent); color: #fff; font-weight: 600;
}
.certflow-portal-shell .certflow-portal-page-link[disabled] { opacity: .5; pointer-events: none; }
/* Named -per-page-SELECT, not -per-page: the organization portal already uses
   `.certflow-portal-per-page` for a WRAPPER <div> around its own select, and one
   class cannot style a flex container and a native control at the same time. */
.certflow-portal-shell .certflow-portal-per-page-select {
    min-height: 34px; padding: 4px 8px; font-size: 13px;
    border: 1px solid #d1d5db; border-radius: var(--cf-r-control);
    background: #fff; color: #374151;
    /* cascade reset: themes and public.css commonly set `select { width: 100% }`,
       and the shell's :where() reset carries no specificity to stop it. Inside
       this flex row that made the control wrap onto its own line and span the
       full card. `flex: 0 0 auto` keeps it beside the "Showing …" label. */
    width: auto; max-width: 100%; flex: 0 0 auto; display: inline-block;
}

/* ===== Search ===== */
.certflow-portal-shell .certflow-portal-search-wrapper { position: relative; }
.certflow-portal-shell .certflow-portal-search-wrapper input {
    padding: 8px 30px 8px 12px; font-size: 14px;
    border: 1px solid #d1d5db; border-radius: var(--cf-r-control);
    background: #fff; min-width: 200px; min-height: 40px;
}
/* ⛔ public.css styles this SAME element through `.certflow-portal`, which the
   shell root also carries (`class="certflow-portal certflow-portal-shell"`), and
   at the same specificity (0,2,0) — so only source order separated them. That
   left the button half-dressed: public.css painted a 20px grey circle and set
   `align-items`/`justify-content` for a flex box, while the rule below set
   `display: block`, so the centring was inert and the glyph sat off-centre in a
   circle that transitioned its background on hover. The JS compounded it by
   toggling `is-visible`, which public.css has never heard of (it expects
   `visible`), so its own show rule never fired either.
   Every property public.css sets is therefore reset here EXPLICITLY. Do not trim
   this block back to the two or three lines that look like they matter — the
   ones that look redundant are the ones neutralising the other stylesheet. */
.certflow-portal-shell .certflow-portal-search-clear {
    position: absolute; right: 6px; top: 50%;
    transform: translateY(-50%);
    width: 24px; height: 24px; min-width: 24px; min-height: 24px;
    padding: 0; border: 0; line-height: 1;
    background: transparent; border-radius: 50%;
    color: #9ca3af; font-size: 17px;
    cursor: pointer;
    display: none;
    align-items: center; justify-content: center;
    transition: background .15s ease, color .15s ease;
}
/* display:flex, not block — it is what makes the centring above real. */
.certflow-portal-shell .certflow-portal-search-clear.is-visible { display: flex; }
.certflow-portal-shell .certflow-portal-search-clear:hover,
.certflow-portal-shell .certflow-portal-search-clear:focus-visible {
    background: #eef0f3; color: #4b5563;
}

/* ===== Filter pills =====
   A segmented control, not a lozenge: the radius comes from the same
   --cf-r-control the buttons and inputs use. The original 999px sat outside the
   shell's own two-tier radius scale and read as a stray pink blob next to
   everything else on the page. */
.certflow-portal-shell .certflow-portal-filter-pills { display: flex; gap: 6px; flex-wrap: wrap; }
.certflow-portal-shell .certflow-portal-filter-pill {
    display: inline-flex; align-items: center;
    font-size: 13px; padding: 6px 13px; min-height: 40px;
    border: 1px solid #d1d5db; border-radius: var(--cf-r-control);
    background: #fff; color: #6b7280; font-weight: 500;
    white-space: nowrap;
}
.certflow-portal-shell .certflow-portal-filter-pill:hover {
    border-color: #9ca3af; color: #374151; background: #fff;
}
.certflow-portal-shell .certflow-portal-filter-pill[aria-pressed="true"] {
    background: rgba(var(--cf-accent-rgb), .08);
    border-color: var(--cf-accent); color: var(--cf-accent); font-weight: 600;
}
.certflow-portal-shell .certflow-portal-filter-pill[aria-pressed="true"]:hover {
    background: rgba(var(--cf-accent-rgb), .14);
    border-color: var(--cf-accent); color: var(--cf-accent);
}

/* ===== Badges ===== */
.certflow-portal-shell .certflow-portal-badge {
    display: inline-block; font-size: 11px; font-weight: 600;
    padding: 2px 9px; border-radius: 999px;
}
.certflow-portal-shell .certflow-portal-badge-success { background: #dcfce7; color: #166534; }
.certflow-portal-shell .certflow-portal-badge-info    { background: #dbeafe; color: #1e40af; }
.certflow-portal-shell .certflow-portal-badge-warning { background: #fef3c7; color: #92400e; }
.certflow-portal-shell .certflow-portal-badge-danger  { background: #fee2e2; color: #991b1b; }
.certflow-portal-shell .certflow-portal-badge-secondary { background: #f3f4f6; color: #4b5563; }

/* ===== Buttons ===== */
.certflow-portal-shell .certflow-portal-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    font-size: 14px; font-weight: 500; padding: 9px 15px; min-height: 40px;
    border: 1px solid #d1d5db; border-radius: var(--cf-r-control);
    background: #fff; color: #374151;
    text-decoration: none; box-shadow: none;
}
.certflow-portal-shell .certflow-portal-btn-primary { background: var(--cf-accent); border-color: var(--cf-accent); color: #fff; }
.certflow-portal-shell .certflow-portal-btn-secondary { background: #fff; border-color: #d1d5db; color: #374151; }
.certflow-portal-shell .certflow-portal-btn-sm { font-size: 13px; padding: 6px 12px; min-height: 36px; }
.certflow-portal-shell .certflow-portal-btn[disabled] { opacity: .5; pointer-events: none; }
/* Destructive stays neutral until hover — a permanently-red row action reads
   as an error state on every row. */
.certflow-portal-shell .certflow-portal-btn-danger-soft { background: #fff; border-color: #d1d5db; color: #6b7280; }
.certflow-portal-shell .certflow-portal-btn-danger-soft:hover { background: #fee2e2; border-color: #fca5a5; color: #991b1b; }

/* ===== Progress ===== */
.certflow-portal-shell .certflow-portal-progress-bar {
    height: 6px; background: #e5e7eb;
    border-radius: var(--cf-r-chip); overflow: hidden;
}
.certflow-portal-shell .certflow-portal-progress-bar-fill {
    height: 100%; background: var(--cf-accent);
    transition: width .3s ease;
}

/* ===== Modal — ONE contract: overlay > modal =====
   The overlay carries `certflow-portal-shell` ITSELF (it is appended to <body>,
   outside the portal root, because a position:fixed overlay under a transformed
   ancestor stops being fixed). Hence the compound `.a.b` selectors here rather
   than descendant ones.

   Hidden state is visibility+pointer-events, NOT display:none, for two reasons:
   the opacity transition still plays, and — more importantly — a closed overlay
   can never become an invisible full-screen click trap if some path forgets to
   re-hide it. `is-open` is the SINGLE toggle; no second hidden class. */
.certflow-portal-shell.certflow-portal-modal-overlay {
    position: fixed; inset: 0; z-index: 100000;
    background: rgba(15, 23, 42, .45);
    display: flex; align-items: center; justify-content: center;
    padding: 16px;
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity .2s ease, visibility .2s ease;
}
.certflow-portal-shell.certflow-portal-modal-overlay.is-open {
    opacity: 1; visibility: visible; pointer-events: auto;
}
.certflow-portal-shell .certflow-portal-modal {
    position: relative;
    display: block;
    inset: auto;
    background: #fff; border-radius: var(--cf-r-container);
    max-width: 520px; width: 100%; max-height: 90vh; overflow-y: auto;
    padding: 20px 22px;
}
.certflow-portal-shell .certflow-portal-modal-header { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.certflow-portal-shell .certflow-portal-modal-header h3 { font-size: 16px; flex: 1; }
.certflow-portal-shell .certflow-portal-modal-close {
    position: static; font-size: 20px; color: #9ca3af;
    min-width: 40px; min-height: 40px; background: none; border: none;
}
.certflow-portal-shell .certflow-portal-modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 18px; }
/* portal-core.js confirmDialog(). A class, not an inline style attribute —
   the shell forbids inline styles, and the 480px bottom-sheet rule below
   needs to be able to override this width. */
.certflow-portal-shell .cf-confirm-modal { max-width: 440px; }

/* ===== Notice / toast =====
   NOTE: portal-core.js injects these INSIDE the shell root, never into <body>.
   Outside it they lose both the design tokens and the reset. */
.certflow-portal-shell .certflow-portal-notice {
    position: fixed; bottom: 20px; left: 50%;
    /* cascade reset: public.css pins its notice to top/right, and an unset
       side here would leave BOTH edges anchored and stretch the toast. */
    top: auto; right: auto;
    transform: translateX(-50%);
    z-index: 100001; display: flex; align-items: center; gap: 10px;
    max-width: 460px; width: auto; padding: 12px 14px;
    border: none; border-radius: var(--cf-r-container); font-size: 14px;
    background: #1a1a2e; color: #fff;
    box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
}
.certflow-portal-shell .certflow-portal-notice-success { background: #166534; color: #fff; border: none; }
.certflow-portal-shell .certflow-portal-notice-error   { background: #991b1b; color: #fff; border: none; }
.certflow-portal-shell .certflow-portal-notice-warning { background: #92400e; color: #fff; border: none; }
.certflow-portal-shell .certflow-portal-notice-info    { background: #1a1a2e; color: #fff; border: none; }
.certflow-portal-shell .certflow-portal-notice p { flex: 1; margin: 0; }
.certflow-portal-shell .certflow-portal-notice-close {
    position: static; color: #fff; opacity: .8;
    min-width: 32px; min-height: 32px; width: auto; height: auto;
    background: none; border: none;
}
.certflow-portal-shell .certflow-portal-notice-action {
    color: #fff; text-decoration: underline; font-weight: 600;
    min-height: 32px; background: none; border: none;
}

/* ===== Export format menu =====
   public.css carries an earlier copy of these rules in WordPress-admin colours
   (#d5d8dc, #1d2327, #f0f6fc) and unscoped, i.e. specificity (0,1,0). Scoping to
   the shell here makes it (0,2,0), so these win wherever both load, and the menu
   reads as part of the portal rather than as a piece of wp-admin. */
.certflow-portal-shell .certflow-portal-export-menu { position: relative; display: inline-block; }

.certflow-portal-shell .certflow-portal-export-dropdown {
    display: none;
    position: absolute; right: 0; top: calc(100% + 6px);
    min-width: 180px; padding: 4px;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: var(--cf-r-container);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .10);
    z-index: 50;
}
.certflow-portal-shell .certflow-portal-export-menu.open .certflow-portal-export-dropdown { display: block; }

.certflow-portal-shell .certflow-portal-export-option {
    display: block; width: 100%;
    padding: 9px 12px;
    background: transparent; border: 0;
    border-radius: var(--cf-r-control);
    text-align: left; font-size: 14px; line-height: 1.3;
    color: #1a1a2e; cursor: pointer;
}
.certflow-portal-shell .certflow-portal-export-option:hover,
.certflow-portal-shell .certflow-portal-export-option:focus-visible {
    background: rgba(var(--cf-accent-rgb), .10);
    color: var(--cf-accent);
}

/* ===== Schedule date range =====
   ⚠ The two fields are given an explicit width. They used to be
   <input type="date">, whose intrinsic width in Chrome is ~180px BEFORE padding
   -- wider than the space the filter bar leaves after the pills and the search
   box -- so `flex-wrap: wrap` on the group broke them onto separate rows and the
   pair swallowed a third of the bar. They are plain text inputs now, which have
   no such intrinsic width, but the explicit basis is what keeps the group one
   row regardless. `nowrap` is the belt to that braces. */
.certflow-portal-shell .certflow-portal-daterange {
    display: flex; align-items: center; gap: 6px; flex-wrap: nowrap;
}
.certflow-portal-shell .certflow-portal-daterange input[type="text"] {
    width: 118px; flex: 0 0 118px; min-width: 0;
    padding: 7px 9px; font-size: 14px; line-height: 1.3;
    border: 1px solid #d8dce3; border-radius: var(--cf-r-control);
    background: #fff; color: #1a1a2e;
    cursor: pointer;   /* readonly, but it opens a calendar */
}
.certflow-portal-shell .certflow-portal-daterange input[type="text"]::placeholder { color: #9ca3af; }
.certflow-portal-shell .certflow-portal-daterange-sep { color: #9ca3af; flex: 0 0 auto; }

.certflow-portal-shell .cf-lect-daterange-clear {
    flex: 0 0 auto;
    width: 24px; height: 24px; padding: 0;
    border: 0; border-radius: 50%;
    background: transparent; color: #9ca3af;
    font-size: 17px; line-height: 1; cursor: pointer;
    display: none; align-items: center; justify-content: center;
    transition: background .15s ease, color .15s ease;
}
.certflow-portal-shell .cf-lect-daterange-clear.is-visible { display: flex; }
.certflow-portal-shell .cf-lect-daterange-clear:hover,
.certflow-portal-shell .cf-lect-daterange-clear:focus-visible { background: #eef0f3; color: #4b5563; }

/* ===== Empty / error / skeleton ===== */
.certflow-portal-shell .certflow-portal-empty,
.certflow-portal-shell .certflow-portal-error { text-align: center; padding: 40px 20px; color: #6b7280; font-size: 14px; }
.certflow-portal-shell .certflow-portal-empty h3 { font-size: 15px; color: #1a1a2e; margin-bottom: 5px; }
.certflow-portal-shell .certflow-portal-empty-actions { margin-top: 14px; display: flex; gap: 8px; justify-content: center; }
.certflow-portal-shell .certflow-portal-error { color: #991b1b; }
.certflow-portal-shell .certflow-portal-error-retry { margin-top: 12px; }

/* Skeleton must have a display and a size, or it collapses to nothing. */
.certflow-portal-shell .certflow-portal-skeleton {
    display: block; height: 12px; width: 100%; border-radius: var(--cf-r-chip);
    background: #eef0f3; position: relative; overflow: hidden;
}
.certflow-portal-shell .certflow-portal-skeleton.is-narrow { width: 42px; }
.certflow-portal-shell .certflow-portal-skeleton::after {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,.7), transparent);
    transform: translateX(-100%);
    animation: certflow-portal-shimmer 1.3s infinite;
}
@keyframes certflow-portal-shimmer { 100% { transform: translateX(100%); } }

@media (max-width: 768px) {
    .certflow-portal-shell .certflow-portal-header { padding: 13px 15px; }
    .certflow-portal-shell .certflow-portal-header-summary { margin-left: 0; width: 100%; }
    .certflow-portal-shell .certflow-portal-nav { padding: 0 10px; overflow-x: auto; flex-wrap: nowrap; }
    /* Labels stay visible. Icon-only nav is unlabelled for sighted AND
       screen-reader users. No repeat(3, 1fr) — tab count varies. */
    .certflow-portal-shell .certflow-portal-card { padding: 13px 15px; }

    /* Tables become purpose-built cards via data-label. */
    .certflow-portal-shell .certflow-portal-table-responsive.is-stacked thead { display: none; }
    .certflow-portal-shell .certflow-portal-table-responsive.is-stacked tr {
        display: block; background: #fff; border: 1px solid #e5e7eb;
        border-radius: var(--cf-r-container); margin-bottom: 8px; padding: 4px 0;
    }
    .certflow-portal-shell .certflow-portal-table-responsive.is-stacked td {
        display: flex; justify-content: space-between; gap: 12px; border-top: none;
    }
    .certflow-portal-shell .certflow-portal-table-responsive.is-stacked td::before {
        content: attr(data-label); font-weight: 600; color: #6b7280; font-size: 12px;
    }

    /* iOS zooms in on any input under 16px and never zooms back out. */
    .certflow-portal-shell input,
    .certflow-portal-shell select,
    .certflow-portal-shell textarea { font-size: 16px !important; }
    /* ===== Schedule cards =====
       ⛔ Scoped to #cf-lect-schedule-wrap, NOT to the generic .is-stacked
       rules above. The ROSTER table uses those too; its values are short, so the
       label-left / value-right form reads well there, and switching it to
       label-above would make every participant card several rows taller -- a
       direct regression to the tick-down-the-list workflow. Only the schedule
       needs this, because only the schedule carries long values: a session title
       plus a dozen course codes, and a full street address, forced into a narrow
       right-hand column and wrapping into a ragged block.

       ⚠ `is-stacked` sits on the WRAPPER (portal.php), not on the <table>. A
       selector written against #cf-lect-schedule will not match. */
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked td {
        display: block; text-align: left;
        padding: 8px 14px; border-top: none;
    }
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked td::before {
        display: block; margin-bottom: 3px;
        font-size: 11px; font-weight: 600; letter-spacing: .4px;
        text-transform: uppercase; color: #9ca3af;
    }
    /* The session title carries itself; a "SESSION" caption above it is noise. */
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked td:nth-child(2)::before { display: none; }
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked td:nth-child(2) strong {
        font-size: 15px; line-height: 1.35; display: block;
    }
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked td:nth-child(2) span {
        display: block; margin-top: 3px;
        font-size: 12px; line-height: 1.45; color: #6b7280;
    }
    /* Booked count and its bar read as one unit rather than a number and a rule. */
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked .certflow-portal-progress-bar {
        margin-top: 6px; width: 100%;
    }
    /* The action button matches every other full-width control on mobile. */
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked td:last-child {
        padding-top: 4px; padding-bottom: 12px;
    }
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked td:last-child .certflow-portal-btn {
        width: 100%; justify-content: center;
    }
    /* Row 1 (When) is the card's heading line -- date and time side by side
       rather than a date with a time orphaned under it. */
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked td:first-child {
        padding-top: 12px;
    }
    .certflow-portal-shell #cf-lect-schedule-wrap.is-stacked tr {
        padding: 0;
    }

    /* Pagination: the per-page select and the page buttons were sized for a
       desktop toolbar and dominated the foot of the card on a phone. */
    .certflow-portal-shell .certflow-portal-pagination {
        flex-wrap: wrap; gap: 8px; justify-content: center;
    }
    .certflow-portal-shell .certflow-portal-pagination select {
        padding: 6px 8px; font-size: 16px; width: auto; min-width: 0;
    }


    /* The two date boxes share a row rather than stacking into a tall column. */
    .certflow-portal-shell .certflow-portal-daterange { width: 100%; }
    .certflow-portal-shell .certflow-portal-daterange input[type="text"] { flex: 1 1 0; width: auto; }
}

@media (max-width: 480px) {
    /* Bottom sheet — applies to the ONE modal contract, so it can no longer
       miss the lecturer portal the way public.css's variant did. */
    .certflow-portal-shell.certflow-portal-modal-overlay { align-items: flex-end; padding: 0; }
    .certflow-portal-shell .certflow-portal-modal { max-width: 100%; border-radius: var(--cf-r-container) var(--cf-r-container) 0 0; }
    .certflow-portal-shell .cf-confirm-modal { max-width: 100%; }
    .certflow-portal-shell .certflow-portal-modal-actions .certflow-portal-btn { flex: 1; }
    .certflow-portal-shell .certflow-portal-notice { left: 10px; right: 10px; transform: none; max-width: none; }
}
