/**
 * Front-end layout corrections.
 *
 * Loaded for every language, after the theme's own stylesheets. Grouped by the
 * problem each block solves so any one of them can be reverted on its own.
 */

/* ========================================================================== *
 * 1. Sliders: no broken flash before initialisation
 *
 * app.js is a footer script and slick only initialises on DOM ready, so until
 * then every slide is in the flow at once. #hero-slider is a `d-flex`, which
 * squeezed each slide into a sliver and wrapped the headings one letter per
 * line - the "broken on first load" state.
 *
 * Showing only the first slide until slick adds `.slick-initialized` keeps the
 * first paint correct and meaningful (good for LCP too), instead of hiding the
 * section behind a spinner.
 * ========================================================================== */

#hero-slider:not(.slick-initialized) > *:not(:first-child),
#usls-slider:not(.slick-initialized) > *:not(:first-child),
#academic-committee:not(.slick-initialized) > *:not(:first-child) {
	display: none;
}

/*
 * The testimonial slider shows three across, so collapsing it to one slide
 * before slick runs paints a single full-width card that then snaps into three
 * columns. Laying the first slides out in the same grid slick will build keeps
 * the first paint stable. Column counts mirror the `responsive` breakpoints in
 * app.js: 3 by default, 2 at <=991px, 1 at <=768px.
 */
#testimonial-slider:not(.slick-initialized) {
	display: flex;
	align-items: stretch;
}

#testimonial-slider:not(.slick-initialized) > * {
	flex: 0 0 33.3333%;
	max-width: 33.3333%;
}

#testimonial-slider:not(.slick-initialized) > *:nth-child(n + 4) {
	display: none;
}

@media (max-width: 991.98px) {
	#testimonial-slider:not(.slick-initialized) > * {
		flex-basis: 50%;
		max-width: 50%;
	}

	#testimonial-slider:not(.slick-initialized) > *:nth-child(n + 3) {
		display: none;
	}
}

@media (max-width: 768px) {
	#testimonial-slider:not(.slick-initialized) > * {
		flex-basis: 100%;
		max-width: 100%;
	}

	#testimonial-slider:not(.slick-initialized) > *:nth-child(n + 2) {
		display: none;
	}
}

/* One visible child must not be stretched by the flex row it sits in. */
#hero-slider:not(.slick-initialized) {
	display: block !important;
}

#hero-slider:not(.slick-initialized) > * {
	width: 100%;
}

/* Reserve height so the page does not jump when slick takes over. */
#hero-slider,
#usls-slider {
	min-height: 320px;
}

@media (max-width: 767.98px) {
	#hero-slider,
	#usls-slider {
		min-height: 0;
	}
}

/* ========================================================================== *
 * 2. Testimonials: equal-height cards
 *
 * slick gives each slide the height of the tallest one but leaves the card
 * inside at its natural height, so the cards used to end at different points.
 * Stretching the track and clamping the quote to a fixed number of lines makes
 * every card identical, whatever the length of the testimonial.
 * ========================================================================== */

/*
 * Flex the whole chain rather than using `height: 100%`.
 *
 * A percentage height on a stretched flex item resolves against an `auto`
 * container height, which cancels the stretch and leaves each card at its
 * natural height again. Making every level a flex container keeps the stretch
 * intact whether the slide is wrapped by slick or not.
 */
#testimonial-slider .slick-track {
	display: flex !important;
	align-items: stretch;
}

#testimonial-slider .slick-slide {
	height: auto !important;
	display: flex !important;
}

#testimonial-slider .slick-slide > div {
	display: flex;
	width: 100%;
}

#testimonial-slider .testimonial-slide {
	display: flex;
	width: 100%;
}

.testimonial-card {
	flex: 1 1 auto;
}

/* Replaces the old blanket `min-height: 550px`, which left short testimonials
   floating in empty space. */
.bg-testimonial-card {
	min-height: 0;
}

/*
 * Truncation by fixed height, not `-webkit-line-clamp`.
 *
 * The editor wraps testimonials in <p> elements, and line-clamp counts lines
 * without accounting for the margins between block children: an eight-line
 * clamp rendered 237px instead of 205px, so the text overran its box and the
 * footer sat on top of it. A fixed height with `overflow: hidden` truncates
 * predictably whatever markup the editor produces, and gives every card the
 * same content height.
 */
