/* Name: Yip Kai Jun Jayden */
/* Student Admin No: 241266U */
/* Tutorial Group: BF2501 */

/* ArcaneVault interaction layer — the small, site-wide "feel" pass.
   Layered on top of site.css (loaded after it) so the two files never overlap.
   Everything here only extends the existing "Sapphire & Amber" tokens, eases
   and patterns rather than introducing a new look: a consistent button lift /
   press, a persistent nav underline for the current page, clearer focus and
   invalid-field states, animated underlines on prose links, and a settled
   toast entrance.

   It is all scoped under .av-fx — a class av-interactions.js adds to <html> on
   every page EXCEPT the splash (where the script no-ops), so the sealed front
   door is provably untouched, and so the enhancements are pure progressive
   enhancement (the base site.css styling stands on its own without JS). The
   reduced-motion block in site.css already neutralises transitions globally;
   the foot of this file adds a couple of belt-and-braces fallbacks. */

/* ---------- Buttons: one tactile language everywhere ----------
   site.css already lifts/presses .btn-primary; here we extend the same gesture
   to the ghost and danger buttons so every button across the site reacts the
   same way — a soft hover lift, a small press on click, and a warm focus halo
   that sits alongside the global focus-visible outline. */
.av-fx .av-btn-ghost:not(:disabled):hover,
.av-fx .av-btn-danger:not(:disabled):hover {
  transform: translateY(-2px);
}
.av-fx .btn:not(:disabled):active {
  transform: translateY(1px);
}
.av-fx .btn:focus-visible {
  box-shadow: 0 0 0 4px rgba(240, 169, 60, 0.18);
}
/* A disabled / in-flight button shouldn't keep inviting the pointer. */
.av-fx .btn:disabled,
.av-fx .btn.av-btn-busy {
  transform: none;
}

/* ---------- Nav: mark where the visitor is ----------
   site.css draws the amber underline on hover/focus; av-interactions.js tags the
   link for the current page so that same underline simply stays lit, giving the
   nav a clear "you are here". The focus-visible reveal keeps keyboard users in
   step with the hover affordance. */
.av-fx .av-navbar .nav-link.av-nav-active {
  color: var(--av-text);
}
.av-fx .av-navbar .nav-link.av-nav-active::after,
.av-fx .av-navbar .nav-link:focus-visible::after {
  transform: scaleX(1);
}

/* The brand sigil leans and warms with the existing wordmark shimmer, so the
   whole mark feels alive on hover — not just the text. */
.av-fx .av-brand .av-sigil-sm {
  transition: transform 0.5s var(--av-ease), filter 0.4s ease;
}
.av-fx .av-brand:hover .av-sigil-sm {
  transform: rotate(12deg) scale(1.06);
  filter: drop-shadow(0 0 6px rgba(240, 169, 60, 0.4));
}

/* ---------- Links: a consistent animated underline ----------
   Inline prose links (auth alt-links, the privacy document, error copy) grow an
   underline from the left on hover, in step with the global colour shift to
   amber. Buttons, nav links and tag pills are excluded by targeting only the
   text-flow contexts. */
.av-fx .av-altlink a,
.av-fx .av-policy-section a,
.av-fx .av-policy-list a,
.av-fx .av-error-blurb a {
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-repeat: no-repeat;
  background-position: 0 100%;
  transition: background-size 0.25s var(--av-ease), color 0.2s ease;
}
.av-fx .av-altlink a:hover,
.av-fx .av-policy-section a:hover,
.av-fx .av-policy-list a:hover,
.av-fx .av-error-blurb a:hover,
.av-fx .av-altlink a:focus-visible,
.av-fx .av-policy-section a:focus-visible,
.av-fx .av-policy-list a:focus-visible,
.av-fx .av-error-blurb a:focus-visible {
  background-size: 100% 1px;
}

/* ---------- Forms: a clear, on-brand invalid state ----------
   jQuery unobtrusive validation flags a bad field with .input-validation-error
   (and Bootstrap uses .is-invalid); site.css colours the message but left the
   input itself unmarked. Tint the border and focus glow in the same coral the
   validation text uses, so the field and its message read as one. */
.av-fx .form-control.input-validation-error,
.av-fx .form-select.input-validation-error,
.av-fx .form-control.is-invalid,
.av-fx .form-select.is-invalid {
  border-color: var(--av-coral);
}
.av-fx .form-control.input-validation-error:focus,
.av-fx .form-select.input-validation-error:focus,
.av-fx .form-control.is-invalid:focus,
.av-fx .form-select.is-invalid:focus {
  border-color: var(--av-coral);
  box-shadow: 0 0 0 3px rgba(255, 122, 107, 0.18);
}

