/* THE SDEIRA ASSISTANT - one component, every page.
   ==========================================================================
   The launcher and its panel used to live only on the two homepages: the
   markup was hand-written into each, these rules sat inside sdeira-v2.css and
   sdeira-v2-en.css, and the behaviour sat inside their two page scripts. So
   the assistant could not appear anywhere else without a fourth copy, and the
   site owner asked for it on every page, present and future.

   The two copies of these rules were compared before they were merged, rather
   than assumed equal: 38 rules on each side, zero differing declarations. So
   this file is real deduplication of an already-identical pair, not a partial
   lift of the kind CLAUDE.md bans - the whole rule set moved, in source order,
   and nothing was retyped from a screenshot.

   ONE RULE DELIBERATELY DID NOT MOVE. "Hide the launcher while the mobile
   drawer is open" is about the PAGE'S drawer, not about the assistant, and
   each page collapses to its drawer at its own width - 980px on the Arabic
   homepage, 1180px on the English one, because English nav labels are longer.
   There is no single breakpoint a shared file could carry, so that rule stays
   with the drawer it serves, in each page's own stylesheet.

   DIRECTION IS HANDLED BY LOGICAL PROPERTIES, not by a mirrored copy. The
   panel and the launcher are pinned with inset-inline-start, so they sit
   bottom-right in Arabic and bottom-left in English from one declaration.
   Do not add a [dir] variant here; if a value has to differ by direction it
   belongs with the page, like the drawer rule above.

   Load order: sdeira-tokens.css first, then this file, then the page's own
   stylesheet. Every visual role and every component-defining dimension reads
   the token contract; the remaining numbers are internal layout arithmetic
   documented with the live component in design-system/index.html. */

/* ===== AI ASSISTANT ENTRY =================================================
   A chat-shaped route into site help, rebuilt on the warm surface ladder.

   Three rules govern the whole component and are worth stating once here
   rather than repeating on every selector below:

   1. NO BORDERS, WITH ONE NAMED EXCEPTION. Every separation is a tonal step
      on the warm ladder - Raised (white) for the panel and the bot's own
      bubble, Base for the conversation floor, Sunken for the composer
      field, Sunken-strong for the visitor's own bubble. That is the
      elevation model section 10 of the token file describes, applied
      literally. `border: 0` still appears where an element has a
      user-agent border to remove (buttons, the textarea); removing a
      border is not drawing one. THE EXCEPTION, site owner, 28 Aug 2026:
      the suggestion chips (`.ai-chat-suggestions button`) sit on the same
      White floor their own reply bubble now uses, so the tonal-step
      separation they relied on is gone - the two backgrounds are the same
      colour and read as one surface. They now borrow the site's own
      secondary-button MECHANIC (outline at rest, sweeps to the accent fill
      on hover/focus - see `.btn-ghost`, sdeira-subpage.css), not its
      literal colours: `.btn-ghost`'s own White-on-photography pairing is
      documented "Never on a light section", and this chip sits on one, so
      the rest state is --ink (the system's own primary text/heading
      colour) instead - the accent read as too loud for a resting chip.
      No other element in this component gets a border from this exception.
   2. NO NEUTRAL GREY SURFACE. The previous composer sat on
      --surface-neutral-soft inside a Grey 70 outline, which is the one
      cool tone in a warm system and read as a disabled field. Nothing in
      this component uses it now. Grey survives only where the token file
      already assigns it to text and icons (--muted, --icon, both Jais).
   3. ONE FOCUS INDICATOR PER CONTROL. The field's ring is drawn on the
      field wrapper via :focus-within, not on the textarea, because the
      textarea no longer has a visible edge of its own - it is a
      transparent layer on the field surface. */
