/* ============================================================================
   responsive-enhance.css

   Cross-device / cross-browser hardening for the whole site (CMS + LMS).
   Loaded LAST in base.html so it can correct the legacy sheets without
   editing them. Every rule here fixes a problem that was measured in a real
   headless browser at 320/360/390/768/1024/1280px, not a theoretical one.

   Kept deliberately low-specificity and additive: it patches behaviour
   (clipping, iOS zoom, tap size, motion) rather than restyling components.
   ========================================================================= */


/* ---------------------------------------------------------------------------
   1. Root / document behaviour
   ------------------------------------------------------------------------ */

html {
  /* iOS Safari and Chrome on Android inflate text when a phone is rotated to
     landscape unless this is pinned. Bootstrap 5 sets it on <body>; setting it
     on the root as well covers elements that escape the body cascade. */
  -webkit-text-size-adjust: 100%;
  -moz-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* style.css sets `body { overflow-x: hidden }`. That does stop sideways
   scrolling, but it also turns <body> into a scroll container, which silently
   disables every `position: sticky` descendant in Chrome and Safari.
   `overflow-x: clip` clips identically WITHOUT creating a scroll container,
   so sticky keeps working. The @supports guard leaves older browsers on the
   original hidden behaviour. */
@supports (overflow-x: clip) {
  body {
    overflow-x: clip;
  }
}

/* Safe area for iPhone X-and-later. base.html now sends viewport-fit=cover so
   the page paints edge to edge; these insets keep content out from under the
   notch, the rounded corners and the home indicator. env() resolves to 0 on
   every device without a notch, so this is inert elsewhere. */
body {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}


/* ---------------------------------------------------------------------------
   2. Fluid media

   Bootstrap 5's reboot does NOT set `max-width: 100%` on images globally —
   only `.img-fluid` does. Any admin-authored CKEditor image carrying an
   explicit width therefore overflows, and because of the body clip above it is
   cut off rather than scrollable, i.e. content is lost with no scrollbar to
   reveal it.
   ------------------------------------------------------------------------ */

img,
svg,
video,
canvas,
iframe,
embed,
object {
  max-width: 100%;
}

/* `height: auto` is applied only where it cannot fight a deliberate fixed
   height: unclassed images (CKEditor output) and the known rich-text wrappers.
   Sliders and decorative art that set their own height are left alone. */
img:not([class]),
.lms-rich-text img,
.rich-text img,
.page-detail img,
.blog-details img,
.event-details img {
  height: auto;
}

/* Embedded video/maps keep their aspect ratio instead of squashing. */
.lms-rich-text iframe,
.rich-text iframe,
.page-detail iframe,
.blog-details iframe {
  aspect-ratio: 16 / 9;
  height: auto;
  width: 100%;
}


/* ---------------------------------------------------------------------------
   3. Long-string overflow guard

   Unbroken strings — pasted URLs, e-mail addresses, long Hindi/English
   compounds in migrated Drupal bodies — are the most common source of
   horizontal overflow in admin-authored content.
   ------------------------------------------------------------------------ */

.lms-rich-text,
.rich-text,
.page-detail,
.blog-details,
.event-details,
.breadcrumb,
.card,
figcaption {
  overflow-wrap: break-word;
  word-wrap: break-word;
}

/* URLs that genuinely have no break opportunity still must not push the page
   wide; `anywhere` is the only value that breaks mid-token when needed. */
.lms-rich-text a,
.rich-text a,
.page-detail a {
  overflow-wrap: anywhere;
}

/* Headings are the other half of the problem, and the half the rules above
   miss: they interpolate values nobody sized the layout for. The LMS page
   header renders `Welcome, {{ user.username }}`, and a username is commonly
   an e-mail address — one unbreakable token that pushes the header, and with
   it the page, past the viewport. Measured on /lms/dashboard/ at 390px with
   text scaled to 200%: the header ran to 457px against a 390px viewport, i.e.
   67px of content unreachable behind the body clip.

   Course and card titles are admin-authored and have the same exposure, and a
   heading should never be what makes a page scroll sideways, so the guard is
   applied to headings generally rather than to the classes that happen to
   overflow today. */
h1, h2, h3, h4, h5, h6 {
  overflow-wrap: break-word;
}

/* The LMS profile renders each field as a flex row of <dt> label and <dd>
   value, and every value is user data: a username, an e-mail address, a place
   name. `min-width: auto` on a flex item resolves to min-content, and an
   e-mail address offers no break opportunity, so the row ends up as wide as
   the whole address — measured 455px for "qa_test_learner@parakh.local" in a
   390px viewport, and a longer address overflows at normal zoom too, not just
   zoomed. Both declarations are needed: `min-width: 0` lets the item shrink
   below min-content, `anywhere` gives min-content somewhere to break. */
.lms-profile-row dt,
.lms-profile-row dd {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The headings that carry interpolated values need `anywhere`, not
   `break-word`. The difference is not cosmetic: `break-word` breaks the line
   when it is drawn but leaves the element's min-content width computed as if
   the word were unbreakable, so a flex item sized to fit-content still comes
   out as wide as the longest token. `.lms-page-header` is a column flex
   container with `align-items: flex-start` at mobile, so its text block is
   sized exactly that way — and measured 424px wide for the single word
   "qa_test_learner" at 200% text. Only `anywhere` lowers min-content. */
.lms-page-title,
.lms-page-heading,
.lms-page-subtitle,
.lms-course-card-title,
.lms-course-card-title a {
  overflow-wrap: anywhere;
}


/* ---------------------------------------------------------------------------
   4. iOS Safari form-focus auto-zoom  (the single largest iOS defect found)

   Measured: 40 controls on /register/, 191 on /hpc/exemplar-foundational/,
   plus /login/, /webforms/your-feedback/ and /lms/dashboard/ render inputs at
   14–15px. Any control under 16px makes iOS Safari zoom the whole page in on
   focus, and it does NOT zoom back out — the user is left on a horizontally
   panned page mid-form.

   16px is the documented threshold. Applied on touch-sized viewports only, so
   desktop typography is untouched.
   ------------------------------------------------------------------------ */

@media screen and (max-width: 991px) {
  /* `!important` is deliberate and load-bearing here. The competing legacy
     declarations are id-scoped (e.g. `#foundational-stages-details-parts-abc
     .form-control { font-size: 14px }`), so no reasonable selector in this
     file could outrank them. This is a functional fix — it stops the viewport
     being hijacked — not a cosmetic preference, so it wins outright.
     Checkbox/radio/range are excluded: they carry no text entry and so never
     trigger the zoom. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]),
  select,
  textarea,
  .form-control,
  .form-select,
  .td-input-text,
  .select2-container .select2-search__field,
  .select2-container--default .select2-selection--single .select2-selection__rendered {
    font-size: 16px !important;
  }
}


/* ---------------------------------------------------------------------------
   5. Touch target sizing

   Applied under `pointer: coarse` so it affects touch devices only and never
   changes the mouse-driven desktop layout. Inline links inside prose are
   deliberately excluded — WCAG 2.2 (2.5.8) exempts them, and padding them out
   would wreck paragraph leading.
   ------------------------------------------------------------------------ */

@media (pointer: coarse) {
  button,
  .btn,
  [role="button"],
  input[type="submit"],
  input[type="button"],
  input[type="reset"] {
    min-height: 44px;
  }

  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]),
  select,
  .form-control,
  .form-select {
    min-height: 44px;
  }

  /* Checkboxes/radios render ~13px natively. 24x24 is the WCAG 2.2 AA (2.5.8)
     minimum; the label beside them stays clickable as before. */
  input[type="checkbox"],
  input[type="radio"] {
    min-width: 24px;
    min-height: 24px;
  }

  /* A label tied to a control is part of the same target — give it the same
     comfortable height so the pair reads as one tap area. `.lms-checkbox-option`
     is the registration form's subject/grade picker, which renders dozens of
     these in a grid. */
  .form-check,
  .lms-checkbox-option {
    min-height: 32px;
    display: flex;
    align-items: center;
    gap: 8px;
  }

  .form-check .form-check-input,
  .lms-checkbox-option input {
    margin-top: 0;
    flex-shrink: 0;
  }

  /* Standalone (non-prose) links: navigation, footer link columns, pagination
     and card actions get a comfortable vertical hit area. The footer markup is
     `<div id="footer">`, not a <footer> element, so it is addressed by id. */
  .wsmenu .wsmenu-list li > a,
  .wsmenu .wsmenu-list li > span,
  .navigation-bar a,
  .pagination a,
  .pagination .page-link,
  #footer .footer-links ul li > a,
  .lms-link-more {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  /* The contact list puts an icon <div> and the link side by side in one <li>,
     so the link there must stay inline or it drops below its icon. */
  #footer .contact-links li > a {
    display: inline-flex;
    min-height: 44px;
    align-items: center;
  }

  /* The legal links sit inline, separated by literal "|" characters, so they
     must not become blocks. */
  .inner-copyright a,
  .terms_conditions a {
    display: inline-flex;
    min-height: 44px;
    align-items: center;
  }

  .scroll-to-top {
    min-width: 44px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
}


/* ---------------------------------------------------------------------------
   6. Minimum legible text

   Measured floors of 10.4px and 11px on the footer copyright, the LMS ticker
   badge and the course category label. 12px is the practical minimum for body
   text on a phone.
   ------------------------------------------------------------------------ */

.copy-right,
.inner-copyright,
.blog-section .blog-details .blog-creation ul li {
  font-size: max(12px, 1em);
}

/* lms.css declares these in rem (0.65rem = 10.4px, 0.72rem = 11.52px) at the
   same specificity as anything sensible here, so they are raised to the 12px
   floor explicitly. */
.lms-ticker-badge,
.lms-ticker-label,
.lms-course-category,
.lms-course-card .lms-course-category {
  font-size: 12px;
}

@media screen and (max-width: 768px) {
  .inner-copyright {
    font-size: 12px;
  }
}


/* ---------------------------------------------------------------------------
   7. Horizontally scrollable tables

   Wide data tables (List of Boards ~692px, PRS state list ~500px, forum
   listing 720px min) already sit in scroll wrappers. These additions make the
   scroll usable and discoverable on touch: momentum scrolling on iOS, no
   rubber-banding out to the browser's back gesture, and a visible hint that
   there is more to the right.
   ------------------------------------------------------------------------ */

.table-responsive,
.sticky-table-wrap,
.foundationlearning,
.attendance-tables,
.lms-table-wrap {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

@media screen and (max-width: 768px) {
  .table-responsive,
  .sticky-table-wrap,
  .foundationlearning,
  .lms-table-wrap {
    /* Right-edge fade: tells the user the table continues off-screen. It is
       painted only while the element is actually scrolled away from its end. */
    background:
      linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)),
      linear-gradient(to left, rgba(0, 0, 0, 0.10), rgba(0, 0, 0, 0)) right / 18px 100% no-repeat;
  }
}

/* The LMS quiz attempts table and the match-question table were the only
   tables on the site with no scroll wrapper at all; both templates now emit
   `.lms-table-scroll` around them. */
.lms-table-scroll {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

/* `:not()` exemption: the match question's table is a legacy-parity table —
   the platform it was ported from never widened it, and forcing it to fill
   the panel stretches a two-word stem across the whole card. It still gets
   the scroll wrapper, which is all it needs here. */
.lms-table-scroll > table:not(.lms-quiz-match-table) {
  width: 100%;
}

/* The match question pairs a prose stem with a <select>; below ~560px the two
   columns are unusable side by side, so each row becomes a stacked block. */
@media screen and (max-width: 560px) {
  .lms-quiz-match-table,
  .lms-quiz-match-table tbody,
  .lms-quiz-match-table tr,
  .lms-quiz-match-table td {
    display: block;
    width: 100%;
  }

  .lms-quiz-match-table tr {
    margin-bottom: 16px;
  }

  .lms-quiz-match-table select {
    width: 100%;
  }
}


/* ---------------------------------------------------------------------------
   8. Reduced motion

   Only lms.css honoured this. AOS scroll animations, the Slick carousels and
   the homepage marquee all animate regardless, which is a WCAG 2.3.3 failure
   and a genuine problem for users with vestibular disorders. The marquee
   element cannot be stopped from CSS — site-init.js handles that half.
   ------------------------------------------------------------------------ */

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

  html {
    scroll-behavior: auto !important;
  }

  /* AOS hides elements with opacity/transform until it animates them in. With
     animation disabled they must be shown outright or the page reads blank. */
  [data-aos] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}


/* ---------------------------------------------------------------------------
   9. Small-phone layout (<= 360px)

   The narrowest supported target is 320px (iPhone SE). The legacy sheets stop
   at a 480px breakpoint, so headings and the logo strip were still sized for a
   larger phone below that.
   ------------------------------------------------------------------------ */

/* Fluid hero headings. lms.css pins .lms-home-hero .lms-page-title at a flat
   2.4rem (38px), which on a 375px phone renders a long name — a course title
   or a username — as four stacked lines. clamp() keeps the desktop size and
   scales down only where there is no room. Declared outside the 480px block
   because the hero is oversized across the whole phone/tablet range. */
@media screen and (max-width: 900px) {
  .lms-home-hero .lms-page-title {
    font-size: clamp(1.6rem, 6.4vw, 2.4rem);
    line-height: 1.2;
  }

  .lms-home-hero .lms-hero-subtitle {
    font-size: clamp(1rem, 3.6vw, 1.1rem);
  }

  /* lms.css centres the hero's text at this breakpoint, but .lms-hero-actions
     is a flex row, so text-align never reached the buttons and they stayed
     left while everything above them was centred. */
  .lms-home-hero .lms-hero-actions {
    justify-content: center;
  }
}

@media screen and (max-width: 480px) {
  /* Fluid headings: stops long migrated Drupal titles from taking six lines
     on a 320px screen while keeping the desktop scale untouched. */
  h1,
  .page-title h1,
  .lms-landing-title {
    font-size: clamp(1.45rem, 6.2vw, 2rem);
    line-height: 1.25;
  }

  h2 {
    font-size: clamp(1.25rem, 5.2vw, 1.6rem);
    line-height: 1.3;
  }

  h3 {
    font-size: clamp(1.1rem, 4.6vw, 1.35rem);
    line-height: 1.35;
  }

  /* Breadcrumbs on the LMS player wrap to three lines and eat 100px of a
     780px-tall screen. Truncating the middle crumbs keeps it to one or two. */
  .breadcrumb {
    row-gap: 2px;
  }

  /* Side gutters stay consistent at the narrowest width. */
  .container,
  .container-fluid {
    padding-left: 16px;
    padding-right: 16px;
  }
}

/* Header at <=359px (Galaxy Fold folded, 320px iPhone SE 1st gen / zoomed
   views): search + theme + language + Login/Logout pill + hamburger sum to
   ~288px with style.css's fixed spacing, pushing the hamburger off-screen.
   The account pill turns icon-only (label kept for screen readers) and the
   gaps tighten; "लॉग आउट" is the widest label, so hiding it is what makes
   this hold in every language and login state. */
@media screen and (max-width: 359.98px) {
  .right-side-header > a.full-search { margin-right: 8px; }
  .right-side-header .language-selector { margin-right: 6px; }
  .lms-header-account { padding-left: 8px; }
  .lms-account-toggle {
    width: 36px;
    height: 36px;
    padding: 0;
    justify-content: center;
  }
  .lms-account-toggle .lms-account-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
}

/* Two fixed-width legacy pieces that only overflow below ~300px:
   - style.css gives the event card's date strip `min-width: 250px`, and the
     card's own margin makes it 10px wider than a 250px card (Galaxy Fold).
   - The dashboard pages put a heading and a 116px "Click Here" button in a
     non-wrapping `d-flex justify-content-between` row; let it wrap instead. */
.events-details .time-duration {
  min-width: min(250px, calc(100% - 20px));
  max-width: calc(100% - 20px);
  flex-wrap: wrap;
  row-gap: 4px;
}

.about-hpc.d-flex {
  flex-wrap: wrap;
  column-gap: 12px;
}

/* Buttons that sit side by side become full-width stacked blocks rather than
   two cramped 140px pills. */
@media screen and (max-width: 420px) {
  .lms-landing-actions .btn,
  .lms-landing-actions .lms-btn,
  .form-actions .btn {
    width: 100%;
  }
}


/* ---------------------------------------------------------------------------
   10. Landscape phones

   A phone in landscape is ~380px tall. Full-height heroes and the fixed mobile
   nav both need to shrink or they leave no room for content.
   ------------------------------------------------------------------------ */

@media screen and (max-height: 480px) and (orientation: landscape) {
  .banner-section,
  .inner-banner,
  .lms-home-hero {
    min-height: 0;
    padding-top: 24px;
    padding-bottom: 24px;
  }

  .navigation-bar {
    /* dvh tracks the collapsing iOS toolbar; the vh line is the fallback. */
    height: calc(100vh - 40px);
    height: calc(100dvh - 40px);
    padding-bottom: env(safe-area-inset-bottom);
  }
}


/* ---------------------------------------------------------------------------
   11. Fixed-position chrome vs. the iPhone home indicator
   ------------------------------------------------------------------------ */

.scroll-to-top {
  bottom: calc(20px + env(safe-area-inset-bottom));
  right: calc(20px + env(safe-area-inset-right));
}

.navigation-bar {
  padding-bottom: env(safe-area-inset-bottom);
}

/* The lightbox and the full-screen spinner cover the whole viewport; on iOS
   `100%` of a fixed element is the large viewport, which hides the bottom of
   the image behind the toolbar. */
@supports (height: 100dvh) {
  #colorbox,
  #cboxWrapper,
  .spinner-fixed {
    height: 100dvh !important;
  }
}


/* ---------------------------------------------------------------------------
   12. Tap targets missed by section 5

   A second measurement pass (Playwright, 320-1440px, Chromium/WebKit/Gecko)
   found the controls below still under the WCAG 2.2 (2.5.8) 24x24 minimum on
   touch. Section 5 missed them because each is sized by a rule that wins on
   specificity, or is clipped by an ancestor so its measured box is not its
   real hit area. Every fix here keeps the visible design identical and
   enlarges only what the finger has to reach.
   ------------------------------------------------------------------------ */

@media (pointer: coarse) {
  /* Header search trigger. style.css:201 pins it to a 22px circle; the icon
     buttons beside it are 40px. Rather than grow the circle (which would
     change the header's look), lay a transparent 40px hit area over it. The
     siblings carry a 10px margin, so a 40px box centred on a 22px element
     extends 9px each side and cannot overlap the next target. */
  .right-side-header > a.full-search {
    position: relative;
  }

  .right-side-header > a.full-search::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 40px;
    height: 40px;
    transform: translate(-50%, -50%);
  }

  /* Slick pagination dots. slick binds its click handler to the <li>, not the
     <button>, and the <li> is a 10x10 box with `overflow: hidden` — so the
     44px min-height section 5 gives the button is clipped away and never
     reachable. Measured hit target was 10x10.

     The dot itself is the <li> background, so it cannot simply be padded.
     Instead the clip is lifted and a transparent 24px box is centred on each
     dot. Margins go 5px -> 7px so the pitch is 24px and no two hit areas
     overlap, which is what SC 2.5.8's spacing exception requires. The dot
     still paints at 10px (26px when active) because border-radius clips the
     background on its own, without `overflow: hidden`. */
  .slick-dots li {
    position: relative;
    overflow: visible;
    margin: 0 7px;
  }

  .slick-dots li::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 24px;
    height: 24px;
    transform: translate(-50%, -50%);
  }

  /* Homepage news ticker links measure 13px/18px, i.e. 18px tall. Padding is
     used rather than min-height because <marquee> sizes itself to its content
     and a min-height would leave the descenders clipped. */
  .latest-news marquee a {
    padding-block: 4px;
  }

  /* FullCalendar day numbers are 12-21px wide depending on how many digits the
     date has, so single-digit days fail while double-digit ones pass. */
  .fc .fc-daygrid-day-number {
    display: inline-block;
    min-width: 24px;
    text-align: center;
  }

  /* The breadcrumb trail, which 32 templates render, lays its links out inline
     at ~15px tall. SC 2.5.8 exempts a link inside a sentence, but a breadcrumb
     is navigation rather than prose, so the exemption does not apply.
     inline-flex keeps the trail on one line. This is the site's own
     `div.breadcrumb > ul > li > a` markup, not Bootstrap's `.breadcrumb-item`
     component, which the descendant `ul li` here deliberately excludes. */
  .breadcrumb ul li > a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 24px;
    /* Hindi "होम" renders 21px wide — short enough to miss the width half. */
    min-width: 24px;
  }

  /* Links in table cells: 19-21px tall wherever they appear — the forum topic
     list, the board directory (built client-side from /api/boards/) and the
     PRS state list. Vertical padding rather than min-height with inline-flex,
     because an inline-flex anchor will not shrink below its max-content width
     and a long board name would then push its cell wider; inline-block keeps
     the text wrapping exactly as it does now. */
  td > a,
  th > a {
    display: inline-block;
    padding-block: 3px;
    /* Single-digit count links in the PRS district list are 19px wide. */
    min-width: 24px;
    text-align: center;
  }

  /* Share toggle on event/blog detail pages: a bare 12x21 icon. Negative
     margin cancels the padding so the row does not shift. */
  .detailed-shared li.toggle-share-btn > a.dropdown-toggle {
    min-width: 44px;
    min-height: 44px;
    justify-content: center;
    margin: -11px -16px;
  }
}