/* ---------- Cards: keyboard parity with the pointer ----------
   The collection tilt-cards already lift under a pointer; mirror that elevation
   when a control inside is focused, so tabbing through reads the same as
   hovering. */
.av-fx .av-item-card:focus-within {
  border-color: var(--av-amber-line);
  box-shadow: 0 22px 54px rgba(0, 0, 0, 0.42);
}

/* ---------- Submit buttons: responsive + double-submit safe ----------
   av-interactions.js adds .av-btn-busy to a CRUD form's submit button while the
   post is in flight (reusing the spinner site.css already defines for the auth
   buttons), so the action feels immediate and a double-click can't fire twice. */
.av-fx .btn.av-btn-busy {
  pointer-events: none;
}

/* ---------- Toast: a settled entrance + graceful exit ----------
   The flash message slides down and fades in once, then av-interactions.js
   auto-dismisses it (pausing if hovered). NO fill-mode: `both` would hold the
   entrance's final keyframe at animation-origin strength forever, which
   out-cascades the .av-toast-hide / Bootstrap .fade exit transitions and
   turns every dismiss into a hard pop. */
.av-fx .av-toast {
  animation: avToastIn 0.45s var(--av-ease);
}
@keyframes avToastIn {
  from { opacity: 0; transform: translateY(-12px); }
  to { opacity: 1; transform: none; }
}

/* ---------- Departure bar (job 4 in av-interactions.js) ----------
   Fixed 2px amber rule across the top of the viewport. It rests at scaleX(0)
   and, once a same-origin navigation is committed, runs out fast then creeps —
   it never reaches the far edge because the next document replaces it. */
.av-fx .av-nav-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 2000;
  background: linear-gradient(90deg, var(--av-amber), var(--av-amber-soft));
  transform-origin: 0 50%;
  transform: scaleX(0);
  pointer-events: none;
}
.av-fx .av-nav-bar--go {
  animation: avNavGo 1.8s cubic-bezier(0.2, 0.6, 0.3, 1) both;
}
@keyframes avNavGo {
  0% { transform: scaleX(0); }
  55% { transform: scaleX(0.7); }
  100% { transform: scaleX(0.93); }
}

/* ---------- Reduced motion ----------
   site.css already flattens transitions/animations globally; these make sure the
   toast and link underline settle in their resting state rather than mid-motion.
   The departure bar's JS never even creates it under reduced motion; the display
   rule below is belt-and-braces for a preference flipped after load. */
@media (prefers-reduced-motion: reduce) {
  .av-fx .av-nav-bar { display: none; }
  .av-fx .av-toast { animation: none; }
  .av-fx .av-altlink a,
  .av-fx .av-policy-section a,
  .av-fx .av-policy-list a,
  .av-fx .av-error-blurb a { transition: color 0.2s ease; }
}

/* ---------- Assistant chat (conversational surface; JS-created, so no .av-fx gate) ---------- */

.av-chat-card { display: flex; flex-direction: column; }
.av-chat-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; }

.av-chat-thread {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  max-height: 52vh;
  min-height: 16rem; /* the empty state keeps presence above the composer card */
  overflow-y: auto;
  padding: 0.25rem 0.25rem 0.75rem;
  scroll-behavior: smooth;
}

.av-chat-row { display: flex; align-items: flex-end; gap: 0.5rem; }
.av-chat-row--user { justify-content: flex-end; }

/* Ringed badge: the 1px face border, a 2px breathing gap, then a hairline
   outer ring (outline follows the radius) — the same engraved-medallion
   language as the seal. The radial sheen keeps the face reading as struck
   metal rather than a flat chip. */
.av-chat-sigil {
  flex: 0 0 auto;
  width: 1.7rem;
  height: 1.7rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--av-amber-line);
  color: var(--av-amber);
  font-size: 0.8rem;
  background: radial-gradient(circle at 35% 30%,
      color-mix(in srgb, var(--av-amber) 22%, transparent),
      color-mix(in srgb, var(--av-amber) 6%, transparent) 72%);
  outline: 1px solid color-mix(in srgb, var(--av-amber) 38%, transparent);
  outline-offset: 2px;
}