.ai-chat-panel {
  position: fixed;
  z-index: 1002;
  display: flex;
  flex-direction: column;
  inline-size: min(var(--assistant-panel-inline), calc(100% - var(--assistant-panel-inline-clearance)));
  /* The panel is a utility that opens over the page, not a second column
     of it. At 460x760 it was reading as the page rather than as something
     floating on top of it, so it comes back to 400x580 - and the space
     the conversation needed is bought inside the panel (a composer that
     rests at three lines, tighter head and message padding) instead of by
     growing the box. The dvh cap still hands the viewport the final say
     on a short window. */
  block-size: min(var(--assistant-panel-block), calc(100dvh - var(--assistant-panel-block-clearance)));
  border-radius: var(--r);
  background: var(--surface-raised);
  color: var(--ink);
  box-shadow: var(--shadow-floating);
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  transform: translateY(var(--assistant-panel-enter-distance));
  transition:
    block-size var(--dur-base) var(--ease-out),
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
  will-change: opacity, transform;
}

.ai-chat-panel {
  inset-block-end: var(--assistant-panel-offset-block);
  inset-inline-start: var(--assistant-panel-offset-inline);
}

.ai-chat-panel[hidden] {
  display: none;
}

.ai-chat-panel.is-open {
  opacity: 1;
  pointer-events: auto;
  transform: none;
}

/* The head is the identity: the Sdeira logo and the minimise control, and
   nothing else. There is no written panel title and no agent identity row
   - the logo says whose assistant this is, which is what those two rows
   were spending three lines of copy to say. */
.ai-chat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-block: 12px;
  padding-inline: 18px 10px;
  background: var(--surface-inverse);
}

/* The light logo, because the head is Black. Height-driven with auto
   width so the intrinsic 260x80 ratio is what sizes it. */
.ai-chat-logo {
  display: block;
  block-size: var(--assistant-logo-block);
  inline-size: auto;
}

.ai-chat-messages {
  display: grid;
  flex: 1 1 auto;
  align-content: start;
  gap: 14px;
  padding: 18px 16px;
  /* REVERSED 28 Aug 2026, site owner: White is now the floor and the
     content sits one tonal step down on Light Grey, not the other way
     round. Still the two-canvas rule applied literally - just the other
     pairing of it - and still no accent colour on either surface. Message
     bubbles and suggestion chips below are --surface-sunken. */
  background: var(--surface-base);
  /* Room for the composer, which is pinned over this list rather than
     stacked under it. Derived, not guessed: the composer's resting height
     is its own 12+16 padding, the field's 8+8, a textarea that rests at
     three line boxes of --leading-body, the field's 8px gap, and the
     actions row (--assistant-icon-target) the attach/send pair needs. The
     reference formula only reached 44px (composer + field padding) before
     the actions row existed; adding its height and gap keeps the last
     message clear of the composer at every scroll position - no fade, no
     hard cut. */
  --ai-composer-rest: calc(3rem * var(--leading-body) + 44px + var(--assistant-icon-target) + 8px);
  padding-block-end: calc(var(--ai-composer-rest) + 18px);
  overflow-y: auto;
  overscroll-behavior-y: contain;
  scrollbar-gutter: stable;
  /* A thin, transparent-track scrollbar - the same minimal pattern
     .land-map-list-items already uses (page-landbank.css), not the site's
     bulkier default OS scrollbar. Owner: the default read as too thick and
     heavy for this small panel. Thumb is --line, the system's neutral
     divider tone, never the accent - a scrollbar is chrome, not an action. */
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.ai-chat-messages::-webkit-scrollbar { width: 4px; }

.ai-chat-messages::-webkit-scrollbar-track { background: transparent; }

.ai-chat-messages::-webkit-scrollbar-thumb {
  border-radius: var(--r);
  background: var(--line);
}

.ai-chat-log {
  display: grid;
  gap: 14px;
}

.ai-message {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  inline-size: 100%;
  max-inline-size: 100%;
}

.ai-message--new {
  animation: ai-message-enter var(--dur-base) var(--ease-out) both;
}

/* The Sdeira mark alone - no tile behind it - painted in Sweihan.

   Drawn as a MASK, not as an image, and that is the whole fix. The mark
   PNG's own pixels are a single flat #B68B4C: measured, 1,557 opaque
   pixels and every one of them that colour. That is not one of the eight
   brand values, it is a hair off Razeen, and on the warm rungs this panel
   is built from it composites to almost nothing - the avatar was a beige
   shape on a beige square. Masking discards the file's colour and keeps
   only its alpha, so the mark is painted from a token instead: the shape
   comes from the asset, the colour comes from the system, and the
   identity stops depending on whatever tone happens to be baked into a
   PNG. Sweihan on the conversation floor measures 3.75, clear of the 3:1 a
   graphic needs.

   The tile is gone with it. A badge is a word and this is a mark: at 34px
   square with a 17px mark inside it, the box was carrying no information
   and adding a surface for the eye to resolve before reaching the
   message. */
.ai-message-avatar {
  position: relative;
  display: block;
}

/* Same FINDING p3-01/p3-02 fix as the launcher above: the mark is the
   official full-colour artwork, not a single-colour silhouette recoloured
   through a mask, and it carries no glow. */
.ai-message-avatar::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("../images/brand/sdeira-emblem-fullcolour.svg?v=9d60ed9cb777d961") center / contain no-repeat;
}