.testimonial-card__content {
	/*
	 * Visible height of the quote. One number resizes every card.
	 *
	 * 320px is 12.5 lines at the 16px/1.6 body setting, so the cut lands
	 * mid-line; the fade below is fully transparent by the bottom edge, which
	 * hides the half-height row.
	 */
	--nvn-testimonial-height: 320px;
	line-height: 1.6;
	/* `max-height` as well as `height`: if anything downstream resets the height
	   to auto, the cap still holds and the card cannot run away down the page. */
	height: var(--nvn-testimonial-height);
	max-height: var(--nvn-testimonial-height);
	overflow: hidden;
	/* Guards against a stray `display: -webkit-box` leaking in from elsewhere,
	   which would hand height control back to line-clamp. */
	display: block;
}

/* Narrow columns wrap more, so the same height would leave short quotes
   floating in empty space on small screens. */
@media (max-width: 991.98px) {
	.testimonial-card__content {
		--nvn-testimonial-height: 280px;
	}
}

@media (max-width: 575.98px) {
	.testimonial-card__content {
		--nvn-testimonial-height: 240px;
	}
}

.testimonial-card__content > *:first-child {
	margin-top: 0;
}

.testimonial-card__content > *:last-child {
	margin-bottom: 0;
}

/* Fade the last line so the cut reads as deliberate. Masking the text rather
   than overlaying a gradient keeps it correct on the hover background too.
   Added by the script only when the text is actually truncated. */
.testimonial-card__content.is-clamped {
	-webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
	mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
}

/* The button belongs with the quote it expands, directly under the text. */
.testimonial-card__actions {
	padding: 0.5rem 1.5rem 0;
}

/* A minimum height keeps the author block on the same line across every card,
   even when one author's role wraps and another's does not. */
.testimonial-card__footer {
	margin-top: 0.5rem;
	padding-top: 0.75rem !important;
	min-height: 104px;
}

.testimonial-card__name,
.testimonial-card__role {
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
	overflow-wrap: anywhere;
}

.testimonial-more {
	white-space: nowrap;
	font-size: 14px;
}

.bg-testimonial-card:hover .testimonial-more {
	color: #fff !important;
}

/* Utility used by the card footer; Bootstrap has no `min-width: 0` helper and
   without it flex children refuse to shrink and force overflow. */
.min-w-0 {
	min-width: 0;
}

/* ---- the modal ---- */

.nvn-testimonial-modal .modal-content {
	position: relative;
}

.nvn-testimonial-modal .modal-header {
	background-color: #d7fde2;
	border-bottom: 0;
	/* Room for the pinned close button so a long role never runs under it. */
	padding-inline-end: 3.25rem;
}

/*
 * Close button pinned to the corner rather than sitting in the header flow,
 * where it competed with the author's name for space.
 *
 * `inset-inline-end` rather than `right`: that puts it top-right in English and
 * Bangla and top-left in Arabic, which is the same corner once the dialog is
 * mirrored - top-right in RTL would sit on top of the title.
 */
.nvn-testimonial-modal .btn-close {
	position: absolute;
	top: 0.75rem;
	inset-inline-end: 0.75rem;
	margin: 0;
	z-index: 2;
	opacity: 0.75;
}

.nvn-testimonial-modal .btn-close:hover,
.nvn-testimonial-modal .btn-close:focus-visible {
	opacity: 1;
}

.nvn-testimonial-modal__thumb .testimonials-img {
	width: 56px;
	height: 56px;
}

.nvn-testimonial-modal__content {
	font-size: 16px;
	line-height: 1.8;
	padding: 1.5rem;
}

.nvn-testimonial-modal .modal-content.rtl {
	direction: rtl;
	text-align: right;
}

/* ========================================================================== *
 * 3. Vertical card sliders
 *
 * The endorsement names and the notice titles are the same component: a column
 * of cards, a fixed number on screen, an arrow above and below. Both are driven
 * by nvnVerticalCardSlider() in assets/js/app.js, which gives every card the
 * height of the tallest one, so they share these rules too.
 * ========================================================================== */

#endorsement-tab .splide__track,
#notice-slider .splide__track {
	/* Splide measures ttb tracks from the first slide; without this the list
	   collapses to a single item's height on load. */
	max-height: none;
}

