:root {
  --bg: #eef3fb;
  --text: #16213a;
  --muted: #5c6b8a;
  --accent: #2563eb;
  --accent-2: #38bdf8;
  --accent-dark: #1e3a8a;
  --accent-light: #dbeafe;
  --border: #d9e3f5;
  --card-bg: #ffffff;
  --max-width: 1080px;
  --gradient: linear-gradient(135deg, #1e3a8a 0%, #2563eb 55%, #38bdf8 100%);
  --shadow-accent: 0 10px 28px rgba(37, 99, 235, 0.28);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--text);
  background:
    radial-gradient(650px 380px at 50% -60px, rgba(56, 189, 248, 0.16), transparent 70%),
    var(--bg);
  line-height: 1.6;
  /* Safety net: nothing on the page is meant to ever be wider than the
     viewport, so if something someday is (a long unbroken string, a
     flex/grid child that won't shrink), this stops it from creating a
     sideways-scrolling page instead of just clipping quietly. */
  overflow-x: hidden;
}

.wrap {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 20px;
}

/* Header */
.site-header {
  background: var(--gradient);
  color: #fff;
  box-shadow: 0 2px 12px rgba(30, 58, 138, 0.25);
}
.site-header .wrap {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  padding: 12px 20px;
}
.share-icons {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}
/* Solid white badge with each icon in its own brand color (WhatsApp
   green, Facebook blue, a warm accent for Email) rather than a flat
   white-on-translucent look, so the row reads as more colorful at a
   glance. */