/* ---------------------------------------------------------------------------
   13. Homepage state picker

   The PARAKH Statistics map is a tablist drawn as SVG geography. It scales
   with its Bootstrap column, so the small states shrink with the viewport —
   Goa and Delhi both measure 6x8px at 360px, Sikkim 8x13px — which no target
   rule can fix, because the targets are the shape of the states.

   templates/home/index.html therefore also renders a <select> over the same
   tabs, and it is styled here rather than in style.css because it exists only
   as a response to viewport size: above the lg breakpoint the map column is
   back to roughly its 530px design width, the states are comfortable targets,
   and the picker is not shown at all.
   ------------------------------------------------------------------------ */

.state-picker {
  display: none;
}

@media screen and (max-width: 991.98px) {
  .state-picker {
    display: block;
    margin: 0 0 20px;
  }

  .state-picker__label {
    display: block;
    margin: 0 0 6px;
    font-size: 14px;
    font-weight: 600;
  }

  .dark-mode .state-picker__label {
    color: #ffffff;
  }

  /* 44px matches the touch height section 5 gives every other control, and
     16px is what stops iOS Safari zooming the page on focus — the same
     reasoning as section 4, restated because .form-select is also sized by
     bootstrap.min.css. */
  .state-picker__select {
    width: 100%;
    max-width: 420px;
    min-height: 44px;
    font-size: 16px;
  }
}


/* ---------------------------------------------------------------------------
   14. Print

   Reports and certificates get printed; the site had no print rules at all.
   ------------------------------------------------------------------------ */

@media print {
  .top-header,
  .navigation-bar,
  .menu-hamburger,
  .scroll-to-top,
  #footer,
  .lms-ticker,
  marquee {
    display: none !important;
  }

  body {
    overflow-x: visible;
    background: #fff;
    color: #000;
  }

  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 11px;
    word-break: break-all;
  }
}