.ai-message-avatar {
  flex: 0 0 var(--assistant-avatar-target);
  inline-size: var(--assistant-avatar-target);
  /* One line box tall, pushed down by the bubble's own block padding, so
     the mark reads as sitting ON the message's first line instead of
     floating above it. Both values are the ones the bubble beside it
     uses; neither is a nudge. */
  block-size: calc(1rem * var(--leading-body));
  margin-block-start: 14px;
}

/* FINDING p3-01/p3-02 (Sdeira Group Guidelines): "Decorative stars, sparkles
   and additional embellishments are not part of the approved graphic
   system," and "the emblem should follow its approved colours, proportions
   and clear-space requirements." The launcher's five orbiting sparks and the
   glowing gradient-through-a-mask treatment are retired site-wide, 31 Aug
   2026 - promoted here from assets/css/sdeira-liwa.css, which carried this
   exact fix for the two homepages only (commit 777cd9b, reverted the same
   day with no reason recorded, so the two pages drifted from every other
   page instead of the other way around). The mark is now the official
   full-colour artwork with nothing drawn on top of it. */
.ai-star-field {
  position: relative;
  z-index: 1;
  display: block;
  inline-size: var(--assistant-launcher-field);
  block-size: var(--assistant-launcher-field);
}

.ai-star-field::before {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 50% auto auto 50%;
  inline-size: 100%;
  block-size: 100%;
  background: url("../images/brand/sdeira-emblem-fullcolour.svg?v=9d60ed9cb777d961") center / contain no-repeat;
  transform: translate(-50%, -50%);
}

.ai-message-content {
  display: grid;
  flex: 1 1 auto;
  gap: 6px;
  min-inline-size: 0;
  padding: 14px 16px;
  border: 0;
  border-radius: var(--r);
  /* REVERSED 28 Aug 2026, site owner: grey bubble on the White conversation
     floor (--surface-base above) - content separates from floor by tonal
     step, never by an accent tint. The accent colour is the action/emphasis
     role, not a surface, so no bubble background may carry it. */
  background: var(--surface-sunken);
}

.ai-message-content p {
  margin: 0;
  color: var(--ink);
  font-size: var(--type-body);
  line-height: var(--leading-body);
}

.ai-message-content p[hidden] {
  display: none;
}

/* An attached image sits inside its own message bubble, above the text -
   one visitor turn can carry both. Capped rather than shown at native size:
   a full-resolution photo would blow past the panel's own fixed width, the
   composer's max-block-size textarea rule this borrows the spirit of. */
.ai-message-image {
  display: block;
  inline-size: 100%;
  max-block-size: 220px;
  border-radius: calc(var(--r) - 4px);
  object-fit: cover;
}

