/* Outcomes Ledger — app styles. Palette matches the plugin's own default report theme
   (ink_brass: #1F2A52 primary / #A9812F accent) so the app and its generated reports feel
   like one product. */

#ol-app-root {
	--ol-primary: #1F2A52;
	--ol-primary-dark: #131b38;
	--ol-accent: #A9812F;
	--ol-bg: #F6F7FB;
	--ol-card: #FFFFFF;
	--ol-border: #E2E5EE;
	--ol-text: #1E2433;
	--ol-muted: #6B7284;
	--ol-danger: #B3261E;
	--ol-danger-bg: #FCEAE9;
	--ol-success: #1F6E43;
	--ol-success-bg: #E9F6EE;
	--ol-radius: 10px;
	display: block;
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
	/* Explicit, not inherited — the active WordPress theme still fires its own enqueue hooks
	   even though our page template skips get_header()/get_footer(), so without this the app's
	   text size was at the mercy of whatever base font-size that theme happens to set. */
	font-size: 16px;
	color: var(--ol-text);
	background: var(--ol-bg);
	line-height: 1.55;
	min-height: 400px;
	/* Breaks the app out of whatever (often narrow, often mobile-first) content column the active
	   WordPress theme centers its pages in — this plugin is a desktop working tool, not an article,
	   so it shouldn't sit in a mail-column-width box with big empty gutters on a real monitor. The
	   theme doesn't offer a "full width" template on every install, so this doesn't depend on one:
	   it's the standard full-bleed trick (stretch to the viewport, then pull back by exactly the
	   parent's own centering offset) rather than anything theme-specific. overflow-x: clip guards
	   against the 1px-of-scrollbar rounding that 100vw is prone to. */
	width: 100vw;
	max-width: 100vw;
	margin-left: calc(50% - 50vw);
	margin-right: calc(50% - 50vw);
	overflow-x: clip;
}

#ol-app-root * { box-sizing: border-box; }
/* :where(#ol-app-root) instead of a bare #ol-app-root prefix: an ID selector's specificity
   otherwise beats EVERY class-based rule in this file no matter how source order or how many
   classes that rule has — a plain <a> gets a sensible default color here, but any class meant
   to override it (.ol-nav__link--active, .ol-btn--primary, .ol-lp-btn--primary...) would
   silently lose that fight and keep this default color instead of its own. That's exactly what
   made the active nav tab's text invisible: its background AND (thanks to this) its text both
   ended up on --ol-primary. :where() zeroes out the ID's specificity contribution so these stay
   sensible defaults instead of unbeatable ones — the same class of bug as the landing-page
   section-padding fix elsewhere in this file, just via ID-vs-class instead of type-vs-class. */