#endorsement-tab .splide__list,
#notice-slider .splide__list {
	align-items: stretch;
}

#endorsement-tab .endorsement-item,
#notice-slider .notice-item {
	display: flex;
}

/* Every card is as tall as the tallest, so a one-line title in a list whose
   longest runs to three would otherwise sit against the top with the slack
   below it. Centring splits that slack above and below the text instead. */
#endorsement-tab .endorsement-item .nav-link,
#notice-slider .notice-item .nav-link {
	width: 100%;
	display: flex;
	flex-direction: column;
	justify-content: center;
}

#endorsement-tab .splide__arrows,
#notice-slider .splide__arrows {
	padding: 4px 0;
}

#endorsement-tab .splide__arrow,
#notice-slider .splide__arrow {
	position: static;
	transform: none;
	background: transparent;
	opacity: 1;
	width: auto;
	height: auto;
}

#endorsement-tab .splide__arrow svg,
#notice-slider .splide__arrow svg {
	width: 40px;
	height: 22px;
	fill: var(--primary-color, #21a947);
}

.bg-endorsement {
	display: flex;
	align-items: center;
}

/* ========================================================================== *
 * 3a. Notices archive: rows that line up
 *
 * Each row is a date block, the notice, and a link. All three were sized by
 * their own contents, so the date block was as wide as its month name spelled
 * out: "November" made it 160px, "July" 103px. The notice text therefore began
 * at a different offset in every row - 57px of drift down the page - and the
 * column of blue blocks had a ragged edge. Nothing about it was specific to
 * Arabic; the longer English month names simply made it worse there.
 *
 * Fixed widths on the two end blocks, wide enough for the longest month name
 * and the longest link label in any of the three languages, so every row starts
 * and ends on the same line. Below the medium breakpoint the row stacks and the
 * widths are released.
 * ========================================================================== */

@media (min-width: 768px) {
	.nvn-notice-row__date {
		/* "September" measures 119px at this size, plus the 24px padding a side. */
		flex: 0 0 170px;
		width: 170px;
	}

	.nvn-notice-row__action {
		/* Sized from the widest label in its own font, not in English: each
		   language loads a different family, and "عرض التفاصيل" set in Noto Kufi
		   Arabic is 99px against "View Details" at 88px in Outfit. 150px leaves
		   118px inside the padding, so none of the three wraps. */
		flex: 0 0 150px;
		width: 150px;
	}
}

/* ========================================================================== *
 * 3b. Notice board: preview panel height
 *
 * The panel is as tall as the notice it is showing, and no taller.
 *
 * It used to carry `h-100`, meaning "match the list of notices beside me". It
 * could not: a percentage height resolves against the whole column, and that
 * column also held the "Read All Notices" link below the panel. The panel grew
 * to the column's full height, the link was pushed past the bottom, the column
 * grew to fit it, and the panel grew again - each pass adding height no content
 * occupied, which pooled as a 78px void under "Read More ...".
 *
 * Sizing to content is also what keeps that void from coming back now that the
 * list beside it is a four-card slider and usually the taller of the two: a
 * stretched panel would put the same emptiness between the excerpt and the
 * link. The slack belongs in the column, outside the tinted box, where it reads
 * as the gap before "Read All Notices" rather than as a hole in the panel.
 * ========================================================================== */

#notice-view {
	display: flex;
	flex-direction: column;
	/* The panel is shorter than the slider beside it, so it is centred against
	   it rather than hung from the top with all the slack underneath. */
	justify-content: center;
}

#notice-view > .nvn-notice-panel {
	display: flex;
	/* Never grow: the tinted box ends where the notice ends. */
	flex: 0 0 auto;
	align-self: stretch;
}

#notice-view > .nvn-notice-panel > div {
	flex: 1 1 auto;
}