.share-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 6px rgba(15, 23, 42, 0.18);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.share-icon svg {
  width: 22px;
  height: 22px;
  display: block;
}
.share-icon.icon-whatsapp { color: #25d366; }
.share-icon.icon-facebook { color: #1877f2; }
.share-icon.icon-email { color: #ea580c; }
.share-icon:hover {
  transform: translateY(-2px) scale(1.06);
  box-shadow: 0 5px 12px rgba(15, 23, 42, 0.28);
}
.brand {
  color: #fff;
  text-decoration: none;
  font-weight: 700;
  font-size: 1.55rem;
  letter-spacing: -0.01em;
  font-family: "Poppins", "Inter", sans-serif;
}
.tag {
  background: rgba(255,255,255,0.15);
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.8rem;
  letter-spacing: 0.03em;
}

/* Footer */
.site-footer {
  background: var(--gradient);
  color: rgba(255, 255, 255, 0.9);
  padding: 14px 0;
  font-size: 0.82rem;
  margin-top: 20px;
  text-align: center;
}
.site-footer p { margin: 0; }

/* Sixth session (2026-09-21/22), footer discussion: a slim bar of three
   icon-only links (YouTube channel, Amazon storefront, onlinenbt.com test
   portal — markup in base.html) pinned to the bottom of the viewport.
   Hidden by default everywhere; turned on only for body.page-unit. The
   full .site-footer above is untouched and still renders normally at the
   true end of the page on every page. Icon badges reuse the same white-
   circle-plus-brand-colored-glyph look as .share-icon in the header, just
   smaller, so this reads as the same visual language rather than a new
   pattern.
   Eighth session (2026-09-22): originally scoped to mobile widths only
   (max-width:700px), on the assumption that desktop/tablet unit pages
   always fit one screen without scrolling (the chapter list has its own
   636px-tall internal scrollbox there — see .chapter-list below). The
   user's screenshot of /genetics-evolution at a normal laptop window size
   showed that's not actually true whenever a unit also has a book banner
   (cover-photo gallery + Buy button) above the chapter table — that extra
   height can push the whole page past the viewport on desktop too, same
   underlying problem as mobile's long chapter list, just a different
   cause. So the three rules below (previously inside the max-width:700px
   block, right after .unit-page) are now unconditional — body.page-unit,
   not body.page-unit plus a width limit — and the bar and its clearance
   padding show at every width. Tested live via getBoundingClientRect /
   elementFromPoint at 1366x768 (desktop) and 768x1024 (tablet-portrait):
   an 18px gap between the copyright text and the bar at full scroll,
   matching the original mobile tuning, no overlap with the chapter table,
   Buy Now button, or footer text at either width. */
.site-footer-mini {
  display: none;
}
.footer-mini-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 6px rgba(15, 23, 42, 0.18);
}
.footer-mini-icon svg {
  width: 18px;
  height: 18px;
  display: block;
}

.footer-mini-icon.icon-youtube { color: #ff0000; }
.footer-mini-icon.icon-amazon { color: #ff9900; }
.footer-mini-icon.icon-tests { color: var(--accent); }

/* Homepage-only: the same three icons shown inline with the copyright
   text in .site-footer, instead of the pinned bar below (which stays
   unit-page only, untouched). Sized to 21px/10px specifically to match
   the footer <p>'s own rendered line-height (measured live before
   picking these numbers) so this adds zero extra height to the
   homepage's frozen, no-scroll layout — a round number like 24px was
   tested first and did add height, tipping a tightly-fit viewport into
   scrolling. See base.html for the markup and shared footer_*_url vars. */
.footer-home-icons { display: none; }

body.page-home .site-footer .wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
}
body.page-home .footer-home-icons {
  display: flex;
  align-items: center;
  gap: 8px;
}

body.page-home .footer-home-icons .footer-mini-icon {
  width: 21px;
  height: 21px;
  background: transparent;
  box-shadow: none;
}
body.page-home .footer-home-icons .footer-mini-icon svg {
  width: 20px;
  height: 20px;
}
/* The base .icon-tests rule (var(--accent), a blue matching the header's
   accent color) reads fine on its usual white circle backing elsewhere on
   the site, but disappears here once that white circle is removed — the
   footer's own background is the same blue family. Swapped to the site's
   existing "test" green (matches .btn-test on unit pages) rather than
   introducing a new arbitrary color. Scoped to this homepage context only
   — the unit-page pinned bar keeps its original blue-on-white look,
   unchanged. */
body.page-home .footer-home-icons .icon-tests {
  color: #a855f7;
}

body.page-unit .site-footer-mini {

  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  background: var(--gradient);
  padding: 8px 0;
  box-shadow: 0 -2px 10px rgba(15, 23, 42, 0.18);
}
/* Reserves room at the bottom of the page content so the fixed bar above
   never overlaps the last chapter row's "Take Test" button, or (on a
   short unit with no book banner) the chapter table itself. Sized taller
   than the bar itself (36px icon + 8px*2 padding = 52px) for a
   comfortable gap rather than a flush fit. */
body.page-unit .unit-page {
  padding-bottom: 68px;
}
/* The fixed bar above sits over the same bottom ~52px of the viewport no
   matter how far the page has scrolled — including at the very end of the
   document, where the real .site-footer would otherwise land in that
   exact strip and be completely covered by it.
   margin-bottom on .site-footer does NOT fix this — tested directly
   (sixth/seventh session): the browser doesn't count a last-in-flow
   child's trailing margin toward the page's scrollable height when
   body/html's overflow has propagated to the viewport (the case here,
   from the height:100%/overflow-y:auto rule further down), so extra
   margin has zero effect on how far the page can actually scroll.
   padding-bottom does count (it's part of the box itself, not a
   collapsible margin), so that's used instead — replaces .site-footer's
   existing 14px bottom padding with 70px, tested live to leave an 18px
   gap between the copyright text and the fixed bar once scrolled all the
   way down, at mobile, tablet, and (eighth session) desktop widths. */
body.page-unit .site-footer {
  padding-bottom: 70px;
}

.flash {
  background: var(--accent-light);
  border: 1px solid var(--accent);
  color: var(--accent-dark);
  padding: 10px 14px;
  border-radius: 8px;
  margin: 16px 0;
}

/* ---------- Homepage radial diagram ---------- */
.hero {
  text-align: center;
  padding: 18px 20px 0;
}
.hero h1 {
  margin: 0 0 4px;
  font-size: clamp(1.15rem, 2.4vh, 1.6rem);
  color: var(--accent-dark);
  letter-spacing: -0.01em;
  font-family: "Poppins", "Inter", sans-serif;
}
.hero p {
  color: var(--muted);
  margin-top: 0;
  font-size: clamp(0.85rem, 1.6vh, 0.98rem);
}
.pick-twinkle {
  display: inline-block;
  /* Same zoom-in-then-back-to-normal shape as the unit boxes' twinkle, but
     scaled to an 0.8s period — exactly the interval between one unit's
     turn and the next in the chase below — and starting unphased (no
     delay), so this pulses in sync every time any unit takes its turn. */
  animation: subtitleTwinkle 0.8s ease-in-out infinite;
  /* Scaling grows outward from the CENTER by default, so at the peak of
     each pulse the right edge of "Pick" was visually pushing into the
     space and touching the next word ("Picka unit..." instead of "Pick a
     unit...") — confirmed on screen, not just in the CSS. Anchoring
     growth to the right edge instead keeps that edge fixed and lets it
     only grow into the empty margin on the left, so the gap to the next
     word never closes. */
  transform-origin: right center;
}

.hero-decor {
  position: relative;
}

/* Decorative background icons in the side margins either side of the
   diagram — purely visual, hidden from screen readers and hidden
   entirely once the viewport is too narrow to have real margins left. */
.decor-icon {
  position: absolute;
  color: var(--accent-2);
  opacity: 0.16;
  pointer-events: none;
  z-index: 0;
  animation: floatY 7s ease-in-out infinite;
}
.decor-icon svg { width: 100%; height: 100%; display: block; }

.decor-dna { width: 60px; height: 135px; left: 4%; top: 6%; }
.decor-leaf { width: 80px; height: 80px; left: 9%; bottom: 4%; color: var(--accent); animation-delay: 1.2s; }
.decor-molecule { width: 90px; height: 90px; right: 5%; top: 10%; animation-delay: 0.6s; }
.decor-flask { width: 64px; height: 84px; right: 10%; bottom: 6%; color: var(--accent); animation-delay: 1.8s; }

@keyframes floatY {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

@media (max-width: 900px) {
  .decor-icon { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .decor-icon { animation: none; }
}

/* Homepage promo row — up to two small cards ("Shop the complete set" on
   Amazon, "Watch on YouTube") sitting side by side between .hero and
   .diagram-wrap, in the same flex chain that Round 7/8 (see project notes)
   carefully budgeted to fit header + hero + footer + diagram on one screen
   on mobile — so this row keeps its natural (small) size there too rather
   than eating into the diagram's share of the remaining space, see
   flex-shrink:0 in the max-width:900px block below. Wraps to a second line
   on its own if both cards don't fit one row (narrow tablet widths). */
.promo-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: 22px;
}
/* "Shop the complete set" — a single static photo (the 11-book combo pack)
   plus a Buy button, same visual language as the unit pages' Buy Now
   button (.btn-buy) but without the unit pages' scrolling filmstrip, since
   there's only ever one image here. */
.combo-banner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 6px 20px 2px;
}
.combo-banner img {
  width: auto;
  height: 140px;
  max-width: min(90vw, 420px);
  object-fit: contain;
  background: #fff;
  border-radius: 8px;
  border: 1px solid var(--border);
  box-shadow: 0 2px 8px rgba(37, 99, 235, 0.1);
}
.combo-banner .btn-buy { margin-top: 0; }

/* Both cards' buttons are deliberately smaller/quieter than the photo
   above them — the photo (or logo) is the visual draw, the button is just
   the "yes, click here" affordance, not the other way around. Scoped to
   .promo-row specifically (not the base .btn) so the unit pages' own
   .btn-buy/.btn-test sizing is untouched. */
.promo-row .btn {
  font-size: 0.78rem;
  padding: 7px 14px;
}

/* "Watch on YouTube" — the channel's own round logo (already circular on
   its own transparent/white background, straight from YouTube) with a
   small red play-button badge tucked into the corner for a bit of
   YouTube-brand color. Deliberately no text button underneath (removed at
   the user's request — the logo + red play badge already reads as
   "watch on YouTube" on its own, so skipping the label leaves more room
   for the .combo-banner beside it and for the diagram below). Padding is
   shallower than .combo-banner's since there's no button row to leave
   room for; .promo-row's align-items:flex-start lets the two cards sit at
   different heights without looking mismatched. */
.youtube-banner {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 6px 20px;
}
.youtube-logo-link {
  position: relative;
  display: inline-flex;
  line-height: 0;
}
.youtube-logo {
  width: 140px;
  height: 140px;
  border-radius: 50%;
  object-fit: cover;
  background: #fff;
  border: 1px solid var(--border);
  box-shadow: 0 2px 8px rgba(37, 99, 235, 0.1);
}
.youtube-play-badge {
  position: absolute;
  right: 0;
  bottom: 2px;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: #ff0000;
  border: 3px solid #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}
.youtube-play-badge svg { width: 19px; height: 19px; }

.diagram-wrap {
  /* Two columns of unit boxes flanking the center circle (not a full
     circle) — width and height are capped independently, bounded by
     viewport size so the whole thing still fits on one screen without
     scrolling, but wide enough to use most of the page's width rather
     than sitting in a narrow strip in the middle. */
  width: min(760px, 92vw);
  margin: 10px auto 18px;
  padding: 0 12px;
  position: relative;
  z-index: 1;
}

.diagram {
  position: relative;
  width: 100%;
  height: min(560px, 62vh);
  margin: 0 auto;
}

.diagram svg {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
}

.center-circle {
  position: absolute;
  /* Fixed, equal width/height (not a % of the container) so this stays a
     true circle even though .diagram's width and height are no longer
     equal (it's a two-column layout, not a radial one). */
  width: clamp(88px, 20vw, 128px);
  height: clamp(88px, 20vw, 128px);
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: var(--gradient);
  color: #fff;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-weight: 700;
  font-family: "Poppins", "Inter", sans-serif;
  font-size: clamp(0.85rem, 2.6vw, 1.3rem);
  box-shadow:
    var(--shadow-accent),
    0 0 0 7px rgba(37, 99, 235, 0.14),
    0 0 0 14px rgba(56, 189, 248, 0.08);
  padding: 6px;
  letter-spacing: 0.02em;
  z-index: 2;
  animation: popIn 0.5s ease both;
}

.unit-box {
  position: absolute;
  /* Width follows its own text (up to a generous cap) rather than a fixed
     %, so a short name like "Ecology" stays compact while a longer one
     stretches out. Font size is one fixed value for every box; the box
     grows outward (via the translate(-50%) below, its anchor point stays
     put so it expands equally in both directions) to fit the full text
     instead of truncating with an ellipsis. */
  width: max-content;
  min-width: 72px;
  max-width: min(460px, 44vw);
  white-space: nowrap;
  transform: translate(-50%, -50%);
  background: var(--card-bg);
  border: 1.5px solid var(--accent-2);
  color: var(--accent-dark);
  border-radius: 10px;
  text-align: center;
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
  padding: 8px 12px;
  box-shadow: 0 3px 10px rgba(37, 99, 235, 0.12);
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease, color 0.15s ease;
  /* Entrance slide-in once, then an endless "chase" pulse on top of it: all
     10 boxes share the same 8s loop and the same quick zoom-in-then-back
     pulse near the start of it, but each box's copy of that loop is
     phase-shifted by its own delay below (0s, 0.8s, 1.6s, ...) — each
     box's pulse fully finishes (~0.6s) well before the next box's starts
     (0.8s later), so they never overlap: unit 1 completes its pulse, then
     unit 2 starts, and so on through unit 10 before looping back to 1. */
  animation: fadeInSide 0.5s ease both, twinkle 8s ease-in-out infinite;
}
.unit-box:hover {
  background: var(--gradient);
  border-color: transparent;
  color: #fff;
  /* !important beats the running twinkle animation for this property,
     which otherwise wins the cascade over a plain :hover transform and
     would make the hover scale flicker/get overridden mid-pulse. */
  transform: translate(-50%, -50%) scale(1.08) !important;
  box-shadow: var(--shadow-accent);
}

/* Staggered entrance: left column drifts in from the left, right column
   from the right, each box a little later than the one above it. Second
   delay value (comma-separated, matching the animation list above) is the
   chase-pulse phase offset — unit 1 through unit 10, 0.8s apart, in order. */
.unit-box:nth-of-type(1) { animation-delay: 0.05s, 0s;    --enter-x: -24px; }
.unit-box:nth-of-type(2) { animation-delay: 0.1s,  0.8s;  --enter-x: -24px; }
.unit-box:nth-of-type(3) { animation-delay: 0.15s, 1.6s;  --enter-x: -24px; }
.unit-box:nth-of-type(4) { animation-delay: 0.2s,  2.4s;  --enter-x: -24px; }
.unit-box:nth-of-type(5) { animation-delay: 0.25s, 3.2s;  --enter-x: -24px; }
.unit-box:nth-of-type(6) { animation-delay: 0.05s, 4.0s;  --enter-x: 24px; }
.unit-box:nth-of-type(7) { animation-delay: 0.1s,  4.8s;  --enter-x: 24px; }
.unit-box:nth-of-type(8) { animation-delay: 0.15s, 5.6s;  --enter-x: 24px; }
.unit-box:nth-of-type(9) { animation-delay: 0.2s,  6.4s;  --enter-x: 24px; }
.unit-box:nth-of-type(10) { animation-delay: 0.25s, 7.2s; --enter-x: 24px; }

@keyframes fadeInSide {
  from { opacity: 0; transform: translate(-50%, -50%) translateX(var(--enter-x, 0)); }
  to { opacity: 1; transform: translate(-50%, -50%); }
}
/* One quick zoom-in-then-back-to-normal pulse near the start of the 8s
   loop, then flat for the rest of the cycle — combined with each box's
   own delay offset above, this is what makes the effect visibly step from
   box to box, each one finishing its own pulse before the next begins. */
@keyframes twinkle {
  0%   { transform: translate(-50%, -50%) scale(1); }
  4%   { transform: translate(-50%, -50%) scale(1.18); }  /* zoom in — 0.32s in */
  7.5%, 100% { transform: translate(-50%, -50%) scale(1); }  /* back to normal by 0.6s, then hold */
}
/* Same shape as .twinkle above, scaled down to an 0.8s period instead of
   8s — 10x shorter because it repeats every unit's turn rather than once
   per full lap, keeping the same real-world timing (~0.6s to settle). */
@keyframes subtitleTwinkle {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.18); }
  75%, 100% { transform: scale(1); }
}
@keyframes popIn {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.7); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .unit-box, .center-circle, .center-ring, .pick-twinkle { animation: none; }
}

/* Rotating "X days to go" ring around the center circle */
.center-ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(150px, 30vw, 195px);
  height: clamp(150px, 30vw, 195px);
  z-index: 1;
  pointer-events: none;
  animation: ringSpin 23s linear infinite;
}
.center-ring svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}
.ring-text {
  fill: var(--accent-dark);
  font-family: "Poppins", "Inter", sans-serif;
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 0.5px;
}
@keyframes ringSpin {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}

