/* V2 — Aria on the field.
   The V1 dock is a pill fixed to the lower right of a page it is not part of.
   Here the very same markup is re-seated as a permanent left rail: her portrait
   at the top, the running transcript beneath it, the microphone at its foot.
   The rest of the page is the open zone she draws into.

   This sheet is loaded after sdui/core.css and aria.css on purpose. It overrides
   both, and the cascade is the only thing keeping those overrides in force. */

.v2-shell {
  display: grid;
  grid-template-columns: minmax(20rem, 24rem) minmax(0, 1fr);
  min-height: 100vh;
}

/* The rail is the same on every V2 page; the zone is what changes. */
.v2-rail {
  /* Drawn, not fetched: an image request here would be a second round trip for
     eighteen pixels, and the mask lets it take the button's own colour. */
  --v2-mic-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='2' width='6' height='11' rx='3'/%3E%3Cpath d='M5 10a7 7 0 0 0 14 0'/%3E%3Cpath d='M12 17v4'/%3E%3C/svg%3E");
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  height: 100vh;
  min-height: 0;
  border-right: 1px solid rgba(255, 255, 255, 0.1);
  background: #0f1420;
}

/* The dock stops floating over the page and becomes the rail's whole body. */
.v2-rail .aria-dock {
  position: static;
  right: auto;
  bottom: auto;
  z-index: auto;
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  padding: 14px;
}

.v2-rail .aria-panel {
  width: auto;
  height: auto;
  flex: 1;
  min-height: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* If the visitor does minimise her, the pill rests in the rail rather than
   jumping to the corner of a page that no longer has a corner for it. */
.v2-rail .aria-pill { align-self: flex-start; }

/* Her portrait is the top of the rail, at a size that says she belongs there.
   Square source, not the dock's 16/9: her rendered frames are square, so a wide
   box in a narrow rail only buys black bars either side of her.

   But the square carries a band of empty backdrop above her hair. That band is
   Ditto's working room — the region it animates is wider than her face box, so
   the source frame has to contain the whole of it. Recropping the files is not
   an option: the still, the idle loop and the frame the renderer animates from
   have to stay identical or her hairline jumps the moment she speaks.

   So the band is masked at display time, and the mask is the box itself. The
   box is shorter than the square its media draws, and the media is anchored to
   the bottom edge, so everything hidden comes off the top. Nothing is scaled —
   she is the same size on screen, and her neckline and shoulders stay whole.
   Her topmost hair pixel is row 218 of the 1224px source, so hiding the top 14%
   clears her with rows to spare. Same rule for the still and the video, so
   nothing shifts when she starts talking.

   The mask ties her height to her width: the box has to keep at least 86% of
   its own width or the crop eats her hair. So the only way to stop her taking
   the rail is to make her narrower than it — the same conclusion sands reaches
   from the other side, where the portrait is a column beside the transcript
   rather than a band above it. At the rail's full 355px she stood 305px tall
   and left the transcript 211px; held to 200px she is 172px, and the running
   conversation gets the rest of the rail instead of a sliver at the bottom. */
.v2-rail .aria-portrait {
  --v2-portrait-shown: 0.86;
  width: min(100%, 12.5rem);
  margin-inline: auto;
  flex: none;
  aspect-ratio: 1 / var(--v2-portrait-shown);
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 12px;
}

.v2-rail .aria-portrait-idle,
.v2-rail .aria-portrait-loop,
.v2-rail .aria-portrait-video {
  position: absolute;
  inset: auto 0 0 0;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  object-position: 50% 100%;
}

.v2-rail .aria-head { padding-left: 2px; border-bottom: 0; background: transparent; }

/* The microphone, the way sandsdynamicswebV2 does it: one control, sitting in
   the composer row beside Send, that turns the device on and off. V1 offers two
   — a latching "Listen" and a held "Push to talk" — stacked in a row of their
   own under the composer. That row costs the transcript another 38px and asks
   the visitor to choose a microphone before they have said anything.

   Barge-in is what makes one button enough: aria.js already drops the running
   answer the moment speech starts, so there is nothing to hold down and nothing
   to press to interrupt her. The same file already closes the device after 20
   seconds of silence and ends the session on the engine's own 20-minute clock,
   so the button is the only piece that was missing.

   Push to talk is hidden here, not removed: aria.js binds it at module load
   with no guard and attaches six listeners to it, and V1 still offers it. */
.v2-rail .aria-composer-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px;
  padding: 10px 0 0;
  border-top: 0;
  background: transparent;
}
.v2-rail .aria-status { flex: 1 0 100%; margin: 0; }
.v2-rail .aria-composer { flex: 1 1 auto; min-width: 0; }
/* The voice row stops being a row and hands its buttons to the composer row. */
.v2-rail .aria-voice { display: contents; }
.v2-rail #aria-ptt { display: none; }

/* The label stays in the markup for anyone listening to the page; on screen it
   is the icon that says so, which is the only thing that fits a 40px control. */
.v2-rail #aria-mic {
  display: grid;
  flex: none;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  font-size: 0;
}
.v2-rail #aria-mic::before {
  content: '';
  width: 1.125rem;
  height: 1.125rem;
  background: currentColor;
  -webkit-mask: var(--v2-mic-glyph) center / contain no-repeat;
  mask: var(--v2-mic-glyph) center / contain no-repeat;
}