/* The visitor's own messages: no avatar, aligned to the reading end, and
   one rung darker than the assistant's so the two voices are told apart
   by tone rather than by a coloured outline or a tail. */
.ai-message--user {
  justify-content: flex-end;
}

.ai-message--user .ai-message-content {
  flex: 0 1 auto;
  max-inline-size: 86%;
  /* REMOVED 28 Aug 2026, site owner: a grey bubble on the White floor read as
     a second surface for text that carries no real content boundary of its
     own - text without a plate reads more legibly than text on a
     low-contrast tint. No background at all now; the visitor's own message
     is told apart purely by alignment to the reading end and by having no
     avatar, which is still the same rule the bot's own bubble states below. */
  padding-inline: 0;
  background: transparent;
}

.ai-chat-suggestions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  margin-block-start: 2px;
}

.ai-chat-suggestions[hidden] { display: none; }

.ai-chat-suggestions button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  inline-size: fit-content;
  max-inline-size: 100%;
  min-block-size: var(--assistant-choice-target);
  padding-inline: 14px;
  border-radius: var(--r);
  /* Named exception to the component's no-border rule (see the comment
     block at the top of this file): this chip sits on the same White
     floor its own reply bubble now uses, so a tonal-step fill can no
     longer separate it. Borrows the site's secondary-button MECHANIC
     (outline at rest, sweeps to the accent fill on hover/focus) rather
     than a literal .btn-ghost, whose White-on-photography colours are
     documented for a dark ground, not this one. Rest is --ink, the
     system's own primary-text/heading colour, not the accent: owner
     direction, 28 Aug 2026, red read as too loud for a resting chip.
     Full shorthand, not just border-color: a bare colour value resolves
     to border-style: none. */
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: var(--type-control);
  text-align: start;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}

/* Fills with the action colour on hover/focus, same mechanic as every
   .btn-ghost on the site - white text is the accessible pairing on the
   fill (9.35 on Liwa), never the ink used at rest. */
.ai-chat-suggestions button:is(:hover, :focus-visible) {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--brand-white);
}

.ai-chat-suggestions button:focus-visible {
  outline: var(--assistant-focus-outline) solid var(--brand-razeen);
  outline-offset: var(--assistant-focus-offset);
}

.ai-typing-indicator { display: inline-flex; gap: 4px; min-block-size: 14px; align-items: center; }
.ai-intro-copy:not(:empty) + .ai-typing-indicator { display: none; }
.ai-typing-indicator i { inline-size: var(--assistant-typing-dot); block-size: var(--assistant-typing-dot); border-radius: 50%; background: var(--surface-brand); animation: ai-typing-dot var(--dur-base) var(--ease-out) infinite alternate; }
.ai-typing-indicator i:nth-child(2) { animation-delay: calc(var(--dur-fast) / 2); }
.ai-typing-indicator i:nth-child(3) { animation-delay: var(--dur-fast); }
.ai-chat-suggestions button { opacity: 0; transform: translateY(8px); }
.ai-chat-panel.is-ready .ai-chat-suggestions button { animation: ai-suggestion-enter var(--dur-base) var(--ease-out) forwards; }
.ai-chat-panel.is-ready .ai-chat-suggestions button:nth-child(2) { animation-delay: var(--dur-fast); }
.ai-chat-panel.is-ready .ai-chat-suggestions button:nth-child(3) { animation-delay: calc(var(--dur-fast) * 2); }
@keyframes ai-typing-dot { to { transform: translateY(calc(-1 * var(--assistant-typing-lift))); } }
@keyframes ai-suggestion-enter { to { opacity: 1; transform: none; } }
@keyframes ai-message-enter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
/* --- The composer floats ON the conversation, it is not a slab under it --
   It used to be an opaque --surface-raised bar below the message list, so
   the panel ended on a white block with a beige field sitting in it: two
   surfaces to read where the visitor only needs one. At the site owner's
   direction (2026-08-08) the bar's own background is gone and the field
   itself is the only surface here.

   Removing the background alone would have changed nothing visible. In
   normal flow the composer sat BELOW the list, so "transparent" would just
   have shown the panel's own --surface-raised through it - the same white.
   For the bar to stop reading as a slab the conversation has to run behind
   it, so the composer leaves the flex column and is pinned to the panel's
   bottom edge instead. .ai-chat-messages then reserves room for it (see
   that rule) so the last message can still scroll clear of the field.

   REMOVED 28 Aug 2026, site owner: the frosted grey backdrop strip behind
   the field was a second surface doing a separation job the floor no longer
   needs. Now that the floor itself is White (reversed above) and the field
   is White too, there is nothing for a tint to separate - it was reading as
   unnecessary complexity, not a legible boundary. The strip is fully
   transparent; the field's own White surface is the whole visual signal.

   OPAQUE AGAIN, 28 Aug 2026 (second pass): full transparency let scrolled
   message text show straight through the gap around the field instead of
   disappearing behind it. The composer box itself is solid --surface-base
   now - the same White as the floor, so it reads as continuing it rather
   than a new surface - and the reservation above (.ai-chat-messages
   padding-block-end) keeps the last message above the composer's top edge
   at every scroll position, so the cut is never visible. */