.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;
}

.diagram-arrow {
  /* Solid color rather than a gradient: an objectBoundingBox gradient
     is undefined (and silently fails to paint at all) on a perfectly
     horizontal or vertical path, which is exactly what the mid-row
     connectors are. */
  stroke: var(--accent-dark);
  /* non-scaling-stroke keeps the line a true, consistent thickness in
     real pixels — without it, the viewBox's non-uniform stretch (the
     diagram is much wider than tall) distorts stroke width by angle. */
  vector-effect: non-scaling-stroke;
  stroke-width: 1px;
  stroke-linecap: round;
  stroke-dasharray: 5 4;
  opacity: 0.8;
}

/* Below 900px, the same radial diagram stays on screen — no more plain-
   list fallback — but its geometry shrinks to fit. The earlier list
   fallback existed because the desktop box sizing (nowrap text, a
   460px/44vw max-width) relies on empty page margin OUTSIDE .diagram-wrap
   to absorb overflow, margin that only exists once the viewport is wide
   enough that .diagram-wrap has hit its 760px cap (roughly ≥826px) — below
   that, .diagram-wrap itself IS the viewport (minus its own padding), so
   there's no outside margin left to spill into and long names got clipped
   off-screen (confirmed by testing at 768px). The fix here is to size
   .unit-box as a PERCENTAGE of .diagram's own width instead of raw vw, and
   let text wrap instead of forcing one line: a box centered at the 12%/88%
   x-position can never cross the diagram's edge as long as its max-width
   stays under 2×12% = 24% of that width, at any viewport — 22% is used
   below to keep a small safety margin. Confirmed by testing the live page
   at phone, landscape-phone and tablet widths, not just calculated. */