/* The way back to V1: present, findable, and deliberately unremarkable. */
.v2-rail-foot {
  margin: 0;
  padding: 10px 16px 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  color: #7c8598;
  font-size: 12px;
}
.v2-rail-foot a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.v2-rail-foot a:hover { color: #d8b26a; }

.v2-field {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.v2-field-head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 1.5rem 2rem 0;
}
.v2-main { padding: 1.5rem 2rem 4rem; }

/* The open zone. It is the width the existing cards were already built for,
   which is what lets those layouts drop in here instead of being redrawn. */
.v2-desk {
  min-height: 14rem;
  padding: 1.25rem;
  border: 1px dashed rgba(255, 255, 255, 0.16);
  border-radius: 0.75rem;
  background: rgba(255, 255, 255, 0.03);
}

/* core.css hides a childless .sdui-zone. That is right for a notice strip and
   wrong here: a zone she empties would take the field with it. */
.v2-desk.sdui-zone:empty { display: block; }

/* T004: while a sailing is open on the desk, the "Lowest fares right now" rack
   below it is not shown. The sailing in front of the visitor is what they are
   working on, and a rack of other fares under it is noise, not an offer. It is
   hidden rather than deleted so Back to results brings the page back whole,
   with no round trip and nothing for the server to remember. */
.v2-main:has(.v2-desk [data-sdui-id='v2-desk-detail']) [aria-labelledby='v2-deals-heading'] {
  display: none;
}

/* The empty line is drawn, not written into the zone. Hydration replaces a
   zone's children, so any markup put inside it would be destroyed on connect.
   It is what the visitor reads in the moment before the server answers hello. */
.v2-desk:empty::before {
  content: 'Ask Aria for a sailing. What she finds appears here.';
  color: #94a3b8;
  font-size: 0.9375rem;
}

/* With the socket down there is nothing behind V2 to fall back on, so the zone
   says what happened rather than sitting there blank or, worse, still promising
   sailings it has no way to fetch. The runtime writes both of these states onto
   every zone it registered: 'offline' while it is still retrying, and 'closed'
   once it has given up. Reading them here means the shared runtime is untouched
   and a zone with contents already in it keeps them, stale but visible, under a
   line saying why nothing more is arriving. */
.v2-desk[data-sdui-state='offline']:empty::before,
.v2-desk[data-sdui-state='closed']:empty::before {
  content: none;
}

.v2-desk[data-sdui-state='offline']::after,
.v2-desk[data-sdui-state='closed']::after {
  display: block;
  color: #e6b980;
  font-size: 0.9375rem;
}
.v2-desk:not(:empty)[data-sdui-state='offline']::after,
.v2-desk:not(:empty)[data-sdui-state='closed']::after {
  margin-top: 0.875rem;
  padding-top: 0.75rem;
  border-top: 1px solid rgba(230, 185, 128, 0.25);
}
.v2-desk[data-sdui-state='offline']::after {
  content: 'The desk lost its connection. Trying to get it back.';
}
.v2-desk[data-sdui-state='closed']::after {
  content: 'The desk is offline. Reload the page to bring it back.';
}

/* Narrow enough that a rail and a field cannot both have room: she goes on top,
   still in the page, and the zone takes the width. */
@media (max-width: 63.9375rem) {
  .v2-shell { grid-template-columns: minmax(0, 1fr); }
  .v2-rail {
    position: static;
    height: auto;
    border-right: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  }
  .v2-rail .aria-panel { height: min(70vh, 34rem); }
  .v2-field-head { padding: 1.25rem 1rem 0; }
  .v2-main { padding: 1.25rem 1rem 3rem; }
}

/* Group 3: what a search puts in the desk. The cards are the runtime's own,
   drawn one at a time; this only lays them out as a field of tiles on the
   width the desk already has, so eight sailings read as a rack and not a
   column. Stack draws with display:flex; the id is the layout's, not a style. */
.v2-desk [data-sdui-id='v2-desk-cards'] {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: 1rem;
}
.v2-desk .sdui-card { margin: 0; }
.v2-desk .sdui-card-body > .sdui-text { margin: 0.125rem 0; }
.v2-desk .sdui-card-body > .sdui-fare { margin-top: 0.25rem; }
.v2-desk .sdui-card-body > .sdui-image { margin-bottom: 0.5rem; }

/* A card is cream, so its neutral text takes the ink colour, not the page's. */
.v2-desk .sdui-card {
  color: #0b1f33;
}

/* PRD 3, Group 1: the sailing the visitor opened, drawn where the cards were.
   The cards are the runtime's own; this only lays the itinerary out as rows
   and the fare grid as a rack of families, and gives the way back a place. */
.v2-desk [data-sdui-id='v2-desk-detail'] > .sdui-button {
  align-self: flex-start;
  margin: 0;
}
.v2-desk [data-sdui-id='v2-desk-sailing'] .sdui-image { max-height: 22rem; object-fit: cover; }
.v2-desk [data-sdui-id='v2-desk-days'] > .sdui-stack {
  padding: 0.5rem 0;
  border-top: 1px solid rgba(11, 31, 51, 0.12);
}
.v2-desk [data-sdui-id='v2-desk-days'] > .sdui-stack:first-child { border-top: 0; }
.v2-desk [data-sdui-id='v2-desk-days'] > .sdui-stack > .sdui-text:first-child {
  flex: 0 0 11rem;
  margin: 0;
}
.v2-desk [data-sdui-id='v2-desk-days'] .sdui-text { margin: 0; }
.v2-desk [data-sdui-id='v2-desk-fare-grid'] {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  gap: 1rem;
}
.v2-desk [data-sdui-id='v2-desk-fare-grid'] .sdui-fare { margin: 0; }
.v2-desk [data-sdui-id='v2-desk-fare-grid'] .sdui-fare-amount { white-space: normal; text-align: right; }
