/* ==========================================================================
   Flash-of-wrong-content guard
   ==========================================================================
   The scripts sit at the bottom of the body, so the browser paints the markup
   first and only then does shell.js correct it. That was visible: the POC
   credentials box appeared and vanished, the dev banner said "stub backend"
   before being rewritten, and Log in flashed before turning into My dashboard.

   Anything whose correctness depends on knowing who is signed in, or which
   backend is live, stays hidden until shell.js says so. Appearing a beat late
   is far less jarring than appearing wrong and being corrected - and on the
   login page it was showing credentials that do not work.
   ========================================================================== */
html:not(.shell-ready) [data-auth],
html:not(.shell-ready) [data-backend],
html:not(.shell-ready) .dev-banner { display: none !important; }

/* Demo mode for screen recordings: localStorage 'edu.demoMode' hides the dev
   banner so marketing captures do not carry "not deployed" in every frame.
   shell.js sets the class; nothing else about the app changes. */
html.demo-mode .dev-banner { display: none !important; }

/* Same guard, one level deeper. The rules above only cover elements the SHELL
   owns; login.html additionally has to choose between a sign-in and a sign-up
   form, and that choice is not the shell's to make. Someone arriving from
   checkout with ?invite= was shown the login form for the length of two network
   calls before it was replaced by the account-setup form they actually wanted.
   The head script sets this class straight off the URL; the page script clears
   it once it knows which pane is right. */
html.invite-pending #signinPane,
html.invite-pending #signupPane { display: none !important; }
html.invite-pending #invitePending { display: block !important; }

/* ==========================================================================
   Elusive Data Academy - shared shell
   Brand tokens carried over from the marketing site (verified 2026-07-24).
   ========================================================================== */