@media (max-width: 900px) {
  /* Round 7: the footer was getting pushed off-screen (or the last row
     cut off entirely) on shorter phones — confirmed by the user across 8
     real device presets: fine at 924–1024px viewport height, broken at
     852px and below (iPhone 16, Galaxy A55, and worst on the 667px-tall
     iPhone SE, where row 10 didn't render at all without scrolling).
     Earlier rounds tried to guess a single height formula (min(540px,
     74vh) etc.) that would leave enough room for the header/hero/footer
     above and below it — but "enough room" depends on their actual
     rendered height, which isn't a fixed number (it shifts with font
     clamp(), line wrapping, etc.), so any one guessed formula was always
     going to be wrong at *some* viewport height. Fixed properly instead
     of re-guessing: make the diagram a flex item that grows to fill
     whatever space is actually left after the header, hero text, and
     footer take what they need, rather than computing its height from
     the viewport in isolation. body/main/.hero-decor form the flex
     chain down to .diagram-wrap; .hero keeps its natural size
     (flex-shrink: 0) and .diagram-wrap/.diagram take the rest, still
     bounded by min-height/max-height below so it can't collapse to
     nothing or balloon to an awkward size on a very short or very tall
     phone. If a device is so short even the min-height doesn't fit,
     body's own overflow-y: auto is the fallback — an actual scroll
     rather than clipped/hidden content. */
  html, body {
    height: 100%;
  }
  /* Sixth session (2026-09-21), tablet-pass follow-up: scoped body/main
     below to body.page-home (set via the body_class block in base.html,
     only index.html opts in). This whole viewport-fit flex chain —
     shrink main/.diagram down to exactly one screen, with body's own
     overflow-y:auto as a last-resort fallback if even that doesn't fit —
     was built for the homepage, where the diagram is meant to fit on one
     screen without its own scrolling. Until now `body`/`main` below were
     bare, unscoped selectors, so this same shrink-to-one-screen behavior
     silently applied to every OTHER page too, at this same <900px width.
     That's a real bug caught on the unit page via the user's own real
     Chrome DevTools (iPhone 14, 390x844): main got flex-shrunk down to
     one screen's height, but its actual content (a full chapter table)
     is naturally taller than that and kept rendering past main's shrunk
     box (overflow:visible doesn't clip it) — while the footer, a flex
     sibling immediately after main in the column, got positioned right
     after main's *shrunk* box edge, not after where the content actually
     ends. The result: the footer rendered visually stacked in the middle
     of the still-overflowing chapter list instead of below it. Scoping
     this chain to .page-home restores plain block-flow stacking (main
     grows to fit its real content, like any normal element) on every
     other page, so the footer always lands after all of it, regardless
     of how long the page is. This is the same underlying "flex chain
     applies site-wide, not just on the homepage" issue noted after the
     Round 7 fix and in the earlier `.unit-page { width: 100% }` fix —
     that one was about horizontal overflow; this is the vertical/footer-
     position counterpart, on the same three selectors. */
  body.page-home {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh; /* dvh accounts for mobile browser chrome; vh above is the fallback for browsers that don't support dvh */
    overflow-y: auto;
  }
  .page-home main {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0; /* without this a flex child won't shrink below its content size, which defeats the whole point here */
  }
  .hero-decor {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }
  .hero {
    flex-shrink: 0; /* the title/subtitle keep their natural size — only the diagram gives up space */
  }
  .promo-row {
    flex-shrink: 0; /* same reasoning as .hero above — only the diagram gives up space */
    gap: 10px 22px;
  }
  .combo-banner {
    padding: 4px 16px 0;
    gap: 6px;
  }
  .youtube-banner {
    padding: 4px 16px;
  }
  .combo-banner img,
  .youtube-logo {
    height: 64px; /* smaller than desktop's 140px — this row competes with the diagram for the same tight mobile vertical budget */
  }
  .youtube-logo { width: 64px; }
  .youtube-play-badge {
    width: 22px;
    height: 22px;
  }
  .youtube-play-badge svg { width: 11px; height: 11px; }
  /* On a short phone (iPhone SE and similar, ~700px tall or less) even the
     diagram's own 320px floor was already close to the limit before this
     row existed (see the Round 7/8 notes above) — adding ~130px more
     content here pushed that device into overflow. The Amazon card still
     has a text button as a fallback, so its photo can just be dropped
     entirely on short screens (as before) — but the YouTube card has no
     button any more (removed at the user's request, see .youtube-banner
     above), so its logo is the *only* thing there is to click: hiding it
     the same way would leave that card completely empty. Shrink it down
     small instead of hiding it, and shrink its badge to match. */
  @media (max-height: 700px) {
    .combo-banner img {
      display: none;
    }
    .combo-banner {
      padding: 2px 10px 0;
    }
    .youtube-banner {
      padding: 2px 10px;
    }
    .youtube-logo {
      width: 40px;
      height: 40px;
    }
    .youtube-play-badge {
      width: 15px;
      height: 15px;
      border-width: 2px;
      right: -1px;
      bottom: -1px;
    }
    .youtube-play-badge svg { width: 7px; height: 7px; }
    .promo-row {
      gap: 6px 14px;
      align-items: center;
    }
    .promo-row .btn {
      font-size: 0.74rem;
      padding: 6px 11px;
      margin-top: 0;
    }
  }
  .diagram-wrap {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    margin: 6px auto 8px; /* tightened from 10px/18px — every bit of margin here is space the diagram doesn't get */
  }
  .diagram {
    flex: 1 1 auto;
    min-height: 320px; /* below this the diagram itself would look cramped/illegible — body's overflow-y: auto takes over instead */
    max-height: 540px; /* keeps it from ballooning on a phone with an unusually tall viewport relative to its width */
    height: auto;
  }
  .unit-box {
    /* width: max-content — grows to fit its own text (a short name like
       "Ecology" stays compact) — clamped between min-width (a floor) and
       max-width (a ceiling), both 35%: the column position (see
       unit_positions() in app/__init__.py) was pulled in from 12%/88% to
       16%/84% specifically to raise this ceiling — a box centered at x%
       can safely be up to ~2x% of the diagram's own width before its edge
       reaches the true viewport edge, so 16% raises the safe ceiling from
       ~26% to ~38% at the naive 2x calculation; 35% is what's actually
       used, leaving a real safety margin (20px+) confirmed empirically
       rather than chasing the naive ceiling exactly. That's enough for
       "6. Reproduction" and "9. Biotechnology" — the two names that used
       to character-split ("Reproductio/n", "Biotechnolo/gy") at the old
       25% ceiling — to render on one line with no wrap at all, confirmed
       via getBoundingClientRect at every phone width in the 360-440px
       range (all clear of the viewport edge with 20px+ to spare) and
       re-checked at 768px and 1280px (unaffected there — the position
       change shortens the connecting line's arm a little at every width,
       but this min/max-width pair only governs sizing below 900px; the
       desktop rule below is untouched). min-width is wrapped in the same
       min(..., 35%) so it can never win a conflict with max-width and
       force the box past it — a mismatch there was the cause of a real
       mobile overflow bug fixed earlier (see project notes), so this stays
       paired on purpose. word-break: normal + overflow-wrap: break-word
       remain as a wrap strategy (number on its own line, name below) and
       character-split last resort respectively, for any future unit name
       too long to fit even alone on its own line at 35%. */
    width: max-content;
    min-width: min(calc(16ch + 20px), 35%);
    max-width: 35%;
    white-space: normal;
    overflow-wrap: break-word;
    word-break: normal;
    line-height: 1.25;
    font-size: clamp(0.62rem, 2.6vw, 0.8rem);
    padding: 6px 8px;
  }
  .center-circle {
    width: clamp(72px, 18vw, 110px);
    height: clamp(72px, 18vw, 110px);
    font-size: clamp(0.72rem, 2.4vw, 1.05rem);
  }
  .center-ring {
    width: clamp(120px, 27vw, 168px);
    height: clamp(120px, 27vw, 168px);
  }
}