/* ========================================================================== *
 * 4. Section vertical rhythm
 *
 * Every top-level section on the site gets the same vertical padding, from one
 * token.
 *
 * What this replaces: the spacing was written per template, on whichever
 * element came to hand, and the two conventions stacked. Page templates put
 * `py-5` on the <section> AND `py-5` on the .container inside it, so a page
 * title banner came to 96px top and bottom while the content section under it
 * came to 48px, and `about` used `mt-4` with no padding at all - 24px above,
 * nothing below. The front page was worse: ten sections spelled their spacing
 * with `pt-5`, `pb-4`, `mb-5`, `pt-4 pb-4` or nothing, one of them chosen by
 * language, producing 24/64, 48/64, 64/112, 64/24, 48/48 and 64/64 in a single
 * scroll. Neighbouring sections therefore stacked two large gaps in one place
 * and almost none in the next.
 *
 * How it works: the padding goes on the section, and the container's vertical
 * padding and margin are zeroed, so it cannot be added a second time from the
 * inside. Sections carry the padding rather than containers because not every
 * section has a `.container` child, and because a section's own background -
 * the blue page-title banner - has to stretch across the padding.
 *
 * `!important` is deliberate and is the reason this is one rule instead of
 * seventy template edits: the values being overridden are Bootstrap spacing
 * utilities, which are themselves `!important`, so nothing else can reach them.
 * Set spacing here, not in markup.
 *
 * Scoped to top-level sections. Anything a plugin renders deeper in the tree -
 * Tutor LMS, WooCommerce - is left alone.
 * ========================================================================== */

:root {
	--nvn-section-gap: 3rem;
}

body[class] main > section,
body[class] > section,
body[class] > div > section {
	padding-top: var(--nvn-section-gap) !important;
	padding-bottom: var(--nvn-section-gap) !important;
	margin-top: 0 !important;
	margin-bottom: 0 !important;
}

body[class] main > section > .container,
body[class] main > section > .container-fluid,
body[class] > section > .container,
body[class] > section > .container-fluid,
body[class] > div > section > .container,
body[class] > div > section > .container-fluid {
	padding-top: 0 !important;
	padding-bottom: 0 !important;
	margin-top: 0 !important;
	margin-bottom: 0 !important;
}

/*
 * The pale tint some section containers carry.
 *
 * A class rather than an inline style on each one, so the two sections using it
 * cannot drift apart and a third does not have to copy a colour literal. Pair it
 * with `nvn-section-gap-inside` on the <section>, or the band will end flush
 * against its own content.
 */
.nvn-section-tint {
	background-color: rgba(221, 244, 255, 0.3);
}

/*
 * Sections whose background is painted by the container, not the section.
 *
 * Padding on the section sits *outside* the container's background, so a tinted
 * container would end flush against its own content - the colour band starting
 * at the cap height of the heading. `nvn-section-gap-inside` moves the same gap
 * in one level, so the tint covers it. The measured spacing is unchanged; only
 * the element carrying it moves, which is the whole point.
 *
 * Put this on the <section> whenever the coloured element is the container
 * (front page "Click to Explore"). A section that paints its own background -
 * the blue bands - needs nothing.
 */
body[class] main > section.nvn-section-gap-inside,
body[class] > section.nvn-section-gap-inside,
body[class] > div > section.nvn-section-gap-inside {
	padding-top: 0 !important;
	padding-bottom: 0 !important;
}

body[class] main > section.nvn-section-gap-inside > .container,
body[class] main > section.nvn-section-gap-inside > .container-fluid,
body[class] > section.nvn-section-gap-inside > .container,
body[class] > section.nvn-section-gap-inside > .container-fluid,
body[class] > div > section.nvn-section-gap-inside > .container,
body[class] > div > section.nvn-section-gap-inside > .container-fluid {
	padding-top: var(--nvn-section-gap) !important;
	padding-bottom: var(--nvn-section-gap) !important;
}

/* The floating chat panel is a fixed-position section, not a band of page. */
#chatroom {
	padding-top: 0 !important;
	padding-bottom: 0 !important;
}

/* Section headings keep their own breathing room, not an extra section's worth.
 *
 * `div` is load-bearing. Section titles are wrapped in a div, but `.w-100` and
 * `.text-center` are ordinary utilities that anything may carry - and the notice
 * board's nav buttons carry both. Without the tag this rule reached inside them
 * and gave every button 0 padding at the top and 28px at the bottom, which sat
 * their titles a clear 29px above centre. */
body[class] main > section div.w-100.text-center,
body[class] > section div.w-100.text-center,
body[class] > div > section div.w-100.text-center {
	padding-top: 0 !important;
	padding-bottom: 1.75rem !important;
	margin-bottom: 0 !important;
}

@media (max-width: 767.98px) {
	:root {
		--nvn-section-gap: 2.5rem;
	}
}