.ai-chat-composer {
  display: grid;
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  padding: 12px 16px 16px;
  background: var(--surface-base);
}

/* THE FIELD IS THIS BOX, not the textarea. The textarea uses its own complete
   row; attachment and send form the row beneath it. A multi-line message can
   therefore use the full field width without text sharing its line with an
   action control. */
.ai-chat-field {
  display: grid;
  gap: 8px;
  padding: 8px;
  padding-inline-start: 14px;
  border-radius: var(--r);
  /* REVERSED 28 Aug 2026, then RE-DISTINGUISHED same day: the floor and the
     composer box are both White now, so a White field disappeared into its
     own background - the site owner's own complaint. Light Grey
     (--surface-sunken) gives the field a surface of its own again, still
     inside the two-canvas system and still not a third tone. The focus ring
     below is the whole focus signal. */
  background: var(--surface-sunken);
}

.ai-chat-field:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Multi-line at rest, not on demand. A one-line field reads like a search box
   and gives no sign that a longer question is welcome. The visible input stays
   at three type-scale line boxes; longer messages scroll inside it so the
   conversation keeps the panel's remaining height. */
.ai-chat-composer textarea {
  display: block;
  inline-size: 100%;
  min-inline-size: 0;
  min-block-size: calc(3 * 1rem * var(--leading-body));
  max-block-size: calc(3 * 1rem * var(--leading-body));
  padding: 7px 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--ink);
  caret-color: var(--accent);
  font: inherit;
  font-size: var(--type-action);
  line-height: var(--leading-body);
  resize: none;
  overflow-y: auto;
  /* Same quiet scrollbar the conversation uses above: it belongs to the
     textarea only, never the field or the page, so a long draft stays
     readable without bringing platform arrow controls into the composer. */
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.ai-chat-composer textarea::-webkit-scrollbar { width: 4px; }

.ai-chat-composer textarea::-webkit-scrollbar-track { background: transparent; }

.ai-chat-composer textarea::-webkit-scrollbar-thumb {
  border-radius: var(--r);
  background: var(--line);
}

.ai-chat-composer textarea::-webkit-scrollbar-button {
  display: none;
  inline-size: 0;
  block-size: 0;
}

.ai-chat-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.ai-chat-composer textarea::placeholder {
  color: var(--muted);
  opacity: 1;
}

/* White is the rest surface, the accent is reserved for the content - the
   same two-canvas rule the rest of the field follows (the send control is
   not a second solid tile sitting on the white field). Its hover then
   matches the site's other icon-only controls (scroll-top, drawer close):
   step to --accent-hover fill under --on-action, never a colour swap to a
   different brand hue. Owner direction, 28 Aug 2026. */