/* Tablet-portrait fix (2026-09-21): the mobile block above shares one
   `.diagram { max-height: 540px }` cap across every width from phones up
   to 900px, tuned specifically against the tallest *phones* tested
   (Round 2/7 — up to ~956px viewport height, see notes above). A tablet
   like an iPad (768x1024) or iPad Air (820x1180) falls in the same
   <=900px width bracket but has much more vertical room than any phone
   — `.diagram-wrap` (flex: 1 1 auto in the Round 7 flex chain) correctly
   grows to fill that space, but `.diagram` itself stayed capped at
   540px, leaving a large empty gap between the diagram and the footer
   (confirmed live: ~233px of dead space at 768x1024). Scoped narrowly to
   `min-width: 600px` — comfortably above the widest phone tested (440px)
   and below the first real tablet width (768px) — so this can never
   affect the phone sizing above; confirmed live at 360-440px that
   `.diagram`'s computed max-height is still exactly 540px, untouched.
   Removing the cap here lets `.diagram` grow to fill whatever height the
   flex chain actually gives it (still bounded by that chain, so it can't
   overflow), closing the gap cleanly at 768x1024 and 820x1180 — confirmed
   live, zero leftover empty space, footer sits right after the last row
   at both sizes, and box/label sizing (still governed by the mobile
   .unit-box rule above) is unaffected. A tablet-width landscape or a
   desktop-breakpoint tablet (e.g. iPad Pro portrait, 1024px wide — over
   this file's 900px cutoff) is governed by the separate desktop `.diagram`
   rule instead, not this one; that desktop rule has its own similar gap
   on tall/narrow desktop-breakpoint viewports but is left untouched here
   since the desktop layout is explicitly frozen — see project notes. */