.av-chat-bubble {
  max-width: 82%;
  padding: 0.55rem 0.85rem;
  border-radius: 1rem;
  font-size: 0.95rem;
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.av-chat-bubble--user {
  background: color-mix(in srgb, var(--av-sapphire) 22%, transparent);
  border: 1px solid var(--av-line-2);
  color: var(--av-text);
  border-bottom-right-radius: 0.3rem;
}
.av-chat-bubble--assistant {
  background: var(--av-panel-2);
  border: 1px solid var(--av-line);
  color: var(--av-text-2);
  border-bottom-left-radius: 0.3rem;
}

/* Inking caret — rides the text end while assistant-chat.js paces a fresh
   reply in, gone the frame the reveal lands. The reduced-motion path never
   adds this class (the reply appears whole), so there is nothing to switch
   off beyond the belt-and-braces line in the block below. */
.av-chat-bubble--inking::after {
  content: "";
  display: inline-block;
  width: 2px;
  height: 1em;
  margin-left: 2px;
  vertical-align: -0.15em;
  background: var(--av-amber);
  animation: avChatCaret 0.9s steps(2, start) infinite;
}
@keyframes avChatCaret { 50% { opacity: 0; } }

.av-chat-note {
  margin: 0.15rem auto;
  text-align: center;
  font-size: 0.8rem;
  color: var(--av-mut);
  max-width: 34rem;
}

.av-chat-typing { display: inline-flex; gap: 0.3rem; padding: 0.7rem 0.85rem; }
.av-chat-dot {
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--av-sky);
  opacity: 0.5;
  animation: avChatDot 1.1s ease-in-out infinite;
}
.av-chat-dot:nth-child(2) { animation-delay: 0.18s; }
.av-chat-dot:nth-child(3) { animation-delay: 0.36s; }
@keyframes avChatDot {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
  30% { transform: translateY(-4px); opacity: 1; }
}

.av-chat-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.35rem 0 0.75rem; }
.av-chat-chip {
  border: 1px solid var(--av-line-2);
  background: color-mix(in srgb, var(--av-sapphire) 10%, transparent);
  color: var(--av-text-2);
  border-radius: 2rem;
  padding: 0.35em 0.9em;
  font-size: 0.85rem;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.av-chat-chip:hover, .av-chat-chip:focus-visible {
  border-color: var(--av-sky);
  color: var(--av-text);
}

.av-chat-composer { display: flex; flex-direction: column; gap: 0.35rem; margin-top: 0.25rem; }

/* ---------- Composer card (2026-08-03 file-analysis pass) ----------
   One bordered input card: attachment chip on top, the bare auto-growing
   textarea, then a footer row of paperclip · hint · counter · send. The card
   carries the border and the focus state; the textarea inside is stripped of
   its own chrome so the whole panel reads as ONE input, and focusing anywhere
   inside lifts the hairline to amber (the .av-item-card:focus-within gesture,
   spoken at input scale). */
.av-composer {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  width: 100%;
  padding: 0.6rem 0.7rem 0.55rem;
  background: var(--av-panel-2);
  border: 1px solid var(--av-line-2);
  border-radius: 14px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.av-composer:focus-within {
  border-color: var(--av-amber-line);
  box-shadow: 0 0 0 3px rgba(240, 169, 60, 0.12);
}

.av-chat-text {
  resize: none;
  overflow: hidden; /* assistant-chat.js flips this to auto past ~8 lines */
  min-height: 2.6rem;
  border: 0;
  background: transparent;
  color: var(--av-text);
  font-size: 0.95rem;
  line-height: 1.45;
  padding: 0.25rem 0.15rem;
  width: 100%;
}
.av-chat-text:focus { outline: none; box-shadow: none; }
.av-chat-text::placeholder { color: var(--av-mut); }
.av-chat-text:disabled { opacity: 0.6; }

.av-composer-row { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }

/* Left: the paperclip — quiet until it matters, same stroke language as the
   app's other 24px icons. */
.av-composer-attach {
  flex: 0 0 auto;
  width: 2.1rem;
  height: 2.1rem;
  display: grid;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 10px;
  background: transparent;
  color: var(--av-mut);
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}
.av-composer-attach:not(:disabled):hover,
.av-composer-attach:focus-visible {
  color: var(--av-amber);
  border-color: var(--av-amber-line);
  background: color-mix(in srgb, var(--av-amber) 8%, transparent);
}
.av-composer-attach:disabled { opacity: 0.45; cursor: not-allowed; }
.av-composer-attach svg { width: 1.05rem; height: 1.05rem; }

/* Right: the compact amber send — disabled (dim) while the draft is empty or
   a reply is in flight. */
.av-composer-send {
  flex: 0 0 auto;
  width: 2.1rem;
  height: 2.1rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--av-amber-line);
  border-radius: 50%;
  background: linear-gradient(180deg, var(--av-amber-soft), var(--av-amber));
  color: var(--av-ink);
  cursor: pointer;
  transition: transform var(--av-t-fast) var(--av-ease), filter 0.2s ease, opacity 0.2s ease;
}
.av-composer-send:not(:disabled):hover { transform: translateY(-1px); filter: brightness(1.06); }
.av-composer-send:not(:disabled):active { transform: translateY(1px); }
.av-composer-send:disabled { opacity: 0.4; cursor: not-allowed; }
.av-composer-send svg { width: 1rem; height: 1rem; }

/* The one-shot notice truncates politely as the row narrows; the counter
   keeps its quiet corner beside the send. */
.av-composer-hint {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.72rem;
  color: var(--av-mut);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.av-chat-count { flex: 0 0 auto; font-size: 0.75rem; color: var(--av-mut); }

/* Attached-file chip — sits INSIDE the card above the textarea, removable. */
.av-composer-file {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  align-self: flex-start;
  max-width: 100%;
  padding: 0.3rem 0.35rem 0.3rem 0.6rem;
  border: 1px solid var(--av-amber-line);
  border-radius: 9px;
  background: color-mix(in srgb, var(--av-amber) 8%, transparent);
  font-size: 0.8rem;
  color: var(--av-text-2);
}
.av-composer-file svg { flex: 0 0 auto; width: 0.85rem; height: 0.85rem; color: var(--av-amber); }
.av-composer-file-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--av-text);
}
.av-composer-file-size { flex: 0 0 auto; color: var(--av-mut); }
.av-composer-file-remove {
  flex: 0 0 auto;
  width: 1.4rem;
  height: 1.4rem;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--av-mut);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.2s ease, background-color 0.2s ease;
}
.av-composer-file-remove:hover,
.av-composer-file-remove:focus-visible {
  color: var(--av-coral); /* the validation coral the forms already speak */
  background: rgba(255, 122, 107, 0.12);
}

