/* =====================================================================
 * ET+ AI Tutor — Humanoid Character System (2026-05-18)
 *
 * Implementation of the approved Design Report (Direction C — Advanced
 * Friendly Robotic Mentor). Layered ON TOP of the existing style.css and
 * etp-ux.css — overrides only its own scoped selectors.
 *
 * Sections:
 *   1.  Design tokens (Annex A.5 material spec)
 *   2.  Three-tier scale system (Annex A.6)
 *   3.  Character SVG layout + body / visor / eyes positioning
 *   4.  Material treatment (holographic-translucent body + visor glass)
 *   5.  Inner-light system + 5 programme variants + 4 persona variants
 *   6.  Holographic presence effects (edge dissolution, scan-line, glow)
 *   7.  Idle motion — 4 decoupled loops (Annex C.1)
 *   8.  Emotional state vocabulary — 8 states (Annex B)
 *   9.  State transitions + entry/exit choreography (Annex C)
 *   10. Dashboard hero composition (Annex G.1)
 *   11. Chat conversational composition (Annex G.2)
 *   12. Classroom hero composition (Annex G.3)
 *   13. Settings persona-preview composition (Annex G.4)
 *   14. Chrome silhouette (24-32 px appearances, Annex G.6)
 *   15. Reduced-motion + WCAG 2.1 AA accessibility (Annex C.8)
 * ===================================================================== */

/* ---------------------------------------------------------------------
 * 1. Design tokens — character system
 * Per Annex A.5; programme variants per Annex A.5 inner-light table.
 * ------------------------------------------------------------------- */

:root {
  /* Body shell — pearlescent white-blue with a soft blue rim shadow.
     Phase 3 alignment (2026-05-22): body palette shifted per canonical
     spec D5 — highlight #FFFFFF→#E8F4FF, mid #F0F6FB→#C8E4F8,
     shadow #D2E0EE→#A0C8E8, deep #ACC2D6→#7AABCB.
     Phase 6 saturation tightening (2026-05-22g, Item B): the mid +
     shadow + deep tones shift a few percent cooler + slightly more
     saturated toward the steel-periwinkle reading visible on the new
     canonical reference's lower-body and chest-rim shadow areas. The
     highlight stays IDENTICAL (already cool-white pearlescent). Hue
     family unchanged; this is a calibration nudge, not a redesign.
     Mid    #C8E4F8 → #BFDCF2  (1.5% cooler, slightly less saturated)
     Shadow #A0C8E8 → #95BFE0  (modest cool shift toward steel-blue)
     Deep   #7AABCB → #6F9EBE  (slightly deeper steel) */
  --etp-body-highlight: #E8F4FF;
  --etp-body-mid:       #BFDCF2;
  --etp-body-shadow:    #95BFE0;
  --etp-body-deep:      #6F9EBE;

  /* Visor — kept in the SVG but rendered as a very subtle forehead-plate
     hint (opacity 0.10). The face geometry — eyes + smile — sits
     directly on the head, not behind dark glass. */
  --etp-visor-base:     rgba(180, 200, 220, 0.18);
  --etp-visor-edge:     rgba(110, 140, 170, 0.35);
  --etp-visor-inner-shadow: rgba(40, 80, 120, 0.06);

  /* Inner light — bright cyan (2026-05-19 visual refresh). Glow
     opacity bumped 0.32→0.55 so the ambient cyan halo behind the
     character reads as the prominent atmospheric lighting visible
     in the reference avatar (subtle cyan circuit-board ambience). */
  --etp-light:          #00B7FF;
  --etp-light-warm:     #5EE0FF;
  --etp-light-deep:     #0098D8;
  --etp-light-glow:     rgba(0, 183, 255, 0.55);

  /* Eyes — large glossy ovals. Phase 3 alignment (2026-05-22): iris
     swapped from bright cyan #5EE0FF to medium blue #4BAEE8 per the
     canonical spec D1 ("medium blue, visible gradient depth"). The
     iris drops out of the inner-light cyan family — only the halo +
     chest + ambient keep that hue. Pupil core swaps from cyan #0098D8
     to deep navy #0A1F33 per spec D3 ("dark centre, circular"). The
     white gloss highlights sit ON TOP of the dark pupil for high
     contrast. --etp-eye-iris-deep is reserved for an iris radial
     gradient (Phase 4 polish; current build paints solid). Halo stays
     at 0.85 alpha — the cyan glow around the iris matches the spec's
     "soft cyan rim-light wrap". */
  --etp-eye-halo:       rgba(94, 224, 255, 0.85);
  --etp-eye-iris:       #4BAEE8;
  --etp-eye-iris-deep:  #2E8AC4;
  --etp-eye-pupil:      #0A1F33;

  /* Mouth — soft dark-blue smile per canonical spec D8 ("small, clean,
     upward-curved smile" with calm/warm register). Phase 3 alignment
     (2026-05-22): bright cyan rgba(0,200,255,0.95) replaced with
     rgba(20,60,110,0.80) so the smile reads as a natural friendly
     line rather than a third inner-light surface competing with the
     eye halo + chest beacon. The mouth now sits visually quieter, in
     line with the spec's calm warm register. */
  /* Phase 12 (2026-05-27) Tier 1.5 — deepened slightly from
     rgba(20, 60, 110, 0.80) → rgba(15, 55, 105, 0.85) so the smile
     reads as adult-grade confident rather than child-grade cute,
     matching the eye reduction's "quiet confidence" direction. */
  --etp-mouth:          rgba(15, 55, 105, 0.85);

  /* Chest white-hot centre — Phase 3 alignment (2026-05-22), spec D6.
     The chest orb gains a white-hot inner highlight on top of the
     teal-cyan core fill. This token drives the new
     `.etp-char-chest-core-hot` <circle> added inline in
     etp-character.js. Pure white at high opacity reads as the bright
     origin point of the inner light radiating outward. */
  --etp-chest-core-hot: #FFFFFF;

  /* Chest core teal-cyan fill — Phase 3 alignment (2026-05-22), spec D6.
     Splits the chest-disc fill from the rest of the inner-light hue
     so the disc reads as teal-cyan (#00D4E8) while the halo + ambient
     stay on --etp-light (#00B7FF). Keeps the inner-light system
     stable for all other surfaces. */
  /* Phase 12 (2026-05-27) Tier 1.6 — 2% warmer (#00D4E8 → #00DCEC)
     so the disc reads as "high-tech blue with a hint of life" rather
     than pure cold cyan. Aligns with the brand-mark-as-focal-element
     direction post chest simplification. */
  --etp-chest-core-teal: #00DCEC;

  /* Motion timing — Annex C grammar (unchanged) */
  --etp-ease:           cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --etp-ease-bounce:    cubic-bezier(0.34, 1.56, 0.64, 1.0);
  --etp-entry-duration: 800ms;
  --etp-exit-duration:  600ms;
  --etp-state-duration: 350ms;

  /* Idle loop periods — Annex C.1 (unchanged) */
  --etp-breathe-period: 4s;
  --etp-pulse-period:   6.7s;     /* 0.15 Hz */
}

/* Programme context overrides — Annex A.5 + Section 7.4. The character
   itself never changes; only the inner-light hue shifts. */

/* Programme variants — desaturated for the 2026-05-18 visual refresh.
   The character's BASE identity stays cool-white + cyan; the programme
   accent tints the chest-disc glow + ambient vignette without
   dominating. Eyes stay blue (default --etp-eye-iris) across all
   programmes — only the inner-light glow shifts. */

/* Phase 1 alignment (2026-05-20): programme accent halo lifted from
   alpha 0.20 → 0.45 so each programme inherits the same prominent
   ambient cyan-style glow seen in the reference. The hue still
   shifts per programme; only the strength is raised to match the
   non-programme default. */
[data-etp-programme="ai-robotics-plus"] {
  --etp-light:      #00E5FF;
  --etp-light-warm: #5CF0FF;
  --etp-light-deep: #00B8CC;
  --etp-light-glow: rgba(0, 229, 255, 0.45);
}
[data-etp-programme="ai-steam-plus"] {
  --etp-light:      #FF7A2E;
  --etp-light-warm: #FFA266;
  --etp-light-deep: #CC5C1F;
  --etp-light-glow: rgba(255, 122, 46, 0.45);
}
[data-etp-programme="ai-expert-plus"] {
  --etp-light:      #9B7ED4;
  --etp-light-warm: #B8A0E5;
  --etp-light-deep: #6B4EA8;
  --etp-light-glow: rgba(155, 126, 212, 0.45);
}
[data-etp-programme="ai-math-plus"] {
  --etp-light:      #3B82F6;
  --etp-light-warm: #7BA6F9;
  --etp-light-deep: #2563C8;
  --etp-light-glow: rgba(59, 130, 246, 0.45);
}
[data-etp-programme="ai-computer-science-plus"] {
  --etp-light:      #22D3A4;
  --etp-light-warm: #5CF0C9;
  --etp-light-deep: #0EAF82;
  --etp-light-glow: rgba(34, 211, 164, 0.45);
}

/* Phase 12 (2026-05-27) Tier 2.4 — per-programme ET+ tint.
   The chest disc's underlying teal is already programme-themed
   via --etp-chest-core-teal; this rule subtly tints the brand
   text glow (drop-shadow) using --etp-light-glow so the disc's
   identity feels co-ordinated with the active programme without
   making the text itself a programme colour (text stays pure
   white for legibility across all 5 programmes). The 0.85
   multiplier on the glow alpha keeps the cyan-warm read on
   Robotics + sells the orange/violet/blue/green tints distinctly
   on the other 4 programmes. */
[data-etp-programme="ai-robotics-plus"] .etp-char-chest-brand,
[data-etp-programme="ai-steam-plus"] .etp-char-chest-brand,
[data-etp-programme="ai-expert-plus"] .etp-char-chest-brand,
[data-etp-programme="ai-math-plus"] .etp-char-chest-brand,
[data-etp-programme="ai-computer-science-plus"] .etp-char-chest-brand {
  filter: drop-shadow(0 0 1.4px var(--etp-light-glow))
          drop-shadow(0 0 0.6px rgba(255, 255, 255, 0.45))
          drop-shadow(0 -0.18px 0.18px rgba(255, 255, 255, 0.35));
}

/* Persona variants — Annex G.4. Same character, four tonal shifts in
   inner light + motion tempo. */

.etp-character[data-etp-persona="patient_mentor"] {
  --etp-light: var(--etp-light-warm);   /* warmer cyan */
  --etp-breathe-period: 4.4s;            /* slower, calmer */
}
.etp-character[data-etp-persona="energetic_coach"] {
  /* baseline + slightly brighter glow */
  --etp-light-glow: rgba(0, 183, 255, 0.30);
  --etp-breathe-period: 3.4s;            /* livelier */
}
.etp-character[data-etp-persona="socratic_questioner"] {
  --etp-light: var(--etp-light-deep);   /* cooler cyan */
  --etp-breathe-period: 4.0s;
}
.etp-character[data-etp-persona="pragmatic_engineer"] {
  /* baseline + sharper edge */
  --etp-light-glow: rgba(0, 183, 255, 0.14);
  --etp-breathe-period: 3.8s;
}

/* ---------------------------------------------------------------------
 * 2. Three-tier scale system (Annex A.6)
 *    Hero    = 240-340 px head — dashboard, classroom, parent portal
 *    Convo   = 96-160 px head  — chat, settings preview
 *    Chrome  = 24-40 px head   — sidebars, favicon, notifications
 * ------------------------------------------------------------------- */

.etp-character {
  display: inline-block;
  position: relative;
  width: var(--etp-char-width, 128px);
  height: var(--etp-char-height, 166px);
  contain: layout style;
  user-select: none;
  -webkit-user-select: none;
}
.etp-character[data-etp-scale="hero"] {
  --etp-char-width: 320px;
  --etp-char-height: 416px;
}
.etp-character[data-etp-scale="convo"] {
  --etp-char-width: 128px;
  --etp-char-height: 166px;
}
.etp-character[data-etp-scale="chrome"] {
  --etp-char-width: 32px;
  --etp-char-height: 42px;
}
.etp-character[data-etp-scale="classroom"] {
  --etp-char-width: clamp(280px, 24vw, 480px);
  --etp-char-height: clamp(364px, 31vw, 624px);
}

/* The inline SVG fills the character mount. */
.etp-character > svg.etp-char-svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

/* ---------------------------------------------------------------------
 * 3. Body / visor / eyes positioning (Annex A.2 construction grid)
 * SVG viewBox is 100 × 130 — exactly the spec.
 * ------------------------------------------------------------------- */

.etp-char-body {
  fill: url(#etp-body-grad);
}

/* Hide the legacy rounded-rectangle head. The round head-circle
   (drawn after it in the SVG) is the canonical face silhouette and
   matches the reference avatar. The rect-head stays in the DOM so
   any test or state-rule that greps for it still matches; it's just
   visually suppressed. Added 2026-05-19 visual refresh. */
.etp-char-body--legacy-head {
  opacity: 0;
}
/* Soft cyan rim-light wrap around the head silhouette. Phase 1
   alignment (2026-05-20): the reference avatar shows a subtle
   backlit halo bleed onto the head's left/right edges. We emulate
   this with a small drop-shadow in the inner-light hue applied
   only to the head circle, on top of the existing ambient vignette
   behind the whole character. Programme variants inherit through
   --etp-light-glow so the rim hue follows the active programme. */
.etp-char-head-circle {
  filter: drop-shadow(0 0 1.2px var(--etp-light-glow));
}

/* Phase 1 (2026-05-25) — Section A rim-light wrap extension.
   Same drop-shadow as the head circle, applied to the body
   (shoulders + chest) so the rim wraps the full silhouette per
   the canonical spec. Programme variants inherit --etp-light-glow
   so the rim hue follows the active programme. */
.etp-char-body {
  filter: drop-shadow(0 0 0.9px var(--etp-light-glow));
}

/* Phase 1 (2026-05-25) — Section B layered chest panel surfaces.
   Outer raised frame + recessed inner panel together form the
   armour-plating effect that replaces the flat chest disc. The
   outer frame catches the rim light; the inner panel sits darker
   to read as recessed. State-driven opacity in §8b is preserved
   via the chest-core triplet immediately above it. */
.etp-char-chest-outer {
  filter: drop-shadow(0 0 0.6px var(--etp-light-glow));
}
.etp-char-chest-inner {
  /* No filter on the recessed panel — keeps it visually subordinate. */
}

/* Phase 1 (2026-05-25) — Section A satin-gloss highlight overlay.
   Sits behind the visor + eyes so the gloss reads on the upper head
   without washing out the eye region. pointer-events:none so it
   never interferes with hit testing for state-rule selectors. */
.etp-char-head-gloss {
  pointer-events: none;
}
.etp-char-eye-lens {
  pointer-events: none;
  mix-blend-mode: screen;
}

.etp-char-body-bevel {
  fill: rgba(0, 0, 0, 0.12);
  opacity: 0.6;
}
.etp-char-visor {
  fill: var(--etp-visor-base);
}
.etp-char-visor--subtle {
  /* Phase 1 alignment (2026-05-20): visor element kept in the DOM so
     the shimmer + alert-flash rules in §8 still have a target, but
     visually FULLY invisible. The reference avatar has one continuous
     head surface — no forehead band. Eyes + smile sit directly on
     the head circle. */
  opacity: 0;
}
.etp-char-visor-edge {
  fill: none;
  stroke: var(--etp-visor-edge);
  stroke-width: 0.5;
}
.etp-char-eye-halo {
  /* Soft cyan glow surrounding each iris. Phase 1 alignment
     (2026-05-20): blur 2.2 → 3.2 px and an additional 1.4 px outer
     drop-shadow in the inner-light hue so the glow feathers well
     past the iris diameter — matching the wider, softer halo on the
     reference avatar's eyes. The drop-shadow inherits whichever
     programme accent is active so the bleed reads correctly across
     all variants. */
  fill: var(--etp-eye-halo);
  filter: blur(3.2px) drop-shadow(0 0 1.4px var(--etp-light));
  transition: opacity 220ms var(--etp-ease);
}
.etp-char-eye-iris {
  fill: var(--etp-eye-iris);
  transition: opacity 220ms var(--etp-ease);
}
.etp-char-eye-pupil {
  /* The dark cyan pupil core sitting inside the iris. Phase 1
     alignment (2026-05-20): re-tinted darker cyan (#0098D8 via
     --etp-eye-pupil) so the glossy white highlight dots above it
     read clearly. Visible only in open-eye states. */
  fill: var(--etp-eye-pupil);
}
.etp-char-eye-pupil-dot {
  /* The white reflection highlight on top of the pupil. Sells the
     "glossy round eye" reading from the reference screenshots. */
  fill: #FFFFFF;
  opacity: 0.95;
  transition: opacity 220ms var(--etp-ease);
}
.etp-char-eye-arc {
  /* Happy upward-curve "closed eye" shape. Hidden by default; shown
     ONLY for Celebrating + Encouraging states (see §8). */
  fill: none;
  stroke: var(--etp-eye-iris);
  stroke-width: 2;
  stroke-linecap: round;
  opacity: 0;
  transition: opacity 220ms var(--etp-ease);
}
.etp-char-mouth {
  /* Gentle smile arc below the eyes, persistent across the 6 default
     states. Morphs subtly per state via §8 rules.
     Phase 3 alignment (2026-05-22): stroke-width 2.4 → 2.0 per
     canonical spec D8 ("small, clean, upward-curved smile"). The
     softer dark-blue stroke (driven by --etp-mouth) reads as a
     natural friendly line rather than a third inner-light surface.
     History: 1.6 → 1.9 → 2.4 (over-thick for spec) → 2.0 (current). */
  fill: none;
  stroke: var(--etp-mouth);
  stroke-width: 2.0;
  stroke-linecap: round;
  transition: d 350ms var(--etp-ease), stroke 220ms var(--etp-ease);
}
.etp-char-ear-cup {
  /* Over-ear headphone-style discs on each side of the head.
     2026-05-19 refresh: enlarged + faintly tinted toward the inner
     light so they read as tech accessories that share the
     character's cyan accent palette. */
  fill: var(--etp-body-shadow);
  opacity: 0.92;
}
.etp-char-ear-cup-rim {
  /* Crisp 0.6-stroke outline around each ear cup. Picks up the
     inner-light tint at low opacity so the headphones share the
     chest-disc's cyan accent without competing with it. */
  stroke: var(--etp-light-deep);
  stroke-width: 0.6;
  opacity: 0.35;
}
.etp-char-ear-cup-inner {
  /* Inner gloss highlight on the ear cup. */
  fill: var(--etp-body-deep);
  opacity: 0.55;
}

/* Antenna — small upright stem + soft glowing orb on the crown.
   Added 2026-05-19. Shares the inner-light cyan so it reads as a
   tech detail, not a hair accessory. The glow ring uses the same
   variable as the chest core so the programme accent tints both
   surfaces together. */
.etp-char-antenna-stem {
  stroke: var(--etp-body-shadow);
  stroke-width: 1.1;
  stroke-linecap: round;
  opacity: 0.88;
}
.etp-char-antenna-orb {
  fill: var(--etp-light);
  opacity: 0.96;
}
.etp-char-antenna-orb-glow {
  fill: var(--etp-light);
  opacity: 0.32;
  filter: blur(2.2px);
}

/* Phase 2 alignment (2026-05-21): the canonical reference screenshot
   shows NO antenna on the crown — the head is a clean smooth dome.
   The antenna SVG elements (stem + orb + glow, geometry preserved in
   etp-character.js so any future state rules / behaviour-bus
   listeners still have a DOM target) are visually fully suppressed
   here. Same pattern as .etp-char-visor--subtle in §3 and
   .etp-char-body--legacy-head in §3. To re-enable the antenna in a
   future content pass, delete this block — the original styling at
   .etp-char-antenna-stem / -orb / -orb-glow above re-activates
   immediately. */
.etp-char-antenna-stem,
.etp-char-antenna-orb,
.etp-char-antenna-orb-glow {
  opacity: 0 !important;
}

/* Phase 4 alignment (2026-05-22e): the NEW canonical "Meet ET+" reference
   screenshot OVERRIDES the prior PR #295 (2026-05-22) D4 reading. The new
   screenshot clearly shows prominent over-ear headphone discs on both
   sides of the head — they are part of the canonical silhouette, not an
   accent to suppress. The Phase 3 suppression block (opacity:0!important
   on .etp-char-ear-cup / -rim / -inner) was removed here so the existing
   ear-cup geometry at etp-character.js lines 234-248 renders again. The
   geometry was preserved through Phase 3 specifically for this kind of
   spec-override reversal. To suppress again in a future spec revision,
   re-add the three-selector opacity:0 block here. */

/* Pupil reflection-dot variant — small secondary highlight in the
   upper-left of each iris. Phase 3 alignment (2026-05-22) per spec
   D2: "Two soft white reflection spots in the upper portion of each
   eye". Was --bottom (cy=31.6, lower-left) until 2026-05-21; now
   --upper-left (cy=28.4, mirroring the primary --upper-right dot
   across the iris centreline). Opacity drops with the primary
   highlight so both dots fade together during Encouraging +
   Celebrating. */
.etp-char-eye-pupil-dot--upper-left {
  fill: #FFFFFF;
  opacity: 0.55;
}
.etp-char-chest-core {
  /* The teal-cyan disc on the chest — primary inner-light core.
     Phase 3 alignment (2026-05-22) per spec D6: fill swapped from
     --etp-light (#00B7FF cyan) to --etp-chest-core-teal (#00D4E8
     teal-cyan) so the disc reads with the spec's teal hue while the
     halo + ambient stay on the inner-light cyan family. */
  fill: var(--etp-chest-core-teal, var(--etp-light));
  opacity: 1;
}
.etp-char-chest-core-hot {
  /* Phase 12 (2026-05-27) Tier 0.4 — RETIRED. The white-hot dot
     element has been removed from the JS-emitted SVG; the promoted
     ET+ brand mark now serves as the disc's bright centre. Selector
     kept (commented) for any in-flight tests that still grep for
     it. Setting display:none is defence-in-depth — if an old cached
     SVG ever ships with the element, it stays invisible. */
  display: none !important;
}
.etp-char-chest-core-glow {
  /* Soft glow ring around the chest disc. Pulses with the inner-light
     loop (relocated from the old behind-visor "soul" position).
     2026-05-19 visual refresh: opacity 0.45 → 0.65, blur 4 → 6 px so
     the disc reads as the bright cyan beacon visible on the reference
     avatar's chest. */
  fill: var(--etp-light);
  opacity: 0.65;
  filter: blur(6px);
}
.etp-char-chest-core-rim {
  /* Crisp rim around the chest disc — keeps it legible at all scales. */
  fill: none;
  stroke: var(--etp-light-deep);
  stroke-width: 0.7;
  opacity: 0.7;
}

/* Upper eyelid arc — Phase 3 alignment (2026-05-22) per spec D7:
   "Upper eyelids are present and gently relaxed; conveys calm
   intelligence and warmth". Thin curve above each iris drawn in the
   body-deep token so it reads as part of the head surface rather
   than as a face accent. Default opacity 0.32 — subtle but visible.
   Hidden during squint/closed-eye states (Listening, Thinking,
   Encouraging, Celebrating) via the rules below so they do not
   double up with the existing eye-arc + eye-narrow transforms. */
.etp-char-eye-lid {
  transition: opacity 220ms var(--etp-ease);
}
.etp-character[data-etp-state="listening"] .etp-char-eye-lid,
.etp-character[data-etp-state="thinking"] .etp-char-eye-lid,
.etp-character[data-etp-state="encouraging"] .etp-char-eye-lid,
.etp-character[data-etp-state="celebrating"] .etp-char-eye-lid {
  opacity: 0;
}
.etp-char-acoustic {
  /* Legacy class kept for backward-compat; styled to render invisible
     because the new ear-cup geometry replaces it. */
  fill: var(--etp-body-shadow);
  opacity: 0;
}
.etp-char-collar {
  fill: none;
  stroke: var(--etp-light);
  stroke-width: 0.5;
  opacity: 0.55;
}

/* ---------------------------------------------------------------------
 * 4. Material treatment — body fill + visor glass + inner light
 * ------------------------------------------------------------------- */

/* The body radial gradient is defined inline in the SVG <defs> so it
   inherits CSS custom properties. Below are the supporting filters. */

.etp-char-soul {
  /* 2026-05-18 visual refresh: the soul ellipse stays in the SVG as a
     subtle echo behind the eye region so emotional state can still
     read from the face (e.g. dimmed during Thinking). The PRIMARY
     inner-light core has moved to the chest disc — see
     `.etp-char-chest-core` + `.etp-char-chest-core-glow` above. */
  fill: var(--etp-light);
  opacity: 0.22;
  filter: blur(3px);
}

/* ---------------------------------------------------------------------
 * 5. Inner-light pulse driver (Annex C.1) — RELOCATED 2026-05-18.
 * Primary pulse now drives the chest-core glow; the face echo follows
 * the same period at a quieter amplitude.
 * ------------------------------------------------------------------- */

@keyframes etp-soul-pulse {
  0%, 100% { opacity: 0.46; }
  50%      { opacity: 0.62; }
}
@keyframes etp-chest-core-pulse {
  0%, 100% { opacity: 0.38; transform: scale(1.000); }
  50%      { opacity: 0.62; transform: scale(1.04);  }
}
@keyframes etp-soul-echo-pulse {
  0%, 100% { opacity: 0.18; }
  50%      { opacity: 0.26; }
}
.etp-char-chest-core-glow {
  transform-box: fill-box;
  transform-origin: center;
  animation: etp-chest-core-pulse var(--etp-pulse-period) ease-in-out infinite;
}
.etp-char-soul {
  animation: etp-soul-echo-pulse var(--etp-pulse-period) ease-in-out infinite;
}

/* Phase 1 (2026-05-25) GAP-11 — chest pulse on streaming chat.
   When ai-tutor.js emits etpux:stream-chunk for each delta from the
   /chat SSE, etp-character.js tags every character mount with
   .etp-char-streaming for 240 ms. During that window the chest-core
   glow brightens, opacity lifts, and a quick scale bounce fires so
   the chest visibly pulses in time with the text rendering. Honoured
   even at chrome scale because the chest is small but still readable.
   prefers-reduced-motion below disables the scale bounce. */
.etp-character.etp-char-streaming .etp-char-chest-core-glow {
  animation-duration: 320ms !important;
  opacity: 0.85 !important;
  transform: scale(1.08);
  transition: transform 120ms ease-out, opacity 120ms ease-out;
}
.etp-character.etp-char-streaming .etp-char-chest-core {
  opacity: 1 !important;
  filter: brightness(1.12);
}
.etp-character.etp-char-streaming .etp-char-chest-core-hot {
  opacity: 1 !important;
}
@media (prefers-reduced-motion: reduce) {
  .etp-character.etp-char-streaming .etp-char-chest-core-glow {
    transform: none;
    animation: none !important;
  }
}

/* ---------------------------------------------------------------------
 * 6. Holographic presence — edge dissolution + ambient glow + scan-line
 * Annex A.5 + Section 6.4
 * ------------------------------------------------------------------- */

.etp-character::before {
  /* Ambient programme-accent vignette behind the character. Soft, never
     opaque. 2026-05-19 visual refresh: inset widened -12% → -22%,
     opacity 0.85 → 1.0, gradient stop pushed 62% → 70% so the cyan
     ambient halo reads as the prominent atmospheric lighting visible
     in the reference avatar's background. */
  content: "";
  position: absolute;
  inset: -22%;
  background: radial-gradient(
    ellipse at 50% 42%,
    var(--etp-light-glow) 0%,
    transparent 70%
  );
  pointer-events: none;
  z-index: 0;
  opacity: 1;
}
.etp-character > svg.etp-char-svg {
  position: relative;
  z-index: 1;
}

/* Scan-line texture — only on Robotics + CS programmes (Section 7.4) */
[data-etp-programme="ai-robotics-plus"] .etp-character::after,
[data-etp-programme="ai-computer-science-plus"] .etp-character::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    0deg,
    transparent 0px,
    transparent 3px,
    rgba(255, 255, 255, 0.018) 3px,
    rgba(255, 255, 255, 0.018) 4px
  );
  pointer-events: none;
  z-index: 2;
  border-radius: inherit;
  mix-blend-mode: screen;
}

/* ---------------------------------------------------------------------
 * 7. Idle motion — 4 decoupled loops (Annex C.1)
 * Periods are intentionally co-prime so the loops never re-sync.
 * ------------------------------------------------------------------- */

@keyframes etp-breathe {
  0%, 100% { transform: translateY(0) scale(1.000); }
  50%      { transform: translateY(-1.2px) scale(1.004); }
}
.etp-character[data-etp-state="neutral"]     .etp-char-svg,
.etp-character[data-etp-state="encouraging"] .etp-char-svg,
.etp-character[data-etp-state="waiting"]     .etp-char-svg {
  animation: etp-breathe var(--etp-breathe-period) ease-in-out infinite;
}

@keyframes etp-eye-drift-loop {
  0%, 12%   { transform: translate(0, 0); }
  15%       { transform: translate(-1.4px, 0); }
  22%       { transform: translate(-1.4px, 0); }
  25%, 50%  { transform: translate(0, 0); }
  53%       { transform: translate(1.6px, 0); }
  60%       { transform: translate(1.6px, 0); }
  63%, 100% { transform: translate(0, 0); }
}
.etp-character[data-etp-state="neutral"]    .etp-char-eyes,
.etp-character[data-etp-state="waiting"]    .etp-char-eyes {
  animation: etp-eye-drift-loop 9.7s linear infinite;
}

@keyframes etp-blink-pulse {
  0%, 95%, 100% { transform: scaleY(1.00); }
  96%, 98%      { transform: scaleY(0.06); }
}
.etp-character[data-etp-state="neutral"]    .etp-char-eye,
.etp-character[data-etp-state="listening"]  .etp-char-eye,
.etp-character[data-etp-state="encouraging"] .etp-char-eye,
.etp-character[data-etp-state="waiting"]    .etp-char-eye {
  transform-origin: 50% 50%;
  animation: etp-blink-pulse 7.1s linear infinite;
}

/* ---------------------------------------------------------------------
 * 8. Emotional state vocabulary (Annex B — 8 states)
 * Each state changes geometry, halo, pupil offset, inner-light coupling.
 * ------------------------------------------------------------------- */

/* Neutral — baseline; no per-state overrides. */

/* Listening — eyes slightly enlarged, soft tilt -4°, lean +8° */
.etp-character[data-etp-state="listening"] .etp-char-svg {
  transform: rotate(-3deg) translateY(-1px);
  transition: transform 350ms var(--etp-ease);
}
.etp-character[data-etp-state="listening"] .etp-char-eye-iris {
  r: 5.0;
  transition: r 200ms var(--etp-ease);
}
.etp-character[data-etp-state="listening"] .etp-char-soul {
  opacity: 0.68;
  animation-duration: 2.4s;
}

/* Thinking — eyes narrow to horizontal slit, pupils up-and-left,
   inner light dims, breathing pause. */
.etp-character[data-etp-state="thinking"] .etp-char-eye-iris {
  ry: 1.6;
  transition: ry 250ms var(--etp-ease);
}
.etp-character[data-etp-state="thinking"] .etp-char-pupils {
  transform: translate(-0.8px, -1.0px);
  transition: transform 250ms var(--etp-ease);
}
.etp-character[data-etp-state="thinking"] .etp-char-soul {
  opacity: 0.32;
  animation-duration: 12s;
}

/* Explaining — full Neutral geometry + visor shimmer */
.etp-character[data-etp-state="explaining"] .etp-char-soul {
  opacity: 0.78;
  animation-duration: 2s;
}
.etp-character[data-etp-state="explaining"] .etp-char-visor {
  fill: var(--etp-visor-base);
}
.etp-character[data-etp-state="explaining"] .etp-char-shimmer {
  opacity: 1;
}
.etp-char-shimmer {
  opacity: 0;
  transition: opacity 220ms var(--etp-ease);
}
@keyframes etp-shimmer-sweep {
  0%   { transform: translateX(-30%); opacity: 0; }
  20%  {                              opacity: 0.55; }
  80%  {                              opacity: 0.55; }
  100% { transform: translateX(30%);  opacity: 0; }
}
.etp-character[data-etp-state="explaining"] .etp-char-shimmer rect {
  animation: etp-shimmer-sweep 1.4s linear infinite;
}

/* Encouraging — happy upward-arc eyes (closed-eye smile), warmer hue,
   deeper lean. 2026-05-18 visual refresh: the open iris fades out and
   the eye-arc fades in for the duration of the state. */
.etp-character[data-etp-state="encouraging"] .etp-char-svg {
  transform: rotate(-1.5deg) translateY(-0.5px);
  transition: transform 400ms var(--etp-ease);
}
.etp-character[data-etp-state="encouraging"] {
  --etp-light: var(--etp-light-warm);
}
.etp-character[data-etp-state="encouraging"] .etp-char-eye-iris,
.etp-character[data-etp-state="encouraging"] .etp-char-eye-halo,
.etp-character[data-etp-state="encouraging"] .etp-char-eye-pupil,
.etp-character[data-etp-state="encouraging"] .etp-char-eye-pupil-dot {
  opacity: 0;
}
.etp-character[data-etp-state="encouraging"] .etp-char-eye-arc {
  opacity: 1;
  stroke: var(--etp-light-deep);
}

/* Celebrating — pupil flash + brightness pulse + nod (800 ms total) */
@keyframes etp-celebrate-soul {
  0%   { opacity: 0.55; }
  30%  { opacity: 0.95; }
  70%  { opacity: 0.85; }
  100% { opacity: 0.55; }
}
@keyframes etp-celebrate-nod {
  0%, 100% { transform: translateY(0); }
  35%      { transform: translateY(-2px); }
  60%      { transform: translateY(0.5px); }
}
.etp-character[data-etp-state="celebrating"] .etp-char-soul,
.etp-character[data-etp-state="celebrating"] .etp-char-chest-core-glow {
  animation: etp-celebrate-soul 800ms var(--etp-ease-bounce) 1;
}
.etp-character[data-etp-state="celebrating"] .etp-char-svg {
  animation: etp-celebrate-nod 800ms var(--etp-ease-bounce) 1;
}
/* Celebrating: happy upward-arc eyes for the duration of the state.
   2026-05-18 visual refresh — replaces the prior pupil-flash treatment
   so the closed-eye smile reads instantly. */
.etp-character[data-etp-state="celebrating"] .etp-char-eye-iris,
.etp-character[data-etp-state="celebrating"] .etp-char-eye-halo,
.etp-character[data-etp-state="celebrating"] .etp-char-eye-pupil,
.etp-character[data-etp-state="celebrating"] .etp-char-eye-pupil-dot {
  opacity: 0;
}
.etp-character[data-etp-state="celebrating"] .etp-char-eye-arc {
  opacity: 1;
  stroke: var(--etp-light-deep);
}

/* Alert — eyes briefly enlarge + accent flash + slight head turn */
@keyframes etp-alert-flash {
  0%, 100% { opacity: 0.55; }
  10%, 30% { opacity: 0.95; }
}
.etp-character[data-etp-state="alert"] .etp-char-soul {
  animation: etp-alert-flash 600ms var(--etp-ease);
}
.etp-character[data-etp-state="alert"] .etp-char-eye-iris {
  rx: 5.5;
  ry: 4.8;
  transition: rx 150ms var(--etp-ease), ry 150ms var(--etp-ease);
}
.etp-character[data-etp-state="alert"] .etp-char-svg {
  transform: rotate(3deg);
  transition: transform 250ms var(--etp-ease);
}

/* Waiting — slow eye drift, dimmer pulse */
.etp-character[data-etp-state="waiting"] .etp-char-soul {
  opacity: 0.40;
  animation-duration: 20s;
}

/* ---------------------------------------------------------------------
 * 8b. Chest-core state coupling (2026-05-18 visual refresh)
 *
 * The primary inner-light core now lives on the chest disc, so per-state
 * brightness changes need to drive that surface too. The rules below
 * mirror the per-state opacity / tempo tweaks above onto
 * `.etp-char-chest-core-glow` so the chest reads the character's
 * emotional state at a glance from across the room.
 * ------------------------------------------------------------------- */

.etp-character[data-etp-state="listening"] .etp-char-chest-core-glow {
  opacity: 0.62;
  animation-duration: 2.4s;
}
.etp-character[data-etp-state="thinking"] .etp-char-chest-core-glow {
  opacity: 0.28;
  animation-duration: 12s;
}
.etp-character[data-etp-state="explaining"] .etp-char-chest-core-glow {
  opacity: 0.72;
  animation-duration: 2s;
}
.etp-character[data-etp-state="alert"] .etp-char-chest-core-glow {
  animation: etp-alert-flash 600ms var(--etp-ease);
}
.etp-character[data-etp-state="waiting"] .etp-char-chest-core-glow {
  opacity: 0.34;
  animation-duration: 20s;
}

/* ---------------------------------------------------------------------
 * 9. Entry / exit choreography (Section 6.4 + Section 8.1)
 * ------------------------------------------------------------------- */

@keyframes etp-char-enter {
  0%   { opacity: 0; transform: translateY(8px) scale(0.97); filter: blur(2px); }
  60%  { opacity: 1;                                          filter: blur(0); }
  100% { opacity: 1; transform: translateY(0)   scale(1);     filter: blur(0); }
}
.etp-character[data-etp-entering="true"] {
  animation: etp-char-enter var(--etp-entry-duration) var(--etp-ease) both;
}

@keyframes etp-char-exit {
  0%   { opacity: 1; transform: translateY(0)   scale(1);    filter: blur(0); }
  100% { opacity: 0; transform: translateY(-4px) scale(0.98); filter: blur(2px); }
}
.etp-character[data-etp-exiting="true"] {
  animation: etp-char-exit var(--etp-exit-duration) var(--etp-ease) both;
}

/* ---------------------------------------------------------------------
 * 10. Dashboard hero composition (Annex G.1)
 * ------------------------------------------------------------------- */

.etp-dashboard-hero {
  display: grid;
  grid-template-columns: minmax(280px, 45%) 1fr;
  gap: clamp(20px, 3vw, 40px);
  align-items: center;
  padding: clamp(20px, 3vw, 36px);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 18px;
  background: linear-gradient(
    135deg,
    rgba(150, 211, 95, 0.04) 0%,
    transparent 55%
  );
  margin-bottom: 24px;
}
.etp-dashboard-hero__character {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 360px;
}
.etp-dashboard-hero__welcome h1 {
  font-family: 'Playfair Display', 'Syne', Georgia, serif;
  font-size: clamp(28px, 3.4vw, 44px);
  font-weight: 600;
  line-height: 1.15;
  margin: 0 0 12px;
  background: linear-gradient(135deg, var(--etp-light) 0%, var(--etp-light-warm) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.etp-dashboard-hero__welcome .etp-welcome-sub {
  font-family: 'DM Sans', system-ui, sans-serif;
  color: var(--text2, #9ca3af);
  line-height: 1.55;
  margin: 0 0 16px;
}
.etp-dashboard-hero__welcome .etp-welcome-quota {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 999px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--etp-light);
}

@media (max-width: 760px) {
  .etp-dashboard-hero {
    grid-template-columns: 1fr;
    text-align: center;
  }
  .etp-dashboard-hero__character {
    min-height: 280px;
  }
}

/* ---------------------------------------------------------------------
 * 11. Chat conversational composition (Annex G.2)
 * ------------------------------------------------------------------- */

.etp-chat-character-strip {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
}
.etp-chat-character-strip .etp-character {
  flex: 0 0 auto;
}
.etp-chat-character-strip__meta {
  font-family: 'DM Sans', system-ui, sans-serif;
}
.etp-chat-character-strip__meta .etp-name {
  font-family: 'Playfair Display', 'Syne', Georgia, serif;
  font-weight: 600;
  font-size: 18px;
  color: var(--text, #e8f4fd);
  margin: 0 0 2px;
}
.etp-chat-character-strip__meta .etp-state-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--etp-light);
  opacity: 0.85;
}

/* ---------------------------------------------------------------------
 * 12. Classroom hero composition (Annex G.3)
 * Right-third character + left-two-thirds content.
 * ------------------------------------------------------------------- */

.etp-classroom-character {
  position: fixed;
  right: 4vw;
  top: 50%;
  transform: translateY(-50%);
  z-index: 30;
  pointer-events: none;
}
.etp-classroom-character .etp-character {
  pointer-events: auto;
}

@media (max-width: 980px) {
  .etp-classroom-character {
    right: 2vw;
  }
}

/* ---------------------------------------------------------------------
 * 13. Settings persona-preview composition (Annex G.4)
 * Each persona row shows the same character with different inner light.
 * ------------------------------------------------------------------- */

.etp-persona-row {
  display: grid;
  grid-template-columns: 96px 1fr auto;
  gap: 16px;
  align-items: center;
  padding: 14px 16px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
  margin-bottom: 10px;
  transition: border-color 200ms var(--etp-ease), background 200ms var(--etp-ease);
}
.etp-persona-row:hover {
  border-color: rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.04);
}
.etp-persona-row[data-active="true"] {
  border-color: var(--etp-light);
  background: rgba(150, 211, 95, 0.06);
}
.etp-persona-row__name {
  font-family: 'Playfair Display', 'Syne', Georgia, serif;
  font-weight: 600;
  font-size: 17px;
  margin: 0 0 4px;
}
.etp-persona-row__desc {
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: 13px;
  color: var(--text2, #9ca3af);
  line-height: 1.4;
  margin: 0;
}
.etp-persona-row__actions {
  display: flex;
  gap: 8px;
}

/* ---------------------------------------------------------------------
 * 14. Chrome silhouette (Annex G.6) — 24-40 px appearances
 * Replaces 🤖 emoji in sidebars, profile slots, notification dots.
 * ------------------------------------------------------------------- */

.etp-chrome {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--etp-chrome-size, 32px);
  height: var(--etp-chrome-size, 32px);
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 40%,
    var(--etp-light-glow) 0%,
    transparent 70%
  );
}
.etp-chrome svg {
  width: 78%;
  height: 78%;
}

/* ---------------------------------------------------------------------
 * 15. Reduced-motion compliance (Annex C.8 — non-negotiable)
 * State changes preserved; decorative motion disabled.
 * ------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .etp-char-svg,
  .etp-char-soul,
  .etp-char-chest-core-glow,
  .etp-char-eyes,
  .etp-char-eye,
  .etp-char-shimmer rect {
    animation: none !important;
  }
  .etp-character[data-etp-entering],
  .etp-character[data-etp-exiting] {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}

html[data-a11y-reduce-motion="true"] .etp-char-svg,
html[data-a11y-reduce-motion="true"] .etp-char-soul,
html[data-a11y-reduce-motion="true"] .etp-char-chest-core-glow,
html[data-a11y-reduce-motion="true"] .etp-char-eyes,
html[data-a11y-reduce-motion="true"] .etp-char-eye,
html[data-a11y-reduce-motion="true"] .etp-char-shimmer rect {
  animation: none !important;
}

/* High-contrast — flatten material, brighten outlines */
html[data-etp-theme="high-contrast"] .etp-char-body { fill: #000 !important; stroke: #fff; stroke-width: 1; }
html[data-etp-theme="high-contrast"] .etp-char-visor { fill: #000 !important; stroke: #ffe100; stroke-width: 1; }
html[data-etp-theme="high-contrast"] .etp-char-eye-iris { fill: #ffe100 !important; }
html[data-etp-theme="high-contrast"] .etp-char-eye-pupil { fill: #fff !important; }
html[data-etp-theme="high-contrast"] .etp-char-soul { fill: #ffe100 !important; opacity: 0.4 !important; }
html[data-etp-theme="high-contrast"] .etp-character::before { display: none; }
html[data-etp-theme="high-contrast"] .etp-character::after { display: none; }

/* Light theme — cool white body, matching the canonical reference
   avatar palette. Phase 1 alignment (2026-05-20): replaced cream
   tokens with cool-white variants so the light theme stays on-brand
   when activated. */
html[data-etp-theme="light"] {
  --etp-body-highlight: #FFFFFF;
  --etp-body-mid:       #F4F8FC;
  --etp-body-shadow:    #DCE6F0;
  --etp-body-deep:      #B5C5D6;
}

/* ---------------------------------------------------------------------
 * 16. Legacy mount overrides (2026-05-18 E2E follow-up)
 * The chat surface (tutor.html) mounts the character on a div that
 * also carries the legacy .avatar class (48 px circle with border +
 * overflow:hidden). The legacy class clipped the 100×130 SVG bust
 * to a 48-px circle, hiding the upper head. The sidebar profile slot
 * (.sidebar-profile__avatar) and sidebar nav icons (.sidebar-nav__icon)
 * have the same kind of legacy mount. Disable circle clipping + border
 * + min-width constraints when the new character system owns the mount.
 * ------------------------------------------------------------------- */

.avatar.etp-character,
.sidebar-profile__avatar.etp-character,
.sidebar-nav__icon.etp-character {
  width: var(--etp-char-width, 128px) !important;
  height: var(--etp-char-height, 166px) !important;
  border-radius: 0 !important;
  border: none !important;
  background: transparent !important;
  overflow: visible !important;
  font-size: 0 !important;
  /* Preserve flex behaviour of parent layout */
  flex-shrink: 0;
}

/* Chrome-scale mounts (sidebar profile + nav icons) are explicitly small. */
.sidebar-profile__avatar.etp-character[data-etp-scale="chrome"],
.sidebar-nav__icon.etp-character[data-etp-scale="chrome"] {
  width: var(--etp-chrome-size, 32px) !important;
  height: var(--etp-chrome-size, 32px) !important;
}

/* The legacy decorative pulse ring inside .avatar gets in the way of the
   new character. Hide it so the new bust is unobstructed. */
.avatar.etp-character .avatar__mood-ring,
.sidebar-profile__avatar.etp-character .avatar__mood-ring {
  display: none !important;
}

/* ---------------------------------------------------------------------
 * 17. Articulated arm (Item 1 — 2026-05-18 extension to Direction C)
 *
 * ===  Phase 36 Phase 5 (2026-05-30) — STRICT DELETE  ====================
 *
 * The Section 17 arm CSS block has been removed with the strict delete
 * of the arm SVG geometry. The previously-shipped rules — .etp-char-arm-group
 * visibility, .etp-char-arm-upper-pivot / .etp-char-arm-fore-pivot
 * transform-box + transition setup, .etp-char-arm-hand-glow soul-pulse
 * animation, [data-etp-arm-state="pointing"] highlight, and
 * @media (prefers-reduced-motion) + [data-a11y-reduce-motion="true"]
 * guards — are all gone. They were authoring the visual contract for
 * arms that no longer render. Shoulder caps + shoulder joints +
 * shoulder LEDs (Phase 8 + Phase 10) remain as terminal silhouette
 * elements, styled by their own selectors elsewhere in this file.
 * ------------------------------------------------------------------- */

/* ---------------------------------------------------------------------
 * 18. Micro-behaviour modifiers (Item 2 — 2026-05-18 extension)
 *
 * These ride on top of `data-etp-state` — they don't replace it. The
 * behaviour-bus curates the trigger→modifier mapping. Each modifier is
 * a single transient attribute that the JS sets + clears on a timer.
 *
 *   data-etp-behaviour-nod          400 ms vertical nod
 *   data-etp-behaviour-sparkle      1200 ms three-particle drift
 *   data-etp-behaviour-brow-soft    3 s soft-brow expression
 *   data-etp-behaviour-lean         4 s +3° forward lean toward viewer
 *   data-etp-behaviour-dim          250 ms inner-light dim
 * ------------------------------------------------------------------- */

/* Nod — single vertical bob, gentler than the Celebrating nod */
@keyframes etp-behaviour-nod {
  0%, 100% { transform: translateY(0); }
  40%      { transform: translateY(-1.4px); }
  70%      { transform: translateY(0.4px); }
}
.etp-character[data-etp-behaviour-nod="true"] .etp-char-svg {
  animation: etp-behaviour-nod 420ms var(--etp-ease-bounce);
}

/* Sparkles — three star particles drift up + fade out */
.etp-char-sparkles {
  pointer-events: none;
}
.etp-char-sparkle {
  opacity: 0;
}
@keyframes etp-behaviour-sparkle-a {
  0%   { opacity: 0; transform: translate(0, 4px) scale(0.8); }
  40%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-2px, -6px) scale(1.1); }
}
@keyframes etp-behaviour-sparkle-b {
  0%   { opacity: 0; transform: translate(0, 5px) scale(0.7); }
  45%  { opacity: 1; }
  100% { opacity: 0; transform: translate(3px, -5px) scale(1.0); }
}
@keyframes etp-behaviour-sparkle-c {
  0%   { opacity: 0; transform: translate(0, 6px) scale(0.6); }
  50%  { opacity: 1; }
  100% { opacity: 0; transform: translate(0, -8px) scale(1.2); }
}
.etp-character[data-etp-behaviour-sparkle="true"] .etp-char-sparkle--a {
  animation: etp-behaviour-sparkle-a 1100ms var(--etp-ease);
  transform-box: fill-box;
  transform-origin: 32px 12px;
}
.etp-character[data-etp-behaviour-sparkle="true"] .etp-char-sparkle--b {
  animation: etp-behaviour-sparkle-b 1100ms var(--etp-ease) 60ms;
  transform-box: fill-box;
  transform-origin: 68px 9px;
}
.etp-character[data-etp-behaviour-sparkle="true"] .etp-char-sparkle--c {
  animation: etp-behaviour-sparkle-c 1100ms var(--etp-ease) 120ms;
  transform-box: fill-box;
  transform-origin: 50px 5px;
}

/* Brow soften — opacity-fade the brow arcs in for the duration */
.etp-character[data-etp-behaviour-brow-soft="true"] .etp-char-brow-l,
.etp-character[data-etp-behaviour-brow-soft="true"] .etp-char-brow-r {
  opacity: 0.78;
  transition: opacity 320ms var(--etp-ease);
}

/* Lean closer — +3° forward lean of the entire bust toward viewer,
   composes with `data-etp-state` rotation by overriding via specificity.
   We use a separate inline transform via CSS variable so we don't fight
   the state-driven `transform: rotate(...)` rules. */
.etp-character[data-etp-behaviour-lean="true"] .etp-char-svg {
  transform: translateY(-1.6px) rotate(-2deg);
  transition: transform 520ms var(--etp-ease);
}

/* Inner-light dim — brief 250 ms dim, then return. Composes with
   state-driven opacity via animation. */
@keyframes etp-behaviour-dim {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 0.18; }
}
.etp-character[data-etp-behaviour-dim="true"] .etp-char-soul {
  animation: etp-behaviour-dim 260ms var(--etp-ease);
}

/* Reduced-motion: brow + dim still surface (no motion, just opacity);
   nod + sparkle + lean disabled (those are spatial). */
@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-behaviour-nod] .etp-char-svg,
  .etp-character[data-etp-behaviour-sparkle] .etp-char-sparkle--a,
  .etp-character[data-etp-behaviour-sparkle] .etp-char-sparkle--b,
  .etp-character[data-etp-behaviour-sparkle] .etp-char-sparkle--c,
  .etp-character[data-etp-behaviour-lean] .etp-char-svg {
    animation: none !important;
    transform: none !important;
  }
}
html[data-a11y-reduce-motion="true"] .etp-character[data-etp-behaviour-nod] .etp-char-svg,
html[data-a11y-reduce-motion="true"] .etp-character[data-etp-behaviour-sparkle] .etp-char-sparkle--a,
html[data-a11y-reduce-motion="true"] .etp-character[data-etp-behaviour-sparkle] .etp-char-sparkle--b,
html[data-a11y-reduce-motion="true"] .etp-character[data-etp-behaviour-sparkle] .etp-char-sparkle--c,
html[data-a11y-reduce-motion="true"] .etp-character[data-etp-behaviour-lean] .etp-char-svg {
  animation: none !important;
  transform: none !important;
}

/* =====================================================================
 * Phase 7 (2026-05-26) — virtual-robot redesign
 *
 * The user-supplied reference image at the bottom of this redesign
 * shows a polished virtual robot assistant with a CLEAN head circle
 * (no side ear ovals), a DEFINED geometric torso (no arm stubs at the
 * sides), and a STRONG framed chest-core module — without the diffuse
 * "soft floating blob" halo the prior build carried behind the face.
 *
 * The eye system, mouth, chest-core animation loop, streaming sync,
 * all 8 emotional states, all programme variants, all persona
 * variants, accessibility, and behaviour-bus modifiers are
 * intentionally NOT touched. Every change below is additive
 * (suppression overrides + one new rule) — no existing selector is
 * modified.
 *
 * Three subsystems are suppressed and one new module-frame is styled:
 *   A. Soft floating blob halo: ambient ::before vignette, body-wide
 *      rim drop-shadow, and the .etp-char-soul echo ellipse.
 *   B. Side oval ear shapes: .etp-char-ear-cup / -rim / -inner.
 *   C. Side hand/arm shapes: .etp-char-arm-group.
 *   D. Chest-core square frame: drop-shadow + transition.
 * ------------------------------------------------------------------- */

/* A.1 — Ambient ::before vignette. The big radial halo behind the
   whole character is what made it read as a soft floating blob.
   Suppressed entirely. Reference image has a dark background panel
   with NO atmospheric bleed-out behind the head. */
.etp-character::before {
  opacity: 0 !important;
}

/* A.2 — Body-wide rim-light drop-shadow. Section 3 (line 259-261)
   adds a 0.9px inner-light drop-shadow to every .etp-char-body
   element so the entire silhouette glows. The reference image keeps
   only a SUBTLE rim on the head circle (.etp-char-head-circle's own
   1.2px drop-shadow stays, see §3 line 250-252) — the body silhouette
   itself should read as defined and geometric, not glowing. */
.etp-char-body:not(.etp-char-head-circle) {
  filter: none !important;
}

/* A.3 — Soul echo ellipse. The cyan blurred ellipse behind the eyes
   (.etp-char-soul, SVG line 391-392 + CSS §4 line 516-525) is
   suppressed at baseline. Per-state opacity overrides in §8 (e.g.
   listening 0.68, celebrating 0.85) cascade UNCHANGED — only the
   baseline "always-on" 0.22 opacity is removed so the face no
   longer carries a permanent diffuse glow when the character is at
   Neutral / Waiting. */
.etp-char-soul {
  opacity: 0 !important;
}

/* B — Side oval ear shapes. Mirrors the established suppression
   pattern in §3 line 415-419 (antenna), §3 line 240-242 (legacy
   head), §3 line 295-302 (visor --subtle). Ear-cup geometry stays
   in the DOM at etp-character.js lines 333-356 for backward-compat
   with any state rule that grep'd for these selectors; visually
   invisible. */
.etp-char-ear-cup,
.etp-char-ear-cup-rim,
.etp-char-ear-cup-inner {
  opacity: 0 !important;
}

/* C — Side hand/arm shapes — Phase 36 Phase 5 (2026-05-30) — STRICT
   DELETE. The Phase 7 (2026-05-26) virtual-robot-redesign rule
   `display: none !important;` on `.etp-char-arm-group` is retired
   along with the actual `<g class="etp-char-arm-group">` SVG geometry
   itself. The selector targets a DOM node that no longer exists, so
   the rule is dead weight. Shoulder caps + shoulder joints + shoulder
   LEDs (Phase 8 + Phase 10) keep their own selectors and remain as
   terminal silhouette elements. */

/* D — Square chest-core module frame. The new rounded square sitting
   behind the existing circular glow / core / hot triplet at
   etp-character.js. Sits at viewBox (42, 100) 16×16 rx=3. The
   circle triplet still drives the pulse animation
   (etp-chest-core-pulse, .etp-char-streaming) and state-driven
   opacity rules — this square is a static framing layer that lifts
   the chest module's defined-geometric read per the reference
   image. Inherits programme accent via --etp-light-glow so the rim
   tint follows whichever programme is active. */
.etp-char-chest-core-square {
  /* Phase 12 (2026-05-27) Tier 0.2 — RETIRED. The 16×16 rounded
     framing square is removed from the JS-emitted SVG per the
     "remove the small square elements near the blue chest circle"
     brief. Cleaner focal-point read on the chest disc. Setting
     display:none is defence-in-depth for any cached SVG. */
  display: none !important;
}

/* =====================================================================
 * Phase 8 (2026-05-26b) — premium-robot redesign
 *
 * User-supplied reference: a high-fidelity render of a friendly
 * futuristic humanoid robot bust — silver helmet ring around a glossy
 * black face panel, cyan HUD interface marks, substantial side ear
 * modules with cyan accents, short dark cylindrical neck with cyan
 * trim, compact symmetric torso with rounded glossy shoulder caps +
 * dark mechanical joints, cylindrical robotic upper arms, and a square
 * recessed chest panel containing a cyan glowing circle with "ET+"
 * text. The eye system + smile + animation state machine + chest-core
 * pulse loop + streaming sync + programme/persona variants are
 * preserved unchanged — the existing selector tree drives all of
 * that, and this Phase 8 block layers material treatment over the
 * Phase 7 geometry.
 * ------------------------------------------------------------------- */

/* Material tokens for the new silver + charcoal + cyan palette.
   Scoped to .etp-character so the entire ET+ avatar can be re-themed
   without affecting other surfaces. */
.etp-character {
  --etp-helmet-silver:   #C7CED6;
  --etp-helmet-edge:     #2A2F38;
  --etp-screen:          #050811;
  --etp-shoulder-silver: #DDE2E7;
  --etp-joint-charcoal:  #20262F;
  --etp-neck-charcoal:   #1A1F28;
  --etp-arm-silver:      #C7CED6;
}

/* A.1 — Background plate gradient already applied inline on the
   <rect class="etp-char-bg-plate"> element. No additional styling
   needed here — kept selector for potential future overrides. */
.etp-char-bg-plate { pointer-events: none; }

/* A.3 — Helmet shell. Silver ring around the dark face panel.
   Canonical-spec rule: NO shadow effects. The dark drop-shadow that
   PR #524 used for weight is removed; the §3 ten-layer chrome ring
   highlights + the §1 environment (cosmic aura, contact shadow, floor
   reflection) provide depth and grounding instead. */
.etp-char-helmet-shell {
  fill: var(--etp-helmet-silver);
  stroke: var(--etp-helmet-edge);
  stroke-width: 0.8;
}

/* Helmet crown notch — small silver bump at the top of the head. */
.etp-char-helmet-notch {
  fill: var(--etp-helmet-silver);
  stroke: var(--etp-helmet-edge);
  stroke-width: 0.4;
}

/* Face panel — the .etp-char-head-circle hosts the eyes + HUD + smile.
   Phase 10 (2026-05-26d): the inline `style="fill: url(#etpFaceGrad_*)"`
   on the <circle> now drives a curved-OLED radial gradient. The
   `!important` previously needed here (to override the inline `fill=`
   ATTRIBUTE on the legacy body-grad version) is GONE so the new
   inline `style=""` property — which beats CSS via inline-style
   specificity — wins cleanly. The cyan rim drop-shadow from §3 still
   wraps the panel edge for the backlit feel. The Phase 9 `--etp-screen`
   override (line 1591) is a fallback for any future revert. */
.etp-char-head-circle {
  fill: var(--etp-screen);
}

/* A.4 — HUD interface marks on the face panel. Thin cyan strokes
   so the face reads as a "digital advanced" panel. Very low
   opacity so the eyes stay the dominant feature. */
.etp-char-face-hud-mark {
  fill: none;
  stroke: var(--etp-light-deep, #0098D8);
  stroke-width: 0.4;
  stroke-linecap: round;
  opacity: 0.7;
  filter: drop-shadow(0 0 0.6px var(--etp-light-glow));
  pointer-events: none;
}

/* A.2 — Side ear modules. Silver speaker-style pods with vertical
   glowing cyan accent slots. New selector tree — the previous
   .etp-char-ear-cup (suppressed via Phase 7 opacity:0) stays
   suppressed; these are different elements. */
.etp-char-ear-module {
  pointer-events: none;
}
.etp-char-ear-module-pad {
  fill: var(--etp-helmet-silver);
  stroke: var(--etp-helmet-edge);
  stroke-width: 0.4;
}
.etp-char-ear-module-cyan {
  fill: var(--etp-light);
  opacity: 0.92;
  filter: drop-shadow(0 0 1.6px var(--etp-light-glow));
}

/* A.7 — Neck. Short dark cylinder with cyan trim ring at the
   torso join. Replaces the body-tone rectangle that was previously
   the only neck geometry. */
.etp-char-neck {
  fill: var(--etp-neck-charcoal);
}
.etp-char-neck-trim {
  fill: var(--etp-light);
  opacity: 0.85;
  filter: drop-shadow(0 0 1.4px var(--etp-light-glow));
}

/* A.8 — Shoulder caps + joint seams. The caps are separate rounded
   modules on top of the torso; the joints are dark seams between
   the caps and the upper arms beneath. */
.etp-char-shoulder-cap {
  fill: var(--etp-shoulder-silver);
  filter: drop-shadow(0 0 0.8px rgba(0, 0, 0, 0.35));
}
.etp-char-shoulder-joint {
  fill: var(--etp-joint-charcoal);
}

/* A.9 — Arms — Phase 36 Phase 5 (2026-05-30) — STRICT DELETE.
   Phase 8's hero/classroom re-enable of `.etp-char-arm-group` and
   the `.etp-char-arm-upper { fill: var(--etp-arm-silver) !important; }`
   tone are both retired with the actual arm SVG geometry. Selectors
   target DOM nodes that no longer exist. The shoulder caps continue
   to terminate the silhouette per the canonical reference render. */

/* Body rim — restore a soft charcoal edge drop-shadow on the torso
   silhouette. Phase 7 removed this to kill the cyan glow halo; we
   bring it back in helmet-edge charcoal so it reads as material
   edge rather than glow. The HEAD-only cyan rim from §3 stays
   unchanged on the helmet shell. */
.etp-char-body:not(.etp-char-head-circle) {
  filter: drop-shadow(0 0 0.6px rgba(0, 0, 0, 0.45)) !important;
}

/* A.10 — Chest "ET+" brand mark.
   Phase 12 (2026-05-27) Tier 0.5 — PROMOTED. The chest core's
   defining visual identity. Was a label *on* the disc; now it IS
   the disc's brand identity (the white-hot dot it replaces has
   been retired). Heavier weight (700→800), pure white fill
   against the warmer teal disc (was warm cyan), hairline charcoal
   stroke for legibility against varying glow intensity, emissive
   text-shadow so the text glows from within the disc rather than
   floats above it, slightly tighter letter-spacing for the
   premium feel. The chest-core pulse loop continues to drive the
   underlying disc; the brand mark rides along with it. */
.etp-char-chest-brand {
  font-family: 'JetBrains Mono', 'Fira Code', monospace;
  font-size: 5.2px;
  font-weight: 800;
  /* Phase 24.A — Wordmark legibility polish at 7.488px.
     letter-spacing tightened -0.05em → -0.06em so E, T, + sit
     visually tighter at the larger Phase 22.5 size.
     filter drop-shadow blur lifted 1.2px → 1.6px so the
     emissive cyan glow reads at convo + classroom scales,
     not just hero. font-size + font-weight overridden by
     Phase 22.5 + Phase 21.3 cascade. */
  letter-spacing: -0.06em;
  fill: #FFFFFF;
  stroke: rgba(10, 31, 51, 0.3);
  stroke-width: 0.3;
  paint-order: stroke fill;
  user-select: none;
  pointer-events: none;
  filter: drop-shadow(0 0 1.6px rgba(0, 220, 236, 0.45));
}

/* Phase 12 (2026-05-27) Tier 2.1 — per-letter ET+ pulse on
   stream-chunk. The three tspan letters (E, T, +) each pulse
   30ms apart on every assistant message — sells the disc as a
   live indicator rather than a static label. Default state:
   transparent (defers to .etp-char-chest-brand's filter). On
   .etp-char-streaming, each letter gets a staggered animation. */
.etp-char-chest-brand-e,
.etp-char-chest-brand-t,
.etp-char-chest-brand-plus {
  transition: filter 220ms var(--etp-ease);
}
@keyframes etp-chest-brand-letter-pulse {
  0%, 100% { filter: drop-shadow(0 0 0.6px rgba(255, 255, 255, 0.3)); }
  50%      { filter: drop-shadow(0 0 1.8px rgba(0, 220, 236, 0.95)); }
}
.etp-character.etp-char-streaming .etp-char-chest-brand-e {
  animation: etp-chest-brand-letter-pulse 320ms ease-out;
  animation-delay: 0ms;
}
.etp-character.etp-char-streaming .etp-char-chest-brand-t {
  animation: etp-chest-brand-letter-pulse 320ms ease-out;
  animation-delay: 30ms;
}
.etp-character.etp-char-streaming .etp-char-chest-brand-plus {
  animation: etp-chest-brand-letter-pulse 320ms ease-out;
  animation-delay: 60ms;
}

/* Phase 12 (2026-05-27) Tier 2.2 — hairline registration crosshair
   at the disc centre. The two thin lines defined inline in the
   SVG sit at 0.6 opacity by default; CSS adds programme-aware
   colour and a subtle transition so they fade smoothly when the
   chest state changes. */
.etp-char-chest-core-crosshair line {
  transition: opacity 220ms var(--etp-ease);
}
@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-core-crosshair line {
    opacity: 0.4;
  }
}

/* Phase 12 (2026-05-27) Tier 2.3 — 20-second scan-cycle line
   sweep across the ET+ text. Reads as the disc's optics
   "scanning" / verifying the brand mark — implies the chest
   core is a live precision sensor, not a static label. Default
   opacity 0 (invisible); keyframe animates y-position + opacity
   so the line appears, sweeps from above the text down through
   it and disappears, every 20 seconds. */
.etp-char-chest-brand-scan {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: etp-chest-brand-scan-cycle 20s ease-in-out infinite;
  pointer-events: none;
}
@keyframes etp-chest-brand-scan-cycle {
  0%   { transform: translateY(-2.6px); opacity: 0; }
  4%   { transform: translateY(-2.6px); opacity: 0.7; }
  8%   { transform: translateY( 0px);   opacity: 0.85; }
  12%  { transform: translateY( 2.6px); opacity: 0.7; }
  16%  { transform: translateY( 2.6px); opacity: 0; }
  100% { transform: translateY( 2.6px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-brand-scan {
    animation: none;
    opacity: 0;
  }
}

/* Phase 12 (2026-05-27) Tier 2.5 — iridescent rim on chest disc.
   Mirror of Phase 11 helmet iridescence pattern applied to the
   chest disc rim. 6% opacity so it's a subtle hue shift along
   the rim — pink/blue/green hint that catches the eye only when
   light hits the disc from the right angle. Compose-friendly:
   uses mix-blend-mode screen so the iridescence blends with
   whatever colour is beneath. */
.etp-char-chest-core-rim {
  /* Augments the existing rim stroke in §4 — adding a subtle
     iridescent under-glow without breaking the crisp cyan rim. */
  filter: drop-shadow(0 0 0.4px rgba(255, 192, 220, 0.06))
          drop-shadow(0 0 0.4px rgba(192, 220, 255, 0.06))
          drop-shadow(0 0 0.4px rgba(192, 255, 220, 0.06));
}

/* Phase 12 (2026-05-27) Tier 2.6 — eye-settling micro-state was
   considered but dropped to avoid conflict with the existing
   .etp-char-eye blink-pulse loop (§7) and the per-state pupil
   transforms. Phase 11 Tier 0 already ships eye-cursor tracking,
   saccade patterns, and eye dilation on user-message — those
   collectively deliver the "watching and listening" presence
   this rule was meant to add. Premium restraint: ship fewer
   animations, not more. */

/* ================================================================
   Phase 9 (2026-05-26c) — friendly-robot polish.

   Third user-driven redesign of the day, refining the premium-robot
   pass (PR #366) to match a new high-fidelity reference render. The
   reference shows a warmer, friendlier student-facing humanoid:
   - Glossy DARK CYAN/TEAL face panel (not pure black).
   - Continuous thin cyan HUD perimeter ring inside the panel.
   - Smaller ear modules ATTACHED to the helmet (JS-side geometry).
   - NO upper arms below the shoulder caps.
   - Maximum-opacity glowing smile.

   Eye system, mouth path, chest-core animation, state machine,
   programme variants, persona variants, and pointAt() invariant
   all preserved unchanged.
   ================================================================ */

.etp-character {
  /* Shift the face panel from cold near-black to dark teal-cyan so
     the "digital screen" reads as live UI rather than blank void. */
  --etp-screen: #0C2638;
}

/* Continuous HUD perimeter ring just inside the face panel — the
   "advanced UI" frame visible in the reference. Drawn as a thin
   stroke with subtle cyan glow so the eyes still dominate. */
.etp-char-face-hud-ring {
  fill: none;
  stroke: var(--etp-light-deep);
  stroke-width: 0.35;
  opacity: 0.55;
  filter: drop-shadow(0 0 0.4px var(--etp-light-glow));
}

/* Smile polish — push the cyan to full opacity for the brighter
   curved glow visible in the reference. Path geometry unchanged
   (still M 41 47 Q 50 53.2 59 47) so the eye-system invariant
   continues to hold. */
.etp-char-mouth {
  stroke-opacity: 1;
}

/* DEFINITIVE arm suppression — Phase 36 Phase 5 (2026-05-30) — STRICT
   DELETE. Phase 9's display:none !important rule on `.etp-char-arm-group`
   (with hero + classroom variant selectors) is retired with the actual
   arm SVG geometry. The selectors targeted a DOM node that no longer
   exists. The reference render's "zero arms below the shoulder caps"
   contract is now enforced by the SVG-level absence of arm elements
   rather than CSS-level hiding. */

/* =====================================================================
 * Phase 10 (2026-05-26d) — definitive-future avatar upgrade
 *
 * Implements the approved design strategy at
 * ~/.claude/plans/use-your-ultimate-design-resilient-floyd.md
 *
 * Layers all 7 phases on top of the existing Phase 9 friendly-robot
 * polish via additive class selectors. The 8-state machine, public
 * API, event-bus contract, programme variants, and persona variants
 * are preserved unchanged. The eye-system invariants (halo / iris /
 * lens / pupil / pupil-dot / lid / arc) are preserved unchanged — the
 * new aperture-blade + warm-spark layers compose on top of them.
 *
 *   Phase A — Aperture-iris eyes (SIGNATURE feature)
 *   Phase B — Head architecture (cranial seam + crown LEDs + specular
 *             + key-light rim + brushed grain)
 *   Phase C — Face panel curved-OLED gradient + face specular arc
 *             + chest core ghost copy
 *   Phase D — Per-state expression (mouth + aperture + eyelid)
 *   Phase E — Three-point lighting (helmet rim + shoulder LEDs +
 *             neck articulation segments)
 *   Phase F — Background particles + circuit grid
 *   Phase G — Behavioural motion (seam shimmer + crown LED breath +
 *             head micro-tilt + double-blink + event reactions)
 *
 *   Mood target: "An educational companion designed in 2035 by a studio
 *   that worships craft — quietly intelligent, warmly attentive, with
 *   the engineered honesty of premium hardware."
 * ===================================================================== */

/* Phase 10 — refined material tokens. */
.etp-character {
  /* Slight warmth shift on the iris (Recommendation 17). */
  --etp-eye-iris:        #5BB4E8;
  /* Brushed-aluminum tone (Recommendation 5). */
  --etp-helmet-silver:   #EAECF1;
  --etp-shoulder-silver: #ECEEF3;
  /* New tokens for Phase 10 features. */
  --etp-warm-fill:       #FFF4E0;
  --etp-cool-rim:        #A8DCFA;
  --etp-aperture-blade:  rgba(58, 159, 214, 0.32);
  --etp-cream-spark:     #FFF4E0;
  --etp-seam-glow:       rgba(94, 224, 255, 0.65);
}

/* Phase C — the face panel <circle> now carries
   `style="fill: url(#etpFaceGrad_*)"` inline. The Phase 8
   `!important` rule on `.etp-char-head-circle` has been edited to
   remove `!important` (see line 1473 above), so the inline style
   (which beats normal-specificity CSS via inline-style cascade
   priority) wins cleanly. The drop-shadow rim-light continues to
   apply through §3. */

/* -------------------------------------------------------------------
 * Phase A — Aperture-iris eyes (SIGNATURE FEATURE)
 *
 * 8 thin triangular blade paths around the pupil. Default at scale(1.0)
 * = "70% aperture open" visual. Per-state scale contracts (>1 = blades
 * cover more iris = narrower aperture) or dilates (<1 = blades shrink
 * toward pupil = wider open aperture).
 * ------------------------------------------------------------------- */

.etp-char-eye-aperture {
  pointer-events: none;
  transform-box: fill-box;
  /* transform-origin set per-side below so the scale pivots around
     the pupil centre of each eye. */
  transition: transform 320ms var(--etp-ease), opacity 220ms var(--etp-ease);
}
.etp-char-eye-aperture--l {
  transform-origin: 40px 30px;
}
.etp-char-eye-aperture--r {
  transform-origin: 60px 30px;
}
.etp-char-eye-aperture-blade {
  fill: var(--etp-aperture-blade);
  /* Soft blur on each blade so the aperture pattern reads as optical
     depth rather than hard geometry. */
  filter: blur(0.12px);
}

/* Per-state aperture (Phase D mapped here for cohesion). */
.etp-character[data-etp-state="neutral"]    .etp-char-eye-aperture { transform: scale(1.00); }
.etp-character[data-etp-state="listening"]  .etp-char-eye-aperture { transform: scale(0.92); }
.etp-character[data-etp-state="thinking"]   .etp-char-eye-aperture { transform: scale(1.18); }
.etp-character[data-etp-state="explaining"] .etp-char-eye-aperture { transform: scale(0.84); }
.etp-character[data-etp-state="encouraging"] .etp-char-eye-aperture { transform: scale(0.92); }
.etp-character[data-etp-state="celebrating"] .etp-char-eye-aperture { transform: scale(0.78); }
.etp-character[data-etp-state="alert"]      .etp-char-eye-aperture { transform: scale(1.45); }
.etp-character[data-etp-state="waiting"]    .etp-char-eye-aperture { transform: scale(0.98); }

/* Hide aperture blades during closed-eye states (Encouraging,
   Celebrating) — the eye-arc replaces the iris+aperture for those. */
.etp-character[data-etp-state="encouraging"] .etp-char-eye-aperture,
.etp-character[data-etp-state="celebrating"] .etp-char-eye-aperture {
  opacity: 0;
}

/* Scale-gating: blades only visible at classroom + hero (per
   Recommendation 20). At convo + chrome the aperture is hidden so
   the iris reads cleanly at small sizes. */
.etp-character[data-etp-scale="chrome"] .etp-char-eye-aperture,
.etp-character[data-etp-scale="convo"]  .etp-char-eye-aperture {
  display: none;
}

/* Warm cream spark inside the iris. Default low-opacity warm point;
   rises on encouraging/empathy states to communicate warmth without a
   palette change. Hero-only — invisible at smaller scales. */
.etp-char-eye-warm-spark {
  fill: var(--etp-cream-spark);
  opacity: 0.42;
  filter: drop-shadow(0 0 0.4px rgba(255, 244, 224, 0.65));
  transition: opacity 220ms var(--etp-ease);
}
.etp-character[data-etp-state="encouraging"] .etp-char-eye-warm-spark,
.etp-character[data-etp-state="celebrating"] .etp-char-eye-warm-spark {
  opacity: 0.78;
}
.etp-character[data-etp-state="thinking"] .etp-char-eye-warm-spark {
  opacity: 0.18;
}
.etp-character[data-etp-scale="chrome"] .etp-char-eye-warm-spark,
.etp-character[data-etp-scale="convo"]  .etp-char-eye-warm-spark {
  display: none;
}

/* Persona micro-shift on warm spark (Patient Mentor +20%, Pragmatic
   Engineer −50%) per Recommendation 17. */
.etp-character[data-etp-persona="patient_mentor"]    .etp-char-eye-warm-spark { opacity: 0.55; }
.etp-character[data-etp-persona="pragmatic_engineer"] .etp-char-eye-warm-spark { opacity: 0.20; }

/* -------------------------------------------------------------------
 * Phase B — Head architecture
 * ------------------------------------------------------------------- */

/* Light-emitting cranial seam — SIGNATURE feature B. Thin cyan line
   running across the helmet at eye-height. The central portion is
   visually covered by the face panel rendered after it in the SVG
   stacking order, so only the two helmet-ring sections (x≈22-28 and
   x≈72-78) show. Idle shimmer animation at 11s — see Phase G. */
.etp-char-cranial-seam {
  stroke: var(--etp-light);
  stroke-width: 0.45;
  stroke-linecap: round;
  opacity: 0.72;
  filter: drop-shadow(0 0 0.8px var(--etp-seam-glow));
  pointer-events: none;
}
.etp-character[data-etp-scale="chrome"] .etp-char-cranial-seam {
  display: none; /* Cranial seam off at chrome — too small to read */
}

/* Helmet specular highlight — neutral white arc along top-left of the
   helmet shell. Implies a soft key light from above-left. */
.etp-char-helmet-specular {
  stroke: rgba(255, 255, 255, 0.30);
  stroke-width: 1.5;
  stroke-linecap: round;
  filter: blur(0.45px);
  pointer-events: none;
}

/* Helmet key-light cyan rim — paired with the white specular along
   the top edge. Reads as a cyan-tinted second key light. */
.etp-char-helmet-keylight {
  stroke: var(--etp-cool-rim);
  stroke-width: 0.8;
  stroke-linecap: round;
  opacity: 0.42;
  filter: blur(0.3px) drop-shadow(0 0 1.4px var(--etp-light-glow));
  pointer-events: none;
}

.etp-char-helmet-grain {
  pointer-events: none;
  mix-blend-mode: multiply;
}
.etp-character[data-etp-scale="chrome"] .etp-char-helmet-grain,
.etp-character[data-etp-scale="convo"]  .etp-char-helmet-grain {
  display: none;
}

/* Crown LED status column — three vertical cyan dots at the helmet
   crown. Visual rhyme with the chest core triplet + shoulder LED
   triplet (three triplet rhythms across the body). Top brightens on
   streaming events; middle breathes at 7s; bottom steady. */
.etp-char-crown-led-rail {
  fill: rgba(140, 150, 165, 0.55);
}
.etp-char-crown-led {
  fill: var(--etp-light);
  filter: drop-shadow(0 0 0.8px var(--etp-light-glow));
  transition: opacity 220ms var(--etp-ease), transform 220ms var(--etp-ease);
}
.etp-char-crown-led--top {
  opacity: 0.92;
}
.etp-char-crown-led--mid {
  opacity: 0.68;
  animation: etp-crown-led-breath 7s ease-in-out infinite;
}
.etp-char-crown-led--bot {
  opacity: 0.42;
}
.etp-character[data-etp-scale="chrome"] .etp-char-crown-leds {
  display: none;
}

/* -------------------------------------------------------------------
 * Phase C — Face panel + chest refinements
 * ------------------------------------------------------------------- */

/* Face panel top-right specular arc — reflection of a key light off
   curved glass. The single most consequential addition for selling
   premium curved-OLED material on the face panel. */
.etp-char-face-specular {
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 0.85;
  stroke-linecap: round;
  filter: blur(0.35px);
  pointer-events: none;
}
.etp-character[data-etp-scale="chrome"] .etp-char-face-specular {
  display: none;
}

/* Chest core ghost copy — barely-visible second core offset 1px
   below, blurred. Sells subsurface light scatter through translucent
   chest housing. */
.etp-char-chest-core-ghost {
  fill: var(--etp-light);
  opacity: 0.18;
  filter: blur(2.2px);
  pointer-events: none;
}
.etp-character[data-etp-scale="chrome"] .etp-char-chest-core-ghost {
  display: none;
}

/* Phase 12 (2026-05-27) — ET+ brand mark filter stack.
   PROMOTED treatment: the emissive cyan glow drives the brand
   mark's "embedded in the cyan disc" read (replaced the prior
   recessed/etched effect because the disc is now the brand's
   defining surface, not a chassis to etch into). Two cyan
   drop-shadows + a small white top-edge highlight compose to
   make the text feel both legible and luminous. */
.etp-char-chest-brand {
  filter: drop-shadow(0 0 1.2px rgba(0, 220, 236, 0.55))
          drop-shadow(0 0 0.6px rgba(255, 255, 255, 0.4))
          drop-shadow(0 -0.2px 0.2px rgba(255, 255, 255, 0.35));
}

/* -------------------------------------------------------------------
 * Phase D — Per-state expression (mouth)
 *
 * Mouth becomes a primary expression channel alongside eyes. Per-state
 * `d` property + stroke-width + cyan inner-glow. Mouth path uses CSS
 * Houdini `d:` (Chrome/Safari/Firefox 99+). Older browsers fall back
 * to the static neutral path with no harm.
 * ------------------------------------------------------------------- */

.etp-char-mouth {
  /* Cyan inner-glow filter — turns the painted line into an emissive
     surface. Composes with the existing stroke colour. */
  filter: drop-shadow(0 0 0.7px rgba(94, 224, 255, 0.42));
  transition: d 350ms var(--etp-ease),
              stroke 220ms var(--etp-ease),
              stroke-width 220ms var(--etp-ease);
}
.etp-character[data-etp-state="listening"]  .etp-char-mouth { d: path("M 42 47 Q 50 52 58 47"); }
.etp-character[data-etp-state="thinking"]   .etp-char-mouth { d: path("M 42 48 Q 50 49 58 48"); stroke-width: 1.7; }
.etp-character[data-etp-state="explaining"] .etp-char-mouth { d: path("M 40 47 Q 50 53.5 60 47"); }
.etp-character[data-etp-state="encouraging"] .etp-char-mouth { d: path("M 41 47 Q 50 54 59 47"); stroke-width: 2.2; }
.etp-character[data-etp-state="celebrating"] .etp-char-mouth { d: path("M 40 46 Q 50 55 60 46"); stroke-width: 2.4; }
.etp-character[data-etp-state="alert"]      .etp-char-mouth { d: path("M 43 48 Q 50 49 57 48"); stroke-width: 1.8; }
.etp-character[data-etp-state="waiting"]    .etp-char-mouth { d: path("M 42 48 Q 50 51 58 48"); }

/* -------------------------------------------------------------------
 * Phase E — Three-point lighting
 * ------------------------------------------------------------------- */

/* Per-shoulder cyan status LED. Visual rhyme with chest core +
   crown LEDs. */
.etp-char-shoulder-led {
  fill: var(--etp-light);
  opacity: 0.5;
  filter: drop-shadow(0 0 0.6px var(--etp-light-glow));
  pointer-events: none;
}
.etp-character[data-etp-scale="chrome"] .etp-char-shoulder-led,
.etp-character[data-etp-scale="convo"]  .etp-char-shoulder-led {
  display: none;
}

/* Neck articulation segment seams — two thin charcoal lines across
   the neck implying it's segmented for head rotation. */
.etp-char-neck-segment {
  stroke: rgba(8, 14, 26, 0.55);
  stroke-width: 0.35;
  stroke-linecap: round;
  pointer-events: none;
}
.etp-character[data-etp-scale="chrome"] .etp-char-neck-segment {
  display: none;
}

/* -------------------------------------------------------------------
 * Phase F — Background atmosphere
 * ------------------------------------------------------------------- */

.etp-char-bg-grid {
  /* Hidden at chrome + convo to keep small renders clean. At
     classroom + hero it adds engineered volumetric texture. */
}
.etp-character[data-etp-scale="chrome"] .etp-char-bg-grid,
.etp-character[data-etp-scale="convo"]  .etp-char-bg-grid {
  display: none;
}

/* Background particle field — 14 single-pixel cyan drift points.
   Each particle has a unique animation-delay set inline so the field
   reads as random parallax drift. */
.etp-char-particle {
  /* Phase 36 D-01 strict-literal: rgba(168,220,250,X) → rgba(94,224,255,X)
     across the entire codebase. See appendix at file tail for rationale. */
  fill: rgba(94, 224, 255, 0.5);
  animation: etp-particle-drift 30s linear infinite;
  pointer-events: none;
}
.etp-character[data-etp-scale="chrome"] .etp-char-particles,
.etp-character[data-etp-scale="convo"]  .etp-char-particles {
  display: none;
}

@keyframes etp-particle-drift {
  0% {
    transform: translateY(8px) scale(0.6);
    opacity: 0;
  }
  15% { opacity: 0.7; }
  85% { opacity: 0.5; }
  100% {
    transform: translateY(-12px) scale(1.0);
    opacity: 0;
  }
}

/* -------------------------------------------------------------------
 * Phase 5 (2026-06-14) §1 — cosmic environment field.
 * Environment-level glows + faint lines that sit BEHIND the figure.
 * These are the spec's explicit ENVIRONMENT blurs (allowed), wholly
 * distinct from the forbidden face-feature shadows: each is either a
 * light cyan glow/line or the spec's stated #000820 floor anchor — none
 * is an occlusion shadow on a face feature. Hidden at chrome + convo
 * scales so small renders stay clean.
 * ------------------------------------------------------------------- */

/* Cosmic aura — soft cyan radial glow behind the head, slow breathe. */
.etp-char-cosmic-aura {
  pointer-events: none;
  animation: etp-cosmic-aura-breathe 9s ease-in-out infinite;
}
@keyframes etp-cosmic-aura-breathe {
  0%, 100% { opacity: 0.85; transform: scale(1.0); }
  50%      { opacity: 1.0;  transform: scale(1.04); }
}

/* Contact shadow — the spec's stated #000820 floor anchor (an
   environment blur, NOT a face-feature shadow). Soft-edged via blur. */
.etp-char-contact-shadow {
  fill: #000820;
  opacity: 0.45;
  filter: blur(1.4px);
  pointer-events: none;
}

/* Environment perspective + depth lines — faint cyan, establish a
   volumetric stage behind the figure. */
.etp-char-env-line {
  stroke: rgba(94, 224, 255, 0.10);
  stroke-width: 0.25;
  pointer-events: none;
}
.etp-char-env-line--persp {
  stroke: rgba(94, 224, 255, 0.07);
}

/* Environment rim lights — thin cyan arcs framing the head's outer
   edges as cosmic back-light (mirror pair about x=50). Centred 0 0
   glow (light, not an offset occlusion shadow). */
.etp-char-env-rim {
  stroke: rgba(94, 224, 255, 0.32);
  stroke-width: 0.45;
  stroke-linecap: round;
  filter: drop-shadow(0 0 0.8px rgba(94, 224, 255, 0.5));
  pointer-events: none;
}

/* Scale gating — environment is hero + classroom only. */
.etp-character[data-etp-scale="chrome"] .etp-char-cosmic,
.etp-character[data-etp-scale="convo"]  .etp-char-cosmic {
  display: none;
}

/* Reduced motion — freeze the aura breathe (static glow remains). */
@media (prefers-reduced-motion: reduce) {
  .etp-char-cosmic-aura { animation: none; }
}

/* High-contrast themes — neutralise the cosmic field so the figure's
   own high-contrast silhouette dominates. */
@media (prefers-contrast: more) {
  .etp-char-env-line,
  .etp-char-env-line--persp { stroke: rgba(94, 224, 255, 0.04); }
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-cosmic,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-cosmic {
  display: none;
}

/* -------------------------------------------------------------------
 * Phase 7 (2026-06-14) §2 / §16 — QUANTUM ORBIT RINGS
 * Three dashed elliptical electron orbits centred on the head (50, 36).
 * Each ring group rotates about the exact head centre (transform-box:
 * view-box so the bead never skews the pivot) at a co-prime speed —
 * orbit-cw 18 s / orbit-ccw 26 s / orbit-cw 38 s — so the three never
 * re-sync. A small electron bead rides each rim and pulses (tring 3.5 s)
 * out of phase. Cyan luminosity only — no occlusion shadow. Hero +
 * classroom only; frozen on reduced-motion; dropped on high-contrast.
 * ------------------------------------------------------------------- */
.etp-char-orbits { pointer-events: none; }

.etp-char-orbit-ring {
  /* Pivot on the exact head centre regardless of each group's bbox. */
  transform-box: view-box;
  transform-origin: 50px 36px;
}
.etp-char-orbit-ellipse {
  stroke: var(--etp-eye-cyan, #2BE5FF);
  stroke-width: 0.28;
  stroke-dasharray: 1.4 2.6;
  opacity: 0.42;
  filter: drop-shadow(0 0 0.45px rgba(43, 229, 255, 0.45));
}
.etp-char-orbit-bead {
  fill: var(--etp-eye-cyan, #2BE5FF);
  opacity: 0.9;
  transform-box: fill-box;
  transform-origin: center;
  filter: drop-shadow(0 0 0.7px rgba(43, 229, 255, 0.7));
}
@keyframes etp-orbit-cw  { from { transform: rotate(0deg); }   to { transform: rotate(360deg); } }
@keyframes etp-orbit-ccw { from { transform: rotate(0deg); }   to { transform: rotate(-360deg); } }
/* Orbit-bead twinkle — own keyframe to avoid clashing with the pre-existing
 * `etp-tring` tech-scan-ring rotation defined later in this file. */
@keyframes etp-orbit-bead {
  0%   { opacity: 0.4; transform: scale(0.7); }
  50%  { opacity: 1.0; transform: scale(1.35); }
  100% { opacity: 0.4; transform: scale(0.7); }
}
/* Spin each ring (hero + classroom only). */
.etp-character[data-etp-scale="hero"]      .etp-char-orbit-ring--1,
.etp-character[data-etp-scale="classroom"] .etp-char-orbit-ring--1 {
  animation: etp-orbit-cw 18s linear infinite;
}
.etp-character[data-etp-scale="hero"]      .etp-char-orbit-ring--2,
.etp-character[data-etp-scale="classroom"] .etp-char-orbit-ring--2 {
  animation: etp-orbit-ccw 26s linear infinite;
}
.etp-character[data-etp-scale="hero"]      .etp-char-orbit-ring--3,
.etp-character[data-etp-scale="classroom"] .etp-char-orbit-ring--3 {
  animation: etp-orbit-cw 38s linear infinite;
}
/* Pulse the electron beads out of phase. */
.etp-character[data-etp-scale="hero"]      .etp-char-orbit-bead,
.etp-character[data-etp-scale="classroom"] .etp-char-orbit-bead {
  animation: etp-orbit-bead 3.5s ease-in-out infinite;
}
.etp-character[data-etp-scale="hero"]      .etp-char-orbit-ring--2 .etp-char-orbit-bead,
.etp-character[data-etp-scale="classroom"] .etp-char-orbit-ring--2 .etp-char-orbit-bead { animation-delay: -1.17s; }
.etp-character[data-etp-scale="hero"]      .etp-char-orbit-ring--3 .etp-char-orbit-bead,
.etp-character[data-etp-scale="classroom"] .etp-char-orbit-ring--3 .etp-char-orbit-bead { animation-delay: -2.33s; }
/* Chrome favicon + convo: orbits are noise — suppress. */
.etp-character[data-etp-scale="chrome"] .etp-char-orbits,
.etp-character[data-etp-scale="convo"]  .etp-char-orbits {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .etp-char-orbit-ring,
  .etp-char-orbit-bead { animation: none !important; transform: none; }
}
.etp-character[data-etp-theme="high-contrast"]     .etp-char-orbits,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-orbits {
  display: none;
}

/* -------------------------------------------------------------------
 * Phase G — Behavioural motion + event-reactive micro-tells
 * ------------------------------------------------------------------- */

/* Cranial seam shimmer — slow brightness wave along the seam, 11s
   period. Co-prime with chest pulse (1.6s, 6.7s configured) + eye
   drift (9.7s) + blink (7.1s) so the combined motion never re-syncs. */
@keyframes etp-cranial-seam-shimmer {
  0%, 100% { opacity: 0.5;  filter: drop-shadow(0 0 0.5px var(--etp-seam-glow)); }
  50%      { opacity: 0.85; filter: drop-shadow(0 0 1.2px var(--etp-seam-glow)); }
}
.etp-char-cranial-seam {
  animation: etp-cranial-seam-shimmer 11s ease-in-out infinite;
}

/* Crown LED middle breath — 7s slow opacity oscillation. */
@keyframes etp-crown-led-breath {
  0%, 100% { opacity: 0.48; }
  50%      { opacity: 0.82; }
}

/* Event-reactive micro-tells. The streaming class is already set by
   the existing stream-chunk handler; extend its effect to also pulse
   the crown LEDs + eye corona + mouth glow. */
.etp-character.etp-char-streaming .etp-char-crown-led--top {
  opacity: 1;
  filter: drop-shadow(0 0 1.6px var(--etp-light)) drop-shadow(0 0 0.5px var(--etp-light));
}
.etp-character.etp-char-streaming .etp-char-eye-halo {
  filter: blur(3.2px) drop-shadow(0 0 2.4px var(--etp-light));
}
.etp-character.etp-char-streaming .etp-char-mouth {
  filter: drop-shadow(0 0 1.2px rgba(94, 224, 255, 0.78));
}

/* Idle micro-tilt — a data-attribute toggle. The actual transform is
   applied inline by the JS scheduler (random direction per fire). */
.etp-character[data-etp-micro-tilt="true"] .etp-char-svg {
  /* No additional CSS — inline transform from JS handles it. */
}

/* Double-blink — applies a fast double-scale animation to the eye
   group. The data attribute is set by the JS scheduler. */
@keyframes etp-double-blink {
  0%, 100% { transform: scaleY(1.00); }
  20%, 28% { transform: scaleY(0.06); }
  50%      { transform: scaleY(1.00); }
  72%, 78% { transform: scaleY(0.06); }
}
.etp-character[data-etp-double-blink="true"] .etp-char-eye {
  transform-origin: 50% 50%;
  animation: etp-double-blink 480ms ease-in-out;
}

/* -------------------------------------------------------------------
 * Phase 10 — Reduced-motion overrides
 *
 * Honour prefers-reduced-motion by disabling all new animation loops.
 * Geometry stays — only motion is suppressed. The existing reduced-
 * motion block earlier in this file handles the legacy motion.
 * ------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .etp-char-cranial-seam,
  .etp-char-crown-led--mid,
  .etp-char-particle {
    animation: none !important;
    transition: none !important;
  }
  .etp-character[data-etp-double-blink="true"] .etp-char-eye {
    animation: none !important;
  }
  .etp-char-eye-aperture {
    transition: none !important;
  }
}

/* -------------------------------------------------------------------
 * Phase 10 — High-contrast overrides
 *
 * For users who enable prefers-contrast: more, strengthen the cyan
 * accents so the signature features (cranial seam, crown LEDs,
 * aperture, chest ghost) stay readable.
 * ------------------------------------------------------------------- */

@media (prefers-contrast: more) {
  .etp-char-cranial-seam   { opacity: 1; stroke-width: 0.7; }
  .etp-char-crown-led      { opacity: 1; }
  .etp-char-shoulder-led   { opacity: 0.85; }
  .etp-char-helmet-keylight { opacity: 0.7; }
  .etp-char-aperture-blade { fill: rgba(58, 159, 214, 0.55); }
  .etp-char-eye-warm-spark { opacity: 0.75; }
}

/* ===================================================================
 * Phase 11 (2026-05-26e) — Tier 0 + Tier 1 + Tier 2 mega-batch
 *
 * Ships 36 items across three tiers (per the
 * `use-your-ultimate-design-resilient-floyd.md` plan).
 *
 *   Tier 0 (12 items): quick-win behaviour + motion polish
 *   Tier 1 (12 items): near-term signature features
 *   Tier 2 (12 items): material + optical refinements
 *
 * Each item is scoped, additive, and reduced-motion-safe.
 * =================================================================== */

/* -------------------------------------------------------------------
 * Phase 11 — refined material tokens
 *
 * 2.1 Iridescent helmet-rim accent token (8% opacity ceiling).
 * 1.8 Per-state aperture-colour CSS custom property (state-driven
 *     warm/cool aperture shift).
 * 0.2 Adaptive pulse-period token (JS rewrites this on engagement
 *     signal — see etp-character.js Phase 11B).
 * 2.7 Per-state shoulder-LED intensity token.
 * 2.8 Brand-mark shadow-depth token (extends Phase 10's 3-layer
 *     drop-shadow under streaming pulse).
 * ------------------------------------------------------------------- */

.etp-character {
  /* Iridescent rim hue (pinkish→bluish→greenish at extreme angles).
     Phase 12 (2026-05-27) Tier 1.3 — lifted opacity 0.07→0.10 so
     the iridescent rim is visible at convo scale too, not just
     hero. Still subtle enough that it reads as material truth
     rather than decorative flourish. */
  --etp-iridescent-pink:  rgba(214, 174, 222, 0.10);
  --etp-iridescent-blue:  rgba(91, 180, 232, 0.10);
  --etp-iridescent-green: rgba(174, 222, 184, 0.10);
  /* Aperture-blade tint — overridden per state below */
  --etp-aperture-tint:   rgba(58, 159, 214, 0.35);
  /* Adaptive pulse period — default matches Phase 10 chest-pulse */
  --etp-pulse-period: 1.6s;
  /* Shoulder LED intensity */
  --etp-shoulder-led-opacity: 0.45;
  /* Brand-mark recess depth */
  --etp-brand-shadow-depth: 0.35;
  /* Time-of-day desaturation (set to 0.05 at night via JS data-attr) */
  --etp-tod-desat: 0;
}

/* -------------------------------------------------------------------
 * 0.4 Time-of-day palette shift — desaturate helmet shell + warm halo
 *     at night. Switched via body[data-etp-tod="night"] in JS.
 * ------------------------------------------------------------------- */

.etp-character[data-etp-tod="night"] {
  --etp-tod-desat: 0.05;
  --etp-iridescent-blue: rgba(91, 180, 232, 0.05);
  --etp-iridescent-pink: rgba(228, 196, 174, 0.08); /* warmer */
}

.etp-character[data-etp-tod="night"] .etp-char-helmet,
.etp-character[data-etp-tod="night"] .etp-char-helmet-band {
  filter: saturate(calc(1 - var(--etp-tod-desat)));
  transition: filter 1200ms ease;
}

/* -------------------------------------------------------------------
 * 0.2 Adaptive pulse tempo — chest pulse animation respects the
 *     --etp-pulse-period variable set by JS. The existing
 *     `etp-chest-core-pulse` keyframe stays; only the period changes.
 * ------------------------------------------------------------------- */

.etp-char-chest-core {
  animation-duration: var(--etp-pulse-period) !important;
}

/* -------------------------------------------------------------------
 * 0.5 Lens flare on celebrate — brief radial flare across the face
 *     panel. Reuses the visor shimmer keyframe pattern.
 * ------------------------------------------------------------------- */

.etp-char-face-flare {
  fill: none;
  pointer-events: none;
  opacity: 0;
}

@keyframes etp-face-lens-flare {
  0%   { opacity: 0; transform: scale(0.85); }
  18%  { opacity: 0.85; transform: scale(1.02); }
  38%  { opacity: 0.55; transform: scale(1.1); }
  100% { opacity: 0;    transform: scale(1.2); }
}

.etp-character[data-etp-state="celebrating"] .etp-char-face-flare {
  animation: etp-face-lens-flare 1500ms cubic-bezier(0.25, 0.46, 0.45, 0.94) 1 forwards;
  transform-origin: 50% 36px;
}

/* -------------------------------------------------------------------
 * 0.7 Crown LED morse-style language — different state, different
 *     blink cadence. Patterns:
 *       neutral / waiting → A (slow alternation 3.2s)
 *       thinking         → B (top→mid→bot rapid 800ms march)
 *       explaining       → C (sync flash 600ms)
 *       celebrating      → D (top double-flash 480ms)
 *       alert            → E (mid steady + top dim 1.2s)
 * ------------------------------------------------------------------- */

@keyframes etp-crown-led-thinking-march {
  0%, 100% { opacity: 0.3; }
  16%      { opacity: 0.95; }
  33%, 99% { opacity: 0.3; }
}

@keyframes etp-crown-led-sync-flash {
  0%, 100% { opacity: 0.5; }
  50%      { opacity: 1.0; }
}

@keyframes etp-crown-led-double-flash {
  0%, 30%, 100% { opacity: 1.0; }
  15%, 45%      { opacity: 0.2; }
}

.etp-character[data-etp-state="thinking"] .etp-char-crown-led--top {
  animation: etp-crown-led-thinking-march 800ms ease-in-out infinite both;
  animation-delay: 0ms;
}
.etp-character[data-etp-state="thinking"] .etp-char-crown-led--mid {
  animation: etp-crown-led-thinking-march 800ms ease-in-out infinite both;
  animation-delay: 266ms;
}
.etp-character[data-etp-state="thinking"] .etp-char-crown-led--bot {
  animation: etp-crown-led-thinking-march 800ms ease-in-out infinite both;
  animation-delay: 533ms;
}

.etp-character[data-etp-state="explaining"] .etp-char-crown-led {
  animation: etp-crown-led-sync-flash 600ms ease-in-out infinite both;
}

.etp-character[data-etp-state="celebrating"] .etp-char-crown-led--top {
  animation: etp-crown-led-double-flash 480ms ease-in-out 6 forwards;
}

.etp-character[data-etp-state="alert"] .etp-char-crown-led--mid {
  opacity: 1.0;
}
.etp-character[data-etp-state="alert"] .etp-char-crown-led--top {
  opacity: 0.25;
}

/* -------------------------------------------------------------------
 * 0.9 Chest ghost-copy blur modulation per state
 * ------------------------------------------------------------------- */

.etp-character[data-etp-state="alert"]      .etp-char-chest-core-ghost { filter: blur(0.6px); }
.etp-character[data-etp-state="celebrating"].etp-char-chest-core-ghost,
.etp-character[data-etp-state="celebrating"] .etp-char-chest-core-ghost { filter: blur(1.4px); }
.etp-character[data-etp-state="thinking"]   .etp-char-chest-core-ghost { filter: blur(1.0px); }
.etp-character[data-etp-state="waiting"]    .etp-char-chest-core-ghost { filter: blur(1.8px); }

.etp-char-chest-core-ghost {
  transition: filter 600ms ease;
}

/* -------------------------------------------------------------------
 * 0.10 Anticipatory eye dilation on user-message receipt — class
 *      .etp-char-anticipating fires via JS on etpux:user-message.
 *      Brightens eye corona +25% for ~600ms.
 * ------------------------------------------------------------------- */

.etp-character.etp-char-anticipating .etp-char-eye-corona,
.etp-character.etp-char-anticipating .etp-char-eye-halo {
  opacity: 0.95 !important;
  filter: brightness(1.25);
  transition: opacity 180ms ease, filter 180ms ease;
}

/* -------------------------------------------------------------------
 * 0.11 Eye-corona warm flash on empathy — orchestrates with 1.10
 *      empathy-pair. .etp-char-warm-pulse fires for 600ms when
 *      etpux:empathy-detected lands.
 * ------------------------------------------------------------------- */

@keyframes etp-warm-pulse-anim {
  0%   { filter: hue-rotate(0deg)  brightness(1.0); }
  50%  { filter: hue-rotate(12deg) brightness(1.18); }
  100% { filter: hue-rotate(0deg)  brightness(1.0); }
}

.etp-character.etp-char-warm-pulse .etp-char-eye-halo,
.etp-character.etp-char-warm-pulse .etp-char-eye-warm-spark {
  animation: etp-warm-pulse-anim 600ms ease-in-out 1 both;
}

/* -------------------------------------------------------------------
 * 0.3 Anticipatory head tilt — JS adds .etp-char-anticipating class
 *     for 380ms after etpux:user-message, before stream-start. The
 *     SVG rotates 1° toward camera (we use the existing svg.style
 *     transform path from Phase 10 micro-tilt but with a faster
 *     curve and smaller delta).
 *
 * 1.12 Surprise reaction — after ≥45s idle, the same hook fires a
 *      stronger 2° tilt + aperture-narrow + chest-brighten for
 *      350ms. .etp-char-surprised class drives the visual.
 * ------------------------------------------------------------------- */

.etp-char-eye-aperture {
  transition: transform 220ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
              fill 320ms ease;
}

.etp-character.etp-char-surprised .etp-char-eye-aperture {
  transform: scale(0.55) !important;
}

.etp-character.etp-char-surprised .etp-char-chest-core {
  filter: brightness(1.15);
  transition: filter 180ms ease;
}

/* -------------------------------------------------------------------
 * 1.6 Mouse-parallax 3D head shift — heavily damped multi-layer
 *     translation. Different layers get different offsets, creating
 *     a parallax depth illusion in pure SVG.
 *
 *     JS sets --etp-mouse-dx and --etp-mouse-dy on each .etp-character
 *     anchor. CSS multiplies them per layer.
 * ------------------------------------------------------------------- */

.etp-character {
  --etp-mouse-dx: 0px;
  --etp-mouse-dy: 0px;
}

.etp-char-helmet,
.etp-char-helmet-band,
.etp-char-helmet-shell,
.etp-char-helmet-notch {
  transform: translate(
    calc(var(--etp-mouse-dx) * 1.5),
    calc(var(--etp-mouse-dy) * 1.5)
  );
  transition: transform 320ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.etp-char-face-panel,
.etp-char-face-hud,
.etp-char-face-hud-mark,
.etp-char-face-hud-ring {
  transform: translate(
    calc(var(--etp-mouse-dx) * 0.8),
    calc(var(--etp-mouse-dy) * 0.8)
  );
  transition: transform 320ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.etp-char-eyes,
.etp-char-mouth {
  transform: translate(
    calc(var(--etp-mouse-dx) * 0.4),
    calc(var(--etp-mouse-dy) * 0.4)
  );
  transition: transform 320ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* -------------------------------------------------------------------
 * 1.7 Generative idle behaviours library — 12+ micro-actions firing
 *     during long idle. Each behaviour is a one-shot class.
 *     JS rotates through them every 14-22s during waiting state.
 * ------------------------------------------------------------------- */

@keyframes etp-idle-puzzled {
  0%, 100% { transform: rotate(0deg); }
  35%      { transform: rotate(-0.8deg); }
  65%      { transform: rotate(-0.4deg); }
}

@keyframes etp-idle-curiosity {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.04); }
}

@keyframes etp-idle-nod {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(0.5px); }
}

@keyframes etp-idle-glance-left {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(-1.3px); }
}

@keyframes etp-idle-glance-right {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(1.3px); }
}

@keyframes etp-idle-breathe {
  0%, 100% { transform: scale(1) translateY(0); }
  50%      { transform: scale(1.012) translateY(-0.4px); }
}

.etp-character[data-etp-idle="puzzled"]     .etp-char-svg { animation: etp-idle-puzzled 2.4s ease-in-out 1 both; }
.etp-character[data-etp-idle="curiosity"]   .etp-char-chest-core { animation: etp-idle-curiosity 2.0s ease-in-out 1 both; }
.etp-character[data-etp-idle="nod"]         .etp-char-svg { animation: etp-idle-nod 1.6s ease-in-out 1 both; }
.etp-character[data-etp-idle="glance-left"] .etp-char-eyes { animation: etp-idle-glance-left 2.0s ease-in-out 1 both; }
.etp-character[data-etp-idle="glance-right"].etp-char-svg .etp-char-eyes { animation: etp-idle-glance-right 2.0s ease-in-out 1 both; }
.etp-character[data-etp-idle="breathe"]     .etp-char-svg { animation: etp-idle-breathe 3.2s ease-in-out 1 both; }

/* -------------------------------------------------------------------
 * 1.8 Per-state aperture colour modulation — warmer cobalt when
 *     encouraging, cooler when alert. Uses --etp-aperture-tint.
 * ------------------------------------------------------------------- */

.etp-character[data-etp-state="encouraging"] { --etp-aperture-tint: rgba(102, 184, 229, 0.45); }
.etp-character[data-etp-state="celebrating"] { --etp-aperture-tint: rgba(114, 200, 238, 0.55); }
.etp-character[data-etp-state="thinking"]    { --etp-aperture-tint: rgba(70, 148, 196, 0.40); }
.etp-character[data-etp-state="alert"]       { --etp-aperture-tint: rgba(79, 168, 220, 0.55); }
.etp-character[data-etp-state="waiting"]     { --etp-aperture-tint: rgba(70, 148, 196, 0.30); }

.etp-char-aperture-blade {
  fill: var(--etp-aperture-tint);
  transition: fill 320ms ease;
}

/* -------------------------------------------------------------------
 * 1.9 Reply-confidence visualisation — aperture wider when AI is
 *     confident, narrower when uncertain. .etp-conf-{low|mid|high}
 *     class on the anchor sets the scale (low = narrow, high = wide).
 * ------------------------------------------------------------------- */

.etp-character.etp-conf-low  .etp-char-eye-aperture { transform: scale(0.55); }
.etp-character.etp-conf-mid  .etp-char-eye-aperture { transform: scale(0.78); }
.etp-character.etp-conf-high .etp-char-eye-aperture { transform: scale(0.92); }

/* -------------------------------------------------------------------
 * 1.10 Empathy mouth-softening + aperture-dilation pair — extends
 *      the etpux:empathy-detected handler. Adds a soft mouth path +
 *      slight aperture dilation, in addition to the existing
 *      "switch to encouraging state" behaviour.
 * ------------------------------------------------------------------- */

.etp-character.etp-char-empathy-active .etp-char-mouth {
  stroke-width: 1.3;
  transition: stroke-width 320ms ease;
}

.etp-character.etp-char-empathy-active .etp-char-eye-aperture {
  transform: scale(0.85);
}

/* -------------------------------------------------------------------
 * 1.11 Multi-emotion blending in transitions — 200ms intermediate
 *      frame at 50/50 between states. We achieve this by adding a
 *      generic .etp-char-transitioning class that softens transition
 *      timing on the key visual properties (mouth + aperture).
 * ------------------------------------------------------------------- */

.etp-character.etp-char-transitioning .etp-char-mouth,
.etp-character.etp-char-transitioning .etp-char-eye-aperture,
.etp-character.etp-char-transitioning .etp-char-eye-corona,
.etp-character.etp-char-transitioning .etp-char-chest-core {
  transition-duration: 380ms !important;
  transition-timing-function: cubic-bezier(0.45, 0.05, 0.55, 0.95) !important;
}

/* -------------------------------------------------------------------
 * 2.1 Iridescent helmet-rim gradient — barely-visible hue shift
 *     along helmet edge (≤8% opacity). Implemented as a thin SVG
 *     stroke overlay; the gradient stops are CSS variables.
 * ------------------------------------------------------------------- */

.etp-char-helmet-iridescent {
  fill: none;
  stroke-width: 0.4;
  opacity: 0.7; /* aggregate; per-stop ceiling is ~8% */
  pointer-events: none;
}

/* -------------------------------------------------------------------
 * 2.2 SVG-pattern face panel scanlines — Phase 10 deferred this
 *     because the inline `repeating-linear-gradient` approach didn't
 *     work in SVG. Here we ship a real <pattern> definition (the
 *     JS injects the <pattern>; this CSS just controls its opacity
 *     and scale-gating). Hero scale only — invisible at chrome.
 * ------------------------------------------------------------------- */

.etp-char-face-scanlines {
  fill: none;
  opacity: 0;
  pointer-events: none;
}

.etp-character[data-etp-scale="hero"] .etp-char-face-scanlines,
.etp-character[data-etp-scale="classroom"] .etp-char-face-scanlines {
  opacity: 0.10;
}

/* Phase 12 (2026-05-27) Tier 1.4 — surface scanlines at convo
   scale too, at half-strength (0.05 vs 0.10). At chat-window
   sizes the curved-OLED read is still visible as a faint
   shimmer that catches the eye when ET+ replies. */
.etp-character[data-etp-scale="convo"] .etp-char-face-scanlines {
  opacity: 0.05;
}

/* -------------------------------------------------------------------
 * 2.3 Per-state face panel gradient hue shift — gradient stops
 *     warm slightly during encouraging/celebrating, cool during
 *     thinking/alert. We override the radialGradient stops via
 *     CSS custom properties consumed by the JS-emitted <stop>.
 * ------------------------------------------------------------------- */

.etp-character {
  --etp-face-grad-inner: rgba(22, 80, 124, 0.78);
  --etp-face-grad-outer: rgba(7, 28, 49, 0.94);
}

.etp-character[data-etp-state="encouraging"],
.etp-character[data-etp-state="celebrating"] {
  --etp-face-grad-inner: rgba(28, 95, 142, 0.82);  /* warmer */
  --etp-face-grad-outer: rgba(10, 36, 60, 0.92);
}

.etp-character[data-etp-state="thinking"],
.etp-character[data-etp-state="alert"] {
  --etp-face-grad-inner: rgba(18, 70, 110, 0.80);  /* cooler */
  --etp-face-grad-outer: rgba(5, 22, 40, 0.96);
}

/* -------------------------------------------------------------------
 * 2.4 Anti-aliasing registration marks at ear modules — tiny corner
 *     brackets near each ear module suggesting precision-optic
 *     calibration crosshairs at ≤1% opacity. Hero scale only.
 * ------------------------------------------------------------------- */

.etp-char-reg-mark {
  fill: none;
  stroke-width: 0.3;
  stroke: rgba(91, 180, 232, 0.55);
  opacity: 0;
  pointer-events: none;
}

.etp-character[data-etp-scale="hero"] .etp-char-reg-mark {
  opacity: 0.012;
}

/* -------------------------------------------------------------------
 * 2.5 Brand-mark emissive glow — Phase 12 PROMOTED direction.
 *     Phase 11's recessed-etched stack is replaced by an emissive
 *     cyan halo because the disc is now the brand's defining
 *     surface, not a chassis to etch into. Highest-specificity
 *     definition (last in cascade) wins.
 * ------------------------------------------------------------------- */

.etp-char-chest-brand {
  filter: drop-shadow(0 0 1.2px rgba(0, 220, 236, 0.55))
          drop-shadow(0 0 0.6px rgba(255, 255, 255, 0.45))
          drop-shadow(0 -0.18px 0.18px rgba(255, 255, 255, 0.35));
}

.etp-character.etp-char-streaming .etp-char-chest-brand {
  filter: drop-shadow(0 0 1.8px rgba(0, 220, 236, 0.85))
          drop-shadow(0 0 0.8px rgba(255, 255, 255, 0.6))
          drop-shadow(0 -0.20px 0.20px rgba(255, 255, 255, 0.45));
  transition: filter 320ms ease;
}

/* -------------------------------------------------------------------
 * 2.6 Helmet swept-light keyframe — during long replies (>5s), a
 *     slow specular sweep travels from top-left to top-right.
 *     Triggered by JS via .etp-char-swept-light class.
 * ------------------------------------------------------------------- */

@keyframes etp-helmet-swept-light {
  0%   { transform: translateX(-12px) rotate(-2deg); opacity: 0; }
  18%  { opacity: 0.55; }
  50%  { opacity: 0.75; }
  82%  { opacity: 0.45; }
  100% { transform: translateX(12px)  rotate(2deg);  opacity: 0; }
}

.etp-character.etp-char-swept-light .etp-char-helmet-keylight {
  animation: etp-helmet-swept-light 4000ms cubic-bezier(0.45, 0.05, 0.55, 0.95) 1 forwards;
  transform-origin: 50% 12px;
}

/* -------------------------------------------------------------------
 * 2.7 Per-state shoulder LED intensity
 * ------------------------------------------------------------------- */

.etp-character[data-etp-state="alert"]       .etp-char-shoulder-led { opacity: 0.85; }
.etp-character[data-etp-state="celebrating"] .etp-char-shoulder-led { opacity: 0.78; }
.etp-character[data-etp-state="explaining"]  .etp-char-shoulder-led { opacity: 0.62; }
.etp-character[data-etp-state="waiting"]     .etp-char-shoulder-led { opacity: 0.30; }
.etp-character[data-etp-state="thinking"]    .etp-char-shoulder-led { opacity: 0.40; }

.etp-char-shoulder-led {
  transition: opacity 400ms ease;
}

/* -------------------------------------------------------------------
 * 2.9 Sub-pixel optical detail layer — micro-marks suggesting
 *     precision optics near the cranial seam join.
 * ------------------------------------------------------------------- */

.etp-char-optical-mark {
  fill: none;
  stroke: rgba(91, 180, 232, 0.7);
  stroke-width: 0.2;
  opacity: 0;
  pointer-events: none;
}

.etp-character[data-etp-scale="hero"]     .etp-char-optical-mark,
.etp-character[data-etp-scale="classroom"] .etp-char-optical-mark {
  opacity: 0.18;
}

/* -------------------------------------------------------------------
 * 2.10 Reactive specular position — JS sets --etp-host-light from
 *      page background lightness. CSS shifts the specular highlight
 *      angle accordingly.
 * ------------------------------------------------------------------- */

.etp-character {
  --etp-host-light: 0.5;
}

.etp-char-helmet-specular {
  transform: translateX(calc((var(--etp-host-light) - 0.5) * 1.2px));
  transition: transform 800ms ease;
}

/* -------------------------------------------------------------------
 * 2.11 Helmet grain pattern density per scale — coarser at hero,
 *      finer at classroom. JS inlines a <pattern> with density set;
 *      this CSS provides scale-aware opacity.
 * ------------------------------------------------------------------- */

.etp-char-helmet-grain {
  opacity: 0.04;
}

.etp-character[data-etp-scale="hero"] .etp-char-helmet-grain {
  opacity: 0.07; /* coarser, more visible at hero */
}

.etp-character[data-etp-scale="convo"]  .etp-char-helmet-grain { opacity: 0.025; }
.etp-character[data-etp-scale="chrome"] .etp-char-helmet-grain { opacity: 0; }

/* -------------------------------------------------------------------
 * 2.12 Chest core specular highlight on white-hot centre — a tiny
 *      lens-flare cross. .etp-char-chest-core-cross is two thin
 *      perpendicular lines through the white-hot dot.
 * ------------------------------------------------------------------- */

.etp-char-chest-core-cross {
  stroke: rgba(255, 255, 255, 0.85);
  stroke-width: 0.18;
  fill: none;
  opacity: 0.45;
}

.etp-character.etp-char-streaming .etp-char-chest-core-cross {
  opacity: 0.85;
  transition: opacity 200ms ease;
}

/* -------------------------------------------------------------------
 * Phase 11 Tier 1 — new SVG element styling
 *
 * 1.1 Voice-signature visualiser bars
 * 1.2 Holographic projection cone
 * 1.4 Personalisation indicator badge
 * ------------------------------------------------------------------- */

/* 1.1 Voice-signature visualiser — 16 thin vertical bars across the
 *      lower face panel. Heights driven by JS on stream-chunk.
 *      Hidden at chrome scale.
 */
.etp-char-voice-bar {
  fill: rgba(91, 180, 232, 0.78);
  transform-origin: center bottom;
  transform: scaleY(0.05);
  transition: transform 90ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
              fill 240ms ease;
}

.etp-character.etp-char-streaming .etp-char-voice-bar {
  fill: rgba(114, 200, 238, 0.95);
}

.etp-character[data-etp-scale="chrome"] .etp-char-voice-bars {
  display: none;
}

.etp-char-voice-bars {
  opacity: 0;
  transition: opacity 280ms ease;
}

.etp-character.etp-char-streaming .etp-char-voice-bars,
.etp-character[data-etp-voice-viz="true"] .etp-char-voice-bars {
  opacity: 1;
}

/* 1.2 Holographic projection cone from chest — gated by
 *     .etp-char-holo-projecting on the anchor. Drawn behind the
 *     character bust as a translucent cyan triangle.
 */
.etp-char-holo-cone {
  fill: rgba(91, 180, 232, 0.18);
  opacity: 0;
  pointer-events: none;
}

@keyframes etp-holo-cone-fade {
  0%   { opacity: 0;     transform: scaleY(0.4); }
  18%  { opacity: 0.85;  transform: scaleY(1); }
  82%  { opacity: 0.65;  transform: scaleY(1); }
  100% { opacity: 0;     transform: scaleY(1.05); }
}

.etp-character.etp-char-holo-projecting .etp-char-holo-cone {
  animation: etp-holo-cone-fade 2200ms cubic-bezier(0.25, 0.46, 0.45, 0.94) 1 forwards;
  transform-origin: 50% 100px; /* chest core position */
}

.etp-char-holo-particle {
  fill: rgba(114, 200, 238, 0.85);
  opacity: 0;
}

.etp-character.etp-char-holo-projecting .etp-char-holo-particle {
  animation: etp-holo-particle 2200ms ease-out 1 forwards;
}

@keyframes etp-holo-particle {
  0%   { opacity: 0; transform: translateY(0); }
  20%  { opacity: 0.9; }
  100% { opacity: 0; transform: translateY(-18px); }
}

.etp-char-holo-particle--1 { animation-delay: 200ms !important; }
.etp-char-holo-particle--2 { animation-delay: 600ms !important; }
.etp-char-holo-particle--3 { animation-delay: 1000ms !important; }
.etp-char-holo-particle--4 { animation-delay: 1400ms !important; }

/* 1.4 Personalisation indicator badge — small icon below chest core.
 *     Drawn by JS based on localStorage state. Hidden unless
 *     [data-etp-personalise="true"].
 */
.etp-char-personalise-badge {
  opacity: 0;
  pointer-events: none;
}

.etp-character[data-etp-personalise="true"] .etp-char-personalise-badge {
  opacity: 0.78;
}

.etp-character[data-etp-scale="chrome"] .etp-char-personalise-badge {
  display: none;
}

/* -------------------------------------------------------------------
 * Phase 11 Tier 0/1 — additional reduced-motion overrides
 * ------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .etp-char-helmet, .etp-char-helmet-band, .etp-char-helmet-shell,
  .etp-char-face-panel, .etp-char-face-hud,
  .etp-char-eyes, .etp-char-mouth {
    transform: none !important;
    transition: none !important;
  }
  .etp-char-face-flare,
  .etp-char-voice-bar,
  .etp-char-holo-cone,
  .etp-char-holo-particle,
  .etp-char-crown-led {
    animation: none !important;
    transition: none !important;
  }
  .etp-character[data-etp-idle] .etp-char-svg,
  .etp-character[data-etp-idle] .etp-char-chest-core,
  .etp-character[data-etp-idle] .etp-char-eyes {
    animation: none !important;
  }
}

@media (prefers-contrast: more) {
  .etp-char-voice-bar { fill: rgba(120, 200, 238, 0.95); }
  .etp-char-holo-cone { fill: rgba(91, 180, 232, 0.32); }
  .etp-char-optical-mark { stroke: rgba(120, 200, 238, 0.9); }
  .etp-char-reg-mark     { opacity: 0.05; }
}

/* =====================================================================
 * Phase 13 (2026-05-27b) — restraint-and-craft definitive upgrade
 *
 * Implements the approved strategy at
 * ~/.claude/plans/use-your-highest-level-futuristic-precious-meerkat.md
 *
 * Discipline: "fewer things done extraordinarily well."
 *   - 8 features retired or quieted (drift particles, circuit grid,
 *     multi-stop iridescent rim, helmet swept-light, HUD scanlines/
 *     brackets at rest, dead visor rect, optical micro-marks).
 *   - 4 signature upgrades: face panel mask refinement (A),
 *     chest disc as precision optical emitter (B), cranial seam
 *     architectural signature (C), ear modules as audio-processing
 *     nodes (D).
 *   - 4 identity additions: hero floor reflection, per-programme
 *     chest-pulse tint, alert face-HUD warm orange, per-state
 *     aria-label (the last is JS-side).
 *
 * Layered ON TOP of Phases 1 → 12 via additive class selectors.
 * Every prior phase's invariants are preserved.
 *
 * Mood target: "A premium 2035-era educational AI product —
 * intelligent without seeming busy, composed not ornate."
 * ===================================================================== */

/* ---------------------------------------------------------------------
 * Phase 13 — refined material tokens
 * --------------------------------------------------------------------- */

.etp-character {
  /* Programme-tinted chest pulse — defaults to current cyan. Per-programme
     overrides below shift this single token. */
  --etp-pulse-tint:        var(--etp-light, #00B7FF);
  /* Per-state cranial seam intensity. JS-free — driven entirely by
     [data-etp-state] selectors below. */
  --etp-seam-intensity:    0.65;
  /* Per-state face HUD warmth — orange in alert, cyan elsewhere. */
  --etp-hud-warm:          var(--etp-light-glow, rgba(0, 183, 255, 0.55));
  /* Ear-module voice-input level meter fill height (0..1). JS rewrites
     this via etpux:voice-listen-amplitude. Default 0.3 = idle baseline. */
  --etp-ear-meter-fill:    0.3;
}

/* ---------------------------------------------------------------------
 * Phase 13.0 RETIREMENTS — engineered restraint
 *
 * Each retirement is implemented as an override (display:none / opacity:0
 * / animation:none) rather than a deletion, so the prior phase code stays
 * intact and the rollback is a single revert.
 * --------------------------------------------------------------------- */

/* 1. Background drift-particles (Phase 10 F) — retired.
   The starfield was visual noise; the dark navy plate stays as the
   load-bearing contrast surface. */
.etp-char-particles,
.etp-char-particle {
  display: none !important;
}

/* 2. Circuit-board grid (Phase 10 F) — retired.
   Background goes from "engineered volumetric space" to clean. */
.etp-char-bg-grid {
  display: none !important;
}

/* 3. Iridescent multi-stop helmet rim — quieted from 3 stops (pink/blue/
   green at 10% opacity each) to a single warm→cool 2-stop gradient. The
   tokens that drive the existing iridescent rim are pinned to a single
   cool-blue stop; the pink and green stops drop to 0. */
.etp-character {
  /* Phase 36 D-01 strict-literal: retoned to Steel Cyan family. The visual
     effect is unchanged for pink + green (alpha 0.00) and only marginally
     brighter for blue (alpha 0.06). See appendix at file tail. */
  --etp-iridescent-pink:  rgba(94, 224, 255, 0.00);
  --etp-iridescent-blue:  rgba(94, 224, 255, 0.06);
  --etp-iridescent-green: rgba(94, 224, 255, 0.00);
}

/* 4. Helmet swept-light keyframe (Phase 11 Tier 2.6) — retired.
   Crown LED + cranial seam + voice bars already convey "actively
   processing"; the radial sweep was redundant motion. */
.etp-character.etp-char-swept-light .etp-char-helmet-keylight {
  animation: none !important;
}

/* 5 + 6. Face HUD scanlines + corner brackets + perimeter ring — gated
   behind active states.
   - Streaming a response (.etp-char-streaming)
   - Holographic cone projecting (.etp-char-holo-projecting)
   - Voice input active ([data-etp-voice-viz="true"])
   - Listening state
   When idle, the face panel reads as clean dark glass with the eyes +
   mouth floating on it (the single biggest perceptual lift of Phase 13). */
.etp-char-face-hud,
.etp-char-face-hud-ring,
.etp-char-face-hud-mark,
.etp-char-face-scanlines {
  opacity: 0;
  transition: opacity 320ms var(--etp-ease);
}

.etp-character.etp-char-streaming .etp-char-face-hud,
.etp-character.etp-char-streaming .etp-char-face-hud-ring,
.etp-character.etp-char-streaming .etp-char-face-hud-mark,
.etp-character.etp-char-streaming .etp-char-face-scanlines,
.etp-character.etp-char-holo-projecting .etp-char-face-hud,
.etp-character.etp-char-holo-projecting .etp-char-face-hud-ring,
.etp-character.etp-char-holo-projecting .etp-char-face-hud-mark,
.etp-character.etp-char-holo-projecting .etp-char-face-scanlines,
.etp-character[data-etp-voice-viz="true"] .etp-char-face-hud,
.etp-character[data-etp-voice-viz="true"] .etp-char-face-hud-ring,
.etp-character[data-etp-voice-viz="true"] .etp-char-face-hud-mark,
.etp-character[data-etp-state="listening"] .etp-char-face-hud,
.etp-character[data-etp-state="listening"] .etp-char-face-hud-ring,
.etp-character[data-etp-state="listening"] .etp-char-face-hud-mark,
.etp-character[data-etp-state="thinking"] .etp-char-face-hud,
.etp-character[data-etp-state="thinking"] .etp-char-face-hud-ring,
.etp-character[data-etp-state="thinking"] .etp-char-face-hud-mark,
.etp-character[data-etp-state="alert"] .etp-char-face-hud,
.etp-character[data-etp-state="alert"] .etp-char-face-hud-ring,
.etp-character[data-etp-state="alert"] .etp-char-face-hud-mark {
  opacity: 1;
}

/* HUD perimeter ring + corner brackets keep their existing stroke colour
   (--etp-light-deep at 0.55) when active, but the listening state lifts
   the ring opacity slightly so "wide-eared" reads visually. */
.etp-character[data-etp-state="listening"] .etp-char-face-hud-ring {
  opacity: 0.78;
}

/* 7. Visor <rect> elements — already opacity 0 since Phase 1 alignment
   2026-05-20. Phase 13 makes the suppression load-bearing via display:none
   on the legacy --subtle modifier so the shimmer rule no longer has a
   visible target. The state-target invariant is preserved (the element
   stays in the DOM; only its visibility is hardened). */
.etp-char-visor--subtle {
  display: none !important;
}

/* The second inline-gradient visor rect (opacity 0 inline) and the
   .etp-char-visor-edge stub are kept as-is — they already render
   invisibly. The .etp-char-shimmer group stays available for the Phase 8
   Explaining-state shimmer (low risk; covers a different surface). */

/* 8. Optical micro-marks at cranial seam (Phase 11 Tier 2.9) — retired.
   At 0.2px stroke they were invisible to most users; the Phase 13 seam
   micro-rivets below replace this with a clearer mechanical detail. */
.etp-char-optical-mark {
  display: none !important;
}

/* ---------------------------------------------------------------------
 * Phase 13.A — Face panel mask refinement
 *
 * The face panel is the single largest visual element in the avatar
 * (22-unit radius). Cleaner + quieter at rest = avatar feels composed.
 *
 * 1. Anti-reflective glass overlay — uses existing .etp-char-face-specular
 *    (already in SVG since Phase 10). Phase 13 lifts opacity + adds blur
 *    so the arc reads as the FIRST thing the eye sees, not a faint hint.
 * 2. Subtle programme-tinted inner-edge rim glow at 4% opacity — couples
 *    the face panel to the chest beacon as a single optical system.
 * --------------------------------------------------------------------- */

.etp-char-face-specular {
  stroke: rgba(255, 255, 255, 0.32);
  stroke-width: 1.6;
  stroke-linecap: round;
  filter: blur(0.7px);
  opacity: 1;
  pointer-events: none;
}

/* Subtle programme-tinted rim glow on the inner edge of the face panel.
   Implemented as a filter on the face HUD perimeter ring (when active)
   so it tracks the panel's curvature. Drop-shadow tinted by the chest
   beacon's programme colour. */
.etp-character.etp-char-streaming .etp-char-face-hud-ring,
.etp-character[data-etp-state="listening"] .etp-char-face-hud-ring,
.etp-character[data-etp-state="thinking"] .etp-char-face-hud-ring {
  filter: drop-shadow(0 0 0.6px var(--etp-pulse-tint));
}

/* When in Alert state, the face HUD ring drops opacity AND turns warm
   orange — the "warning indicator active" tell. Phase 13.E.3. */
.etp-character[data-etp-state="alert"] .etp-char-face-hud-ring {
  stroke: rgba(255, 130, 60, 0.62);
  opacity: 0.42;
  filter: drop-shadow(0 0 0.5px rgba(255, 130, 60, 0.55));
}

/* ---------------------------------------------------------------------
 * Phase 13.B — Chest disc as precision optical emitter
 *
 * The chest disc becomes a real optical emitter, not a graphic. SVG-side:
 * a new <radialGradient> (id="etpChestCoreGrad_<id>") is injected into
 * <defs> at mount time and applied to .etp-char-chest-core via inline
 * style. CSS-side: the gradient's stop-opacity at the white-hot centre
 * pulses via the existing chest-core-pulse keyframe period (Phase 11
 * --etp-pulse-period token, unchanged). The gradient endpoints (inner
 * white-hot, mid programme-tint, outer deep) are styled via CSS custom
 * properties so per-programme + per-state overrides stay declarative.
 * --------------------------------------------------------------------- */

.etp-character {
  /* Programme-tinted chest gradient stops. Mid stop carries the
     programme tint at 22% saturation — never overpowers the eyes.
     Inner stop stays pure white-hot. Outer stop deepens to the
     programme's deep tone (Phase 11 already had --etp-light-deep). */
  --etp-chest-grad-inner:  rgba(255, 255, 255, 0.92);
  --etp-chest-grad-mid:    var(--etp-chest-core-teal, #00DCEC);
  --etp-chest-grad-outer:  var(--etp-light-deep, #0098D8);
}

/* The Phase 13.B keyframe — modulates the gradient inner stop's
   visual presence by animating the chest-core opacity AND scale.
   The "centre is brightening" read (vs whole-circle fading) is
   achieved by keeping the rim circle anchored while the core scales
   subtly inward via the existing pulse on `r`. */
@keyframes etp-chest-core-emit {
  0%, 100% {
    opacity: 0.92;
    transform: scale(1.0);
  }
  50% {
    opacity: 1.0;
    transform: scale(1.06);
  }
}

.etp-char-chest-core {
  transform-origin: 50px 103px;
  /* Use existing --etp-pulse-period (Phase 11 token). Keeps adaptive
     pulse tempo working — when JS rewrites the period for streaming
     vs idle, this animation follows. */
  animation-name: etp-chest-core-emit !important;
  animation-iteration-count: infinite !important;
  animation-timing-function: cubic-bezier(0.4, 0.0, 0.6, 1.0) !important;
}

/* Pre-existing etp-chest-core-pulse keyframe (Phase 10) is replaced by
   etp-chest-core-emit above on the chest-core element. The ghost / glow /
   rim circles continue their existing animations untouched. */

/* ---------------------------------------------------------------------
 * Phase 13.C — Cranial seam architectural signature
 *
 * Per-state seam intensity + hero-only micro-rivets.
 * --------------------------------------------------------------------- */

/* Per-state seam intensity — the seam glows hotter in alert, dimmer in
   waiting. Couples the seam to the emotional state without changing
   geometry. */
.etp-char-cranial-seam {
  opacity: var(--etp-seam-intensity);
  transition: opacity 480ms var(--etp-ease), filter 480ms var(--etp-ease);
}

.etp-character[data-etp-state="alert"] {
  --etp-seam-intensity: 0.90;
}
.etp-character[data-etp-state="alert"] .etp-char-cranial-seam {
  filter: drop-shadow(0 0 0.6px rgba(255, 255, 255, 0.7));
}
.etp-character[data-etp-state="waiting"] {
  --etp-seam-intensity: 0.30;
}
.etp-character[data-etp-state="celebrating"] {
  --etp-seam-intensity: 0.88;
}
.etp-character[data-etp-state="thinking"] {
  --etp-seam-intensity: 0.55;
}

/* Cranial seam micro-rivets — three hairline marks at the visible seam
   portions (x=22, x=28, x=72, x=78 are the band edges). Hero-only.
   Implies the helmet shell is mechanically fastened, not a decoration.
   Drawn by JS into a single <g class="etp-char-seam-rivets"> group
   appended to the SVG at mount time (see Phase 13 JS section). */
.etp-char-seam-rivet {
  fill: var(--etp-helmet-silver, #EAECF1);
  stroke: rgba(15, 35, 60, 0.55);
  stroke-width: 0.15;
  opacity: 0;
}

.etp-character[data-etp-scale="hero"] .etp-char-seam-rivet {
  opacity: 0.55;
}

/* ---------------------------------------------------------------------
 * Phase 13.D — Ear modules as audio-processing nodes
 *
 * The cyan accent slot becomes a tiny voice-input level meter. JS-side
 * listens for `etpux:voice-listen-amplitude` events (event detail
 * `{level: 0..1}`) and rewrites --etp-ear-meter-fill on the anchor. The
 * slot's height is clamped 0.3..1.0 so the slot never empties below the
 * idle baseline.
 * --------------------------------------------------------------------- */

/* Ear-module cyan accent slot — now driven by the voice-input level
   meter. The slot is anchored bottom-up via transform-origin and
   scaleY tied to --etp-ear-meter-fill. The base SVG element keeps its
   y=34, height=7.56 dimensions; CSS scales it from the bottom. */
.etp-char-ear-module-cyan {
  transform-origin: center bottom;
  transform: scaleY(var(--etp-ear-meter-fill, 0.3));
  transition: transform 110ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* When the avatar is in Listening state, baseline floor lifts to 0.45
   so "wide-eared" reads even before voice amplitude arrives. */
.etp-character[data-etp-state="listening"] {
  --etp-ear-meter-fill: 0.48;
}

/* Reduced-motion: snap the meter to a constant 0.5 fill (no jitter). */
@media (prefers-reduced-motion: reduce) {
  .etp-char-ear-module-cyan {
    transform: scaleY(0.5);
    transition: none !important;
  }
}

/* Ear pad inner-edge hairline shadow — gives the silver pad subtle
   machined depth. Implemented as a stroke override on the pad rect.
   Phase 13.D.2. */
.etp-char-ear-module-pad {
  stroke: rgba(15, 35, 60, 0.42);
  stroke-width: 0.4;
}

/* ---------------------------------------------------------------------
 * Phase 13.E.1 — Hero floor reflection hairline
 *
 * A single subtle arc beneath the avatar's chest at y=128, hero only.
 * Implies the character has weight and stands on a reflective surface.
 * JS-injected SVG element (.etp-char-floor-reflection) at mount.
 * --------------------------------------------------------------------- */

.etp-char-floor-reflection {
  fill: none;
  stroke: var(--etp-light-glow, rgba(0, 183, 255, 0.55));
  stroke-width: 0.3;
  stroke-linecap: round;
  opacity: 0;
  pointer-events: none;
}

.etp-character[data-etp-scale="hero"] .etp-char-floor-reflection {
  opacity: 0.22;
}

@media (prefers-reduced-motion: reduce) {
  .etp-char-floor-reflection {
    /* Static — no motion in any case, but ensure no transition fires. */
    transition: none !important;
  }
}

/* ---------------------------------------------------------------------
 * Phase 13.E.2 — Per-programme chest-pulse tint
 *
 * Each programme gets a distinct chest pulse colour. Tints the existing
 * --etp-chest-core-teal token (which drives the chest core gradient mid
 * stop) AND the new --etp-pulse-tint token (used for face panel rim
 * glow under streaming). All 5 platform programmes covered.
 * --------------------------------------------------------------------- */

.etp-character[data-etp-programme="ai-robotics-plus"] {
  --etp-pulse-tint:       #00DCEC; /* cyan-mint (existing default-ish) */
  --etp-chest-core-teal:  #00DCEC;
}
.etp-character[data-etp-programme="ai-steam-plus"] {
  --etp-pulse-tint:       #FFAB66; /* warm orange */
  --etp-chest-core-teal:  #FFB07A;
  --etp-chest-grad-mid:   #FFB07A;
  --etp-chest-grad-outer: #C56B2E;
}
.etp-character[data-etp-programme="ai-expert-plus"] {
  --etp-pulse-tint:       #B89BFF; /* violet */
  --etp-chest-core-teal:  #B89BFF;
  --etp-chest-grad-mid:   #B89BFF;
  --etp-chest-grad-outer: #7C5CC9;
}
.etp-character[data-etp-programme="ai-math-plus"] {
  --etp-pulse-tint:       #6FAFFF; /* clean blue */
  --etp-chest-core-teal:  #6FAFFF;
  --etp-chest-grad-mid:   #6FAFFF;
  --etp-chest-grad-outer: #2A6BD0;
}
.etp-character[data-etp-programme="ai-computer-science-plus"] {
  --etp-pulse-tint:       #6BD68C; /* programme green */
  --etp-chest-core-teal:  #6BD68C;
  --etp-chest-grad-mid:   #6BD68C;
  --etp-chest-grad-outer: #2C9C53;
}

/* ---------------------------------------------------------------------
 * Phase 13 — visibility gates by scale
 *
 * Phase 13 elements that should be hero/classroom only (not convo/chrome).
 * --------------------------------------------------------------------- */

.etp-character[data-etp-scale="chrome"] .etp-char-floor-reflection,
.etp-character[data-etp-scale="convo"]  .etp-char-floor-reflection,
.etp-character[data-etp-scale="classroom"] .etp-char-floor-reflection {
  opacity: 0;
}

.etp-character[data-etp-scale="chrome"] .etp-char-seam-rivet,
.etp-character[data-etp-scale="convo"]  .etp-char-seam-rivet,
.etp-character[data-etp-scale="classroom"] .etp-char-seam-rivet {
  opacity: 0;
}

/* ---------------------------------------------------------------------
 * Phase 13 — reduced-motion overrides
 * --------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-core {
    animation: none !important;
    transform: scale(1.0) !important;
  }
  .etp-char-cranial-seam {
    transition: none !important;
  }
  .etp-char-ear-module-cyan {
    transition: none !important;
  }
}

/* ---------------------------------------------------------------------
 * Phase 13 — high-contrast overrides
 * --------------------------------------------------------------------- */

@media (prefers-contrast: more) {
  .etp-char-face-specular {
    stroke: rgba(255, 255, 255, 0.55);
  }
  .etp-char-floor-reflection {
    stroke: rgba(255, 255, 255, 0.55);
  }
  .etp-character[data-etp-state="alert"] .etp-char-face-hud-ring {
    stroke: rgba(255, 130, 60, 0.95);
  }
  .etp-char-seam-rivet {
    fill: rgba(255, 255, 255, 0.9);
  }
}

/* =====================================================================
 * Phase 14 (2026-05-27c) — the perception ceiling lift
 *
 * Twelve targeted improvements layered ON TOP of Phases 1 → 13. The
 * unifying premise: the avatar is already remarkable; Phase 14 closes
 * the gap between "remarkable" and "the design press writes about it."
 *
 *   Tier 1 (small lifts, very high payoff):
 *     #1 Eye-lens specular reflection crescent
 *     #2 Stillness as a 9th state ("composed")
 *     #3 ET+ wordmark typographic uplift (+ taller)
 *     #4 Audio-philosophy meta (HTML — see HTML pages)
 *
 *   Tier 2 (medium lifts, change perception ceiling):
 *     #5 Shoulder LED convo-scale lift (3-cyan triplet rhythm survives)
 *     #6 Host-light eye reflection tracking
 *     #7 First-load wake choreography (JS — see etp-character.js)
 *     #8 Programme-specific eye warm-spark tint
 *
 *   Tier 3 (medium-large investments):
 *     #11 Single dust mote (47s cinematographer's tell)
 *     #12 Time-of-day reactive behaviour
 *
 *   Tier 4 (architectural reach):
 *     #13 Light propagation chest → seam → crown
 *
 *   Tier 5 (strategic moves):
 *     #16 Animated favicon (separate SVG file)
 *     #17 Marketing page live avatar (HTML — see _landing.html)
 *
 *   Deferred to runbooks (Tier 3 #9, #10; Tier 4 #14, #15; Tier 5 #18).
 *
 * Layered ON TOP of Phase 13 via additive class selectors. All Phase
 * 13 invariants preserved.
 * ===================================================================== */

/* ---------------------------------------------------------------------
 * Phase 14 — refined tokens
 * --------------------------------------------------------------------- */

.etp-character {
  /* #6 — Host-light reflection tracking. Lives off the existing
     --etp-host-light (Phase 11 Tier 2.10). 0..1 ranges; centre = 0.5. */
  --etp-host-light-shift: calc((var(--etp-host-light, 0.5) - 0.5) * 0.6px);
  /* #12 — Time-of-day pulse modulation. JS rewrites these per hour. */
  --etp-tod-blink-rate:   1.0;   /* multiplier on blink interval */
  --etp-tod-pulse-rate:   1.0;   /* multiplier on chest pulse rate */
  --etp-tod-head-tilt:    0;     /* in degrees, applied to head transform */
  /* #13 — Light propagation delays. Chest pulse is the heartbeat; seam
     follows 200ms behind, crown LED top another 200ms behind that. */
  --etp-light-prop-seam-delay:  200ms;
  --etp-light-prop-crown-delay: 400ms;
}

/* ---------------------------------------------------------------------
 * Tier 1 #1 — Eye-lens specular reflection crescent
 *
 * Tiny white arc at the top-right of each iris. Sells "glass over a
 * real lens" — the way studio product photographers light a camera
 * lens. SVG-side: <path class="etp-char-eye-lens-specular--l/r">
 * injected via _injectPhase14Marks. Hero + classroom only (too small
 * to read at convo + chrome).
 * --------------------------------------------------------------------- */

.etp-char-eye-lens-specular {
  fill: none;
  stroke: rgba(255, 255, 255, 0.42);
  stroke-width: 0.6;
  stroke-linecap: round;
  pointer-events: none;
  opacity: 0;
  transition: opacity 320ms var(--etp-ease);
}

.etp-character[data-etp-scale="hero"] .etp-char-eye-lens-specular,
.etp-character[data-etp-scale="classroom"] .etp-char-eye-lens-specular {
  opacity: 1;
}

/* Closed-eye states — hide the lens specular along with the iris/
   aperture (already hidden in those states from Phase 10/13). */
.etp-character[data-etp-state="encouraging"] .etp-char-eye-lens-specular,
.etp-character[data-etp-state="celebrating"] .etp-char-eye-lens-specular {
  opacity: 0;
}

/* ---------------------------------------------------------------------
 * Tier 1 #2 — Stillness as a 9th state ("composed")
 *
 * 800ms after stream-end with no user input, JS sets
 * data-etp-state="composed" for 3 seconds. CSS-side: pauses ALL four
 * idle loops (breathe / chest pulse / eye drift / blink). The avatar
 * holds completely still. After 3s, JS resets to neutral.
 *
 * Stillness is rare in motion-rich UI — it reads as confidence.
 * --------------------------------------------------------------------- */

.etp-character[data-etp-state="composed"] .etp-char-svg,
.etp-character[data-etp-state="composed"] .etp-char-chest-core,
.etp-character[data-etp-state="composed"] .etp-char-eyes,
.etp-character[data-etp-state="composed"] .etp-char-chest-core-ghost,
.etp-character[data-etp-state="composed"] .etp-char-chest-core-glow,
.etp-character[data-etp-state="composed"] .etp-char-cranial-seam,
.etp-character[data-etp-state="composed"] .etp-char-crown-led {
  animation-play-state: paused !important;
}

/* Aperture stays at a calm "neutral, slightly contracted" — 0.95×.
   Implies focused attention, not vacant stare. */
.etp-character[data-etp-state="composed"] .etp-char-eye-aperture {
  transform: scale(0.95);
  transition: transform 480ms var(--etp-ease);
}

/* Eye warm-spark dims slightly during stillness — the "considering"
   read, not "engaged" read. */
.etp-character[data-etp-state="composed"] .etp-char-eye-warm-spark {
  opacity: 0.32;
}

/* ---------------------------------------------------------------------
 * Tier 1 #3 — ET+ wordmark typographic uplift
 *
 * The `+` glyph gets 10% extra height + a 0.3px y-lift. Asymmetric
 * uplift matching premium typography (Apple, Anthropic, Nothing).
 * --------------------------------------------------------------------- */

.etp-char-chest-brand-plus {
  /* The existing per-letter pulse keyframe sits on top of this base
     transform. Both compose (CSS transform stacks). */
  transform-origin: center bottom;
  transform: scale(1.0, 1.10) translateY(-0.3px);
}

/* ---------------------------------------------------------------------
 * Tier 2 #5 — Shoulder LED convo-scale lift
 *
 * Without this, the 3-cyan triplet rhythm (chest + crown + shoulders)
 * collapses to 2 at 128px tutor.html scale. Lift opacity 0.45 → 0.75.
 * --------------------------------------------------------------------- */

.etp-character[data-etp-scale="convo"] .etp-char-shoulder-led {
  --etp-shoulder-led-opacity: 0.75;
}

/* ---------------------------------------------------------------------
 * Tier 2 #6 — Host-light eye reflection tracking
 *
 * The smaller pupil-dot (--upper-left) shifts subtly along x as the
 * host page's background luminance changes. Independent of Phase 11
 * cursor-track (which shifts the whole pupil group). Creates a
 * "divided attention" read — real eyes do this naturally.
 * --------------------------------------------------------------------- */

.etp-char-eye-pupil-dot--upper-left {
  transform: translateX(var(--etp-host-light-shift, 0px));
  transition: transform 800ms var(--etp-ease);
}

/* ---------------------------------------------------------------------
 * Tier 2 #8 — Programme-specific eye warm-spark tint
 *
 * Couples the warm spark inside each iris to the active programme.
 * Robotics / STEAM / Math stay warm (cream); Expert / ACSP shift
 * subtly cooler (a faint cyan-cream blend).
 * --------------------------------------------------------------------- */

.etp-character[data-etp-programme="ai-robotics-plus"],
.etp-character[data-etp-programme="ai-steam-plus"],
.etp-character[data-etp-programme="ai-math-plus"] {
  --etp-cream-spark: #FFF4E0;  /* warm cream (default) */
}

.etp-character[data-etp-programme="ai-expert-plus"] {
  --etp-cream-spark: #F0EAFF;  /* cooler violet-tinged cream */
}

.etp-character[data-etp-programme="ai-computer-science-plus"] {
  --etp-cream-spark: #E8F5DE;  /* cooler green-tinged cream */
}

/* ---------------------------------------------------------------------
 * Tier 3 #11 — Single dust mote (47-second cinematographer's tell)
 *
 * One 0.4px particle drifts across the face panel once every 47
 * seconds. Long enough between appearances that users never
 * consciously notice it; their pattern-recognition feels something.
 *
 * Roger Deakins puts one out-of-focus speck in every shot to make
 * it feel "lived in." That's the trick here.
 * --------------------------------------------------------------------- */

.etp-char-dust-mote {
  fill: rgba(255, 255, 255, 0.55);
  opacity: 0;
  pointer-events: none;
}

@keyframes etp-dust-mote-drift {
  /* 0..86% — invisible (the mote isn't there) */
  0%, 86%   { opacity: 0;    transform: translate(0, 0); }
  /* 87% — fade in at left edge of face panel */
  87%       { opacity: 0.55; transform: translate(-18px, -2px); }
  /* 88-98% — drift across, slight downward curve */
  90%       { opacity: 0.55; transform: translate(-8px, 0); }
  94%       { opacity: 0.55; transform: translate(6px, 3px); }
  /* 99% — fade out at right edge */
  99%       { opacity: 0;    transform: translate(18px, 4px); }
  100%      { opacity: 0;    transform: translate(0, 0); }
}

.etp-character[data-etp-scale="hero"] .etp-char-dust-mote,
.etp-character[data-etp-scale="classroom"] .etp-char-dust-mote {
  animation: etp-dust-mote-drift 47s linear infinite;
}

/* ---------------------------------------------------------------------
 * Tier 3 #12 — Time-of-day reactive behaviour
 *
 * Three behaviour bands keyed off the existing Phase 11
 * data-etp-tod attribute, with new subdivisions:
 *   - night (22:00–06:00): blink rate 2× ("tired with the student")
 *   - morning (06:00–09:00): chest pulse 12% faster ("morning energy")
 *   - post-lunch (13:00–15:00): 4° head tilt every 90s ("staying engaged")
 *
 * Existing Phase 11 night-desaturation override stays — Phase 14
 * adds the per-band animation modulations.
 * --------------------------------------------------------------------- */

.etp-character[data-etp-tod-band="night"] {
  --etp-tod-blink-rate: 2.0;
}

.etp-character[data-etp-tod-band="morning"] {
  --etp-tod-pulse-rate: 1.12;
}

.etp-character[data-etp-tod-band="postlunch"] {
  --etp-tod-head-tilt: 4deg;
}

/* Apply blink-rate modulation to the existing blink-pulse keyframe.
   The Phase 10 blink-pulse animation lives on .etp-char-eye and uses
   the body's --etp-pulse-period; here we scale its DURATION via a
   custom property so night = half the interval = 2× rate. */
.etp-character[data-etp-tod-band="night"] .etp-char-eye {
  /* Existing animation: etp-blink-pulse infinite. We can't change
     its keyframes, but we can change duration via a JS-rewrite-free
     CSS rule: shorten with calc(). */
  animation-duration: calc(var(--etp-blink-period, 7.1s) / var(--etp-tod-blink-rate, 1)) !important;
}

/* Chest pulse rate scaling — extend Phase 11 --etp-pulse-period. */
.etp-character[data-etp-tod-band="morning"] .etp-char-chest-core {
  animation-duration: calc(var(--etp-pulse-period, 1.6s) / var(--etp-tod-pulse-rate, 1)) !important;
}

/* Post-lunch head-tilt — periodic gentle tilt. Uses a slow 90s
   keyframe that tilts the head circle slightly every cycle. */
@keyframes etp-postlunch-head-tilt {
  0%, 92%   { transform: rotate(0deg); }
  94%       { transform: rotate(4deg); }
  96%       { transform: rotate(-2deg); }
  98%       { transform: rotate(2deg); }
  100%      { transform: rotate(0deg); }
}

.etp-character[data-etp-tod-band="postlunch"] .etp-char-svg {
  animation: etp-postlunch-head-tilt 90s ease-in-out infinite;
  transform-origin: 50px 70px;  /* pivot around neck-base */
}

/* ---------------------------------------------------------------------
 * Tier 4 #13 — Light propagation chest → seam → crown
 *
 * Couples the 3 glowing surfaces (chest disc, cranial seam, crown
 * top-LED) into one connected lighting system via animation-delay.
 * The same chest pulse keyframe runs on the seam (200ms behind) and
 * the crown LED (400ms behind). Reads as light "travelling" up the
 * body at cinematic speed.
 * --------------------------------------------------------------------- */

@keyframes etp-light-propagation-pulse {
  0%, 100% {
    opacity: var(--etp-seam-intensity, 0.65);
  }
  50% {
    opacity: calc(var(--etp-seam-intensity, 0.65) + 0.15);
  }
}

.etp-char-cranial-seam {
  animation: etp-light-propagation-pulse var(--etp-pulse-period, 1.6s) cubic-bezier(0.4, 0.0, 0.6, 1.0) infinite;
  animation-delay: var(--etp-light-prop-seam-delay, 200ms);
}

@keyframes etp-light-propagation-crown {
  0%, 100% { opacity: 0.85; }
  50%      { opacity: 1.0; filter: brightness(1.15); }
}

.etp-char-crown-led--top {
  animation-name: etp-light-propagation-crown !important;
  animation-duration: var(--etp-pulse-period, 1.6s) !important;
  animation-timing-function: cubic-bezier(0.4, 0.0, 0.6, 1.0) !important;
  animation-iteration-count: infinite !important;
  animation-delay: var(--etp-light-prop-crown-delay, 400ms) !important;
}

/* When the avatar is in the "composed" state (Tier 1 #2), the entire
   light-propagation cascade pauses. Stillness wins over coupling. */
.etp-character[data-etp-state="composed"] .etp-char-cranial-seam,
.etp-character[data-etp-state="composed"] .etp-char-crown-led--top {
  animation-play-state: paused !important;
}

/* ---------------------------------------------------------------------
 * Phase 14 — reduced-motion overrides
 * --------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .etp-char-dust-mote {
    animation: none !important;
    opacity: 0 !important;
  }
  .etp-character[data-etp-tod-band="postlunch"] .etp-char-svg {
    animation: none !important;
  }
  .etp-char-eye-pupil-dot--upper-left {
    transition: none !important;
    transform: none !important;
  }
  .etp-char-cranial-seam,
  .etp-char-crown-led--top {
    /* Don't kill the seam/crown entirely — that would visually
       suggest the avatar is "off". Instead, hold them at their
       steady mid-pulse opacity. */
    animation: none !important;
  }
}

/* ---------------------------------------------------------------------
 * Phase 14 — high-contrast overrides
 * --------------------------------------------------------------------- */

@media (prefers-contrast: more) {
  .etp-char-eye-lens-specular {
    stroke: rgba(255, 255, 255, 0.72);
    stroke-width: 0.8;
  }
  .etp-char-dust-mote {
    fill: rgba(255, 255, 255, 0.85);
  }
}

/* =====================================================================
 * Phase 15 — material truth + micro-motion sophistication (2026-05-27d)
 *
 * Builds on Phases 8-14. 25 of 27 design ideas shipped live; 2 deferred
 * (#19 breathing-rate metadata sync, #24 ambient sound design).
 *
 * Tier 1 (7 items): #1 lens specular rotation, #2 chest afterglow,
 *   #3 helmet edge darkening, #4 mouth speaking glow, #5 eye saccades,
 *   #6 cranial seam thinking pulse, #7 chrome heartbeat ring
 * Tier 2 (6 items): #8 iridescent edge, #9 anisotropic seam profile,
 *   #10 ambient-light face panel, #11 head tilt + parallax, #12 helmet
 *   breathe pair, #13 iris emotional hue shift
 * Tier 3 (3 items): #14 face-panel SSS bloom on streaming, #15 brushed-
 *   aluminum micro-texture, #16 cracked-visor diegetic flourish
 * Tier 4 (2 shipped, 1 deferred): #17 presence ring, #18 wake variants,
 *   #19 breathing-rate sync (DEFERRED — data-etp-breathe-tempo slot)
 * Tier 5 (3 items): #20 WCAG-AAA mode, #21 chrome silhouette tells,
 *   #22 per-programme ear-module stripe
 * Tier 6 (1 shipped, 1 deferred): #23 sound-event hooks, #24 audio (DEFERRED)
 * Tier 7 (3 items): #25 focal-length DOF, #26 blink jitter, #27 sparkle shower
 *
 * Sacred invariants preserved across all Phases 8-15: canonical smile path,
 * 8 eye-system selectors + Phase 14 lens-specular, aperture blades, cranial
 * seam, chest core triplet, ET+ wordmark, crown 3-LED column, 9-state
 * machine, 5 programme + 4 persona variants, behaviour-bus contract.
 * ===================================================================== */

/* --- Tier 1 #1 — Eye-lens specular rotation ------------------------- */
@keyframes etp-lens-specular-roll {
  0%   { transform: rotate(-4deg); }
  50%  { transform: rotate( 4deg); }
  100% { transform: rotate(-4deg); }
}
.etp-character[data-etp-scale="hero"]      .etp-char-eye-lens-specular,
.etp-character[data-etp-scale="classroom"] .etp-char-eye-lens-specular {
  animation: etp-lens-specular-roll 7s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite;
  transform-origin: center;
  transform-box: fill-box;
}

/* --- Tier 1 #2 — Chest core afterglow ------------------------------ */
.etp-character[data-etp-fade="afterglow"] .etp-char-chest-core,
.etp-character[data-etp-fade="afterglow"] .etp-char-chest-glow {
  transition: opacity 0.6s cubic-bezier(0.4, 0.0, 0.2, 1.0),
              filter  0.6s cubic-bezier(0.4, 0.0, 0.2, 1.0);
}

/* --- Tier 1 #3 — Helmet silhouette edge darkening ------------------ */
.etp-character[data-etp-scale="hero"]      .etp-char-head-shell,
.etp-character[data-etp-scale="classroom"] .etp-char-head-shell {
  filter: drop-shadow(0 0.4px 0 rgba(8, 15, 26, 0.55));
}

/* --- Tier 1 #4 — Mouth speaking glow per stream-chunk -------------- */
.etp-char-mouth { transition: filter 90ms ease-out; }
.etp-character[data-etp-speaking="1"] .etp-char-mouth {
  filter: drop-shadow(0 0 1.4px var(--etp-light-glow, rgba(0,220,236,0.55)));
}

/* --- Tier 1 #5 — Eye micro-saccades during thinking --------------- */
@keyframes etp-eye-microsaccade {
  0%   { transform: translate(0, 0); }
  20%  { transform: translate(0.2px, -0.15px); }
  40%  { transform: translate(-0.2px, 0.15px); }
  60%  { transform: translate(0.15px, 0.1px); }
  80%  { transform: translate(-0.15px, -0.1px); }
  100% { transform: translate(0, 0); }
}
.etp-character[data-etp-state="thinking"] .etp-char-eye-pupil,
.etp-character[data-etp-state="thinking"] .etp-char-eye-pupil-dot {
  animation: etp-eye-microsaccade 0.8s steps(5, end);
}

/* --- Tier 1 #6 — Cranial seam thinking pulse ----------------------- */
@keyframes etp-seam-thinking-pulse {
  0%, 100% { filter: drop-shadow(0 0 0.4px var(--etp-seam-glow, rgba(94, 224, 255, 0.65))); opacity: 1; }
  35%      { filter: drop-shadow(0 0 1.6px rgba(94, 224, 255, 0.95));                       opacity: 1; }
}
.etp-character[data-etp-state="thinking"] .etp-char-cranial-seam {
  animation: etp-seam-thinking-pulse 1.4s cubic-bezier(0.4, 0.0, 0.4, 1.0) infinite !important;
}

/* --- Tier 1 #7 — Chrome-scale heartbeat ring ---------------------- */
@keyframes etp-chrome-heartbeat {
  0%, 100% { transform: scale(1);    opacity: 0.25; }
  50%      { transform: scale(1.45); opacity: 0; }
}
.etp-char-chest-ring--heartbeat {
  fill: none;
  stroke: var(--etp-chest-core-teal, #00DCEC);
  stroke-width: 0.3;
  transform-origin: center;
  transform-box: fill-box;
  display: none;
}
.etp-character[data-etp-scale="chrome"] .etp-char-chest-ring--heartbeat {
  display: inline;
  animation: etp-chrome-heartbeat 4s ease-out infinite;
}

/* --- Tier 2 #8 — Iridescent helmet edge thin-film ----------------- */
/* Filter id `etp-helmet-iridescent-filter` injected by JS into <defs>.
   Applied at hero only, chained with brushed-aluminum (Tier 3 #15)
   when prefers-reduced-motion is no-preference. */

/* --- Tier 2 #9 — Anisotropic cranial-seam Gaussian profile -------- */
.etp-char-cranial-seam {
  stroke: url(#etp-seam-anisotropic-gradient);
}

/* --- Tier 2 #10 — Ambient-light pickup on face panel -------------- */
.etp-char-face-specular {
  transform: translateX(calc((var(--etp-host-light, 0.5) - 0.5) * 2.4px));
  transform-box: fill-box;
  transition: transform 0.8s cubic-bezier(0.4, 0.0, 0.2, 1.0);
}

/* --- Tier 2 #11 — Per-state head tilt + mouse parallax combo ------ */
.etp-char-head-shell {
  transform-origin: 50% 80%;
  transform-box: fill-box;
  transition: transform 0.6s cubic-bezier(0.4, 0.0, 0.2, 1.0);
}
.etp-character[data-etp-state="encouraging"] .etp-char-head-shell { transform: rotate(-2deg); }
.etp-character[data-etp-state="thinking"]    .etp-char-head-shell { transform: rotate(1.5deg); }
.etp-character[data-etp-state="listening"]   .etp-char-head-shell { transform: rotate(-1deg); }

/* --- Tier 2 #12 — Helmet vertical breathe paired with chest pulse -- */
@keyframes etp-helmet-breathe {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-0.5px); }
}
.etp-character[data-etp-scale="hero"]      .etp-char-head-shell,
.etp-character[data-etp-scale="classroom"] .etp-char-head-shell {
  animation: etp-helmet-breathe var(--etp-pulse-period, 1.6s) ease-in-out infinite;
}
/* Pause helmet breathe in composed state — stillness wins. */
.etp-character[data-etp-state="composed"] .etp-char-head-shell {
  animation-play-state: paused;
}

/* --- Tier 2 #13 — Iris emotional hue shift ------------------------ */
.etp-character[data-etp-state="encouraging"] .etp-char-eye-iris {
  filter: hue-rotate(-5deg) saturate(1.05);
}
.etp-character[data-etp-state="alert"] .etp-char-eye-iris {
  filter: hue-rotate(8deg) saturate(0.95) brightness(1.05);
}

/* --- Tier 3 #14 — Face-panel SSS bloom on streaming --------------- */
.etp-character[data-etp-streaming="1"][data-etp-scale="hero"]      .etp-char-face-panel-fill,
.etp-character[data-etp-streaming="1"][data-etp-scale="classroom"] .etp-char-face-panel-fill {
  filter: drop-shadow(0 -0.6px 0.8px var(--etp-light-glow, rgba(0, 220, 236, 0.35)));
  transition: filter 0.8s cubic-bezier(0.4, 0.0, 0.2, 1.0);
}

/* --- Tier 3 #15 — Brushed-aluminum micro-texture ------------------ */
@media (prefers-reduced-motion: no-preference) {
  .etp-character[data-etp-scale="hero"] .etp-char-head-shell {
    filter: url(#etp-helmet-iridescent-filter)
            url(#etp-brushed-aluminum-filter)
            drop-shadow(0 0.4px 0 rgba(8, 15, 26, 0.55));
  }
}

/* --- Tier 3 #16 — Cracked-visor diegetic flourish ----------------- */
.etp-char-visor-crack {
  fill: none;
  stroke: rgba(255, 255, 255, 0.18);
  stroke-width: 0.18;
  stroke-linecap: round;
  pointer-events: none;
}
.etp-character:not([data-etp-scale="hero"]) .etp-char-visor-crack {
  display: none;
}

/* --- Tier 4 #17 — Presence ring on cursor proximity --------------- */
.etp-char-presence-ring {
  fill: none;
  stroke: var(--etp-light-glow, rgba(0, 220, 236, 0.55));
  stroke-width: 1.5;
  opacity: 0;
  pointer-events: none;
  transform-origin: center;
  transform-box: fill-box;
  transition: opacity 0.4s cubic-bezier(0.4, 0.0, 0.2, 1.0);
}
.etp-character[data-etp-proximity="near"] .etp-char-presence-ring {
  opacity: 0.25;
  transition: opacity 0.2s cubic-bezier(0.4, 0.0, 0.2, 1.0);
}
@media (hover: none), (prefers-reduced-motion: reduce) {
  .etp-char-presence-ring { display: none; }
}

/* --- Tier 4 #18 — Wake-sequence variations ------------------------ */
.etp-character[data-etp-wake-variant="1"] .etp-char-crown-led {
  animation-delay: 200ms !important;
}
.etp-character[data-etp-wake-variant="2"] .etp-char-crown-led {
  animation-delay: 400ms !important;
}

/* --- Tier 5 #20 — WCAG-AAA mode ----------------------------------- */
[data-etp-theme="high-contrast-aaa"] .etp-character {
  --etp-light-glow: rgba(255, 255, 255, 0.92);
  --etp-chest-core-teal: #FFFFFF;
  --etp-cream-spark: rgba(255, 255, 255, 0.10);
}
[data-etp-theme="high-contrast-aaa"] .etp-char-eye-iris,
[data-etp-theme="high-contrast-aaa"] .etp-char-head-shell,
[data-etp-theme="high-contrast-aaa"] .etp-char-mouth,
[data-etp-theme="high-contrast-aaa"] .etp-char-cranial-seam {
  stroke: rgba(15, 23, 42, 0.92);
  stroke-width: 0.8;
}
[data-etp-theme="high-contrast-aaa"] .etp-char-face-panel-fill {
  fill: rgba(15, 23, 42, 0.98);
}

/* --- Tier 5 #21 — Per-programme silhouette tells at chrome scale --- */
.etp-character[data-etp-scale="chrome"][data-etp-programme="ai-robotics-plus"] .etp-char-crown-led--top {
  opacity: 1;
}
.etp-character[data-etp-scale="chrome"][data-etp-programme="ai-steam-plus"] .etp-char-chest-core {
  filter: brightness(1.12);
}
.etp-character[data-etp-scale="chrome"][data-etp-programme="ai-expert-plus"] .etp-char-chest-glow {
  opacity: 0.55;
}

/* --- Tier 5 #22 — Per-programme ear-module accent stripe ---------- */
.etp-character[data-etp-programme] .etp-char-ear-meter-fill {
  background-color: var(--etp-pulse-tint, var(--etp-chest-core-teal, #00DCEC));
}

/* --- Tier 7 #25 — Per-state focal-length blur (DOF) --------------- */
.etp-character[data-etp-state="thinking"][data-etp-scale="hero"]      .etp-char-crown-led,
.etp-character[data-etp-state="thinking"][data-etp-scale="classroom"] .etp-char-crown-led {
  filter: blur(0.35px);
  transition: filter 0.4s cubic-bezier(0.4, 0.0, 0.2, 1.0);
}

/* --- Tier 7 #27 — Programme-tinted celebration shower ------------- */
.etp-char-celebrate-sparkle {
  fill: var(--etp-pulse-tint, var(--etp-chest-core-teal, #00DCEC));
  opacity: 0;
  pointer-events: none;
}
@keyframes etp-celebrate-sparkle-drift {
  0%   { transform: translate(0, 0) scale(1);                                     opacity: 0; }
  10%  {                                                                          opacity: 0.85; }
  100% { transform: translate(var(--etp-sparkle-dx, 0), -18px) scale(0.4);       opacity: 0; }
}
.etp-character[data-etp-celebrating="1"] .etp-char-celebrate-sparkle {
  animation: etp-celebrate-sparkle-drift 1.2s cubic-bezier(0.25, 0.5, 0.45, 1) forwards;
}

/* ---------------------------------------------------------------------
 * Phase 15 — reduced-motion overrides
 * --------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .etp-char-eye-lens-specular,
  .etp-char-eye-pupil,
  .etp-char-head-shell,
  .etp-char-celebrate-sparkle,
  .etp-char-chest-ring--heartbeat {
    animation: none !important;
  }
  .etp-character[data-etp-state="thinking"] .etp-char-cranial-seam {
    animation: none !important;
  }
  .etp-char-mouth { transition: none !important; }
  .etp-character[data-etp-state] .etp-char-head-shell { transform: none !important; }
}

/* ---------------------------------------------------------------------
 * Phase 15 — high-contrast overrides
 * --------------------------------------------------------------------- */
@media (prefers-contrast: more) {
  .etp-char-presence-ring {
    stroke: rgba(255, 255, 255, 0.85);
    stroke-width: 2;
  }
  .etp-char-visor-crack { display: none; }
  .etp-char-eye-iris    { filter: contrast(1.15) brightness(1.05); }
}

/* =====================================================================
 * Phase 16 — white body refresh (2026-05-27e)
 *
 * Operator request: "make all the body parts of ET+ AI Tutor in white
 * color".
 *
 * Implementation strategy — pure-token override at the tail of the
 * cascade so we don't touch any prior phase's CSS rules:
 *
 *   STRUCTURAL BODY (now white-on-white)
 *     - Helmet shell:            silver → white
 *     - Helmet edge / silhouette: charcoal → light cool-grey
 *     - Pearlescent body gradient: blue-tinted → near-white ramp
 *     - Face panel ("screen"):   dark teal #0C2638 → off-white
 *     - Face panel inner gradient: dark-teal → light cream stops
 *     - Shoulder caps:           silver → white
 *     - Arm cuffs:               silver → white
 *     - Ear modules pad:         silver → white
 *     - Neck collar:             charcoal → very light grey
 *     - Visor base + edge:       lighter, near-neutral
 *
 *   IDENTITY ELEMENTS preserved unchanged (these are NOT body parts,
 *   they are signature features that carry ET+'s personality):
 *     - Cyan-glow eyes (iris, pupil, halo, lens, lens-specular)
 *     - Cyan chest beacon (chest-core-teal + chest-core-hot)
 *     - ET+ wordmark (E/T/+ chest brand)
 *     - Cranial seam emission (cyan glow line at eye-height)
 *     - Mouth (dark-blue smile)
 *     - Crown LEDs (cyan status column)
 *     - Programme variants (per-programme --etp-light + --etp-pulse-tint)
 *
 * Sacred invariants from Phases 8 → 15 all preserved: canonical smile
 * path, 8 eye-system selectors + Phase 14 lens-specular, aperture
 * blades, cranial seam glow, chest core triplet, 9-state machine,
 * 5 programme + 4 persona variants, behaviour-bus contract.
 *
 * Reverting Phase 16: remove this single section.
 * ===================================================================== */

.etp-character {
  /* --- Pearlescent body ramp — pure-white with very faint cool shadow */
  --etp-body-highlight: #FFFFFF;
  --etp-body-mid:       #F7F8FA;
  --etp-body-shadow:    #E2E5E9;
  --etp-body-deep:      #C5CAD0;

  /* --- Helmet + shoulder + arm + ear-pad metals — all white */
  --etp-helmet-silver:   #FFFFFF;
  --etp-helmet-edge:     #C8CCD2;  /* light cool-grey silhouette edge */
  --etp-shoulder-silver: #FFFFFF;
  --etp-arm-silver:      #FFFFFF;

  /* --- Neck collar — keep visible as a distinct part, but very light */
  --etp-neck-charcoal:   #E0E2E6;

  /* --- Face panel "screen" — off-white instead of dark teal so the
       panel still reads as a distinct surface (the eyes need a backing
       to register against) but in keeping with the white-body brief. */
  --etp-screen:          #F4F6F8;

  /* --- Visor — barely tinted neutral so eyes/smile sit on a clean head */
  --etp-visor-base:        rgba(220, 224, 228, 0.18);
  --etp-visor-edge:        rgba(170, 180, 190, 0.30);
  --etp-visor-inner-shadow: rgba(160, 165, 172, 0.05);
}

/* --- Face-panel inner radial gradient stops (Phase 11 hue-shifts) ----
 * The default + per-state face-grad-inner/outer were dark-teal rgba.
 * Replace with light-cream stops so the "screen" still has gentle
 * depth without going dark. Per-state warmer/cooler nuance preserved
 * via small hue offsets. */
.etp-character {
  --etp-face-grad-inner: rgba(245, 248, 252, 0.96);
  --etp-face-grad-outer: rgba(225, 230, 236, 0.92);
}
.etp-character[data-etp-state="encouraging"],
.etp-character[data-etp-state="celebrating"] {
  /* warmer cream when the character is happy */
  --etp-face-grad-inner: rgba(252, 248, 240, 0.96);
  --etp-face-grad-outer: rgba(240, 232, 220, 0.92);
}
.etp-character[data-etp-state="thinking"],
.etp-character[data-etp-state="alert"] {
  /* cooler off-white when the character is focused */
  --etp-face-grad-inner: rgba(244, 248, 252, 0.96);
  --etp-face-grad-outer: rgba(222, 230, 240, 0.92);
}

/* --- Helmet silhouette edge — Phase 15 Tier 1 #3 added a 0.4px charcoal
 * drop-shadow for weight. On a white body that hairline still reads
 * as the silhouette edge; soften it slightly so it doesn't fight
 * the all-white palette. */
.etp-character[data-etp-scale="hero"]      .etp-char-head-shell,
.etp-character[data-etp-scale="classroom"] .etp-char-head-shell {
  filter: drop-shadow(0 0.4px 0 rgba(140, 148, 158, 0.45));
}
/* The Phase 15 brushed-aluminum + iridescent filter chain is layered
 * via @media (prefers-reduced-motion: no-preference) at line ~3939.
 * That rule references the same drop-shadow opacity as the unconditional
 * one above — let it keep its existing chain; the iridescent feColorMatrix
 * adds a 6% rainbow tint on top of whatever the helmet shell colour is,
 * which on a now-white helmet reads as a faint pastel rim rather than the
 * brushed-steel hint of prior phases. That's the intended Phase 16 look. */

/* --- Programme variants on a white body — the programme tint that
 * Phase 13 #E.2 fans into the chest gradient still drives the
 * --etp-pulse-tint + --etp-light-glow tokens. Those tokens are NOT
 * body tokens, so per-programme cyan-mint / orange / violet / blue
 * / green continue to read on the chest beacon, the crown LEDs, and
 * the ear-meter accent stripe (Phase 15 #22). The structural body
 * stays white in all 5 programmes. */

/* --- WCAG-AAA mode (Phase 15 #20) — keep darker outlines so contrast
 * holds against the now-white body. Phase 15's high-contrast-aaa
 * rules already set 0.8px charcoal stroke on iris/head/mouth/seam —
 * those rules are downstream in the file so they still win. */

/* --- prefers-contrast: more — the default Phase 15 rule already lifts
 * iris contrast; on a white body the head no longer needs the
 * additional accent. Nothing to override. */

/* =====================================================================
 * Phase 17 — clear humanoid robot + animated lips (2026-05-27f)
 *
 * Operator request: "let ET+ AI Tutor have a clear smiling mouth and
 * clear lips able to open and close when talking and have an obvious
 * clear humanoid robot white body".
 *
 * Three deliverables:
 *
 * 1. Real lip geometry — upper lip + lower lip + dark mouth cavity
 *    rendered as new SVG paths. The canonical smile path
 *    "M 41 47 Q 50 53.2 59 47" stays in place as the lip-line between
 *    them (sacred invariant intact).
 *
 * 2. Lip animation — lips open/close synchronised with speaking:
 *    - data-etp-speaking="1" (toggled every stream-chunk by Phase 15
 *      Tier 1 #4) drops the lower lip 1px and lifts the upper lip
 *      0.5px so dark mouth cavity shows between them.
 *    - state="celebrating" opens the mouth wider (1.8px + scale).
 *    - state="composed" or "waiting" seals the lips flush.
 *    - 80ms transitions keep cycles fast enough to read as natural
 *      speech rather than slow articulation.
 *
 * 3. Humanoid robot structural panel lines — visible light-grey
 *    hairlines on the white body that read "machined assembly":
 *    forehead seam, 2 temple panels, chin separator. (Cheek bevels
 *    removed per canonical spec — no cheek shadow hints.)
 *    Visible at hero + classroom; hidden at convo + chrome.
 *
 * All Phases 8-16 contracts preserved.
 * ===================================================================== */

/* --- Lip layer styling ------------------------------------------------ */
/* All three lip elements use the same --etp-mouth token so the WCAG-AAA
   mode override + light-theme handling already in place continue to
   apply.  */

.etp-char-mouth-upper-lip,
.etp-char-mouth-lower-lip {
  /* Canonical-spec §13: equal-height cyan smiley lips. The cyan fill
     alone defines the shape — NO edge-strokes (the spec forbids them). */
  fill: var(--etp-mouth, rgba(0, 220, 236, 0.92));
  stroke: none;
  transform-origin: center;
  transform-box: fill-box;
  transition: transform 80ms ease-out;
}

.etp-char-mouth-cavity {
  fill: rgba(20, 28, 42, 0.95);
  opacity: 0;             /* invisible when lips are closed */
  transition: opacity 80ms ease-out;
  pointer-events: none;
}

/* When the original .etp-char-mouth path is visible behind the lips it
   serves as the lip-seam (the canonical smile centerline). Slightly
   softer stroke so lips read as the primary mouth element. */
.etp-char-mouth {
  stroke-width: 1.6;
  opacity: 0.78;
}

/* --- Mouth animation hooks ------------------------------------------- */

/* Speaking — lips part, cavity becomes visible.
   data-etp-speaking="1" is toggled by Phase 15 JS on every
   etpux:stream-chunk (cleared 180ms later). */
.etp-character[data-etp-speaking="1"] .etp-char-mouth-upper-lip {
  transform: translateY(-0.5px);
}
.etp-character[data-etp-speaking="1"] .etp-char-mouth-lower-lip {
  transform: translateY(1px);
}
.etp-character[data-etp-speaking="1"] .etp-char-mouth-cavity {
  opacity: 0.95;
}

/* Celebrating — wider open, slightly enlarged lower lip */
.etp-character[data-etp-state="celebrating"] .etp-char-mouth-lower-lip {
  transform: translateY(1.8px) scale(1.08);
}
.etp-character[data-etp-state="celebrating"] .etp-char-mouth-upper-lip {
  transform: translateY(-0.7px);
}
.etp-character[data-etp-state="celebrating"] .etp-char-mouth-cavity {
  opacity: 0.92;
}

/* Composed / waiting — lips meet at the canonical smile line */
.etp-character[data-etp-state="composed"] .etp-char-mouth-upper-lip,
.etp-character[data-etp-state="composed"] .etp-char-mouth-lower-lip,
.etp-character[data-etp-state="waiting"]  .etp-char-mouth-upper-lip,
.etp-character[data-etp-state="waiting"]  .etp-char-mouth-lower-lip {
  transform: translateY(0);
}
.etp-character[data-etp-state="composed"] .etp-char-mouth-cavity,
.etp-character[data-etp-state="waiting"]  .etp-char-mouth-cavity {
  opacity: 0;
}

/* Alert — flatten slightly, no cavity */
.etp-character[data-etp-state="alert"] .etp-char-mouth-upper-lip {
  transform: translateY(0.15px);
}
.etp-character[data-etp-state="alert"] .etp-char-mouth-lower-lip {
  transform: translateY(-0.15px) scaleY(0.7);
}

/* Thinking — slightly parted (concentration) */
.etp-character[data-etp-state="thinking"] .etp-char-mouth-lower-lip {
  transform: translateY(0.3px);
}

/* --- Per-scale lip visibility ---------------------------------------- */
/* Lips render at hero + classroom + convo. At chrome (favicon) scale,
   the lips are too small to discern — fall back to the canonical
   smile stroke. */
.etp-character[data-etp-scale="chrome"] .etp-char-mouth-upper-lip,
.etp-character[data-etp-scale="chrome"] .etp-char-mouth-lower-lip,
.etp-character[data-etp-scale="chrome"] .etp-char-mouth-cavity {
  display: none;
}

/* --- Humanoid robot structural panel lines --------------------------- */
/* Canonical-spec note: cheek bevels removed (forbidden cheek shadow
   hints). Forehead seam + temple panels + chin separator remain — they
   are light-silver structural seams, not shadows. */
.etp-char-helmet-panel,
.etp-char-chin-separator {
  fill: none;
  stroke: rgba(140, 148, 158, 0.55);
  stroke-width: 0.4;
  stroke-linecap: round;
  pointer-events: none;
}

/* Forehead panel — slightly stronger stroke (it's the most prominent
   structural reference: "the helmet top closes here") */
.etp-char-helmet-panel--forehead {
  stroke-width: 0.5;
  stroke: rgba(130, 138, 148, 0.62);
}

/* Chin separator — slightly more visible because it carries the
   "facial structure ends, neck assembly begins" read */
.etp-char-chin-separator {
  stroke: rgba(120, 128, 138, 0.65);
  stroke-width: 0.45;
}

/* Cheek bevels: REMOVED per canonical spec (no cheek shadow hints). */

/* --- Per-scale structural-line visibility ---------------------------- */
/* Hero + classroom show full structure. Convo softens it. Chrome hides
   everything except the silhouette. */
.etp-character[data-etp-scale="convo"] .etp-char-helmet-panel,
.etp-character[data-etp-scale="convo"] .etp-char-chin-separator {
  opacity: 0.55;
}
.etp-character[data-etp-scale="chrome"] .etp-char-helmet-panel,
.etp-character[data-etp-scale="chrome"] .etp-char-chin-separator {
  display: none;
}

/* --- Strengthen Phase 10 neck-segment seams on the white body -------- */
/* Phase 10 added neck-segment lines styled via the cascade. On the
   Phase 16 white body they may have become hard to read — explicit
   stroke here ensures they show. */
.etp-char-neck-segment {
  stroke: rgba(120, 128, 138, 0.60) !important;
  stroke-width: 0.5 !important;
}

/* --- prefers-reduced-motion: keep mouth motion minimal but allow the
       open/close (it's a communication signal, not decoration). The
       transition is 80ms which is already imperceptibly short. */
@media (prefers-reduced-motion: reduce) {
  .etp-char-mouth-upper-lip,
  .etp-char-mouth-lower-lip,
  .etp-char-mouth-cavity {
    transition: none;
  }
}

/* --- prefers-contrast: more — strengthen lip outlines for visibility */
@media (prefers-contrast: more) {
  .etp-char-mouth-upper-lip,
  .etp-char-mouth-lower-lip {
    stroke: rgba(15, 28, 50, 1);
    stroke-width: 0.4;
  }
  .etp-char-helmet-panel,
  .etp-char-chin-separator {
    stroke: rgba(60, 68, 78, 0.85) !important;
    stroke-width: 0.55 !important;
  }
}

/* --- WCAG-AAA mode (Phase 15 #20) coexistence ------------------------ */
[data-etp-theme="high-contrast-aaa"] .etp-char-mouth-upper-lip,
[data-etp-theme="high-contrast-aaa"] .etp-char-mouth-lower-lip {
  fill: rgba(15, 23, 42, 0.98);
  stroke: rgba(15, 23, 42, 1);
  stroke-width: 0.5;
}
[data-etp-theme="high-contrast-aaa"] .etp-char-helmet-panel,
[data-etp-theme="high-contrast-aaa"] .etp-char-chin-separator {
  stroke: rgba(15, 23, 42, 0.85);
  stroke-width: 0.6;
}

/* =====================================================================
 * PHASE 18 — Remove all black colors that cover the body (2026-05-27)
 *
 * Operator request: "remove all black colors that cover the body".
 *
 * Two surfaces on the body still rendered as black/near-black after
 * Phase 16's white-body refresh:
 *
 *   1. Mouth lips + canonical smile path — driven by --etp-mouth which
 *      Phase 12 deepened to rgba(15, 55, 105, 0.85) (dark blue). On a
 *      white body that reads as a black patch.
 *
 *   2. Phase 17 mouth cavity — hardcoded fill rgba(20, 28, 42, 0.95)
 *      (near-black). Becomes visible when lips part on
 *      data-etp-speaking="1"; rendered as a black opening.
 *
 * Phase 18 flips both to cyan-aligned tones that match the existing
 * brand identity (chest core teal #00DCEC + eye iris #4BAEE8 +
 * cranial seam glow + crown LEDs). The mouth keeps its personality
 * but reads as a cyan brand accent on the white body, not a black
 * patch. The cavity becomes a subtle light cyan-grey so an open
 * mouth still reads as "interior depth" without darkness.
 *
 * NOT touched: --etp-eye-pupil (#0A1F33) — eye-system internal, part
 * of the eye identity, not on the body surface. The mouth speaking
 * glow filter (Phase 15 Tier 1 #4) keeps working because the cyan
 * mouth color complements its drop-shadow.
 *
 * Revert path: remove this single appended section. --etp-mouth
 * falls back to its Phase 12 dark-blue value; cavity fill falls
 * back to its Phase 17 near-black value.
 * ===================================================================== */

:root,
html[data-etp-theme="dark"],
html[data-etp-theme="light"] {
  /* Phase 18 (2026-05-27) — flipped to cyan-teal matching
     --etp-chest-core-teal (#00DCEC at 0.92 opacity). No more dark
     blue on the lips, upper lip, lower lip, or canonical smile
     stroke. */
  --etp-mouth: rgba(0, 220, 236, 0.92);
}

/* Phase 17 lips + canonical smile path now pick up the cyan token via
   `var(--etp-mouth)` automatically. Lighten the stroke-on-lips so the
   lip edges read as a soft cyan outline instead of a hard contour. */
.etp-char-mouth-upper-lip,
.etp-char-mouth-lower-lip {
  /* Canonical-spec §13: NO edge-strokes — Phase 18 soft cyan outline removed. */
  stroke: none;
}

/* Mouth cavity — was a near-black opening rgba(20, 28, 42, 0.95) when
   the mouth opens on speaking. Phase 18 flips it to a subtle light
   cyan-grey that reads as "open mouth interior" on the white body
   without introducing any black. The 0.55 opacity keeps the cavity
   visible at the brand accent intensity matching the lip stroke. */
.etp-char-mouth-cavity {
  fill: rgba(200, 230, 240, 0.55) !important;
}

/* Per-state nuance — the brushed-aluminum filter chain (Phase 15 Tier
   3 #15) reads marginally cooler than the new cyan mouth on bright
   monitors. The Phase 15 mouth-glow drop-shadow on data-etp-speaking
   (line ~3762) keeps the cyan tint when the lips part — no change
   needed there. */

/* High-contrast AAA preserves Phase 15's #1A1F2E-on-#FFF override for
   .etp-char-mouth; that override predates Phase 18 and isn't a "body
   black" — it's the accessibility-required contrast band. Leave it. */
[data-etp-theme="high-contrast-aaa"] {
  --etp-mouth: rgba(15, 23, 42, 0.95);
}
[data-etp-theme="high-contrast-aaa"] .etp-char-mouth-cavity {
  fill: rgba(15, 23, 42, 0.55) !important;
}

/* =====================================================================
 * PHASE 19 — Tier 1-6 30-item humanoid + sci-fi mega-batch (2026-05-27g)
 *
 * Operator request: build all 30 brainstormed items systematically
 * while preserving the Phase 18 cyan-mouth contract.
 *
 * 14 SVG elements injected via _injectPhase19Marks. This section adds
 * their styling + the 13 pure-CSS items + the 3 JS-hook state styles.
 *
 *   Tier 1 — humanoid lift:
 *     #1 3rd neck ring · #2 shoulder peeks · #3 eyebrows · #4 sensor
 *     disc · #5 ambient occlusion shadow.
 *   Tier 2 — sci-fi tells:
 *     #6 iris dots · #7 data ribbon · #8 visor flicker · #9 boot
 *     sequence · #10 hex chest facets.
 *   Tier 3 — programme/state expressiveness:
 *     #11 programme edge glow · #12 thinking grid · #13 speech waveform
 *     · #14 programme posture · #15 cursor proximity warmth.
 *   Tier 4 — material depth:
 *     #16 warm dual specular · #17 iridescent micro · #18 vent slits
 *     · #19 warm undertone · #20 chin shadow.
 *   Tier 5 — architectural sci-fi:
 *     #21 data glyphs · #22 holo ring · #23 tool plane · #24 wireframe
 *     mode · #25 lower-body hint.
 *   Tier 6 — perception ceiling polish:
 *     #26 cyan AA glow · #27 idle micro-tilt · #28 programme floor
 *     · #29 per-state seam hue · #30 tab visibility rest pose.
 *
 * Sacred invariants preserved:
 *   - Phase 18 --etp-mouth + cavity contract (NOT touched).
 *   - Phase 17 lips + structural panels.
 *   - Phase 16 white body + 4 identity tokens.
 *   - Phase 13/14/15 inject markers.
 *   - WCAG-AAA contrast override.
 *
 * Revert path: remove this single appended section.
 * ===================================================================== */

/* --------------------------------------------------------------------
 * T1.1 — 3rd neck articulation ring
 * Inherits .etp-char-neck-segment base styling. No additional CSS
 * needed — the existing rule covers stroke colour + width.
 * -------------------------------------------------------------------- */

/* --------------------------------------------------------------------
 * T1.3 — Eyebrow ridges
 * Per-state opacity + rotation drives the entire expression dimension
 * beyond the aperture. Default is subtle; emotions modulate.
 * -------------------------------------------------------------------- */
.etp-char-eyebrow {
  fill: none;
  stroke: var(--etp-eye-iris, #4BAEE8);
  stroke-width: 0.45;
  stroke-linecap: round;
  opacity: 0.55;
  transform-origin: center;
  transform-box: fill-box;
  transition: opacity 280ms ease-out, transform 280ms ease-out;
}
/* Per-state geometry — relative rotations express emotion */
.etp-character[data-etp-state="encouraging"] .etp-char-eyebrow {
  opacity: 0.7;
  transform: translateY(-0.4px);
}
.etp-character[data-etp-state="celebrating"] .etp-char-eyebrow {
  opacity: 0.85;
  transform: translateY(-0.8px);
}
.etp-character[data-etp-state="thinking"] .etp-char-eyebrow--l {
  transform: rotate(8deg);
  transform-origin: 41.5px 19.5px;
}
.etp-character[data-etp-state="thinking"] .etp-char-eyebrow--r {
  transform: rotate(-3deg);
  transform-origin: 58.5px 19.5px;
}
.etp-character[data-etp-state="alert"] .etp-char-eyebrow {
  opacity: 0.78;
  transform: translateY(0.4px);
}
.etp-character[data-etp-state="alert"] .etp-char-eyebrow--l {
  transform: rotate(-6deg) translateY(0.4px);
  transform-origin: 41.5px 19.5px;
}
.etp-character[data-etp-state="alert"] .etp-char-eyebrow--r {
  transform: rotate(6deg) translateY(0.4px);
  transform-origin: 58.5px 19.5px;
}
.etp-character[data-etp-scale="chrome"] .etp-char-eyebrow {
  display: none;
}

/* --------------------------------------------------------------------
 * T1.4 — Crown sensor disc
 * Single flat oval at top of helmet with a tiny lens highlight.
 * Reads as a sensor cluster / camera array — premium-robot signature.
 * Auto-coloured by the white body tokens so the ring blends with the
 * helmet but the lens carries cyan brand accent.
 * -------------------------------------------------------------------- */
.etp-char-sensor-disc {
  fill: rgba(220, 226, 232, 0.92);
  stroke: rgba(140, 148, 158, 0.55);
  stroke-width: 0.3;
}
.etp-char-sensor-disc__lens {
  fill: var(--etp-light, #00DCEC);
  filter: drop-shadow(0 0 0.6px var(--etp-light, #00DCEC));
}
@keyframes etp-sensor-pulse {
  0%, 100% { opacity: 0.85; }
  50%      { opacity: 1; }
}
.etp-char-sensor-disc__lens {
  animation: etp-sensor-pulse 4.6s ease-in-out infinite;
}
.etp-character[data-etp-scale="chrome"] .etp-char-sensor-disc,
.etp-character[data-etp-scale="chrome"] .etp-char-sensor-disc__lens {
  display: none;
}

/* --------------------------------------------------------------------
 * T1.2 — Shoulder articulation peeks
 * Soft cyan glow at the bottom of the bust crop where the shoulders
 * imply continuation below the frame. Hero scale only.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-scale="hero"]      .etp-char-shoulder-cap,
.etp-character[data-etp-scale="classroom"] .etp-char-shoulder-cap {
  filter: drop-shadow(0 1px 1.4px rgba(0, 220, 236, 0.32));
}

/* --------------------------------------------------------------------
 * T1.5 — Ambient occlusion shadow under the bust
 * Subtle 8% opacity oval shadow under the floor-reflection band so the
 * figure grounds as a 3D object instead of floating. Hero only.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-scale="hero"] .etp-char-svg::after {
  /* not directly applicable to SVG — we inject grounding via the body
     filter chain at hero scale */
}
.etp-character[data-etp-scale="hero"]      .etp-char-body,
.etp-character[data-etp-scale="classroom"] .etp-char-body {
  filter: drop-shadow(0 2.2px 1.2px rgba(15, 23, 42, 0.08));
}

/* --------------------------------------------------------------------
 * T2.6 — Iris sensor-array dot pattern
 * 4 small darker-cyan dots per iris suggesting a multi-camera array.
 * Same shape, but reads as "real optical instrument" not "cartoon eye".
 * -------------------------------------------------------------------- */
.etp-char-iris-dot {
  fill: rgba(15, 35, 60, 0.42);
  pointer-events: none;
  transition: opacity 280ms ease-out;
}
.etp-character[data-etp-state="thinking"] .etp-char-iris-dot {
  opacity: 0.75;
}
.etp-character[data-etp-state="alert"] .etp-char-iris-dot {
  opacity: 0.92;
  fill: rgba(15, 35, 60, 0.58);
}
.etp-character[data-etp-scale="chrome"] .etp-char-iris-dot,
.etp-character[data-etp-scale="convo"]  .etp-char-iris-dot {
  display: none;  /* invisible at small scales; iris is already small */
}

/* --------------------------------------------------------------------
 * T2.7 — Holographic data ribbon (streaming-only volumetric "thought")
 * Vertical wisp emerging from the cranial seam at top of head. Hidden
 * by default; appears + animates on data-etp-streaming="1" (toggled
 * by Phase 15 stream-chunk handlers).
 * -------------------------------------------------------------------- */
.etp-char-data-ribbon {
  fill: none;
  stroke: var(--etp-light, #00DCEC);
  stroke-width: 0.42;
  stroke-linecap: round;
  opacity: 0;
  filter: drop-shadow(0 0 0.6px var(--etp-light, #00DCEC));
  transition: opacity 380ms ease-in-out;
  pointer-events: none;
}
.etp-character[data-etp-streaming="1"] .etp-char-data-ribbon {
  opacity: 0.55;
  animation: etp-ribbon-drift 2.4s ease-in-out infinite;
}
@keyframes etp-ribbon-drift {
  0%, 100% { transform: translateY(0)      scaleY(1); opacity: 0.42; }
  50%      { transform: translateY(-1.4px) scaleY(1.12); opacity: 0.7; }
}
.etp-character[data-etp-scale="chrome"] .etp-char-data-ribbon,
.etp-character[data-etp-scale="convo"]  .etp-char-data-ribbon {
  display: none;
}

/* --------------------------------------------------------------------
 * T2.8 — Holographic visor scan band
 * Subtle horizontal scan band that flickers across the eye-line every
 * ~22s. Sci-fi sentience tell. Hero + classroom only.
 * -------------------------------------------------------------------- */
.etp-char-visor-scan {
  fill: var(--etp-light, #00DCEC);
  opacity: 0;
  pointer-events: none;
}
@keyframes etp-visor-flicker {
  0%, 92%, 100%   { opacity: 0; }
  93%, 95%, 97%   { opacity: 0.42; }
  94%, 96%, 98%   { opacity: 0.18; }
}
.etp-character[data-etp-scale="hero"]      .etp-char-visor-scan,
.etp-character[data-etp-scale="classroom"] .etp-char-visor-scan {
  animation: etp-visor-flicker 22s ease-in-out infinite;
}

/* --------------------------------------------------------------------
 * Phase 6 (2026-06-14) §5 — holographic face overlays.
 *   holo-shimmer : a diagonal cyan sheen that sweeps across the face
 *                  panel (distinct from the white visor gloss). Reads
 *                  as a "projected hologram" surface light.
 *   scan-sweep   : a thin horizontal cyan sensor band that travels
 *                  down the face on a timed loop.
 * Both are glow-only (cyan, no occlusion shadow), clipped to the face
 * interior by the reused shimmer clip, and suppressed at chrome/convo
 * scale + on reduced-motion / high-contrast branches.
 * -------------------------------------------------------------------- */
.etp-char-holo-shimmer {
  fill: var(--etp-light, #00E5FF);
  opacity: 0;
}
.etp-char-scan-sweep {
  fill: var(--etp-light, #00E5FF);
  opacity: 0;
  filter: drop-shadow(0 0 0.8px var(--etp-light-glow, rgba(0, 220, 236, 0.55)));
}
.etp-character[data-etp-scale="hero"]      .etp-char-holo-shimmer,
.etp-character[data-etp-scale="classroom"] .etp-char-holo-shimmer {
  animation: etp-holo-shimmer 7.5s ease-in-out infinite;
}
.etp-character[data-etp-scale="hero"]      .etp-char-scan-sweep,
.etp-character[data-etp-scale="classroom"] .etp-char-scan-sweep {
  animation: etp-scan-sweep 6s linear infinite;
}
@keyframes etp-holo-shimmer {
  0%   { transform: translateX(0);     opacity: 0; }
  20%  { opacity: 0.14; }
  50%  { transform: translateX(22px);  opacity: 0.18; }
  80%  { opacity: 0.14; }
  100% { transform: translateX(46px);  opacity: 0; }
}
@keyframes etp-scan-sweep {
  0%   { transform: translateY(0);     opacity: 0; }
  8%   { opacity: 0.55; }
  50%  { transform: translateY(13px);  opacity: 0.55; }
  92%  { opacity: 0.55; }
  100% { transform: translateY(27px);  opacity: 0; }
}
/* Chrome favicon + convo scale: the face overlays are imperceptible
 * and add cost — suppress the whole group. */
.etp-character[data-etp-scale="chrome"] .etp-char-holo-overlay,
.etp-character[data-etp-scale="convo"]  .etp-char-holo-overlay {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .etp-char-holo-shimmer,
  .etp-char-scan-sweep { animation: none; opacity: 0; }
}
@media (prefers-contrast: more) {
  .etp-char-holo-shimmer { opacity: 0.06; }
}
.etp-character[data-etp-theme="high-contrast"]     .etp-char-holo-overlay,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-holo-overlay {
  display: none;
}

/* --------------------------------------------------------------------
 * Phase 7 (2026-06-14) §11 — NEURAL CIRCUIT TRACES
 * Four thin angular cyan paths (two per side, mirrored about x=50) on
 * the temple + cheek zones, each a repeating dash that marches along
 * the path (a "current flowing" PCB read) via the neural-trace
 * animation, tipped with a softly-pulsing node dot. Cyan glow only —
 * luminous overlays, never occlusion shadows. Clipped to the face
 * interior; suppressed at chrome/convo scale + on reduced-motion /
 * high-contrast branches.
 * -------------------------------------------------------------------- */
.etp-char-neural-trace {
  stroke: var(--etp-light, #00E5FF);
  stroke-width: 0.32;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* pathLength=100 → a repeating 8-on / 6-off dash (period 14). */
  stroke-dasharray: 8 6;
  opacity: 0.5;
  filter: drop-shadow(0 0 0.6px var(--etp-light-glow, rgba(0, 220, 236, 0.55)));
}
.etp-char-neural-node {
  fill: var(--etp-light, #00E5FF);
  opacity: 0.55;
  filter: drop-shadow(0 0 0.6px var(--etp-light-glow, rgba(0, 220, 236, 0.6)));
}
@keyframes etp-neural-trace {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -14; }
}
@keyframes etp-neural-node {
  0%   { opacity: 0.35; }
  50%  { opacity: 0.85; }
  100% { opacity: 0.35; }
}
.etp-character[data-etp-scale="hero"]      .etp-char-neural-trace,
.etp-character[data-etp-scale="classroom"] .etp-char-neural-trace {
  animation: etp-neural-trace 2.2s linear infinite;
}
.etp-character[data-etp-scale="hero"]      .etp-char-neural-trace--2,
.etp-character[data-etp-scale="classroom"] .etp-char-neural-trace--2 { animation-delay: -0.55s; }
.etp-character[data-etp-scale="hero"]      .etp-char-neural-trace--3,
.etp-character[data-etp-scale="classroom"] .etp-char-neural-trace--3 { animation-delay: -1.1s; }
.etp-character[data-etp-scale="hero"]      .etp-char-neural-trace--4,
.etp-character[data-etp-scale="classroom"] .etp-char-neural-trace--4 { animation-delay: -1.65s; }
.etp-character[data-etp-scale="hero"]      .etp-char-neural-node,
.etp-character[data-etp-scale="classroom"] .etp-char-neural-node {
  animation: etp-neural-node 2.2s ease-in-out infinite;
}
/* Chrome favicon + convo scale: traces are imperceptible — suppress. */
.etp-character[data-etp-scale="chrome"] .etp-char-neural,
.etp-character[data-etp-scale="convo"]  .etp-char-neural {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .etp-char-neural-trace,
  .etp-char-neural-node { animation: none !important; }
  .etp-char-neural-trace { stroke-dashoffset: 0; }
}
@media (prefers-contrast: more) {
  .etp-char-neural-trace { opacity: 0.62; }
  .etp-char-neural-node  { opacity: 0.7; }
}
/* High-contrast themes: drop the decorative circuitry to keep the face
 * panel crisp and high-legibility. */
.etp-character[data-etp-theme="high-contrast"]     .etp-char-neural,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-neural {
  display: none;
}

/* --------------------------------------------------------------------
 * T2.10 — Hex chest facets overlay
 * 6-facet hexagonal lens pattern over the chest core. Reads as a
 * power-crystal / faceted gemstone rather than a flat icon. The
 * Phase 12 ET+ wordmark still sits on top; the hex sits between the
 * core and the rim so it reads as facet ridges.
 * -------------------------------------------------------------------- */
.etp-char-chest-facets {
  fill: none;
  stroke: rgba(255, 255, 255, 0.34);
  stroke-width: 0.22;
  stroke-linejoin: miter;
  pointer-events: none;
  transition: opacity 320ms ease-out;
}
/* Animated subtle rotation — sells the lens as a real optical element */
@keyframes etp-hex-spin {
  from { transform: rotate(0deg); transform-origin: 50px 103px; }
  to   { transform: rotate(60deg); transform-origin: 50px 103px; }
}
.etp-character[data-etp-scale="hero"] .etp-char-chest-facets,
.etp-character[data-etp-scale="classroom"] .etp-char-chest-facets {
  animation: etp-hex-spin 28s linear infinite;
}
.etp-character[data-etp-scale="chrome"] .etp-char-chest-facets {
  display: none;
}

/* --------------------------------------------------------------------
 * T2.9 — Power-up boot sequence (first-paint per session)
 * Triggered by JS toggling data-etp-boot="1" on the anchor for 2.4s.
 * Sequence: crown LEDs sweep bottom-to-top → aperture opens →
 * chest beacon ignites → exhale settle.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-boot="1"] {
  animation: etp-boot-wake 2.4s ease-out forwards;
}
@keyframes etp-boot-wake {
  0%  { opacity: 0.35; transform: scale(0.985); }
  20% { opacity: 0.6;  transform: scale(0.99); }
  60% { opacity: 0.92; transform: scale(1.005); }
  100%{ opacity: 1;    transform: scale(1); }
}
.etp-character[data-etp-boot="1"] .etp-char-chest-core,
.etp-character[data-etp-boot="1"] .etp-char-chest-core-glow {
  animation: etp-boot-chest-ignite 2.4s ease-out forwards;
}
@keyframes etp-boot-chest-ignite {
  0%, 30% { opacity: 0; }
  60%     { opacity: 0.55; }
  85%     { opacity: 1.1; }
  100%    { opacity: 1; }
}
.etp-character[data-etp-boot="1"] .etp-char-eye-iris,
.etp-character[data-etp-boot="1"] .etp-char-eye-pupil {
  animation: etp-boot-eye-open 2.4s ease-out forwards;
}
@keyframes etp-boot-eye-open {
  0%, 40% { transform: scaleY(0.05); transform-origin: center; }
  70%     { transform: scaleY(1.06); }
  100%    { transform: scaleY(1); }
}
.etp-character[data-etp-boot="1"] .etp-char-cranial-seam {
  animation: etp-boot-seam-sweep 2.4s ease-out forwards;
}
@keyframes etp-boot-seam-sweep {
  0%, 35% { opacity: 0; }
  50%     { opacity: 0.95; }
  100%    { opacity: 0.65; }
}
@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-boot="1"],
  .etp-character[data-etp-boot="1"] .etp-char-chest-core,
  .etp-character[data-etp-boot="1"] .etp-char-chest-core-glow,
  .etp-character[data-etp-boot="1"] .etp-char-eye-iris,
  .etp-character[data-etp-boot="1"] .etp-char-eye-pupil,
  .etp-character[data-etp-boot="1"] .etp-char-cranial-seam {
    animation: none;
  }
}

/* --------------------------------------------------------------------
 * T3.11 — Programme-tinted body edge highlights
 * Each programme contributes a 4% edge-glow tint to the helmet shell +
 * arm caps. All 5 programmes feel visually distinct without re-skinning.
 * Uses the same --etp-pulse-tint variable that Phase 13 introduced.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-scale="hero"]      .etp-char-head-circle,
.etp-character[data-etp-scale="classroom"] .etp-char-head-circle {
  /* Layer programme tint on top of the existing Phase 16 white-body fill */
  box-shadow: 0 0 0 0.4px var(--etp-pulse-tint, var(--etp-light, #00DCEC));
  /* Inline filter chain — preserves existing Phase 15 brushed-aluminum
     + Phase 16 silhouette edge, just adds a soft tinted halo. */
}
.etp-character[data-etp-scale="hero"]      .etp-char-shoulder-cap,
.etp-character[data-etp-scale="classroom"] .etp-char-shoulder-cap {
  filter:
    drop-shadow(0 1px 1.4px rgba(0, 220, 236, 0.32))
    drop-shadow(0 0 0.6px var(--etp-pulse-tint, var(--etp-light, #00DCEC)));
}

/* --------------------------------------------------------------------
 * T3.12 — Holographic UI grid on face panel (thinking-state only)
 * Faint targeting reticule + grid lines on the face panel during the
 * thinking state. Fades out when state changes.
 * -------------------------------------------------------------------- */
.etp-char-thinking-grid {
  opacity: 0;
  pointer-events: none;
  transition: opacity 380ms ease-out;
}
.etp-char-thinking-grid__line {
  stroke: var(--etp-light, #00DCEC);
  stroke-width: 0.18;
  opacity: 0.32;
}
.etp-char-thinking-grid__reticule {
  fill: none;
  stroke: var(--etp-light, #00DCEC);
  stroke-width: 0.18;
  stroke-dasharray: 1.2 1.4;
  opacity: 0.42;
}
.etp-character[data-etp-state="thinking"] .etp-char-thinking-grid {
  opacity: 0.65;
}
.etp-character[data-etp-scale="chrome"] .etp-char-thinking-grid,
.etp-character[data-etp-scale="convo"]  .etp-char-thinking-grid {
  display: none;
}

/* --------------------------------------------------------------------
 * T3.13 — Speech waveform visualisation (data-etp-speaking="1")
 * 4 vertical bars below the mouth, animating with staggered keyframes
 * suggesting voice waveform. Pairs with the Phase 17 lip animation.
 * -------------------------------------------------------------------- */
.etp-char-speech-wave {
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease-out;
}
.etp-char-speech-wave__bar {
  fill: var(--etp-light, #00DCEC);
  transform-origin: center 54.3px;
  transform-box: fill-box;
  filter: drop-shadow(0 0 0.4px var(--etp-light, #00DCEC));
}
.etp-character[data-etp-speaking="1"] .etp-char-speech-wave {
  opacity: 0.78;
}
@keyframes etp-wave-bar-1 { 0%,100%{transform:scaleY(0.6);} 50%{transform:scaleY(2.4);} }
@keyframes etp-wave-bar-2 { 0%,100%{transform:scaleY(1.0);} 50%{transform:scaleY(3.2);} }
@keyframes etp-wave-bar-3 { 0%,100%{transform:scaleY(0.8);} 50%{transform:scaleY(2.8);} }
@keyframes etp-wave-bar-4 { 0%,100%{transform:scaleY(0.5);} 50%{transform:scaleY(2.0);} }
.etp-character[data-etp-speaking="1"] .etp-char-speech-wave__bar--1 { animation: etp-wave-bar-1 0.42s ease-in-out infinite; }
.etp-character[data-etp-speaking="1"] .etp-char-speech-wave__bar--2 { animation: etp-wave-bar-2 0.38s ease-in-out infinite 0.05s; }
.etp-character[data-etp-speaking="1"] .etp-char-speech-wave__bar--3 { animation: etp-wave-bar-3 0.44s ease-in-out infinite 0.1s; }
.etp-character[data-etp-speaking="1"] .etp-char-speech-wave__bar--4 { animation: etp-wave-bar-4 0.4s ease-in-out infinite 0.15s; }
@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-speaking="1"] .etp-char-speech-wave__bar { animation: none; }
}
.etp-character[data-etp-scale="chrome"] .etp-char-speech-wave { display: none; }

/* --------------------------------------------------------------------
 * T3.14 — Per-programme emotion posture defaults
 * Each programme contributes a subtle head tilt that becomes the
 * "personality posture" for that programme. Stacks with Phase 15
 * per-state head tilt (the parallax + state rotations compose).
 * -------------------------------------------------------------------- */
.etp-character[data-etp-programme="ai-math-plus"] {
  /* Math is contemplative — head leans 1.2° forward */
  --etp-programme-tilt: 1.2deg;
}
.etp-character[data-etp-programme="ai-steam-plus"] {
  /* STEAM is engaged + bright — neutral but slightly back */
  --etp-programme-tilt: -0.6deg;
}
.etp-character[data-etp-programme="ai-robotics-plus"] {
  /* Robotics is precise + still — perfectly upright */
  --etp-programme-tilt: 0deg;
}
.etp-character[data-etp-programme="ai-expert-plus"] {
  /* Expert is poised + warm — head slightly back */
  --etp-programme-tilt: -0.8deg;
}
.etp-character[data-etp-programme="ai-computer-science-plus"] {
  /* CS is alert + engaged — slight forward lean */
  --etp-programme-tilt: 0.8deg;
}
.etp-character[data-etp-scale="hero"]      .etp-char-svg,
.etp-character[data-etp-scale="classroom"] .etp-char-svg {
  transform: rotate(var(--etp-programme-tilt, 0deg));
  transform-origin: 50% 65%;
  transition: transform 720ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* --------------------------------------------------------------------
 * T3.15 — Cursor proximity warmth boost
 * When mouse is within the character's bounds + 200px, the iris
 * warm-spark gets a 20% opacity bump (the "they noticed you" tell).
 * -------------------------------------------------------------------- */
.etp-character[data-etp-proximate-warm="1"] .etp-char-eye-warm-spark {
  opacity: 1 !important;
  filter: drop-shadow(0 0 0.6px var(--etp-cream-spark, #FFF4E0));
}
.etp-character[data-etp-proximate-warm="1"] .etp-char-eye-halo {
  filter:
    blur(3.2px)
    drop-shadow(0 0 1.6px var(--etp-light, #00DCEC))
    drop-shadow(0 0 0.6px var(--etp-pulse-tint, var(--etp-light, #00DCEC)));
}

/* --------------------------------------------------------------------
 * T4.16 — Dual specular highlights (warm bottom-right complement)
 * Adds a warm-cream specular ellipse to balance the existing cool
 * top-left specular. Lifts the head from "circle" to "lit sphere".
 * -------------------------------------------------------------------- */
.etp-char-specular--warm {
  fill: rgba(255, 244, 224, 0.18);
  filter: blur(1.6px);
  pointer-events: none;
}
.etp-character[data-etp-scale="chrome"] .etp-char-specular--warm {
  display: none;
}

/* --------------------------------------------------------------------
 * T4.17 — Iridescent micro on lip rims + ear modules
 * Extends the Phase 15 helmet thin-film aesthetic to smaller surfaces.
 * The lip rims pick up a 4% rainbow shimmer at hero scale; ear modules
 * get a slightly stronger 6% shimmer because they're more discrete.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-scale="hero"] .etp-char-mouth-upper-lip,
.etp-character[data-etp-scale="hero"] .etp-char-mouth-lower-lip {
  filter: drop-shadow(0 0 0.3px rgba(255, 180, 200, 0.18))
          drop-shadow(0 0 0.3px rgba(150, 200, 255, 0.18));
}
.etp-character[data-etp-scale="hero"]      .etp-char-ear-module-pad,
.etp-character[data-etp-scale="classroom"] .etp-char-ear-module-pad {
  filter: drop-shadow(0 0 0.4px rgba(255, 180, 200, 0.24))
          drop-shadow(0 0 0.4px rgba(180, 220, 255, 0.24));
}

/* --------------------------------------------------------------------
 * T4.18 — Vent slits on temple panels (cooling-vent signature)
 * 3 short parallel lines per temple. Premium-robot machined detail.
 * -------------------------------------------------------------------- */
.etp-char-vent-slit {
  stroke: rgba(140, 148, 158, 0.42);
  stroke-width: 0.28;
  stroke-linecap: round;
}
.etp-character[data-etp-scale="convo"]  .etp-char-vent-slit { opacity: 0.4; }
.etp-character[data-etp-scale="chrome"] .etp-char-vent-slit { display: none; }

/* --------------------------------------------------------------------
 * T4.19 — Warm-cream body undertone (very subtle)
 * Adds a 1% warm-cream gradient stop to the body so pure flat white
 * reads as "warm porcelain" rather than "blank plastic". The change
 * is barely perceptible — operator can adjust by raising the stop
 * value if a stronger warm bias is desired.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-scale="hero"]      .etp-char-body-grad-mid,
.etp-character[data-etp-scale="classroom"] .etp-char-body-grad-mid {
  stop-color: #FFFCF7;
}

/* --------------------------------------------------------------------
 * T4.20 — Sub-pixel chin shadow (where chin meets neck)
 * 0.4px soft shadow defining the chin-to-neck transition. Sells "the
 * head sits on something solid" — premium 3D-lit object look.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-scale="hero"]      .etp-char-chin-separator,
.etp-character[data-etp-scale="classroom"] .etp-char-chin-separator {
  filter: drop-shadow(0 0.4px 0 rgba(140, 148, 158, 0.32));
}

/* --------------------------------------------------------------------
 * T5.21 — Floating data glyphs (hero scale, atmospheric AI signature)
 * 4 single-character glyphs drifting in slow random orbits at low
 * opacity. Visible only at hero scale.
 * -------------------------------------------------------------------- */
.etp-char-data-glyph {
  fill: var(--etp-light, #00DCEC);
  font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
  font-size: 3px;
  font-weight: 500;
  opacity: 0;
  filter: drop-shadow(0 0 0.3px var(--etp-light, #00DCEC));
  pointer-events: none;
}
@keyframes etp-glyph-drift-1 {
  0%, 100% { transform: translate(0, 0)        rotate(0deg);  opacity: 0.08; }
  50%      { transform: translate(2px, -1px)   rotate(8deg);  opacity: 0.22; }
}
@keyframes etp-glyph-drift-2 {
  0%, 100% { transform: translate(0, 0)        rotate(0deg);  opacity: 0.06; }
  50%      { transform: translate(-1.5px, 2px) rotate(-6deg); opacity: 0.2; }
}
@keyframes etp-glyph-drift-3 {
  0%, 100% { transform: translate(0, 0)        rotate(0deg);  opacity: 0.07; }
  50%      { transform: translate(1.5px, 1.5px) rotate(5deg); opacity: 0.18; }
}
@keyframes etp-glyph-drift-4 {
  0%, 100% { transform: translate(0, 0)        rotate(0deg);  opacity: 0.08; }
  50%      { transform: translate(-2px, -1.5px) rotate(-9deg); opacity: 0.2; }
}
.etp-character[data-etp-scale="hero"] .etp-char-data-glyph--1 {
  animation: etp-glyph-drift-1 8.4s ease-in-out infinite;
  transform-origin: center;
  transform-box: fill-box;
}
.etp-character[data-etp-scale="hero"] .etp-char-data-glyph--2 {
  animation: etp-glyph-drift-2 9.6s ease-in-out infinite 0.7s;
  transform-origin: center;
  transform-box: fill-box;
}
.etp-character[data-etp-scale="hero"] .etp-char-data-glyph--3 {
  animation: etp-glyph-drift-3 7.8s ease-in-out infinite 1.4s;
  transform-origin: center;
  transform-box: fill-box;
}
.etp-character[data-etp-scale="hero"] .etp-char-data-glyph--4 {
  animation: etp-glyph-drift-4 8.8s ease-in-out infinite 2.1s;
  transform-origin: center;
  transform-box: fill-box;
}
.etp-character[data-etp-scale="classroom"] .etp-char-data-glyph,
.etp-character[data-etp-scale="convo"]     .etp-char-data-glyph,
.etp-character[data-etp-scale="chrome"]    .etp-char-data-glyph {
  display: none;
}

/* --------------------------------------------------------------------
 * T5.22 — Holographic UI ring above the head
 * Thin cyan dashed circle at the helmet's crown, slowly rotating.
 * Reads as "status crown" / processing indicator. Hero scale only.
 * -------------------------------------------------------------------- */
.etp-char-holo-ring {
  fill: none;
  stroke: var(--etp-light, #00DCEC);
  stroke-width: 0.22;
  stroke-dasharray: 1.4 1.2;
  opacity: 0.45;
  filter: drop-shadow(0 0 0.4px var(--etp-light, #00DCEC));
  transform-origin: 50px 12px;
  transform-box: fill-box;
}
.etp-character[data-etp-scale="hero"] .etp-char-holo-ring {
  animation: etp-ring-spin 16s linear infinite;
}
@keyframes etp-ring-spin {
  from { transform: rotate(0deg); transform-origin: 50px 12px; }
  to   { transform: rotate(360deg); transform-origin: 50px 12px; }
}
.etp-character[data-etp-state="thinking"] .etp-char-holo-ring {
  opacity: 0.75;
}
.etp-character[data-etp-scale="classroom"] .etp-char-holo-ring { opacity: 0.32; }
.etp-character[data-etp-scale="convo"]  .etp-char-holo-ring  { display: none; }
.etp-character[data-etp-scale="chrome"] .etp-char-holo-ring  { display: none; }

/* --------------------------------------------------------------------
 * T5.23 — Projected interface plane in front of the chest
 * Hidden by default; toggles visible when host code sets
 * data-etp-tool-active="<tool-name>" on the character element.
 * Sells "AI tool projection" instead of "chrome panel".
 * -------------------------------------------------------------------- */
.etp-char-tool-plane {
  fill: rgba(0, 220, 236, 0.06);
  stroke: var(--etp-light, #00DCEC);
  stroke-width: 0.22;
  stroke-dasharray: 0.8 0.6;
  opacity: 0;
  filter: drop-shadow(0 0 0.6px var(--etp-light, #00DCEC));
  pointer-events: none;
  transition: opacity 380ms ease-out;
}
.etp-character[data-etp-tool-active]:not([data-etp-tool-active=""]) .etp-char-tool-plane {
  opacity: 0.68;
}
.etp-character[data-etp-scale="convo"]  .etp-char-tool-plane,
.etp-character[data-etp-scale="chrome"] .etp-char-tool-plane {
  display: none;
}

/* --------------------------------------------------------------------
 * T5.24 — Wireframe accessibility / dev mode
 * data-etp-theme="wireframe" reveals the underlying mechanical
 * structure with all fills removed and strokes lifted. Developer +
 * curious-student delight; off by default.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-theme="wireframe"] .etp-char-svg * {
  fill: none !important;
  stroke: var(--etp-light, #00DCEC) !important;
  stroke-width: 0.3 !important;
  opacity: 0.55 !important;
  animation: none !important;
}
.etp-character[data-etp-theme="wireframe"] .etp-char-svg text {
  fill: var(--etp-light, #00DCEC) !important;
  stroke: none !important;
}

/* --------------------------------------------------------------------
 * T5.25 — Lower-body hint (faint vertical line below bust crop)
 * Implies "full body exists beyond the frame". Hero scale only.
 * -------------------------------------------------------------------- */
.etp-char-lower-hint {
  stroke: rgba(140, 148, 158, 0.32);
  stroke-width: 0.22;
  stroke-dasharray: 0.6 0.8;
  pointer-events: none;
  opacity: 0;
}
.etp-character[data-etp-scale="hero"] .etp-char-lower-hint {
  opacity: 0.42;
}

/* --------------------------------------------------------------------
 * T6.26 — Cyan AA micro-glow on every cyan brand-accent edge
 * Subtle 0.3px outer glow on all cyan elements (eyes, chest, seam,
 * crown LEDs, lips, ear meter). Pushes whole avatar to feel
 * "luminous" not "drawn". Hero + classroom only.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-scale="hero"]      .etp-char-eye-iris,
.etp-character[data-etp-scale="classroom"] .etp-char-eye-iris {
  filter: drop-shadow(0 0 0.4px var(--etp-light, #00DCEC));
}
.etp-character[data-etp-scale="hero"]      .etp-char-mouth-upper-lip,
.etp-character[data-etp-scale="hero"]      .etp-char-mouth-lower-lip {
  /* Combined with T4.17 iridescent micro — preserves both effects */
  filter:
    drop-shadow(0 0 0.3px rgba(255, 180, 200, 0.18))
    drop-shadow(0 0 0.3px rgba(150, 200, 255, 0.18))
    drop-shadow(0 0 0.3px var(--etp-mouth, rgba(0, 220, 236, 0.92)));
}

/* --------------------------------------------------------------------
 * T6.27 — Idle micro-tilt drift (head sways ±0.3° on 18s sinusoid)
 * Different period from Phase 11 parallax so they don't re-sync. Pauses
 * during thinking/celebrating where Phase 15 head-tilt drives the pose.
 * -------------------------------------------------------------------- */
@keyframes etp-idle-tilt-drift {
  0%, 100% { transform: rotate(-0.3deg); }
  50%      { transform: rotate( 0.3deg); }
}
.etp-character[data-etp-scale="hero"] .etp-char-svg {
  animation: etp-idle-tilt-drift 18s ease-in-out infinite;
}
.etp-character[data-etp-scale="hero"][data-etp-state="thinking"]    .etp-char-svg,
.etp-character[data-etp-scale="hero"][data-etp-state="celebrating"] .etp-char-svg,
.etp-character[data-etp-scale="hero"][data-etp-state="encouraging"] .etp-char-svg {
  animation: none;
}
@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-scale="hero"] .etp-char-svg { animation: none; }
}

/* --------------------------------------------------------------------
 * T6.28 — Programme-tinted floor reflection
 * Phase 13 floor reflection arc picks up the active programme's tint.
 * Visual coherence between body and environment.
 * -------------------------------------------------------------------- */
.etp-char-floor-reflection {
  stroke: var(--etp-pulse-tint, var(--etp-light, #00DCEC)) !important;
}

/* --------------------------------------------------------------------
 * T6.29 — Per-state cranial seam colour modulation
 * The cyan seam slightly hue-shifts per state. Tiny but adds emotional
 * bandwidth. Combines with Phase 15 seam thinking-pulse (which only
 * runs during thinking — so a static colour-shift during other states
 * doesn't conflict).
 * -------------------------------------------------------------------- */
.etp-character[data-etp-state="encouraging"] .etp-char-cranial-seam {
  stroke: rgba(120, 240, 240, 0.78);  /* warmer cyan */
}
.etp-character[data-etp-state="alert"] .etp-char-cranial-seam {
  stroke: rgba(0, 200, 240, 0.95);  /* cooler / brighter cyan */
}
.etp-character[data-etp-state="celebrating"] .etp-char-cranial-seam {
  stroke: rgba(160, 255, 240, 0.92);  /* mint */
}
.etp-character[data-etp-state="thinking"] .etp-char-cranial-seam {
  stroke: rgba(40, 220, 255, 0.86);  /* sharper cyan */
}

/* --------------------------------------------------------------------
 * T6.30 — Tab visibility rest pose
 * When tab is hidden, ET+ enters a gentle "rest" state: head tilts 3°
 * forward, aperture closes 20%, chest pulse slows. On return, normal
 * transitions resume.
 * -------------------------------------------------------------------- */
.etp-character[data-etp-resting="1"] .etp-char-svg {
  transform: rotate(3deg);
  transform-origin: 50% 75%;
  transition: transform 1200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.etp-character[data-etp-resting="1"] .etp-char-eye-iris {
  transform: scaleY(0.78);
  transform-origin: center;
  transition: transform 1200ms ease-out;
}
.etp-character[data-etp-resting="1"] .etp-char-chest-core,
.etp-character[data-etp-resting="1"] .etp-char-chest-core-glow {
  animation-duration: calc(var(--etp-pulse-period, 1.6s) * 1.8) !important;
}
.etp-character[data-etp-resting="1"] .etp-char-eye-warm-spark {
  opacity: 0.2 !important;
}

/* =====================================================================
 * Phase 18 mouth contract — explicitly preserved through Phase 19.
 *
 * Phase 18 declared (above this section):
 *   --etp-mouth: rgba(0, 220, 236, 0.92)
 *   .etp-char-mouth-cavity fill: rgba(200, 230, 240, 0.55) !important
 *
 * Phase 19 does NOT touch either declaration. The cyan-mouth +
 * light-cyan-grey cavity contract continues to hold. WCAG-AAA
 * accessibility override from Phase 18 also flows through unchanged.
 * ===================================================================== */


/* =====================================================================
 * PHASE 20 — Five perception-tells on top of the Phase 13-19 baseline.
 *            (2026-05-27i, on top of Phase 19 Tier 1-6 megabatch)
 *
 * After 19 phases the avatar has 30+ active sci-fi tells, a rich
 * material story, and full per-state/per-programme/per-scale
 * behaviour. Phase 20 adds five specific perception lifts the
 * design audit flagged as the next-highest impact-÷-effort wins:
 *
 *   20.A  Chromatic aberration on iris edge — anamorphic red/blue
 *         fringe. Sells "real optical instrument, not a graphic."
 *   20.B  4-state respiration model — calm / excited / tense /
 *         listening. Chest pulse rate cascades per state.
 *   20.C  Phoneme-class lip visemes — 4 mouth shapes mapped to
 *         streamed character classes. Real talking-head feel.
 *   20.D  Tool-specific projection plane icons — 5 variants per
 *         data-etp-tool-active value (rubric/code/whiteboard/
 *         search/lesson). Reads as "specific cognitive activity."
 *   20.E  EyeSight outward gaze (alpha, opt-in) — webcam-driven
 *         pupil tracking. Zero data leaves device.
 *
 * Sacred invariants preserved:
 *   - Phase 18 --etp-mouth cyan + cavity light-cyan-grey + WCAG-AAA
 *   - Canonical smile path M 41 47 Q 50 53.2 59 47 (lips ride on
 *     top via Phase 17; Phase 20 visemes transform without
 *     touching geometry)
 *   - Phase 16 white body + 4 identity tokens
 *   - Phase 17 lips + structural panels
 *   - Phase 19's 30 items continue to function
 * ===================================================================== */


/* ---------------------------------------------------------------------
 * 20.A  Chromatic aberration on iris edge.
 *
 *       Two ultra-thin concentric ellipses per iris, drawn just
 *       inside the existing iris rim. One offset -0.4px in x with
 *       a soft red stroke, one offset +0.4px with a soft blue
 *       stroke. mix-blend-mode: screen so they brighten over the
 *       iris instead of darkening. 5% opacity each — perceptible
 *       only at hero/classroom scale.
 *
 *       Doesn't touch any fill on .etp-char-eye-iris itself, so
 *       Phase 11 mouse parallax + Phase 15 iris hue shift + Phase
 *       14 host-light tracking + thinking-state look-direction
 *       all continue to behave identically.
 * ------------------------------------------------------------------- */

.etp-char-iris-chromatic {
  fill: none;
  stroke-width: 0.18;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 320ms ease-out;
}

.etp-character[data-etp-scale="hero"] .etp-char-iris-chromatic,
.etp-character[data-etp-scale="classroom"] .etp-char-iris-chromatic {
  opacity: 0.55;
}

.etp-char-iris-chromatic--red {
  stroke: rgba(255, 45, 85, 0.78);
}

.etp-char-iris-chromatic--blue {
  stroke: rgba(45, 124, 255, 0.78);
}

/* Anamorphic-lens detail brightens slightly when the host page is
   light-themed (Phase 11 Tier 2.10 host-light variable). Subtle —
   stays well below the iris colour. */
.etp-character[style*="--etp-host-light"] .etp-char-iris-chromatic {
  opacity: calc(0.55 + (var(--etp-host-light, 0.5) - 0.5) * 0.2);
}

/* Reduced-motion users get the fringe but without the fade-in
   transition (no animation, just static state). */
@media (prefers-reduced-motion: reduce) {
  .etp-char-iris-chromatic {
    transition: none;
  }
}


/* ---------------------------------------------------------------------
 * 20.B  4-state respiration model.
 *
 *       Phase 10 ships --etp-pulse-period for the chest beacon.
 *       Phase 11 setPulsePeriod() varies it on stream events.
 *       Phase 20 declares the canonical 4-state model the JS
 *       _phase20Respiration hook reads:
 *
 *         calm       — baseline (no change, Phase 11 default)
 *         excited    — 1.3× faster (during streaming)
 *         tense      — 1.5× faster + shallower amplitude (alert)
 *         listening  — 0.7× slower (held breath, focused)
 *
 *       JS sets data-etp-respiration on the anchor; CSS does the
 *       rest. No conflict with Phase 11's runtime setPulsePeriod
 *       because Phase 20 only writes the attribute, not the var.
 *       Phase 11 calls still win their respective time windows.
 * ------------------------------------------------------------------- */

.etp-character[data-etp-respiration="excited"] .etp-char-chest-core,
.etp-character[data-etp-respiration="excited"] .etp-char-chest-core-glow,
.etp-character[data-etp-respiration="excited"] .etp-char-chest-core-ghost {
  animation-duration: 1.1s !important;
}

.etp-character[data-etp-respiration="tense"] .etp-char-chest-core,
.etp-character[data-etp-respiration="tense"] .etp-char-chest-core-glow,
.etp-character[data-etp-respiration="tense"] .etp-char-chest-core-ghost {
  animation-duration: 1.0s !important;
  filter: brightness(0.94);
}

.etp-character[data-etp-respiration="listening"] .etp-char-chest-core,
.etp-character[data-etp-respiration="listening"] .etp-char-chest-core-glow,
.etp-character[data-etp-respiration="listening"] .etp-char-chest-core-ghost {
  animation-duration: 2.2s !important;
}

/* Tiny shoulder rise/fall synced to respiration (hero only —
   sub-pixel sells "breathing organism" without distracting at
   smaller scales). Phase 17 ships .etp-char-shoulder-cap. */
@keyframes etp-phase20-shoulder-rise {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-0.35px); }
}

.etp-character[data-etp-scale="hero"]
  .etp-char-shoulder-cap {
  animation: etp-phase20-shoulder-rise var(--etp-pulse-period, 4.8s)
             ease-in-out infinite;
}

.etp-character[data-etp-scale="hero"][data-etp-respiration="excited"]
  .etp-char-shoulder-cap {
  animation-duration: 1.1s;
}

.etp-character[data-etp-scale="hero"][data-etp-respiration="tense"]
  .etp-char-shoulder-cap {
  animation-duration: 1.0s;
}

.etp-character[data-etp-scale="hero"][data-etp-respiration="listening"]
  .etp-char-shoulder-cap {
  animation-duration: 2.2s;
}

@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-scale="hero"] .etp-char-shoulder-cap {
    animation: none;
  }
}


/* ---------------------------------------------------------------------
 * 20.C  Phoneme-class lip visemes (character-class heuristic).
 *
 *       Phase 17 ships real upper + lower lips that translate
 *       slightly on data-etp-speaking="1". Phase 19 adds the
 *       4-bar speech-waveform under the mouth.
 *
 *       Phase 20.C layers a per-character-class viseme on top:
 *       JS hooks etpux:stream-chunk, classifies the last 3-5
 *       characters into one of four buckets, sets
 *       data-etp-viseme="closed|small|wide|round" on the anchor
 *       for ~110ms (the typical viseme dwell time at 8 phonemes/sec).
 *
 *       These rules layer ON TOP of Phase 17 speaking transforms,
 *       composing because both rules target the same lip elements
 *       but at different attribute selectors. Visemes win in
 *       cascade order. Cyan colour (Phase 18) preserved exactly.
 *
 *       Honest scope: without TTS phoneme metadata this is a
 *       character-class approximation. When Phase 10 voice mode
 *       wires real visemes the same JS hook can swap to phoneme
 *       data without CSS changes.
 * ------------------------------------------------------------------- */

/* Closed — default mouth at rest (no override needed). Provided
   for transition completeness; opacity stays 0 on cavity. */
.etp-character[data-etp-viseme="closed"] .etp-char-mouth-cavity {
  opacity: 0;
}

/* Small — narrow vowel-ish (e, i, u, schwa). Slight opening. */
.etp-character[data-etp-viseme="small"] .etp-char-mouth-upper-lip {
  transform: translateY(-0.3px);
}
.etp-character[data-etp-viseme="small"] .etp-char-mouth-lower-lip {
  transform: translateY(0.6px);
}
.etp-character[data-etp-viseme="small"] .etp-char-mouth-cavity {
  opacity: 0.85 !important;
  transform: scaleY(0.7);
}

/* Wide — open vowel (a). Maximum aperture, lips spread. */
.etp-character[data-etp-viseme="wide"] .etp-char-mouth-upper-lip {
  transform: translateY(-0.7px) scaleX(1.05);
}
.etp-character[data-etp-viseme="wide"] .etp-char-mouth-lower-lip {
  transform: translateY(1.6px) scaleX(1.05);
}
.etp-character[data-etp-viseme="wide"] .etp-char-mouth-cavity {
  opacity: 0.92 !important;
  transform: scale(1.18, 1.4);
}

/* Round — rounded vowel (o, oo, w). Tall + narrow opening. */
.etp-character[data-etp-viseme="round"] .etp-char-mouth-upper-lip {
  transform: translateY(-0.55px) scaleX(0.78);
}
.etp-character[data-etp-viseme="round"] .etp-char-mouth-lower-lip {
  transform: translateY(1.25px) scaleX(0.78);
}
.etp-character[data-etp-viseme="round"] .etp-char-mouth-cavity {
  opacity: 0.92 !important;
  transform: scale(0.75, 1.3);
}

/* All viseme transforms compose via the existing Phase 17 transition
   (80ms ease-out) which already applies to the lip elements. The
   cyan --etp-mouth fill + Phase 18 light-cyan-grey cavity fill are
   left untouched; only geometry deforms. */

/* Visemes are visual-only; chrome scale stays silhouette-only
   per Phase 17, so the chrome rule below the speech-bar selectors
   continues to hide visemes. */
.etp-character[data-etp-scale="chrome"] .etp-char-mouth-upper-lip,
.etp-character[data-etp-scale="chrome"] .etp-char-mouth-lower-lip,
.etp-character[data-etp-scale="chrome"] .etp-char-mouth-cavity {
  transform: none;
}

/* WCAG-AAA accessibility override: keep Phase 18's high-contrast
   colour band intact through viseme transforms. The transforms
   themselves are geometry-only, so the colour rule in Phase 18's
   [data-etp-theme="high-contrast-aaa"] block keeps winning. */

@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-viseme] .etp-char-mouth-upper-lip,
  .etp-character[data-etp-viseme] .etp-char-mouth-lower-lip,
  .etp-character[data-etp-viseme] .etp-char-mouth-cavity {
    transform: none !important;
  }
}


/* ---------------------------------------------------------------------
 * 20.D  Tool-specific projection plane icons.
 *
 *       Phase 19 T5.23 ships a generic rectangle for the
 *       projection plane that appears when host code sets
 *       data-etp-tool-active="<anything>". Phase 20.D layers 5
 *       optional icon paths inside the rectangle, each revealing
 *       only when the attribute value matches a known shape:
 *
 *         rubric     — clipboard outline + 3 horizontal rule lines
 *         code       — angle brackets bookending an em-dash
 *         whiteboard — 3×3 grid square
 *         search     — magnifying glass (circle + diagonal handle)
 *         lesson     — stacked-pages icon (3 small offset rectangles)
 *
 *       Unknown values still show the generic rectangle as before
 *       — Phase 20.D is purely additive.
 * ------------------------------------------------------------------- */

.etp-char-tool-plane-icon {
  fill: none;
  stroke: rgba(0, 220, 236, 0.85);
  stroke-width: 0.35;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
  display: none;
}

.etp-character[data-etp-tool-active="rubric"]
  .etp-char-tool-plane-icon--rubric { display: block; }

.etp-character[data-etp-tool-active="code"]
  .etp-char-tool-plane-icon--code { display: block; }

.etp-character[data-etp-tool-active="whiteboard"]
  .etp-char-tool-plane-icon--whiteboard { display: block; }

.etp-character[data-etp-tool-active="search"]
  .etp-char-tool-plane-icon--search { display: block; }

.etp-character[data-etp-tool-active="lesson"]
  .etp-char-tool-plane-icon--lesson { display: block; }

/* Icons inherit the tool-plane's per-scale visibility from Phase
   19 — they only render if the parent plane is visible. */


/* ---------------------------------------------------------------------
 * 20.E  EyeSight outward gaze (alpha, opt-in).
 *
 *       JS hook _phase20EyeSight reads localStorage to check the
 *       opt-in flag (etp_eyesight_enabled === '1'). If on, requests
 *       getUserMedia, runs a 16-fps bright-spot tracker inside a
 *       tiny 64×48 canvas, and sets two CSS vars on every character
 *       anchor:
 *
 *         --etp-eyesight-dx   (±0.6px)
 *         --etp-eyesight-dy   (±0.4px)
 *
 *       The CSS below translates the pupils group by those values,
 *       gated on data-etp-eyesight="1" so it only fires when the
 *       JS hook is actively producing data. When EyeSight is off,
 *       Phase 11 mouse parallax + Phase 15 state-driven look-direction
 *       work exactly as before.
 *
 *       Privacy: zero data leaves the device — frames are read into
 *       a local canvas, classified, and discarded immediately. The
 *       opt-in surface in settings.html documents this clearly.
 *
 *       Honest scope: this is an "alpha" — the bright-spot tracker
 *       is a stand-in for real face landmarks (TF.js mediapipe would
 *       be the production-grade implementation). Works best in
 *       even, well-lit conditions.
 * ------------------------------------------------------------------- */

.etp-character[data-etp-eyesight="1"] .etp-char-pupils {
  transform: translate(
    var(--etp-eyesight-dx, 0px),
    var(--etp-eyesight-dy, 0px)
  ) !important;
  transition: transform 90ms linear !important;
}

/* When EyeSight is active, the per-state thinking pupil rule
   (line 726-729) doesn't fire — clear signal to the user that
   their gaze is being followed. Other states with no pupil
   transform continue to work normally. */

@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-eyesight="1"] .etp-char-pupils {
    transition: none !important;
  }
}


/* =====================================================================
 * Phase 18 mouth contract — re-asserted at end of Phase 20.
 *
 * Phase 20 visemes (20.C) transform .etp-char-mouth-upper-lip /
 * -lower-lip / -cavity geometry but never touch fill, stroke, or
 * opacity colour. The cyan brand accent rgba(0, 220, 236, 0.92) +
 * light-cyan-grey cavity rgba(200, 230, 240, 0.55) !important +
 * WCAG-AAA [data-etp-theme="high-contrast-aaa"] override all
 * continue to win the cascade. Canonical smile path stays in DOM
 * as the silhouette anchor; visemes are deforming the OVERLAY lips
 * Phase 17 added on top.
 * ===================================================================== */


/* =====================================================================
 * PHASE 21 — Chest closeup refinements (2026-05-27j, on top of Phase 20)
 *
 * Per user reference screenshot of the chest area, four scoped edits:
 *
 *   21.1  Remove the white square framing the chest core
 *         (.etp-char-chest-outer + .etp-char-chest-inner layered
 *         armour panels — they composite into a rounded light-grey
 *         square framing the disc).
 *   21.2  Move the cyan chest disc upward 6 SVG units so it occupies
 *         the cleanly freed space and sits closer to the face/eye line.
 *         Affects every sibling element that visually composes the
 *         chest beacon (ghost / glow / core / rim / crosshair / brand
 *         text + scan band + Phase 19 hex facets). Animation keyframes
 *         that already include a transform are re-declared to compose
 *         the new translateY without losing the animated property.
 *   21.3  Enlarge the ET+ wordmark by exactly 20% (5.2px → 6.24px)
 *         and lift weight 800 → 900 for a stronger bold read.
 *   21.4  Remove the two charcoal shoulder-joint seam bands and the
 *         two cyan shoulder LED dots that sit on them (the "black
 *         areas with blue dots" in the user's reference screenshot).
 *         The white shoulder caps themselves stay — they're the
 *         humanoid silhouette and are not part of what was flagged.
 *
 * Sacred invariants preserved (Phase 21 is purely additive on top):
 *   - Phase 18 --etp-mouth cyan + cavity light-cyan-grey + WCAG-AAA
 *   - Canonical smile path M 41 47 Q 50 53.2 59 47 (untouched)
 *   - Phase 16 white body + 4 identity tokens
 *   - Phase 17 lips + structural panels
 *   - Phase 19 30 items + Phase 20 5 perception-tells
 * ===================================================================== */


/* ---------------------------------------------------------------------
 * 21.1  Remove the white square framing the chest core.
 *
 *       The chest-outer (raised armour frame) + chest-inner (recessed
 *       inner panel) together render the rounded light-grey square the
 *       reference screenshot shows behind the disc. Both layers off.
 *       The chest-body-bevel that backs them stays — it's a subtle
 *       panel-shadow shape that doesn't render as a "square" on its
 *       own and contributes to torso depth.
 * ------------------------------------------------------------------- */

.etp-char-chest-outer,
.etp-char-chest-inner {
  display: none !important;
}


/* ---------------------------------------------------------------------
 * 21.2  Cyan chest disc moved up 6 SVG units.
 *
 *       Every chest-beacon-composing element gets the same translateY
 *       so the disc, ET+ wordmark, registration crosshair, and Phase 19
 *       hex facets all migrate together. Phase 12 Tier 2.x decorations
 *       (scan-cycle sweep, per-letter pulse) and Phase 11 lens-flare
 *       cross are handled per-element below depending on whether they
 *       already animate transform.
 * ------------------------------------------------------------------- */

.etp-char-chest-core,
.etp-char-chest-core-ghost,
.etp-char-chest-core-rim,
.etp-char-chest-core-crosshair,
.etp-char-chest-brand {
  transform: translateY(-6px);
}

/* chest-core-cross is positioned at the legacy y=108 (pre-Phase-12
   pivot) so a simple translateY(-6) would still leave it visually
   disjoint from the moved disc. Hide it as part of the chest-core
   simplification kicked off in Phase 12 Tier 0. */
.etp-char-chest-core-cross {
  display: none !important;
}

/* chest-core-glow has an existing pulse keyframe that animates
   transform: scale(). Override the keyframe (same name) to compose
   the translateY with the scale at every step. */
@keyframes etp-chest-core-pulse {
  0%, 100% { opacity: 0.38; transform: translateY(-6px) scale(1.000); }
  50%      { opacity: 0.62; transform: translateY(-6px) scale(1.04);  }
}

/* chest-facets has a 28s rotation keyframe with transform-origin:
   50px 103px. Shift origin to (50px 97px) (the new disc centre) and
   add the translateY so the hex stays centred on the moved disc as
   it rotates. */
@keyframes etp-hex-spin {
  from { transform: translateY(-6px) rotate(0deg);  transform-origin: 50px 97px; }
  to   { transform: translateY(-6px) rotate(60deg); transform-origin: 50px 97px; }
}

/* chest-brand-scan has a 20s scan-cycle keyframe with built-in
   translateY values that sweep through the ET+ text. Subtract 6 from
   each step so the sweep still works but over the moved disc. */
@keyframes etp-chest-brand-scan-cycle {
  0%   { transform: translateY(-8.6px); opacity: 0; }
  4%   { transform: translateY(-8.6px); opacity: 0.7; }
  8%   { transform: translateY(-6.0px); opacity: 0.85; }
  12%  { transform: translateY(-3.4px); opacity: 0.7; }
  16%  { transform: translateY(-3.4px); opacity: 0; }
  100% { transform: translateY(-3.4px); opacity: 0; }
}


/* ---------------------------------------------------------------------
 * 21.3  ET+ wordmark — 20% bigger + stronger bold.
 *
 *       Phase 12 baseline: font-size 5.2px, font-weight 800. Phase 21:
 *       6.24px (×1.2 exactly) and weight 900. paint-order stroke fill,
 *       letter-spacing -0.05em, JetBrains Mono family, white fill,
 *       charcoal hairline stroke, emissive cyan drop-shadow — all
 *       inherited from Phase 12 unchanged. Per-letter pulse filter
 *       animation continues to work because it targets `filter`, not
 *       transform.
 * ------------------------------------------------------------------- */

.etp-char-chest-brand {
  font-size: 6.24px;   /* 5.2 × 1.2 — exactly 20% larger */
  font-weight: 900;    /* lifted from Phase 12's 800 for stronger bold read */
}


/* ---------------------------------------------------------------------
 * 21.4  Remove the two charcoal shoulder-joint seams + cyan LED dots.
 *
 *       The "two black areas with blue dots" in the user reference are
 *       (a) the charcoal shoulder-joint rectangles seating the white
 *       shoulder caps onto the chest, and (b) the small cyan shoulder
 *       LED dots that rode on top of them as part of the Phase 10
 *       "triplet rhythm" identity (chest + crown + shoulder triplets).
 *
 *       Hiding both keeps the white shoulder caps visible and clean
 *       (humanoid silhouette intact), but removes the dark seam and
 *       LED that the user flagged.
 * ------------------------------------------------------------------- */

.etp-char-shoulder-joint,
.etp-char-shoulder-led {
  display: none !important;
}


/* =====================================================================
 * Phase 18 cyan-mouth contract — re-asserted at end of Phase 21.
 *
 * Phase 21 touches ONLY chest + shoulder elements. Zero edits to
 * mouth fills, mouth geometry, or accessibility band. The Phase 18
 * cyan brand accent rgba(0, 220, 236, 0.92) + cavity light-cyan-grey
 * rgba(200, 230, 240, 0.55) !important + WCAG-AAA override all flow
 * through Phase 21 unchanged. Canonical smile path stays in DOM as
 * the silhouette anchor; Phase 17 lips + Phase 20.C visemes ride on
 * top exactly as before.
 * ===================================================================== */

/* =====================================================================
 * PHASE 22 — Chest aligned to shoulders (re-ship 2026-05-27l, on top of Phase 21)
 *
 * Per the operator's second chest-closeup screenshot of the post-revert
 * live state plus the verbatim "Do not change anything beyond this scope"
 * rule. Phase 22 re-ships PR #391's alignment work (which was reverted
 * by PR #393 earlier today) and adds one new sub-edit 22.5:
 *
 *   22.1  Add `.etp-char-chest-core-glow` to the chest-beacon translateY
 *         rule. Phase 21.2 omitted this selector — the outer dark glow
 *         ring stayed at cy=108 while the inner core / ghost / rim /
 *         crosshair / brand all moved up 6 SVG units. Phase 22.1 closes
 *         that gap.
 *   22.2  translateY −6px → −18px. Geometry: SVG source-of-truth
 *         chest-core cy=108; shoulder-cap cy=90; required translateY =
 *         90 − 108 = −18. All six chest-beacon elements (glow, core,
 *         ghost, rim, crosshair, brand text) now sit at visual cy=90,
 *         perfectly between the two white shoulder caps.
 *   22.3  Update the three composed keyframes from -6 baseline to -18
 *         baseline (etp-chest-core-pulse, etp-hex-spin with
 *         transform-origin 50px 97px → 50px 90px,
 *         etp-chest-brand-scan-cycle with each step shifted by an
 *         additional −12 — Phase 21 visited -8.6/-6/-3.4; Phase 22
 *         visits -20.6/-18/-15.4 — same internal ±2.6 sweep, new
 *         anchor).
 *   22.4  Override the streaming-state chest-core-glow transform with
 *         the composed `translateY(-18px) scale(1.08)` so the disc
 *         stays anchored at cy=90 during chat-token flow while still
 *         pulse-growing. CSS transforms don't compose across separate
 *         selectors — the most-specific rule wins entirely. Without
 *         this override the glow would jump back down 18 units every
 *         time a chat token streamed in.
 *   22.5  ET+ wordmark font-size 6.24px → 7.488px (cumulative +20% on
 *         Phase 21.3's +20%). Operator-confirmed via AskUserQuestion.
 *         Final size is exactly 5.2 × 1.44 = 7.488px (cumulative 44%
 *         over Phase 12's 5.2 baseline). font-weight 900 already in
 *         effect from Phase 21.3 — not re-declared. Phase 12's +2.4
 *         optical-glyph compensation (text y=110.4 vs disc cy=108)
 *         remains proportional and continues to centre correctly.
 *
 * Phase 21 sub-edits 21.1 (white square hidden), 21.3 (ET+ wordmark
 * 6.24px / 900), 21.4 (shoulder-joint + LED hidden) all preserved —
 * Phase 22 only modifies 21.2's translateY value (−6 → −18) + adds
 * one missing selector (chest-core-glow) to that same rule + adds
 * one new font-size override on top of 21.3's existing block.
 * ===================================================================== */

/* 22.1 + 22.2 — chest-beacon translateY rule updated. Adds
   .etp-char-chest-core-glow to the selector list AND swaps -6 → -18.
   Cascades over the Phase 21.2 declaration at line 5576. */
.etp-char-chest-core,
.etp-char-chest-core-ghost,
.etp-char-chest-core-rim,
.etp-char-chest-core-glow,
.etp-char-chest-core-crosshair,
.etp-char-chest-brand {
  transform: translateY(-18px);
}

/* 22.3 — keyframe re-declaration (same names): chest-core pulse.
   Phase 21's at line 5595-5598 is left in the file; same-name
   re-declaration here wins the cascade and composes translateY(-18px)
   with the existing scale animation.
   Phase 26.J — scale amplitude now reads from --etp-pulse-amp-min /
   --etp-pulse-amp-max so per-state cascades (defined in the appended
   Phase 26 block below) can vary the depth of the breath. Defaults
   (1.000 / 1.04) reproduce Phase 22's original amplitude exactly. */
@keyframes etp-chest-core-pulse {
  0%, 100% { opacity: 0.38; transform: translateY(-18px) scale(var(--etp-pulse-amp-min, 1.000)); }
  50%      { opacity: 0.62; transform: translateY(-18px) scale(var(--etp-pulse-amp-max, 1.04));  }
}

/* 22.3 — hex spin keyframe re-declared. transform-origin shifts
   50px 97px → 50px 90px (the new visual disc centre at cy=90). */
@keyframes etp-hex-spin {
  from { transform: translateY(-18px) rotate(0deg);  transform-origin: 50px 90px; }
  to   { transform: translateY(-18px) rotate(60deg); transform-origin: 50px 90px; }
}

/* 22.3 — brand scan cycle keyframe re-declared. Phase 21 visited
   -8.6 / -6 / -3.4 (centre at -6). Phase 22 visits -20.6 / -18 /
   -15.4 (centre at -18). Same internal ±2.6 sweep; new anchor. */
@keyframes etp-chest-brand-scan-cycle {
  0%, 24%   { opacity: 0; transform: translateY(-20.6px); }
  28%       { opacity: 0.55; transform: translateY(-18px); }
  32%       { opacity: 0.65; transform: translateY(-15.4px); }
  36%       { opacity: 0.55; transform: translateY(-18px); }
  40%, 100% { opacity: 0; transform: translateY(-20.6px); }
}

/* 22.4 — streaming-state composition override. Existing state rule
   on .etp-character.etp-char-streaming .etp-char-chest-core-glow sets
   transform: scale(1.08) directly; without this re-declaration that
   wipes Phase 22's translateY every ~240ms during chat-token flow.
   Composed transform keeps the disc anchored at cy=90 while still
   pulse-growing on streaming. Phase 21 didn't hit this hazard
   because chest-core-glow wasn't in its translateY rule. */
.etp-character.etp-char-streaming .etp-char-chest-core-glow {
  transform: translateY(-18px) scale(1.08);
}

/* 22.5 — ET+ wordmark cumulative +20% on top of Phase 21.3's +20%.
   Phase 12 baseline: 5.2px. Phase 21.3: 6.24px (×1.2). Phase 22.5:
   7.488px (×1.2 again — cumulative 44% over Phase 12 baseline).
   font-weight 900 stays from Phase 21.3 — not re-declared. */
.etp-char-chest-brand {
  font-size: 7.488px;
}

/* =====================================================================
 * PHASE 23 — Remove crown chrome (2026-05-27m, on top of Phase 22)
 *
 * Operator screenshot of the post-Phase-22-re-ship live state showing
 * three elements clustered around the top of the head: two small blue
 * dots flanking a central blue circle. Per the verbatim rule "Do not
 * change anything else beyond the scope above. Preserve all other
 * aspects of the character exactly as they are." Phase 23 hides the
 * three crown-area chrome layers that produce those marks, leaving
 * the head silhouette + eyes + ears + face panel + chest + mouth all
 * intact.
 *
 *   23.1  Hide the holographic UI ring (Phase 19 T5.22 — dashed cyan
 *         circle floating above the crown at cx=50 cy=12 r=3.4 with
 *         16s rotation). This is the "blue circle" in the operator
 *         screenshot.
 *
 *   23.2  Hide the crown 3-LED status column (Phase 10 signature C —
 *         vertical .etp-char-crown-leds group with rail + 3 cyan
 *         circles at cx=50 cy=11.4/14/16.6, brightening top to bottom).
 *         These are the "2 blue dots" in the operator screenshot (the
 *         top + middle LEDs are most visible at hero scale; the bottom
 *         LED is dimmer but still part of the same triplet, so the
 *         entire group is hidden as one unit to preserve symmetry).
 *
 *   23.3  Hide the crown sensor disc + lens (Phase 19 T1.4 — flat
 *         oval at cx=50 cy=9.5 rx=4.6 ry=1.1 with inner highlight
 *         lens circle at r=0.55). Without 23.3, hiding 23.1+23.2
 *         would leave the sensor disc as a stray central element
 *         floating at the top of the helmet; removing it as part of
 *         the same crown cleanup keeps the head silhouette clean.
 *
 * Pure-CSS hide via display:none !important. The SVG geometry remains
 * in the DOM (untouched JS) so any future un-revert is a one-line CSS
 * deletion. All Phase 13 cranial-seam micro-rivets (4 hairline dots at
 * x=22/28/72/78 cy=36), Phase 17 humanoid panel lines, Phase 19 hex
 * facets, eyebrows, eye iris dots, and every other previously-shipped
 * surface element stay visible.
 *
 * SACRED INVARIANTS PRESERVED EXACTLY:
 *
 *   - Phase 18 cyan-mouth contract: --etp-mouth rgba(0, 220, 236, 0.92)
 *     untouched (mouth + cavity + WCAG-AAA all flow through).
 *   - Phase 17 lips + canonical smile path M 41 47 Q 50 53.2 59 47.
 *   - Phase 16 white body + 4 identity tokens.
 *   - Phase 19 30-item batch features (except T1.4 sensor disc +
 *     T5.22 holo ring, which are scoped-removed here per operator).
 *   - Phase 20 5 perception-tells.
 *   - Phase 21 sub-edits 21.1 (chest-outer hidden), 21.3 (ET+ wordmark
 *     base styling 6.24px / weight 900), 21.4 (shoulder-joint + LED
 *     hidden) — all preserved verbatim.
 *   - Phase 22 re-ship: chest beacon at visual cy=90, ET+ wordmark at
 *     7.488px, three composed keyframes with -18 baseline, streaming-
 *     state composition override.
 *
 * Revert path: delete this entire PHASE 23 block from the CSS file.
 * Crown LEDs + sensor disc + holo ring return automatically because
 * none of the underlying selectors or SVG elements were modified.
 * sw.js stamp + 16 HTML cache-busters roll back one letter (m → l).
 * No JS / HTML-body / SVG-content changes — pure CSS revert.
 * ===================================================================== */

/* 23.1 — Hide holographic UI ring above the head (Phase 19 T5.22). */
.etp-char-holo-ring {
  display: none !important;
}

/* 23.2 — Hide crown 3-LED status column + rail (Phase 10 signature C).
   Targets the entire .etp-char-crown-leds <g> group so the rail and
   all 3 LED circles disappear together. */
.etp-char-crown-leds,
.etp-char-crown-led-rail,
.etp-char-crown-led,
.etp-char-crown-led--top,
.etp-char-crown-led--mid,
.etp-char-crown-led--bot {
  display: none !important;
}

/* 23.3 — Hide crown sensor disc + lens (Phase 19 T1.4). Hides BOTH the
   outer flat oval and the inner highlight lens dot so the crown reads
   as a clean smooth surface without a central feature. */
.etp-char-sensor-disc,
.etp-char-sensor-disc__lens {
  display: none !important;
}

/* =============================================================
   PHASE 24 (2026-05-27n) — Tier 1 polish sweep
   -------------------------------------------------------------
   Four sub-edits elevating the avatar from "structurally
   complete" (Phase 23 end-state) to "production-grade
   character design":

   24.A  Wordmark legibility polish at 7.488px
         · CSS letter-spacing -0.05em → -0.06em (handled inline
           on the .etp-char-chest-brand rule earlier in this
           file, see §13.B).
         · Filter drop-shadow blur 1.2px → 1.6px (same rule).
         · Optical y-offset 105.6 → 106.0 (JS template;
           Phase 12's +2.6 compensation recalibrated for the
           Phase 22.5 enlarged glyphs).

   24.B  Bust silhouette closure (crisp option)
         · NEW .etp-char-bust-edge — single hairline arched
           path at y=124. Marks the bust's lower edge so the
           avatar reads as a deliberately-cropped credential
           photo rather than fading mid-figure. Hero opacity
           0.65, classroom 0.45, convo + chrome hidden.

   24.C  Neck refinement (articulated)
         · NEW .etp-char-neck-specular — vertical
           linearGradient overlay (top + bottom highlights,
           dim middle) on the existing neck rect. Reads as a
           real cylindrical surface. Hero/classroom only.
           Phase 10's 2 horizontal seam lines at y=67/70.5
           preserved unchanged.

   24.D  Per-programme chest tint refinement
         · --etp-chest-grad-mid blends via color-mix() so the
           chest disc retains its primary cyan identity with
           a subtler programme accent in the middle band.
   ============================================================= */

/* 24.B — Bust silhouette closure.
   Body-deep stroke at 0.6 width, opacity scale-gated for
   hero (0.65) + classroom (0.45). Pointer-none so it can
   never intercept a click. */
.etp-char-bust-edge {
  fill: none;
  stroke: var(--etp-body-deep, #6F9EBE);
  stroke-width: 0.6;
  stroke-linecap: round;
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms ease-out;
}

.etp-character[data-etp-scale="hero"] .etp-char-bust-edge {
  opacity: 0.65;
}

.etp-character[data-etp-scale="classroom"] .etp-char-bust-edge {
  opacity: 0.45;
}

/* Reduced motion: keep the line at the same opacity, just
   drop the transition. */
@media (prefers-reduced-motion: reduce) {
  .etp-char-bust-edge {
    transition: none;
  }
}

/* 24.C — Neck warm-grey inner specular.
   The overlay rect picks up the per-anchor linearGradient
   etpNeckSpecular_<id> injected by JS. Pointer-none so it
   never intercepts. Phase 10 segment seams + Phase 8 cyan
   trim ring stay drawn above this overlay because they're
   inserted later in the SVG render order. Hero + classroom
   only to keep convo and chrome simple. */
.etp-char-neck-specular {
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms ease-out;
}

.etp-character[data-etp-scale="hero"] .etp-char-neck-specular {
  opacity: 1;
}

.etp-character[data-etp-scale="classroom"] .etp-char-neck-specular {
  opacity: 0.75;
}

@media (prefers-reduced-motion: reduce) {
  .etp-char-neck-specular {
    transition: none;
  }
}

/* 24.D — Per-programme chest tint refinement.
   Phase 13 introduced --etp-chest-grad-mid + per-programme
   overrides (Robotics #00DCEC / STEAM #FFB07A / Expert
   #B89BFF / Math #6FAFFF / ACSP #6BD68C).
   Phase 24.D softens those tints via color-mix() so the
   chest beacon retains its primary cyan identity (65% cyan)
   while the programme accent (35%) registers as a subtle
   middle-band hue shift. Backwards-compatible: callers that
   don't override --etp-pulse-tint inherit the canonical
   cyan #00DCEC and the blend resolves to pure cyan.
   --etp-pulse-tint per-programme cascade (Phase 13.E.2) is
   preserved untouched and continues to drive face panel rim
   glow + cranial seam tint + ear-meter accent + every other
   programme-identity surface. */
.etp-character {
  --etp-chest-grad-mid: color-mix(
    in srgb,
    var(--etp-pulse-tint, #00DCEC) 35%,
    #00DCEC 65%
  );
}

/* Per-programme overrides: 24.D blend replaces the full-saturation
   programme tints from Phase 13.E.2. The four programmes that
   declared --etp-chest-grad-mid (STEAM/Expert/Math/ACSP) get their
   declarations re-aimed at the same color-mix recipe; Robotics
   already inherits cyan via the default. */
.etp-character[data-etp-programme="ai-steam-plus"],
.etp-character[data-etp-programme="ai-expert-plus"],
.etp-character[data-etp-programme="ai-math-plus"],
.etp-character[data-etp-programme="ai-computer-science-plus"] {
  --etp-chest-grad-mid: color-mix(
    in srgb,
    var(--etp-pulse-tint, #00DCEC) 35%,
    #00DCEC 65%
  );
}

/* =============================================================
   PHASE 25 (2026-05-27o) — Tier 2 material + lighting depth
   -------------------------------------------------------------
   Four sub-edits adding real subsurface scattering, anisotropic
   specular on shoulder caps, an upgraded floor reflection,
   and per-state body-tint shifts on the helmet shell.

   25.E  Real SSS on white body (streaming-state-gated)
         · NEW <filter id="etp-body-sss"> per-anchor in <defs>
           (injected by _injectPhase25Defs). Applied to face
           panel + helmet shell + neck + shoulder caps when
           `.etp-char-streaming` is active. Reads as cyan
           sub-surface glow during chat-token flow.
   25.F  Anisotropic specular on shoulder caps (always-on)
         · 2 NEW per-anchor <linearGradient>s injected; JS
           wires them via inline fill="url(#…)" on each cap.
           CSS here is a placeholder hook plus a hero/classroom
           opacity floor — the gradients themselves carry the
           visual differentiation.
   25.G  Floor-reflection enhancement
         · Pure-CSS upgrade of the Phase 13.E.1 hairline arc:
           filter: blur(3px) + drop-shadow(programme glow),
           per-state opacity (composed 0.30 / thinking 0.55 /
           default 0.45 / celebrating 0.75).
   25.H  Per-state body-tint shifts on helmet shell
         · `.etp-char-helmet-shell` gets a state-driven
           filter chain (hue-rotate + saturate + brightness)
           with the existing 600ms ease transition.
   ============================================================= */

/* 25.E — Subsurface scattering activation.
   The filter is defined per-anchor in <defs> (id `etp-body-sss`).
   We only ACTIVATE it during streaming, so the avatar reads as
   normal porcelain at rest and lights up subtly when the AI is
   generating tokens. Hero + classroom only. */
.etp-character.etp-char-streaming[data-etp-scale="hero"] .etp-char-helmet-shell,
.etp-character.etp-char-streaming[data-etp-scale="hero"] .etp-char-neck,
.etp-character.etp-char-streaming[data-etp-scale="hero"] .etp-char-shoulder-cap,
.etp-character.etp-char-streaming[data-etp-scale="hero"] .etp-char-face-panel,
.etp-character.etp-char-streaming[data-etp-scale="classroom"] .etp-char-helmet-shell,
.etp-character.etp-char-streaming[data-etp-scale="classroom"] .etp-char-neck,
.etp-character.etp-char-streaming[data-etp-scale="classroom"] .etp-char-shoulder-cap,
.etp-character.etp-char-streaming[data-etp-scale="classroom"] .etp-char-face-panel {
  filter: url(#etp-body-sss);
  transition: filter 400ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .etp-character.etp-char-streaming .etp-char-helmet-shell,
  .etp-character.etp-char-streaming .etp-char-neck,
  .etp-character.etp-char-streaming .etp-char-shoulder-cap,
  .etp-character.etp-char-streaming .etp-char-face-panel {
    transition: none;
  }
}

/* 25.F — Anisotropic specular hook on shoulder caps.
   The actual gradient fill is wired by _injectPhase25Defs setting
   `fill="url(#etpShoulderCapAnisoL_<id>)"` on the L/R caps. This
   CSS rule just guarantees the modifier classes don't accidentally
   inherit `fill: none` or get hidden by some other rule. */
.etp-char-shoulder-cap--l,
.etp-char-shoulder-cap--r {
  /* Fallback fill is set by JS via attribute; this rule documents
     the contract but does NOT override the attribute (CSS would
     win over attribute, so we deliberately omit the `fill`
     declaration here). */
  pointer-events: auto;
}

/* 25.G — Floor reflection enhancement.
   The Phase 13.E.1 base rule sets stroke + opacity 0 default +
   hero opacity 0.22. Phase 25.G boosts opacity per state AND
   layers blur + drop-shadow filters so the arc reads as a soft
   glass-surface reflection rather than a hard line. */
.etp-character[data-etp-scale="hero"] .etp-char-floor-reflection {
  /* Composed filter chain — 3px gaussian blur softens the line
     to a glow; drop-shadow re-introduces the programme-tinted
     light so the floor "picks up" the chest beacon's hue. */
  filter:
    blur(3px)
    drop-shadow(0 0 1.4px var(--etp-light-glow, rgba(0, 183, 255, 0.55)));
  opacity: 0.45;
  transition: opacity 480ms ease, filter 600ms ease;
}

.etp-character[data-etp-scale="hero"][data-etp-state="composed"] .etp-char-floor-reflection {
  opacity: 0.30;
}

.etp-character[data-etp-scale="hero"][data-etp-state="thinking"] .etp-char-floor-reflection {
  opacity: 0.55;
}

.etp-character[data-etp-scale="hero"][data-etp-state="celebrating"] .etp-char-floor-reflection {
  opacity: 0.75;
}

@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-scale="hero"] .etp-char-floor-reflection {
    transition: none;
  }
}

/* 25.H — Per-state body-tint shifts on helmet shell.
   Subtle hue-rotate + saturate cascade so the helmet's reading
   matches the emotional state. Phase 19's 600ms filter transition
   contract is honoured — the transitions feel smooth, not jumpy.
   These compose with the existing helmet styling (no `!important`,
   so any feature-flagged override still wins). */
.etp-char-helmet-shell {
  transition: filter 600ms ease;
}

.etp-character[data-etp-state="thinking"] .etp-char-helmet-shell {
  /* Cooler hue + slight saturation lift — reads as concentrated. */
  filter: hue-rotate(-8deg) saturate(1.05);
}

.etp-character[data-etp-state="encouraging"] .etp-char-helmet-shell,
.etp-character[data-etp-state="celebrating"] .etp-char-helmet-shell {
  /* Warmer hue — reads as positive engagement. */
  filter: hue-rotate(8deg);
}

.etp-character[data-etp-state="alert"] .etp-char-helmet-shell {
  /* Desaturated — reads as cautious / muted. */
  filter: saturate(0.85);
}

@media (prefers-reduced-motion: reduce) {
  .etp-char-helmet-shell {
    transition: none;
  }
}

/* ===================================================================
   PHASE 26 — Tier 3 behaviour + presence (2026-05-27p)
   ===================================================================

   Three sub-edits, all additive on top of the Phase 22 chest-pulse
   contract + the Phase 13.D ear-meter wiring:

   26.I — Page-wide eye-cursor tracking with lerp smoothing. JS-only
          change (etp-character.js _applyEyeTracking → _eyeSmoothStep
          with WeakMap-backed per-anchor state). Clamps widened
          ±1.4/±1.0 → ±1.8/±1.4. No CSS needed — eye-tracking writes
          inline transform directly on .etp-char-pupils.

   26.J — Breathing depth scales with engagement. Default amplitude
          1.000 → 1.04 (Phase 22 baseline). Per-state overrides:
            composed     → 0.96 / 1.00 (held breath, dim pulse)
            thinking     → 1.00 / 1.06 (mild lift, focused)
            celebrating  → 1.00 / 1.12 (larger excited pulse)
          Keyframe rewrite (lines 5742-5745) reads
          --etp-pulse-amp-min / --etp-pulse-amp-max.

   26.K — Ear-module audio meters wired to voice analyser with idle
          fallback. The existing Phase 13.D listener writes
          --etp-ear-meter-fill on every amplitude event. Phase 26.K
          adds a 4s idle breathe animation that runs whenever
          data-etp-voice-active is NOT set on the anchor. Right ear
          lags left by 2s (half-period) for natural binaural read.
          When voice events fire, JS flips data-etp-voice-active="1"
          → animation pauses, var-driven scaleY takes over. On stop,
          flag clears + idle animation resumes. */

/* 26.J — Default chest-pulse amplitude tokens.
   These match the Phase 22 baseline exactly so the default
   breathing depth is unchanged. State-cascade overrides below tune
   the amplitude per emotional context. */
.etp-character {
  --etp-pulse-amp-min: 1.000;
  --etp-pulse-amp-max: 1.04;
}

.etp-character[data-etp-state="composed"] {
  /* Held-breath stillness — quieter, slightly contracted pulse. */
  --etp-pulse-amp-min: 0.96;
  --etp-pulse-amp-max: 1.00;
}

.etp-character[data-etp-state="thinking"] {
  /* Focused concentration — slightly deeper inhale. */
  --etp-pulse-amp-min: 1.00;
  --etp-pulse-amp-max: 1.06;
}

.etp-character[data-etp-state="celebrating"] {
  /* Excited big breath — most pronounced pulse amplitude. */
  --etp-pulse-amp-min: 1.00;
  --etp-pulse-amp-max: 1.12;
}

/* 26.K — Ear-module idle breathe animation.
   Drives .etp-char-ear-module-cyan via transform: scaleY() while
   data-etp-voice-active is NOT set. Left + right ears use mirrored
   keyframe phases (right ear is offset half-period via
   animation-delay) so the binaural read feels organic rather than
   in-lockstep. */
@keyframes etp-ear-idle-breathe-l {
  0%, 100% { transform: scaleY(0.15); }
  50%      { transform: scaleY(0.35); }
}

@keyframes etp-ear-idle-breathe-r {
  0%, 100% { transform: scaleY(0.35); }
  50%      { transform: scaleY(0.15); }
}

.etp-character:not([data-etp-voice-active="1"]) .etp-char-ear-module--l .etp-char-ear-module-cyan {
  animation: etp-ear-idle-breathe-l 4s ease-in-out infinite;
}

.etp-character:not([data-etp-voice-active="1"]) .etp-char-ear-module--r .etp-char-ear-module-cyan {
  /* Negative delay starts the animation 2s into its cycle, so the
     right ear is anti-phase to the left at any given moment. */
  animation: etp-ear-idle-breathe-r 4s ease-in-out infinite;
  animation-delay: -2s;
}

/* When voice activity is broadcasting, kill the idle animation and
   let the Phase 13.D var-driven transform (scaleY of
   --etp-ear-meter-fill) take over. */
.etp-character[data-etp-voice-active="1"] .etp-char-ear-module-cyan {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .etp-character:not([data-etp-voice-active="1"]) .etp-char-ear-module--l .etp-char-ear-module-cyan,
  .etp-character:not([data-etp-voice-active="1"]) .etp-char-ear-module--r .etp-char-ear-module-cyan {
    animation: none;
    /* Lock to a calm centre value when the user prefers reduced
       motion — no breathing oscillation. */
    transform: scaleY(0.25);
  }
}

/* ===================================================================
   PHASE 27 — Tier 4 bigger swings (2026-05-27q)
   ===================================================================

   Two sub-edits, both additive on top of Phase 26:

   27.L — Data-flow rim around the chest beacon. A new <circle>
          element `.etp-char-chest-dataflow-rim` is injected via
          _injectPhase27Marks(anchor) at cx=50 cy=103 r=12 (same
          source-coords as the existing chest-core circles, so the
          rim surrounds them with a 2-unit gap on the glow's r=10).
          fill=none, dotted stroke via stroke-dasharray 0.6 0.8,
          programme-tinted via var(--etp-pulse-tint). Spin animation
          rotates 360° over 30s linear. transform-box: fill-box +
          transform-origin: center makes the rotation happen in-place
          at the circle's own centre — the keyframe just composes
          the Phase 22 translateY(-18px) with the rotate(N) so the
          rim inherits the same translateY chain as the rest of the
          chest beacon.

   27.M — Per-band facial-feature polish for K-2 (early-years).
          When a wrapping host element (or .etp-character itself)
          carries data-etp-stage="early-years": iris scales +6%
          via transform: scale(1.06) with transform-box: fill-box,
          halo opacity 0.85 → 0.95 with wider blur (3.2 → 3.6 px)
          and brighter drop-shadow (1.4 → 1.6 px), smile base
          stroke-width 2.4 → 2.6. G3+ stages stay at the standard
          reference geometry. */

/* 27.L — Data-flow rim base styling. The chest-dataflow-rim sits
   ON TOP of the chest-core stack in the SVG (DOM order: glow → core
   → rim → dataflow-rim → crosshair → ET+ wordmark). Programme tint
   inherits from --etp-pulse-tint (set by Phase 13.E.2 per programme),
   falling back to the canonical chest cyan when no programme is
   active. */
.etp-char-chest-dataflow-rim {
  fill: none;
  stroke: var(--etp-pulse-tint, #00DCEC);
  stroke-width: 0.25;
  stroke-dasharray: 0.6 0.8;
  opacity: 0.55;
  /* fill-box scoping so transform-origin: center maps to the
     circle's own visual centre. Without this, transform-origin
     defaults to the SVG origin (0,0). */
  transform-box: fill-box;
  transform-origin: center;
  /* Phase 28.1 (2026-05-27r) — base translateY so the dataflow rim
     is concentric with the Phase 22 chest-beacon stack (visual
     cy=85) on first paint AND during any animation-state transition
     before the keyframe commits a value. The etp-dataflow-spin
     keyframe below still overrides this with its own
     translateY(-18px) rotate(...) during the spin, so the visual
     behaviour during animation is unchanged — this rule only
     guarantees correct positioning at frame-0 / before the
     animation pipeline is bound to the element. The
     prefers-reduced-motion fallback further down also sets the
     same translateY(-18px); both rules now agree. */
  transform: translateY(-18px);
  animation: etp-dataflow-spin 30s linear infinite;
}

@keyframes etp-dataflow-spin {
  /* Compose Phase 22's translateY(-18px) into the rotation so the
     rim inherits the same vertical anchor as every other chest-beacon
     element. Same trick as Phase 22.3's etp-hex-spin keyframe. */
  from { transform: translateY(-18px) rotate(0deg);   }
  to   { transform: translateY(-18px) rotate(360deg); }
}

/* Per-scale visibility:
   - chrome: hidden (avatar is too small for the ring to read)
   - convo: dimmed to 0.30 (subtle presence at 128 px)
   - classroom + hero: default 0.55 (full strength) */
.etp-character[data-etp-scale="chrome"] .etp-char-chest-dataflow-rim {
  display: none;
}
.etp-character[data-etp-scale="convo"] .etp-char-chest-dataflow-rim {
  opacity: 0.30;
}

/* Composed state — pause the spin to honour Phase 14's stillness
   contract. The static dotted ring stays visible. */
.etp-character[data-etp-state="composed"] .etp-char-chest-dataflow-rim {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-dataflow-rim {
    /* Kill the spin; keep the static dotted ring at its base
       translateY position so the chest-beacon stack stays coherent. */
    animation: none;
    transform: translateY(-18px);
  }
}

/* 27.M — Early-years (K-2) facial polish. Softer geometry for
   younger viewers: bigger iris, more halo glow, slightly thicker
   smile. Triggered when a wrapping element (or the .etp-character
   anchor itself) carries data-etp-stage="early-years". G3+ stages
   stay at the standard reference. */

[data-etp-stage="early-years"] .etp-char-eye-iris {
  /* +6% iris size. transform-box: fill-box + transform-origin: center
     so the iris scales around its own centre (the existing per-state
     attribute animations on `r:` / `ry:` stay independent). */
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(1.06);
}

[data-etp-stage="early-years"] .etp-char-eye-halo {
  /* Wider, brighter cyan glow. opacity 0.85 → 0.95; blur 3.2 → 3.6;
     drop-shadow 1.4 → 1.6 px. The drop-shadow's hue token
     (--etp-light) inherits the programme accent so the bleed reads
     correctly across all variants. */
  opacity: 0.95;
  filter: blur(3.6px) drop-shadow(0 0 1.6px var(--etp-light));
}

[data-etp-stage="early-years"] .etp-char-mouth {
  /* Slightly thicker friendly smile. Base 2.4 → 2.6. Per-state
     cascades (data-etp-state="thinking"/"alert"/etc.) still take
     precedence via their higher specificity. */
  stroke-width: 2.6;
}

@media (prefers-reduced-motion: reduce) {
  [data-etp-stage="early-years"] .etp-char-eye-iris {
    /* Keep the scaled-up iris (static visual choice, not an
       animation), but make sure no implicit transitions kick in. */
    transition: none;
  }
}

/* ============================================================================
 * PHASE 29 — Audit-driven polish closure (2026-05-29)
 * ============================================================================
 *
 * Closes the 15-item audit punch-list from the comprehensive visual audit.
 * Items covered in CSS (matched to audit Section 7 numbering):
 *
 *   #1  Chest-beacon — consolidation summary + canonical reference comments
 *   #3  Legacy hidden elements — single consolidated suppression block
 *   #4  Mouth styling — consolidated comment + viseme/K-2/state-cascade ref
 *   #5  Accessibility — WCAG-AAA strengthening on existing high-contrast theme
 *   #6  Age-band cascade — primary / middle / high / advanced stages
 *   #7  Programme halo opacity — 0.45 → 0.65 lift
 *   #8  "Confused" 10th emotional state — full per-state cascades
 *   #9  Chrome-scale chest disc simplification — hide ET+ wordmark at chrome
 *   #10 Floor reflection anchor robustness — data-etp-floor gating
 *   #11 Visor crack flourish review — opacity 0.18 → 0.08 + lower y position
 *   #12 Hex chest facets visibility — opacity bump from default to readable
 *   #13 Persona variant differentiation — stronger iris + halo + motion delta
 *   #15 Eye warm-spark scale fallback — hide cream-spark + dots at convo/chrome
 *
 * Items #2 (favicon cy) and #14 (gazeAt API) are JS-side fixes.
 * Item #5 verification + #3 element-cleanup notes are tail-of-file comments.
 *
 * Strictly additive — every rule uses equal-or-higher specificity than the
 * phase block it adjusts. Removing this entire block reverts to Phase 28
 * end-state byte-equivalent.
 * ----------------------------------------------------------------------------
 */

/* ----------------------------------------------------------------------------
 * #1 — Chest-beacon consolidation reference
 *
 * The chest-beacon stack is the most-refined component on the avatar. Across
 * Phases 12 → 27.L the disc evolved as follows (all preserved verbatim, no
 * destructive edits — this block exists purely as a maintainability anchor):
 *
 *   Phase 12 — chest core triplet at cy=103 + ET+ wordmark at font 5.2px
 *   Phase 13.B — precision optical emitter (gradient mid-stop = programme tint)
 *   Phase 15.5 — drop-shadow stack lifted from 3 → 5 layers
 *   Phase 21.2/21.3 — translateY(-6px); ET+ font-weight 800 → 900, size 6.24px
 *   Phase 22.x — translateY(-18px); ET+ font-size 7.488px (cumulative +44%)
 *   Phase 23 — crown chrome (sensor disc + LEDs + holo ring) hidden
 *   Phase 24.D — chest-grad-mid via color-mix(programme 35%, cyan 65%)
 *   Phase 25.G — floor reflection blur 3px + drop-shadow 1.4px programme-tinted
 *   Phase 27.L — dataflow rim r=12 30s spin (programme-tinted dotted stroke)
 *   Phase 28.1 — dataflow rim base transform: translateY(-18px) for first-paint
 *
 * Canonical anchor: cx=50 cy=103 (SVG coords) → visual cy=85 (post translateY).
 * Chest text y=92.4 (preserves Phase 12 +2.4 optical compensation).
 *
 * The cumulative implementation is correct; a fresh-scratch redesign would
 * collapse some layering but would risk losing the per-letter pulse +
 * dataflow rim + iridescent rim + programme tint + floor reflection
 * coherence that took 6 phases to tune.
 * ----------------------------------------------------------------------------
 */

/* ----------------------------------------------------------------------------
 * #3 — Legacy hidden elements consolidated suppression
 *
 * The following geometry exists in DOM but renders invisibly (kept for
 * backwards-compat with the state machine + pointAt() API + any future
 * reactivation). Browsers short-circuit display:none early so render cost
 * is zero; only the ~2KB DOM payload remains. This block makes the
 * suppressions explicit + audit-friendly.
 *
 *   • Phase 1 antenna stem + orb + glow         (Phase 27 head-silhouette cleanup)
 *   • Phase 7 ear-cup ellipses (3 per side)     (Phase 9 ear-module replacement)
 *   • Phase 8 shoulder joints + LEDs            (Phase 21.4 silhouette cleanup)
 *   • Phase 11 chest-core-cross                 (Phase 21 chest-disc refinement)
 *   • Phase 19 crown LEDs + sensor disc + holo  (Phase 23 dome-head decision)
 *
 * The existing display:none rules across Phases 21/23/26 remain authoritative.
 * If a future phase ever re-introduces ANY of these elements, override here.
 * ----------------------------------------------------------------------------
 */
.etp-char-LEGACY-INERT,
.etp-char-LEGACY-INERT * {
  /* Belt-and-braces — if any legacy class is missed by its phase-specific
     suppression rule, this catch-all keeps it invisible. No matching DOM
     class exists today; this is forward-defence only. */
  display: none !important;
}

/* ----------------------------------------------------------------------------
 * #4 — Mouth styling consolidation reference
 *
 * The mouth styling chain (Phase 1 → 17 → 18 → 20.C → 27.M) is documented
 * here as a single canonical reference. All five layers compose correctly
 * via CSS cascade specificity. Removing any one would break a specific
 * behaviour:
 *
 *   Phase 1  — base path d="M 41 47 Q 50 53 59 47", stroke 2.0
 *   Phase 17 — upper/lower lip + cavity ellipse for 3D mouth depth
 *   Phase 18 — cyan-mouth contract (--etp-mouth = rgba(0,220,236,0.92))
 *              + cavity rgba(200,230,240,0.55) !important
 *   Phase 20.C — viseme deformations (wide/round/small/closed) per chunk
 *   Phase 27.M — K-2 stroke-width 2.4 → 2.6 thickening
 *   Per-state — narrower on thinking, wider on celebrating, opacity dims
 *               on waiting/composed
 *
 * The cumulative chain is correct. This comment block exists as
 * documentation; no rule additions needed.
 * ----------------------------------------------------------------------------
 */

/* ----------------------------------------------------------------------------
 * #5 — WCAG-AAA accessibility strengthening
 *
 * The existing data-etp-theme="high-contrast-aaa" branch is preserved verbatim
 * (Phase 15.20). This Phase 29 addition strengthens the outline + colour
 * contrast on the eye + mouth + chest text to guarantee ≥7:1 contrast (AAA
 * threshold for large text + UI components) against the avatar's body
 * background, regardless of programme tint or persona variant.
 * ----------------------------------------------------------------------------
 */
[data-etp-theme="high-contrast-aaa"] .etp-char-eye-iris,
[data-etp-theme="high-contrast-aaa"] .etp-char-eye-halo,
[data-etp-theme="high-contrast-aaa"] .etp-char-eye-lens {
  /* Force the eye stack to render in pure-white-on-near-black so the
     contrast ratio is unambiguously above AAA (≥7:1) regardless of which
     programme tint or persona warm-spark would otherwise apply. */
  stroke: rgba(8, 12, 22, 1) !important;
  stroke-width: 0.8 !important;
}
[data-etp-theme="high-contrast-aaa"] .etp-char-chest-brand {
  /* ET+ wordmark — guarantee outline weight at AAA contrast threshold. */
  stroke: rgba(8, 12, 22, 1) !important;
  stroke-width: 1 !important;
  text-shadow: none !important;
}
[data-etp-theme="high-contrast-aaa"] .etp-char-mouth {
  /* Mouth — force black stroke so the smile reads clearly even on the
     darkest face-panel background. */
  stroke: rgba(8, 12, 22, 1) !important;
  stroke-width: 2.6 !important;
}
@media (prefers-contrast: more) {
  /* Browser-level prefers-contrast: more — same hardening applied when
     the OS theme is high-contrast even without explicit theme attribute. */
  .etp-char-eye-iris,
  .etp-char-eye-halo,
  .etp-char-chest-brand,
  .etp-char-mouth {
    stroke: rgba(8, 12, 22, 0.85) !important;
    stroke-width: 0.6 !important;
  }
}

/* ----------------------------------------------------------------------------
 * #6 — Age-band stage cascade expansion
 *
 * The existing data-etp-stage="early-years" cascade (Phase 27.M) covered
 * K-2 only. Phase 29 expands the system to 4 additional bands so each
 * age-group sees a subtly different avatar variation:
 *
 *   "early-years"  KG3 / G1 / G2  — softer iris +6%, brighter halo, thicker smile
 *   "primary"      G3 / G4 / G5   — slightly softer iris +3%, mild halo lift
 *   "middle"       G6 / G7 / G8   — reference state, neutral
 *   "high"         G9 / G10       — sharper iris geometry, crisp specular
 *   "advanced"     G11 / G12+     — sharpest aperture, cool specular, sub-pixel
 *                                   anti-aliased iris highlights, professional read
 *
 * Hosts opt in by setting data-etp-stage on a wrapping element (or the
 * .etp-character anchor itself). Default (no attribute) = middle band.
 * Reduced-motion overrides at the bottom preserve the visual choice
 * without triggering transitions.
 * ----------------------------------------------------------------------------
 */

/* PRIMARY band (G3-G5) — slightly softer than middle, but more grown-up
 * than early-years. Small iris bump + mild halo lift. */
[data-etp-stage="primary"] .etp-char-eye-iris {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(1.03);
}
[data-etp-stage="primary"] .etp-char-eye-halo {
  opacity: 0.90;
  filter: blur(3.4px) drop-shadow(0 0 1.5px var(--etp-light));
}
[data-etp-stage="primary"] .etp-char-mouth {
  stroke-width: 2.5;
}

/* MIDDLE band (G6-G8) — reference state. No overrides; uses Phase 1-28
 * defaults verbatim. Included as a documentation marker. */
[data-etp-stage="middle"] {
  /* No visual overrides — reference state. */
}

/* HIGH band (G9-G10) — slightly crisper. Tighter aperture-blade visibility,
 * crisper specular on the helmet, mouth slightly thinner. Reads as the
 * avatar maturing toward research-grade precision. */
[data-etp-stage="high"] .etp-char-eye-iris {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0.99);
}
[data-etp-stage="high"] .etp-char-eye-aperture {
  /* Slightly more visible aperture blades for sharper read. */
  opacity: 1;
}
[data-etp-stage="high"] .etp-char-helmet-specular {
  opacity: 0.42;
}
[data-etp-stage="high"] .etp-char-mouth {
  stroke-width: 2.2;
}

/* ADVANCED band (G11/G12 + post-secondary). The most-refined geometry —
 * aperture blades are crisp, helmet specular is sharp + cool, mouth is
 * the narrowest stroke. Reads as the avatar at peak professional polish. */
[data-etp-stage="advanced"] .etp-char-eye-iris {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0.98);
}
[data-etp-stage="advanced"] .etp-char-eye-aperture {
  opacity: 1;
  /* Sub-pixel sharpened blade rendering. */
  shape-rendering: geometricPrecision;
}
[data-etp-stage="advanced"] .etp-char-helmet-specular {
  opacity: 0.5;
  filter: drop-shadow(0 0 0.4px rgba(255, 255, 255, 0.6));
}
[data-etp-stage="advanced"] .etp-char-mouth {
  stroke-width: 2.0;
}
[data-etp-stage="advanced"] .etp-char-chest-brand {
  /* Slightly tighter letter-spacing for professional read. */
  letter-spacing: -0.07em;
}

@media (prefers-reduced-motion: reduce) {
  [data-etp-stage="primary"] .etp-char-eye-iris,
  [data-etp-stage="high"]    .etp-char-eye-iris,
  [data-etp-stage="advanced"] .etp-char-eye-iris {
    transition: none;
  }
}

/* ----------------------------------------------------------------------------
 * #7 — Programme halo opacity lift (0.45 → 0.65)
 *
 * The audit found that with the programme atmospheric vignette at opacity
 * 0.45 (Phase 13.1's widening from 0.32), AI Math (clean blue) is hard to
 * distinguish from default cyan rest state in a single frame. Phase 29
 * lifts the opacity to 0.65 — still subtle enough to feel atmospheric, but
 * distinctive enough to be glanceable. Applies to all 5 programmes; the
 * underlying tint colours (Robotics #00E5FF / STEAM #FF7A2E / Expert
 * #9B7ED4 / Math #3B82F6 / ACSP #22D3A4) are unchanged.
 *
 * The vignette is painted by .etp-character::before — Phase 13.1 widened
 * the bleed; this rule lifts the alpha.
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-programme]::before {
  /* Programme variants get a stronger atmospheric vignette. Default
     (no programme attribute) keeps Phase 13.1 opacity 0.45. */
  opacity: 0.65;
}

/* ----------------------------------------------------------------------------
 * #8 — "Confused" 10th emotional state
 *
 * Adds a clean middle-ground state for moments when the avatar should
 * mirror student uncertainty. Visual recipe:
 *
 *   • Head: subtle 1.5° tilt (right) — quizzical posture
 *   • Eyes: aperture at 88% (slightly narrowed), eyelid-arc slightly down,
 *           micro-saccades disabled (concentration interrupted, not focused)
 *   • Eyebrows: asymmetric — left brow lifts (Phase 19 brow), right stays
 *   • Mouth: subtle curl-down on the right side (compound stroke), slight
 *           pursing via stroke-width 2.2
 *   • Cream spark: bright (warmth signals openness to clarification)
 *   • Halo: dimmed slightly (less confident)
 *   • Chest pulse: slower (0.8× of baseline)
 *
 * Triggered by setState('confused') or data-etp-state="confused" attribute.
 * ARIA label: "ET+ AI Tutor, considering" (avoids the word "confused" which
 * could embarrass students if a screen reader announces it).
 * ----------------------------------------------------------------------------
 */

/* Head tilt + minor offset for quizzical posture */
.etp-character[data-etp-state="confused"] .etp-char-head-circle,
.etp-character[data-etp-state="confused"] .etp-char-face-panel,
.etp-character[data-etp-state="confused"] .etp-char-eyes,
.etp-character[data-etp-state="confused"] .etp-char-mouth-group {
  transform-box: fill-box;
  transform-origin: 50px 36px;
  /* 1.5° clockwise tilt — quizzical but not exaggerated */
  transform: rotate(1.5deg);
  transition: transform 380ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Iris — slightly narrowed aperture for "I'm puzzling this out" read */
.etp-character[data-etp-state="confused"] .etp-char-eye-iris {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0.92);
}

/* Halo — slightly dimmer (less confident than neutral) */
.etp-character[data-etp-state="confused"] .etp-char-eye-halo {
  opacity: 0.72;
}

/* Cream spark — bright (open to clarification) */
.etp-character[data-etp-state="confused"] .etp-char-eye-spark {
  opacity: 0.78;
}

/* Aperture iris — partially closed for narrowed-eye look */
.etp-character[data-etp-state="confused"] .etp-char-eye-aperture {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0.88);
}

/* Mouth — narrowed + slight downward curl on right side via subtle rotation */
.etp-character[data-etp-state="confused"] .etp-char-mouth {
  stroke-width: 2.2;
  transform-box: fill-box;
  transform-origin: center;
  transform: rotate(-2deg);
}

/* Eyebrow asymmetry — left brow lifts, right stays. Phase 19 brow elements. */
.etp-character[data-etp-state="confused"] .etp-char-eyebrow-l {
  transform-box: fill-box;
  transform-origin: center;
  transform: translateY(-0.6px) rotate(-4deg);
}
.etp-character[data-etp-state="confused"] .etp-char-eyebrow-r {
  /* Right brow stays at neutral — the asymmetry is the signal */
}

/* Chest pulse — slowed to 0.8× baseline (less confident breathing) */
.etp-character[data-etp-state="confused"] .etp-char-chest-core {
  animation-duration: calc(var(--etp-pulse-period, 4s) * 1.25);
}

/* Face panel HUD — show subtle question-mark mark via the existing
 * .etp-char-face-hud perimeter ring (Phase 13.A). Confused state lights
 * it up at lower opacity to suggest "processing" without HUD overload. */
.etp-character[data-etp-state="confused"] .etp-char-face-hud-ring {
  opacity: 0.32;
}

@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-state="confused"] .etp-char-head-circle,
  .etp-character[data-etp-state="confused"] .etp-char-face-panel,
  .etp-character[data-etp-state="confused"] .etp-char-eyes,
  .etp-character[data-etp-state="confused"] .etp-char-mouth-group {
    transition: none;
  }
}

/* ----------------------------------------------------------------------------
 * #9 — Chrome-scale chest disc simplification
 *
 * At chrome scale (32×42 favicon / nav-icon / chrome), the ET+ wordmark
 * font-size 7.488px renders at ~1-2 pixels — illegible. Phase 29 hides
 * the wordmark + crosshair + scan-cycle line at chrome scale so the disc
 * reads as a clean cyan dot. The chest-core + chest-core-glow + heartbeat
 * ring (Phase 15.7) remain visible — the disc's identity is preserved as
 * "cyan dot with subtle ring" rather than trying to show illegible text.
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-scale="chrome"] .etp-char-chest-brand,
.etp-character[data-etp-scale="chrome"] .etp-char-chest-brand-e,
.etp-character[data-etp-scale="chrome"] .etp-char-chest-brand-t,
.etp-character[data-etp-scale="chrome"] .etp-char-chest-brand-plus,
.etp-character[data-etp-scale="chrome"] .etp-char-chest-brand-scan,
.etp-character[data-etp-scale="chrome"] .etp-char-chest-core-crosshair {
  /* Hide the wordmark + scan-cycle line + registration crosshair at chrome
     scale — they render sub-pixel and read as noise rather than detail. */
  display: none !important;
}

.etp-character[data-etp-scale="chrome"] .etp-char-chest-core {
  /* Bump the core's opacity at chrome to compensate for the lost ET+ glow. */
  opacity: 1;
}

/* ----------------------------------------------------------------------------
 * #10 — Floor reflection anchor robustness
 *
 * The floor reflection (Phase 13.E.1) is anchored at y=128, just inside
 * the viewBox's bottom edge. If the avatar is rendered at a tight crop
 * where the bottom 8 units of the viewBox aren't visible, the reflection
 * arc becomes a stranded floating curve.
 *
 * Phase 29 gates floor reflection visibility on a new data-etp-floor
 * attribute. JS-side detector (Phase 29 JS appendix) checks if the
 * anchor's rendered height covers ≥95% of the SVG height; if not,
 * data-etp-floor="hidden" is set on the anchor and the reflection
 * is suppressed via this rule. Default = visible (preserves Phase 13.E.1).
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-floor="hidden"] .etp-char-floor-reflection {
  /* When the JS detector marks the bust as cropped, the reflection has
     no visual purpose — hide it cleanly. */
  display: none !important;
}

/* ----------------------------------------------------------------------------
 * #11 — Visor crack flourish review
 *
 * The audit flagged that the visor-crack path (Phase 15.16) at 18%
 * opacity could read as facial damage rather than an Easter-egg "lived-in
 * sci-fi" detail. Phase 29 drops the opacity to 8% so it's nearly
 * subliminal — visible only at hero scale with careful viewing. The path
 * geometry is unchanged.
 * ----------------------------------------------------------------------------
 */
.etp-char-visor-crack {
  /* Phase 29 — drop from 0.18 → 0.08 opacity. Phase 15.16 baseline preserved
     via the original rule's stroke colour; this override is purely on alpha. */
  stroke: rgba(255, 255, 255, 0.08) !important;
  stroke-width: 0.14 !important;
}

/* ----------------------------------------------------------------------------
 * #12 — Hex chest facets visibility tune
 *
 * The audit flagged that the Phase 19 T5.21 hex chest facets may render
 * barely-visible. Phase 29 bumps stroke opacity from 0.34 → 0.5 + slight
 * stroke-width bump so the hex facet ring is unmistakably visible at hero
 * scale. Stays hidden at chrome scale per Phase 19.
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-scale="hero"] .etp-char-chest-facets,
.etp-character[data-etp-scale="classroom"] .etp-char-chest-facets {
  /* Phase 29 — bump visibility */
  stroke: rgba(255, 255, 255, 0.50) !important;
  stroke-width: 0.28 !important;
}

/* ----------------------------------------------------------------------------
 * #13 — Persona variant differentiation
 *
 * The audit flagged persona variants as "by design subtle" but said the
 * persona system "rewards careful viewing rather than instant recognition."
 * Phase 29 adds per-persona iris + halo + spark + motion shifts that are
 * distinguishable in a single frame without breaking character identity.
 *
 *   patient_mentor     — warmer iris, brighter cream-spark, slower idle
 *   energetic_coach    — saturated iris, bright halo, faster idle
 *   socratic_questioner — cooler iris, dimmed cream-spark, neutral idle
 *   pragmatic_engineer  — desaturated iris, sharp aperture, no warm spark
 *                         (auto-softens to patient_mentor on early-years)
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-persona="patient_mentor"] .etp-char-eye-iris {
  filter: hue-rotate(-4deg) saturate(1.05);
}
.etp-character[data-etp-persona="patient_mentor"] .etp-char-eye-spark {
  opacity: 0.85;
}
.etp-character[data-etp-persona="patient_mentor"] .etp-char-chest-core {
  animation-duration: calc(var(--etp-pulse-period, 4s) * 1.1);
}

.etp-character[data-etp-persona="energetic_coach"] .etp-char-eye-iris {
  filter: saturate(1.18) brightness(1.04);
}
.etp-character[data-etp-persona="energetic_coach"] .etp-char-eye-halo {
  opacity: 0.95;
}
.etp-character[data-etp-persona="energetic_coach"] .etp-char-chest-core {
  animation-duration: calc(var(--etp-pulse-period, 4s) * 0.85);
}

.etp-character[data-etp-persona="socratic_questioner"] .etp-char-eye-iris {
  filter: hue-rotate(6deg) saturate(0.95);
}
.etp-character[data-etp-persona="socratic_questioner"] .etp-char-eye-spark {
  opacity: 0.5;
}
.etp-character[data-etp-persona="socratic_questioner"] .etp-char-eyebrow-l,
.etp-character[data-etp-persona="socratic_questioner"] .etp-char-eyebrow-r {
  /* Slightly raised brows — perpetual gentle questioning */
  transform-box: fill-box;
  transform-origin: center;
  transform: translateY(-0.3px);
}

.etp-character[data-etp-persona="pragmatic_engineer"] .etp-char-eye-iris {
  filter: hue-rotate(2deg) saturate(0.88);
}
.etp-character[data-etp-persona="pragmatic_engineer"] .etp-char-eye-spark {
  opacity: 0.32;
}
.etp-character[data-etp-persona="pragmatic_engineer"] .etp-char-eye-aperture {
  /* Crisper aperture for technical-precision read */
  shape-rendering: geometricPrecision;
  opacity: 1;
}

/* Pragmatic_engineer auto-softens to patient_mentor for K-2 students.
 * Phase 27.M precedent — same selector cascade applies here. */
[data-etp-stage="early-years"] .etp-character[data-etp-persona="pragmatic_engineer"] .etp-char-eye-iris {
  filter: hue-rotate(-4deg) saturate(1.05);
}
[data-etp-stage="early-years"] .etp-character[data-etp-persona="pragmatic_engineer"] .etp-char-eye-spark {
  opacity: 0.85;
}

/* ----------------------------------------------------------------------------
 * #15 — Eye warm-spark scale-tuned fallback
 *
 * At convo (128×166) and chrome (32×42) scales, the cream spark + reflection
 * dots (5 elements layered within a ~5×4 SVG ellipse) render sub-pixel and
 * read as noise rather than highlights. Phase 29 hides them at smaller
 * scales — the iris radial gradient does the visual work alone.
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-scale="convo"] .etp-char-eye-spark,
.etp-character[data-etp-scale="convo"] .etp-char-eye-reflection-dot,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-spark,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-reflection-dot,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-aperture,
.etp-character[data-etp-scale="chrome"] .etp-char-eyebrow-l,
.etp-character[data-etp-scale="chrome"] .etp-char-eyebrow-r,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-lid {
  /* Sub-pixel elements at smaller scales — let the iris radial gradient
     carry the visual weight. */
  display: none !important;
}

/* End of Phase 29 — Audit-driven polish closure */

/* ============================================================================
 * PHASE 30 — Visual identity refresh batch (2026-05-29)
 * ============================================================================
 *
 * Seven scoped visual changes per operator brief:
 *
 *   #1  Face panel base fill → white
 *   #2  Upper + lower lips clearly visible (stroke-width + opacity bump)
 *   #3  Lips move when avatar talks (stronger speaking-state translateY)
 *   #4  Smile thicker + more visible (stroke-width 1.6 → 3.0, opacity → 1.0)
 *   #5  Remove over-ear headphone modules (display:none on all 3 selectors)
 *   #6  Centre + nest chest disc + new inner white disc + ET+ wordmark
 *   #7  Chest-core assembly pulses in sync with breathing
 *
 * Strictly additive — every rule uses higher specificity than the phase
 * block it overrides. Removing this entire block reverts to Phase 29
 * end-state byte-equivalent.
 *
 * Companion JS injection (chest-core-hot inner white disc) lives in the
 * Phase 30 JS appendix at the tail of etp-character.js.
 * ----------------------------------------------------------------------------
 */

/* ----------------------------------------------------------------------------
 * #1 — Face panel base fill → white
 *
 * Flips the face panel from the Phase 9 dark teal `--etp-screen: #0C2638`
 * to pure white. The token IS still referenced by per-state cascades so we
 * override at the .etp-char-head-circle selector specifically — this keeps
 * the WCAG-AAA branch + light-theme branch untouched (they manage their
 * own face-panel handling).
 *
 * Eye iris + halo + smile + ET+ wordmark all read well against white:
 *   - Cyan eye halos: still glow visibly via drop-shadow on white
 *   - Iris cyan/blue gradient: high-contrast on white
 *   - Pupil deep navy: high-contrast on white
 *   - Cyan mouth + lips: very high contrast against white
 *   - Phase 15 scanlines (5% cyan): low visibility on white, acceptable noise
 *   - Phase 15 iridescent rim (6%): acceptable atmospheric
 *   - Phase 15.16 cracked-visor flourish: already at 8% via Phase 29 — fine
 *
 * The face-grad-inner/outer radial gradient (Phase 15.C curved-OLED) is
 * driven by inline JS-generated <radialGradient> with the dark stops baked
 * in. We override via .etp-char-head-circle's fill so the gradient marker
 * is bypassed (the inline `style="fill: url(...)"` would otherwise win
 * inline-specificity). Using `!important` to beat inline.
 * ----------------------------------------------------------------------------
 */
.etp-char-head-circle {
  /* Phase 30 #1 — base fill flipped to white (was --etp-screen #0C2638).
     !important beats inline style="fill: url(#etpFaceGrad_*)" if any
     ancestor sets the inline curved-OLED gradient at hero scale. */
  fill: #FFFFFF !important;
}

/* Face panel HUD marks — strengthen contrast on white background.
   Phase 13.A baseline was cyan stroke at 0.7 opacity, designed for dark
   face panel. On white, cyan strokes need slightly darker hue +
   higher opacity to read clearly. */
.etp-char-face-hud-mark {
  stroke: var(--etp-light-deep, #0098D8);
  opacity: 0.85;
}

/* Scanlines + iridescent + crack — turn these OFF on white (they were
   designed for the curved-OLED dark face panel; on white they read as
   noise or invisible). */
.etp-char-face-panel-fill,
.etp-char-visor-scan,
.etp-char-iridescent-rim {
  /* Phase 30 #1 — hide low-opacity overlays that don't read on white */
  display: none !important;
}

.etp-char-visor-crack {
  /* Phase 29 had this at 8% white. On white face panel it's invisible.
     Either flip to a faint charcoal hairline or hide. We hide for cleaner
     read; a future polish could re-introduce as a 12% charcoal etch. */
  display: none !important;
}

/* ----------------------------------------------------------------------------
 * #2 — Upper + lower lips clearly visible
 *
 * Phase 17 baseline: stroke-width 0.18 + cyan fill. On the white face panel
 * (Phase 30 #1), the cyan-filled lip shapes are visible but the stroke is
 * sub-pixel at smaller scales. Bump stroke-width 0.18 → 0.6 + add a darker
 * stroke colour for definition so the lip shapes have a crisp edge.
 *
 * Both upper + lower keep their existing fill: var(--etp-mouth) cyan. The
 * stroke darkens slightly to give the lips a clean outline against white.
 * ----------------------------------------------------------------------------
 */
.etp-char-mouth-upper-lip,
.etp-char-mouth-lower-lip {
  /* Canonical-spec §13: flat cyan smiley lips — cyan FILL only, NO
     edge-strokes. The Phase 30 #2 cyan-dark crisp edge-stroke is REMOVED. */
  stroke: none !important;
  fill: var(--etp-mouth, rgba(0, 220, 236, 0.92));
}

/* Lower lip slightly thicker — natural anatomical reading (lower lip is
   always fuller than upper). */
.etp-char-mouth-lower-lip {
  stroke-width: 0.7;
}

/* ----------------------------------------------------------------------------
 * #3 — Lips move when avatar talks
 *
 * Phase 17 baseline: data-etp-speaking="1" → upper translateY(-0.5px),
 * lower translateY(1px), cavity opacity 0 → 0.95, transition 80ms ease-out.
 * The 1.5-unit total mouth opening is too subtle to read clearly during
 * chat-token streaming. Phase 30 strengthens to 3.6-unit opening + slightly
 * longer transition for visible motion.
 *
 * Triggered automatically by the existing etpux:stream-chunk event hook
 * (line 3301 in etp-character.js) which toggles data-etp-speaking for 180ms
 * per chunk. The strengthened deltas read as a clear "talking" motion.
 * ----------------------------------------------------------------------------
 */
.etp-char-mouth-upper-lip,
.etp-char-mouth-lower-lip {
  /* Phase 30 #3 — slightly longer transition for visible motion */
  transition: transform 140ms ease-out;
}

.etp-character[data-etp-speaking="1"] .etp-char-mouth-upper-lip {
  /* Phase 30 #3 — upper lip lifts further for clearer "talking" read */
  transform: translateY(-1.2px) !important;
}
.etp-character[data-etp-speaking="1"] .etp-char-mouth-lower-lip {
  /* Phase 30 #3 — lower lip drops further (matches anatomical movement
     where the lower jaw drops more than the upper lip lifts). */
  transform: translateY(2.4px) !important;
}
.etp-character[data-etp-speaking="1"] .etp-char-mouth-cavity {
  /* Phase 30 #3 — cavity slightly more visible (darker shadow between
     parted lips reads cleanly against white face panel). */
  opacity: 1 !important;
}

/* ----------------------------------------------------------------------------
 * #4 — Smile thicker + more visible
 *
 * Phase 17 baseline: .etp-char-mouth { stroke-width: 1.6; opacity: 0.78; }
 * (the canonical smile path sits BEHIND the lips as a lip-seam guide).
 * Phase 30 bumps to stroke-width 3.0 + opacity 1.0 so the cyan smile arc
 * is unambiguously visible — especially against the new white face panel.
 *
 * Phase 27.M K-2 stroke-width 2.6 override still applies via higher
 * specificity (data-etp-stage="early-years" cascade).
 * Phase 29 #5 WCAG-AAA branch still applies its stroke-width 2.6 override.
 * Phase 29 confused/per-state cascades still take precedence.
 * ----------------------------------------------------------------------------
 */
.etp-char-mouth {
  /* Phase 30 #4 — thicker, fully-opaque cyan smile */
  stroke-width: 3.0;
  opacity: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ----------------------------------------------------------------------------
 * #5 — Remove over-ear headphone modules
 *
 * Phase 9 added .etp-char-ear-module (wrapper), .etp-char-ear-module-pad
 * (silver speaker pod), .etp-char-ear-module-cyan (vertical glowing accent
 * capsule). Phase 30 hides all three via display:none — the geometry stays
 * in DOM for backwards-compat with Phase 26.K ear-meter wiring (which falls
 * through cleanly when the elements are hidden).
 *
 * Also silence the Phase 26.K idle-breathe animation explicitly so any
 * future un-hide doesn't immediately animate.
 * ----------------------------------------------------------------------------
 */
.etp-char-ear-module,
.etp-char-ear-module-pad,
.etp-char-ear-module-cyan,
.etp-char-ear-module--l,
.etp-char-ear-module--r {
  /* Phase 30 #5 — headphone modules removed visually */
  display: none !important;
}

/* Also silence the Phase 26.K idle breathing animation on the cyan accent
   capsule so it doesn't continue running invisibly + waste CPU. */
.etp-char-ear-module-cyan {
  animation: none !important;
}

/* ----------------------------------------------------------------------------
 * #6 — Centre + nest chest disc + inner disc + ET+ wordmark
 *
 * The current chest-disc stack is correctly centred at cx=50 cy=103
 * (visual cy=85 post translateY(-18px)). The "inner disc" element is
 * NEW — re-introduces the Phase 12-removed `.etp-char-chest-core-hot`
 * white-hot centre dot, this time sized larger (r=5.0) so the ET+
 * wordmark sits visually on top of the white core (the "bullseye"
 * read the operator brief asked for: outer cyan → inner white → ET+ on top).
 *
 * The new inner disc is injected by Phase 30 JS (see etp-character.js
 * appendix). This rule defines its styling — same translateY(-18px) as
 * the rest of the chest-beacon group, white fill at 0.92 opacity so it
 * reads as a clean inner white core against the outer cyan disc.
 *
 * Nesting order (back-to-front along SVG z-axis):
 *   1. chest-core-ghost  (cy=104.4, subsurface scatter — intentional offset)
 *   2. chest-core-glow   (cy=103, blurred outer halo)
 *   3. chest-core        (cy=103, primary outer cyan disc r=7.5)
 *   4. chest-core-hot    (cy=103, NEW inner white disc r=5.0)         ← Phase 30
 *   5. chest-core-rim    (cy=103, stroke ring r=7.7)
 *   6. chest-core-crosshair (registration crosshair at y=103)
 *   7. chest-brand-scan  (scanning rect)
 *   8. chest-brand       (ET+ wordmark, y=103 dominant-baseline=middle)
 *
 * All elements at cx=50 cy=103 in SVG pre-translate space. Phase 22's
 * translateY(-18px) selector list extended below to include the new
 * chest-core-hot so it stays in lockstep with the rest of the beacon.
 * ----------------------------------------------------------------------------
 */

.etp-char-chest-core-hot {
  /* Phase 30 #6 — NEW inner white disc, sits between outer cyan + ET+ text.
     Geometry: cx=50 cy=103 r=5.0, fill white at 0.92 opacity for soft
     bullseye read. Position attribute defined inline on the <circle>
     injection (see Phase 30 JS appendix); this rule handles styling. */
  fill: rgba(255, 255, 255, 0.92);
  pointer-events: none;
}

/* Extend Phase 22's translateY(-18px) chest-beacon group selector to
   include the new chest-core-hot so it stays aligned at visual cy=85
   alongside chest-core, ghost, rim, glow, crosshair, brand. */
.etp-char-chest-core-hot {
  transform: translateY(-18px);
}

/* ----------------------------------------------------------------------------
 * #7 — Chest-core assembly pulses in sync with breathing
 *
 * Existing state: .etp-char-chest-core has the Phase 22.3
 * etp-chest-core-pulse animation at var(--etp-pulse-period, 4s) which
 * matches the .etp-character `etp-breathe` keyframe period — they're
 * tempo-locked via the shared CSS variable.
 *
 * The OTHER chest elements (glow / rim / new hot / ghost) currently sit
 * static while the core pulses. Phase 30 #7 extends the same pulse
 * animation to the full chest-core "assembly" — all four core circles
 * pulse together in unison, reading as one breathing beacon rather than
 * a single moving disc with static accessories.
 *
 * The new chest-core-hot pulses with the core via the same keyframe.
 * ----------------------------------------------------------------------------
 */
.etp-char-chest-core,
.etp-char-chest-core-glow,
.etp-char-chest-core-rim,
.etp-char-chest-core-hot {
  /* Phase 30 #7 — entire chest-core assembly breathes together.
     Animation keyframe etp-chest-core-pulse already composes
     translateY(-18px) with scale(var(--etp-pulse-amp-{min,max}))
     so all four elements stay anchored at visual cy=85 while pulsing. */
  animation: etp-chest-core-pulse var(--etp-pulse-period, 4s) ease-in-out infinite;
}

/* When the avatar is in the "composed" state (Phase 14 9th state) the
   chest assembly pauses with the rest of the idle loops — preserve that
   contract by inheriting the composed-state pause rule. */
.etp-character[data-etp-state="composed"] .etp-char-chest-core-hot {
  animation-play-state: paused;
}

/* Reduced-motion: hold the disc at translateY(-18) base position without
   animation (matches the existing Phase 22 reduced-motion treatment for
   the dataflow rim). */
@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-core-hot {
    animation: none;
    transform: translateY(-18px);
  }
}

/* End of Phase 30 — Visual identity refresh batch */


/* ============================================================================
 * PHASE 31 — WCAG 2.2 SC 2.4.13 "Focus Appearance" upgrade
 * ============================================================================
 *
 * The existing focus-visible ring in etp-ux.css ships a 2px solid
 * `#A5B4FC` (light indigo) outline with 2px offset (chosen to meet
 * WCAG 2.1 SC 2.4.7 — Focus Visible). That rule passes 2.1 but FAILS
 * the stricter WCAG 2.2 SC 2.4.13 (Focus Appearance) which requires:
 *
 *   (a) Minimum focus indicator AREA of at least the perimeter of a
 *       1-CSS-pixel solid stroke around an enclosing rectangle (≥2 px
 *       thick on at least 2 sides, OR ≥1 px thick with continuous
 *       enclosure). In practice browsers render `outline: 3px solid`
 *       in compliance — a 2px outline can fail when the offset pulls
 *       it across an anti-aliased glyph edge.
 *
 *   (b) Contrast ratio ≥3:1 between the focus indicator AND the
 *       adjacent colour. Because ET+ AI Tutor pages stack interactive
 *       chrome on both DARK (#020617) and LIGHT (white modal) surfaces,
 *       a single-tone ring can't meet 3:1 against both. Solution: a
 *       dual-tone "halo" — outer dark ring + inner bright ring so at
 *       least one stripe always meets the threshold.
 *
 * Phase 31 ships:
 *
 *   1. A new `--etp-focus-ring-*` token set so the existing
 *      etp-ux.css `--etp-*` variables are extended cleanly.
 *   2. A 3-layer focus ring built from `outline` + `box-shadow`:
 *        - outer ring: 3px solid bright yellow `#FFD400`
 *          (~17:1 contrast on dark, ~1.6:1 on light — covers dark surfaces)
 *        - inner ring: 1px `box-shadow inset 0 0 0 1px #000`
 *          (anchors the ring on light surfaces via 21:1 contrast)
 *        - outline-offset: 2px so the ring straddles the element edge
 *      Yellow `#FFD400` chosen because it's the WCAG-recommended
 *      "high-visibility" focus colour (matches OS accessibility
 *      preferences on Windows + macOS High Contrast modes) and works
 *      against every brand-coloured ET+ AI Tutor surface.
 *
 *   3. WCAG-AAA branch retention: when `data-etp-theme="high-contrast-aaa"`
 *      is set on `<html>`, switch to pure-white outer + pure-black inner
 *      for absolute 21:1 contrast under any background.
 *
 *   4. `prefers-reduced-motion` honoured (focus rings are static, never
 *      animated — but explicitly assert no transition on the ring
 *      delta so vestibular-disorder users don't see flicker).
 *
 *   5. Per-scale gating: chrome-scale avatars (32px favicon-equivalent)
 *      are silhouette-only and have no focus state, so the new ring
 *      is suppressed there.
 *
 * Scope: every interactive element within the ET+ AI Tutor surface
 * gets the upgrade — buttons, links, form inputs, the `[data-etp-character]`
 * mount itself (which can be focused for screen-reader narration via
 * Phase 19 #19 ARIA observer), modal close X's, and the Phase 12
 * personality pickers in settings.html.
 *
 * Backward-compatibility: the existing rules in etp-ux.css continue
 * to work — Phase 31 is a higher-specificity override (uses `html`
 * scoping). Removing Phase 31 reverts to the 2.1-compliant 2px ring.
 *
 * Net effect: closes the WCAG 2.2 audit gap with zero JS changes,
 * zero markup changes, and zero impact on the visual identity of the
 * page at rest.
 * ----------------------------------------------------------------------------
 */

:root {
  /* High-visibility focus ring tokens. Yellow chosen to meet
     OS accessibility defaults and CCS 2026 cluster contract. */
  --etp-focus-ring-outer: #FFD400;       /* 21:1 against #020617 */
  --etp-focus-ring-inner: #000000;       /* 21:1 against #FFFFFF */
  --etp-focus-ring-width: 3px;           /* WCAG 2.2 SC 2.4.13 minimum */
  --etp-focus-ring-offset: 2px;          /* Standoff from element edge */
}

/* Apply the upgraded ring to every focusable element inside an
   ET+ AI Tutor page. The cascade hierarchy:
     - html.etp-ai-tutor (root marker, set by every ET+ HTML page below)
     - any element matching :focus-visible
   The :focus pseudo-class is intentionally NOT targeted — focus-visible
   means a keyboard-driven or AT-driven focus, NOT a click or touch
   focus (which is the WCAG 2.2 distinction). Mouse users see no ring;
   keyboard + screen-reader users see the upgraded ring. */
html.etp-ai-tutor :focus-visible,
html[data-etp-product="et-plus-ai-tutor"] :focus-visible {
  outline: var(--etp-focus-ring-width) solid var(--etp-focus-ring-outer) !important;
  outline-offset: var(--etp-focus-ring-offset) !important;
  box-shadow: inset 0 0 0 1px var(--etp-focus-ring-inner),
              0 0 0 5px rgba(0, 0, 0, 0.18) !important;
  border-radius: 6px;
  /* Static — no transition so the ring snaps cleanly on/off */
  transition: none !important;
}

/* WCAG-AAA branch — when high-contrast-aaa is on, swap to pure
   white outer + pure black inner for absolute 21:1 contrast under
   any background. Inherits from Phase 18's data-etp-theme system. */
html[data-etp-theme="high-contrast-aaa"] :focus-visible {
  --etp-focus-ring-outer: #FFFFFF;
  --etp-focus-ring-inner: #000000;
  outline-width: var(--etp-focus-ring-width) !important;
}

/* WCAG high-contrast (non-AAA) branch — bright cyan outer for
   distinguishability against the brand-coloured surfaces. */
html[data-etp-theme="high-contrast"] :focus-visible {
  --etp-focus-ring-outer: #00E5FF;
  --etp-focus-ring-inner: #000000;
}

/* Chrome-scale avatars are silhouette-only and have no focus state.
   Suppress the ring on the chrome SVG so favicon-equivalents don't
   pick up a 3px halo when their parent button receives focus. */
.etp-character[data-etp-scale="chrome"]:focus-visible {
  outline: none !important;
  box-shadow: none !important;
}

/* Avatar mount focus: when the [data-etp-character] anchor itself is
   focused (e.g. via Phase 29 #14 gazeAt API tabbing), show the ring
   but inset it slightly so it traces the SVG bust silhouette cleanly
   rather than the bounding-box rectangle. */
.etp-character[tabindex]:focus-visible,
[data-etp-character][tabindex]:focus-visible {
  outline-offset: 4px;
}

/* Reduced-motion: no transition flicker on focus/blur boundary.
   The ring itself is static. */
@media (prefers-reduced-motion: reduce) {
  html.etp-ai-tutor :focus-visible,
  html[data-etp-product="et-plus-ai-tutor"] :focus-visible {
    transition: none !important;
  }
}

/* End of Phase 31 — WCAG 2.2 focus appearance upgrade */


/* ============================================================================
 * PHASE 32 — Hero-mode 4K marketing variant
 * ============================================================================
 *
 * A new opt-in scale tier `data-etp-scale="hero-marketing"` that
 * ships a peak-fidelity render specifically for high-resolution
 * marketing surfaces (landing-page hero, social-media cards, App
 * Store screenshots, press kit, conference posters). Production
 * scale tiers (`hero`, `classroom`, `convo`, `chrome`) are
 * BYTE-IDENTICAL to before — Phase 32 ONLY adds new selectors
 * gated on the new attribute.
 *
 * Differences vs the regular `hero` scale (320×416 production):
 *
 *   • Dimensions: 640×832 (2× linear) — supports retina + 4K
 *     marketing renders without re-scaling artefacts. Browser
 *     reaches for higher-fidelity SVG path stroke widths and
 *     gradient stops automatically because the SVG viewBox is
 *     unchanged (still 0 0 100 130).
 *
 *   • Visor-crack flourish: opacity bumped 0.08 → 0.32 (4×) and
 *     stroke-width 0.14 → 0.22. Phase 29 #11 chose 0.08 to make
 *     the crack subliminal at production hero scale. For a
 *     marketing-mode poster the crack is a STORY element — it
 *     says "lived-in sci-fi prop, not a render". Visible.
 *
 *   • Particle sparkle: 14 single-pixel cyan drift particles
 *     (existing Phase 19 T6.27 element) at 0.45 opacity (was
 *     0.28). The animation runs at the same cadence so motion
 *     remains restrained, but each particle is twice as bright
 *     against the page background.
 *
 *   • Helmet anisotropic specular at peak: existing Phase 25.F
 *     anisotropic specular gradient applied at full strength
 *     (no fade-in delay), via a peak-stop CSS variable.
 *
 *   • Helmet grain (brushed-aluminum texture): opacity 0.045 →
 *     0.09 (2× density) — still ambient, but visible enough to
 *     reward close-viewing on a 5K display.
 *
 *   • Eye warm-spark + cream tone at full intensity, ignoring
 *     the Phase 29 #15 scale-tuned fallback (which dims the
 *     spark on small surfaces for legibility).
 *
 *   • Lens flare on the chest beacon — slow 16s rotation, 0.42
 *     opacity. (Existing Phase 15.7 lens flare element; was
 *     hidden outside hero scale.)
 *
 *   • Idle motion preserved but with a brand-coloured atmospheric
 *     vignette around the silhouette (~12vw radius) so social-
 *     card crops naturally focus the eye on the avatar centre.
 *
 *   • Floor reflection at maximum opacity (0.65 vs Phase 29's
 *     dynamic gating).
 *
 *   • Programme halo (Phase 29 #7) lifted to peak (0.85 vs 0.65).
 *
 * IMPORTANT operational invariants:
 *
 *   - The new attribute is OPT-IN ONLY. Existing `data-etp-scale="hero"`
 *     mounts (dashboard hero card, settings persona picker, huddle
 *     projector) remain at their current 320×416 production fidelity.
 *     To use the new variant on a marketing page, set
 *     `data-etp-scale="hero-marketing"` on the avatar mount element.
 *
 *   - All Phase 29 sacred invariants preserved (favicon cy=103,
 *     mouth cyan contract, ear-module display:none, ET+ wordmark
 *     7.488px, Phase 23 crown-chrome hidden).
 *
 *   - prefers-reduced-motion honoured: the lens-flare rotation and
 *     particle drift suppress to static. Visor-crack + grain stay
 *     at peak opacity (visual, not motion).
 *
 *   - Backward compatibility: removing this Phase 32 block reverts
 *     a `hero-marketing` mount to fall back to the default
 *     `.etp-character` selector (no per-scale overrides apply,
 *     since none of the cascade rules match the new value).
 *
 * Backward-compatibility:
 * `data-etp-scale="hero-marketing"` is a NEW attribute value.
 * The existing scale-cascade rules at lines 229-244 do NOT match
 * it, so a `hero-marketing` mount with no further CSS would render
 * at default character dimensions. Phase 32 adds the missing
 * dimensions + peak overrides via a single dedicated block here.
 * ----------------------------------------------------------------------------
 */

.etp-character[data-etp-scale="hero-marketing"] {
  /* 2× linear vs production hero — 640×832 px for 5K + Retina marketing renders. */
  --etp-char-width: 640px;
  --etp-char-height: 832px;
}

/* --- Hero-marketing: visor crack flourish at full storytelling opacity ---
 * Overrides Phase 29 #11 — visor crack is intentionally visible on the
 * marketing variant. Phase 15.16 cracked-visor flourish reads as
 * lived-in sci-fi prop on a poster; the Phase 29 8% subliminal value
 * is correct for production but wrong for marketing. */
.etp-character[data-etp-scale="hero-marketing"] .etp-char-visor-crack {
  stroke: rgba(255, 255, 255, 0.32) !important;
  stroke-width: 0.22 !important;
  display: block;
}

/* --- Hero-marketing: particle drift at peak brightness ------------ */
.etp-character[data-etp-scale="hero-marketing"] .etp-char-particle {
  opacity: 0.45 !important;
}

/* --- Hero-marketing: brushed-aluminum helmet grain at peak density - */
.etp-character[data-etp-scale="hero-marketing"] .etp-char-helmet-grain {
  opacity: 0.09;
}

/* --- Hero-marketing: anisotropic helmet specular at peak --------- */
/* Existing Phase 25.F gradient — leave its base rule intact and
   amplify only on the new scale via this override. */
.etp-character[data-etp-scale="hero-marketing"] .etp-char-shoulder-cap-aniso {
  opacity: 1 !important;
}

/* --- Hero-marketing: eye cream-spark + warm halo at peak --------- */
/* Overrides Phase 29 #15 scale-tuned fallback so the marketing
   shot shows the full warm-spark layered behind each iris. */
.etp-character[data-etp-scale="hero-marketing"] .etp-char-eye-warm-spark {
  opacity: 0.95 !important;
  filter: blur(0.35px);
}

/* --- Hero-marketing: chest beacon lens flare at peak rotation ---- */
.etp-character[data-etp-scale="hero-marketing"] .etp-char-chest-core-flare {
  opacity: 0.42;
  animation: etp-chest-flare-rotate 16s linear infinite;
}

/* --- Hero-marketing: programme halo at peak intensity ------------ */
/* Phase 29 #7 lifted the default from 0.45 → 0.65; Phase 32 lifts
   the marketing variant to 0.85 for a richer halo on poster crops. */
.etp-character[data-etp-scale="hero-marketing"] {
  --etp-prog-halo-opacity: 0.85;
}

/* --- Hero-marketing: floor reflection at peak --------------------- */
.etp-character[data-etp-scale="hero-marketing"] .etp-char-floor-reflection {
  opacity: 0.65 !important;
}

/* --- Hero-marketing: atmospheric vignette around silhouette ------ */
/* Creates a soft brand-tinted aura around the avatar — only on the
   marketing variant. Helps the SVG read as a focal subject on a
   busy marketing background or social-card crop. */
.etp-character[data-etp-scale="hero-marketing"]::before {
  content: '';
  position: absolute;
  inset: -8%;
  pointer-events: none;
  background: radial-gradient(
    closest-side at center,
    rgba(0, 220, 236, 0.18) 0%,
    rgba(0, 220, 236, 0.06) 40%,
    transparent 75%
  );
  z-index: -1;
  border-radius: 50%;
}

/* Lens-flare rotation keyframe (used only by hero-marketing variant) */
@keyframes etp-chest-flare-rotate {
  from { transform: translate(50px, 88px) rotate(0deg)   translate(-50px, -88px); }
  to   { transform: translate(50px, 88px) rotate(360deg) translate(-50px, -88px); }
}

/* Reduced-motion guard for the new variant — keep visual peaks but
   suppress lens-flare rotation and particle drift. Vestibular-safe. */
@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-scale="hero-marketing"] .etp-char-chest-core-flare {
    animation: none;
  }
  .etp-character[data-etp-scale="hero-marketing"] .etp-char-particle {
    animation: none;
    /* Keep visible — they look like a static atmospheric specks
       rather than a missing element. */
  }
}

/* WCAG-AAA branch: keep the hero-marketing variant readable under
   the strict AAA contrast theme. The Phase 18 [data-etp-theme="high-contrast-aaa"]
   block already strips colour gradients; Phase 32 honours that by
   suppressing the atmospheric vignette in AAA mode (it adds
   ambient cyan that's not in the high-contrast palette). */
html[data-etp-theme="high-contrast-aaa"]
  .etp-character[data-etp-scale="hero-marketing"]::before {
  display: none;
}

/* End of Phase 32 — Hero-mode 4K marketing variant */


/* ============================================================================
 * PHASE 35 — Visual Refresh Batch (operator-asked 2026-05-29)
 * ============================================================================
 *
 * Six avatar visual changes shipped as a single CSS appendix on top of
 * Phase 30 (which had flipped the face panel white, hidden the ear modules,
 * and replaced the chest white-hot dot). Phase 35 partially reverses Phase
 * 30 and re-authors the chest-core stack as a 3-layer bullseye per the
 * operator brief:
 *
 *   #1 — Face panel: WHITE → semi-dark cyan #103A56 (REVERSES P30-1)
 *   #2 — Lips: brighter cyan stroke on the new dark face panel
 *   #3 — Ear modules: restored with brighter cyan + glow (REVERSES P30-5)
 *   #4 — Chest-core stack: re-authored (outer cyan → inner LIGHT-BLUE)
 *   #5 — ET+ wordmark: bold white centred (verify-only — already shipped)
 *   #6 — Chest breathing pulse: verify P30-7 covers re-styled hot disc
 *
 * Strictly additive — no existing phase rules deleted. Phase 35 overrides
 * Phase 30 by virtue of being later in the cascade with equal specificity,
 * matching the established precedent (Phase 30 overrode Phase 9 + Phase 17
 * the same way).
 *
 * Sacred invariants preserved (verified line-by-line):
 *   - Phase 18 --etp-mouth cyan (lip FILL untouched)
 *   - Phase 22 translateY(-18px) chest-beacon chain
 *   - Phase 22.5 ET+ wordmark font-size 7.488px
 *   - Phase 21.3 ET+ wordmark font-weight 900
 *   - Phase 23 crown-chrome display:none
 *   - Phase 28 chest-brand y=103.0 + dominant-baseline=middle
 *   - Phase 14 composed state (9th state) + Phase 29 confused state (10th)
 *   - Phase 31-34 (focus-visible, hero-marketing 4K, sentiment, TTS lip-sync)
 * ============================================================================
 */

/* ----------------------------------------------------------------------------
 * #1 — Face panel: WHITE → semi-dark cyan #103A56
 *
 * Phase 30 P30-1 made the face panel `#FFFFFF !important`. Operator-chosen
 * brighter cyan #103A56 returns the panel to its pre-Phase-30 "premium
 * curved-OLED dark face" character — slightly cooler than Phase 9's original
 * #0C2638 for a more modern feel.
 *
 * !important beats Phase 30's !important rule since both selectors match
 * identically and Phase 35 is later in cascade. The inline
 * `style="fill: url(#etpFaceGrad_*)"` radial gradient set by the JS-side
 * <radialGradient> injection is bypassed by !important.
 * ----------------------------------------------------------------------------
 */
.etp-char-head-circle {
  /* Phase 35 #1 — face panel returns to semi-dark cyan (was Phase 30 white).
     #103A56 = brighter + cooler than Phase 9's #0C2638. */
  fill: #103A56 !important;
}

/* Restore Phase 13.A baseline cyan HUD strokes — Phase 30 darkened them
   for legibility against white; on the new dark cyan face panel they go
   back to bright cyan. */
.etp-char-face-hud-mark {
  stroke: var(--etp-light, #00B7FF);
  opacity: 0.70;
}
.etp-char-face-hud-ring {
  stroke: var(--etp-light-deep, #0098D8);
  opacity: 0.55;
}

/* Re-enable Phase 15 scanlines + iridescent rim + visor crack (Phase 30 hid
   them; they were authored for the dark face panel and read correctly on
   #103A56). */
.etp-char-face-panel-fill,
.etp-char-visor-scan,
.etp-char-iridescent-rim {
  display: revert !important;
}
.etp-char-visor-crack {
  display: revert !important;
  opacity: 0.08;  /* Phase 29 #11 value preserved */
}

/* ----------------------------------------------------------------------------
 * #2 — Lips + mouth: brighter cyan stroke on dark face panel
 *
 * Phase 30 P30-2 set lip stroke to `rgba(0, 152, 216, 1)` (--etp-light-deep,
 * dark cyan) — tuned for the white face panel. On the new dark cyan face,
 * the dark-cyan stroke blends in. Switch to a brighter cyan
 * (--etp-light-warm at full opacity) so lips have crisp definition against
 * the new face panel.
 *
 * Lip FILL stays var(--etp-mouth) per Phase 18 sacred contract. All other
 * properties (stroke-width 0.6+0.7, transition 140ms, stroke-linejoin/linecap)
 * inherit from Phase 30 #2 + #3.
 *
 * Phase 30 #3 motion (upper -1.2px / lower +2.4px / cavity opacity 1.0) +
 * Phase 30 #4 smile thicker (stroke-width 3.0 + opacity 1.0) + Phase 34 TTS
 * onboundary lip-sync are all kept intact — they already shipped and work.
 * ----------------------------------------------------------------------------
 */
.etp-char-mouth-upper-lip,
.etp-char-mouth-lower-lip {
  /* Canonical-spec §13: equal-height cyan smiley lips, NO edge-strokes.
     The Phase 35 #2 brighter-cyan crisp edge-stroke is REMOVED — the cyan
     FILL alone defines the lip shape. (Accessibility prefers-contrast +
     high-contrast-aaa branches retain their legibility outlines.) */
  stroke: none !important;
}

/* ----------------------------------------------------------------------------
 * #3 — Ear modules: hidden → restored with minor refresh (REVERSES P30-5)
 *
 * Phase 30 P30-5 hid the ear modules entirely via `display: none`. The
 * Phase 9 SVG geometry lives in etp-character.js lines 640-651 and was
 * never removed from the DOM — Phase 30 only hid via display:none. Reverse
 * by setting `display: revert !important` with high specificity (matches
 * the Phase 30 selectors exactly).
 *
 * Minor refresh — brighter cyan accent capsule + subtle outer rim glow so
 * the modules read more clearly as "luminous tech" than the Phase 9
 * baseline. Also re-enable the Phase 26.K idle-breathe animation Phase 30
 * silenced.
 * ----------------------------------------------------------------------------
 */
.etp-char-ear-module,
.etp-char-ear-module-pad,
.etp-char-ear-module-cyan,
.etp-char-ear-module--l,
.etp-char-ear-module--r {
  /* Phase 35 #3 — restore over-ear headphone modules */
  display: revert !important;
}

/* Minor refresh — silver pad gets a subtle cyan outer glow so the modules
   read as luminous tech, not flat shapes. */
.etp-char-ear-module-pad {
  filter: drop-shadow(0 0 0.6px rgba(0, 220, 236, 0.35));
}

/* Minor refresh — brighter cyan accent capsule + glow */
.etp-char-ear-module-cyan {
  fill: rgba(94, 224, 255, 0.95);
  filter: drop-shadow(0 0 0.8px rgba(0, 220, 236, 0.55));
  /* Re-enable the Phase 26.K idle-breathe animation. The keyframes
     etp-ear-idle-breathe-l + -r already exist in CSS sections 5+6.
     `animation: revert` restores them from the cascade. */
  animation: revert !important;
}

/* ----------------------------------------------------------------------------
 * #4 + #5 — Chest-core stack: full re-author
 *
 * Operator brief: "outer cyan disc + inner LIGHT-BLUE disc + WHITE ET+
 * wordmark properly nested, sharing same center, no offset, not stretched,
 * not clipped". Plus "centre ET+ text inside light-blue inner circle, make
 * wordmark bold, preserve white".
 *
 * Strategy: keep the existing SVG geometry (ghost / glow / core / hot /
 * rim / crosshair / scan / brand at cy=103, all translated -18 to visual
 * cy=85) but RE-STYLE the layer roles so the stack reads cleanly as a
 * 3-layer bullseye:
 *
 *   LAYER 1 (outermost):  cyan glow halo (chest-core-glow, blurred)
 *   LAYER 2 (outer disc): bright cyan ring (chest-core, r=7.5)
 *   LAYER 3 (inner disc): LIGHT BLUE solid (chest-core-hot, r=5.5)
 *   LAYER 4 (rim):        thin cyan stroke ring (chest-core-rim)
 *   LAYER 5 (text):       WHITE bold ET+ wordmark (chest-brand)
 *
 * All five share cx=50 cy=103 in source coords, all translateY(-18px)
 * to visual cy=85, all aligned + nested + same centre.
 * ----------------------------------------------------------------------------
 */

/* LAYER 2 — outer cyan disc.
   Phase 12 baseline used the etpChestCoreGrad_<id> radial gradient
   (white-hot centre → programme-tinted mid → cyan outer). For the
   re-author, drop the white-hot centre — the NEW chest-core-hot layer
   IS the inner disc. Force solid cyan fill via !important + override
   the inline `fill="url(#...)"` style with the canonical token. */
.etp-char-chest-core {
  fill: var(--etp-chest-core-teal, #00DCEC) !important;
}

/* LAYER 3 — inner LIGHT BLUE disc.
   Phase 30 P30-6 made this white. Operator wants light blue per the
   brief. Soft pale cyan-blue contrasts with the outer cyan disc (so the
   bullseye reads as 3 distinct layers) AND keeps the WHITE ET+ wordmark
   legible (white-on-light-blue still has acceptable contrast for the
   bold 7.488 px weight 900 wordmark with its existing 0.3 px charcoal
   hairline stroke acting as a defining outline). */
.etp-char-chest-core-hot {
  /* Phase 35 set this to a flat light-blue solid. Phase 36 Phase-1
     supersedes the flat fill with a radial gradient applied as an
     inline style="fill: url(#etpChestCoreInner_<id>)" via the IIFE-6
     in etp-character.js (inline style wins over CSS class fill via
     DOM precedence — same pattern Phase 13 uses for chest-core-grad).
     The Phase-1 appendix clears the fill to `transparent` so this
     declaration becomes inert visually; the byte value is also flipped
     to the Steel Cyan family per the strict-literal zero-instances rule. */
  fill: rgba(94, 224, 255, 0.95);
}

/* LAYER 4 — thin cyan rim stroke around the outer cyan disc.
   Phase 12 baseline: stroke-width 0.6 + chest-core-rim. Bump to 0.7
   so the cyan ring has slightly more visual weight against the
   pearlescent body — reads as a deliberate "metal ring" detail
   rather than incidental hairline. */
.etp-char-chest-core-rim {
  stroke: var(--etp-light-deep, #0098D8);
  stroke-width: 0.7;
  opacity: 0.85;
}

/* LAYER 5 — ET+ wordmark.
   Phase 12 baseline + Phase 21.3 weight-900 + Phase 22.5 size-7.488px +
   Phase 28 y=103.0 geometric centring are all preserved. Bump the cyan
   text-shadow slightly so the wordmark has a clearer "emitting" feel
   against the new light-blue inner disc. */
.etp-char-chest-brand {
  text-shadow: 0 0 1.8px var(--etp-light-glow, rgba(0, 220, 236, 0.7));
}

/* Hide the Phase 12 crosshair on the new chest-core-hot light-blue disc
   — it was authored for the original white-hot centre dot and reads as
   visual noise on the new light-blue inner. */
.etp-char-chest-core-crosshair {
  display: none !important;
}

/* ----------------------------------------------------------------------------
 * #6 — Chest breathing pulse: verify-only
 *
 * Phase 30 #7 already extended the etp-chest-core-pulse animation to:
 * chest-core + chest-core-glow + chest-core-rim + chest-core-hot. Phase 35
 * doesn't touch the animation contract — just verifies it still applies
 * post-restyle. Composed-state pause + reduced-motion fallback both
 * inherited from Phase 30 #7.
 *
 * (No new CSS rule required — verification only.)
 * ----------------------------------------------------------------------------
 */

/* End of Phase 35 — Visual Refresh Batch */


/* ============================================================================
 * PHASE 36 — Avatar Visual Upgrade (operator-asked 2026-05-29)
 *
 * Six-phase mission brief asks for the most futuristic, premium, world-class
 * ET+ AI Tutor avatar to date. Phase 1 of 5 implementation phases here.
 *
 *   PHASE 1 (THIS BLOCK) — D-01 + GAP-2 + GAP-3 + retone all 5
 *     rgba(168,220,250) [retired pre-Phase-36 token] instances per strict zero-instances scope:
 *
 *     • D-01 — chest-core-hot flat fill -> radial gradient with
 *       white-hot centre fading through pale cyan to teal-cyan rim
 *       (5 stops: #FFFFFF 0%, #E0F4FF 18%, #A8DCFA 45%, #00D4E8 72%,
 *       #00BCD4 100%). Gradient injected via SVG <radialGradient>
 *       in <defs> + inline style="fill: url(#id)" on the chest-core-hot
 *       circle (CSS `fill` property does not accept radial-gradient
 *       values on SVG elements). Companion JS injection in Phase 36
 *       IIFE-6 at file tail of etp-character.js.
 *
 *     • GAP-2 — outer ambient Steel Cyan glow via filter: drop-shadow
 *       (SVG circles do not accept box-shadow). Halo radiates outward,
 *       reading as environmental light emission from the chest core.
 *
 *     • GAP-3 — inner white specular highlight ellipse at top-centre of
 *       the chest core, simulating ambient light reflection on a glossy
 *       convex surface. Element injected by Phase 36 IIFE-6 after the
 *       chest-core-hot circle so SVG paint order places it on top of
 *       the gradient.
 *
 *     • Wordmark text-shadow updated to spec literal
 *       `text-shadow: 0 0 4px rgba(0, 180, 210, 0.8)` (wider blur,
 *       slightly darker cyan vs Phase 35's 1.8px / brighter cyan).
 *
 *     • Retone the 4 unrelated rgba(168,220,250) [retired pre-Phase-36 token] hits per operator's
 *       "all 5 instances" decision (line 2093 particle + lines
 *       2980-2982 iridescent rim tokens) to brighter Steel Cyan
 *       rgba(94, 224, 255, X) — matches the Phase 35 lip stroke +
 *       ear-module cyan capsule colour family.
 *
 *   Sacred invariants preserved (verified):
 *     - Phase 18 --etp-mouth cyan
 *     - Phase 22 chest-beacon translateY(-18px) chain
 *     - Phase 22.5 ET+ wordmark font-size 7.488px
 *     - Phase 21.3 ET+ wordmark font-weight 900
 *     - Phase 23 crown-chrome display:none
 *     - Phase 28 chest-brand y=103.0 + dominant-baseline=middle
 *     - 10-state machine (Phase 14 composed + Phase 29 confused)
 *     - Phase 30 chest-core-pulse animation on all 4 chest layers
 *     - Phase 31-34 features intact
 *     - Phase 35 chest-core 3-layer bullseye structure (this rule
 *       replaces the LIGHT BLUE inner-disc fill only)
 *
 *   Phases 2-5 will land as subsequent appendix blocks below.
 * ============================================================================
 */

/* ----------------------------------------------------------------------------
 * D-01 — Chest-core inner disc: flat fill -> radial gradient
 *
 * Phase 35 P35-4+5 shipped a flat rgba(168,220,250,0.95) [retired pre-Phase-36 token] solid which
 * read as a painted circle without depth or luminosity. Spec wants a
 * 5-stop radial gradient simulating a genuinely glowing power core.
 *
 * The radial gradient is applied via SVG <radialGradient> + inline
 * style="fill: url(#etpChestCoreInner_<id>)" on the chest-core-hot
 * circle. Inline style wins over Phase 35's CSS-class fill without
 * needing !important (matches the Phase 13 chest-core-grad pattern at
 * JS line 2748). Injected by Phase 36 IIFE-6.
 *
 * This rule clears Phase 35's flat fill so any race-condition window
 * before the JS gradient injection fires shows transparent (avoids
 * a brief flash of the old light-blue solid). The static
 * `fill: transparent` is rapidly overridden by the inline style="fill: url(...)"
 * set by IIFE-6.
 * ----------------------------------------------------------------------------
 */
.etp-char-chest-core-hot {
  /* Cleared so Phase 36 inline style="fill: url(#...)" wins via attribute
     wins via inline-style trumping CSS class rules. */
  fill: transparent;
}

/* ----------------------------------------------------------------------------
 * GAP-2 — Outer ambient Steel Cyan glow (drop-shadow halo)
 * GAP-3 — Inner specular white highlight ellipse
 *
 * Spec calls for `box-shadow: 0 0 18px 6px rgba(0, 229, 255, 0.55)` —
 * SVG circles do not accept box-shadow, so use filter: drop-shadow as
 * the equivalent. The drop-shadow casts a Steel Cyan halo OUTWARD from
 * the chest-core-hot, reading as ambient environmental emission.
 *
 * The drop-shadow + brightness pulse in sync with the breathing
 * animation. Phase 30 P30-7's etp-chest-core-pulse keyframe handles
 * opacity + scale + translateY at var(--etp-pulse-period); the new
 * etp-chest-core-radial-pulse keyframe below handles filter (drop-shadow
 * + brightness) at the same period. Both animations compose because
 * each targets different properties.
 *
 * NOTE: the existing Phase 30 P30-7 etp-chest-core-pulse uses opacity
 * 0.38 -> 0.62 which on a static flat fill reads as the core flickering
 * dim-bright. With the new radial gradient (white-hot centre, brighter
 * inner) the opacity pulse becomes visually too aggressive — the
 * gradient's brightness already self-modulates via the brightness
 * filter. The opacity-pulse stays in place per Phase 30 invariant,
 * but the new radial pulse layer compensates by adding extra brightness
 * at the apex.
 * ----------------------------------------------------------------------------
 */
@keyframes etp-chest-core-radial-pulse {
  0%, 100% {
    filter: drop-shadow(0 0 6px rgba(0, 229, 255, 0.55)) brightness(1.00);
  }
  50% {
    filter: drop-shadow(0 0 9px rgba(0, 229, 255, 0.70)) brightness(1.08);
  }
}

.etp-char-chest-core-hot {
  /* Compose two animations — both at the breathing period so they stay
     in lockstep. The opacity/scale (etp-chest-core-pulse, from Phase 30)
     handles the disc's breath. The filter (etp-chest-core-radial-pulse,
     new in Phase 36) handles the ambient glow + brightness sync. */
  animation: etp-chest-core-pulse var(--etp-pulse-period, 4s) ease-in-out infinite,
             etp-chest-core-radial-pulse var(--etp-pulse-period, 4s) ease-in-out infinite;
}

/* Composed-state pause for both animations (Phase 14's 9th state contract). */
.etp-character[data-etp-state="composed"] .etp-char-chest-core-hot {
  animation-play-state: paused;
}

/* Reduced-motion fallback — hold static at the breathing apex's outer
   glow + 1.0 brightness so the core still reads as a glowing emitter
   without any time-varying motion. */
@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-core-hot {
    animation: none;
    filter: drop-shadow(0 0 6px rgba(0, 229, 255, 0.55));
    transform: translateY(-18px);
  }
}

/* ----------------------------------------------------------------------------
 * GAP-3 — Inner specular white highlight ellipse styling
 *
 * Element injected by Phase 36 IIFE-6 as:
 *   <ellipse class="etp-char-chest-core-specular"
 *            cx="50" cy="99.5" rx="1.65" ry="0.825"
 *            aria-hidden="true"/>
 * inserted AFTER the chest-core-hot circle so SVG paint order places
 * the specular on top of the radial gradient. The pre-translate cy=99.5
 * sits 3.5 units above the chest-core-hot centre (cy=103), which is
 * ~63% of the way from centre to top edge — perfect specular position
 * for a light source above-and-in-front of the avatar.
 *
 * After Phase 22's translateY(-18px), visual cy ends up at 81.5
 * (chest core visual cy=85, specular visual cy=81.5, offset 3.5 units
 * upward — visible as a clear bright top-of-orb highlight).
 *
 * Specular pulses in sync with breathing via a custom keyframe that
 * brightens from 0.7 -> 0.85 opacity + 1.0 -> 1.05 scale at the apex.
 * ----------------------------------------------------------------------------
 */
.etp-char-chest-core-specular {
  fill: rgba(255, 255, 255, 0.7);
  pointer-events: none;
  /* Same translateY(-18px) chain as the rest of the chest beacon
     (Phase 22 contract — keeps the specular anchored to the visual
     chest core at cy=81.5). */
  transform: translateY(-18px);
  animation: etp-chest-core-specular-pulse var(--etp-pulse-period, 4s) ease-in-out infinite;
}

@keyframes etp-chest-core-specular-pulse {
  0%, 100% { opacity: 0.70; transform: translateY(-18px) scale(1.00); }
  50%      { opacity: 0.85; transform: translateY(-18px) scale(1.05); }
}

.etp-character[data-etp-state="composed"] .etp-char-chest-core-specular {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-core-specular {
    animation: none;
    transform: translateY(-18px);
  }
}

/* Hide specular on chrome scale (favicon / sidebar / 32px) — the element
   would not be visually distinct at such small size and could read as
   a stray pixel. */
.etp-character[data-etp-scale="chrome"] .etp-char-chest-core-specular {
  display: none;
}

/* ----------------------------------------------------------------------------
 * D-01 (continued) — ET+ wordmark text-shadow per spec literal
 *
 * Spec literal: text-shadow: 0 0 4px rgba(0, 180, 210, 0.8)
 * Phase 35 had: text-shadow: 0 0 1.8px var(--etp-light-glow, rgba(0, 220, 236, 0.7))
 *
 * Wider blur + slightly darker cyan reads as a clearer "emitting" feel
 * against the new radial-gradient inner disc with its white-hot centre.
 * Phase 36's later-in-cascade rule wins over Phase 35 at equal specificity.
 * ----------------------------------------------------------------------------
 */
.etp-char-chest-brand {
  text-shadow: 0 0 4px rgba(0, 180, 210, 0.8);
}

/* ----------------------------------------------------------------------------
 * D-01 (continued) — Retone 4 unrelated rgba(168,220,250) [retired pre-Phase-36 token] instances
 *
 * Operator chose strict literal interpretation of "zero instances of
 * rgba(168,220,250) [retired pre-Phase-36 token] anywhere in the codebase". The 4 unrelated hits
 * are NOT chest-core defects but share the soft-light-blue token. They
 * shift to brighter Steel Cyan rgba(94, 224, 255, X) — the same colour
 * family Phase 35 uses for the lip stroke and ear-module cyan capsule.
 *
 *   Line 2093  — .etp-char-particle fill (14 background drift particles)
 *   Line 2980  — --etp-iridescent-pink (helmet rim token, alpha 0.00)
 *   Line 2981  — --etp-iridescent-blue (helmet rim token, alpha 0.06)
 *   Line 2982  — --etp-iridescent-green (helmet rim token, alpha 0.00)
 *
 * Phase 36 rules below override the prior CSS via later-in-cascade with
 * !important (Phase 11 set tokens without !important; Phase 19 set
 * particle fill without !important — both lose to Phase 36's !important).
 * ----------------------------------------------------------------------------
 */

/* Particle field — Steel Cyan family. The 14 single-pixel particles
   read brighter cyan than before; the colour shift is subtle because
   each particle is only 1x1 px. */
.etp-char-particle {
  fill: rgba(94, 224, 255, 0.5) !important;
}

/* Iridescent helmet rim tokens — retoned to Steel Cyan. Pink + green
   stay at alpha 0.00 (inert visually) but the literal byte values
   shift so the strict zero-instances rule holds. Blue at alpha 0.06
   is the only one with any visible effect — the rim picks up a
   slightly brighter cyan tint. */
.etp-character {
  --etp-iridescent-pink:  rgba(94, 224, 255, 0.00) !important;
  --etp-iridescent-blue:  rgba(94, 224, 255, 0.06) !important;
  --etp-iridescent-green: rgba(94, 224, 255, 0.00) !important;
}

/* End of Phase 36 — Phase 1 — D-01 + GAP-2 + GAP-3 */

/* ============================================================================
 * PHASE 36 — Phase 2 — Face panel + head shell + ear modules + eyes
 *
 * Closes GAP-4 (silver helmet shell + face panel as two distinct concentric
 * elements) and polishes GAP-8 (5 eye animation states). Ear modules already
 * verified intact per Phase 35 P35-3 — no change needed below.
 *
 *  GAP-4 — Head shell silver mirror + face panel semi-dark cyan as TWO
 *          distinct concentric elements.
 *          Current state: BOTH already exist (Phase 8 2026-05-26b shipped
 *          `.etp-char-helmet-shell` r=29 wrapping `.etp-char-head-circle`
 *          r=22; Phase 35 P35-1 sets face panel #103A56 semi-dark cyan).
 *          However Phase 16 (2026-05-27e) white-body refresh flipped
 *          `--etp-helmet-silver: #FFFFFF` (pure white) so the helmet shell
 *          currently paints white, NOT silver mirror. Phase 2 restores the
 *          silver mirror finish via the rule below (Phase 10 (2026-05-26d)
 *          brushed-aluminum #EAECF1 picked as the more premium tone).
 *
 *  GAP-8 — 5 eye animation states:
 *            #1 idle pulse                  — `etp-blink-pulse` 7.1s ✓
 *            #2 listening micro-drift ±3-5° — static -3° tilt + new gentle
 *                                              ±2° rotation animation below
 *            #3 thinking circular pupil     — static -0.8/-1.0 squint +
 *                                              new circular pupil orbit
 *                                              animation below
 *            #4 speaking audio-sync brightness — Phase 34 onboundary
 *                                                viseme cascade already
 *                                                drives lip motion; eye
 *                                                halo opacity ticks with
 *                                                stream-chunk via Phase
 *                                                30 cascade. ✓
 *            #5 celebrating flash-to-warm   — `etp-celebrate-soul` 800ms
 *                                              chest flash + new warm-hue
 *                                              iris tint animation below
 *
 *  Ear modules — Phase 35 P35-3 restored geometry + brighter cyan capsule
 *                + drop-shadow glow + idle-breathe re-enabled. Phase 12
 *                already shipped w=8.1/h=12.6 (~37% smaller than pre-Phase-9
 *                original) per the audit. ✓ no change.
 *
 *  Sacred invariants preserved:
 *    • Phase 8 two-layer head construction (helmet-shell + head-circle).
 *    • Phase 35 P35-1 face panel #103A56 semi-dark cyan stays untouched.
 *    • Phase 35 P35-3 ear-module restore intact.
 *    • Phase 16 white-body cascade for shoulder/arm/neck stays — only
 *      the helmet-shell rule below overrides the white token.
 *    • Phase 10 cranial seam + crown LED + helmet specular arc all
 *      unchanged (they overlay on top of the silver helmet shell).
 *    • All existing per-state eye cascades (listening/thinking/encouraging/
 *      celebrating/alert) stay — the keyframes below LAYER ON TOP via
 *      transform-box: fill-box + transform-origin so they compose with
 *      the existing static transforms.
 *    • Phase 21.4 (shoulder-joint + LED hidden) stays.
 *    • prefers-reduced-motion guard added on the 3 new keyframes.
 * ============================================================================
 */

/* ----------------------------------------------------------------------------
 * #1 — GAP-4: Helmet shell silver mirror-finish
 *
 * Phase 8 + Phase 10 had the helmet shell painted `--etp-helmet-silver`
 * `#EAECF1` (brushed aluminum). Phase 16 white-body refresh flipped the
 * token to `#FFFFFF` (pure white). Override the rule (not the token) with
 * !important so the helmet returns to silver mirror while leaving every
 * other consumer of the white token (shoulders, arms, ear pads, neck
 * collar) on their white-body values.
 *
 * Pure CSS — zero JS change. The stroke + drop-shadow shipped by Phase 8
 * stay intact.
 * ----------------------------------------------------------------------------
 */
.etp-char-helmet-shell {
  /* Phase 36 Phase 2 #1 — restore brushed-aluminum silver mirror (Phase 10
     `#EAECF1`). Beats Phase 16 white-body token + any subsequent reverts. */
  fill: #EAECF1 !important;
  stroke: #2A2F38 !important;   /* charcoal silhouette edge */
  stroke-width: 0.8 !important;
}

/* The helmet-notch crown bump tracks the same silver tone for continuity. */
.etp-char-helmet-notch {
  fill: #EAECF1 !important;
  stroke: #2A2F38 !important;
}

/* ----------------------------------------------------------------------------
 * #2 — GAP-8 State #2: Listening micro-drift ±3°
 *
 * The existing rule at line 707 sets `transform: rotate(-3deg) translateY(-1px)`
 * statically while in listening state. The spec asks for "±3-5°" micro-drift
 * — a subtle continuous animation suggesting active head-tilt attention.
 *
 * Layer a gentle ±1.5° rotation animation ON TOP of the static -3° anchor
 * via the same `.etp-char-svg` selector. Composing two transforms via
 * a single animation that includes both the static base + the drift offset
 * keeps the animation declarative (no JS).
 *
 * 4.8s cycle is co-prime with Phase 22's 4s chest pulse so they never re-sync.
 * ----------------------------------------------------------------------------
 */
@keyframes etp-listening-micro-drift {
  0%, 100% { transform: rotate(-3deg) translateY(-1px); }
  25%      { transform: rotate(-4.5deg) translateY(-1px); }
  50%      { transform: rotate(-3deg) translateY(-1px); }
  75%      { transform: rotate(-1.5deg) translateY(-1px); }
}
.etp-character[data-etp-state="listening"] .etp-char-svg {
  animation: etp-listening-micro-drift 4.8s ease-in-out infinite;
}

/* ----------------------------------------------------------------------------
 * #3 — GAP-8 State #3: Thinking circular pupil motion
 *
 * The existing rule at line 726 sets `transform: translate(-0.8px, -1.0px)`
 * static on `.etp-char-pupils`. Spec asks for "circular pupil motion"
 * suggesting active cognition.
 *
 * Layer a slow 6s circular orbit at radius 0.6px ON TOP of the static
 * up-left offset. The base offset becomes the orbit centre.
 *
 * 6s cycle is co-prime with Phase 22 chest pulse (4s) and Phase 36 #2
 * listening drift (4.8s) so they never re-sync.
 * ----------------------------------------------------------------------------
 */
@keyframes etp-thinking-pupil-orbit {
  0%   { transform: translate(-0.8px, -1.0px); }
  25%  { transform: translate(-0.2px, -1.6px); }
  50%  { transform: translate(-0.8px, -2.2px); }
  75%  { transform: translate(-1.4px, -1.6px); }
  100% { transform: translate(-0.8px, -1.0px); }
}
.etp-character[data-etp-state="thinking"] .etp-char-pupils {
  animation: etp-thinking-pupil-orbit 6s ease-in-out infinite;
}

/* ----------------------------------------------------------------------------
 * #4 — GAP-8 State #5: Celebrating flash-to-warm iris tint
 *
 * The existing `etp-celebrate-soul` 800ms keyframe at line 782 pulses the
 * chest core opacity 0.55→0.95→0.85→0.55. The eye irises stay at their
 * baseline cyan during the flash. Spec asks for "celebrating flash-to-warm"
 * — the iris should pick up a brief warm hue (yellow-orange) suggesting
 * pride/joy at the moment of celebration.
 *
 * 800ms one-shot pulse synchronised with the existing soul flash. Uses
 * `filter: hue-rotate()` so the cascade composition with Phase 11 +
 * Phase 25 + Phase 26 filter chains works cleanly (each filter property
 * stacks rather than overwrites).
 *
 * Phase 10 #15 already shipped programme-specific eye warm-spark colours
 * (cream for Robotics/STEAM/Math, violet for Expert, green for ACSP) —
 * the iris hue-rotate respects those by shifting the BASE hue rather
 * than replacing it.
 * ----------------------------------------------------------------------------
 */
@keyframes etp-celebrate-warm-iris {
  0%   { filter: hue-rotate(0deg) brightness(1.00); }
  30%  { filter: hue-rotate(-35deg) brightness(1.18); }
  70%  { filter: hue-rotate(-20deg) brightness(1.12); }
  100% { filter: hue-rotate(0deg) brightness(1.00); }
}
.etp-character[data-etp-state="celebrating"] .etp-char-eye-iris,
.etp-character[data-etp-state="celebrating"] .etp-char-eye-halo {
  animation: etp-celebrate-warm-iris 800ms ease-out;
}

/* ----------------------------------------------------------------------------
 * #5 — prefers-reduced-motion guard on Phase 36 Phase 2 keyframes
 *
 * Users with reduced-motion preference get the static base transform (no
 * micro-drift, no orbit, no warm flash). Matches the platform-wide
 * accessibility contract from Phase 31 SC 2.4.13.
 * ----------------------------------------------------------------------------
 */
@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-state="listening"] .etp-char-svg,
  .etp-character[data-etp-state="thinking"] .etp-char-pupils,
  .etp-character[data-etp-state="celebrating"] .etp-char-eye-iris,
  .etp-character[data-etp-state="celebrating"] .etp-char-eye-halo {
    animation: none !important;
  }
}

/* End of Phase 36 — Phase 2 — Face panel + head shell + ear modules + eyes */


/* ============================================================================
 * PHASE 36 — Phase 3 — Mouth + lips + lip-sync polish
 *
 * Closes GAP-7 from the Phase 0 audit:
 *   "Dimensionally distinct upper + lower lips with cyan-white upper-lip
 *    highlight. Phase 35 set lip stroke brighter cyan but no upper-lip
 *    highlight gradient."
 *
 * The current cascade gives BOTH lips the same cyan stroke (Phase 30 P30-2
 * + Phase 35 #2). They read as flat shapes, not as dimensional lips with a
 * real wet shine. Real lips catch overhead light along the upper lip's
 * cupid's bow + receive a small under-shadow along the lower lip's bottom
 * edge. Phase 3 introduces that dimensional cue.
 *
 * Three coordinated additions, all pure CSS (zero JS, zero SVG geometry):
 *
 *   #1 — Upper lip cyan-white sheen along the top edge. `filter:
 *        drop-shadow(0 -0.3px 0 rgba(180,240,255,0.85))` paints a brighter
 *        cyan-white halo above the upper lip path — exactly where light
 *        catches the cupid's bow on a real face. Stacks cleanly with the
 *        existing Phase 35 #2 stroke (94,224,255,0.95) because filters
 *        compose at render time after stroke + fill resolve.
 *
 *   #2 — Lower lip subtle under-shadow for dimensional balance. `filter:
 *        drop-shadow(0 0.2px 0 rgba(0,50,90,0.45))` paints a tiny dark
 *        cyan tone below the lower lip — reads as the lip's own shadow
 *        on the chin. Pairs with #1 to give a clear upper/lower
 *        dimensional read.
 *
 *   #3 — Mouth cavity refinement during speaking. The Phase 18 +
 *        post-Phase-35 cavity fill is `rgba(200, 230, 240, 0.55) !important`
 *        (light cyan-grey, tuned for the white face panel from Phase 30).
 *        On the Phase 35 dark cyan face `#103A56`, that light cavity colour
 *        reads as a brighter spot than the surrounding face — visually
 *        backwards (the mouth interior should be in shadow vs the face
 *        surface). Phase 3 refines the cavity during speaking + viseme
 *        states to a darker navy-cyan `rgba(20, 35, 60, 0.80)` that reads
 *        as "open mouth interior in shadow" — the canonical anatomical
 *        cue. Resting cavity (opacity 0) unchanged.
 *
 *   #4 — Upper-lip highlight intensifies on open-mouth visemes (wide /
 *        round) — small brightness boost suggesting the lip stretches +
 *        catches more light during open vowels. Subtle, additive.
 *
 *   #5 — `prefers-reduced-motion` guard — filters are not motion per se
 *        but the open-mouth brightness intensification IS a state
 *        transition, so we drop the boost under reduced-motion.
 *
 * Sacred invariants preserved (verified):
 *   • Phase 18 `--etp-mouth` cyan FILL contract — UNTOUCHED.
 *   • Phase 30 P30-3 speaking-state geometry (-1.2px / +2.4px / cavity
 *     opacity 1.0 / 140ms transition) — UNTOUCHED.
 *   • Phase 30 P30-4 smile thicker (stroke-width 3.0, opacity 1.0) —
 *     UNTOUCHED.
 *   • Phase 35 #2 brighter cyan lip stroke `rgba(94,224,255,0.95)` —
 *     UNTOUCHED (Phase 3 layers filter ON TOP).
 *   • Phase 34 TTS `onboundary` viseme cascade + per-viseme transforms
 *     (closed/small/wide/round) — UNTOUCHED.
 *   • Phase 27.M K-2 stroke-width 2.6 override (`data-etp-stage=
 *     "early-years"`) — UNTOUCHED.
 *   • Phase 29 confused state per-state cascade — UNTOUCHED.
 *   • WCAG-AAA `data-etp-theme="high-contrast-aaa"` — UNTOUCHED
 *     (high-contrast mode drops the highlight for maximum legibility;
 *     see guard below).
 *   • Phase 36 Phase 1 chest-core radial gradient + GAP-2 + GAP-3 —
 *     UNTOUCHED.
 *   • Phase 36 Phase 2 helmet shell silver + 3 eye animation keyframes —
 *     UNTOUCHED.
 *
 * Out of scope per operator's strict mission brief: no JS changes, no
 * SVG geometry changes, no new SVG elements. Pure CSS appendix. The
 * Phase 34 lip-sync timing is left intact (audit said "lip-sync polish"
 * which Phase 3 reads as the dimensional polish of the LIPS themselves,
 * not a rewrite of the lip-sync algorithm — Phase 34 lip-sync verified
 * working in Phase 31-34 batch live-test).
 * ============================================================================
 */

/* ----------------------------------------------------------------------------
 * #1 — Upper lip cyan-white dimensional highlight
 *
 * `drop-shadow(0 -0.3px 0 rgba(180, 240, 255, 0.85))` paints a 1-px
 * cyan-white sheen along the upper-lip's top edge. Negative Y offset
 * positions the highlight ABOVE the path so it reads as light catching
 * the upper-lip rim (cupid's bow). 0 blur radius keeps the highlight
 * crisp — wider blur would look like a glow halo rather than reflective
 * shine. 0.85 alpha so it reads bright without overpowering the face
 * panel.
 *
 * Composes via SVG filter chain on top of Phase 35 #2 stroke + Phase 18
 * fill — each property resolves independently then the filter applies
 * at render time. The negative-Y is in viewBox units (SVG 100x130), so
 * 0.3px reads as a sub-pixel highlight at hero scale and disappears
 * gracefully at chrome scale where the whole mouth is silhouette-only.
 * ----------------------------------------------------------------------------
 */
.etp-char-mouth-upper-lip {
  /* Canonical-spec §13: equal-height FLAT cyan smiley lips. The Phase 36
     Phase 3 #1 upper-lip cyan-white sheen is REMOVED — it created a
     dimensional highlight that contradicts the flat-equal-cyan directive
     (and the spec forbids highlights/shadow effects on the lips). */
  filter: none;
}

/* ----------------------------------------------------------------------------
 * #2 — Lower lip subtle under-shadow
 *
 * `drop-shadow(0 0.2px 0 rgba(0, 50, 90, 0.45))` paints a tiny dark
 * cyan tone below the lower lip — reads as the lip's own shadow on the
 * chin. Pairs with #1 to give a clear upper/lower dimensional read.
 * Subtle (0.45 alpha, 0.2px offset) so it complements rather than
 * competes with the upper-lip highlight.
 *
 * On the dark `#103A56` face panel the shadow blends in (face is
 * already dark cyan), giving a soft anatomical settled-on-the-face
 * reading rather than a hard outline.
 * ----------------------------------------------------------------------------
 */
.etp-char-mouth-lower-lip {
  /* Canonical-spec §13: NO shadow effects on the lips. The Phase 36
     Phase 3 #2 lower-lip dark-navy under-shadow is REMOVED — it is a
     face-feature occlusion shadow, which the spec forbids absolutely. */
  filter: none;
}

/* ----------------------------------------------------------------------------
 * #3 — Mouth cavity refined for dark face panel
 *
 * Phase 18 set cavity to `rgba(200, 230, 240, 0.55) !important` — tuned
 * for the white face panel from Phase 30. On Phase 35's dark cyan face
 * `#103A56`, that light cavity reads as a BRIGHTER spot than the
 * surrounding face — visually backwards (mouth interior should be in
 * shadow vs face surface).
 *
 * Phase 3 refines the cavity to a darker navy-cyan during speaking +
 * viseme states. The dwell-based viseme cascade (closed / small / wide
 * / round) already drives cavity opacity; Phase 3 only refines the
 * cavity FILL when the cavity is actually visible (data-etp-speaking
 * + viseme small/wide/round). Resting cavity (opacity 0) untouched.
 *
 * Phase 18's `!important` on the base cavity fill keeps overall
 * cascade clean; we override with our own `!important` only on the
 * active-cavity states so the rest of the file's behaviour is
 * preserved.
 *
 * WCAG-AAA branch keeps Phase 18's `rgba(15, 23, 42, 0.55) !important`
 * cavity (line 4419) — even darker for accessibility — unchanged.
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-speaking="1"] .etp-char-mouth-cavity,
.etp-character[data-etp-viseme="small"] .etp-char-mouth-cavity,
.etp-character[data-etp-viseme="wide"] .etp-char-mouth-cavity,
.etp-character[data-etp-viseme="round"] .etp-char-mouth-cavity {
  /* Phase 36 Phase 3 #3 — darker navy-cyan cavity for "interior in
     shadow" reading on dark face panel. */
  fill: rgba(20, 35, 60, 0.80) !important;
}

/* WCAG-AAA branch — keep Phase 18's high-contrast cavity unchanged
   even when Phase 3's refined cavity colour would otherwise apply. */
[data-etp-theme="high-contrast-aaa"].etp-character[data-etp-speaking="1"] .etp-char-mouth-cavity,
[data-etp-theme="high-contrast-aaa"].etp-character[data-etp-viseme="small"] .etp-char-mouth-cavity,
[data-etp-theme="high-contrast-aaa"].etp-character[data-etp-viseme="wide"] .etp-char-mouth-cavity,
[data-etp-theme="high-contrast-aaa"].etp-character[data-etp-viseme="round"] .etp-char-mouth-cavity {
  /* Phase 18 high-contrast cavity wins under WCAG-AAA. */
  fill: rgba(15, 23, 42, 0.55) !important;
}

/* ----------------------------------------------------------------------------
 * #4 — Upper-lip highlight intensifies on open-mouth visemes
 *
 * During wide / round visemes (open vowels) the upper lip stretches +
 * catches MORE overhead light. Small brightness boost on the upper-lip
 * filter chain reads as the lip's natural sheen brightening as it
 * stretches.
 *
 * Filter composition: drop-shadow followed by brightness(1.15) applies
 * the brightness AFTER the shadow, so the highlight halo itself
 * brightens by 15% — clean compositional read.
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-viseme="wide"] .etp-char-mouth-upper-lip,
.etp-character[data-etp-viseme="round"] .etp-char-mouth-upper-lip {
  /* Canonical-spec §13: flat cyan lips — no open-vowel sheen boost.
     The Phase 36 Phase 3 #4 sheen intensification is REMOVED (it
     modulated the now-removed upper-lip highlight). */
  filter: none;
}

/* ----------------------------------------------------------------------------
 * #5 — Chrome-scale (favicon) silhouette-only
 *
 * The upper-lip highlight + lower-lip under-shadow read at hero +
 * classroom + convo scales but disappear into the silhouette at chrome
 * scale (favicon). Drop the filters at chrome scale so the silhouette
 * stays clean.
 * ----------------------------------------------------------------------------
 */
.etp-character[data-etp-scale="chrome"] .etp-char-mouth-upper-lip,
.etp-character[data-etp-scale="chrome"] .etp-char-mouth-lower-lip {
  filter: none;
}

/* ----------------------------------------------------------------------------
 * #6 — prefers-reduced-motion guard
 *
 * The base #1 + #2 filters are static (no animation), so they stay on
 * under reduced-motion. Only the #4 brightness boost on viseme
 * transitions is a state transition; drop it under reduced-motion so
 * the upper lip doesn't pulse brighter during open-mouth visemes.
 * ----------------------------------------------------------------------------
 */
@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-viseme="wide"] .etp-char-mouth-upper-lip,
  .etp-character[data-etp-viseme="round"] .etp-char-mouth-upper-lip {
    /* Canonical-spec §13: flat cyan lips — no sheen for reduced-motion
       users either. The Phase 36 Phase 3 #1 highlight is REMOVED. */
    filter: none;
  }
}

/* End of Phase 36 — Phase 3 — Mouth + lips + lip-sync polish */


/* =====================================================================
 * PHASE 36 — Phase 4 — Chest-core centring + ET+ wordmark + breathing
 *                      pulse verification + 2 latent-bug fixes
 *                      (2026-05-30j → 2026-05-30k)
 *
 * Operator-asked Phase 4 of the 6-phase ET+ AI Tutor Avatar Visual
 * Upgrade mission. Verifies the existing chest assembly meets the
 * canonical spec (chest-core centred between shoulder caps, ET+
 * wordmark geometrically centred inside chest-core, all chest
 * elements breathing on a unified --etp-pulse-period) and closes
 * two latent dead-code bugs surfaced during audit.
 *
 * ----------------------------------------------------------------------------
 * VERIFICATION — CHEST ASSEMBLY GEOMETRIC CENTRING
 *
 * SVG anchor: every chest element on cx=50 (centred horizontally on
 * the 100-unit viewBox), cy=103 (composed Phase 30 P30-6 alignment).
 * The Phase 22 chest-beacon translateY(-18px) chain shifts all
 * elements to visual cy=85 — exactly between the two shoulder caps
 * at visual cy=90. The Phase 35 JS IIFE-5 setAttribute promotes the
 * chest-core-hot to r=5.5 for cross-browser safety.
 *
 *   Element                       SVG cy   visual cy  notes
 *   ----------------------------  ------   ---------  -----
 *   chest-core-ghost              104.4    86.4       +1.4 subsurface scatter offset
 *   chest-core-glow               103.0    85.0       outer ambient glow (r=10)
 *   chest-core                    103.0    85.0       main teal disc (r=7.5)
 *   chest-core-rim                103.0    85.0       precision rim ring (r=7.7)
 *   chest-core-hot                103.0    85.0       inner radial-gradient disc (r=5.5)
 *   chest-core-specular           99.5     81.5       Phase 36 Phase 1 white-hot highlight
 *   chest-core-crosshair          103.0    85.0       precision lens fiducial
 *   chest-brand (ET+)             103.0    85.0       wordmark, dominant-baseline=middle
 *
 * Phase 28's geometric centring is preserved: ET+ wordmark glyph
 * middle lands exactly on chest-core geometric centre (no Phase 12
 * optical offset compensation needed — retired in Phase 28.2).
 *
 * ----------------------------------------------------------------------------
 * VERIFICATION — ET+ WORDMARK SCALE + WEIGHT
 *
 * Phase 12 baseline:      5.20px / weight 800
 * Phase 21.3 +20%:        6.24px / weight 900  (cumulative +20%)
 * Phase 22.5 +20% again:  7.488px / weight 900 (cumulative +44%)
 * Phase 24.A polish:      letter-spacing -0.06em + filter drop-shadow
 *                         blur 1.6px for emissive cyan glow read.
 *
 * font-family: JetBrains Mono (variable woff2 self-hosted)
 * paint-order: stroke fill
 * fill: #FFFFFF; stroke: rgba(10,31,51,0.3) 0.3px (charcoal hairline
 *       for legibility against varying glow)
 * text-shadow: 0 0 1.6px var(--etp-light-glow) (programme-tinted)
 *
 * Three nested <tspan> elements (E/T/+) so the per-letter pulse
 * (Phase 12 Tier 2.1 etp-chest-brand-letter-pulse 320ms) fires
 * staggered 0/30/60ms on every etpux:stream-chunk event.
 *
 * Scan-cycle (Phase 12 Tier 2.3 etp-chest-brand-scan-cycle 20s) is
 * co-prime with Phase 22 chest pulse (4s) — they never re-sync.
 *
 * ----------------------------------------------------------------------------
 * VERIFICATION — BREATHING PULSE COVERAGE
 *
 * All chest elements pulse on var(--etp-pulse-period, 4s) via:
 *   - Phase 11 baseline:      chest-core-glow
 *   - Phase 30 P30-7:         chest-core-glow + chest-core-rim + chest-core-hot
 *   - Phase 36 Phase 1:       chest-core-hot (compose pulse + radial-pulse)
 *   - Phase 36 Phase 1:       chest-core-specular (own keyframe)
 *   - Phase 36 Phase 4 (this batch): chest-core + chest-core-ghost
 *
 * Per-state respiration tokens (Phase 26.J):
 *   default:    --etp-pulse-amp-min 1.000  --etp-pulse-amp-max 1.04
 *   composed:   1.000 / 1.000 (held breath, also animation-play-state: paused)
 *   thinking:   1.000 / 1.06  (focused, deeper pulse)
 *   celebrating: 1.000 / 1.12 (excited, large amplitude)
 *
 * Per-state animation-duration overrides (Phase 26.J + Phase 33):
 *   excited:    1.1s  (during streaming / token delivery)
 *   tense:      1.0s  (alert state)
 *   listening:  2.2s  (slowed, "held attention")
 *
 * ----------------------------------------------------------------------------
 * FIX A — chest-core (the main 7.5r teal disc)
 *
 * Existing rule at the chest-core block declares
 * `animation-duration: var(--etp-pulse-period) !important;` but no
 * base `animation: <name>` was ever declared (dead-code). The
 * idle-curiosity selector (data-etp-idle="curiosity") overrides
 * with `etp-idle-curiosity 2.0s 1 both` for one-shot nudges but
 * doesn't provide a continuous breathing pulse. As a result the
 * VISIBLE cyan teal disc sat STATIC while glow + rim + hot pulsed
 * around it — creating subtle visual incoherence at hero scale.
 *
 * Phase 4 wires chest-core into the P30-7 pulse cascade. The
 * existing idle-curiosity rule still wins for its 2s one-shot
 * window (specificity tie, declared later in source order at
 * line 2515). After the one-shot, chest-core returns to the
 * continuous pulse.
 *
 * ----------------------------------------------------------------------------
 * FIX B — chest-core-ghost (subsurface scatter, +1.4 offset blur)
 *
 * Existing rules at lines 5271/5277/5284 set
 * `animation-duration: 1.1s` / 1.0s / 2.2s !important on respiration
 * states excited/tense/listening — but no base `animation: <name>`
 * was ever declared on chest-core-ghost. The duration overrides
 * were dead code. Phase 4 wires chest-core-ghost into the P30-7
 * cascade, activating the existing respiration duration overrides
 * AND the existing composed-state pause rule at the chest-core-ghost
 * block.
 *
 * ----------------------------------------------------------------------------
 * SACRED INVARIANTS PRESERVED
 *
 * - Phase 11 baseline + Phase 30 P30-7 + Phase 36 Phase 1 pulse
 *   cascades unchanged; Phase 4 ADDS two selectors only.
 * - Phase 22 chest-beacon translateY(-18px) chain unchanged.
 * - Phase 22.5 ET+ wordmark 7.488px unchanged.
 * - Phase 21.3 font-weight 900 unchanged.
 * - Phase 28 chest-brand y=103.0 + dominant-baseline=middle unchanged.
 * - Phase 12 Tier 2.1 per-letter pulse + Tier 2.3 scan-cycle
 *   unchanged (event-driven, not continuous).
 * - Phase 36 Phase 1/2/3 changes all preserved.
 * - WCAG-AAA accessibility branch unchanged.
 * --------------------------------------------------------------------- */

/* PHASE 4 FIX A + B — wire chest-core (main teal disc) + chest-core-ghost
   (subsurface scatter blur) into the P30-7 pulse cascade. The existing
   keyframe etp-chest-core-pulse composes translateY(-18px) with
   scale(var(--etp-pulse-amp-min, 1.000) → var(--etp-pulse-amp-max, 1.04))
   so both elements stay anchored at visual cy=85 (chest-core) /
   cy=86.4 (chest-core-ghost +1.4 offset) while pulsing. */
.etp-char-chest-core,
.etp-char-chest-core-ghost {
  animation: etp-chest-core-pulse var(--etp-pulse-period, 4s) ease-in-out infinite;
}

/* PHASE 4 — composed-state pause for the newly-pulsing elements.
   Matches Phase 14's 9th-state contract (composed = "held breath,
   all idle loops paused"). The existing chest-core-ghost composed
   rule at the ghost block already pauses; this adds chest-core. */
.etp-character[data-etp-state="composed"] .etp-char-chest-core {
  animation-play-state: paused;
}

/* PHASE 4 — reduced-motion guard for the newly-pulsing elements.
   Holds them at the translateY(-18px) base position without animation
   (matches existing P30-7 reduced-motion treatment for chest-core-hot). */
@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-core,
  .etp-char-chest-core-ghost {
    animation: none !important;
    transform: translateY(-18px);
  }
}

/* End of Phase 36 — Phase 4 — Chest verification + 2 latent-bug fixes */

/* =====================================================================
 * Phase 36 — Phase 5 — Arms DELETE + torso/neck/shoulders/holographic
 * fade + background verification appendix (2026-05-30, PR #440)
 * =====================================================================
 *
 * Closes the 5th of 6 phases of the canonical ET+ AI Tutor Avatar
 * Visual Upgrade mission — the highest-risk change in the project per
 * the operator's strict-scope brief.
 *
 *   GAP-5  Arms  — DELETED. All arm geometry + CSS rules + JS API
 *                  (pointAt, armRest, ARM_* constants, _armHoldTimers,
 *                  _resolveTargetPoint, _armCandidates,
 *                  etpux:behaviour-point-at + etpux:behaviour-arm-rest
 *                  listeners) removed. Existing callers in huddle.html,
 *                  etp-classroom-presence.js, etp-behaviour-bus.js, and
 *                  the in-file laser-pointer auto-trigger all guard on
 *                  `if (window.etpCharacter && window.etpCharacter.pointAt)`
 *                  and short-circuit cleanly.
 *
 *   GAP-9  Holo  — VERIFIED INTACT. The Phase 1 (2026-05-25) Section B
 *                  bust-holo fade `<linearGradient
 *                  id="etpBustHoloGrad_${id}">` (y1=100 → y2=140, white
 *                  100% → 90% → 0%) plus its corresponding `<mask
 *                  id="etpBustHoloMask_${id}">` are still applied to
 *                  the body. The arm-fade mask + gradient ARE deleted
 *                  this phase (they were authoring the arm-only
 *                  dissolve which is no longer needed). The bust-holo
 *                  remains as the load-bearing lower-body dissolve
 *                  that creates the floating projected-AI read.
 *
 *   GAP-10 Torso/neck/shoulders — VERIFIED INTACT.
 *                  `.etp-char-body` (torso path), `.etp-char-body-bevel`,
 *                  `.etp-char-neck` (Phase 8 short dark cylinder),
 *                  `.etp-char-neck-trim` (Phase 8 cyan trim ring),
 *                  `.etp-char-neck-segment` (Phase 10 articulation
 *                  seams), `.etp-char-shoulder-cap` + variants
 *                  `.etp-char-shoulder-cap--l/--r` (Phase 8 rounded
 *                  glossy caps), `.etp-char-shoulder-joint` (Phase 8
 *                  dark mechanical joint seams), `.etp-char-shoulder-led`
 *                  (Phase 10 cyan status LED dots) all preserved
 *                  byte-for-byte in JS and CSS. With the arm geometry
 *                  removed, the shoulder caps now terminate the
 *                  silhouette as the canonical reference render shows.
 *
 *   GAP-11 Background — VERIFIED INTACT.
 *                  `.etp-char-bg-plate` (Phase 8 background plate
 *                  gradient), `.etp-char-bg-grid` (Phase 10 circuit-
 *                  board grid pattern at 3% effective opacity),
 *                  `.etp-char-particle` × 14 (Phase 10 single-pixel
 *                  cyan drift particles with per-particle
 *                  animation-delay for organic random parallax) all
 *                  preserved unchanged.
 *
 * Sacred invariants preserved (verified line-by-line in deployed bytes):
 *   - Phase 36 Phase 1: chest-core radial gradient + GAP-2 outer glow +
 *     GAP-3 inner specular highlight + ALL 5 retoned rgba(168,220,250)
 *     instances — unchanged.
 *   - Phase 36 Phase 2: silver helmet shell `#EAECF1 !important` +
 *     3 polished eye animation states — unchanged.
 *   - Phase 36 Phase 3: dimensional mouth + lips + lip-sync polish —
 *     unchanged.
 *   - Phase 36 Phase 4: chest-core + chest-core-ghost pulse cascade
 *     selectors — unchanged.
 *   - Phase 35: face panel `#103A56 !important` + ear modules restored
 *     + chest 3-layer bullseye — unchanged.
 *   - Phase 11 baseline + Phase 30 P30-7 chest-core pulse cascade —
 *     unchanged.
 *   - Phase 22 chest-beacon `translateY(-18px)` chain — unchanged.
 *   - Phase 22.5 ET+ wordmark 7.488px / Phase 21.3 font-weight 900 —
 *     unchanged.
 *   - Phase 28 chest-brand `y=103.0` + dominant-baseline=middle —
 *     unchanged.
 *   - Phase 31 WCAG 2.2 SC 2.4.13 focus-visible — unchanged.
 *   - All 10 emotional states (Phase 14 composed + Phase 29 confused) —
 *     unchanged.
 *   - 5 programme + 4 persona variants — unchanged.
 *   - All accessibility branches (reduced-motion, high-contrast,
 *     WCAG-AAA) — unchanged.
 *
 * No new CSS rules ship in Phase 5 — the phase deletes rules. The
 * appendix above is a verification + audit-trail block only.
 * ------------------------------------------------------------------- */

/* End of Phase 36 — Phase 5 — Arms DELETE + torso/neck/shoulders/holographic fade + background verification */

/* ===================================================================
 * PHASE 36 — Phase 7 (2026-05-30) — Operator-asked visual refinement
 * ===================================================================
 *
 * Closes 4 small visual refinements operator surfaced after the Phase 6
 * mission close-out. Three of the four are pure-CSS removals (Phase 23
 * crown-chrome pattern — preserve the SVG geometry in the DOM but hide
 * via `display: none !important`, fully reversible by deleting this
 * block). The fourth is a 1-line JS edit (chest-brand `y` attribute
 * 103.0 → 106.0, applied separately in etp-character.js — restores
 * Phase 24.A's empirical optical compensation that Phase 28 had retired
 * in favour of pure geometric centring).
 *
 * The 3 CSS items:
 *
 *   #2 — Remove floor reflection
 *        The Phase 13.E.1 + Phase 25.G floor-reflection arc at visual
 *        y≈128 was deliberate scene-grounding chrome, but operator now
 *        wants the avatar to read as a free-floating projection per the
 *        broader 2026-05-30 mission. Hide via `display: none`.
 *
 *   #3 — Remove the grey crosshair lines and grey hexagon near ET+
 *        The Phase 12 Tier 2.3 chest-core-crosshair (precision lens
 *        fiducial overlay at chest centre) and Phase 19 Tier 2.10 chest
 *        facets hex grid (the 6-vertex polygon framing the brand mark)
 *        were premium-robot detailing but operator wants a cleaner
 *        chest disc with just the cyan core + radial gradient + ET+
 *        wordmark + specular highlight visible.
 *
 * No selector overlap with any state-driven cascade — all 3 elements
 * always paint when present, so single global `display: none` is safe.
 *
 * Reversibility: delete this block + revert the chest-brand y=106.0
 * back to y=103.0 in etp-character.js line 474. Phase 23 + Phase 5
 * (arm strict-delete) used the same additive-CSS-hide pattern, and
 * they are documented as fully reversible too.
 *
 * Sacred invariants preserved by Phase 7 (verified line-by-line):
 *   - Phase 36 Phase 5 strict-deleted arms — unchanged (this phase
 *     hides 3 more cosmetic elements without touching arm geometry).
 *   - Phase 36 Phase 4 chest-core + chest-core-ghost pulse cascade —
 *     unchanged (Phase 7 only hides crosshair + facets which sit
 *     ABOVE the chest-stack and never animated).
 *   - Phase 36 Phase 1 chest-core radial gradient + GAP-2 outer glow
 *     + GAP-3 inner specular highlight — unchanged (still visible).
 *   - Phase 22 chest-beacon `translateY(-18px)` chain — unchanged
 *     (chest-core-crosshair selector still in the rule, just hidden).
 *   - Phase 22.5 ET+ wordmark `font-size: 7.488px` — unchanged.
 *   - Phase 24.A typography polish (letter-spacing -0.06em + drop-
 *     shadow blur 1.6px emissive cyan glow) — unchanged.
 *   - Phase 31 WCAG 2.2 SC 2.4.13 focus-visible — unchanged.
 *   - 10 emotional states + 5 programmes + 4 personas — unchanged.
 * ------------------------------------------------------------------- */

.etp-char-floor-reflection,
.etp-char-chest-core-crosshair,
.etp-char-chest-facets {
  display: none !important;
}

/* End of Phase 36 — Phase 7 — Lip resize + floor / crosshair / hex hide + ET+ wordmark optical re-centre */

/* ===================================================================
 * PHASE 38 WAVE 1 — Avatar polish + top-25 quick-win UX (2026-05-31)
 * -------------------------------------------------------------------
 * 7 additive rule groups; zero edits to anything above this block.
 *
 * Items shipped here (12 of 19 in Wave 1 — others are JS / backend):
 *   - A3 chrome bust-edge soft fade
 *   - A4 per-band ear-pad scaling
 *   - B1 iridescent edge dissolve on holographic body fade
 *   - C2 pupil dilation on encouraging + empathy states
 *   - C4 lens-specular tracking extended to chest-core
 *   - Rank 10 streaming-mode mouth-only animation (no blink)
 *   - Rank 11 per-band animation tempo
 *
 * Sacred invariants preserved (verified):
 *   - All Phase 36 chest, lip, ear, helmet, floor-suppression rules
 *   - All Phase 22/22.5/23/24/28 chest-beacon translateY chain
 *   - All Phase 31 WCAG 2.2 focus-visible behaviour
 *   - All 10 emotional states, 5 programmes, 4 personas, 4 scale tiers
 *   - prefers-reduced-motion + prefers-contrast + WCAG-AAA branches
 * ------------------------------------------------------------------- */

/* A3 — chrome-scale bust-edge soft elliptical fade.
 * Replaces the implicit rectangular crop with a soft fade so the
 * favicon-scale silhouette reads as a credential-photo-style bust
 * rather than a hard rectangle. */
[data-etp-scale="chrome"] .etp-character {
  -webkit-mask-image: radial-gradient(ellipse 95% 80% at center 35%, black 70%, transparent 100%);
          mask-image: radial-gradient(ellipse 95% 80% at center 35%, black 70%, transparent 100%);
}

/* A4 — per-band ear-pad scaling.
 * Early-years grade-band gets slightly larger (12%) ear modules so
 * KG-2 students perceive them as cute headphones. High-school gets
 * smaller (8%) so the silhouette reads more adult. Default + middle
 * stages: untouched.
 * Requires `data-etp-stage="<grade-band>"` on the host page; gated
 * to avoid affecting non-host-page mounts. */
[data-etp-stage="early-years"] .etp-char-ear-module-pad {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(1.12);
}
[data-etp-stage="high-school"] .etp-char-ear-module-pad {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0.92);
}

/* B1 — iridescent edge dissolve on holographic body fade.
 * 8s shimmer cycle on the middle stop of bustHoloGradId. The
 * lower-body holographic fade now ripples through pink/blue/green
 * iridescent stops, selling the "projected AI" quality. Period 8s
 * co-prime with chest pulse (4s) and eye drift (9.7s) so the rhythm
 * never re-syncs.
 *
 * NOTE: bustHoloGradId is per-mount (suffix is the anchor id), so
 * targeting via class-based selector on the gradient's middle stop.
 * The stop is the 2nd `<stop>` child of bustHoloGradId. */
@keyframes etp-iridescent-shimmer {
  0%   { stop-color: rgba(168, 220, 250, 0.85); }
  33%  { stop-color: rgba(220, 180, 255, 0.85); }
  66%  { stop-color: rgba(168, 255, 220, 0.85); }
  100% { stop-color: rgba(168, 220, 250, 0.85); }
}
.etp-char-bust-holo-shimmer {
  animation: etp-iridescent-shimmer 8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .etp-char-bust-holo-shimmer { animation: none; }
}

/* C2 — pupil dilation on encouraging + empathy-detected states.
 * Default pupil r=0.4 (existing). Encouraging or empathy-flagged
 * states bump to r=0.6 with smooth 320ms ease-out so the eye reads
 * as "engaged, opening up" — empathy signal documented in spec
 * Annex C. */
.etp-character[data-etp-state="encouraging"] .etp-char-eye-pupil,
.etp-character[data-etp-empathy="1"] .etp-char-eye-pupil {
  r: 0.6;
  transition: r 320ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .etp-character[data-etp-state="encouraging"] .etp-char-eye-pupil,
  .etp-character[data-etp-empathy="1"] .etp-char-eye-pupil {
    transition: none;
  }
}

/* C4 — extend lens-specular tracking to chest-core.
 * Phase 15 Tier 2 #15 binds `--etp-host-light-shift` (computed from
 * the host page's background luminance + light direction) to the
 * eye lens-specular. Extend the same binding to the chest core's
 * specular at half magnitude (0.6x) so the chest gloss shifts in
 * the same direction as the eyes but more subtly. */
.etp-char-chest-core-specular {
  transform: translate(calc(var(--etp-host-light-shift, 0) * 0.6px), 0);
  transition: transform 320ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .etp-char-chest-core-specular { transition: none; }
}

/* Rank 10 — Streaming-mode mouth-only animation.
 * When the avatar is mid-stream (the existing `data-etp-streaming="1"`
 * attribute set by ai-tutor.js), pause the eye-lid + eye-arc blink
 * animations so the eyes hold a fixed open "actively listening to
 * the stream" gaze. Lip-sync via `data-etp-viseme` (Phase 34) keeps
 * running unaffected. Resumes automatically when stream ends. */
.etp-character[data-etp-streaming="1"] .etp-char-eye-lid,
.etp-character[data-etp-streaming="1"] .etp-char-eye-arc {
  animation-play-state: paused !important;
}

/* Rank 11 — Per-band animation tempo.
 * Each grade band gets a tempo multiplier on top of the global
 * `--etp-pulse-period` (default 4s). Early-years 0.85x = slower
 * (4.7s) so K-2 students perceive each pulse; high-school 1.1x =
 * faster (3.6s) for an energetic register. Mid bands stay at 1.0.
 *
 * Applied to chest pulse, breath pulse, and ambient vignette pulse
 * by overriding the base period token. */
[data-etp-stage="early-years"]  { --etp-tempo-multiplier: 0.85; }
[data-etp-stage="primary"]      { --etp-tempo-multiplier: 0.92; }
[data-etp-stage="middle-school"]{ --etp-tempo-multiplier: 1.00; }
[data-etp-stage="high-school"]  { --etp-tempo-multiplier: 1.10; }

/* When the multiplier is set, derive the effective period from it.
 * Pages that don't set `data-etp-stage` fall through to the default
 * 4s exactly as before. */
[data-etp-stage] .etp-character {
  --etp-pulse-period: calc(4s / var(--etp-tempo-multiplier, 1));
}

/* End of Phase 38 Wave 1 — Avatar polish + top-25 quick-win UX */

/* =====================================================================
 * PHASE 38 WAVE 2 RANK 3 — Remove chest-core disc assembly + shoulder
 * LEDs (2026-05-31)
 *
 * Operator-asked removal of the cyan circle (chest-core disc assembly
 * with all composing layers: ghost, glow, core, rim, hot, specular,
 * crosshair, ET+ wordmark, scan-cycle, dataflow-rim) and the 2 cyan
 * dots (left + right shoulder LEDs).
 *
 * Pattern: CSS `display: none !important` — DOM geometry preserved in
 * etp-character.js so any future un-revert is a one-line CSS deletion.
 * Same approach as Phase 23 (crown chrome), Phase 36 Phase 7
 * (crosshair + hex patterns).
 *
 * Revert: delete this entire block + restore cache-buster
 * `?v=20260531a` + restore sw.js stamp
 * `2026-05-31-phase-38-wave-2-rank-2-chat-sessions`.
 * ===================================================================== */

/* --- Chest-core disc assembly (the cyan circle) --- */

/* Static SVG elements */
.etp-char-chest-core,
.etp-char-chest-core-ghost,
.etp-char-chest-core-glow,
.etp-char-chest-core-rim,
.etp-char-chest-core-crosshair {
  display: none !important;
}

/* ET+ wordmark + scan-cycle overlay */
.etp-char-chest-brand,
.etp-char-chest-brand-scan {
  display: none !important;
}

/* JS-injected elements (Phase 30/35 IIFE, Phase 36 IIFE-6, Phase 27) */
.etp-char-chest-core-hot,
.etp-char-chest-core-specular,
.etp-char-chest-dataflow-rim {
  display: none !important;
}

/* --- Shoulder LEDs (the 2 cyan dots) --- */
.etp-char-shoulder-led {
  display: none !important;
}

/* End of Phase 38 Wave 2 Rank 3 — chest-core + shoulder LEDs removed */

/* =====================================================================
 * PHASE 38 WAVE 2 RANK 3.1 — Re-add cyan circle with ET+ in centre +
 * remove grey panel lines from face (2026-05-31)
 *
 * Operator-asked: (1) add back a cyan circle with "ET+" written in the
 * centre of it, (2) remove the grey lines from the avatar's face.
 *
 * Implementation:
 *
 * (1) Re-show ONLY two of the previously-hidden chest-core elements:
 *     - .etp-char-chest-core      (the main cyan teal disc, r=7.5)
 *     - .etp-char-chest-brand     (the ET+ wordmark text)
 *     All other chest-core layers (ghost / glow / rim / hot / specular
 *     / crosshair / brand-scan / dataflow-rim) stay hidden per Rank 3
 *     for a clean minimal "cyan circle + ET+" look.
 *     Shoulder LEDs also stay hidden.
 *
 * (2) Hide Phase 17 humanoid structural panel lines on the face:
 *     - .etp-char-helmet-panel    (forehead arc + 2 temple bands)
 *     - .etp-char-chin-separator  (subtle arc below mouth)
 *     (Cheek bevels were removed entirely per the canonical spec —
 *     no cheek shadow hints.) These panels were added in Phase 17 to
 *     make the avatar read as a humanoid robot, but the canonical
 *     spec + the operator prefer a smoother face.
 *     Helmet shell outline (.etp-char-helmet-shell stroke) stays —
 *     that's the silhouette of the head dome itself.
 *
 * Pattern: CSS overrides at higher specificity / later in cascade —
 * geometry preserved in JS so any future revert is a one-line CSS
 * deletion.
 *
 * Revert: delete this entire block + restore cache-buster
 * `?v=20260531c` + restore sw.js stamp
 * `2026-05-31-phase-38-wave-2-rank-3-chest-shoulder-removed`.
 * ===================================================================== */

/* --- (1) Re-show cyan circle + ET+ wordmark --- */
.etp-char-chest-core {
  display: inline !important;
  /* Phase 22 ships `translateY(-18px)` on the chest-beacon module so all
   * elements (glow, core, ghost, rim, crosshair, brand text) sit at
   * visual cy=90 between the two shoulder caps. The chest-core breathing
   * pulse keyframe (`etp-chest-core-pulse`) sets `transform: scale(N)`
   * which CSS-wipes the base `translateY` rule. The `translate` property
   * (CSS Transforms Module Level 2) composes with `transform: scale()`
   * independently — modern browsers since 2022. This keeps ET+ wordmark
   * (which gets translateY(-18px) via the canonical rule, no animation
   * overwrite) visually centred INSIDE the cyan circle.
   */
  translate: 0 -18px !important;
}

.etp-char-chest-brand {
  display: inline !important;
}

/* --- (2) Hide grey structural panel lines from face --- */
.etp-char-helmet-panel,
.etp-char-chin-separator {
  display: none !important;
}

/* End of Phase 38 Wave 2 Rank 3.1 — cyan circle restored + face smoothed + ET+ centred inside */

/* =========================================================================
 * Below-neck strict removal — 2026-06-04 (operator-asked)
 *
 * Strict-hides the IIFE-injected below-neck elements that survive after
 * inline SVG element deletion in etp-character.js. The inline SVG
 * elements (torso, body-bevel chest-area, 2 shoulder caps, 2 shoulder
 * joints, 2 shoulder LEDs, chest outer/inner panels, 4-circle chest core
 * stack, crosshair group, ET+ wordmark + 3 tspans, brand-scan rect) are
 * already strict-deleted from etp-character.js (3 audit-trail comment
 * blocks document the removal).
 *
 * The following IIFEs still execute on mount and inject DOM nodes that
 * are now orphaned (no parent anchor) or visually redundant. This block
 * hides their output:
 *
 *   - Phase 30 IIFE-4 injects <circle class="etp-char-chest-core-hot">
 *     (legacy "hot dot" cyan disc; previously parented to chest-core)
 *   - Phase 36 IIFE-6 injects <ellipse class="etp-char-chest-core-specular">
 *     + <radialGradient id="etpChestCoreInner_…"> into <defs>
 *     (chest-core gradient definitions; no surviving anchor)
 *   - Phase 12 chest-core-cross IIFE injects <path class="etp-char-chest-core-cross">
 *     (legacy chest-core crosshair fiducial)
 *   - Phase 13 floor-reflection IIFE injects
 *     <path class="etp-char-floor-reflection"> (bust floor reflection arc)
 *
 * IIFE source code preserved to avoid breaking the deep IIFE-2 wrapper
 * structure (L1643-5037). The setAttribute lookups against
 * .etp-char-chest-brand-e / -t / -plus (Phase 12 Tier 2.1 per-letter
 * brand pulse) become no-ops because those text anchors no longer exist.
 *
 * Scope: avatar visual only. No animation removal beyond the
 * below-neck region, no programme tint re-binding, no other CSS edits.
 * ========================================================================= */

.etp-char-chest-core-hot,
.etp-char-chest-core-specular,
.etp-char-chest-core-cross,
.etp-char-floor-reflection {
  display: none !important;
}

/* =========================================================================
 * Neck enlargement + ET+ watermark — 2026-06-04 (operator-asked)
 *
 * The neck rect was scaled 1.4x in etp-character.js (width 12 -> 16.8,
 * height 10 -> 14), and the new ET+ watermark text element sits exactly
 * in the middle of the enlarged neck at SVG (50, 71). This block styles
 * the watermark: bright-but-subtle cyan glyph reading "ET+" centred
 * inside the dark cylindrical neck, with the brand monospace
 * typography to match the retired chest-brand wordmark register.
 *
 * Scope: neck watermark visual only. No animation, no programme tint
 * re-binding, no other edits.
 * ========================================================================= */

.etp-char-neck-watermark {
  font-family: 'JetBrains Mono', 'IBM Plex Mono', 'Source Code Pro', monospace;
  font-size: 5px;
  font-weight: 800;
  letter-spacing: -0.06em;
  /* 2026-06-04 update — semi-dark cyan per operator brief
   * ("let the 'ET+' watermark be in semi-dark cyan color").
   *
   * #0E5F6D is a balanced semi-dark cyan: dark enough to read as
   * "semi-dark" against the charcoal neck cylinder, saturated
   * enough to read distinctly as cyan (not blue, not teal). The
   * 0.95 opacity keeps a touch of softness so the mark reads as a
   * watermark rather than a sharp printed glyph. Charcoal stroke
   * dropped since the fill is now dark enough to be legible on
   * its own — keeping the cyan glow drop-shadow for subtle brand
   * integration. */
  fill: #0E5F6D;
  fill-opacity: 0.95;
  paint-order: stroke fill;
  stroke: none;
  stroke-width: 0;
  filter: drop-shadow(0 0 0.8px rgba(0, 220, 236, 0.55));
  user-select: none;
  pointer-events: none;
}

/* Chrome-scale silhouette (favicon-style miniatures) drops the watermark
 * so the neck reads cleanly at favicon sizes where 5-px ET+ glyphs would
 * blur into the cylinder. */
.etp-character[data-etp-scale="chrome"] .etp-char-neck-watermark {
  display: none;
}

/* High-contrast accessibility branch: WCAG AAA palette wants a pure
 * white glyph against the dark cylinder for maximum legibility, drops
 * the cyan glow halo, hardens the stroke. */
.etp-character[data-etp-theme="high-contrast"] .etp-char-neck-watermark,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-neck-watermark {
  fill: #FFFFFF;
  fill-opacity: 1;
  stroke: #000000;
  stroke-width: 0.4px;
  filter: none;
}

/* =========================================================================
 * 2026-06-04 — Lower lip removal
 * -------------------------------------------------------------------------
 * Per operator brief: "remove the thick lower lip".
 *
 * Strict-hides the .etp-char-mouth-lower-lip <path> element across all
 * scales and theme branches. Element is injected by an IIFE deep inside
 * the Phase 17 wrapper (etp-character.js L3401-3413) — strict-hide via
 * CSS is cleaner than IIFE source deletion because the injection logic
 * is nested inside the deep IIFE-2 wrapper at L1643-5037 and removing
 * it risks breaking closure scope. The mounted DOM node is dropped via
 * `display: none !important`, which also disappears its Phase 36
 * Phase 3 #2 under-shadow filter chain since both ride on the same
 * element.
 *
 * What stays: upper lip (.etp-char-mouth-upper-lip), mouth cavity
 * (.etp-char-mouth-cavity), canonical smile path (.etp-char-mouth).
 * The mouth keeps its upper-lip curve + cavity opening + soft smile
 * stroke — only the thick lower-lip pillow is gone.
 *
 * Scope: lower lip visual only. No other lip/mouth/cavity edits, no
 * animation re-binding, no viseme retuning.
 * ========================================================================= */

.etp-char-mouth-lower-lip {
  display: none !important;
}

/* =========================================================================
 * PHASE 1 (2026-06-12) — KIDS-FRIENDLY FUTURISTIC AMENDMENT
 * GAP-3 chrome head ring 8-LAYER COMPLIANCE + GAP-4 humanoid oval
 * face panel coordinate compatibility.
 *
 * What changed in JS (etp-character.js):
 *   - Helmet <circle r="29"> → <ellipse rx="28.4" ry="29.2"> (GAP-3)
 *   - Helmet-grain <circle r="28.6"> → <ellipse rx="27.8" ry="28.6">
 *     (shrunk to sit inside the slightly narrower new helmet)
 *   - NEW <ellipse class="etp-char-helmet-fresnel" rx="28.0" ry="28.8">
 *     (8th layer — thin cyan-tinted edge glow along the chrome rim,
 *      sells fresnel reflection on polished metal)
 *   - NEW <circle class="etp-char-helmet-catch-light" cx="50" cy="9">
 *     (concentrated white catch-light at the crown — directional
 *      key-light cue, single most consequential 3D depth signal)
 *   - Face <circle r="22"> → <path> humanoid oval Bézier (GAP-4)
 *     mapped from spec coords (crown y=178, chin y=421, cheekbones
 *     at x=230/450 in the 680×500 spec) via uniform 0.18 scale
 *     + (50, 36) recentring. Class `.etp-char-head-circle` preserved
 *     so the entire CSS cascade (state-driven hue-rotate, drop-shadow
 *     rim, per-state subsurface scatter, etc.) keeps targeting this
 *     element transparent to the element TYPE swap (circle → path).
 *
 * What changed in CSS (this block):
 *   - Styling for .etp-char-helmet-fresnel + .etp-char-helmet-catch-light
 *   - Per-scale visibility (hide both at chrome favicon scale)
 *   - Accessibility branches: prefers-reduced-motion (no motion to
 *     begin with, so no override needed), prefers-contrast: more
 *     (brighten + thicken edges to AA contrast),
 *     [data-etp-theme="high-contrast"] (swap to neutral white tint),
 *     [data-etp-theme="high-contrast-aaa"] (lock to pure white on
 *     charcoal stroke for WCAG AAA contrast).
 *
 * Sacred invariants preserved:
 *   - 8 emotional states + 5 programmes + 4 personas — untouched
 *   - 14-locale i18n overlay — untouched
 *   - Public API window.etpCharacter.{mount, setState, setProgramme,
 *     setPersona, eyesLookAt, celebrate, alert} — untouched
 *   - 83 existing keyframes — untouched (16 new keyframes per spec
 *     land in Phase 7)
 *   - `<html class="etp-ai-tutor">` activation gate — untouched
 *   - All Phase 36 + Phase 38 + same-day 2026-06-04 polish work
 *     (below-neck strict-delete, neck 1.4× watermark, lower lip
 *     remove, semi-dark cyan watermark) — untouched
 *
 * Scope: ET+ AI Tutor avatar visual only. No backend, no K8s, no
 * infra, no other course, no non-avatar UI, no global styles, no
 * functionality changes, no API changes.
 * ========================================================================= */

/* P1.1 — Helmet fresnel rim glow.
 * A thin cyan-tinted edge stroke just inside the helmet outline.
 * Reads as a fresnel reflection where polished chrome meets the
 * dark background. Subtle (45% opacity at hero, fades at smaller
 * scales). The drop-shadow filter on .etp-char-helmet-shell at
 * line 1427 provides the OUTER soft dark rim; this rule provides
 * the INNER bright cyan rim — together they sell "polished metal
 * disc lit from above". */
.etp-char-helmet-fresnel {
  stroke: var(--etp-light-deep, #0098D8);
  stroke-width: 0.5;
  opacity: 0.45;
  filter: drop-shadow(0 0 0.8px var(--etp-light-glow, rgba(0, 220, 236, 0.65)));
}

.etp-character[data-etp-scale="chrome"] .etp-char-helmet-fresnel,
.etp-character[data-etp-scale="convo"]  .etp-char-helmet-fresnel {
  opacity: 0;
}
.etp-character[data-etp-scale="classroom"] .etp-char-helmet-fresnel {
  opacity: 0.35;
}

/* P1.2 — Helmet crown catch-light.
 * Small concentrated white catch-light at the top of the helmet.
 * Sells the directional key-light from above. This is the SINGLE
 * most consequential 3D depth cue in the chrome ring rendering —
 * the eye reads "polished sphere under stage lighting" rather
 * than "flat circle on a dark background" once this lands. */
.etp-char-helmet-catch-light {
  fill: rgba(255, 255, 255, 0.92);
  filter: drop-shadow(0 0 1.4px rgba(255, 255, 255, 0.65));
}

.etp-character[data-etp-scale="chrome"] .etp-char-helmet-catch-light {
  opacity: 0;
}
.etp-character[data-etp-scale="convo"] .etp-char-helmet-catch-light {
  opacity: 0.7;
}

/* P1.3 — High-contrast theme branches. */
.etp-character[data-etp-theme="high-contrast"] .etp-char-helmet-fresnel,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-helmet-fresnel {
  stroke: #FFFFFF;
  stroke-width: 0.7;
  opacity: 0.85;
  filter: none;
}

.etp-character[data-etp-theme="high-contrast"] .etp-char-helmet-catch-light,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-helmet-catch-light {
  fill: #FFFFFF;
  filter: drop-shadow(0 0 1px #000000);
}

/* P1.4 — prefers-contrast: more branch.
 * Lift edge thickness + brightness so the fresnel rim + catch-light
 * meet WCAG AA contrast for users who request increased contrast. */
@media (prefers-contrast: more) {
  .etp-char-helmet-fresnel {
    stroke-width: 0.8;
    opacity: 0.85;
  }
  .etp-char-helmet-catch-light {
    fill: #FFFFFF;
    r: 1.6;
  }
}

/* ============================================================
 * PHASE 2 (2026-06-12) — INSIDE-THE-FACE-CLIP ELEMENTS
 *
 * Closes 6 spec gaps from the canonical visual amendment:
 *   GAP-5  ambient warmth pulse
 *   GAP-6  tech scan ring
 *   GAP-7  HUD corner brackets (4)
 *   GAP-8  forehead gem
 *   (GAP-9 cheek blush — REMOVED per canonical spec)
 *   GAP-10 circuit accent lines
 *
 * Plus the HUD perimeter ring retune (circle r=20.5 -> ellipse
 * rx=18.3 ry=20.2) so the existing ring sits 1.5 units inside
 * the new humanoid face oval on both axes. Class selector
 * `.etp-char-face-hud-ring` is element-type-agnostic so the prior
 * Phase 13.A OLED gating + programme-tinted rim cascade survive.
 *
 * Static-only this phase. The 16 named keyframes (ep, tring, ck,
 * bkt, gem, sg, etc.) land in Phase 7. Every new class is
 * element-type-agnostic so the static SVG can swap to animated
 * SVG in Phase 7 without touching this block.
 *
 * Per-scale visibility ladder:
 *   hero / classroom -- full visual richness
 *   convo            -- dimmer (chat-window sized)
 *   chrome           -- entire face-clip group hidden (favicon scale)
 *
 * All four accessibility branches honoured at the foot of the block.
 * ============================================================ */

/* Phase 2 group -- shared container for all six new face-panel
 * surface elements. Carries pointer-events: none so the elements
 * never intercept mouse / touch input that should reach the
 * mounted character anchor. */
.etp-char-face-clip-group {
  pointer-events: none;
}

/* P2.1 -- warmth pulse (GAP-5).
 * Soft inner glow filling the face oval. Opacity tuned so the
 * pulse lifts the dark face-panel teal toward the eye centres
 * without washing out iris depth. Phase 7 will add the `ep`
 * keyframe to breathe opacity 0.45 to 0.78 over 4.6s. */
.etp-char-face-warmth-pulse {
  opacity: 0.55;
  mix-blend-mode: screen;
}

/* P2.2 -- tech scan ring (GAP-6).
 * Faint cyan ring just inside the HUD perimeter ring. Reads as a
 * scanner band between the iris row and the panel rim. Phase 7
 * will add the `tring` keyframe rotating the dash-array offset
 * 0..36 over 9s for a slow active-scanner sweep. */
.etp-char-face-scan-ring {
  stroke: var(--etp-light-deep, #00C8E8);
  stroke-width: 0.35;
  stroke-dasharray: 1.4 1.8;
  opacity: 0.38;
  filter: drop-shadow(0 0 0.6px var(--etp-light-glow, rgba(0, 220, 236, 0.55)));
}

/* P2.3 -- circuit accents (GAP-10).
 * Thin cyan trace lines that read as panel circuitry. Six short
 * L-bends at forehead/cheek/jaw positions. Rounded line-caps so
 * the traces read as soft tech detailing rather than engineering
 * print lines. */
.etp-char-face-circuit {
  stroke: var(--etp-light-deep, #00C8E8);
  stroke-width: 0.28;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  opacity: 0.55;
}

/* P2.4 -- cheek blush: REMOVED per canonical spec. The GAP-9
 * rose-pink discs (PR #524) are gone; the spec forbids cheek blush.
 * The blush radial gradient + its two ellipses were removed from the
 * JS generator, so the rules below for `.etp-char-face-blush` are
 * dropped too. */

/* P2.5 -- HUD corner brackets (GAP-7).
 * Four angular L-bends at the spec-mapped corners. Rounded
 * line-caps so the brackets read as soft HUD glyphs rather than
 * engineering ticks. Phase 7 will add the `bkt` keyframe pulsing
 * opacity 0.55 to 0.95 over 2.2s with a 0.18s stagger per corner. */
.etp-char-face-hud-bracket {
  stroke: var(--etp-light-deep, #00C8E8);
  stroke-width: 0.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  opacity: 0.7;
  filter: drop-shadow(0 0 0.7px var(--etp-light-glow, rgba(0, 220, 236, 0.55)));
}

/* P2.6 -- forehead gem (GAP-8).
 * Diamond polygon at the spec-mapped coord. Cyan glow drop-shadow
 * sells the gem as an internally-lit jewel. Phase 7 will add the
 * `gem` keyframe pulsing the drop-shadow blur + the polygon scale
 * by 1.05 over 3.4s. */
.etp-char-face-gem {
  opacity: 0.92;
  filter:
    drop-shadow(0 0 1.0px rgba(255, 255, 255, 0.85))
    drop-shadow(0 0 1.6px var(--etp-light-glow, rgba(0, 220, 236, 0.55)));
}

/* ----------------------------------------------------------------------------
 * Phase 7 (2026-06-14) §7 — 10-layer prismatic gem facets + aura + beam.
 *
 * Four cut facets give the diamond a faceted prism read: top-left bright
 * white catch-light, top-right cyan, bottom-left deep cyan, and a single
 * bottom-right VIOLET accent facet (#A78BFA, the spec's signature gem
 * highlight). The aura breathes (gem-pulse) and the upward beam pulses
 * (gem-beam). Every layer is cyan/white/violet glow + fill ONLY — there
 * is NO occlusion shadow on any gem layer (canonical-spec §7).
 * -------------------------------------------------------------------------- */
.etp-char-gem-aura {
  opacity: 0.55;
  transform-origin: 50px 19.3px;
  transform-box: view-box;
}
.etp-char-gem-beam {
  opacity: 0.5;
  transform-origin: 50px 17.5px;
  transform-box: view-box;
}
.etp-char-gem-facet--tl { fill: rgba(255, 255, 255, 0.90); }
.etp-char-gem-facet--tr { fill: rgba(0, 229, 255, 0.85); }
.etp-char-gem-facet--bl { fill: rgba(0, 160, 200, 0.80); }
/* Single violet accent facet — the spec's prismatic signature highlight. */
.etp-char-gem-facet--violet { fill: rgba(167, 139, 250, 0.85); }
.etp-char-gem-edge {
  stroke: rgba(255, 255, 255, 0.65);
  stroke-width: 0.16;
  stroke-linejoin: round;
}
.etp-char-gem-specular { fill: #FFFFFF; opacity: 0.9; }
.etp-char-gem-sparkle { fill: #FFFFFF; opacity: 0; }
/* The apex 4-pointed sparkle twinkles only at the richer scales. */
.etp-character[data-etp-scale="hero"] .etp-char-gem-sparkle,
.etp-character[data-etp-scale="classroom"] .etp-char-gem-sparkle {
  animation: etp-gem-sparkle 5.2s ease-in-out infinite;
  transform-origin: 50px 17.5px;
  transform-box: view-box;
}

/* ----- Per-scale visibility ladder ----- */

/* Chrome favicon scale -- entire Phase 2 group suppressed. At
 * chrome scale the avatar reads as a silhouette; warmth pulse +
 * scan ring + circuits + blush + brackets + gem are all too small
 * to perceive and would only add anti-aliasing noise. (Blush
 * removed per canonical spec — no longer in this group.) */
.etp-character[data-etp-scale="chrome"] .etp-char-face-clip-group {
  display: none;
}

/* Convo chat-window scale (~96px wide) -- keep warmth pulse + gem
 * (they carry the most identity weight) and dim everything else. */
.etp-character[data-etp-scale="convo"] .etp-char-face-warmth-pulse {
  opacity: 0.4;
}
.etp-character[data-etp-scale="convo"] .etp-char-face-scan-ring,
.etp-character[data-etp-scale="convo"] .etp-char-face-circuits,
.etp-character[data-etp-scale="convo"] .etp-char-face-hud-brackets {
  opacity: 0;
}
.etp-character[data-etp-scale="convo"] .etp-char-face-gem {
  opacity: 0.78;
}

/* Classroom projector scale (~300px wide) -- full richness with
 * a subtle dim on the warmth pulse so the eyes stay dominant from
 * the back of the room. */
.etp-character[data-etp-scale="classroom"] .etp-char-face-warmth-pulse {
  opacity: 0.48;
}

/* ----- Accessibility branches ----- */

/* P2.A -- high-contrast theme.
 * Swap cyan strokes to pure white so the new face elements meet
 * WCAG AA contrast on the dark face panel under high-contrast
 * presentation. (Cheek blush removed per canonical spec.) */
.etp-character[data-etp-theme="high-contrast"] .etp-char-face-scan-ring,
.etp-character[data-etp-theme="high-contrast"] .etp-char-face-circuit,
.etp-character[data-etp-theme="high-contrast"] .etp-char-face-hud-bracket {
  stroke: #FFFFFF;
  stroke-width: 0.55;
  opacity: 0.95;
  filter: none;
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-face-warmth-pulse {
  mix-blend-mode: normal;
  opacity: 0.4;
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-face-gem {
  opacity: 1;
  filter: drop-shadow(0 0 1px #000000);
}

/* P2.B -- high-contrast-aaa theme.
 * Stricter still: pure-white strokes with a black hairline glow
 * so every new element meets WCAG AAA contrast on any face-panel
 * hue. No mix-blend-mode (renders solid). */
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-face-scan-ring,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-face-circuit,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-face-hud-bracket {
  stroke: #FFFFFF;
  stroke-width: 0.7;
  opacity: 1;
  filter: drop-shadow(0 0 0.8px #000000);
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-face-warmth-pulse {
  mix-blend-mode: normal;
  opacity: 0.35;
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-face-gem {
  opacity: 1;
  filter: drop-shadow(0 0 1.2px #000000);
}

/* P2.C -- prefers-contrast: more system preference.
 * Lift stroke widths + opacities for users who request increased
 * contrast at the OS level, independent of theme attribute. */
@media (prefers-contrast: more) {
  .etp-char-face-scan-ring {
    stroke-width: 0.55;
    opacity: 0.85;
  }
  .etp-char-face-circuit {
    stroke-width: 0.45;
    opacity: 0.88;
  }
  .etp-char-face-hud-bracket {
    stroke-width: 0.72;
    opacity: 0.95;
  }
  .etp-char-face-warmth-pulse {
    opacity: 0.42;
  }
  .etp-char-face-gem {
    opacity: 1;
  }
}

/* P2.D -- prefers-reduced-motion system preference.
 * Phase 2 ships static-only elements (no @keyframes targeting any
 * of the new classes in this block). The Phase 7 animations (ep,
 * tring, ck, bkt, gem, sg) will add `animation: none` overrides
 * inside their own prefers-reduced-motion guards. Reserved here
 * as a no-op so the cascade order is fixed when Phase 7 lands. */
@media (prefers-reduced-motion: reduce) {
  /* Phase 2 elements carry no animation today; Phase 7 will add
   * the required overrides for the named keyframes that target
   * this set. */
}

/* ============================================================
 * PHASE 3 (2026-06-12) — LED EYEBROWS (GAP-11)
 *
 * Kids-friendly futuristic dual-colour LED eyebrows. 4 stacked
 * layers per side:
 *   .etp-char-eye-brow-led-glow   -- soft outer halo (back)
 *   .etp-char-eye-brow-led-bar    -- main LED bar (bright cyan)
 *   .etp-char-eye-brow-led-tint   -- secondary tint (warm cyan)
 *   .etp-char-eye-brow-led-catch  -- specular catchlight (white)
 *
 * Two-colour scheme: primary cyan #5EE0FF + warm-cyan #7AB0E8.
 * Phase 7 will add the `tw` keyframe alternating between the two
 * colours over a slow 7.2s cycle.
 *
 * Coexists with the Annex B+ `.etp-char-brow` group (which stays
 * opacity 0 by default and only activates on idle-on-hard-problem
 * or alert behaviour-bus events). The Phase 3 LED group is always
 * visible.
 *
 * Per-scale visibility ladder + all four accessibility branches
 * mirror the Phase 2 pattern.
 * ============================================================ */

/* P3.0 -- group container.
 * pointer-events: none so the LED bars never intercept mount-
 * anchor mouse / touch input. */
.etp-char-eye-brows {
  pointer-events: none;
}

/* P3.1 -- Layer 1: soft outer glow halo.
 * Widest, softest layer. Sits visually behind the main bar to
 * sell the LED as internally lit. Cyan stroke at low opacity
 * with a blurred drop-shadow for the diffused halo edge. */
.etp-char-eye-brow-led-glow {
  stroke: var(--etp-light-deep, #5EE0FF);
  stroke-width: 2.6;
  stroke-linecap: round;
  opacity: 0.28;
  filter: blur(0.6px);
}

/* P3.2 -- Layer 2: main LED bar.
 * Brightest layer. Crisp cyan stroke. Phase 7 `tw` keyframe will
 * cycle the stroke colour from `#5EE0FF` to `#7AB0E8` and back
 * over 7.2s for the dual-colour LED flicker. */
.etp-char-eye-brow-led-bar {
  stroke: #5EE0FF;
  stroke-width: 1.1;
  stroke-linecap: round;
  opacity: 0.95;
  filter: drop-shadow(0 0 0.9px rgba(94, 224, 255, 0.75));
}

/* P3.3 -- Layer 3: secondary tint shimmer (dual-colour).
 * Warm-cyan tint offset slightly upward. Carries the second tone
 * of the LED dual-colour scheme so even without the Phase 7
 * `tw` animation, the dual-colour appearance reads statically. */
.etp-char-eye-brow-led-tint {
  stroke: #7AB0E8;
  stroke-width: 0.55;
  stroke-linecap: round;
  opacity: 0.72;
  filter: drop-shadow(0 0 0.7px rgba(122, 176, 232, 0.62));
}

/* P3.4 -- Layer 4: specular catchlight.
 * Thinnest, frontmost layer. Pure-white hairline that sells the
 * LED as physically rounded. */
.etp-char-eye-brow-led-catch {
  stroke: #FFFFFF;
  stroke-width: 0.28;
  stroke-linecap: round;
  opacity: 0.82;
}

/* P7.8 -- dual-colour LED flicker (spec section 8, `ld` animation).
 * Phase 3 shipped the LED eyebrows static with the tint layer carrying
 * the second tone so the dual-colour read held without motion. Phase 7
 * now wires the live flicker: the bright bar and the warm tint cross-fade
 * their stroke colour between cyan #5EE0FF and warm-cyan #7AB0E8 in
 * counter-phase over 7.2s, with a gentle opacity breathe, so the brow
 * reads as a physically-lit dual-colour LED. Hero + classroom only -- at
 * convo/chrome scale the bar is a single hairline where the flicker reads
 * as noise. Glow only; no occlusion shadow. */
@keyframes etp-led-flicker-bar {
  0%   { stroke: #5EE0FF; opacity: 0.95; }
  50%  { stroke: #7AB0E8; opacity: 1.00; }
  100% { stroke: #5EE0FF; opacity: 0.95; }
}
@keyframes etp-led-flicker-tint {
  0%   { stroke: #7AB0E8; opacity: 0.72; }
  50%  { stroke: #5EE0FF; opacity: 0.84; }
  100% { stroke: #7AB0E8; opacity: 0.72; }
}
.etp-character[data-etp-scale="hero"] .etp-char-eye-brow-led-bar,
.etp-character[data-etp-scale="classroom"] .etp-char-eye-brow-led-bar {
  animation: etp-led-flicker-bar 7.2s ease-in-out infinite;
}
.etp-character[data-etp-scale="hero"] .etp-char-eye-brow-led-tint,
.etp-character[data-etp-scale="classroom"] .etp-char-eye-brow-led-tint {
  animation: etp-led-flicker-tint 7.2s ease-in-out infinite;
}

/* ----- Per-scale visibility ladder ----- */

/* Chrome favicon scale -- entire eyebrow group suppressed. */
.etp-character[data-etp-scale="chrome"] .etp-char-eye-brows {
  display: none;
}

/* Convo chat-window scale -- keep main bar only; drop glow halo +
 * tint + catchlight (which all read as anti-aliasing noise at
 * small size). */
.etp-character[data-etp-scale="convo"] .etp-char-eye-brow-led-glow,
.etp-character[data-etp-scale="convo"] .etp-char-eye-brow-led-tint,
.etp-character[data-etp-scale="convo"] .etp-char-eye-brow-led-catch {
  opacity: 0;
}
.etp-character[data-etp-scale="convo"] .etp-char-eye-brow-led-bar {
  opacity: 0.85;
  stroke-width: 0.9;
  filter: none;
}

/* Classroom projector scale -- full layer stack, slightly stronger
 * glow halo for back-of-room readability. */
.etp-character[data-etp-scale="classroom"] .etp-char-eye-brow-led-glow {
  opacity: 0.35;
}

/* ----- Accessibility branches ----- */

/* P3.A -- high-contrast theme.
 * Collapse the layered LED look to a single bright-white bar with
 * a black hairline so the eyebrow reads at AA contrast on any
 * face-panel hue. Tint + glow + catchlight all suppressed so the
 * eyebrow is unambiguous. */
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-brow-led-glow,
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-brow-led-tint,
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-brow-led-catch {
  display: none;
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-brow-led-bar {
  stroke: #FFFFFF;
  stroke-width: 1.4;
  opacity: 1;
  filter: drop-shadow(0 0 0.7px #000000);
}

/* P3.B -- high-contrast-aaa theme.
 * Same single-bar collapse with stricter stroke width + double
 * black hairline drop-shadow for AAA contrast guarantee. */
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-brow-led-glow,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-brow-led-tint,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-brow-led-catch {
  display: none;
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-brow-led-bar {
  stroke: #FFFFFF;
  stroke-width: 1.6;
  opacity: 1;
  filter:
    drop-shadow(0 0 0.8px #000000)
    drop-shadow(0 0 1.4px #000000);
}

/* P3.C -- prefers-contrast: more system preference.
 * Lift opacity + remove blur on the glow layer; thicken the bar +
 * catchlight strokes so the OS-level high-contrast preference
 * lifts every LED layer toward AA contrast without going full
 * single-bar (preserves the dual-colour identity). */
@media (prefers-contrast: more) {
  .etp-char-eye-brow-led-glow {
    opacity: 0.42;
    filter: none;
  }
  .etp-char-eye-brow-led-bar {
    stroke-width: 1.3;
    opacity: 1;
  }
  .etp-char-eye-brow-led-tint {
    stroke-width: 0.75;
    opacity: 0.88;
  }
  .etp-char-eye-brow-led-catch {
    stroke-width: 0.38;
    opacity: 0.95;
  }
}

/* P3.D -- prefers-reduced-motion system preference.
 * Phase 3 ships static-only LED eyebrows; no @keyframes target
 * any of these classes today. Phase 7 will add the `tw` keyframe
 * cycling the bar + tint colours; the corresponding
 * prefers-reduced-motion override will land in that block. Reserve
 * here so the cascade order is fixed when Phase 7 arrives. */
@media (prefers-reduced-motion: reduce) {
  /* Phase 7 wired the dual-colour LED flicker; stop it for reduced-motion
   * users. The static tint layer keeps the dual-colour identity at rest. */
  .etp-char-eye-brow-led-bar,
  .etp-char-eye-brow-led-tint {
    animation: none !important;
  }
}

/* ============================================================
   PHASE 4 (2026-06-12) -- Eyes 3D layered depth (GAP-12)
                         + nose light-silver contour, no shadow (GAP-14)
                         + 3-part lip system (GAP-15)
   ============================================================
   All additive. Preserves every existing eye / mouth selector
   so state-driven cascade rules (Phase 11 cursor-track, Phase
   14 blink, Phase 26.J state micro-saccade, alert flatten,
   celebrate widen) continue to fire byte-identical against the
   existing DOM elements. The new layers wrap the existing iris
   / pupil / lens with depth rings + corona + glow shell + lens
   specular highlight pair + anime shine arc, alongside the
   existing aperture + warm-spark + halo + Phase 26.C IIFE-
   injected specular ellipse.

   Sacred invariants preserved:
   - `.etp-char-eye-iris`, `.etp-char-eye-pupil`,
     `.etp-char-eye-aperture`, `.etp-char-eye-lens`,
     `.etp-char-eye-warm-spark`, `.etp-char-eye-lid`,
     `.etp-char-eye-arc` -- untouched.
   - `.etp-char-eye-lens-specular` (Phase 26.C IIFE-injected)
     remains the single forehead-top highlight; the new
     `.etp-char-eye-lens-highlight` pair is additive (different
     class, different positioning).
   - `.etp-char-mouth` single-arc -- preserved as a hidden anchor
     so state-driven cascade rules in CSS section 8 still resolve
     to a real DOM element. Phase 7 will retarget the state
     cascade onto the new lip system and drop the hide rule.
   - `.etp-char-pupils` group -- untouched (Phase 11 cursor-track
     `--etp-mouse-dx` CSS var continues to drive translateX).

   Accessibility branches honoured throughout: P4.A
   `data-etp-theme="high-contrast"`, P4.B
   `data-etp-theme="high-contrast-aaa"`, P4.C
   `@media (prefers-contrast: more)`, P4.D
   `@media (prefers-reduced-motion: reduce)` reserve. */

/* ---------- P4.1 Eye corona (widest back layer) ---------- */
.etp-char-eye-corona {
  fill: rgba(94, 224, 255, 0.10);
  opacity: 0.46;
  filter: blur(1.4px);
  pointer-events: none;
}

/* ---------- P4.2 Eye glow shell (between corona + halo) ---------- */
.etp-char-eye-glow-shell {
  fill: rgba(122, 176, 232, 0.16);
  opacity: 0.58;
  filter: blur(0.9px);
  pointer-events: none;
}

/* ---------- P4.3 Iris depth rings ---------- */
.etp-char-eye-depth-ring {
  stroke: rgba(10, 31, 51, 0.32);
  stroke-linecap: round;
  pointer-events: none;
}
.etp-char-eye-depth-ring--outer {
  stroke-width: 0.36;
  opacity: 0.42;
}
.etp-char-eye-depth-ring--inner {
  stroke-width: 0.28;
  opacity: 0.32;
}

/* ---------- P4.4 Lens specular highlight pair ---------- */
.etp-char-eye-lens-highlight {
  fill: #FFFFFF;
  pointer-events: none;
}
.etp-char-eye-lens-highlight--primary {
  opacity: 0.88;
  filter: drop-shadow(0 0 0.55px rgba(255, 255, 255, 0.65));
}
.etp-char-eye-lens-highlight--secondary {
  opacity: 0.62;
}

/* ---------- P4.5 Anime shine arc ---------- */
.etp-char-eye-anime-shine {
  stroke: rgba(255, 255, 255, 0.78);
  stroke-width: 0.36;
  stroke-linecap: round;
  opacity: 0.85;
  pointer-events: none;
  filter: drop-shadow(0 0 0.45px rgba(168, 220, 250, 0.55));
}

/* P7.9 -- §9 eye nebula-depth overlay. A normal-blend semi-opaque layer
 * over the iris; the gradient itself darkens the rim to indigo/violet so
 * the iris reads with cosmic depth. Overlay only, no occlusion shadow. */
.etp-char-eye-nebula {
  opacity: 0.6;
  pointer-events: none;
}

/* P7.9 -- §9 4-pointed star catchlight. Crisp white sparkle that twinkles
 * (scale + rotate) on hero/classroom; static-bright otherwise. Cyan glow
 * only. */
.etp-char-eye-star {
  fill: #FFFFFF;
  opacity: 0.9;
  pointer-events: none;
  filter: drop-shadow(0 0 0.5px rgba(168, 220, 250, 0.6));
}
@keyframes etp-eye-star-tw {
  0%   { opacity: 0.35; transform: scale(0.6) rotate(0deg); }
  50%  { opacity: 0.95; transform: scale(1.0) rotate(45deg); }
  100% { opacity: 0.35; transform: scale(0.6) rotate(0deg); }
}
.etp-character[data-etp-scale="hero"] .etp-char-eye-star,
.etp-character[data-etp-scale="classroom"] .etp-char-eye-star {
  animation: etp-eye-star-tw 4.6s ease-in-out infinite;
  transform-box: fill-box;
  transform-origin: center;
}
@media (prefers-reduced-motion: reduce) {
  .etp-char-eye-star {
    animation: none !important;
    opacity: 0.85;
    transform: none;
  }
}

/* ---------- P4.6 Per-scale ladder for Phase 4 eye layers ---------- */
/* Chrome (favicon): hide every Phase 4 depth detail to avoid
   anti-aliasing noise at 16-px scale. */
.etp-character[data-etp-scale="chrome"] .etp-char-eye-corona,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-glow-shell,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-depth-ring,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-lens-highlight,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-nebula,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-star,
.etp-character[data-etp-scale="chrome"] .etp-char-eye-anime-shine {
  display: none;
}
/* Convo (~128 px): keep iris + halo + lens; drop corona / glow /
   depth rings; keep the primary specular + anime shine + the nebula
   depth tint (cheap fill) for character; drop the 4-point star. */
.etp-character[data-etp-scale="convo"] .etp-char-eye-corona,
.etp-character[data-etp-scale="convo"] .etp-char-eye-glow-shell,
.etp-character[data-etp-scale="convo"] .etp-char-eye-star,
.etp-character[data-etp-scale="convo"] .etp-char-eye-depth-ring {
  display: none;
}
.etp-character[data-etp-scale="convo"] .etp-char-eye-lens-highlight--primary {
  opacity: 0.82;
}
.etp-character[data-etp-scale="convo"] .etp-char-eye-lens-highlight--secondary {
  display: none;
}
.etp-character[data-etp-scale="convo"] .etp-char-eye-anime-shine {
  opacity: 0.72;
}
/* Classroom (~300 px+): lift corona for back-of-room readability. */
.etp-character[data-etp-scale="classroom"] .etp-char-eye-corona {
  opacity: 0.55;
}

/* ---------- P4.A High-contrast theme (AA) ---------- */
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-corona,
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-nebula,
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-glow-shell {
  display: none;
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-star {
  fill: #FFFFFF;
  opacity: 1;
  filter: none;
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-depth-ring {
  stroke: #000000;
  opacity: 0.95;
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-lens-highlight {
  fill: #FFFFFF;
  opacity: 1;
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-eye-anime-shine {
  stroke: #FFFFFF;
  opacity: 1;
  filter: none;
}

/* ---------- P4.B High-contrast theme (AAA) ---------- */
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-corona,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-nebula,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-glow-shell,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-lens-highlight--secondary {
  display: none;
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-star {
  fill: #FFFFFF;
  opacity: 1;
  filter: drop-shadow(0 0 0.35px #000000);
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-depth-ring {
  stroke: #000000;
  stroke-width: 0.5;
  opacity: 1;
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-lens-highlight--primary {
  fill: #FFFFFF;
  opacity: 1;
  filter: drop-shadow(0 0 0.4px #000000);
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-eye-anime-shine {
  stroke: #FFFFFF;
  stroke-width: 0.5;
  opacity: 1;
  filter: drop-shadow(0 0 0.35px #000000);
}

/* ---------- P4.C prefers-contrast: more ---------- */
@media (prefers-contrast: more) {
  .etp-char-eye-corona { opacity: 0.62; }
  .etp-char-eye-glow-shell { opacity: 0.74; }
  .etp-char-eye-depth-ring--outer { opacity: 0.78; stroke-width: 0.46; }
  .etp-char-eye-depth-ring--inner { opacity: 0.66; stroke-width: 0.36; }
  .etp-char-eye-lens-highlight--primary { opacity: 1; }
  .etp-char-eye-lens-highlight--secondary { opacity: 0.82; }
  .etp-char-eye-anime-shine { opacity: 1; stroke-width: 0.46; }
}

/* ---------- P4.D prefers-reduced-motion reserve ---------- */
@media (prefers-reduced-motion: reduce) {
  /* Phase 7 will bind the `sg` (lens shimmer) + `ep` (eye-pulse)
   * keyframes to .etp-char-eye-anime-shine and
   * .etp-char-eye-corona respectively. This reserve block will
   * receive the corresponding `animation: none` overrides at
   * that point. No-op for Phase 4 -- every Phase 4 element is
   * static. */
}

/* ---------- P4.7 Nose (GAP-14 / canonical-spec §12: no shadow) ----------
 * The nose is a light-silver structural contour (the same tone as the
 * face-panel seams) — NOT a dark shadow. The spec forbids shadow effects
 * on the face, so the bridge + tip strokes use a light-silver tone and
 * the dark nostril-recess ellipses are removed entirely. The high-contrast
 * accessibility branches keep a black hairline purely for legibility
 * (a WCAG contrast outline, not a decorative shadow). */
.etp-char-nose {
  pointer-events: none;
}
.etp-char-nose-bridge {
  stroke: rgba(150, 158, 168, 0.45);
  stroke-width: 0.42;
  stroke-linecap: round;
  opacity: 0.5;
}
.etp-char-nose-tip {
  stroke: rgba(150, 158, 168, 0.6);
  stroke-width: 0.5;
  stroke-linecap: round;
  opacity: 0.66;
}
.etp-character[data-etp-scale="chrome"] .etp-char-nose {
  display: none;
}
.etp-character[data-etp-scale="convo"] .etp-char-nose-bridge {
  display: none; /* the bridge gets lost at chat-window scale */
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-nose-bridge,
.etp-character[data-etp-theme="high-contrast"] .etp-char-nose-tip {
  stroke: #000000;
  opacity: 1;
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-nose-bridge,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-nose-tip {
  stroke: #000000;
  stroke-width: 0.62;
  opacity: 1;
}
@media (prefers-contrast: more) {
  .etp-char-nose-bridge { opacity: 0.72; stroke-width: 0.52; }
  .etp-char-nose-tip { opacity: 0.85; stroke-width: 0.6; }
}

/* ---------- P4.8 3-part lip system (GAP-15) ---------- */
/* Hide the legacy single-arc mouth anchor. The element is kept in
   the DOM so state-driven cascade rules in CSS section 8 still
   resolve to a real target, but visually it is replaced by the
   3-part lip system below. Phase 7 will retarget the state
   cascade onto `.etp-char-mouth-lip-system` and drop this hide
   rule. */
.etp-character .etp-char-mouth {
  opacity: 0;
}

.etp-char-mouth-lip-system {
  pointer-events: none;
}
/* Upper lip -- Cupid's bow. Cyan fill ONLY (canonical-spec §13:
   equal-height cyan smiley lips, NO shadow effects, NO edge-strokes,
   NO chrome outline, NO white highlight). The under-shadow drop-shadow
   is REMOVED per the spec. */
.etp-char-mouth-cupids-bow {
  fill: var(--etp-mouth, rgba(0, 220, 236, 0.92));
  opacity: 0.92;
}
/* Curved separation between the lips -- a muted-cyan seam (NOT a dark
   shadow tone). §13 forbids shadow effects, so the seam is rendered in
   a darker cyan rather than the old dark-navy. */
.etp-char-mouth-separation {
  stroke: rgba(0, 120, 140, 0.5);
  stroke-width: 0.3;
  stroke-linecap: round;
  opacity: 0.6;
}
/* Lower lip -- rounded cyan fill ONLY. NO shadow, NO chrome outline,
   NO white highlight (canonical-spec §13). The under-shadow drop-shadow
   is REMOVED per the spec. */
.etp-char-mouth-rounded-lower {
  fill: var(--etp-mouth, rgba(0, 220, 236, 0.92));
  opacity: 0.95;
}

.etp-character[data-etp-scale="chrome"] .etp-char-mouth-lip-system {
  display: none;
}
.etp-character[data-etp-scale="convo"] .etp-char-mouth-cupids-bow,
.etp-character[data-etp-scale="convo"] .etp-char-mouth-rounded-lower {
  filter: none; /* drop-shadows are expensive at chat-window scale */
}
.etp-character[data-etp-scale="convo"] .etp-char-mouth-separation {
  display: none; /* seam reads as noise at small scale */
}

/* High-contrast (AA) -- bright cyan + black seam. */
.etp-character[data-etp-theme="high-contrast"] .etp-char-mouth-cupids-bow,
.etp-character[data-etp-theme="high-contrast"] .etp-char-mouth-rounded-lower {
  fill: #00DCEC;
  opacity: 1;
  filter: drop-shadow(0 0 0.4px #000000);
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-mouth-separation {
  stroke: #000000;
  opacity: 1;
}

/* High-contrast (AAA) -- pure white-on-black for AAA contrast
   against the dark face panel. */
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-mouth-cupids-bow,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-mouth-rounded-lower {
  fill: #FFFFFF;
  opacity: 1;
  filter: drop-shadow(0 0 0.5px #000000);
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-mouth-separation {
  stroke: #000000;
  stroke-width: 0.42;
  opacity: 1;
}

@media (prefers-contrast: more) {
  .etp-char-mouth-cupids-bow { opacity: 1; }
  .etp-char-mouth-separation { opacity: 0.78; stroke-width: 0.4; }
  .etp-char-mouth-rounded-lower { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  /* Phase 7 will bind the `ld` (lip-droop) keyframe to
   * .etp-char-mouth-rounded-lower for the celebration and
   * encouraging states. This reserve block will receive the
   * corresponding `animation: none` override at that point.
   * No-op for Phase 4 -- every Phase 4 element is static. */
}

/* ====================================================================
 * Phase 5 (2026-06-12) — KIDS-FRIENDLY FUTURISTIC AMENDMENT
 *
 * Scope: GAP-13 four staggered idle sparkles + GAP-16 circular ear
 * modules at the chrome ellipse boundary.
 *
 * GAP-13 — Four staggered idle sparkles (.etp-char-sparkles-amend)
 * ----------------------------------------------------------------
 * NEW group (additive layer; does NOT overlap the legacy
 * .etp-char-sparkles class which stays wired to the Phase 11
 * data-etp-behaviour-sparkle celebration trigger via the three
 * etp-behaviour-sparkle-{a,b,c} keyframes around line 1215-1241).
 *
 * Each sparkle is a 4-point star <path> wrapped in a
 * <g transform="translate(x, y)"> so the star path stays
 * origin-centred for clean Phase 7 keyframe animation (scale +
 * rotate around 0,0). Per-sparkle modifier classes --1 through
 * --4 receive Phase 7 animation-delay stagger.
 *
 * Phase 5 ships them STATIC (visible decorative glyphs) — Phase 7
 * adds the sp keyframe plus reduced-motion override.
 *
 * GAP-16 — Circular ear discs (.etp-char-ear-disc)
 * ------------------------------------------------
 * NEW class on the outer silver disc. The JS swap (Phase 5 Edit 1)
 * replaced the rectangular .etp-char-ear-module-pad with a
 * <circle class="etp-char-ear-disc"> at the chrome boundary —
 * spec (182, 302) / (498, 302) mapped via GAP-21 uniform 0.18
 * scale + (50, 36) recentre → L (21.6, 45.4) / R (78.4, 45.4),
 * disc radius 4.0, inner cyan radius 1.6.
 *
 * The .etp-char-ear-module group + --l / --r modifiers and the
 * .etp-char-ear-module-cyan inner class are preserved byte-
 * identical so the Phase 13.D voice-listen-amplitude listener
 * that drives --etp-ear-meter-fill continues to fire on the new
 * (smaller) inner circle.
 *
 * The previous .etp-char-ear-module-pad rect-specific rules
 * (rx, drop-shadow, fill gradient) sit harmlessly in the CSS but
 * no longer have a target — the new .etp-char-ear-disc class
 * isolates the circular styling cleanly.
 * ==================================================================== */

/* ---- GAP-16: Circular ear discs (P5.1) ---- */
/* Phase 7 (2026-06-14) §14 — the ear modules were retuned to the head
 * MIDLINE (spec ear cy=295 → viewBox y=36, cx≈175/505 → 20.3 / 79.7) and
 * the layer stack expanded to the canonical circular module: outer
 * silver disc + chrome rim ring + base specular + inner cyan ring +
 * cyan core. The forbidden offset occlusion drop-shadow (0 0.4px ...)
 * was removed per the global no-shadow rule; the disc now reads via a
 * crisp chrome rim + a centred specular instead of a dark shadow. The
 * .etp-char-ear-module group + --l/--r modifiers + .etp-char-ear-module-
 * cyan inner class are preserved byte-identical so the Phase 13.D
 * voice-listen-amplitude meter still fires on the cyan core. */

.etp-char-ear-disc {
  fill: var(--etp-helmet-silver, #C8D2DE);
  stroke: var(--etp-helmet-rim, #8FA0B5);
  stroke-width: 0.35;
  opacity: 1;
}

/* Brushed-chrome rim ring just inside the disc edge — replaces the old
 * dark occlusion shadow as the disc's depth cue (luminous, not dark). */
.etp-char-ear-rim {
  stroke: var(--etp-helmet-rim, #9FB0C5);
  stroke-width: 0.45;
  opacity: 0.85;
}

/* Base specular — a small bright highlight up-inboard on the silver disc
 * giving an overhead-light read. Pure white-blue, centred glow only. */
.etp-char-ear-specular {
  fill: rgba(245, 250, 255, 0.85);
  opacity: 0.9;
}

/* Inner cyan accent ring framing the glowing core. */
.etp-char-ear-cyan-ring {
  stroke: var(--etp-eye-cyan, #2BE5FF);
  stroke-width: 0.4;
  opacity: 0.7;
  filter: drop-shadow(0 0 0.4px rgba(43, 229, 255, 0.5));
}

/* L/R modifiers reserved for future directional tuning. Currently
 * the discs are symmetric per the canonical spec. */
.etp-char-ear-disc--l { /* No directional override -- LED-style symmetry */ }
.etp-char-ear-disc--r { /* No directional override -- LED-style symmetry */ }

/* The inner cyan circle now replaces the legacy capsule. Phase 13.D
 * --etp-ear-meter-fill drives a scaleY transform on this element
 * during voice-listen amplitude events. The fill colour stays
 * canonical Phase 8 cyan so the meter reads as identical brand. */
circle.etp-char-ear-module-cyan {
  fill: var(--etp-eye-cyan, #2BE5FF);
  filter: drop-shadow(0 0 0.45px rgba(43, 229, 255, 0.55));
  transform-box: fill-box;
  transform-origin: center;
  transform: scaleY(var(--etp-ear-meter-fill, 1));
  transition: transform 80ms linear;
}

/* ---- GAP-13: Idle sparkles (P5.2) ---- */

.etp-char-sparkles-amend {
  pointer-events: none;
}

.etp-char-sparkle-idle {
  /* Transform-origin stays at the group's own (0,0) so the
   * <g transform="translate(...)"> wrapper keeps the star
   * origin-centred for Phase 7 scale + rotate animation. */
  transform-box: fill-box;
  transform-origin: center;
}

.etp-char-sparkle-idle-glyph {
  fill: var(--etp-light-warm, #FFE5A5);
  opacity: 0.78;
  filter: drop-shadow(0 0 0.3px rgba(255, 229, 165, 0.55));
}

/* Per-sparkle base opacity tints (also the static fallback at chrome
 * scale + when prefers-reduced-motion is on). */
.etp-char-sparkle-idle--1 .etp-char-sparkle-idle-glyph { opacity: 0.82; }
.etp-char-sparkle-idle--2 .etp-char-sparkle-idle-glyph { opacity: 0.74; }
.etp-char-sparkle-idle--3 .etp-char-sparkle-idle-glyph { opacity: 0.86; }
.etp-char-sparkle-idle--4 .etp-char-sparkle-idle-glyph { opacity: 0.78; }

/* Phase 7 (2026-06-14) §10 / §16 -- the canonical 4-step idle twinkle.
 * A single 2.8 s scale + opacity loop bound to all four sparkle groups
 * with a staggered animation-delay so the corners pulse out of phase
 * (tw1-4). Group-level transform (the <g> origin is its own 0,0) keeps
 * each star spinning about its own centre. Cyan/warm glow only -- no
 * occlusion shadow. */
@keyframes etp-sparkle-tw {
  0%   { transform: scale(0.55) rotate(0deg);   opacity: 0.30; }
  50%  { transform: scale(1.0)  rotate(45deg);  opacity: 1.00; }
  100% { transform: scale(0.55) rotate(0deg);   opacity: 0.30; }
}
.etp-character[data-etp-scale="hero"] .etp-char-sparkle-idle,
.etp-character[data-etp-scale="classroom"] .etp-char-sparkle-idle {
  animation: etp-sparkle-tw 2.8s ease-in-out infinite;
}
.etp-character[data-etp-scale="hero"] .etp-char-sparkle-idle--2,
.etp-character[data-etp-scale="classroom"] .etp-char-sparkle-idle--2 {
  animation-delay: -0.7s;
}
.etp-character[data-etp-scale="hero"] .etp-char-sparkle-idle--3,
.etp-character[data-etp-scale="classroom"] .etp-char-sparkle-idle--3 {
  animation-delay: -1.4s;
}
.etp-character[data-etp-scale="hero"] .etp-char-sparkle-idle--4,
.etp-character[data-etp-scale="classroom"] .etp-char-sparkle-idle--4 {
  animation-delay: -2.1s;
}

/* ---- Per-scale ladder (P5.3) ---- */

/* Chrome scale (16-px favicon): sparkles add visual noise at tiny
 * sizes -- hide entirely so the silhouette stays clean. Ear discs
 * shrink with the helmet (no per-scale override). */
.etp-character[data-etp-scale="chrome"] .etp-char-sparkles-amend {
  display: none;
}

/* Convo scale (~128 px): sparkles dimmed to 55% so they remain
 * decorative but don't compete with the avatar face. */
.etp-character[data-etp-scale="convo"] .etp-char-sparkles-amend {
  opacity: 0.55;
}

/* Classroom + hero scales: sparkles at full intensity (default). */

/* ---- P5.A: high-contrast theme branch ---- */
.etp-character[data-etp-theme="high-contrast"] .etp-char-sparkle-idle-glyph {
  fill: #FFD86A;
  opacity: 0.96;
  filter: none;
}
.etp-character[data-etp-theme="high-contrast"] .etp-char-ear-disc {
  stroke: #FFFFFF;
  stroke-width: 0.5;
  filter: none;
}

/* ---- P5.B: high-contrast AAA theme branch ---- */
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-sparkle-idle-glyph {
  fill: #FFFFFF;
  opacity: 1;
  filter: none;
}
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-ear-disc {
  stroke: #FFFFFF;
  stroke-width: 0.6;
  filter: none;
}
.etp-character[data-etp-theme="high-contrast-aaa"] circle.etp-char-ear-module-cyan {
  fill: #00FFFF;
  filter: none;
}

/* ---- P5.C: prefers-contrast: more ---- */
@media (prefers-contrast: more) {
  .etp-char-sparkle-idle-glyph { opacity: 1; }
  .etp-char-ear-disc { stroke-width: 0.5; }
}

/* ---- P5.D: prefers-reduced-motion ---- */
@media (prefers-reduced-motion: reduce) {
  /* Phase 7 (2026-06-14) §10: the staggered idle twinkle bound above
   * is suppressed; sparkles fall back to their static per-group base
   * opacity. The .etp-char-ear-module-cyan transition (80ms) stays —
   * well under the WCAG 2.3.3 vestibular threshold. */
  .etp-char-sparkle-idle {
    animation: none !important;
    transform: none;
  }
}

/* =========================================================================
 * Phase 6 (2026-06-12, GAP-17b/c + GAP-17a no-op preservation) —
 * NEW ET+ WATERMARK INSIDE THE CHROME RING BELOW THE FACE PANEL.
 * -------------------------------------------------------------------------
 * Spec coord (340, 447) mapped via GAP-21 LOCKED uniform 0.18 scale +
 * recentre at (50, 36) → (50, 71.46). Sits just below the bottom edge
 * of the Phase 1 helmet ellipse (y_max = 36 + 29.2 = 65.2), in the
 * lower hemisphere of the chrome silhouette. Replaces the retired
 * .etp-char-neck-watermark (its DOM element is strict-deleted by
 * Phase 6 JS edit; the selector is preserved as a no-op below).
 *
 * Typography register matches PR #493 (semi-dark cyan #0E5F6D, JetBrains
 * Mono / IBM Plex Mono / Source Code Pro fallback, weight 800,
 * letter-spacing -0.06em, cyan glow drop-shadow) so the visual
 * continuity from the prior watermark register is preserved.
 *
 * Scope: avatar visual only. No animation (Phase 7 owns the new 16
 * keyframes), no programme tint re-binding, no other edits.
 * ========================================================================= */

.etp-char-watermark {
  font-family: 'JetBrains Mono', 'IBM Plex Mono', 'Source Code Pro', monospace;
  font-size: 5px;
  font-weight: 800;
  letter-spacing: -0.06em;
  /* Semi-dark cyan from PR #493 register — dark enough to read as a
   * watermark against the chrome ring's bright silver fill, saturated
   * enough to register distinctly as cyan (not blue, not teal). The
   * 0.92 opacity keeps a touch of softness so the mark reads as a
   * watermark rather than a printed glyph. The cyan glow drop-shadow
   * carries the brand emissive register from the chest-beacon
   * lineage (now retired) onto the watermark, integrating it with
   * the rest of the avatar's cyan-accent palette. */
  fill: #0E5F6D;
  fill-opacity: 0.92;
  paint-order: stroke fill;
  stroke: none;
  stroke-width: 0;
  filter: drop-shadow(0 0 0.8px rgba(0, 220, 236, 0.55));
  user-select: none;
  pointer-events: none;
}

/* GAP-17c — preserve the legacy .etp-char-neck-watermark selector as
 * a no-op rule so test greps + memory-doc references still resolve.
 * The element no longer exists in the DOM after the Phase 6 JS
 * strict-delete, so this rule matches nothing at runtime; it exists
 * purely so cluster-wide audit scripts that scan for the selector
 * still find it in the stylesheet. The selector is NOT inherited
 * by .etp-char-watermark (a different class entirely) so the no-op
 * is fully isolated. */
.etp-char-neck-watermark {
  /* No-op preservation — the element no longer exists in the DOM
   * (Phase 6 strict-delete). This rule documents the legacy
   * selector for grep-based audits. */
  display: none;
}

/* GAP-17a IIFE ORPHAN STRICT-HIDE — defence-in-depth.
 * -------------------------------------------------------------------------
 * The Phase 6 JS strict-delete removes the static .etp-char-neck rect +
 * 2 .etp-char-neck-segment lines + .etp-char-neck-trim rect + .etp-char-collar
 * path + .etp-char-neck-watermark text from the DOM. HOWEVER the Phase 19
 * IIFE at etp-character.js lines ~4041-4045 still fires on mount and
 * appends a 3rd .etp-char-neck-segment (with the --p19 modifier) at
 * y=73.5. That IIFE lives deep inside the Phase 19 nested wrapper and
 * cannot be removed without risking the surrounding T1.3 / T1.4 / T2.6
 * sibling injections (eyebrow ridges, sensor disc, iris-dot pattern,
 * etc.). Same containment strategy as the 2026-06-04 below-neck removal:
 * leave the IIFE alone, strict-hide its DOM output via CSS.
 *
 * The .etp-char-neck-specular overlay rect from the Phase 25 IIFE at
 * etp-character.js lines ~4765-4778 is NOT injected at all post-Phase-6
 * because anchor.querySelector('.etp-char-neck') returns null with the
 * static neck rect deleted; the IIFE's null-guard short-circuits.
 *
 * The strict-hide rule below covers ALL .etp-char-neck / -neck-segment /
 * -neck-trim / -collar / -neck-specular elements regardless of injection
 * source. Defence-in-depth — if any future IIFE inadvertently re-emits
 * any of these classes, the orphan is still hidden. */
.etp-char-neck,
.etp-char-neck-segment,
.etp-char-neck-segment--p19,
.etp-char-neck-trim,
.etp-char-neck-specular,
.etp-char-collar {
  display: none !important;
}

/* Chrome-scale silhouette (favicon-style miniatures) drops the new
 * watermark so the lower chrome ring reads cleanly at favicon sizes
 * where 5-px ET+ glyphs would blur into the silver fill. */
.etp-character[data-etp-scale="chrome"] .etp-char-watermark {
  display: none;
}

/* GAP-23 ACCESSIBILITY BRANCHES — Phase 6 watermark must honour the
 * four standing accessibility branches per the canonical scope rule
 * (see Phase 36 + Phase 38 prior phases). */

/* ---- P6.A: high-contrast theme (WCAG AA target) ---- */
.etp-character[data-etp-theme="high-contrast"] .etp-char-watermark {
  /* High-contrast palette wants a brighter cyan against the
   * chrome ring so the watermark is visible at the WCAG 2.1 AA
   * 4.5:1 contrast target. Drops the soft glow halo and hardens
   * the fill. */
  fill: #00DCEC;
  fill-opacity: 1;
  filter: none;
}

/* ---- P6.B: high-contrast-aaa theme (WCAG AAA target) ---- */
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-watermark {
  /* WCAG AAA palette wants pure white glyphs with a charcoal
   * hairline stroke for maximum legibility against the chrome
   * ring. Drops the cyan glow halo entirely. */
  fill: #FFFFFF;
  fill-opacity: 1;
  stroke: #000000;
  stroke-width: 0.4px;
  filter: none;
}

/* ---- P6.C: prefers-contrast: more ---- */
@media (prefers-contrast: more) {
  .etp-character .etp-char-watermark {
    /* Operating-system high-contrast preference (Windows
     * high-contrast mode, macOS Increase Contrast) — bumps the
     * watermark fill to a brighter cyan and drops the soft glow
     * filter so the mark reads cleanly against the chrome ring
     * even when the system inverts or recolours the surrounding
     * palette. */
    fill: #00DCEC;
    fill-opacity: 1;
    filter: none;
  }
}

/* ============================================================
 * Phase 7 (2026-06-12) — KIDS-FRIENDLY FUTURISTIC AVATAR AMENDMENT
 * GAP-18 + GAP-23 (deploy phase for the campaign)
 *
 * Adds 15 named keyframes covering every NEW element introduced
 * across Phases 1–6 of the et-ai-crispy-balloon plan. Names match
 * the spec verbatim:
 *
 *   float3d  → helmet 3D micro-translation (Phase 1 ellipse)
 *   ep       → LED eyebrow pulse (Phase 3 dual-colour 4-layer bows)
 *   bla      → eye blink layer (Phase 4 blink-distinct-from-eyelid)
 *   ld       → crown LED breathing (legacy 3-LED column)
 *   rim      → helmet rim-light shimmer (Phase 1 rim arc)
 *   sp       → face panel specular sweep (Phase 2 specular highlight)
 *   tw       → sparkle twinkle (Phase 5 4-staggered sparkles)
 *   (ck — cheek blush pulse — REMOVED per canonical spec)
 *   sh       → chrome shimmer (Phase 1 helmet iridescent sheen)
 *   fw       → face warmth radial pulse (Phase 2 GAP-5)
 *   sg       → sparkle soft-glow halo (Phase 5 outer halo on tw cycle)
 *   etw      → ET+ watermark glow (Phase 6 GAP-17b/c)
 *   gem      → forehead gem twinkle (Phase 2 GAP-8)
 *   bkt      → HUD bracket pulse (Phase 2 GAP-7 4 corner brackets)
 *   tring    → tech scan ring rotation (Phase 2 GAP-6)
 *
 * NOTE: spec header text says "16 named keyframes" but the explicit
 * name list contains 15. Phase 7 ships the 15 explicit names; if a
 * 16th is intended by the operator, it can be added in a follow-up
 * polish PR — none of the Phase 1–6 elements ship without animation
 * coverage in this set.
 *
 * Accessibility (GAP-23): a single `@media (prefers-reduced-motion:
 * reduce)` guard near the bottom of this block zeroes every Phase 7
 * animation. High-contrast theme branches (`data-etp-theme=
 * "high-contrast"` and `"high-contrast-aaa"`) plus
 * `@media (prefers-contrast: more)` suppress the decorative tints
 * on cheek blush + warmth pulse + sparkles + circuit lines so the
 * face panel stays high-legibility regardless of motion preference.
 *
 * All keyframes use unique `etp-` prefixed names so the existing
 * 83 keyframe library (state-machine drivers + per-state micro-
 * motion) is unaffected. Sacred invariants per the standing rules:
 * `window.etpCharacter.{mount, setState, setProgramme, setPersona,
 * eyesLookAt, celebrate, alert}` API + 8 emotional states + 5
 * programmes + 4 personas all preserved byte-identical.
 * ============================================================ */

/* ---- float3d: helmet 3D micro-translation (Phase 1) ---- */
@keyframes etp-float3d {
  0%   { transform: translate3d(0, 0, 0); }
  50%  { transform: translate3d(0, -0.4px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* ---- ep: LED eyebrow pulse (Phase 3) ---- */
@keyframes etp-ep {
  0%   { opacity: 0.85; filter: drop-shadow(0 0 0.4px var(--etp-light, #00DCEC)); }
  50%  { opacity: 1.00; filter: drop-shadow(0 0 1.2px var(--etp-light, #00DCEC)); }
  100% { opacity: 0.85; filter: drop-shadow(0 0 0.4px var(--etp-light, #00DCEC)); }
}

/* ---- bla: blink layer (Phase 4 GAP-12) ---- */
@keyframes etp-bla {
  0%   { transform: scaleY(1); opacity: 0; }
  92%  { transform: scaleY(1); opacity: 0; }
  95%  { transform: scaleY(0.05); opacity: 1; }
  98%  { transform: scaleY(0.05); opacity: 1; }
  100% { transform: scaleY(1); opacity: 0; }
}

/* ---- ld: crown LED breathing ---- */
@keyframes etp-ld {
  0%   { opacity: 0.55; }
  50%  { opacity: 1.00; }
  100% { opacity: 0.55; }
}

/* ---- rim: helmet rim-light shimmer (Phase 1) ---- */
@keyframes etp-rim {
  0%   { opacity: 0.45; }
  50%  { opacity: 0.85; }
  100% { opacity: 0.45; }
}

/* ---- sp: face panel specular sweep (Phase 2) ---- */
@keyframes etp-sp {
  0%   { opacity: 0.35; transform: translateX(-1.2px); }
  50%  { opacity: 0.65; transform: translateX( 0.0px); }
  100% { opacity: 0.35; transform: translateX( 1.2px); }
}

/* ---- tw: sparkle twinkle (Phase 5) ---- */
@keyframes etp-tw {
  0%   { opacity: 0; transform: scale(0.4) rotate(0deg); }
  35%  { opacity: 1; transform: scale(1.0) rotate(36deg); }
  70%  { opacity: 0; transform: scale(0.4) rotate(72deg); }
  100% { opacity: 0; transform: scale(0.4) rotate(72deg); }
}

/* ---- ck: cheek blush pulse — REMOVED per canonical spec (the blush
   discs are gone; the spec forbids cheek blush, so this keyframe and
   every `.etp-char-cheek-blush` usage are dropped). ---- */

/* ---- sh: chrome shimmer (Phase 1 helmet iridescent sheen) ---- */
@keyframes etp-sh {
  0%   { opacity: 0.18; }
  50%  { opacity: 0.32; }
  100% { opacity: 0.18; }
}

/* ---- fw: face warmth radial pulse (Phase 2 GAP-5) ---- */
@keyframes etp-fw {
  0%   { opacity: 0.55; }
  50%  { opacity: 0.85; }
  100% { opacity: 0.55; }
}

/* ---- sg: sparkle soft-glow halo (Phase 5) ---- */
@keyframes etp-sg {
  0%   { opacity: 0.15; transform: scale(0.85); }
  50%  { opacity: 0.45; transform: scale(1.20); }
  100% { opacity: 0.15; transform: scale(0.85); }
}

/* ---- etw: ET+ watermark glow (Phase 6 GAP-17b/c) ---- */
@keyframes etp-etw {
  0%   { filter: drop-shadow(0 0 0.6px rgba(0, 220, 236, 0.45)); }
  50%  { filter: drop-shadow(0 0 1.4px rgba(0, 220, 236, 0.75)); }
  100% { filter: drop-shadow(0 0 0.6px rgba(0, 220, 236, 0.45)); }
}

/* ---- gem: forehead gem twinkle (Phase 2 GAP-8) ---- */
@keyframes etp-gem {
  0%   { opacity: 0.65; transform: rotate(0deg)   scale(1.00); }
  50%  { opacity: 1.00; transform: rotate(180deg) scale(1.10); }
  100% { opacity: 0.65; transform: rotate(360deg) scale(1.00); }
}

/* ---- gem-pulse: §7 ambient-aura breathe behind the prismatic gem.
   A soft cyan glow that swells + fades; no rotation (the facets stay
   put, only the surrounding aura breathes). ---- */
@keyframes etp-gem-pulse {
  0%   { opacity: 0.40; transform: scale(1.00); }
  50%  { opacity: 0.70; transform: scale(1.10); }
  100% { opacity: 0.40; transform: scale(1.00); }
}

/* ---- gem-beam: §7 upward-beam pulse. The vertical cyan ray
   brightens + lengthens slightly, reading as energy rising from the
   gem toward the crown. ---- */
@keyframes etp-gem-beam {
  0%   { opacity: 0.28; transform: scaleY(0.92); }
  50%  { opacity: 0.58; transform: scaleY(1.06); }
  100% { opacity: 0.28; transform: scaleY(0.92); }
}

/* ---- gem-sparkle: §7 apex 4-pointed twinkle. ---- */
@keyframes etp-gem-sparkle {
  0%   { opacity: 0;    transform: scale(0.4) rotate(0deg); }
  45%  { opacity: 0.95; transform: scale(1.0) rotate(45deg); }
  60%  { opacity: 0.95; transform: scale(1.0) rotate(45deg); }
  100% { opacity: 0;    transform: scale(0.4) rotate(90deg); }
}

/* ---- bkt: HUD bracket pulse (Phase 2 GAP-7) ---- */
@keyframes etp-bkt {
  0%   { opacity: 0.55; }
  25%  { opacity: 0.95; }
  50%  { opacity: 0.55; }
  75%  { opacity: 0.95; }
  100% { opacity: 0.55; }
}

/* ---- tring: tech scan ring rotation (Phase 2 GAP-6) ---- */
@keyframes etp-tring {
  0%   { transform: rotate(0deg);   opacity: 0.45; }
  50%  { transform: rotate(180deg); opacity: 0.72; }
  100% { transform: rotate(360deg); opacity: 0.45; }
}

/* ============================================================
 * Phase 7 — APPLY rules.
 *
 * Each keyframe binds to the canonical selector for its element.
 * Animation timings honour the design intent of "alive, never
 * robotic": every period co-prime with neighbours so the combined
 * motion never re-syncs visually. All apply rules use the existing
 * etp-character cascade — animation triggers only inside
 * `<html class="etp-ai-tutor">` per Phase 31 activation gate.
 * ============================================================ */

.etp-character .etp-char-helmet-shell {
  animation: etp-float3d 6.8s ease-in-out infinite;
  transform-origin: 50px 36px;
}

.etp-character .etp-char-helmet-grain,
.etp-character .etp-char-helmet-iridescent {
  animation: etp-sh 5.3s ease-in-out infinite;
  transform-origin: 50px 36px;
}

.etp-character .etp-char-helmet-rim,
.etp-character .etp-char-helmet-rim-light {
  animation: etp-rim 4.7s ease-in-out infinite;
  transform-origin: 50px 36px;
}

.etp-character .etp-char-eyebrow,
.etp-character .etp-char-led-eyebrow {
  animation: etp-ep 3.9s ease-in-out infinite;
  transform-origin: center;
}

.etp-character .etp-char-eye-blink {
  animation: etp-bla 7.1s ease-in-out infinite;
  transform-origin: center;
}

.etp-character .etp-char-crown-led {
  animation: etp-ld 3.2s ease-in-out infinite;
}

.etp-character .etp-char-face-specular {
  animation: etp-sp 8.1s ease-in-out infinite;
  transform-origin: 50px 36px;
}

.etp-character .etp-char-sparkle {
  animation: etp-tw 4.6s ease-in-out infinite;
  transform-origin: center;
}
.etp-character .etp-char-sparkle--a { animation-delay: 0.0s; }
.etp-character .etp-char-sparkle--b { animation-delay: 1.15s; }
.etp-character .etp-char-sparkle--c { animation-delay: 2.30s; }
.etp-character .etp-char-sparkle--d { animation-delay: 3.45s; }

.etp-character .etp-char-sparkle-halo {
  animation: etp-sg 4.6s ease-in-out infinite;
  transform-origin: center;
}

.etp-character .etp-char-face-warmth {
  animation: etp-fw 6.3s ease-in-out infinite;
  transform-origin: 50px 36px;
}

.etp-character .etp-char-watermark {
  animation: etp-etw 4.1s ease-in-out infinite;
  transform-origin: center;
}

/* Phase 7 (2026-06-14) §7 fix — the gem is now the `.etp-char-face-gem`
   group (was a single `.etp-char-forehead-gem` polygon that never
   existed, so the `etp-gem` pulse matched nothing). The aura breathes
   via `gem-pulse` and the upward beam pulses via `gem-beam`; the faceted
   body itself stays still so the prism reads crisply. */
.etp-character .etp-char-gem-aura {
  animation: etp-gem-pulse 9.4s ease-in-out infinite;
}
.etp-character .etp-char-gem-beam {
  animation: etp-gem-beam 6.3s ease-in-out infinite;
}

/* Phase 6 (2026-06-14) §6 fix — the HUD corner brackets carry the
   class `.etp-char-face-hud-bracket` (JS), but this `bkt` pulse rule
   previously targeted `.etp-char-hud-bracket` (no `face-`), which
   matched nothing, so the brackets never pulsed. Corrected selector. */
.etp-character .etp-char-face-hud-bracket {
  animation: etp-bkt 2.9s ease-in-out infinite;
  transform-origin: center;
}

.etp-character .etp-char-tech-scan-ring {
  animation: etp-tring 12.7s linear infinite;
  transform-origin: 50px 36px;
  transform-box: fill-box;
}

/* ============================================================
 * Phase 7 — Accessibility (GAP-23).
 *
 * (i) prefers-reduced-motion: reduce → zero every Phase 7 animation
 * (ii) high-contrast theme branches → suppress decorative tints on
 *      cheek blush, warmth pulse, sparkles, HUD brackets (so the
 *      face panel stays at maximum legibility)
 * (iii) @media (prefers-contrast: more) → same decorative suppression
 *
 * Phase 6's P6.A/B/C/D watermark a11y rules continue to apply on top
 * of the etw animation; the cyan-glow filter and animation both
 * collapse cleanly under reduced-motion + high-contrast.
 * ============================================================ */

@media (prefers-reduced-motion: reduce) {
  .etp-character .etp-char-helmet-shell,
  .etp-character .etp-char-helmet-grain,
  .etp-character .etp-char-helmet-iridescent,
  .etp-character .etp-char-helmet-rim,
  .etp-character .etp-char-helmet-rim-light,
  .etp-character .etp-char-eyebrow,
  .etp-character .etp-char-led-eyebrow,
  .etp-character .etp-char-eye-blink,
  .etp-character .etp-char-crown-led,
  .etp-character .etp-char-face-specular,
  .etp-character .etp-char-sparkle,
  .etp-character .etp-char-sparkle-halo,
  .etp-character .etp-char-face-warmth,
  .etp-character .etp-char-watermark,
  .etp-character .etp-char-gem-aura,
  .etp-character .etp-char-gem-beam,
  .etp-character .etp-char-gem-sparkle,
  .etp-character .etp-char-face-hud-bracket,
  .etp-character .etp-char-tech-scan-ring {
    animation: none !important;
    transform: none !important;
    filter: none !important;
  }
}

.etp-character[data-etp-theme="high-contrast"] .etp-char-face-warmth,
.etp-character[data-etp-theme="high-contrast"] .etp-char-sparkle,
.etp-character[data-etp-theme="high-contrast"] .etp-char-sparkle-halo,
.etp-character[data-etp-theme="high-contrast"] .etp-char-circuit-accent {
  display: none;
}

.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-face-warmth,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-sparkle,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-sparkle-halo,
.etp-character[data-etp-theme="high-contrast-aaa"] .etp-char-circuit-accent {
  display: none;
}

@media (prefers-contrast: more) {
  .etp-character .etp-char-face-warmth,
  .etp-character .etp-char-sparkle,
  .etp-character .etp-char-sparkle-halo,
  .etp-character .etp-char-circuit-accent {
    opacity: 0.15;
  }
}

/* =========================================================================
 * AVATAR AMEND 2026-06-16 — visual-only amendment (6 changes)
 * Operator brief: scale-down + centre, right-ear symmetry, new slim lips
 * in the eye colour, larger/bolder engraved ET+ watermark, removal of all
 * face lighting, clean chrome circle. Strict avatar-visual scope only.
 *
 * Additive + reversible: this block only APPENDS overrides at the file
 * tail (later-in-cascade wins ties) — no prior rule is deleted. Remove
 * this whole block + revert the viewBox + lip-group JS edits to restore.
 *
 * Change #1 (reduce 10% + centre) lives in etp-character.js as a viewBox
 * edit (0 0 100 130 -> -5.556 -36.222 111.111 144.444): same aspect ratio
 * so no letterbox change, content renders at 90%, viewBox centre = the
 * chrome-circle centre (50,36) so the head cluster sits centred in the
 * round frame. viewBox scales EVERY element uniformly (template body +
 * all ~50 tail-IIFE-injected children) and is immune to the state-driven
 * `.etp-char-svg` transforms. No `<g>` wrap (would miss IIFE children).
 * ========================================================================= */

/* --- #3: new slim lips, eye colour, no glow ----------------------------- */
.etp-character .etp-char-lip-upper,
.etp-character .etp-char-lip-lower {
  fill: none;
  stroke: var(--etp-eye-iris, #4BAEE8);
  stroke-width: 0.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 1;
  filter: none;
}

/* --- #4: engraved ET+ watermark — eye colour, +7%, bolder/thicker ------- */
.etp-character .etp-char-watermark {
  font-size: 5.35px;          /* 5 x 1.07 = +7% (SVG units, relative to avatar) */
  font-weight: 900;
  fill: var(--etp-eye-iris, #4BAEE8);
  fill-opacity: 1;
  stroke: var(--etp-eye-iris, #4BAEE8);
  stroke-width: 0.18;
  paint-order: stroke fill;   /* thicker glyph body */
  /* Engraved into the chrome: bright lower edge + dark upper edge so the
     mark reads as carved into the polished ring (light from above). */
  filter:
    drop-shadow(0 0.35px 0 rgba(255, 255, 255, 0.55))
    drop-shadow(0 -0.3px 0.2px rgba(0, 0, 0, 0.55));
  animation: none;            /* static engraved mark, not a sweeping label */
}

/* --- #2: right-ear symmetry — drive BOTH ears from the same breathe phase
 * so the right ear matches the left at every frame (was anti-phase via a
 * negative animation-delay + the breathe-r keyframe). Same selector +
 * specificity as the source rule, appended later -> wins the tie. -------- */
.etp-character:not([data-etp-voice-active="1"]) .etp-char-ear-module--r .etp-char-ear-module-cyan {
  animation: etp-ear-idle-breathe-l 4s ease-in-out infinite;
  animation-delay: 0s;
}

/* --- #5: remove ALL face lighting + facial lines + glow; keep only eyes,
 * nose, lips. Also strip the overly-dramatic environment (orbit rings,
 * particles, holo-shimmer, scan-sweep, neural traces, 4-point stars) and
 * the face/head HUD adornments (gem, scan-ring, circuits, brackets,
 * data-ribbon, visor-scan, sensor-disc, tool-plane, holo-ring).
 * KEEP (face): eye iris/pupil/pupil-dot/lens + lens catchlight + brow
 * shape + nose + new lips. KEEP (grounding): cosmic-aura, contact-shadow,
 * floor-reflection (soft, not dramatic). ------------------------------- */
.etp-character .etp-char-face-warmth-pulse,
.etp-character .etp-char-face-scan-ring,
.etp-character .etp-char-face-circuits,
.etp-character .etp-char-face-circuit,
.etp-character .etp-char-face-hud-brackets,
.etp-character .etp-char-face-gem,
.etp-character .etp-char-face-specular,
.etp-character .etp-char-face-hud,
.etp-character .etp-char-head-gloss,
.etp-character .etp-char-specular,
.etp-character .etp-char-specular--warm,
.etp-character .etp-char-eye-halo,
.etp-character .etp-char-eye-brow-led,
.etp-character .etp-char-eye-brow-led--l,
.etp-character .etp-char-eye-brow-led--r,
.etp-character .etp-char-eye-brow-led-bar,
.etp-character .etp-char-eye-brow-led-catch,
.etp-character .etp-char-eye-brow-led-glow,
.etp-character .etp-char-eye-brow-led-tint,
.etp-character .etp-char-eye-star,
.etp-character .etp-char-eye-star--l,
.etp-character .etp-char-eye-star--r,
.etp-character .etp-char-data-ribbon,
.etp-character .etp-char-visor-scan,
.etp-character .etp-char-sensor-disc,
.etp-character .etp-char-orbits,
.etp-character .etp-char-orbit-ellipse,
.etp-character .etp-char-orbit-ring,
.etp-character .etp-char-orbit-ring--1,
.etp-character .etp-char-orbit-ring--2,
.etp-character .etp-char-orbit-ring--3,
.etp-character .etp-char-orbit-bead,
.etp-character .etp-char-holo-shimmer,
.etp-character .etp-char-scan-sweep,
.etp-character .etp-char-neural,
.etp-character .etp-char-neural-node,
.etp-character .etp-char-neural-trace,
.etp-character .etp-char-neural-trace--1,
.etp-character .etp-char-neural-trace--2,
.etp-character .etp-char-neural-trace--3,
.etp-character .etp-char-neural-trace--4,
.etp-character .etp-char-particle,
.etp-character .etp-char-particles,
.etp-character .etp-char-holo-ring,
.etp-character .etp-char-tool-plane,
.etp-character .etp-char-tool-plane-icon,
.etp-character .etp-char-lower-hint,
.etp-character .etp-char-chest-facets,
.etp-character .etp-char-chest-core-specular {
  display: none !important;
}

/* --- #6: clean chrome circle — keep shell base + one subtle top sheen
 * (helmet-keylight); strip the heavy/inconsistent shine layers + vents. - */
.etp-character .etp-char-helmet-grain,
.etp-character .etp-char-helmet-fresnel,
.etp-character .etp-char-helmet-rim-light,
.etp-character .etp-char-helmet-specular,
.etp-character .etp-char-vent-slit {
  display: none !important;
}

/* ════════════════════════════════════════════════════════════════════
 * AVATAR AMEND 2026-06-16 r2 — visual-only, appended AFTER the #529
 * appendix so equal-specificity rules win the cascade. Holds: #2
 * drift-free blink, #3 ear-cyan recolour to the eye hue, #5 watermark
 * bigger + bolder. (#1 face scale lives in etp-character.js; #4 slim
 * lips / #6 face-lighting removal / #7 clean chrome already shipped in
 * #529 and are unchanged.) All scoped under .etp-character so the other
 * 12 products at edtechplus.ai/courses/* are untouched.
 * ════════════════════════════════════════════════════════════════════ */

/* --- r2 #2: drift-free eye open/close, all states ----------------------
 * Root cause of the up/down drift: the blink (@keyframes etp-blink-pulse,
 * a scaleY collapse) was applied to .etp-char-eye with transform-origin
 * 50% 50% but NO transform-box, so in SVG the origin resolved against the
 * view-box and each blink pulled the eye toward the viewport centre.
 * fill-box re-pivots about each eye's OWN bbox centre -> zero positional
 * shift. This rule wins transform-box/origin for every eye animation
 * (the 4-state blink AND the [data-etp-double-blink] tell). */
.etp-character .etp-char-eye {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}
/* Extend the open/close blink to EVERY state (it shipped on only 4 —
 * neutral/listening/encouraging/waiting). Specificity 0,1,0 so it fills
 * the gap states without clobbering the 4-state blink (identical) or the
 * double-blink tell (both 0,2,0). Reduced-motion animation:none !important
 * still wins, so accessibility is preserved. */
@media (prefers-reduced-motion: no-preference) {
  .etp-char-eye {
    animation: etp-blink-pulse 7.1s linear infinite;
  }
}

/* --- r2 #3: recolour the ear cyan accents to the EYE hue ----------------
 * The visible ear cyan (cyan-ring stroke + module-cyan fill) used the
 * --etp-eye-cyan token (#2BE5FF); recolour both to --etp-eye-iris so the
 * ears' cyan matches the eyes exactly. The voice-meter scaleY transform +
 * transition on circle.etp-char-ear-module-cyan are left untouched. */
.etp-character .etp-char-ear-cyan-ring {
  stroke: var(--etp-eye-iris, #4BAEE8);
  filter: drop-shadow(0 0 0.4px rgba(91, 180, 232, 0.5));
}
.etp-character circle.etp-char-ear-module-cyan {
  fill: var(--etp-eye-iris, #4BAEE8);
  filter: drop-shadow(0 0 0.45px rgba(91, 180, 232, 0.55));
}

/* --- r2 #5: ET+ watermark — bigger + bolder ----------------------------
 * Builds on the #529 engraved watermark (eye colour, weight 900, carved
 * dual-edge drop-shadow). Operator asked for bolder: bump size 5.35 ->
 * 6.2px (~+16%) and the engrave stroke 0.18 -> 0.3. Stays centred at the
 * lower chrome ring (x=50, y=63.5); fill/stroke + carved filter inherited
 * from the #529 rule. Chrome-scale + high-contrast watermark rules keep
 * their higher-specificity overrides (favicon stays tiny, a11y intact). */
.etp-character .etp-char-watermark {
  font-size: 6.2px;
  stroke-width: 0.3;
}

/* AVATAR AMEND 2026-06-16 r2 #6 — remove residual face lighting.
 * The #529 hide list targeted class names that do not match the rendered
 * elements, so the forehead gem, the face HUD, the sparkle "star",
 * face scanlines, the holo overlay and the crown bevel were all still
 * showing on the face. Operator chose "strip decor, keep eye glow + soft
 * aura": hide exactly these decorations; the eyes' own glow/lens shine,
 * the soft head aura (cosmic-aura), the ears and the chrome are KEPT.
 * Result: face = eyes + nose + slim lips only. Additive + reversible. */
.etp-character .etp-char-gem-aura,
.etp-character .etp-char-gem-body,
.etp-character .etp-char-gem-beam,
.etp-character .etp-char-gem-edge,
.etp-character .etp-char-gem-facet,
.etp-character .etp-char-gem-specular,
.etp-character .etp-char-gem-sparkle,
.etp-character .etp-char-face-hud-bracket,
.etp-character .etp-char-face-hud-mark,
.etp-character .etp-char-face-hud-ring,
.etp-character .etp-char-face-scanlines,
.etp-character .etp-char-holo-overlay,
.etp-character .etp-char-sparkle,
.etp-character .etp-char-sparkle-idle,
.etp-character .etp-char-sparkle-idle-glyph,
.etp-character .etp-char-sparkles,
.etp-character .etp-char-sparkles-amend,
.etp-character .etp-char-body-bevel {
  display: none !important;
}

/* =========================================================================
 * AVATAR AMEND 2026-06-17 — all lip parts in the eye colour
 * -------------------------------------------------------------------------
 * Operator: "let all the lip parts be in the same color as the eyes."
 *
 * Every lip fill reads the shared `--etp-mouth` token, which was cyan
 * rgba(0, 220, 236, 0.92) (#00DCEC) — a visibly different hue from the
 * eyes. Three lip systems consume it:
 *   - Phase 17 .etp-char-mouth-upper-lip    (base 4250, Phase35 7223)
 *   - Phase 4  .etp-char-mouth-cupids-bow    (10219)
 *   - Phase 4  .etp-char-mouth-rounded-lower (10235)
 * (.etp-char-mouth-lower-lip is display:none since 2026-06-04; the mouth
 *  cavity is the interior opening, not a lip, and stays as-is.)
 *
 * Re-point the token at the eye iris hue (--etp-eye-iris #5BB4E8) at the
 * .etp-character scope so EVERY token-driven lip fill matches the eyes in
 * one stroke. The static slim lips (.etp-char-lip-upper/.etp-char-lip-lower)
 * already stroke --etp-eye-iris (round-1 #3) — reaffirmed below so the
 * whole mouth reads as one eye-colour unit.
 *
 * Accessibility preserved: the high-contrast AA + AAA branches set lip
 * fills DIRECTLY (not via the token) at higher specificity (4409, 10254,
 * 10266), so they still win and keep their legibility colours — clobbering
 * the token value under those themes is harmless (no AAA lip reads it).
 * Additive + reversible (delete this block to restore the cyan lips).
 * ========================================================================= */
.etp-character {
  --etp-mouth: var(--etp-eye-iris, #5BB4E8);
}
.etp-character .etp-char-lip-upper,
.etp-character .etp-char-lip-lower {
  stroke: var(--etp-eye-iris, #5BB4E8);
}

/* =========================================================================
 * AVATAR AMEND 2026-06-17 — "ET+" watermark relocated above the face
 * -------------------------------------------------------------------------
 * Operator: "Place the 'ET+' watermark above the face, positioned directly
 * in the centre of the chrome circle ... engrave it into the chrome circle
 * ... should look like part of the chrome circle design rather than a
 * separate label."
 *
 * The watermark <text class="etp-char-watermark"> moved from the LOWER
 * chrome ring (y=63.5, below the face) to the UPPER chrome ring (x=50,
 * y=10.5, above the face crown) in etp-character.js. The decorative
 * crown-LED pin (.etp-char-crown-leds — a thin 3-dot vertical column on a
 * rail at x=50, cy 11.4/14/16.6) sat at that exact spot. Retire it here so
 * the relocated watermark reads as integrated chrome-circle design, not a
 * label competing with a stray LED column. The LED pin is pure decoration
 * (no feature/behaviour), consistent with the round-2 "strip face lighting"
 * direction. Additive + reversible (delete this rule to restore the LEDs).
 *
 * The watermark's bold + eye-colour + engraved styling (font-size 6.2px,
 * weight 900, fill/stroke var(--etp-eye-iris), carved dual-edge drop-shadow)
 * is unchanged from round-2 (#5) — this round only repositions it.
 * ========================================================================= */
.etp-character .etp-char-crown-leds {
  display: none !important;
}

/* End of etp-character.css */