@media (min-width: 600px) and (max-width: 900px) {
  .diagram {
    max-height: none;
  }
}

/* ---------- Unit detail page ---------- */
.unit-page {
  max-width: 1080px;
  margin: 0 auto;
  /* .unit-page is a flex child of <main> (display:flex; flex-direction:
     column). Without an explicit width, margin:0 auto (auto cross-axis
     margins) disables flexbox's default align-items:stretch, so this
     element falls back to its own content-driven preferred size instead
     of shrinking to main's actual width — on narrow phones this let the
     chapter table/book gallery force the page to ~1080px wide and
     overflow horizontally. width:100% gives it a definite width to
     stretch/shrink with, still capped and centered by max-width/margin
     above on wider screens. */
  width: 100%;
  /* Kept fairly tight top/bottom (rather than the more generous padding
     other pages use) so a 4-chapter unit — the assumed common case, see
     .chapter-list below — has the best shot at fitting a desktop screen
     without the page itself needing to scroll. */
  padding: 18px 20px 20px;
}
/* Sized (and capped) to sit just under the "Neela Bakore Tutorials" brand
   in the header (1.55rem / ~24.8px, see .brand) rather than above it — a
   page heading outsizing the site's own name read oddly top-heavy. */
.unit-page h1 {
  margin: 0 0 10px;
  text-align: center;
  font-size: clamp(1.1rem, 1.8vw, 1.45rem);
  font-family: "Poppins", "Inter", sans-serif;
  color: var(--accent-dark);
}
.unit-nav {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  font-size: 1rem;
  font-weight: 600;
}
.unit-nav a {
  color: var(--accent-dark);
  text-decoration: none;
  /* Each link is a flex item; without this, a long unit name's automatic
     content-based minimum width can force the item (and the page) wider
     than the viewport instead of wrapping — the same underlying issue as
     the min-width:0 on .book-banner-gallery above, just for text instead
     of a fixed-width image strip. overflow-wrap lets a genuinely long
     single word break rather than overflow as a last resort. */
  min-width: 0;
  overflow-wrap: break-word;
}
.unit-nav a:hover { text-decoration: underline; }
.unit-nav-arrow {
  font-weight: 800;
  font-size: 2em;
  line-height: 1;
  vertical-align: middle;
  display: inline-block;
  position: relative;
  top: -0.05em;
}
/* .unit-nav-short ("Unit 3") is the mobile-only stand-in for
   .unit-nav-full (the full "3. Cell: Structure and Functions" name,
   from unit.display_name) — see the @media block below. Hidden by
   default so desktop/tablet keep showing the full name, unchanged. */
.unit-nav-short {
  display: none;
}
@media (max-width: 700px) {
  /* The desktop clamp() bottoms out quite small on a narrow screen (it
     was tuned to sit just under the header brand at desktop widths) —
     mobile gets its own fixed size instead of inheriting that floor. */
  .unit-page h1 { font-size: 1.3rem; }
  /* Sixth session (2026-09-21), tablet-pass follow-up: this used to
     stack the two nav links into two full-width lines instead of
     keeping them side by side, because a long full-name pair (e.g. "1.
     Diversity in Living World" / "3. Cell: Structure and Functions")
     doesn't comfortably fit two-up in one row this narrow. Per the
     user's request, mobile now swaps to the short "Unit N" label
     instead (.unit-nav-short here, .unit-nav-full hidden) — short
     enough that both links fit on a single row again, so the row goes
     back to the same left/right-leaning layout as desktop
     (justify-content: space-between on .unit-nav, unchanged) rather
     than stacking. */
  .unit-nav-full { display: none; }
  .unit-nav-short { display: inline; }
}

.empty-note {
  color: var(--muted);
  font-style: italic;
}