/* Inline attachment error — same coral as the form invalid state. */
.av-composer-error { margin: 0; font-size: 0.78rem; color: var(--av-coral); }

/* Sent-attachment chip on a user bubble in the thread (name + size). */
.av-chat-bubble-file {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  max-width: 100%;
  margin-bottom: 0.35rem;
  padding: 0.25rem 0.55rem;
  border: 1px solid var(--av-line-2);
  border-radius: 8px;
  background: color-mix(in srgb, var(--av-ink) 45%, transparent);
  font-size: 0.75rem;
  color: var(--av-text-2);
}
.av-chat-bubble-file svg { flex: 0 0 auto; width: 0.75rem; height: 0.75rem; color: var(--av-amber); }
.av-chat-bubble-file-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.av-chat-bubble-file-size { flex: 0 0 auto; color: var(--av-mut); }
.av-chat-bubble-text { display: block; }

/* 375px: the card is already full-width; keep the footer on one line by
   letting the hint give way first (it still lives in the attach button's
   title/aria for anyone who needs it). */
@media (max-width: 420px) {
  .av-composer-hint { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .av-chat-thread { scroll-behavior: auto; }
  .av-chat-dot { animation: none; opacity: 0.8; }
  .av-chat-bubble--inking::after { animation: none; }
  /* Composer settles instantly; the send never lifts. site.css's global
     flatten already covers these — belt-and-braces, per this file's habit. */
  .av-composer,
  .av-composer-attach,
  .av-composer-send,
  .av-composer-file-remove { transition: none; }
  .av-composer-send:not(:disabled):hover { transform: none; }
}

/* ---------- Confirm dialog (av-confirm.js; JS-created, so no .av-fx gate) ----------
   The branded stand-in for window.confirm(): a dark ink scrim and a panel on
   the app's own surface — hairline border, elev-3, the 16px card radius, and
   the .av-ticked corner marks the script adds alongside this class. The two
   buttons reuse the site's existing classes (.btn.btn-primary / .av-btn-ghost /
   .av-btn-danger for data-confirm-tone="danger"), so no new button styling
   lives here — the dialog inherits whatever the rest of the site wears. */
.av-confirm-scrim {
  position: fixed;
  inset: 0;
  z-index: 3000; /* above the sticky navbar (1020) and the departure bar (2000) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(10, 17, 36, 0.72); /* --av-ink at ~0.7 */
  animation: avConfirmScrim 160ms ease-out both;
}
.av-confirm-panel {
  width: min(26rem, 100%);
  /* background-COLOR longhand on purpose: the shorthand would reset the
     .av-ticked corner-mark background-images this panel composes with —
     the same per-property cascade trap site.css's ticked kit documents. */
  background-color: var(--av-panel);
  border: 0.5px solid var(--av-line-2);
  border-radius: 16px;
  box-shadow: var(--av-elev-3);
  padding: 1.6rem 1.8rem 1.5rem;
  animation: avConfirmIn 160ms var(--av-ease) both;
}
.av-confirm-title {
  font-family: var(--font-display);
  font-size: 1.4rem;
  color: var(--av-text);
  margin: 0 0 0.45rem;
  line-height: 1.2;
}
.av-confirm-msg {
  color: var(--av-text-2);
  margin: 0 0 1.35rem;
  line-height: 1.55;
}
.av-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
}