:where(#ol-app-root) a { color: var(--ol-primary); }
/* Headings use a separate display face (Sora) from body text (Inter) on purpose — one face
   used for everything reads as a generic app; a heavier, more geometric face reserved for
   headings and big numbers (see .ol-kpi-number) is what gives report-style pages their own
   typographic identity. */
:where(#ol-app-root) h1 { font-family: 'Sora', 'Inter', -apple-system, sans-serif; font-size: 2rem; font-weight: 800; letter-spacing: -0.015em; margin: 0 0 18px; color: var(--ol-primary-dark); }
:where(#ol-app-root) h2 { font-family: 'Sora', 'Inter', -apple-system, sans-serif; font-size: 1.4rem; font-weight: 700; letter-spacing: -0.01em; margin: 0 0 12px; color: var(--ol-primary-dark); }
:where(#ol-app-root) h3 { font-family: 'Sora', 'Inter', -apple-system, sans-serif; font-size: 1.1rem; font-weight: 700; margin: 20px 0 10px; }
:where(#ol-app-root) h4 { font-family: 'Sora', 'Inter', -apple-system, sans-serif; font-size: 0.98rem; font-weight: 700; margin: 12px 0 6px; }
:where(#ol-app-root) p { margin: 0 0 10px; font-size: 0.98rem; }

.ol-app { max-width: none; margin: 0; padding: 0; }
/* Widened from 1080px — on an actual desktop monitor (this is a computer tool, not a phone app)
   1080px centered read as a small window floating in the middle of the browser with large empty
   margins either side. 1600px still caps line length on very large/ultrawide monitors, but fills
   the great majority of laptop and desktop screens edge to edge (minus this side padding). */
.ol-main { max-width: 1600px; margin: 0 auto; padding: 0 32px 60px; }

.ol-footer {
	max-width: 1600px; margin: 0 auto; padding: 18px 32px 28px;
	text-align: center; font-size: 0.82rem; color: var(--ol-text-muted, #8A93A6);
}

/* ---- "Log in as" banner -------------------------------------------------------------- */
.ol-imp-banner {
	background: #B45309; color: #fff; text-align: center; font-size: 0.88rem;
	padding: 8px 16px; display: flex; gap: 10px; align-items: center; justify-content: center;
	flex-wrap: wrap;
}
.ol-imp-banner .ol-btn { border-color: #fff; }

/* ---- Header / nav ------------------------------------------------------------------- */
.ol-header {
	background: linear-gradient(180deg, #fff, var(--ol-bg));
	border-bottom: 3px solid var(--ol-primary);
	box-shadow: 0 2px 10px rgba(31, 42, 82, 0.06);
	margin-bottom: 28px;
}
.ol-header-inner {
	max-width: 1600px; margin: 0 auto; padding: 18px 32px;
	display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
}
.ol-brand { display: inline-flex; align-items: center; gap: 8px; font-weight: 800; font-size: 1.3rem; letter-spacing: -0.01em; color: var(--ol-primary-dark); text-decoration: none; }
.ol-brand-logo { height: 28px; width: auto; max-width: 120px; object-fit: contain; border-radius: 4px; }
.ol-dept-logo { height: 36px; width: auto; max-width: 100%; object-fit: contain; margin-bottom: 8px; border-radius: 4px; }
.ol-nav { display: flex; flex-wrap: wrap; gap: 4px; margin-left: 12px; flex: 1; }
.ol-nav__link {
	padding: 8px 14px; border-radius: 8px; font-size: 0.94rem; font-weight: 600; text-decoration: none;
	color: var(--ol-text); transition: background-color .12s ease, color .12s ease; border-bottom: 3px solid transparent;
}
.ol-nav__link:hover { background: var(--ol-border); }
/* The active tab needs to read as active from across the room, not just via a background tint —
   a solid fill plus a brass accent underline plus a bold weight bump, so it can't get lost the
   way a same-tone background/text pairing did before (see the :where() note above). */
.ol-nav__link--active { background: var(--ol-primary); color: #fff !important; font-weight: 700; border-bottom-color: var(--ol-accent); box-shadow: 0 3px 10px -3px rgba(31, 42, 82, .45); }
.ol-nav__link--admin { color: var(--ol-accent); }
.ol-nav__link--admin.ol-nav__link--active { background: var(--ol-accent); border-bottom-color: var(--ol-primary); box-shadow: 0 3px 10px -3px rgba(169, 129, 47, .45); }

/* ---- Grouped navigation -----------------------------------------------------------------
   A group button looks exactly like a nav link, because it does the same job — the caret is
   the only thing that says there is more behind it. The panel is positioned, not inline, so
   opening a menu never reflows the header and shoves the rest of the nav down a row. */
.ol-navgroup { position: relative; }
.ol-nav__group { appearance: none; border: 0; background: transparent; cursor: pointer; font: inherit; display: inline-flex; align-items: center; gap: 6px; }
/* Restated after the reset above, which would otherwise strip the fill off an active group and
   leave white text on white — the button is a nav link and has to keep looking like one. */
.ol-nav__group.ol-nav__link--active { background: var(--ol-primary); }
.ol-nav__caret {
	width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent;
	border-top: 5px solid currentColor; opacity: .7; transition: transform .12s ease;
}
.ol-nav__group[aria-expanded="true"] .ol-nav__caret { transform: rotate(180deg); }
.ol-navmenu {
	position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
	min-width: 268px; padding: 6px; background: var(--ol-card);
	border: 1px solid var(--ol-border); border-radius: 10px;
	box-shadow: 0 2px 6px rgba(31, 42, 82, .06), 0 18px 40px -18px rgba(31, 42, 82, .5);
}
.ol-navmenu__item {
	display: block; padding: 8px 10px; border-radius: 7px; text-decoration: none; color: var(--ol-text);
}
.ol-navmenu__item:hover, .ol-navmenu__item:focus-visible { background: var(--ol-bg); }
.ol-navmenu__item.is-active { background: var(--ol-bg); box-shadow: inset 3px 0 0 var(--ol-accent); }
.ol-navmenu__label { display: block; font-size: .92rem; font-weight: 600; }
/* The one-line note is what stops a grouped menu from being a guessing game: "Accord & Wording"
   means nothing on its own to somebody who has not opened it before. */
.ol-navmenu__note { display: block; font-size: .76rem; color: var(--ol-muted); margin-top: 1px; line-height: 1.35; }

@media (max-width: 720px) {
	/* No room to float a panel over a narrow header: the menu becomes part of the flow. */
	.ol-navmenu { position: static; min-width: 0; box-shadow: none; margin: 4px 0 8px; }
}
.ol-header__right { display: flex; align-items: center; gap: 12px; }
.ol-user-chip { display: flex; align-items: center; gap: 9px; }
.ol-user-avatar {
	width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center;
	background: linear-gradient(135deg, var(--ol-accent), #cf9f52); color: #fff; font-weight: 800; font-size: 0.82rem; letter-spacing: 0.02em;
	box-shadow: 0 3px 8px -2px rgba(169, 129, 47, .55);
}
.ol-user-meta { display: flex; flex-direction: column; line-height: 1.2; }
.ol-nav__user { font-weight: 700; font-size: 0.92rem; color: var(--ol-primary-dark); }
.ol-user-role { font-size: 0.7rem; font-weight: 700; color: var(--ol-accent); text-transform: uppercase; letter-spacing: 0.05em; }
.ol-nav__logout { font-size: 0.85rem; text-decoration: none; color: var(--ol-danger); font-weight: 600; }
.ol-nav__logout:hover { text-decoration: underline; }

/* =========================================================================================
   Landing page ("home" view) — its own richer, colorful visual system. Scoped to
   .ol-landing-main so the rest of the app (dashboard, setup, etc.) keeps its plainer,
   utilitarian navy/brass look untouched.
   ========================================================================================= */
#ol-app-root {
	--lp-blue: #2563EB;
	--lp-indigo: #4F46E5;
	--lp-ink: #0F172A;
	--lp-muted: #64748B;
	--lp-line: #E2E8F0;
	--lp-bg-soft: #F8FAFC;
	--lp-green: #059669;
	--lp-green-bg: #D1FAE5;
	--lp-red-bg: #FEE2E2;
}
.ol-landing-main { max-width: none; padding: 0; font-family: "Inter", -apple-system, sans-serif; color: var(--lp-ink); overflow-x: hidden; }
.ol-landing-main section { padding: 72px 0; }
/* Sections below that need their OWN padding (not this 72px 0 default) are addressed as
   ".ol-landing-main .ol-lp-x" rather than plain ".ol-lp-x" — one class beats the rule above by
   specificity, so a bare ".ol-lp-hero{padding:...}" here was silently losing to it entirely.
   That was a real bug, not a style choice: the hero, preview, and CTA-band sections were all
   rendering with a flat 72px/0 instead of their intended padding, which is what made the hero
   look emptier and more unbalanced than it was ever designed to be. */
.ol-lp-wrap { max-width: 1180px; margin: 0 auto; padding: 0 28px; }
.ol-landing-main h1, .ol-landing-main h2, .ol-landing-main h3 { font-family: "Inter", sans-serif; letter-spacing: -0.01em; color: var(--lp-ink); }

.ol-lp-section-head { max-width: 640px; margin: 0 auto 44px; text-align: center; }
.ol-lp-section-head h2 { font-size: 1.9rem; font-weight: 800; margin: 0 0 12px; }
.ol-lp-section-head p { color: var(--lp-muted); font-size: 1rem; line-height: 1.6; margin: 0; max-width: none; }

/* ---- Hero ---- */
.ol-landing-main .ol-lp-hero {
	text-align: center; padding: 76px 28px 32px; max-width: 880px; margin: 0 auto; position: relative;
}
/* A soft two-color glow behind the headline — the one deliberate spot of visual richness on an
   otherwise plain grey background, which was the actual complaint: with the padding bug above
   fixed there was still nothing behind the hero text but flat --ol-bg grey. Kept behind
   everything (z-index/negative isn't needed since it paints before the hero's own content in
   source order isn't guaranteed — using ::before pinned with z-index -1 instead) and blurred
   soft enough that it reads as atmosphere, not a shape competing with the headline. */
.ol-landing-main .ol-lp-hero::before {
	/* No z-index here on purpose: a ::before paints before its element's other children
	   regardless, and giving it a negative z-index instead sends it behind the nearest
	   positioned ancestor's own (opaque) background — which was the actual reason an earlier,
	   more intense version of this glow rendered as barely visible. The blur is what turns a
	   gradient with a visible box edge into a soft ambient glow with no hard edge at all. */
	content: ""; position: absolute; inset: -20px 6% auto 6%; height: 380px;
	background:
		radial-gradient(closest-side, rgba(37, 99, 235, 0.30), transparent),
		radial-gradient(closest-side, rgba(79, 70, 229, 0.26), transparent);
	background-position: 18% 0%, 82% 30%;
	background-repeat: no-repeat;
	background-size: 60% 100%, 55% 90%;
	filter: blur(60px);
	pointer-events: none;
}
.ol-landing-main .ol-lp-hero > * { position: relative; }
.ol-lp-badge {
	display: inline-flex; align-items: center; gap: 8px; padding: 8px 18px; border-radius: 999px;
	background: #EFF6FF; border: 1px solid #DBEAFE; color: #1D4ED8; font-weight: 600; font-size: .82rem; margin-bottom: 24px;
}
.ol-lp-h1 { font-size: clamp(2.1rem, 4.2vw, 3.2rem); font-weight: 800; line-height: 1.12; margin: 0 0 22px; text-wrap: balance; }
.ol-lp-gradient { background: linear-gradient(90deg, var(--lp-blue), var(--lp-indigo)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.ol-lp-sub { font-size: 1.12rem; color: var(--lp-muted); max-width: 620px; margin: 0 auto 34px; line-height: 1.65; }
.ol-lp-ctas { display: flex; justify-content: center; gap: 14px; flex-wrap: wrap; }
.ol-lp-btn {
	display: inline-flex; align-items: center; gap: 8px; text-decoration: none; cursor: pointer;
	font-family: "Inter", sans-serif; font-weight: 700; font-size: .95rem; border-radius: 999px; border: 2px solid transparent;
	padding: 14px 28px;
}
.ol-lp-btn--primary { background: var(--lp-blue); color: #fff; box-shadow: 0 14px 28px rgba(37,99,235,.28); }
.ol-lp-btn--primary:hover { background: #1D4ED8; }
.ol-lp-btn--ghost { background: #fff; color: #334155; border-color: var(--lp-line); }
.ol-lp-btn--ghost:hover { border-color: var(--lp-blue); color: var(--lp-blue); }
.ol-lp-btn--band { background: #fff; color: var(--lp-blue); box-shadow: 0 14px 28px rgba(0,0,0,.18); }

/* ---- Preview card (real sample data, colorfully rendered) ---- */
.ol-landing-main .ol-lp-preview { max-width: 900px; margin: 0 auto; padding-top: 8px; padding-bottom: 40px; position: relative; }
.ol-lp-preview-card {
	background: #fff; border: 1px solid var(--lp-line); border-radius: 22px; box-shadow: 0 30px 60px rgba(15,23,42,.14);
	overflow: hidden; margin: 0 28px;
}
.ol-lp-preview-head { display: flex; justify-content: space-between; align-items: baseline; padding: 16px 24px; background: var(--lp-bg-soft); border-bottom: 1px solid var(--lp-line); font-weight: 700; font-size: .95rem; }
.ol-lp-preview-tag { font-size: .72rem; color: var(--lp-muted); font-weight: 600; }
table.ol-lp-attain { width: 100%; border-collapse: collapse; font-size: .92rem; }
table.ol-lp-attain th { text-align: left; font-size: .68rem; letter-spacing: .06em; text-transform: uppercase; color: var(--lp-muted); padding: 12px 24px 8px; }
table.ol-lp-attain td { padding: 12px 24px; border-top: 1px solid var(--lp-line); }
.ol-lp-clo-code { font-weight: 700; color: var(--lp-blue); }
.ol-lp-bar-cell { display: flex; align-items: center; gap: 10px; justify-content: flex-end; }
.ol-lp-bar-track { flex: 1; max-width: 160px; height: 8px; background: var(--lp-line); border-radius: 6px; overflow: hidden; }
.ol-lp-bar-fill { height: 100%; border-radius: 6px; }
.ol-lp-bar-fill--good { background: linear-gradient(90deg, #34D399, var(--lp-green)); }
.ol-lp-bar-fill--mid { background: linear-gradient(90deg, #93C5FD, var(--lp-blue)); }
.ol-lp-bar-fill--low { background: linear-gradient(90deg, #FCA5A5, #DC2626); }
.ol-lp-pct { font-weight: 700; width: 3.2em; text-align: right; font-variant-numeric: tabular-nums; }
.ol-lp-preview-foot { display: flex; justify-content: space-between; padding: 12px 24px; background: var(--lp-bg-soft); border-top: 1px solid var(--lp-line); font-size: .78rem; color: var(--lp-muted); }
.ol-lp-check { color: var(--lp-green); font-weight: 700; }
.ol-lp-float {
	/* Docked onto the card's own top-right corner (a small negative offset, not floating in
	   open space above it) with a slight rotation so it reads as a deliberately-placed sticker
	   accent rather than a stray disconnected box — the latter is what it looked like before
	   the section padding bug above was inflating the empty space around it. */
	position: absolute; top: -18px; right: 44px; background: #fff; padding: 12px 18px; border-radius: 16px;
	box-shadow: 0 20px 40px rgba(37,99,235,.18); border: 1px solid #EEF2FF; display: flex; align-items: center; gap: 10px;
	font-size: .68rem; color: var(--lp-green); transform: rotate(-2deg); z-index: 2;
}
.ol-lp-float div { display: flex; flex-direction: column; font-size: .72rem; color: var(--lp-ink); }
.ol-lp-float strong { font-size: .82rem; }
.ol-lp-float span { color: var(--lp-muted); font-weight: 500; }

/* ---- Feature grid ---- */
.ol-lp-features { background: #fff; border-top: 1px solid var(--lp-line); }
.ol-lp-feature-grid { max-width: 1180px; margin: 0 auto; padding: 0 28px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; }
.ol-lp-feature-card { background: var(--lp-bg-soft); padding: 30px; border-radius: 22px; border: 1px solid #F1F5F9; box-shadow: 0 6px 20px rgba(15,23,42,.05); }
.ol-lp-feature-icon { width: 50px; height: 50px; border-radius: 14px; display: flex; align-items: center; justify-content: center; margin-bottom: 18px; font-size: 1.2rem; }
.ol-lp-feature-icon--blue { background: #DBEAFE; color: var(--lp-blue); }
.ol-lp-feature-icon--indigo { background: #E0E7FF; color: var(--lp-indigo); }
.ol-lp-feature-icon--green { background: var(--lp-green-bg); color: var(--lp-green); }
.ol-lp-feature-card h3 { font-size: 1.08rem; font-weight: 700; margin: 0 0 8px; }
.ol-lp-feature-card p { font-size: .9rem; color: var(--lp-muted); line-height: 1.6; margin: 0; }

/* ---- Screenshot showcase ("See it in action") ----
   One big screen at a time, like an ordinary WordPress slider — owner's request, after the
   small scrolling tile strip: "the tiles are too small — they should be of hero size... one
   tile at one time, then move to next image and text... no slider bar." Every real screenshot
   (assets/img/landing/*.png) now fills a large, consistent frame with its own headline and copy
   directly under it, one screen in view at a time; arrows and dots switch which one shows, and
   it also advances on its own the way the old strip auto-scrolled — except now that's a full
   slide crossfade instead of a sideways drift, and there is nothing to show a scrollbar for:
   every slide but the active one sits invisible in the same grid cell (see .ol-lp-showcase-slides
   below), never in normal document flow at the same time. */
.ol-lp-showcase { background: var(--lp-bg-soft); border-top: 1px solid var(--lp-line); }
.ol-lp-showcase-head-row {
	display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;
	max-width: 1180px; margin: 0 auto 28px; padding: 0 28px;
}
.ol-lp-showcase-head-row .ol-lp-section-head { text-align: left; margin: 0; max-width: 640px; }
.ol-lp-showcase-nav { display: flex; gap: 10px; flex: 0 0 auto; padding-bottom: 6px; }
.ol-lp-showcase-arrow {
	width: 40px; height: 40px; border-radius: 999px; border: 1.5px solid var(--lp-line); background: #fff;
	color: var(--lp-ink); font-size: 1.05rem; cursor: pointer; display: flex; align-items: center; justify-content: center;
	transition: background .15s, border-color .15s;
}
.ol-lp-showcase-arrow:hover { background: var(--lp-bg-soft); border-color: var(--lp-blue); color: var(--lp-blue); }
.ol-lp-showcase-arrow:focus-visible { outline: 2px solid var(--lp-blue); outline-offset: 2px; }

.ol-lp-showcase-viewport { max-width: 900px; margin: 0 auto; padding: 0 28px; }
/* Every slide is placed in the SAME grid cell — the container's own height becomes the tallest
   slide automatically, so switching slides (some screenshots pair with more copy than others)
   never causes the page to jump, and the crossfade below has a stable box to fade within. */
.ol-lp-showcase-slides { display: grid; touch-action: pan-y; }
.ol-lp-showcase-slide {
	grid-area: 1 / 1; display: flex; flex-direction: column;
	opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .5s ease;
}
.ol-lp-showcase-slide.is-active { opacity: 1; visibility: visible; pointer-events: auto; }
.ol-lp-showcase-media {
	background: #fff; border: 1px solid var(--lp-line); border-radius: 20px; overflow: hidden;
	aspect-ratio: 16 / 10; box-shadow: 0 24px 50px rgba(15,23,42,.12);
}
.ol-lp-showcase-media img { width: 100%; height: 100%; display: block; object-fit: cover; object-position: top; }
.ol-lp-showcase-copy { max-width: 620px; margin: 26px auto 0; text-align: center; }
.ol-lp-showcase-copy h3 { font-size: 1.28rem; font-weight: 800; margin: 0 0 10px; }
.ol-lp-showcase-copy p { font-size: .96rem; color: var(--lp-muted); line-height: 1.65; margin: 0; }

.ol-lp-showcase-dots { display: flex; justify-content: center; gap: 9px; margin: 24px auto 0; }
.ol-lp-showcase-dot {
	width: 9px; height: 9px; padding: 0; border: none; border-radius: 999px; background: var(--lp-line);
	cursor: pointer; transition: background .15s, transform .15s;
}
.ol-lp-showcase-dot:hover { background: #94A3B8; }
.ol-lp-showcase-dot.is-active { background: var(--lp-blue); transform: scale(1.3); }
.ol-lp-showcase-dot:focus-visible { outline: 2px solid var(--lp-blue); outline-offset: 2px; }

/* ---- Roles grid ---- */
.ol-lp-role-grid { max-width: 1180px; margin: 0 auto; padding: 0 28px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.ol-lp-role-card { background: #fff; border: 1px solid var(--lp-line); border-radius: 22px; padding: 26px; box-shadow: 0 8px 24px rgba(15,23,42,.06); }
.ol-lp-role-tag { display: inline-block; font-size: .7rem; font-weight: 700; padding: 5px 12px; border-radius: 999px; margin-bottom: 14px; }
.ol-lp-role-tag--blue { background: #DBEAFE; color: #1D4ED8; }
.ol-lp-role-tag--indigo { background: #E0E7FF; color: #4338CA; }
.ol-lp-role-tag--dark { background: var(--lp-line); color: var(--lp-ink); }
.ol-lp-role-card h3 { font-size: 1.02rem; font-weight: 700; margin: 0 0 10px; }
.ol-lp-role-card ul { margin: 0; padding: 0 0 0 18px; font-size: .84rem; color: var(--lp-muted); line-height: 1.9; }

/* ---- Pricing ---- */
.ol-lp-pricing { background: var(--lp-bg-soft); border-top: 1px solid var(--lp-line); }
.ol-lp-pricing-grid { max-width: 900px; margin: 0 auto; padding: 0 28px; display: grid; grid-template-columns: 1fr 1fr; gap: 26px; }
.ol-lp-price-card { background: #fff; border: 1px solid var(--lp-line); border-radius: 22px; padding: 32px; display: flex; flex-direction: column; position: relative; }
.ol-lp-price-card--featured { border: 2px solid var(--lp-blue); box-shadow: 0 20px 44px rgba(37,99,235,.14); }
.ol-lp-price-badge { position: absolute; top: -13px; left: 50%; transform: translateX(-50%); background: var(--lp-blue); color: #fff; font-size: .7rem; font-weight: 700; padding: 5px 16px; border-radius: 999px; }
.ol-lp-price-tier { font-size: .78rem; font-weight: 700; color: var(--lp-blue); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 8px; }
.ol-lp-price-amount { font-size: 2rem; font-weight: 800; margin-bottom: 4px; }
.ol-lp-price-amount span { font-size: .85rem; font-weight: 500; color: var(--lp-muted); }
.ol-lp-price-sub { font-size: .82rem; color: var(--lp-muted); margin-bottom: 20px; }
.ol-lp-price-cta { width: 100%; text-align: center; justify-content: center; margin-bottom: 22px; }
.ol-lp-feature-list { list-style: none; margin: 0; padding: 0; }
.ol-lp-feature-list li { display: flex; align-items: flex-start; gap: 8px; padding: 8px 0; font-size: .84rem; border-top: 1px solid #F1F5F9; }
.ol-lp-feature-list li:first-child { border-top: none; }
.ol-lp-included { color: var(--lp-ink); }
.ol-lp-locked { color: #94A3B8; }

/* ---- Register / contact cards (landing page versions) ---- */
.ol-lp-register, .ol-lp-contact { border-top: 1px solid var(--lp-line); }
.ol-lp-card { max-width: 560px; margin: 0 auto; background: #fff; border: 1px solid var(--lp-line); border-radius: 22px; padding: 36px 40px; box-shadow: 0 8px 24px rgba(15,23,42,.05); }
.ol-lp-loggedin { text-align: center; }
.ol-lp-loggedin p { margin-bottom: 18px; }
.ol-lp-tabs { display: flex; gap: 0; margin-bottom: 24px; border: 1.5px solid var(--lp-line); border-radius: 12px; overflow: hidden; }
.ol-lp-tab { flex: 1; text-align: center; padding: 12px 10px; font-family: "Inter", sans-serif; font-size: .78rem; font-weight: 600; background: #fff; color: var(--lp-muted); cursor: pointer; border: none; }
.ol-lp-tab + .ol-lp-tab { border-left: 1.5px solid var(--lp-line); }
.ol-lp-tab--active { background: var(--lp-blue); color: #fff; }
.ol-lp-form { display: flex; flex-direction: column; gap: 16px; }
.ol-lp-form-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.ol-lp-field { display: flex; flex-direction: column; gap: 6px; }
.ol-lp-field label { font-size: .78rem; font-weight: 600; color: #334155; }
.ol-lp-field input, .ol-lp-field textarea, .ol-landing-main textarea {
	font-family: "Inter", sans-serif; font-size: .92rem; padding: 11px 14px; border: 1.5px solid var(--lp-line);
	border-radius: 12px; background: #fff; color: var(--lp-ink);
}
.ol-lp-field input:focus, .ol-lp-field textarea:focus { outline: none; border-color: var(--lp-blue); }
.ol-lp-form-submit { width: 100%; justify-content: center; }
.ol-lp-fine { text-align: center; font-size: .82rem; color: var(--lp-muted); margin-top: 18px; margin-bottom: 0; }
.ol-lp-fine a { color: var(--lp-blue); font-weight: 700; }
.ol-lp-contact-card { max-width: 620px; }

/* ---- CTA band ---- */
.ol-landing-main .ol-lp-cta-band {
	background: linear-gradient(135deg, var(--lp-blue), var(--lp-indigo)); border-radius: 32px; margin: 0 28px 0;
	padding: 64px 40px; text-align: center; color: #fff;
}
.ol-lp-cta-band h2 { color: #fff; font-size: 1.9rem; font-weight: 800; margin: 0 0 12px; }
.ol-lp-cta-band p { color: #DBEAFE; font-size: .95rem; max-width: 460px; margin: 0 auto 26px; }

/* ---- Footer ---- */
.ol-lp-footer {
	background: var(--lp-ink); color: #94A3B8; padding: 40px 28px; text-align: center; font-size: .82rem;
	display: flex; flex-direction: column; gap: 8px; margin-top: 72px;
}

@media (max-width: 900px) {
	.ol-lp-feature-grid, .ol-lp-role-grid { grid-template-columns: 1fr; }
	.ol-lp-pricing-grid, .ol-lp-form-cols { grid-template-columns: 1fr; }
	.ol-lp-float { display: none; }
	.ol-lp-card { padding: 28px 24px; }
	.ol-lp-showcase-head-row { flex-direction: column; align-items: flex-start; }
	.ol-lp-showcase-nav { display: none; } /* swipe (see viewHome()) plus the dots covers it at this width */
}

/* ---- Cards ---------------------------------------------------------------------------- */
.ol-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
.ol-card {
	background: var(--ol-card); border: 1px solid var(--ol-border); border-radius: var(--ol-radius);
	padding: 20px 22px; margin-bottom: 18px;
	box-shadow: 0 1px 2px rgba(31, 42, 82, 0.04), 0 8px 20px -14px rgba(31, 42, 82, 0.18);
}
.ol-card--wide { max-width: none; }
.ol-picker { display: flex; gap: 16px; align-items: flex-end; flex-wrap: wrap; }
.ol-picker .ol-field { margin-bottom: 0; min-width: 220px; }

/* ---- Executive summary / KPI cards (Dashboard) -----------------------------------------
   Big-number tiles with a colored top accent bar and a plain-language label underneath —
   deliberately not just another table row. Numbers use tabular-nums so a row of KPI cards
   lines up cleanly, and the Sora display face (see heading rule above) instead of body text,
   which is what makes a number read as a headline figure rather than a data-table cell. */
.ol-kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; margin: 4px 0 20px; }
.ol-kpi-card {
	background: var(--ol-card); border: 1px solid var(--ol-border); border-radius: var(--ol-radius);
	padding: 16px 18px 14px; box-shadow: 0 1px 2px rgba(31, 42, 82, 0.04), 0 8px 20px -14px rgba(31, 42, 82, 0.18);
	border-top: 4px solid var(--ol-primary); position: relative;
}
.ol-kpi-card--accent { border-top-color: var(--ol-accent); }
.ol-kpi-card--danger { border-top-color: var(--ol-danger); }
.ol-kpi-card--success { border-top-color: var(--ol-success); }
/* Between "fine" and "wrong": a count of things worth a look, which is not a failure. Uses the
   same amber the "one course only" verdict does, so the two read as the same class of signal. */
.ol-kpi-card--warn { border-top-color: #C08A1E; }
.ol-kpi-card__number {
	font-family: 'Sora', 'Inter', -apple-system, sans-serif; font-size: 2.1rem; font-weight: 800; line-height: 1;
	color: var(--ol-primary-dark); font-variant-numeric: tabular-nums; display: block; margin-bottom: 4px;
}
.ol-kpi-card__label { font-size: 0.82rem; font-weight: 600; color: var(--ol-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.ol-kpi-card__sub { font-size: 0.8rem; color: var(--ol-muted); margin-top: 4px; }
.ol-kpi-card a.ol-kpi-card__link { position: absolute; inset: 0; border-radius: inherit; }
.ol-exec-callout {
	display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-radius: var(--ol-radius);
	background: var(--ol-danger-bg); border: 1px solid rgba(179, 38, 30, 0.25); margin-bottom: 16px;
}
.ol-exec-callout--ok { background: var(--ol-success-bg); border-color: rgba(31, 110, 67, 0.25); }
.ol-exec-callout strong { color: var(--ol-primary-dark); }

/* ---- Forms ------------------------------------------------------------------------------ */
.ol-form { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }
.ol-form--inline { flex-direction: row; flex-wrap: wrap; align-items: center; max-width: none; }
.ol-form--inline input, .ol-form--inline select, .ol-form--inline textarea { flex: 1; min-width: 140px; }
.ol-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.ol-field label { font-size: 0.85rem; font-weight: 700; color: var(--ol-muted); }
/* .ol-modal-overlay is listed alongside #ol-app-root throughout: a modal is appended to
   document.body, so it sits outside the app root and an #ol-app-root-only selector never
   reaches the form inside it. Without this a modal's fields fell back to the browser default —
   a textarea in monospace, inputs with no border — while the same form on a page looked right. */
#ol-app-root input, #ol-app-root select, #ol-app-root textarea,
.ol-modal-overlay input, .ol-modal-overlay select, .ol-modal-overlay textarea {
	padding: 9px 12px; border: 1px solid var(--ol-border); border-radius: 7px; font-size: 0.95rem;
	font-family: inherit; background: #fff; color: var(--ol-text);
}
#ol-app-root input:focus, #ol-app-root select:focus, #ol-app-root textarea:focus,
.ol-modal-overlay input:focus, .ol-modal-overlay select:focus, .ol-modal-overlay textarea:focus {
	outline: none; border-color: var(--ol-primary);
}
/* A checkbox styled as a text field is a box with a border and 9px of padding around a tick.
   Restored to the browser's own control, which is also the one screen readers announce. */
#ol-app-root input[type="checkbox"], #ol-app-root input[type="radio"],
.ol-modal-overlay input[type="checkbox"], .ol-modal-overlay input[type="radio"] {
	padding: 0; border-radius: 0; width: auto;
}
.ol-select { min-width: 220px; }
.ol-hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

/* ---- Buttons ---------------------------------------------------------------------------- */
.ol-btn {
	display: inline-block; padding: 9px 18px; border-radius: 7px; border: 1px solid var(--ol-border);
	background: #fff; color: var(--ol-text); font-size: 0.92rem; font-weight: 700; cursor: pointer;
	text-decoration: none; transition: border-color .12s ease, background-color .12s ease, transform .05s ease;
}
.ol-btn:hover { border-color: var(--ol-primary); }
.ol-btn:active { transform: translateY(1px); }
.ol-btn--primary { background: var(--ol-primary); color: #fff; border-color: var(--ol-primary); box-shadow: 0 4px 12px -4px rgba(31, 42, 82, 0.5); }
.ol-btn--primary:hover { background: var(--ol-primary-dark); }
.ol-btn--small { padding: 6px 12px; font-size: 0.82rem; }
.ol-btn--tiny { padding: 3px 8px; font-size: 0.76rem; }
.ol-btn--ghost { background: transparent; border-color: transparent; color: var(--ol-muted); }
.ol-btn--ghost:hover { color: var(--ol-primary); border-color: var(--ol-border); }
.ol-btn:disabled { opacity: 0.6; cursor: default; }
.ol-quicklinks { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.ol-dl-row { display: flex; gap: 10px; flex-wrap: wrap; margin: 4px 0 4px; }

/* ---- Tabs (landing page registration) --------------------------------------------------- */
.ol-tabs { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
.ol-tab {
	padding: 8px 14px; border-radius: 20px; border: 1px solid var(--ol-border); background: #fff;
	font-size: 0.85rem; cursor: pointer; color: var(--ol-muted);
}
.ol-tab--active { background: var(--ol-primary); color: #fff; border-color: var(--ol-primary); }

/* ---- Tables ------------------------------------------------------------------------------ */
/* Every data table in the app — matrices, reports, transcripts — gets a real outer border, a
   tinted header row, and a light zebra stripe, so a table reads as one bounded object rather
   than a loose stack of rows with only a bottom hairline each. .rpt-table (the Cohort Report /
   LDL matrix / Student PLO transcript tables, built as raw HTML strings) shares the exact same
   look as .ol-table rather than having its own unstyled default. */
.ol-table, .rpt-table {
	width: 100%; border-collapse: separate; border-spacing: 0; margin-top: 8px; font-size: 0.94rem;
	border: 1px solid var(--ol-border); border-radius: 10px; overflow: hidden;
}
.ol-table th, .ol-table td, .rpt-table th, .rpt-table td {
	text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--ol-border); border-right: 1px solid var(--ol-border);
}
.ol-table th:last-child, .ol-table td:last-child, .rpt-table th:last-child, .rpt-table td:last-child { border-right: none; }
.ol-table tbody tr:last-child td, .rpt-table tbody tr:last-child td { border-bottom: none; }
.ol-table th, .rpt-table th {
	color: var(--ol-primary-dark); font-weight: 700; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em;
	background: var(--ol-bg); border-bottom: 2px solid var(--ol-border);
}
.ol-table tbody tr:nth-child(even), .rpt-table tbody tr:nth-child(even) { background: rgba(31, 42, 82, 0.025); }
.ol-table tbody tr:hover, .rpt-table tbody tr:hover { background: #EEF2FF; }
.ol-table--matrix { overflow-x: auto; display: block; border-radius: 10px; }
.ol-table--matrix thead, .ol-table--matrix tbody { display: table; width: 100%; table-layout: fixed; }
/* A table nested inside a card (drill-downs, per-term breakdown sections) doesn't need its own
   card-like border on top of the card's — this variant drops it back to a plain bordered grid. */
.ol-table--plain, .ol-table--plain.rpt-table { border-radius: 0; }

/* ---- Badges / banners --------------------------------------------------------------------- */
.ol-badge { display: inline-block; padding: 4px 11px; border-radius: 20px; background: var(--ol-border); font-size: 0.8rem; font-weight: 700; margin: 0 4px 4px 0; }
.ol-badge--trial { background: #FCEFD8; color: #9A6A0A; }
.ol-badge--ok { background: var(--ol-success-bg); color: var(--ol-success); }
.ol-banner {
	display: flex; align-items: flex-start; gap: 10px;
	padding: 12px 16px; border-radius: 8px; margin-bottom: 16px;
	font-size: 0.94rem; font-weight: 600; line-height: 1.4;
	border-left: 4px solid transparent;
	box-shadow: 0 1px 2px rgba(31, 42, 82, 0.05);
}
.ol-banner::before { flex-shrink: 0; font-weight: 700; line-height: 1.4; }
.ol-banner--error { background: var(--ol-danger-bg); color: var(--ol-danger); border-left-color: var(--ol-danger); }
.ol-banner--error::before { content: "⛔"; }
.ol-banner--success { background: var(--ol-success-bg); color: var(--ol-success); border-left-color: var(--ol-success); }
.ol-banner--success::before { content: "✔"; }
/* Neither an error nor an all-clear: a capability this server doesn't have, which the person
   reading it may be able to get their administrator to install. */
.ol-banner--warning { background: #FDF4E3; color: #7A5A12; border-left-color: var(--ol-accent); }
.ol-banner--warning::before { content: "!"; }
.ol-empty { padding: 30px; text-align: center; color: var(--ol-muted); font-size: 0.98rem; background: var(--ol-card); border: 1px dashed var(--ol-border); border-radius: var(--ol-radius); }
.ol-warning { background: var(--ol-danger-bg); color: var(--ol-danger); border-left: 4px solid var(--ol-danger); padding: 12px 16px; border-radius: 8px; font-weight: 600; line-height: 1.5; }
.ol-btn--danger { background: var(--ol-danger); color: #fff; border-color: var(--ol-danger); }
.ol-btn--danger:hover { filter: brightness(0.9); }
.ol-btn--danger:disabled { opacity: 0.5; cursor: not-allowed; }
.ol-muted { color: var(--ol-muted); font-size: 0.92rem; }

/* ---- Misc rows ------------------------------------------------------------------------------ */
.ol-meter { display: flex; justify-content: space-between; font-size: 0.82rem; padding: 2px 0; }
.ol-roster-row, .ol-person-row, .ol-org-row, .ol-contact-row {
	display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 8px 0; border-bottom: 1px solid var(--ol-border);
}
.ol-org-row { flex-direction: column; align-items: flex-start; }
.ol-org-row__usage { font-size: 0.8rem; color: var(--ol-muted); }
.ol-org-row__actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ol-org-row__panel {
	width: 100%; margin-top: 8px; padding: 10px 12px;
	background: var(--ol-bg, #F7F8FA); border: 1px solid var(--ol-border); border-radius: 8px;
}
.ol-page-access__grid {
	display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
	gap: 4px 16px; margin: 8px 0;
}
.ol-page-access__item { display: flex; align-items: center; gap: 6px; font-size: 0.85rem; font-weight: normal; }

/* The licensed programme count, sitting with the plan control because it is the other half of
   the same answer: which plan they are on, and how much of it they bought. */
.ol-ent { display: inline-flex; align-items: center; gap: 4px; font-size: .78rem; color: var(--ol-muted); }
.ol-ent-input { width: 52px; text-align: right; }
.ol-over { color: var(--ol-danger); font-weight: 700; }
.ol-contact-row { flex-direction: column; align-items: stretch; }
.ol-contact-row--new { background: #FDF8EC; }
.ol-contact-row__actions { display: flex; gap: 8px; align-items: center; }
.ol-code-row { display: flex; gap: 10px; align-items: center; }
.ol-code-row code { background: var(--ol-bg); padding: 4px 10px; border-radius: 6px; font-size: 0.95rem; }
.ol-issue-list { margin: 0; padding-left: 20px; }
.ol-issue-list li { margin-bottom: 6px; font-size: 0.88rem; }

/* ---- Bulk upload: manual "corrected max marks" input for a likely-wrong-max item ---- */
.ol-bulk-overmax { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--ol-border); }
.ol-bulk-overmax .ol-form--inline { margin-bottom: 8px; max-width: none; }
.ol-bulk-overmax label { font-size: 0.82rem; color: var(--ol-muted); flex: none; }

/* ---- Logo upload (Setup) ---- */
.ol-logo-row { display: flex; align-items: center; gap: 16px; margin-bottom: 10px; flex-wrap: wrap; }
.ol-logo-preview { width: 84px; height: 84px; border: 1px dashed var(--ol-border); border-radius: 8px; display: flex; align-items: center; justify-content: center; overflow: hidden; background: #fff; flex-shrink: 0; }
.ol-logo-preview img { max-width: 100%; max-height: 100%; object-fit: contain; }
#ol-logo-form { display: flex; align-items: center; gap: 8px; }

/* ---- Courses & assignments (Setup) ---- */
.ol-course-filters { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 6px; }
.ol-course-filters .ol-field { min-width: 200px; flex: 1; }
.ol-course-row { padding: 14px 0; border-bottom: 1px solid var(--ol-border); }
.ol-course-row__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.ol-course-row__teachers { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.ol-teacher-chip { display: inline-flex; align-items: center; gap: 6px; background: #E7ECF7; color: var(--ol-primary-dark); }
.ol-chip-remove { border: none; background: none; cursor: pointer; color: var(--ol-danger); font-size: 0.8rem; padding: 0; line-height: 1; }
.ol-assign-form { gap: 8px; }
.ol-assign-form select { min-width: 180px; }

/* ---- Transcript "document" framing — makes the on-screen transcript read as an official
   record rather than another app card: a double top rule (its letterhead border, set inline),
   a slightly warmer paper-like background, and generous padding instead of the standard card's
   tighter app padding. */
.ol-transcript-doc { background: #FDFCFA; padding: 28px 32px; border: 1px solid var(--ol-border); }
@media (max-width: 600px) { .ol-transcript-doc { padding: 18px 16px; } }

/* ---- Student PLO: left-nav student list + right-hand transcript ---- */
.ol-spt-layout { display: flex; gap: 16px; align-items: flex-start; }
.ol-spt-sidebar { flex: 0 0 300px; max-width: 300px; position: sticky; top: 12px; }
.ol-spt-sidebar-filters { padding: 10px; margin-bottom: 0; }
.ol-spt-main { flex: 1; min-width: 0; }
.ol-spt-student-list { display: flex; flex-direction: column; gap: 4px; max-height: 70vh; overflow-y: auto; background: var(--ol-card-bg, #fff); border: 1px solid var(--ol-border); border-radius: 8px; padding: 4px; }
.ol-spt-student-row { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; text-align: left; background: none; border: none; border-radius: 6px; padding: 7px 9px; cursor: pointer; font: inherit; }
.ol-spt-student-row:hover { background: #EEF1F8; }
.ol-spt-student-row--active { background: #1F2A52; }
.ol-spt-student-row--active .ol-spt-student-row__name,
.ol-spt-student-row--active .ol-spt-student-row__meta { color: #fff; }
.ol-spt-student-row__name { font-weight: 600; font-size: 0.88rem; }
.ol-spt-student-row__meta { font-size: 0.74rem; color: #6b7280; }
.ol-spt-student-row .ol-badge { margin-top: 2px; }
@media (max-width: 860px) {
	.ol-spt-layout { flex-direction: column; }
	.ol-spt-sidebar { position: static; flex: 1 1 auto; max-width: none; width: 100%; }
	.ol-spt-student-list { max-height: 320px; }
}

/* ---- Course tiles (Course Analysis browse grid) ---- */
/* Plain block wrapper — holds one full-width [term header, tile grid] section per term, stacked
   top to bottom. This must NOT be a grid/flex container itself: it previously reused
   .ol-course-tiles (see below), which made each term's entire header+tiles block collapse into a
   single ~240px grid column, rendering terms side-by-side as cramped columns instead of full-
   width stacked sections. */
.ol-course-tiles-wrap { display: block; width: 100%; }
.ol-course-tiles__term-header { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 22px 0 10px; padding-bottom: 6px; border-bottom: 2px solid var(--ol-border); }
.ol-course-tiles__term-header h3 { margin: 0; font-size: 1.1rem; }
.ol-course-tiles__term-header:first-child { margin-top: 0; }
.ol-course-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; margin-bottom: 18px; }
/* One tile per course, colored by a hash of its code+term (see courseTileAccentIndex in app.js)
   so the grid reads as a set of distinct courses at a glance instead of a wall of identical
   white cards — the color itself carries no meaning, it's purely for visual separation. Each
   .ol-tile-cN variant below sets --tile-accent / --tile-accent-tint, which everything else reads. */
.ol-course-tile { --tile-accent: var(--ol-primary); --tile-accent-tint: #EEF1F8; background: var(--ol-card); border: 1px solid var(--ol-border); border-top: 4px solid var(--tile-accent); border-radius: 12px; padding: 16px; cursor: pointer; transition: box-shadow .15s ease, border-color .15s ease, transform .15s ease; display: flex; flex-direction: column; gap: 6px; }
.ol-course-tile:hover, .ol-course-tile:focus { box-shadow: 0 10px 24px rgba(31,42,82,.14); border-color: var(--ol-border); outline: none; transform: translateY(-2px); }
.ol-tile-c0 { --tile-accent: #A9812F; --tile-accent-tint: #FBF3E1; }
.ol-tile-c1 { --tile-accent: #2E7D6B; --tile-accent-tint: #E7F4F1; }
.ol-tile-c2 { --tile-accent: #3B5BA5; --tile-accent-tint: #EAEFFA; }
.ol-tile-c3 { --tile-accent: #B5563C; --tile-accent-tint: #FBEBE6; }
.ol-tile-c4 { --tile-accent: #7A4C86; --tile-accent-tint: #F2E9F5; }
.ol-tile-c5 { --tile-accent: #3F7A43; --tile-accent-tint: #EAF4EA; }
.ol-tile-c6 { --tile-accent: #1F6E7A; --tile-accent-tint: #E6F2F3; }
.ol-tile-c7 { --tile-accent: #9C5B24; --tile-accent-tint: #F8ECE0; }
.ol-course-tile__head { display: flex; align-items: center; gap: 8px; font-size: 1.05rem; }
.ol-course-tile__badge { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex: 0 0 auto; border-radius: 8px; background: var(--tile-accent-tint); color: var(--tile-accent); font-size: 0.7rem; font-weight: 700; letter-spacing: .02em; }
.ol-course-tile__head strong { flex: 1 1 auto; }
.ol-course-tile__name { color: var(--ol-muted); font-size: 0.85rem; }
.ol-course-tile__meta { display: flex; gap: 8px; font-size: 0.78rem; color: var(--tile-accent); }
.ol-course-tile__meta span { background: var(--tile-accent-tint); border-radius: 6px; padding: 2px 8px; font-weight: 600; }
.ol-course-tile__teacher { font-size: 0.85rem; font-weight: 600; color: var(--ol-primary-dark); }
.ol-course-tile__stats { display: flex; gap: 10px; font-size: 0.78rem; color: var(--ol-muted); margin-top: 4px; padding-top: 8px; border-top: 1px dashed var(--ol-border); }

/* ---- PLO Matrix — clickable cells + mapping editor ---- */
.ol-matrix-cell { cursor: pointer; transition: background .15s ease; }
.ol-matrix-cell:hover, .ol-matrix-cell:focus { background: #EEF2FF; outline: none; }
#ol-plo-editor:empty { display: none; }

/* ---- Insights reports ------------------------------------------------------------------
   Shared styling for the four Insights tabs (PLO coverage, section comparison, item quality,
   course trends). Deliberately built from the same card/table vocabulary as the rest of the
   app rather than a new visual language — these are more reports, not a different product. */

/* Stat strip at the top of each report: a few big numbers that say what the table below is
   about, before anyone has to read a single row. */
.ol-insight-stats { gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.ol-stat-tile {
	flex: 1 1 150px; min-width: 140px; text-align: center; margin-bottom: 0; padding: 14px 16px;
}
.ol-stat-tile .ol-muted { font-size: 0.76rem; text-transform: uppercase; letter-spacing: .04em; }
.ol-stat-tile__value {
	font-family: 'Sora', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
	font-size: 1.7rem; font-weight: 700; line-height: 1.15; font-variant-numeric: tabular-nums;
	color: var(--ol-primary);
}

/* "What to look at" cells — short sentences, not jargon codes. Kept tight so a row with three
   findings doesn't tower over its neighbours. */
.ol-flag-list { margin: 0; padding-left: 16px; font-size: 0.84rem; line-height: 1.45; }
.ol-flag-list li { margin: 0 0 2px; }
.ol-flag-list li:last-child { margin-bottom: 0; }

/* Severity tinting on a whole row, so the eye lands on the problems first. Left border rather
   than a full fill: readable in both the table's striped and hovered states. */
.ol-table tbody tr.ol-row--bad td:first-child { box-shadow: inset 3px 0 0 var(--ol-danger); }
.ol-table tbody tr.ol-row--warn td:first-child { box-shadow: inset 3px 0 0 #9A6A0A; }

/* Any wide table scrolls inside its own box — the page body itself must never scroll
   sideways, on any screen. */
.ol-table-scroll { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ol-table-scroll > .ol-table { min-width: 640px; }

/* Course-trend sparkline: one bar per term, no chart library (same approach as every other
   chart in this app). */
.ol-trend-chart {
	display: flex; align-items: flex-end; gap: 14px; padding: 12px 4px 4px;
	overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.ol-trend-bar { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 62px; }
.ol-trend-bar__fill { width: 34px; border-radius: 5px 5px 0 0; }
.ol-trend-bar__val { font-size: 0.78rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.ol-trend-bar__label { font-size: 0.72rem; color: var(--ol-muted); text-align: center; }

/* ---- Responsive pass -------------------------------------------------------------------
   The app is used on laptops in an office and on phones in a corridor between classes. These
   rules keep every page usable at narrow widths instead of forcing a horizontal page scroll. */

@media (max-width: 780px) {
	#ol-app-root { font-size: 15px; }

	/* Filter/picker bars stack instead of squeezing every control onto one line. */
	.ol-picker, .ol-course-filters, .ol-form--inline { flex-direction: column; align-items: stretch; }
	.ol-picker .ol-field, .ol-course-filters .ol-field { min-width: 0; width: 100%; }

	/* Tabs scroll horizontally rather than wrapping into an unreadable stack. */
	.ol-tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; flex-wrap: nowrap; }
	.ol-tab { flex: 0 0 auto; white-space: nowrap; }

	/* Every table gets the scroll-in-its-own-box treatment on small screens, not just the ones
	   explicitly wrapped in .ol-table-scroll. */
	.ol-table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }

	.ol-grid { grid-template-columns: 1fr; }
	.ol-card { padding: 16px 16px; }
	.ol-stat-tile { flex: 1 1 100%; }

	/* Download/action rows wrap so a button never runs off the edge. */
	.ol-dl-row { flex-wrap: wrap; }
	.ol-dl-row .ol-btn { flex: 1 1 auto; }

	/* Comfortable tap targets — 44px is the usual minimum for a finger, not a mouse. */
	.ol-btn, .ol-tab { min-height: 44px; }
}

@media (max-width: 480px) {
	.ol-course-tiles { grid-template-columns: 1fr; }
	.ol-stat-tile__value { font-size: 1.45rem; }
	h1 { font-size: 1.5rem; }
}

/* A checkbox with its label on one line — used by the Insights filters. */
.ol-check { display: inline-flex; align-items: center; gap: 8px; margin: 8px 0 12px; cursor: pointer; font-size: 0.9rem; }
.ol-check input { width: 16px; height: 16px; }

/* ---- Course report (Course Analysis) ---------------------------------------------------
   The report reads top to bottom like a document: a small uppercase eyebrow names the kind of
   section, a serif title names the section itself. That pairing is what stops eight stacked
   charts from turning into an undifferentiated wall. */

.ol-rpt-head { text-align: center; margin: 4px 0 14px; }
.ol-rpt-eyebrow {
	font-size: 0.68rem; letter-spacing: .18em; text-transform: uppercase;
	color: var(--ol-muted); font-weight: 700; margin-bottom: 4px;
}
.ol-rpt-title {
	font-family: 'Sora', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
	font-size: 1.3rem; font-weight: 700; margin: 0; color: var(--ol-primary);
}
.ol-rpt-sub { max-width: 62ch; margin: 6px auto 0; font-size: 0.86rem; }

/* Cover block: the course, then the four numbers that frame everything below it. */
.ol-rpt-cover { text-align: center; }
.ol-rpt-cover h2 { margin: 0 0 2px; }
.ol-rpt-figures { display: flex; justify-content: center; gap: 34px; flex-wrap: wrap; margin-top: 14px; }
.ol-rpt-figures > div { display: flex; flex-direction: column; align-items: center; }
.ol-rpt-figures strong {
	font-family: 'Sora', -apple-system, BlinkMacSystemFont, sans-serif;
	font-size: 1.6rem; color: var(--ol-accent); font-variant-numeric: tabular-nums; line-height: 1.1;
}
.ol-rpt-figures span { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ol-muted); }

/* A chart wider than its card scrolls sideways inside the card, never dragging the page with it. */
.ol-chart-scroll { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ol-chart-scroll svg { display: block; }

.ol-domain-row { display: flex; gap: 14px; flex-wrap: wrap; }
.ol-domain-tile {
	display: flex; flex-direction: column; align-items: center; gap: 2px;
	border: 1px solid var(--ol-border); border-radius: var(--ol-radius);
	background: var(--ol-bg); padding: 14px 20px; min-width: 120px;
}
.ol-domain-tile strong {
	font-family: 'Sora', -apple-system, BlinkMacSystemFont, sans-serif;
	font-size: 1.5rem; color: var(--ol-accent); font-variant-numeric: tabular-nums;
}
.ol-domain-tile span { font-size: 0.82rem; font-weight: 600; }
.ol-domain-tile em { font-size: 0.72rem; color: var(--ol-muted); font-style: normal; }

/* Source switch + thresholds above the report. */
.ol-rpt-controls { display: flex; gap: 14px; align-items: flex-end; flex-wrap: wrap; }
.ol-rpt-controls .ol-field { margin-bottom: 0; }
.ol-seg { display: inline-flex; border: 1px solid var(--ol-border); border-radius: 8px; overflow: hidden; }
.ol-seg button {
	appearance: none; border: 0; background: var(--ol-card); color: var(--ol-text);
	padding: 9px 14px; font: inherit; font-size: 0.86rem; cursor: pointer; min-height: 40px;
}
.ol-seg button + button { border-left: 1px solid var(--ol-border); }
.ol-seg button.is-active { background: #EEF0FF; color: var(--ol-primary); font-weight: 700; }

@media (max-width: 780px) {
	.ol-rpt-figures { gap: 20px; }
	.ol-rpt-controls { flex-direction: column; align-items: stretch; }
	.ol-seg { width: 100%; }
	.ol-seg button { flex: 1 1 0; }
}

/* ---- Student document downloads --------------------------------------------------------
   Two documents (transcript, scorecard) x two formats (PDF, Excel), offered for one student
   or the whole batch. Laid out as plainly-labelled buttons rather than a dropdown, because
   which of the four you want is the whole decision. */

.ol-doc-downloads { margin-top: 16px; padding-top: 14px; border-top: 1px dashed var(--ol-border); }
.ol-doc-downloads--compact { margin: 4px 0 10px; padding-top: 0; border-top: 0; }
.ol-doc-downloads__label {
	font-size: 0.7rem; text-transform: uppercase; letter-spacing: .08em;
	font-weight: 700; color: var(--ol-muted); margin-bottom: 8px;
}
.ol-doc-downloads .ol-dl-row { display: flex; gap: 8px; flex-wrap: wrap; }
.ol-doc-downloads--compact .ol-dl-row { flex-direction: column; }
.ol-doc-downloads--compact .ol-btn { width: 100%; justify-content: center; }
.ol-doc-downloads__note { font-size: 0.76rem; margin: 8px 0 0; }

/* Sub-tabs inside a single course's view (Report / mapping & data). Lighter than the page-level
   tab strip so the two levels stay visually distinct. */
.ol-tabs--sub { margin: 4px 0 16px; }
.ol-tabs--sub .ol-tab { font-size: 0.86rem; padding: 8px 14px; }

/* ---- Setup: side section menu ----------------------------------------------------------
   Setup renders one section at a time behind this menu. Loading every section at once meant
   opening the page pulled down the department's whole course roster whether you needed it or
   not — the single biggest thing making that page slow to open. */

.ol-setup-layout { display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: 20px; align-items: start; }
.ol-setup-nav {
	display: flex; flex-direction: column; gap: 2px; position: sticky; top: 16px;
	background: var(--ol-card); border: 1px solid var(--ol-border);
	border-radius: var(--ol-radius); padding: 8px;
}
.ol-setup-nav__item {
	appearance: none; border: 0; background: transparent; text-align: left; cursor: pointer;
	font: inherit; font-size: 0.88rem; color: var(--ol-text);
	padding: 10px 12px; border-radius: 8px; min-height: 40px;
}
.ol-setup-nav__item:hover { background: var(--ol-bg); }
.ol-setup-nav__item.is-active { background: #EEF0FF; color: var(--ol-primary); font-weight: 700; }
.ol-setup-nav__item--danger { color: var(--ol-danger); }
.ol-setup-nav__item--danger.is-active { background: var(--ol-danger-bg); color: var(--ol-danger); }
.ol-setup-pane > .ol-card:first-child { margin-top: 0; }

@media (max-width: 780px) {
	/* On a phone the menu becomes a scrolling strip above the section, not a squeezed column. */
	.ol-setup-layout { grid-template-columns: 1fr; }
	.ol-setup-nav { position: static; flex-direction: row; overflow-x: auto; -webkit-overflow-scrolling: touch; }
	.ol-setup-nav__item { flex: 0 0 auto; white-space: nowrap; }
}

/* ---- Toolkit: file-name preview --------------------------------------------------------
   The file name is the whole contract for this tool, so it's shown as a live result rather
   than validated only on submit — green once it's buildable, red with the exact reasons
   while it isn't. */

.ol-tk-name {
	margin-top: 12px; padding: 10px 12px; border-radius: 8px; font-size: 0.88rem;
	border: 1px solid var(--ol-border); background: var(--ol-bg);
}
.ol-tk-name.is-ok { border-color: #17A398; background: #17A3980F; color: var(--ol-primary); }
.ol-tk-name.is-bad { border-color: var(--ol-danger); background: var(--ol-danger-bg); }
.ol-tk-name code {
	background: rgba(31, 42, 82, 0.07); padding: 2px 6px; border-radius: 4px;
	font-size: 0.9em; font-weight: 700;
}
.ol-tk-name .ol-flag-list { margin-top: 6px; }

/* ---- Course tile: the three generated files -------------------------------------------
   Every tile can produce the same workbook / report / CSV the Toolkit does, from the data
   already stored for that course. Kept visually quiet — the tile's job is still to be
   clicked to open the course; these are a secondary action on it. */

/* Four downloads now, so they wrap rather than squeezing each other to unreadable slivers
   on a narrow tile. */
.ol-course-tile__files { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.ol-tile-btn {
	appearance: none; cursor: pointer; font: inherit; font-size: 0.72rem; font-weight: 600;
	padding: 5px 9px; border-radius: 6px; min-height: 28px; flex: 1 1 auto; min-width: 62px;
	border: 1px solid var(--tile-accent, var(--ol-border));
	background: var(--tile-accent-tint, var(--ol-bg));
	color: var(--tile-accent, var(--ol-text));
}
.ol-tile-btn:hover:not(:disabled) { background: var(--tile-accent, var(--ol-primary)); color: #fff; }
.ol-tile-btn:disabled { opacity: .6; cursor: default; }
.ol-course-tile__filemsg { margin-top: 6px; font-size: 0.72rem; line-height: 1.35; }
.ol-course-tile__filemsg.is-error { color: var(--ol-danger); }

@media (max-width: 780px) {
	.ol-tile-btn { min-height: 36px; font-size: 0.76rem; }
}

/* ---------------------------------------------------------------------------------------
   Master Manual (Toolkit)
   --------------------------------------------------------------------------------------- */
/* One card per assignment: a tick to include it, the facts a teacher checks before choosing
   (total marks, rubric, submission type, how many are actually graded), then the three
   sample pickers colour-coded to match the bands they produce in the finished PDF. */

.ol-mm-assign__head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ol-mm-assign__head h3 { margin: 0; flex: 1 1 auto; }
.ol-mm-include {
	display: inline-flex; align-items: center; gap: 6px; font-size: 0.82rem; font-weight: 600;
	color: var(--ol-muted); white-space: nowrap;
}
.ol-mm-include input { width: 16px; height: 16px; }

.ol-mm-facts { display: flex; flex-wrap: wrap; gap: 8px 22px; margin: 10px 0 14px; }
.ol-mm-facts span { display: flex; flex-direction: column; }
.ol-mm-facts em {
	font-style: normal; font-size: 0.66rem; letter-spacing: 0.06em; text-transform: uppercase;
	color: var(--ol-muted);
}
.ol-mm-facts strong { font-size: 0.92rem; }

.ol-mm-tier { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
.ol-mm-tier label {
	flex: 0 0 118px; font-size: 0.78rem; font-weight: 700; padding: 5px 9px; border-radius: 6px;
	color: #fff; text-align: center;
}
.ol-mm-tier select { flex: 1 1 auto; min-width: 0; }
.ol-mm-tier--best label { background: #17A398; }
.ol-mm-tier--average label { background: #E9A23B; }
.ol-mm-tier--below label { background: #E8455F; }

@media (max-width: 640px) {
	.ol-mm-tier { flex-direction: column; align-items: stretch; }
	.ol-mm-tier label { flex: 0 0 auto; }
}

/* ---------------------------------------------------------------------------------------
   PLO working (student transcript)
   --------------------------------------------------------------------------------------- */
/* One block per PLO, showing every contributing CLO and the sum that produced the score.
   Kept whole across a page break — a working split over two pages can't be followed. */

.ol-plo-working {
	border: 1px solid var(--ol-border); border-radius: 8px;
	padding: 10px 14px; margin: 12px 0; background: var(--ol-card);
	page-break-inside: avoid; break-inside: avoid;
}
.ol-plo-working__head {
	display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
	margin: 0 0 8px; font-size: 0.98rem;
}
.ol-plo-working__score { font-weight: 800; color: var(--ol-primary); white-space: nowrap; }
.ol-plo-working__sub { font-weight: 400; color: var(--ol-muted); font-size: 0.66rem; }
.ol-plo-working__table .num, .ol-plo-working__table th.num { text-align: right; }
.ol-plo-working__table tfoot th { background: #f2f5fa; }
.ol-plo-working__sum {
	margin: 8px 0 0; padding: 8px 12px; font-size: 0.92rem;
	background: #f2f5fa; border-left: 3px solid var(--ol-accent); border-radius: 0 6px 6px 0;
}
.ol-plo-working__note { margin: 6px 0 0; font-size: 0.78rem; color: #8a6d1f; }

/* ---------------------------------------------------------------------------------------
   Roster detail — students, identity fields, and linked ids
   --------------------------------------------------------------------------------------- */

.ol-roster-detail-host { margin-top: 10px; }
.ol-roster-detail {
	border-top: 1px dashed var(--ol-border); padding-top: 12px; margin-top: 10px;
}
/* A linked id is shown as a chip rather than plain text so it reads as an attached label on
   the student, not as another student. */
.ol-alias-chip {
	display: inline-flex; align-items: center; gap: 4px;
	background: #EEF2FB; border: 1px solid #D3DCEF; border-radius: 12px;
	padding: 1px 4px 1px 8px; margin: 2px 4px 2px 0; font-size: 0.78rem;
}
.ol-alias-chip code { background: none; padding: 0; font-size: 0.78rem; }
.ol-alias-chip button {
	appearance: none; border: none; background: none; cursor: pointer;
	color: var(--ol-muted); font-size: 0.95rem; line-height: 1; padding: 0 3px;
}
.ol-alias-chip button:hover { color: var(--ol-danger); }
.ol-rd-editor > td { background: #F7F9FD; }

/* ---------------------------------------------------------------------------------------
   Loading skeletons
   ---------------------------------------------------------------------------------------
   Replaces the bare "Loading…" line these pages used to show. The point isn't decoration:
   a text placeholder is a different SHAPE from the content that replaces it, so the layout
   visibly jumps when data arrives and the page reads as having glitched. These blocks match
   the real geometry — the same tile grid, the same bar rows — so the arriving content
   swaps in place and nothing moves. Same wait, no jolt.

   The shimmer is a background-position animation on a gradient rather than an opacity
   pulse, because it stays smooth when many of these are on screen at once. */
.ol-skel-wrap { display: block; }
.ol-skel {
	display: block;
	background: linear-gradient(90deg, #E8EBF2 25%, #F2F4F9 37%, #E8EBF2 63%);
	background-size: 400% 100%;
	animation: ol-skel-shimmer 1.4s ease-in-out infinite;
	border-radius: 6px;
}
@keyframes ol-skel-shimmer {
	0%   { background-position: 100% 50%; }
	100% { background-position: 0 50%; }
}
.ol-skel--title    { width: 240px; height: 26px; margin: 4px 0 14px; }
.ol-skel--h3       { width: 260px; height: 18px; margin: 22px 0 12px; }
.ol-skel--num      { width: 64px;  height: 30px; margin-bottom: 8px; }
.ol-skel--label    { width: 100%;  height: 12px; }
.ol-skel-tile      { padding: 16px; }
.ol-skel-barrow    { display: flex; align-items: center; gap: 10px; margin: 5px 0; }
.ol-skel--barlabel { width: 150px; height: 12px; flex-shrink: 0; }
.ol-skel--bar      { flex: 1; height: 14px; border-radius: 5px; }

/* Announce the wait to a screen reader without putting the word on screen, where the
   skeleton already says it visually. */
.ol-sr-only {
	position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
	overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Someone who has asked their system to reduce motion should not be shown a looping
   shimmer. The skeleton still does its job — it's the shape that prevents the jump, not
   the animation — so it simply holds still. Applied to the app's other transitions too:
   this is the only place in the stylesheet that was honouring the preference at all. */
@media (prefers-reduced-motion: reduce) {
	.ol-skel { animation: none; background: #E8EBF2; }
	#ol-app-root *, #ol-app-root *::before, #ol-app-root *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

/* ---------------------------------------------------------------------------------------
   Report surfaces on screen
   ---------------------------------------------------------------------------------------
   These rules exist so the app and the documents it produces read as one product. Everything
   here mirrors a decision already made in the print stylesheet (docCss() in app.js): the same
   short accent rule under a section title, the same colored table header band, the same score
   chips. Before this, a report looked one way on screen and a different way once printed, and
   the printed one was the one that left the building.

   All of it is driven by --ol-primary / --ol-accent, which the app now sets at runtime from
   the department's own saved report colours (see applyDeptBrandColor in app.js) — so a
   department that picks forest green gets a green app AND a green PDF, not a navy app and a
   green PDF. */

/* A section title with its own colored rule, instead of a heading floating over a table. */
.ol-rpt-head::after {
	content: ''; display: block; width: 64px; height: 3px; margin: 10px auto 0;
	background: var(--ol-accent); border-radius: 2px;
}

/* Card headings get a colored marker, so a long page of cards has visible structure when
   scanned rather than reading as one continuous column of text. */
.ol-card > h2:first-child,
.ol-card > h3:first-child {
	position: relative; padding-left: 14px; margin-top: 0;
}
.ol-card > h2:first-child::before,
.ol-card > h3:first-child::before {
	content: ''; position: absolute; left: 0; top: 0.18em; bottom: 0.18em; width: 4px;
	background: var(--ol-primary); border-radius: 2px;
}

/* A labelled divider between report sections. */
.ol-sec-divider {
	display: flex; align-items: center; gap: 12px; margin: 26px 0 14px;
	font-size: 0.7rem; letter-spacing: .16em; text-transform: uppercase;
	color: var(--ol-muted); font-weight: 700;
}
.ol-sec-divider::after {
	content: ''; flex: 1; height: 1px; background: var(--ol-border);
}

/* Tables on screen, matching the printed ones: a solid header band in the department's
   colour, zebra body rows, and right-aligned figures that line up as a column. */
.ol-table thead th {
	background: var(--ol-primary); color: #fff; font-weight: 600;
	font-size: 0.74rem; letter-spacing: .04em; text-transform: uppercase;
	border-color: var(--ol-primary);
}
.ol-table tbody tr:nth-child(even) > td { background: rgba(31, 42, 82, 0.035); }
.ol-table td.num, .ol-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.ol-table tr.ol-total > td { background: rgba(31, 42, 82, 0.07); font-weight: 700; }

/* Score chips — a percentage carries its own verdict, in the same three colours the printed
   bars use, so "62%" does not require looking up what counts as good. */
.ol-score {
	display: inline-block; min-width: 46px; text-align: center;
	padding: 1px 7px; border-radius: 10px; font-size: 0.78rem; font-weight: 700;
	font-variant-numeric: tabular-nums;
}
.ol-score--good { background: #E4F2E9; color: #1a6b38; }
.ol-score--mid  { background: #FBF2DA; color: #8a6d1f; }
.ol-score--low  { background: #FBE7DF; color: #99380d; }
.ol-score--none { background: #EEF0F5; color: #8b91a1; }

/* The department logo, wherever a report header shows it on screen. */
.ol-doc-letterhead {
	display: flex; align-items: center; gap: 14px;
	border-bottom: 3px solid var(--ol-primary); padding-bottom: 10px; margin-bottom: 16px;
}
.ol-doc-letterhead img { height: 46px; width: auto; flex-shrink: 0; }
.ol-doc-letterhead__org {
	font-family: 'Sora', 'Inter', sans-serif; font-size: 1.1rem; font-weight: 700; color: var(--ol-primary);
}
.ol-doc-letterhead__kind {
	font-size: 0.7rem; letter-spacing: .14em; text-transform: uppercase; color: var(--ol-accent); font-weight: 700;
}
.ol-doc-letterhead__right { margin-left: auto; text-align: right; font-size: 0.74rem; color: var(--ol-muted); }

/* ---------------------------------------------------------------------------------------
   olTable — toolbar, sortable headers, pager
   ---------------------------------------------------------------------------------------
   The shared list component (olTable in app.js). Every long list in the app now draws
   through it, so these rules are the only place list behaviour is styled — a change here
   reaches the student matrix, the course map, the roster and the staff lists at once. */

.ol-tbar {
	display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
	margin: 0 0 12px;
}
.ol-tbar__search { flex: 1 1 260px; min-width: 200px; max-width: 380px; }
.ol-tbar__search input { width: 100%; }
/* The count sits at the far end and is the answer to "did my filter do anything" — so it is
   quiet but always present, never hidden when the list is unfiltered. */
.ol-tbar__count {
	margin-left: auto; font-size: 0.8rem; color: var(--ol-muted);
	font-variant-numeric: tabular-nums; white-space: nowrap;
}

.ol-table-scroll { overflow-x: auto; }

/* A sortable header is a real <button> inside the <th>, not a click handler on the cell:
   that is what makes it reachable by keyboard and announced as actionable. */
.ol-th-sort { padding: 0 !important; }
.ol-th-btn {
	display: flex; align-items: center; gap: 5px; width: 100%;
	appearance: none; border: none; background: none; cursor: pointer;
	font: inherit; color: inherit; text-align: left;
	padding: 9px 12px;
}
.ol-th-btn:hover { background: rgba(255, 255, 255, 0.12); }
.ol-th-btn:focus-visible { outline: 2px solid var(--ol-accent); outline-offset: -2px; }
.ol-th-arrow { font-size: 0.62rem; opacity: 0.9; margin-left: auto; }
.ol-table--sortable th.is-sorted { background: var(--ol-primary-dark); }

.ol-pager {
	display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
	margin-top: 12px; font-size: 0.82rem; color: var(--ol-muted);
}
.ol-pager__pos { font-variant-numeric: tabular-nums; }
.ol-pager__of { font-variant-numeric: tabular-nums; margin: 0 2px; }
.ol-pager .ol-btn[disabled] { opacity: 0.45; cursor: default; }

@media (max-width: 640px) {
	.ol-tbar__count { margin-left: 0; width: 100%; }
	.ol-tbar__search { max-width: none; }
}

/* Hidden by a screen filter. Deliberately a class and not the hidden attribute: the batch
   report's student grid is cloned verbatim into the PDF, and this class is defined here but
   NOT in the document stylesheet — so a filtered screen still exports the complete cohort.
   Filtering what you are looking at must never quietly remove people from an issued report. */
.ol-filtered-out { display: none !important; }

/* The two narrowing controls above the course dropdown. */
.ol-course-pick { display: flex; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.ol-course-pick__q { flex: 1 1 200px; min-width: 160px; }
.ol-course-pick__term { flex: 0 1 160px; }

/* ---- Attainment targets ---------------------------------------------------------------
   The PLO target editor saves each row on blur rather than behind one Save button, so the
   row needs to say it was saved. A brief tint is enough — a toast per field would be noise
   when someone is filling in twenty-four numbers. */
.ol-target-input { max-width: 84px; text-align: right; }
.ol-row-saved > td { transition: background 240ms ease; background: var(--ol-success-bg); }

.ol-callout--info {
	border: 1px solid var(--ol-border); border-left: 3px solid var(--ol-accent);
	border-radius: var(--ol-radius); background: #FBFCFE; padding: 12px 15px; margin-bottom: 14px;
}
.ol-callout--info strong { color: var(--ol-primary); }

/* The same shape in the "this is a problem, but not an error" register. Used where the screen is
   telling a department something is wrong with what it has claimed — which is advice, not a
   failure of the software, and should not look like a red error banner. */
.ol-callout--warn {
	border: 1px solid #E8D9AE; border-left: 3px solid #B8860B;
	border-radius: var(--ol-radius); background: #FDF9EF; padding: 12px 15px; margin-bottom: 14px;
}
.ol-callout--warn strong { color: #7A5C0E; }

/* And the "this is fine" register, for a state worth confirming rather than merely not warning
   about — a valid licence is one of the few places a green box earns its space. */
.ol-callout--ok {
	border: 1px solid #BFE0CC; border-left: 3px solid var(--ol-success);
	border-radius: var(--ol-radius); background: var(--ol-success-bg); padding: 12px 15px; margin-bottom: 14px;
}
.ol-callout--ok strong { color: var(--ol-success); }

/* A PLO's verdict against its target. Never colour alone — the word MET or NOT MET carries
   the meaning, which matters both for colour-blind readers and in greyscale print. */
.ol-verdict {
	display: inline-flex; align-items: center; gap: 5px;
	font-size: 0.72rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
	padding: 2px 8px; border-radius: 3px; white-space: nowrap;
}
.ol-verdict--met     { background: var(--ol-success-bg); color: var(--ol-success); }
.ol-verdict--not-met { background: var(--ol-danger-bg);  color: var(--ol-danger); }
.ol-verdict--none    { background: #EEF0F5; color: #7B8194; }

/* The attainment bar: how many students cleared the bar, with the target marked on it. */
.ol-attain-row { display: flex; align-items: center; gap: 10px; margin: 7px 0; }
.ol-attain-label { width: 190px; flex-shrink: 0; font-size: 0.84rem; }
.ol-attain-track {
	flex: 1; position: relative; height: 16px; background: var(--ol-border);
	border-radius: 3px; overflow: hidden; min-width: 90px;
}
.ol-attain-fill { height: 100%; }
/* The target marker sits ON the track, so "did we clear it" is answered by looking, not by
   comparing two numbers in different places. */
.ol-attain-target {
	position: absolute; top: -3px; bottom: -3px; width: 2px;
	background: var(--ol-primary-dark);
}
.ol-attain-target::after {
	content: ''; position: absolute; top: -3px; left: -3px;
	border-left: 4px solid transparent; border-right: 4px solid transparent;
	border-top: 5px solid var(--ol-primary-dark);
}
.ol-attain-val {
	width: 118px; text-align: right; font-size: 0.8rem; font-variant-numeric: tabular-nums;
	flex-shrink: 0;
}
.ol-attain-mean { color: var(--ol-muted); font-size: 0.76rem; }

/* A tick or cross beside a student's PLO score: did that student personally clear the bar.
   A mark, not a second colour — the cell's background already carries the grade band, and
   two meanings on one colour teaches a reader to trust neither. */
.ol-attain-mark { font-size: 0.78rem; opacity: 0.75; margin-left: 2px; }
.ol-attain-mark--no { color: var(--ol-danger); opacity: 0.9; }

/* ---- Report contents chooser ----------------------------------------------------------
   Grouped by the registry's own categories, because the decision is "do we report at item
   level at all", not sixteen unrelated yes/nos. A locked section stays visible rather than
   hidden — something that exists and is unavailable reads differently from something absent
   and unexplained. */
.ol-rf-group {
	border: 1px solid var(--ol-border); border-radius: var(--ol-radius);
	padding: 10px 14px 12px; margin: 0 0 12px;
}
.ol-rf-group legend {
	font-size: 0.72rem; letter-spacing: .12em; text-transform: uppercase;
	color: var(--ol-accent); font-weight: 700; padding: 0 6px;
}
.ol-rf-row {
	display: flex; align-items: baseline; gap: 9px; padding: 5px 0;
	border-bottom: 1px solid var(--ol-border);
}
.ol-rf-group .ol-rf-row:last-child { border-bottom: none; }
.ol-rf-row.is-locked { opacity: 0.62; }
.ol-rf-label { flex: 1; }
.ol-rf-note { font-size: 0.76rem; color: var(--ol-muted); }
.ol-rf-note--locked { color: var(--ol-accent); }

/* ---- Chart frame ----------------------------------------------------------------------
   The style choice and the save buttons sit UNDER the chart, quiet and small. They are
   controls for the chart, not part of what the chart says, and putting them above would
   make every figure look like a form. */
.ol-chartframe { margin: 6px 0 16px; }
.ol-chartframe__body { min-height: 40px; }
.ol-chartframe__bar {
	display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
	margin-top: 6px; padding-top: 7px; border-top: 1px dashed var(--ol-border);
}
.ol-chartframe__style {
	font-size: 0.78rem; color: var(--ol-muted);
	display: inline-flex; align-items: center; gap: 6px;
}
.ol-select--tiny {
	font-size: 0.78rem; padding: 3px 6px; min-width: 0; width: auto;
}
/* Pushed to the far end: saving is the less common action, and it should not sit where the
   eye lands first when the question is "what am I looking at". */
.ol-chartframe__save { margin-left: auto; display: inline-flex; gap: 6px; }
.ol-chartframe__note {
	font-size: 0.78rem; color: var(--ol-muted); margin: 6px 0 0; max-width: 70ch;
}
@media (max-width: 560px) {
	.ol-chartframe__save { margin-left: 0; width: 100%; }
}

/* ---------------------------------------------------------------------------------------
   Improvement Plan — findings, actions, verification
   ---------------------------------------------------------------------------------------
   The screen that answers "and what did you do about it". Its visual job is to make an
   overdue action and an unverified finding impossible to scroll past, because those are
   exactly the two things a panel goes looking for. */

/* Severity and status carry a word as well as a colour. Colour alone fails in greyscale
   print and for readers who cannot separate red from green, and this register gets printed. */
.ol-sev, .ol-fstatus {
	display: inline-block; font-size: 0.7rem; font-weight: 700; letter-spacing: .05em;
	text-transform: uppercase; padding: 2px 7px; border-radius: 3px; white-space: nowrap;
}
.ol-sev--high   { background: var(--ol-danger-bg);  color: var(--ol-danger); }
.ol-sev--medium { background: #FBF2DA;              color: #8a6d1f; }
.ol-sev--low    { background: #EEF0F5;              color: #6B7284; }
.ol-fstatus              { background: #EEF0F5; color: #5A6270; }
.ol-fstatus--open        { background: var(--ol-danger-bg);  color: var(--ol-danger); }
.ol-fstatus--in_progress { background: #FBF2DA;              color: #8a6d1f; }
.ol-fstatus--verified    { background: var(--ol-success-bg); color: var(--ol-success); }
.ol-fstatus--closed      { background: var(--ol-success-bg); color: var(--ol-success); }

.ol-delta { font-weight: 700; font-variant-numeric: tabular-nums; }
.ol-delta--up   { color: var(--ol-success); }
.ol-delta--down { color: var(--ol-danger); }

/* The two numbers side by side. This block is the whole argument of the feature — a
   document can record that a problem existed, only the data can show it moved. */
.ol-movement {
	display: flex; align-items: stretch; gap: 14px; flex-wrap: wrap;
	border: 1px solid var(--ol-border); border-radius: var(--ol-radius);
	padding: 14px 16px; margin: 14px 0 20px; background: var(--ol-card);
}
.ol-movement__cell { flex: 1 1 150px; min-width: 130px; }
.ol-movement__cell.is-pending { opacity: 0.65; }
.ol-movement__label {
	font-size: 0.7rem; letter-spacing: .1em; text-transform: uppercase; color: var(--ol-muted);
}
.ol-movement__value {
	font-family: 'Sora', 'Inter', sans-serif; font-size: 1.8rem; font-weight: 800;
	line-height: 1.1; margin-top: 3px; color: var(--ol-primary-dark);
	font-variant-numeric: tabular-nums;
}
.ol-movement__value.is-up   { color: var(--ol-success); }
.ol-movement__value.is-down { color: var(--ol-danger); }
.ol-movement__note { font-size: 0.78rem; color: var(--ol-muted); margin-top: 2px; }
.ol-movement__arrow { align-self: center; font-size: 1.3rem; color: var(--ol-muted); }
.ol-movement__delta {
	align-self: center; font-family: 'Sora', 'Inter', sans-serif; font-weight: 800;
	font-size: 1.35rem; padding-left: 14px; border-left: 1px solid var(--ol-border);
	font-variant-numeric: tabular-nums; color: var(--ol-muted);
}
.ol-movement__delta.is-up   { color: var(--ol-success); }
.ol-movement__delta.is-down { color: var(--ol-danger); }
.ol-movement__delta span {
	display: block; font-size: 0.66rem; font-weight: 600; letter-spacing: .08em;
	text-transform: uppercase; color: var(--ol-muted);
}

/* An action. The overdue rail is a left border, not a background wash — the row still has
   to be readable, and a red row reads as broken rather than as late. */
.ol-action {
	display: flex; gap: 14px; align-items: flex-start;
	border: 1px solid var(--ol-border); border-left: 3px solid var(--ol-border);
	border-radius: 6px; padding: 10px 13px; margin-bottom: 8px;
}
.ol-action.is-overdue { border-left-color: var(--ol-danger); }
.ol-action__main { flex: 1; min-width: 0; }
.ol-action__title { font-weight: 600; }
.ol-action__meta {
	display: flex; gap: 14px; flex-wrap: wrap; margin-top: 4px;
	font-size: 0.78rem; color: var(--ol-muted);
}
.ol-action__side { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }

.ol-verify {
	border: 1px solid var(--ol-border); border-radius: var(--ol-radius);
	padding: 14px 16px; background: #FBFCFE;
}
.ol-verify__reading { margin-bottom: 12px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.ol-verify__now { font-size: 1rem; }
.ol-verify .ol-form { max-width: none; }

/* The prompt that turns a weak outcome into a tracked finding. Sits under the chart that
   revealed it, because that is the moment somebody decides to do something. */
.ol-capture {
	display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
	border: 1px solid rgba(179, 38, 30, 0.25); background: var(--ol-danger-bg);
	border-radius: var(--ol-radius); padding: 11px 15px; margin: 12px 0;
}
.ol-capture__text { flex: 1 1 300px; font-size: 0.88rem; }
.ol-capture__btns { display: flex; gap: 6px; flex-wrap: wrap; }

/* ---- Modal ---------------------------------------------------------------------------- */
/* A modal is appended to document.body so it can escape any overflow or stacking context on the
   page beneath it. That also puts it OUTSIDE #ol-app-root, where every --ol-* token is declared —
   so the whole token set has to be repeated here, or a modal renders with every colour undefined
   and falls back to whatever the active WordPress theme happens to set. The font is restated for
   the same reason: nothing above this node belongs to the app. */
.ol-modal-overlay {
	--ol-primary: #1F2A52;
	--ol-primary-dark: #131b38;
	--ol-accent: #A9812F;
	--ol-bg: #F6F7FB;
	--ol-card: #FFFFFF;
	--ol-border: #E2E5EE;
	--ol-text: #1E2433;
	--ol-muted: #6B7284;
	--ol-danger: #B3261E;
	--ol-danger-bg: #FCEAE9;
	--ol-success: #1F6E43;
	--ol-success-bg: #E9F6EE;
	--ol-radius: 10px;
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
	font-size: 16px;
	line-height: 1.55;
	color: var(--ol-text);

	position: fixed; inset: 0; background: rgba(15, 20, 35, 0.5);
	display: flex; align-items: flex-start; justify-content: center;
	padding: 40px 16px; z-index: 1000; overflow-y: auto;
}
.ol-modal {
	background: var(--ol-card); border-radius: var(--ol-radius); width: 100%;
	box-shadow: 0 18px 50px -12px rgba(15, 20, 35, 0.4);
	max-height: calc(100vh - 80px); display: flex; flex-direction: column;
}
.ol-modal__head {
	display: flex; align-items: flex-start; gap: 12px;
	padding: 18px 22px 12px; border-bottom: 1px solid var(--ol-border);
}
.ol-modal__head h2 { margin: 0; flex: 1; font-size: 1.15rem; }
.ol-modal__close {
	appearance: none; border: none; background: none; cursor: pointer;
	font-size: 1.5rem; line-height: 1; color: var(--ol-muted); padding: 0 4px;
}
.ol-modal__close:hover { color: var(--ol-text); }
.ol-modal__body { padding: 18px 22px 24px; overflow-y: auto; }
.ol-modal__body .ol-form { max-width: none; }

@media (max-width: 620px) {
	.ol-movement { gap: 8px; }
	.ol-movement__arrow { display: none; }
	.ol-movement__delta { padding-left: 0; border-left: none; }
	.ol-modal-overlay { padding: 16px 8px; }
}

/* =========================================================================================
   PEO -> PLO map
   A grid of toggles, not a form of checkboxes: the question being answered is "which outcomes
   is this objective built on", which is read across a row and down a column, so the cell has to
   carry its state visually rather than through a control the eye has to inspect one at a time.
   ========================================================================================= */
.ol-peo-map th { text-align: center; white-space: nowrap; }
.ol-peo-map td { text-align: center; vertical-align: middle; }
.ol-peo-map__cell { padding: 4px !important; }
.ol-peo-map__cell.is-on { background: var(--ol-success-bg); }

.ol-peo-map__toggle {
	appearance: none; cursor: pointer;
	width: 26px; height: 26px; line-height: 1;
	border: 1px solid var(--ol-border); border-radius: 6px;
	background: var(--ol-card); color: var(--ol-success);
	font-size: .95rem; font-weight: 700;
	display: inline-flex; align-items: center; justify-content: center;
}
.ol-peo-map__toggle:hover:not(:disabled) { border-color: var(--ol-primary); }
.ol-peo-map__toggle:disabled { cursor: default; opacity: .7; }
.ol-peo-map__cell.is-on .ol-peo-map__toggle { border-color: var(--ol-success); background: var(--ol-card); }
.ol-peo-map__toggle:focus-visible { outline: 2px solid var(--ol-primary); outline-offset: 2px; }

/* The weight sits under its own tick rather than in a separate column, so a glance down a
   column still reads as one question. Blank is the normal state and means "equal share". */
.ol-peo-map__weight {
	display: block; width: 54px; margin: 4px auto 0;
	padding: 2px 3px; text-align: center;
	font-size: .72rem; border: 1px solid var(--ol-border); border-radius: 4px;
	background: var(--ol-card); color: var(--ol-text);
	/* The spinner arrows eat about a third of the field, which was enough to clip "60" to "6".
	   Nothing here is stepped through one at a time anyway — the value is typed. */
	-moz-appearance: textfield;
}
.ol-peo-map__weight::-webkit-outer-spin-button,
.ol-peo-map__weight::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* display:block above outranks the user agent's [hidden] rule, so an untouched cell was showing
   an empty weight box under an empty tick. Restored explicitly. */
.ol-peo-map__weight[hidden] { display: none; }
.ol-peo-map__weight::placeholder { color: var(--ol-muted); }
.ol-peo-map__count { white-space: nowrap; font-size: .82rem; color: var(--ol-muted); }

/* =========================================================================================
   Survey builder
   The builder is two panes: what the department is defining, and what a respondent will meet.
   The preview is the point — a question reads differently once it sits in a form with its
   options laid out, and this is the last moment a wording problem costs nothing to fix.
   ========================================================================================= */
.ol-sstatus {
	display: inline-block; padding: 2px 8px; border-radius: 999px;
	font-size: .7rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
}
.ol-sstatus--draft  { background: #EEF0F5; color: var(--ol-muted); }
.ol-sstatus--open   { background: var(--ol-success-bg); color: var(--ol-success); }
.ol-sstatus--closed { background: #E8EAF1; color: #4A5468; }

.ol-survey-head {
	display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap;
	padding-bottom: 12px; border-bottom: 1px solid var(--ol-border);
}
.ol-survey-head__tools { margin-left: auto; display: flex; flex-wrap: wrap; gap: 6px; }
.ol-survey-subhead { margin: 0 0 10px; font-size: .95rem; }

.ol-survey-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin-top: 16px; align-items: start; }
.ol-survey-cols__preview { position: sticky; top: 0; }

/* ---- one question in the editing list ---- */
.ol-qrow {
	display: flex; gap: 10px; align-items: flex-start;
	padding: 10px 0; border-bottom: 1px solid var(--ol-border);
}
.ol-qrow:last-of-type { border-bottom: none; }
.ol-qrow__main { flex: 1; min-width: 0; }
.ol-qrow__text { font-size: .92rem; line-height: 1.4; }
.ol-qrow__tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.ol-qrow__acts { display: flex; gap: 4px; flex-shrink: 0; }

.ol-qtag {
	display: inline-block; padding: 1px 7px; border-radius: 3px;
	font-size: .68rem; font-weight: 600; letter-spacing: .03em;
	background: #EEF0F5; color: #4A5468;
}
.ol-qtag--peo   { background: #E7EEFA; color: #1F4A8C; }
.ol-qtag--plo   { background: var(--ol-success-bg); color: var(--ol-success); }
.ol-qtag--warn  { background: #FBF0DC; color: #8A6D1F; }
.ol-qtag--muted { background: #F2F3F7; color: var(--ol-muted); font-weight: 500; }
/* A scored question feeding no outcome is a silent hole: answers are collected and reach no
   report. Called out here rather than left for the reader to notice. */
.ol-qtag--bad   { background: var(--ol-danger-bg); color: var(--ol-danger); }

/* ---- the respondent's-eye preview ---- */
.ol-formprev {
	border: 1px solid var(--ol-border); border-radius: var(--ol-radius);
	background: var(--ol-card); padding: 18px 20px 20px;
}
.ol-formprev__title { font-weight: 700; font-size: 1.05rem; margin-bottom: 6px; }
.ol-formprev__intro { color: var(--ol-muted); font-size: .85rem; margin: 0 0 14px; }
.ol-formprev__section {
	margin: 16px 0 8px; font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
	font-weight: 700; color: var(--ol-primary);
	border-bottom: 1px solid var(--ol-border); padding-bottom: 4px;
}
.ol-formprev__q { margin-bottom: 14px; }
.ol-formprev__qtext { font-size: .88rem; line-height: 1.45; }
.ol-formprev__req { color: var(--ol-danger); }
.ol-formprev__help { font-size: .78rem; color: var(--ol-muted); margin-top: 2px; }
.ol-formprev__opts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
.ol-formprev__opt {
	border: 1px solid var(--ol-border); border-radius: 999px;
	padding: 3px 11px; font-size: .78rem; background: var(--ol-bg);
}
.ol-formprev__opt--na { border-style: dashed; color: var(--ol-muted); }
.ol-formprev__num { font-size: .78rem; color: var(--ol-muted); border: 1px dashed var(--ol-border); padding: 3px 11px; border-radius: 6px; }
.ol-formprev__textbox {
	margin-top: 7px; border: 1px solid var(--ol-border); border-radius: 6px;
	height: 46px; background: var(--ol-bg);
}

/* ---- question editor ---- */
.ol-optrow { display: flex; gap: 6px; align-items: center; margin-bottom: 6px; }
.ol-optrow .ol-opt-label { flex: 1; }
.ol-optrow .ol-opt-value { width: 84px; }

.ol-targetgroup { margin-bottom: 10px; }
.ol-targetgroup__head {
	font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
	color: var(--ol-muted); font-weight: 700; margin-bottom: 5px;
}
.ol-targetgroup__items { display: flex; flex-wrap: wrap; gap: 6px; }
.ol-targetchip {
	appearance: none; cursor: pointer; text-align: left;
	border: 1px solid var(--ol-border); border-radius: 999px;
	padding: 4px 11px; font-size: .78rem; background: var(--ol-card); color: var(--ol-text);
	max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ol-targetchip:hover { border-color: var(--ol-primary); }
.ol-targetchip:focus-visible { outline: 2px solid var(--ol-primary); outline-offset: 2px; }
.ol-targetchip--peo.is-on { background: #E7EEFA; border-color: #1F4A8C; color: #1F4A8C; font-weight: 600; }
.ol-targetchip--plo.is-on { background: var(--ol-success-bg); border-color: var(--ol-success); color: var(--ol-success); font-weight: 600; }

.ol-checks { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 10px; }
.ol-checks label { display: inline-flex; align-items: center; gap: 6px; font-size: .85rem; }

/* display:block on a field would beat the user agent's [hidden] rule, the same trap the PEO
   weight input hit — stated explicitly so a type switch really does hide what does not apply. */
.ol-q-options[hidden], .ol-q-scale[hidden] { display: none !important; }

@media (max-width: 860px) {
	.ol-survey-cols { grid-template-columns: 1fr; }
	.ol-survey-cols__preview { position: static; }
}

/* =========================================================================================
   Survey results
   ========================================================================================= */
.ol-qresult { border-bottom: 1px solid var(--ol-border); padding: 12px 0; }
.ol-qresult:last-child { border-bottom: none; }
.ol-qresult__head { display: flex; gap: 16px; align-items: flex-start; }
.ol-qresult__text { flex: 1; min-width: 0; font-size: .92rem; line-height: 1.4; }
.ol-qresult__mean { text-align: right; flex-shrink: 0; min-width: 110px; }
.ol-qresult__mean strong { font-size: 1.05rem; }

/* The distribution behind a mean. Ten who strongly agree and ten who strongly disagree average
   to exactly the same figure as twenty who are neutral, and those two employers' views call for
   opposite responses — so the shape is never optional. */
.ol-qdist { margin-top: 9px; display: flex; flex-direction: column; gap: 4px; }
.ol-qdist__row { display: flex; align-items: center; gap: 10px; font-size: .8rem; }
.ol-qdist__label { width: 150px; flex-shrink: 0; color: var(--ol-muted); }
.ol-qdist__track { flex: 1; height: 12px; background: var(--ol-border); border-radius: 3px; overflow: hidden; }
.ol-qdist__fill { height: 100%; background: var(--ol-primary); }
.ol-qdist__n { width: 30px; text-align: right; font-variant-numeric: tabular-nums; }

.ol-qcomments { margin-bottom: 16px; }
.ol-qcomments__q { font-size: .85rem; font-weight: 600; color: var(--ol-muted); margin-bottom: 6px; }
.ol-qcomments__c {
	margin: 0 0 6px; padding: 9px 14px; font-size: .9rem;
	background: var(--ol-bg); border-left: 3px solid var(--ol-accent); border-radius: 0 6px 6px 0;
}

/* ---- triangulation ---- */
.ol-gap { font-weight: 700; font-variant-numeric: tabular-nums; }
/* Direction, not judgement: respondents rating an outcome ABOVE the department's own measurement
   is not automatically good news, and below it is not automatically bad. Both are worth reading,
   so the colours mark which way the gap runs rather than scoring it. */
.ol-gap--up   { color: var(--ol-success); }
.ol-gap--down { color: var(--ol-danger); }
.ol-gap--none { color: var(--ol-muted); font-weight: 400; }

/* =========================================================================================
   Change log
   ========================================================================================= */
.ol-clchange { font-size: .85rem; white-space: nowrap; }
.ol-clchange__arrow { color: var(--ol-muted); margin: 0 2px; }

/* =========================================================================================
   Programmes
   ========================================================================================= */
.ol-badge--muted { background: var(--ol-border); color: var(--ol-muted); font-weight: 600; font-size: .7rem; }
.ol-prog-acts { white-space: nowrap; }
.ol-prog-acts .ol-btn { margin-right: 4px; }

/* =========================================================================================
   Washington Accord attributes
   ========================================================================================= */
.ol-wa-desc { font-size: .88rem; line-height: 1.4; }
.ol-wa-flag { margin: 8px 0 0; padding-left: 20px; font-size: .85rem; }
.ol-wa-flag li { margin-bottom: 3px; }

.ol-wa-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.ol-wachip {
	appearance: none; cursor: pointer;
	border: 1px solid var(--ol-border); border-radius: 7px;
	padding: 7px 13px; background: var(--ol-card); color: var(--ol-text);
	font: inherit; font-size: .85rem; font-weight: 600;
	font-variant-numeric: tabular-nums;
}
.ol-wachip:hover { border-color: var(--ol-primary); }
.ol-wachip:focus-visible { outline: 2px solid var(--ol-primary); outline-offset: 2px; }
.ol-wachip.is-on { background: var(--ol-primary); border-color: var(--ol-primary); color: #fff; }
/* WP1 is the qualifying attribute, so it is marked as different from its six siblings before
   anyone clicks it rather than only in the warning afterwards. */
.ol-wachip--key { border-style: dashed; }
.ol-wachip--key.is-on { border-style: solid; }

/* An attribute covered by exactly one course is a concentration risk, not a pass and not a
   failure — it needs its own colour rather than being folded into either. */
.ol-verdict--warn { background: #FBF0DC; color: #8A6D1F; }
.ol-wa-inline { font-size: .78rem; font-weight: 600; color: var(--ol-primary); font-variant-numeric: tabular-nums; }

/* ---- Wording analyser ------------------------------------------------------------------
   A suggestion has to look unlike a decision. This panel is deliberately quiet — a tinted
   block with a rule down its left edge, no fill on the buttons, nothing pre-selected — so
   that reading it feels like consulting a note rather than being handed an answer. The one
   emphatic element is the matched wording, because that is the part a person checks. */
.ol-wa-suggest {
	border: 1px solid var(--ol-border); border-left: 3px solid var(--ol-accent);
	border-radius: 8px; background: var(--ol-bg); padding: 12px 14px; margin: 0 0 18px;
}
.ol-wa-suggest__head { font-weight: 700; font-size: .85rem; color: var(--ol-primary); }
.ol-wa-suggest__row {
	display: flex; gap: 10px; align-items: flex-start;
	padding: 7px 0; border-top: 1px solid var(--ol-border);
}
.ol-wa-suggest__label {
	flex: 0 0 40px; font-size: .72rem; font-weight: 700; letter-spacing: .04em;
	text-transform: uppercase; color: var(--ol-muted); padding-top: 5px;
}
/* Dashed, unfilled: it is an offer, and it must not look like an attribute already claimed. */
.ol-wachip--suggest { border-style: dashed; font-size: .8rem; padding: 4px 10px; }
.ol-wa-suggest__note {
	margin: 8px 0 0; font-size: .78rem; line-height: 1.45; color: #7A5C0E;
	border-top: 1px solid var(--ol-border); padding-top: 8px;
}

/* The review table's three lines per cell: what is recorded, what the words read as, and why.
   The recorded tag is struck through nowhere — it has not been overruled, it is being
   questioned — so the difference is carried by weight and colour, not by deletion. */
.ol-wr-was { font-size: .82rem; color: var(--ol-muted); }
.ol-wr-sugg { font-size: .85rem; font-weight: 600; color: var(--ol-primary); }
.ol-wr-why { font-size: .75rem; line-height: 1.35; margin-top: 2px; }
/* A second finding in the same cell needs air, or "WP6: client, community" and "WP4 claimed"
   run together into one sentence that says neither. */
.ol-wr-why + .ol-wr-was, .ol-wr-why + .ol-wr-sugg { margin-top: 7px; }

/* ---- Sortable headers on tables that are cloned into issued documents -------------------
   Deliberately defined only here and never in the document stylesheet. The screen gets a
   clickable header with a caret; the PDF gets the same rows in the same order with a plain
   header, because a sort caret in an accreditation exhibit is furniture, not information. */
.ol-th-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.ol-th-sortable:hover { text-decoration: underline; }
.ol-th-sortable:focus-visible { outline: 2px solid var(--ol-accent); outline-offset: -2px; }
.ol-th-sortable::after { content: ' \2195'; opacity: .35; font-size: .9em; }
.ol-th-sortable[aria-sort="ascending"]::after { content: ' \2191'; opacity: 1; }
.ol-th-sortable[aria-sort="descending"]::after { content: ' \2193'; opacity: 1; }

/* The "showing the first N" footer under a capped tile grid. Spans the whole grid rather than
   sitting in a column, because it is about the list, not a member of it. */
.ol-tile-more {
	grid-column: 1 / -1; text-align: center; padding: 18px 12px 4px;
	border-top: 1px solid var(--ol-border); margin-top: 12px;
}
.ol-tile-more p { margin: 0 0 10px; font-size: .86rem; }

/* An explicit ordering control, for lists where the useful default is not the obvious one. */
.ol-tbar__sort { display: inline-flex; align-items: center; gap: 6px; font-size: .85rem; color: var(--ol-muted); }
.ol-tbar__sort select { font-size: .85rem; }

/* The "show more" under a paged list of rows. Sits at the end of the list it belongs to. */
.ol-list-more { text-align: center; padding: 10px 0 2px; }

/* Scatter legend. Identity is never colour alone here — each key carries its own marker shape,
   which is what survives a greyscale print and a colour-blind reader. */
.ol-scatter-legend { display: flex; flex-wrap: wrap; gap: 16px; margin: 6px 0 2px; font-size: .82rem; color: var(--ol-muted); }
.ol-scatter-key { display: inline-flex; align-items: center; gap: 6px; }
.ol-scatter-key b { color: var(--ol-text); }

/* ---- Survey question: how one answer splits across several outcomes --------------------
   Only rendered when there are two or more outcomes to split between, so this is never an
   empty panel teaching people to ignore the area. The total sits at the bottom and speaks up
   when it is not 100 — the calculation treats the numbers as relative shares and works either
   way, but a total of 90 is nearly always a slip, and saying so beats correcting it silently. */
.ol-qweights {
	margin: 10px 0 0; padding: 12px 14px;
	border: 1px solid var(--ol-border); border-left: 3px solid var(--ol-accent);
	border-radius: 8px; background: var(--ol-bg);
}
.ol-qweights__head { font-weight: 700; font-size: .85rem; color: var(--ol-primary); margin-bottom: 2px; }
.ol-qweights__row {
	display: flex; align-items: center; gap: 8px;
	padding: 5px 0; border-top: 1px solid var(--ol-border); font-size: .85rem;
}
.ol-qweights__row > span:first-child { flex: 1 1 auto; }
.ol-qweights__row input { flex: none; width: 84px; min-width: 0; text-align: right; font-variant-numeric: tabular-nums; }
.ol-qweights__total {
	margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--ol-border);
	font-size: .78rem; line-height: 1.4;
}
.ol-qweights__total.is-off strong { color: #7A5C0E; }

/* A caution attached to one analysed statement — the compound-verb note, the missing-WP1 note.
   Sits under the statement it is about rather than in its own column, because it is a remark on
   that sentence and a column of them would read as a separate finding per row. */
.ol-wl-note {
	margin-top: 6px; padding-left: 9px; border-left: 2px solid var(--ol-accent);
	font-size: .76rem; line-height: 1.4; color: #7A5C0E;
}

/* ---- Course File slots -------------------------------------------------------------------
   A slot is a row, not a card: there are six of them on screen at once and the eye should be
   able to run down the labels. Filled and empty differ by a left rule and the weight of the
   label — not by colour alone, since "uploaded" is a fact rather than a verdict and does not
   want a green tick shouting at a reader every time the card opens. */
.ol-cf-group { border: 1px solid var(--ol-border); border-radius: 10px; overflow: hidden; margin-top: 8px; }
.ol-cf-group__head {
	padding: 8px 12px; background: var(--ol-bg); border-bottom: 1px solid var(--ol-border);
	font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ol-muted);
}
.ol-cf-slot {
	display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
	padding: 10px 12px; border-top: 1px solid var(--ol-border);
	border-left: 3px solid transparent;
}
.ol-cf-slot:first-child { border-top: 0; }
.ol-cf-slot.is-filled { border-left-color: var(--ol-accent); }
.ol-cf-slot__what { flex: 1 1 260px; min-width: 0; }
.ol-cf-slot__label { display: block; font-size: .9rem; font-weight: 600; }
.ol-cf-slot.is-filled .ol-cf-slot__label { color: var(--ol-primary); }
.ol-cf-slot__note { display: block; font-size: .76rem; color: var(--ol-muted); margin-top: 1px; }
.ol-cf-slot__acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ol-cf-slot__size { font-size: .76rem; font-variant-numeric: tabular-nums; }
.ol-cf-input { font-size: .78rem; max-width: 190px; }

/* ---- Standards & taxonomies -------------------------------------------------------------
   Each vocabulary is shown in full before it is chosen. A dropdown of names alone would ask a
   HOD to pick between "Bloom's revised (2001)" and "Bloom's original (1956)" on the strength of
   the year — and the difference that matters is that one has Synthesis above Evaluation. */
.ol-tax-group { border: 1px solid var(--ol-border); border-radius: 10px; padding: 14px; margin-top: 14px; }
.ol-tax-group__head { font-size: .95rem; font-weight: 700; color: var(--ol-primary); margin-bottom: 2px; }
.ol-tax-detail { margin-top: 4px; }
.ol-tax-levels { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; margin-top: 6px; font-size: .8rem; }
.ol-tax-levels strong { flex: 0 0 92px; color: var(--ol-muted); font-size: .72rem; letter-spacing: .06em; text-transform: uppercase; }
.ol-tax-level {
	padding: 3px 8px; border: 1px solid var(--ol-border); border-radius: 999px;
	background: var(--ol-bg); font-variant-numeric: tabular-nums;
}
.ol-tax-edit { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--ol-border); }
.ol-tax-edit__levels { display: flex; flex-wrap: wrap; gap: 6px; }
.ol-tax-edit__levels input { max-width: 190px; }

/* ---- Accord codes, with what they mean -------------------------------------------------
   These were bare chips carrying the wording on a title attribute. A tooltip is invisible on
   a touch screen, invisible to anyone who does not think to hover, and invisible in a
   screenshot — so in practice the meanings were not shown at all. "WK7" tells nobody
   anything; the sentence behind the code is the entire point of the vocabulary. */
.ol-wa-list { display: flex; flex-direction: column; gap: 4px; }
.ol-wa-item {
	appearance: none; cursor: pointer; font: inherit; text-align: left;
	display: flex; align-items: flex-start; gap: 10px;
	padding: 8px 10px; border: 1px solid var(--ol-border); border-radius: 8px;
	background: var(--ol-card); color: var(--ol-text);
}
.ol-wa-item:hover { border-color: var(--ol-primary); }
.ol-wa-item:focus-visible { outline: 2px solid var(--ol-primary); outline-offset: 2px; }
.ol-wa-item.is-on { border-color: var(--ol-primary); background: var(--ol-bg); box-shadow: inset 3px 0 0 var(--ol-primary); }
.ol-wa-item__code {
	flex: 0 0 44px; font-weight: 700; font-size: .82rem; font-variant-numeric: tabular-nums;
	color: var(--ol-muted); padding-top: 1px;
}
.ol-wa-item.is-on .ol-wa-item__code { color: var(--ol-primary); }
/* WP1 is the qualifying attribute — marked apart from its six siblings before anyone clicks. */
.ol-wa-item__code--key::after { content: ' ★'; color: var(--ol-accent); }
.ol-wa-item__text { font-size: .8rem; line-height: 1.4; color: var(--ol-muted); }
.ol-wa-item.is-on .ol-wa-item__text { color: var(--ol-text); }

/* The analyser's level readings are buttons now, so the best one has to look different from
   the alternatives without looking like it has already been applied. */
.ol-wa-suggest__opts { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.ol-wachip--suggest.is-best { border-style: solid; border-color: var(--ol-primary); color: var(--ol-primary); }
.ol-wachip--suggest.is-taken { background: var(--ol-primary); border-color: var(--ol-primary); color: #fff; }

/* The CLO's level in the mapping table. Neutral, not a verdict: a level is a description of
   what the CLO asks for, not a score. */
.ol-badge--ldl { background: var(--ol-bg); color: var(--ol-primary); border: 1px solid var(--ol-border); font-weight: 600; white-space: nowrap; }

/* "Full backup" is a claim. These two columns are what makes it checkable — a person deciding
   whether this file is their disaster plan needs to see which half of their work is in it, and
   the second column is the one that matters. */
.ol-backup-what { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin: 12px 0 16px; }
.ol-backup-what > div { border: 1px solid var(--ol-border); border-radius: 8px; padding: 10px 14px; background: var(--ol-bg); }
.ol-backup-what strong { font-size: .8rem; }
.ol-backup-what ul { margin: 6px 0 0; padding-left: 18px; font-size: .78rem; line-height: 1.5; color: var(--ol-muted); }

/* The bulk-download bar sits above a list and acts on exactly what that list is showing, so it
   reads as a caption on the list rather than a card of its own competing with it. */
.ol-bulk-bar {
	display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
	gap: 12px; padding: 12px 16px; margin-bottom: 12px;
}

/* A <label> wrapping a hidden file input, so "Upload an edited file" looks and behaves like the
   button beside it instead of like a browser's own file control. */
.ol-tax-uplabel { cursor: pointer; display: inline-flex; align-items: center; }

/* Getting Started: a plain step count, not a percentage badge — the number that matters to
   someone mid-walkthrough is "how many are left," not a rounded figure. */
.ol-progressbar { height: 8px; border-radius: 999px; background: var(--ol-bg); border: 1px solid var(--ol-border); overflow: hidden; }
.ol-progressbar__fill { height: 100%; background: var(--ol-primary); border-radius: 999px 0 0 999px; transition: width .2s ease; }

.ol-training-steps { display: flex; flex-direction: column; gap: 10px; }
.ol-training-step { display: flex; gap: 14px; align-items: flex-start; }
.ol-training-step__check { padding-top: 3px; }
.ol-training-step__check input { width: 18px; height: 18px; cursor: pointer; }
.ol-training-step__body h3 { margin: 0 0 4px; font-size: .96rem; }
.ol-training-step__body p { margin: 0 0 8px; font-size: .84rem; line-height: 1.5; }
.ol-training-step__actions { display: flex; flex-wrap: wrap; gap: 8px; }
/* Done stays legible, not greyed out — an HOD revisiting a finished step to double-check
   something shouldn't have to fight faded text to read it. */
.ol-training-step--done { border-left: 3px solid var(--ol-success); }
.ol-training-step--done h3 { color: var(--ol-success); }
/* The brief flash a step gets when it's handed focus — landing on the page, or right after the
   step above it was ticked off — so "what's next" is something you notice, not something you
   have to go looking for. */
.ol-training-step--focus { box-shadow: 0 0 0 3px var(--ol-accent); transition: box-shadow .3s ease; }

.ol-training-answer { padding: 8px 0; border-bottom: 1px solid var(--ol-border); font-size: .84rem; }
.ol-training-answer:last-child { border-bottom: none; }
.ol-training-answer .ol-muted { margin-top: 2px; font-size: .78rem; }