/* One row per chapter — Chapter name / YouTube Videos / Free Practice
   Tests side by side across a single shared header, rather than separate
   Video Lectures and Practice Tests columns each repeating the chapter
   name. Keeps a chapter's video and test visually paired, and the name
   only appears once. */
.chapter-table {
  margin-top: 0;
}
.chapter-table-head {
  display: grid;
  grid-template-columns: 1fr 1.6fr 1fr;
  gap: 16px;
  padding: 0 18px 4px;
  font-family: "Poppins", "Inter", sans-serif;
  font-size: 0.95rem;
  color: var(--accent-dark);
  border-bottom: 2px solid var(--accent-light);
  margin-bottom: 8px;
}
/* All three headers centered over their columns (the Video/Test columns'
   content is already centered; Chapter is centered by request even though
   .chapter-row-name below stays left-aligned — chapter titles vary too
   much in length to center neatly row to row). */
.chapter-table-head span {
  text-align: center;
}
/* Chapter rows scroll as a unit once there are more than ~4 of them, so
   the rest of the unit page (nav, book banner, this header) stays put on
   a desktop screen instead of growing indefinitely — see the template
   comment above .chapter-list. The height below is sized to roughly 4
   rows at the current thumbnail/padding proportions. */
.chapter-list {
  max-height: 636px;
  overflow-y: auto;
  padding-right: 4px;
}
.chapter-row {
  display: grid;
  grid-template-columns: 1fr 1.6fr 1fr;
  gap: 16px;
  align-items: center;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 18px;
  margin-bottom: 8px;
  box-shadow: 0 4px 16px rgba(37, 99, 235, 0.06);
}
.chapter-row-name {
  font-weight: 600;
  font-size: 0.98rem;
}
.chapter-row-video, .chapter-row-test {
  display: flex;
  justify-content: center;
  text-align: center;
}
@media (max-width: 700px) {
  .chapter-table-head { display: none; }
  .chapter-list { max-height: none; overflow: visible; padding-right: 0; }
  .chapter-row {
    grid-template-columns: 1fr;
    text-align: center;
    gap: 10px;
  }
  .chapter-row-name { font-size: 1.02rem; }
}
.chapter-thumb {
  display: block;
  width: 100%;
  max-width: 220px;
  height: auto;
  border-radius: 8px;
  margin: 0 auto 10px;
}
/* Thumbnail-as-the-whole-link case (chapter has both a thumbnail and a
   playlist URL) — no separate title/button below it, just a play icon
   centered on the image itself. */