/* Entry: a small fade + 6px rise — the toast's settle, at dialog speed. */
@keyframes avConfirmScrim {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes avConfirmIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* The global reduced-motion block in site.css already collapses every
   animation to 0.001ms; this local gate is belt-and-braces so the dialog
   simply appears settled even if that global rule ever narrows. */
@media (prefers-reduced-motion: reduce) {
  .av-confirm-scrim,
  .av-confirm-panel { animation: none; }
}


/* ---- Assistant voice (2026-08-03): dictation + spoken replies -----------
   The mic button reuses .av-composer-attach chrome; while listening it wears
   an amber pulse honest enough to read as "hot". The per-bubble speaker is a
   quiet round ghost that only exists when speechSynthesis does. */

.av-composer-mic.is-listening {
  color: var(--av-amber);
  border-color: var(--av-amber);
  animation: av-mic-pulse 1.6s ease-in-out infinite;
}

@keyframes av-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(240, 185, 100, 0.35); }
  50%      { box-shadow: 0 0 0 7px rgba(240, 185, 100, 0); }
}

.av-chat-speak {
  align-self: flex-end;
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  margin-left: 0.35rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0.5px solid var(--av-line-2);
  border-radius: 50%;
  background: transparent;
  color: var(--av-text-2);
  opacity: 0.65;
  cursor: pointer;
  transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.av-chat-speak svg { width: 16px; height: 16px; }

.av-chat-speak:hover,
.av-chat-speak:focus-visible {
  opacity: 1;
  color: var(--av-amber);
  border-color: var(--av-amber);
}

.av-chat-speak.is-speaking {
  opacity: 1;
  color: var(--av-amber);
  border-color: var(--av-amber);
  animation: av-mic-pulse 1.6s ease-in-out infinite;
}

/* The global reduced-motion kill already zeroes these animations; the ring
   simply rests solid, which still reads as "active". */
@media (prefers-reduced-motion: reduce) {
  .av-composer-mic.is-listening,
  .av-chat-speak.is-speaking { animation: none; box-shadow: 0 0 0 2px rgba(240, 185, 100, 0.3); }
}

/* ---- Honest touch targets (2026-08-03 UI audit) --------------------------
   The composer's small round controls draw at 32–34px (the × at ~22px) —
   right for the card's scale, but under the 44px floor a fingertip needs.
   On coarse pointers only, each control grows an INVISIBLE hit halo (a
   pseudo-element extends the clickable box past the drawn chrome), so the
   art stays exactly as designed and a mouse keeps its precision. Halo sizes
   are chosen so neighbours meet without overlapping: the composer row's
   0.5rem gap absorbs ±6px, the chip row's gap absorbs ±4px, and the × gets
   the biggest reach because it is the smallest mark. */
@media (pointer: coarse) {
  .av-composer-attach,
  .av-composer-send,
  .av-composer-file-remove,
  .av-chat-speak,
  .av-chat-chip { position: relative; }

  .av-composer-attach::before,
  .av-composer-send::before,
  .av-chat-speak::before {
    content: "";
    position: absolute;
    inset: -6px; /* 32–34px drawn -> 44–46px hit */
    border-radius: inherit;
  }

  .av-composer-file-remove::before {
    content: "";
    position: absolute;
    inset: -10px; /* ~22px drawn -> ~42px hit; the chip's padding does the rest */
    border-radius: 50%;
  }

  .av-chat-chip::before {
    content: "";
    position: absolute;
    inset: -4px; /* halves of the 0.5rem flex gap — adjacent halos touch, never overlap */
    border-radius: inherit;
  }

  /* Collapsed-menu nav links measure ~42px; give the row an explicit floor.
     Inline-flex + centred content means desktop-width bars are untouched
     (they already clear 44px with the bar's own padding). */
  .av-navbar .nav-link { min-height: 44px; }
}