.ai-chat-send {
  display: grid;
  flex: 0 0 var(--assistant-icon-target);
  place-items: center;
  inline-size: var(--assistant-icon-target);
  block-size: var(--assistant-icon-target);
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.ai-chat-send:is(:hover, :focus-visible) {
  background: var(--accent-hover);
  color: var(--on-action);
}

/* Same control family as the send button - identical size, identical rest
   and hover mechanic - so the two read as one pair of field-level actions
   rather than a foreign control grafted on. --muted at rest, not --accent:
   attaching is a secondary action next to sending, and the accent stays
   reserved for the one action that actually leaves the composer. */
.ai-chat-attach {
  display: grid;
  flex: 0 0 var(--assistant-icon-target);
  place-items: center;
  inline-size: var(--assistant-icon-target);
  block-size: var(--assistant-icon-target);
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.ai-chat-attach:is(:hover, :focus-visible) {
  background: var(--accent-hover);
  color: var(--on-action);
}

/* One attached image at a time, shown as a small named chip above the
   field rather than inline in the textarea - the message stays plain text
   and the image is its own, removable item. Same chip geometry the
   suggestion buttons use, but on this surface it sits on the field's own
   Light Grey, so it steps UP to White rather than outlining - a filled
   chip is legible here without competing with the outlined suggestions. */
.ai-chat-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-inline: 16px;
  padding-block-end: 8px;
}

.ai-chat-attachments[hidden] { display: none; }

.ai-chat-attachment {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-inline-size: 100%;
  padding-inline: 10px;
  padding-block: 6px;
  border-radius: var(--r);
  background: var(--surface-raised);
  color: var(--ink);
  font-size: var(--type-control);
}

.ai-chat-attachment-thumb {
  flex: 0 0 auto;
  inline-size: 28px;
  block-size: 28px;
  border-radius: calc(var(--r) - 4px);
  object-fit: cover;
}

.ai-chat-attachment-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Removing the attachment is its own small control, not a second job for
   the chip itself - the chip stays a label, this is the action. Same
   family as the send/attach pair again: transparent at rest, steps to the
   accent-hover fill under --on-action on hover/focus. */
.ai-chat-attachment-remove {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  inline-size: 22px;
  block-size: 22px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.ai-chat-attachment-remove:is(:hover, :focus-visible) {
  background: var(--accent-hover);
  color: var(--on-action);
}

.ai-welcome-nudge {
  position: fixed;
  z-index: 1001;
  inset-block-end: calc(var(--assistant-panel-offset-inline) + var(--assistant-launcher-target) + var(--assistant-nudge-gap));
  inset-inline-start: var(--assistant-panel-offset-inline);
  display: none;
  align-items: center;
  gap: var(--assistant-nudge-gap);
  max-inline-size: min(var(--assistant-nudge-inline), calc(100% - var(--assistant-panel-inline-clearance)));
  min-block-size: var(--assistant-choice-target);
  padding: var(--assistant-nudge-padding-block) var(--assistant-nudge-padding-inline);
  border: 0;
  border-radius: var(--r);
  background: var(--surface-raised);
  color: var(--ink);
  box-shadow: var(--shadow-floating);
  font: inherit;
  font-size: var(--type-control);
  line-height: var(--leading-body);
  text-align: start;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transform: translateY(var(--assistant-panel-enter-distance));
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}

.ai-welcome-nudge:is(:hover, :focus-visible) {
  background: var(--surface-brand-deep);
  color: var(--brand-white);
}

.ai-welcome-nudge:focus-visible {
  outline: var(--assistant-focus-outline) solid var(--brand-razeen);
  outline-offset: var(--assistant-focus-offset);
}

.ai-welcome-nudge.is-visible:not([hidden]) { display: flex; opacity: 1; pointer-events: auto; transform: none; }

.ai-chat-toggle {
  position: fixed;
  z-index: 1001;
  inset-block-end: var(--assistant-panel-offset-inline);
  inset-inline-start: var(--assistant-panel-offset-inline);
  display: grid;
  place-items: center;
  inline-size: var(--assistant-launcher-target);
  block-size: var(--assistant-launcher-target);
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: var(--brand-white);
  color: var(--brand-sweihan);
  cursor: pointer;
  overflow: visible;
  transition: color var(--dur-fast) var(--ease-out), opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}

.ai-chat-toggle:is(:hover, :focus-visible) {
  color: var(--brand-sweihan);
  transform: scale(var(--assistant-launcher-hover-scale));
}

.ai-chat-toggle .ai-chat-toggle-icon--close {
  position: relative;
  z-index: 1;
}

/* No glow, no rotation: the button's own :hover already scales the whole
   launcher (see .ai-chat-toggle above). The mark only needs to keep centred
   through that scale, not carry a second, larger one of its own. */
.ai-chat-toggle:is(:hover, :focus-visible) .ai-star-field::before {
  transform: translate(-50%, -50%);
}

/* SCOPED TO THE BUTTON ON PURPOSE, and this is a specificity fix rather than a
   style choice. The bare class is (0,1,0); sdeira-subpage.css sets
   `i.ph { display: inline-block }` at (0,1,1), which beats it. Every sub-page
   loads that sheet and the two homepages do not, so the resting state was
   correct on the homepages and wrong everywhere else: both glyphs showed at
   once and the launcher grew into a tall box with a robot above a caret.

   Written as two classes (0,2,0) so it wins on specificity rather than on load
   order - a shared component cannot depend on which sheet a page happens to
   load last, and !important would only move the same fight somewhere else.
   The expanded-state rules below are already (0,3,0) and were never affected,
   which is why the panel's own open/close swap looked right. */
.ai-chat-toggle .ai-chat-toggle-icon--close {
  display: none;
}

.ai-chat-toggle[aria-expanded="true"] .ai-chat-toggle-icon--open {
  display: none;
}

.ai-chat-toggle[aria-expanded="true"] .ai-chat-toggle-icon--close {
  display: block;
}

/* THE TWO PERSISTENT CONTROLS SHARE ONE POSITION CONTRACT. The assistant sits
   at the reading start; back-to-top sits at the reading end using the same
   approved edge clearance. A physical `left` put both controls in the lower
   left corner on every LTR sub-page, so the transparent Sdeira mark was drawn
   directly over the grey arrow tile. This relationship belongs here once,
   where every page gets the assistant, rather than in one language's homepage
   sheet. `left: auto` explicitly releases the legacy physical anchor. */
.scroll-top {
  left: auto;
  inset-inline-end: var(--assistant-panel-offset-inline);
  inset-block-end: var(--assistant-panel-offset-inline);
  inline-size: var(--assistant-launcher-target);
  block-size: var(--assistant-launcher-target);
}

.scroll-top i.ph {
  font-size: var(--icon-control);
}

.ai-chat-toggle .ai-chat-toggle-icon--close {
  font-size: var(--icon-default);
}

/* The send control takes the same ring as the panel's other two controls.
   It is NOT covered by .ai-chat-field:focus-within, which fires for the
   textarea as well and would leave a keyboard user unable to tell which
   of the two has focus. */
/* THE PANEL'S CLOSE BUTTON IS PART OF THE PANEL, and it was the last piece
   still living in the two homepage stylesheets. `.utility-panel-close` reads
   like a shared utility and is not one: it has exactly one user on the whole
   site, the assistant's own head. So every page except the two homepages was
   rendering it unstyled - no 40x40 box, no White glyph, no transparent ground -
   which measured as a panel head 6px taller on a sub-page than on a homepage.
   That is the same half-shared drift the header bar hit, caught here by
   comparing the component's computed values across page families rather than
   by looking at it. */
.utility-panel-close {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  inline-size: var(--assistant-icon-target);
  block-size: var(--assistant-icon-target);
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--brand-white);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.utility-panel-close:is(:hover, :focus-visible) {
  background: color-mix(in srgb, var(--brand-white) 14%, transparent);
  color: var(--brand-white);
}

.utility-panel-close:focus-visible,
.ai-chat-send:focus-visible,
.ai-chat-attach:focus-visible,
.ai-chat-attachment-remove:focus-visible,
.ai-chat-toggle:focus-visible {
  outline: var(--assistant-focus-outline) solid var(--brand-razeen);
  outline-offset: var(--assistant-focus-offset);
}

@media (max-width: 560px) {

  .scroll-top {
    left: auto;
    inset-inline-end: var(--assistant-panel-offset-inline-mobile);
    inset-block-end: var(--assistant-panel-offset-inline-mobile);
    inline-size: var(--assistant-launcher-target-mobile);
    block-size: var(--assistant-launcher-target-mobile);
  }

  .ai-chat-panel {
    inline-size: calc(100% - var(--assistant-panel-inline-clearance-mobile));
    block-size: min(var(--assistant-panel-block-mobile), calc(100dvh - var(--assistant-panel-block-clearance-mobile)));
    inset-block-end: var(--assistant-panel-offset-block-mobile);
    inset-inline-start: var(--assistant-panel-offset-inline-mobile);
  }

  .ai-welcome-nudge {
    inset-block-end: calc(var(--assistant-panel-offset-inline-mobile) + var(--assistant-launcher-target-mobile) + var(--assistant-nudge-gap));
    inset-inline-start: var(--assistant-panel-offset-inline-mobile);
    max-inline-size: min(var(--assistant-nudge-inline), calc(100% - var(--assistant-panel-inline-clearance-mobile)));
  }

  .ai-chat-toggle {
    inset-block-end: var(--assistant-panel-offset-inline-mobile);
    inset-inline-start: var(--assistant-panel-offset-inline-mobile);
    inline-size: var(--assistant-launcher-target-mobile);
    block-size: var(--assistant-launcher-target-mobile);
  }

  .ai-star-field {
    inline-size: var(--assistant-launcher-field-mobile);
    block-size: var(--assistant-launcher-field-mobile);
  }
}

@media (prefers-reduced-motion: reduce) {

  .ai-chat-panel,
  .ai-welcome-nudge,
  .ai-chat-toggle {
    transition: none;
  }

  .ai-chat-panel {
    transform: none;
  }

  .ai-chat-toggle:is(:hover, :focus-visible) {
    transform: none;
  }

  .ai-message--new {
    animation: none;
  }

}

/* ===== THE ASSISTANT LAUNCHER WAITS FOR HALF THE HERO ================
   The shared assistant script measures every supported hero family and keeps
   this fixed control out of the opening composition until the visitor passes
   its midpoint. Pages without a hero remove the class immediately. The open
   state still wins below because the toggle is also the close control. */
.ai-chat-toggle {
  transition: color var(--dur-fast) var(--ease-out),
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

/* Visible is the safe resting state. Hero-aware hiding is an opt-in class from
   the external component, so a page without a hero or a failed script never
   leaves a present-but-unreachable control in the focus order. */
.ai-chat-toggle {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transform: none;
  display: none;
}

.ai-chat-toggle.is-under-hero {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transform: translateY(var(--assistant-launcher-enter-distance));
}

/* An open panel keeps its control: the launcher is also the close button, so
   hiding it mid-conversation would strand the panel with no way out. Stated
   after .is-under-hero so it wins on source order at equal specificity. */
.ai-chat-toggle[aria-expanded="true"] {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transform: none;
}

/* While the mobile menu drawer is open, the assistant control, panel and
   welcome nudge stay out of the way, exactly as they already do on the two
   homepages (25 Sept change request: previously only the homepages carried
   this rule, so interior pages showed the panel above the open menu). */
body.menu-open :is(.ai-chat-toggle, .ai-chat-panel, .ai-welcome-nudge) {
  visibility: hidden;
  pointer-events: none;
}