.chapter-thumb-link {
  display: block;
  position: relative;
  width: max-content;
  max-width: 100%;
  margin: 0 auto;
  text-decoration: none;
}
.chapter-thumb-link .chapter-thumb {
  margin-bottom: 0;
}
.play-badge {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: rgba(15, 23, 42, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  padding-left: 3px;
  transition: background 0.15s ease, transform 0.15s ease;
}
.chapter-thumb-link:hover .play-badge {
  background: var(--accent);
  transform: translate(-50%, -50%) scale(1.08);
}
/* Recommended Book banner — full width. The gallery is a filmstrip of
   several cover/sample-page photos (about 4-5 visible at once on desktop)
   scrolling continuously left; the Buy button sits in its own row below
   the images rather than beside them. */
.book-banner {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 24px;
  margin-bottom: 10px;
  box-shadow: 0 4px 16px rgba(37, 99, 235, 0.06);
  /* A flex child's default min-width is auto, which (depending on the
     engine) can let a very wide descendant — here, .book-banner-track,
     which is several times the page's width by design — force this
     column, and the whole page, wider than the viewport even though
     .book-banner-gallery below clips it visually with overflow:hidden.
     min-width:0 removes that content-based floor so the explicit 100%
     width actually wins. */
  min-width: 0;
}
.book-banner-gallery {
  position: relative;
  width: 100%;
  min-width: 0;
  height: 150px;
  overflow: hidden;
  /* Fades the strip's edges instead of hard-cropping a photo mid-image. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%);
}
.book-banner-track {
  display: flex;
  align-items: stretch;
  gap: 16px;
  height: 100%;
  width: max-content;
}
/* Only a gallery with more than one image (.is-scrolling, set in the
   template) actually scrolls — the track's duplicated second copy of the
   images (also template-side) is what lets translating exactly -50% loop
   seamlessly back to the start. animation-duration is set inline per unit,
   scaled to its image count, so the per-image scroll speed stays constant
   regardless of how many photos a unit has. */
.book-banner-gallery.is-scrolling .book-banner-track {
  animation-name: book-banner-scroll;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
/* Sixth session (2026-09-21), unit-page mobile testing: the hover-to-pause
   rule below used to apply unconditionally at every width, with no
   min-width guard. That's fine on desktop, which has no pause/play button
   and relies on hover alone — but on mobile, where the explicit
   .gallery-pause-toggle button (above) exists specifically because touch
   has no reliable hover, the two controls fought each other: clicking
   "play" correctly removed .is-paused (confirmed via the button's own
   aria-pressed state), but the pointer was still resting over the gallery
   right after that click (it's positioned inside the gallery), so this
   unconditional :hover rule kept matching and silently re-paused the
   animation anyway — the reported "paused, but doesn't play again" bug.
   Scoping the hover rule to the same >700px range where the button itself
   is hidden fixes this: desktop/tablet keep hover-to-pause exactly as
   before, and mobile relies solely on the explicit button with nothing
   else able to override its resume. */
@media (min-width: 701px) {
  .book-banner-gallery.is-scrolling:hover .book-banner-track {
    animation-play-state: paused;
  }
}
.book-banner-gallery.is-scrolling.is-paused .book-banner-track {
  animation-play-state: paused;
}
@keyframes book-banner-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .book-banner-gallery.is-scrolling .book-banner-track { animation: none; }
}
/* Pause/play button for the filmstrip — desktop already has hover-to-pause
   (see :hover above), so this is hidden there and only shown on mobile
   (max-width:700px below), where there's no hover. site.js toggles
   .is-paused on the gallery when it's clicked; the two icons are both
   always in the DOM and CSS swaps which one shows. */
.gallery-pause-toggle {
  display: none;
  position: absolute;
  right: 8px;
  bottom: 8px;
  z-index: 2;
  width: 30px;
  height: 30px;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(15, 23, 42, 0.55);
  color: #fff;
  padding: 0;
  cursor: pointer;
}
.gallery-pause-toggle svg { width: 14px; height: 14px; display: block; }
.gallery-pause-toggle .icon-play { display: none; }
.gallery-pause-toggle.is-paused .icon-pause { display: none; }
.gallery-pause-toggle.is-paused .icon-play { display: block; }
.book-banner-track > a,
.book-banner-track > img {
  display: block;
  flex-shrink: 0;
  height: 100%;
  width: 122px;
}
.book-banner-track img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #fff;
  border-radius: 8px;
  border: 1px solid var(--border);
  box-shadow: 0 2px 8px rgba(37, 99, 235, 0.1);
}
.book-banner-info {
  display: flex;
  align-items: center;
  justify-content: center;
}
.book-banner-info .btn-buy { margin-top: 0; }
@media (max-width: 700px) {
  .book-banner-gallery { height: 170px; }
  .book-banner-track > a,
  .book-banner-track > img { width: 130px; }
  .gallery-pause-toggle { display: flex; }
}
.btn {
  display: inline-block;
  background: var(--gradient);
  color: #fff;
  text-decoration: none;
  padding: 9px 18px;
  border-radius: 8px;
  font-weight: 600;
  font-size: 0.9rem;
  margin-top: 8px;
  box-shadow: 0 4px 12px rgba(37, 99, 235, 0.25);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(37, 99, 235, 0.35);
}
/* Amazon orange for Buy Now, green for Take Test — distinct from the
   default blue "Watch Playlist" button so the three columns stay visually
   distinguishable at a glance. */
.btn-buy {
  background: linear-gradient(135deg, #f59e0b 0%, #ea580c 100%);
  box-shadow: 0 4px 12px rgba(234, 88, 12, 0.28);
}
.btn-buy:hover { box-shadow: 0 6px 16px rgba(234, 88, 12, 0.38); }
.btn-test {
  background: linear-gradient(135deg, #16a34a 0%, #15803d 100%);
  box-shadow: 0 4px 12px rgba(21, 128, 61, 0.28);
}
.btn-test:hover { box-shadow: 0 6px 16px rgba(21, 128, 61, 0.38); }

/* ---------- Admin ---------- */
.admin-shell {
  display: flex;
  align-items: flex-start;
  max-width: 1080px;
  margin: 0 auto;
  padding: 24px 20px 60px;
  gap: 28px;
}

.admin-sidebar {
  width: 260px;
  flex-shrink: 0;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  position: sticky;
  top: 20px;
}

.sidebar-section {
  margin-bottom: 18px;
}
.sidebar-section:last-child { margin-bottom: 0; }

.sidebar-heading {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.72rem;
  color: var(--muted);
  font-weight: 700;
  margin-bottom: 6px;
  padding: 0 4px;
}

.sidebar-toplink {
  display: block;
  padding: 7px 8px;
  border-radius: 6px;
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.92rem;
}
.sidebar-toplink:hover { background: var(--accent-light); }
.sidebar-toplink.active { background: var(--accent); color: #fff; }

.unit-tree {
  margin-bottom: 2px;
}
.unit-tree summary {
  cursor: pointer;
  padding: 7px 8px;
  border-radius: 6px;
  font-size: 0.88rem;
  font-weight: 600;
  list-style: none;
}
.unit-tree summary::-webkit-details-marker { display: none; }
.unit-tree summary:hover { background: var(--accent-light); }
.unit-tree[open] summary { color: var(--accent-dark); }

.unit-tree ul {
  list-style: none;
  margin: 2px 0 8px;
  padding-left: 18px;
  border-left: 2px solid var(--border);
}
.unit-tree li { margin: 1px 0; }
.unit-tree a {
  display: block;
  padding: 6px 8px;
  border-radius: 6px;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.85rem;
}
.unit-tree a:hover { background: var(--accent-light); color: var(--accent-dark); }
.unit-tree a.active { background: var(--accent); color: #fff; }
.unit-tree a.add-link {
  color: var(--accent);
  font-style: italic;
}
.unit-tree a.add-link:hover { text-decoration: underline; }

.admin-main {
  flex: 1;
  min-width: 0;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 24px 28px 32px;
}
.admin-main h1 { margin-top: 0; }

@media (max-width: 760px) {
  .admin-shell { flex-direction: column; }
  .admin-sidebar { width: 100%; position: static; }
}

.admin-wrap {
  max-width: 700px;
  margin: 0 auto;
  padding: 40px 20px 60px;
}
.admin-form label {
  display: block;
  font-weight: 600;
  margin: 16px 0 4px;
  font-size: 0.9rem;
}
.admin-form input[type="text"],
.admin-form input[type="url"],
.admin-form textarea {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.95rem;
  font-family: inherit;
}
.admin-form textarea { min-height: 70px; }
.admin-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 16px;
}
.admin-table th, .admin-table td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 0.92rem;
}
.admin-table a { color: var(--accent-dark); font-weight: 600; text-decoration: none; }
.admin-table a:hover { text-decoration: underline; }
.login-card {
  max-width: 360px;
  margin: 60px auto;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 28px 26px;
}
.login-card h1 { font-size: 1.2rem; margin-top: 0; }