:root {
  --panel-dark: #1B293B;
  --panel-dark-2: #16222F;
  --footer-bottom: #253950;
  --body-bg: #101923;
  --accent: #2B64F8;
  --accent-hover: #1C4FD6;
  --accent-soft: #E8F2FF;
  /* Lifted variants for dark surfaces. The brand palette is tuned for white
     backgrounds; on navy, --accent and --warn both sink into the panel. Any
     blue or red placed as TEXT or a BORDER on a dark surface uses these. */
  --link-blue: #439CF1;   /* links / interactive on dark */
  --cyan: #66DEF9;        /* wave cyan, the style guide's highlight accent */
  --warn-bright: #FF6B7D; /* brand red lifted so it reads on navy */
  --yellow: #FFDE59;
  --ink: #101828;
  --text-dark: #333B49;
  --text-mid: #435D78;
  --line: #E1E9F2;
  --ok: #2E7D32;
  --ok-soft: #E8F5E9;
  --warn: #EE3D53;
  --orange: #F37934;   /* LEGACY. Firefly's action colour, not the site's. Do
                          not introduce it into course content; it is kept only
                          for surfaces that still have to match the desktop
                          suite until that moves to blue. */

  /* ---- The blue wave ----
     Four stops, used at 135deg on hero panels and 90deg on table headers.
     Stop D was #2F78ED. Measured, white small text on that stop is 4.21:1,
     under the 4.5 AA floor, and every table header in the course is white
     small text sitting on it. #2A6FE2 is the triad's own mid-blue and
     measures 4.74:1, so the wave keeps its identity and passes. */
  --wave-a: #00128D;
  --wave-b: #0635CB;
  --wave-c: #1046DC;
  --wave-d: #2A6FE2;

  /* ---- Lifted variants, continuing the --link-blue / --warn-bright idea ----
     --text-mid is tuned for white and measures 3.85:1 on a navy panel, so
     muted text on dark needs its own value: this one is 8.3:1. --ok is
     likewise too dark to read on navy; --ok-bright is 11.8:1 there.
     CYAN IS NOT A GENERAL-PURPOSE ACCENT: on navy it is 10.3:1, but on
     wave stop D it is 2.66:1. Cyan belongs on dark surfaces only. */
  --text-mid-dark: #9DB9E8;
  /* --accent as TEXT on --accent-soft is 4.34:1, just under the floor at
     normal weight. Wave stop C is the same blue family and measures 6.6:1,
     so callout headings use this and the border keeps --accent.

     PORTING A BLOCK, READ THIS. The course blocks were written with #1046DC
     as their heading and accent-text colour, and --accent-ink IS #1046DC, so
     for any FOREGROUND use the swap is the identity. Mapping #1046DC to
     --accent instead lightens the text to #2B64F8 and puts it under the AA
     floor wherever it sits on --accent-soft; that mistake has been made on
     four pages so far. Backgrounds and borders take --accent, text takes
     --accent-ink. */
  --accent-ink: #1046DC;
  --ok-bright: #7DE8A0;

  /* ---- Caution, which is not an error ----
     Red is reserved for things that broke or cannot be undone. A caution that
     shouts in red teaches the reader to ignore red. Ink on bg is 5.8:1. */
  --caution-bg: #FFF8E1;
  --caution-line: #F0E0A0;
  --caution-ink: #7A5C00;

  /* A border tint for a card under the pointer. --accent itself is far too
     strong for a 1px edge and reads as a selection. */
  --line-accent: #C3D7FA;

  --font-head: "Be Vietnam Pro", sans-serif;
  --font-body: "Inter", sans-serif;
  --font-label: "Saira", sans-serif;
  --radius: 6px;

  /* Single source of truth for the sticky header height. The rail and the
     player toolbar offset themselves against this, so it must never be
     hardcoded in more than one place. */
  --header-h: 74px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.65;
  color: var(--text-dark);
  background: #fff;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; height: auto; display: block; }
a { color: var(--accent); text-decoration: none; }
h1, h2, h3, h4 { font-family: var(--font-head); font-weight: 600; line-height: 1.15; letter-spacing: -0.03em; }

.container { max-width: 1140px; margin: 0 auto; padding: 0 24px; }

/* ---------------- Buttons ---------------- */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-label); font-size: 14px; font-weight: 500;
  padding: 13px 22px; border-radius: var(--radius);
  border: 2px solid transparent; cursor: pointer;
  transition: background .2s, color .2s, border-color .2s;
}
.btn-primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-outline-light { background: transparent; color: #fff; border-color: #fff; }
.btn-outline-light:hover { background: #fff; color: var(--panel-dark); }
.btn-outline-blue { background: transparent; color: var(--accent); border-width: 1px; border-color: var(--accent); }
.btn-outline-blue:hover { background: var(--accent); color: #fff; }
.btn-ghost { background: transparent; color: var(--text-mid); border-color: var(--line); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn:disabled { opacity: .45; cursor: not-allowed; }

/* ---------------- Header ----------------
   The sticky lives on the #shell-header WRAPPER, not on .site-header.
   A sticky element is constrained by its parent's box, and the wrapper is
   only as tall as the header, so sticking the inner element gives it zero
   travel and it scrolls away. Sticking the wrapper (a flex child of .page,
   which spans the document) is what actually pins it. */
#shell-header { position: sticky; top: 0; z-index: 200; }
.site-header { background: var(--panel-dark); }
.site-header .container { max-width: none; padding: 0 32px; }
.header-inner { display: flex; align-items: center; justify-content: space-between; min-height: var(--header-h); }
.brand img { height: 44px; width: auto; }
.brand { display: flex; align-items: center; gap: 14px; }
/* 19px is measured, not picked: the letters in the wordmark PNG paint 15.5px tall
   (the file is 44px tall in the header but most of that is transparent padding -
   the ink is rows 64..140 of 219), and Saira caps are 0.8125 of the font size, so
   15.5 / 0.8125 lands on 19. That puts "TRAINING ACADEMY" at the same cap height
   as "ELUSIVE DATA" beside it, which is what makes the two read as one lockup
   rather than a title with a subtitle. */
.brand-tag {
  font-family: var(--font-label); font-size: 19px; font-weight: 500;
  letter-spacing: 3px; text-transform: uppercase; color: var(--yellow);
  border-left: 1px solid rgba(255,255,255,.25); padding-left: 16px;
  /* The ink in the wordmark PNG is not centred in the file - it sits 1.9px high
     once scaled - so flex centring lines up the two BOXES and leaves the two
     WORDS out of line. Invisible while the tag was small; at matching cap height,
     with a rule between them, a 2px baseline step reads as a mistake. Nudge the
     tag rather than the logo: the logo's position is brand, not ours to move. */
  position: relative; top: -1.9px;
  /* Two words now, where "Academy" was one, so it CAN wrap - and a two-line tag
     beside a one-line wordmark looks broken. Never wrap; drop it instead. */
  white-space: nowrap;
}
.main-nav ul { list-style: none; display: flex; align-items: center; gap: 8px; }
.main-nav > ul > li > a {
  font-family: var(--font-label); color: #fff; font-size: 14px;
  padding: 26px 14px; display: inline-flex; align-items: center; gap: 6px;
}
/* Blue-on-dark rule: any blue used as TEXT or a BORDER on a dark surface uses
   --link-blue. --accent stays for blue FILLS (buttons, the dropdown hover bar)
   and for text on white. --accent on navy is too dark to read, and mixing the
   two makes the header and footer look like different blues. */
.main-nav > ul > li > a:hover,
.main-nav .has-sub:hover > a,
.main-nav > ul > li > a.active { color: var(--link-blue); }
.main-nav .caret { font-size: 9px; opacity: .8; }
.main-nav .flag { width: 18px; height: 14px; }
.has-sub { position: relative; }
.has-sub .sub {
  position: absolute; top: 100%; left: 0; min-width: 230px; background: #fff;
  border-radius: 0 0 5px 5px; box-shadow: 0 14px 34px rgba(0,0,0,.28);
  display: none; flex-direction: column; text-align: left; padding: 0;
  /* Both of these override .main-nav ul above, which would otherwise centre
     the items and space them 8px apart. The live site's menus are flush and
     left-aligned, and the full-width hover bar depends on stretch. */
  align-items: stretch; gap: 0;
}
.has-sub:hover .sub, .has-sub:focus-within .sub { display: flex; }
.has-sub .sub a {
  font-family: var(--font-label); color: var(--text-mid); font-size: 14px;
  font-weight: 500; text-transform: uppercase; letter-spacing: 1px;
  padding: 13px 20px; white-space: nowrap; display: block;
}
.has-sub .sub a:hover { color: #fff; background: var(--accent); }

/* Right-edge menus (user chip, language) hang off the right so they never
   overflow the viewport. */
.has-sub.right .sub { left: auto; right: 0; }
.has-sub .sub .flag { width: 18px; height: 14px; display: inline-block;
  vertical-align: -2px; margin-right: 8px; }

.nav-login {
  border: 1px solid rgba(255,255,255,.4); border-radius: var(--radius);
  padding: 9px 18px !important; margin-left: 8px;
}
.nav-login:hover { background: #fff; color: var(--panel-dark) !important; }

/* Avatar + user menu */
.user-chip {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 8px 10px; border-radius: var(--radius);
}
.user-chip:hover { background: rgba(255,255,255,.08); }
.avatar {
  width: 34px; height: 34px; border-radius: 50%; flex: 0 0 auto;
  display: grid; place-items: center;
  font-family: var(--font-head); font-weight: 600; font-size: 14px; color: #fff;
  background: linear-gradient(135deg, #0635CB, #2F78ED);
}
.avatar.lg { width: 76px; height: 76px; font-size: 28px; }
.user-chip .who { font-family: var(--font-label); font-size: 13px; color: #fff; line-height: 1.3; }
.user-chip .who small { display: block; color: rgba(255,255,255,.55); font-size: 11px; }

.nav-toggle { display: none; background: none; border: 0; width: 42px; height: 38px;
  flex-direction: column; justify-content: center; gap: 6px; cursor: pointer; }
.nav-toggle span { display: block; height: 2px; background: #fff; border-radius: 2px; }

/* ---------------- Footer ---------------- */
/* margin-top:auto must live on the WRAPPER, not on .site-footer. The footer is
   injected into #shell-footer, so auto-margin on the inner element pushes
   against that content-sized div and does nothing: short pages left the footer
   floating with white space beneath it. Same shape of bug as the sticky header,
   where the wrapper was also the real flex child of .page. */
#shell-footer { margin-top: auto; }

/* ---------------- Session notice ----------------
   Shown when the session dies under an open page, or when work parked during
   one lands afterwards. Fixed to the BOTTOM rather than the top: the top is
   where the sticky header and the course toolbar live, and a bar there either
   covers the controls or shoves the whole page down mid-lesson. z-index above
   the chat launcher, which sits bottom-right on every signed-in page. */
.session-notice {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 9000;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 14px 56px 14px 20px;
  font-size: 14.5px; line-height: 1.5;
  box-shadow: 0 -6px 24px rgba(16, 24, 40, .18);
}
.session-notice.warn { background: #FEF6E7; color: #6B4A05; border-top: 3px solid #F37934; }
.session-notice.ok   { background: #E8F5E9; color: #1B4D20; border-top: 3px solid #2E7D32; }
.session-notice .btn { margin-left: auto; }
.session-notice .notice-x {
  position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; font-size: 22px; line-height: 1;
  color: inherit; opacity: .55; cursor: pointer; padding: 4px 8px;
}
.session-notice .notice-x:hover { opacity: 1; }
@media (max-width: 620px) {
  /* Clear of the chat launcher on a narrow screen, where they would overlap. */
  .session-notice { padding-bottom: 68px; }
  .session-notice .btn { margin-left: 0; }
}
.site-footer { background: var(--panel-dark); color: rgba(255,255,255,.8); }
.footer-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 44px; padding: 60px 32px 44px; max-width: 1140px; margin: 0 auto; }
.footer-brand img { height: 38px; width: auto; margin-bottom: 16px; }
.footer-brand p { font-size: 15px; max-width: 320px; }
.footer-col h5 { font-family: var(--font-head); color: #fff; font-size: 18px; margin-bottom: 14px; }
.footer-col ul { list-style: none; display: flex; flex-direction: column; gap: 9px; }
.footer-col a { color: rgba(255,255,255,.75); font-size: 14px; transition: color .15s; }
.footer-col a:hover { color: var(--link-blue); }
.footer-bottom { background: var(--footer-bottom); padding: 16px 0; font-size: 13px; }
.footer-bottom .container { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; }

/* ---------------- Utility ---------------- */
.page { min-height: 100vh; display: flex; flex-direction: column; }
.eyebrow {
  font-family: var(--font-label); font-size: 13px; font-weight: 600;
  letter-spacing: 2px; text-transform: uppercase; color: var(--accent);
  margin-bottom: 12px;
}
.badge {
  font-family: var(--font-label); font-size: 11px; font-weight: 500;
  letter-spacing: .8px; text-transform: uppercase;
  padding: 3px 9px; border-radius: 20px; display: inline-block;
}
.badge-topic { background: #EEF3FB; color: #4A6789; }
.badge-lab { background: #FFF1E8; color: #C2571B; }
.badge-quiz { background: #F2ECFB; color: #6B45B8; }
.badge-challenge { background: #FFE9EC; color: #C22338; }
.badge-draft { background: #F0F2F5; color: #6B7280; }

.dev-banner {
  background: #FFF8E1; border-bottom: 1px solid #F0E0A0; color: #7A5C00;
  font-family: var(--font-label); font-size: 12.5px; letter-spacing: .3px;
  padding: 8px 32px; text-align: center;
}

@media (max-width: 900px) {
  .nav-toggle { display: flex; }
  .main-nav { display: none; position: absolute; top: 74px; left: 0; right: 0;
    background: var(--panel-dark); padding: 10px 0 20px; }
  .main-nav.open { display: block; }
  .main-nav ul { flex-direction: column; align-items: stretch; }
  .main-nav > ul > li > a { padding: 12px 24px; }
  .has-sub .sub { position: static; display: flex; background: transparent; box-shadow: none; }
  .has-sub .sub a { color: rgba(255,255,255,.7); padding-left: 40px; }
  .footer-grid { grid-template-columns: 1fr; gap: 28px; }
}

/* Below roughly 600px the lockup and the MENU BUTTON want the same pixels: the
   wordmark plus rule plus tag needs 484px, which with the container padding and a
   42px button is ~590px before anything overlaps. Not close either - at 375px even
   a 13px tag still ran 128px under the button, so shrinking is not a way out. Drop
   the tag and keep the wordmark, which is the half that has to be there; the page
   title and the hero still name the Training Academy.

   This case is CSS because the collapsed nav is absolutely positioned, so there is
   no meaningful edge to measure against. The wider-viewport case - where the tag
   can collide with the expanded NAV, at a width that depends on how long the
   signed-in user's name is - is handled by fitBrandTag() in shell.js, which
   measures. Do not try to fold that into a breakpoint here: a media query that
   clears "Vincent Ramirez" still overlaps a longer name.

   Note the mobile collision predates the rename - "ACADEMY" at 16px already ran
   57px under the button at 375px. It was never visible on desktop. */
@media (max-width: 600px) {
  .brand-tag { display: none; }
}

/* ==========================================================================
   Chat widget - bottom right, every signed-in page (js/chat.js).
   Student face: "Chat" with a green dot while staff are available,
   "Message us" otherwise. Admin face: the inbox and the availability toggle.
   ========================================================================== */
.chatw { position: fixed; right: 22px; bottom: 22px; z-index: 900;
  display: flex; flex-direction: column; align-items: flex-end; gap: 10px;
  font-family: var(--font-body, Inter, sans-serif); }

/* The widget toggles visibility with the hidden ATTRIBUTE, and the author
   display rules below (flex on the panel, the availability label, the foot)
   silently beat the browser's built-in [hidden] { display: none }. This one
   rule is why the close button closes, why incoming messages append to a
   panel that is really open, and why students never see the admin-only
   availability toggle. */
.chatw [hidden] { display: none !important; }

.chatw-fab { display: flex; align-items: center; gap: 9px; border: 0;
  cursor: pointer; padding: 12px 20px; border-radius: 26px; color: #fff;
  background: linear-gradient(135deg, #0635CB, #1046DC 60%, #2F78ED);
  font-family: var(--font-head); font-weight: 600; font-size: 14.5px;
  box-shadow: 0 8px 26px rgba(6, 53, 203, .38); position: relative; }
.chatw-dot { width: 9px; height: 9px; border-radius: 50%; background: #4ADE80;
  box-shadow: 0 0 0 3px rgba(74, 222, 128, .25); }
.chatw-badge { position: absolute; top: -6px; right: -4px; min-width: 20px;
  height: 20px; border-radius: 10px; background: #EE3D53; color: #fff;
  font-family: var(--font-label); font-size: 11.5px; font-weight: 600;
  display: grid; place-items: center; padding: 0 6px;
  box-shadow: 0 2px 8px rgba(0,0,0,.25); }

.chatw-pop { max-width: 300px; background: #fff; border: 1px solid var(--line);
  border-radius: 12px 12px 2px 12px; padding: 12px 15px; cursor: pointer;
  box-shadow: 0 10px 30px rgba(16,24,40,.18); font-size: 13.5px; color: var(--text-dark);
  line-height: 1.45; }
.chatw-pop b { display: block; font-size: 12px; color: #1046DC; margin-bottom: 3px;
  font-family: var(--font-label); letter-spacing: .4px; }

.chatw-panel { width: 370px; max-width: calc(100vw - 32px); height: 520px;
  max-height: calc(100vh - 120px); max-height: calc(100dvh - 120px);
  background: #fff; border-radius: 14px;
  overflow: hidden; display: flex; flex-direction: column;
  box-shadow: 0 18px 60px rgba(16,24,40,.3); border: 1px solid var(--line); }

.chatw-head { background: linear-gradient(135deg, #00128D 0%, #0635CB 45%, #1046DC 70%, #2F78ED 100%);
  color: #fff; padding: 13px 16px; display: flex; align-items: center; gap: 10px; }
.chatw-head-t { flex: 1; min-width: 0; }
.chatw-title { font-family: var(--font-head); font-weight: 600; font-size: 15.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chatw-status { font-size: 11.5px; color: rgba(255,255,255,.75); margin-top: 1px; }
.chatw-back, .chatw-close { background: rgba(255,255,255,.14); border: 0;
  color: #fff; width: 28px; height: 28px; border-radius: 8px; cursor: pointer;
  font-size: 14px; flex: 0 0 auto; }
/* hover states live behind (hover:hover) below - on touchscreens hover
   STICKS after a tap, which left the Send button orange on the phone. */
.chatw-avail { display: flex; align-items: center; gap: 5px; font-family: var(--font-label);
  font-size: 10.5px; letter-spacing: .8px; text-transform: uppercase;
  color: rgba(255,255,255,.85); cursor: pointer; flex: 0 0 auto; }
.chatw-avail input { accent-color: #4ADE80; }

.chatw-body { flex: 1; overflow-y: auto; padding: 14px; background: #F7FAFD;
  display: flex; flex-direction: column; gap: 10px; }
/* The body SCROLLS; its children must never shrink to make room. Without
   this, a full transcript in the offline view crushed the leave-a-message
   textarea down to a one-line sliver. */
.chatw-body > * { flex: 0 0 auto; }
.chatw-off-text { min-height: 110px; }
.chatw-greet { background: #EAF1FE; border-left: 3px solid #1046DC; border-radius: 8px;
  padding: 11px 13px; font-size: 13.5px; color: var(--text-dark); line-height: 1.5; }
.chatw-greet.chatw-err { background: #FDEFF1; border-left-color: #EE3D53; }

.chatw-msg { max-width: 82%; }
.chatw-msg.me { align-self: flex-end; text-align: right; }
.chatw-msg.them { align-self: flex-start; }
.chatw-bubble { display: inline-block; text-align: left; padding: 9px 13px;
  border-radius: 13px; font-size: 14px; line-height: 1.45; white-space: pre-wrap;
  word-break: break-word; }
.chatw-msg.me .chatw-bubble { background: #1046DC; color: #fff;
  border-bottom-right-radius: 4px; }
.chatw-msg.them .chatw-bubble { background: #fff; color: var(--text-dark);
  border: 1px solid var(--line); border-bottom-left-radius: 4px; }
.chatw-meta { font-size: 10.5px; color: var(--text-low, #9AA7B6); margin-top: 3px;
  font-family: var(--font-label); letter-spacing: .3px; }

.chatw-foot { display: flex; gap: 8px; padding: 10px; border-top: 1px solid var(--line);
  background: #fff; }
/* 16px is not a taste choice: iOS Safari ZOOMS THE WHOLE PAGE when an input
   under 16px is focused, which threw the fixed-position widget half off the
   right edge of the phone screen. */
.chatw-foot textarea { flex: 1; border: 1px solid #C9D6E4; border-radius: 9px;
  padding: 9px 12px; font: inherit; font-size: 16px; resize: none; outline: none; }
.chatw-foot textarea:focus { border-color: #1046DC; }
.chatw-send { border: 0; background: #1046DC; color: #fff; border-radius: 9px;
  padding: 0 16px; font-family: var(--font-head); font-weight: 600; font-size: 13.5px;
  cursor: pointer; }

.chatw-off-text { border: 1px solid #C9D6E4; border-radius: 9px; padding: 10px 12px;
  font: inherit; font-size: 16px; resize: vertical; outline: none; }
.chatw-off-text:focus { border-color: #1046DC; }
.chatw-off-send { padding: 10px 16px; align-self: flex-start; }
.chatw-off-msg { font-size: 13px; color: #B8123C; }

.chatw-thread { display: flex; flex-direction: column; gap: 2px; text-align: left;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 13px; cursor: pointer; font: inherit; }
@media (hover: hover) {
  .chatw-fab:hover { filter: brightness(1.12); }
  .chatw-back:hover, .chatw-close:hover { background: rgba(255,255,255,.28); }
  .chatw-send:hover { background: #F37934; }
  .chatw-thread:hover { border-color: #1046DC; background: #F7FAFF; }
}
.chatw-th-name { font-weight: 600; color: var(--ink, #142A40); font-size: 14px;
  display: flex; align-items: center; gap: 8px; }
.chatw-th-unread { font-style: normal; background: #EE3D53; color: #fff;
  border-radius: 10px; min-width: 18px; height: 18px; font-size: 11px;
  display: inline-grid; place-items: center; padding: 0 5px;
  font-family: var(--font-label); }
.chatw-th-line { font-size: 12.5px; color: var(--text-mid); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.chatw-th-sub { font-size: 11px; color: var(--text-low, #9AA7B6); }

/* Phones get a bottom sheet - the shape people know from every mobile
   messenger. Full width, anchored to the bottom, dvh so iOS toolbars are
   accounted for. */
@media (max-width: 600px) {
  .chatw { left: 12px; right: 12px; bottom: 12px; align-items: stretch; }
  .chatw-fab, .chatw-pop { align-self: flex-end; }
  .chatw-panel { width: 100%; max-width: none;
    height: 76vh; height: 76dvh; max-height: none; }
}

@media print { .chatw { display: none !important; } }


/* ============================================================================
   Shared card presentation
   ============================================================================
   Adopted by class, not restated per block: the landing page's format cards,
   the welcome page's outcome and step cards, and the How This Course Works
   step cards were four implementations of one component and had already
   drifted three separate ways. A change here reaches all of them.

   THESE CARDS ARE INFORMATIONAL, NOT BUTTONS. There is deliberately no lift
   and no pointer cursor: both promise a click that never arrives, which reads
   as broken rather than considered. What moves is the small disc inside, so
   the card acknowledges the pointer rather than inviting it.
   ========================================================================= */

/* TWO shadows, not one. The tight shadow gives the card an edge and the wide
   one gives it float; a single blurry shadow gives it neither and reads as a
   grey haze. This is most of the difference at rest, which is where a card
   spends nearly all of its life. */
.ed-card {
  box-shadow: 0 1px 2px rgba(16,24,40,.06), 0 8px 24px -4px rgba(16,24,40,.08);
  transition: border-color .16s cubic-bezier(.2,.7,.3,1);
}
.ed-card:hover { border-color: var(--line-accent); }

/* The disc is the only thing that moves. 1px and a colour step, on the same
   curve as the border, so the two read as one response. */
.ed-ico {
  transition: background-color .16s cubic-bezier(.2,.7,.3,1),
              transform .16s cubic-bezier(.2,.7,.3,1);
}
.ed-card:hover .ed-ico { background: var(--accent-hover); transform: translateY(-1px); }

/* ---- Entrance ----
   The hidden state exists ONLY under html.reveal-ready, which shell.js adds
   after confirming IntersectionObserver exists and the visitor has not asked
   for reduced motion. No script, old browser, or a thrown error therefore
   means the cards are simply visible. Content that is invisible because an
   animation did not run is a real failure, not a cosmetic one. */
html.reveal-ready .ed-reveal { opacity: 0; transform: translateY(8px); }
html.reveal-ready .ed-reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity .45s cubic-bezier(.2,.7,.3,1),
              transform .45s cubic-bezier(.2,.7,.3,1);
}

@media (prefers-reduced-motion: reduce) {
  html.reveal-ready .ed-reveal { opacity: 1; transform: none; }
  .ed-card, .ed-ico { transition: none; }
  .ed-card:hover .ed-ico { transform: none; }
}
