@ottotheagent/mcp-card-assets 0.7.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,11 +6,11 @@ Renderers, logos and photo placeholders for the Otto flight, hotel, room and car
6
6
 
7
7
  ## Contents
8
8
 
9
- - `flight-card.js`, `hotel-card.js`, `room-card.js` and `car-card.js` each draw one card and expose `OttoFlightCard`, `OttoHotelCard`, `OttoRoomCard` or `OttoCarCard` with `render(rootElement, card)`. Each carries its own CSS and only the icons it uses inline, so the sandbox loads one script per card. The server's snippet loads jsDelivr's minified copy (`*-card.min.js`).
9
+ - `flight-card.js`, `hotel-card.js`, `room-card.js` and `car-card.js` each draw one card and expose `OttoFlightCard`, `OttoHotelCard`, `OttoRoomCard` or `OttoCarCard` with `render(rootElement, card)`. Each carries its own CSS and only the icons it uses inline, so the sandbox loads one script per card. The server's snippet loads jsDelivr's minified copy (`*-card.min.js`). Since 0.10.0, `room-card.js` draws only a row of room buttons: the host draws the rooms, with photos, from the same tool's MCP Apps resource.
10
10
  - Select (Show rooms on the hotel card) sends the pick as the traveler's message through the host's `sendPrompt()`: "Book flight …", "Show rooms at …", "Select room …" or "Book car …". The message carries no id.
11
11
  - `*-card.css` holds each card's CSS as a separate file, for reference.
12
12
  - `marks/<IATA>.png` holds airline logos, and `marks/cars/<code>.svg` holds car-company logos.
13
- - `placeholders/hotel.svg`, `room.svg` and `car.svg` fill the photo plate, because host sandboxes block supplier photos.
13
+ - `placeholders/car.svg` fills the car card's photo plate, because host sandboxes block supplier photos. The hotel card has no photo plate: the host's own map shows the hotels with their photos. `hotel.svg` and `room.svg` stay for renderers before 0.8.0 and 0.10.0.
14
14
 
15
15
  The sources are `src/flight-card.js`, and `src/core.js` plus `src/<kind>-card.js` for the hotel, room and car cards. `scripts/mcp/build_card_assets.py` writes the published files from them, from the card CSS in `server/mcp/apps/static/`, and from the logo tables. The generated files are not committed; `placeholders/` is.
16
16
 
package/car-card.js CHANGED
@@ -8,6 +8,8 @@
8
8
  * the current pick and ui.pick(label) makes its Select button.
9
9
  * spec.message(item) is the text Select sends through the host's sendPrompt().
10
10
  * spec.empty is the line shown when the card has no items.
11
+ * spec.bar draws the options as one wrapping row of buttons with no title, for a card
12
+ * whose details the host already shows above it.
11
13
  */
12
14
  const CSS = ":root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme=\"light\"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme=\"dark\"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:140px;}.opt{padding:0 0 var(--otto-space-7);overflow:hidden;}.opt>:not(.photo){margin-inline:var(--otto-space-7);}.photo{position:relative;height:var(--otto-photo-height);overflow:hidden;background:#ffffff;}.photo img:not(.vendor){width:100%;height:100%;object-fit:contain;display:block;}.chips{position:absolute;top:var(--otto-space-4);left:var(--otto-space-4);right:132px;display:flex;flex-wrap:wrap;gap:var(--otto-space-1);}.vendor{position:absolute;top:var(--otto-space-4);right:var(--otto-space-4);height:var(--otto-mark-size);max-width:110px;padding:2px var(--otto-space-2);box-sizing:border-box;border-radius:4px;background:#ffffff;object-fit:contain;}.vendorName{position:absolute;top:var(--otto-space-4);right:var(--otto-space-4);max-width:110px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:var(--otto-mark-size);font-size:var(--otto-text-md);font-weight:var(--otto-weight-semibold);color:#0a0a0a;}.name{margin-top:var(--otto-space-6);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);line-height:1.3;}.loyalty{display:flex;align-items:center;gap:var(--otto-space-2);margin-top:var(--otto-space-1);font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.features{display:flex;flex-wrap:wrap;gap:var(--otto-space-1) var(--otto-space-5);margin-top:var(--otto-space-5);}.fact{display:flex;align-items:center;gap:var(--otto-space-2);font-size:var(--otto-text-base);color:var(--otto-text-body);}.fact svg{flex-shrink:0;}.fare{display:flex;align-items:center;gap:var(--otto-space-5);margin-top:var(--otto-space-7);}.fare .btn{flex:0 0 45%;}.fare>div{min-width:0;}.amount{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.per,.total{font-size:var(--otto-text-md);font-weight:var(--otto-weight-regular);color:var(--otto-text-secondary);}.per{color:inherit;}.total{display:block;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}";
13
15
  const ICONS = {"cars/person-seat-reclined-solid-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M312 136C312 105.1 286.9 80 256 80C225.1 80 200 105.1 200 136C200 166.9 225.1 192 256 192C286.9 192 312 166.9 312 136zM354.2 384L330.5 265.5C325.7 241.4 304.5 224 279.9 224C247.3 224 222.9 253.8 229.3 285.7L251.5 396.5C257.5 426.4 283.7 447.9 314.3 447.9L327.4 447.9C327.8 447.9 328.2 447.9 328.7 447.9L414.8 447.9C421.5 447.9 427.4 452 429.8 458.3L466 555.2C472 571.3 489.8 579.8 506.1 574.3L554.1 558.3C570.9 552.7 579.9 534.6 574.3 517.8C568.7 501 550.6 492 533.8 497.6L515.1 503.8L489.6 435.8C477.9 404.6 448 383.9 414.7 383.9L354.2 383.9zM159.2 248.7C155.2 231.5 137.9 220.8 120.7 224.9C103.5 229 92.8 246.2 96.9 263.4L143.5 461.4C157 519.1 208.6 560 268 560L352 560C369.7 560 384 545.7 384 528C384 510.3 369.7 496 352 496L268 496C238.3 496 212.5 475.6 205.7 446.7L159.1 248.7z\"/></svg>","cars/suitcase-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M264 112L376 112C380.4 112 384 115.6 384 120L384 160L256 160L256 120C256 115.6 259.6 112 264 112zM208 120L208 160L128 160C92.7 160 64 188.7 64 224L64 480C64 515.3 92.7 544 128 544L512 544C547.3 544 576 515.3 576 480L576 224C576 188.7 547.3 160 512 160L432 160L432 120C432 89.1 406.9 64 376 64L264 64C233.1 64 208 89.1 208 120zM232 208L416 208L416 496L224 496L224 208L232 208zM176 208L176 496L128 496C119.2 496 112 488.8 112 480L112 224C112 215.2 119.2 208 128 208L176 208zM464 496L464 208L512 208C520.8 208 528 215.2 528 224L528 480C528 488.8 520.8 496 512 496L464 496z\"/></svg>","hotels/check-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M534 132.5C544.8 140.2 547.2 155.2 539.5 166L275.5 534C271.4 539.7 265 543.4 258 543.9C251 544.4 244 542 239 537L103 401C93.6 391.6 93.6 376.4 103 367.1C112.4 357.8 127.6 357.7 136.9 367.1L253 483L500.5 138C508.2 127.2 523.2 124.8 534 132.5z\"/></svg>","hotels/circle-info-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M320 112C434.9 112 528 205.1 528 320C528 434.9 434.9 528 320 528C205.1 528 112 434.9 112 320C112 205.1 205.1 112 320 112zM320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM280 400C266.7 400 256 410.7 256 424C256 437.3 266.7 448 280 448L360 448C373.3 448 384 437.3 384 424C384 410.7 373.3 400 360 400L352 400L352 312C352 298.7 341.3 288 328 288L280 288C266.7 288 256 298.7 256 312C256 325.3 266.7 336 280 336L304 336L304 400L280 400zM320 256C337.7 256 352 241.7 352 224C352 206.3 337.7 192 320 192C302.3 192 288 206.3 288 224C288 241.7 302.3 256 320 256z\"/></svg>","hotels/coin-light-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M192 396.7C225.3 407.5 263.4 414.3 304 415.7L304 479.7C262.3 478.3 224.1 471.6 192 461.4L192 396.7zM96 341.7C112.2 358.2 134.1 372.6 160 384.3L160 449C157.7 447.9 155.5 446.9 153.3 445.8C110.4 424.3 96 400.6 96 383.9L96 341.7zM320 128C178.6 128 64 192.5 64 272L64 384C64 454.7 178.6 512 320 512C461.4 512 576 454.7 576 384L576 272C576 192.5 461.4 128 320 128zM480 384.4C505.9 372.7 527.8 358.2 544 341.8L544 384C544 400.7 529.6 424.4 486.7 445.9C484.5 447 482.3 448.1 480 449.1L480 384.4zM336 415.7C376.6 414.3 414.7 407.5 448 396.7L448 461.4C415.9 471.7 377.7 478.3 336 479.7L336 415.7zM96 272C96 210.1 196.3 160 320 160C443.7 160 544 210.1 544 272C544 333.9 443.7 384 320 384C196.3 384 96 333.9 96 272zM320 352C417.2 352 496 316.2 496 272C496 227.8 417.2 192 320 192C222.8 192 144 227.8 144 272C144 316.2 222.8 352 320 352zM184 272C184 245.5 244.9 224 320 224C395.1 224 456 245.5 456 272C456 298.5 395.1 320 320 320C244.9 320 184 298.5 184 272z\"/></svg>"};
@@ -110,9 +112,7 @@ function mount(root, card, spec) {
110
112
  root.append(el("p", "empty", spec.empty));
111
113
  return;
112
114
  }
113
- const head = el("div", "head");
114
- head.append(el("div", "title", card.title), el("div", "subtitle", card.subtitle));
115
- const row = el("div", "row");
115
+ const row = el("div", spec.bar ? "bar" : "row");
116
116
  for (const item of card.items) {
117
117
  const ui = {
118
118
  chosen: chosen === item.id,
@@ -126,7 +126,12 @@ function mount(root, card, spec) {
126
126
  };
127
127
  row.append(spec.option(item, ui));
128
128
  }
129
- root.append(head, row);
129
+ if (!spec.bar) {
130
+ const head = el("div", "head");
131
+ head.append(el("div", "title", card.title), el("div", "subtitle", card.subtitle));
132
+ root.append(head);
133
+ }
134
+ root.append(row);
130
135
  row.scrollLeft = scroll;
131
136
  root.classList.toggle("busy", busy);
132
137
  root.querySelectorAll("button").forEach((b) => (b.disabled = busy));
package/hotel-card.css CHANGED
@@ -1 +1 @@
1
- :root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme="dark"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:140px;}.opt{padding:0;overflow:hidden;}.body{display:flex;flex-direction:column;padding:var(--otto-space-7);}.photo{position:relative;height:var(--otto-photo-height);overflow:hidden;background:var(--otto-photo-bg);}.photo img{width:100%;height:100%;object-fit:cover;display:block;}.chips{position:absolute;top:var(--otto-space-4);left:var(--otto-space-4);right:var(--otto-space-4);display:flex;flex-wrap:wrap;gap:var(--otto-space-1);}.name{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);line-height:1.3;}.loyalty{display:flex;align-items:center;gap:var(--otto-space-2);margin-top:var(--otto-space-1);font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.loyalty svg{flex-shrink:0;}.stars{color:var(--otto-text-primary);--otto-icon-size:12px;}.fare{display:flex;align-items:center;gap:var(--otto-space-5);margin-top:var(--otto-space-7);}.fare .btn{flex:0 0 45%;}.fare>div{min-width:0;}.amount{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.per,.total{font-size:var(--otto-text-md);font-weight:var(--otto-weight-regular);color:var(--otto-text-secondary);}.per{color:inherit;}.total{display:block;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}
1
+ :root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme="dark"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:140px;}.opt{padding:0;overflow:hidden;}.body{display:flex;flex-direction:column;padding:var(--otto-space-7);}.chips{display:flex;flex-wrap:wrap;gap:var(--otto-space-1);margin-bottom:var(--otto-space-3);}.name{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);line-height:1.3;}.loyalty{display:flex;align-items:center;gap:var(--otto-space-2);margin-top:var(--otto-space-1);font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.loyalty svg{flex-shrink:0;}.stars{color:var(--otto-text-primary);--otto-icon-size:12px;}.fare{display:flex;align-items:center;gap:var(--otto-space-5);margin-top:var(--otto-space-7);}.fare .btn{flex:0 0 45%;}.fare>div{min-width:0;}.amount{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.per,.total{font-size:var(--otto-text-md);font-weight:var(--otto-weight-regular);color:var(--otto-text-secondary);}.per{color:inherit;}.total{display:block;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}
package/hotel-card.js CHANGED
@@ -8,8 +8,10 @@
8
8
  * the current pick and ui.pick(label) makes its Select button.
9
9
  * spec.message(item) is the text Select sends through the host's sendPrompt().
10
10
  * spec.empty is the line shown when the card has no items.
11
+ * spec.bar draws the options as one wrapping row of buttons with no title, for a card
12
+ * whose details the host already shows above it.
11
13
  */
12
- const CSS = ":root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme=\"light\"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme=\"dark\"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:140px;}.opt{padding:0;overflow:hidden;}.body{display:flex;flex-direction:column;padding:var(--otto-space-7);}.photo{position:relative;height:var(--otto-photo-height);overflow:hidden;background:var(--otto-photo-bg);}.photo img{width:100%;height:100%;object-fit:cover;display:block;}.chips{position:absolute;top:var(--otto-space-4);left:var(--otto-space-4);right:var(--otto-space-4);display:flex;flex-wrap:wrap;gap:var(--otto-space-1);}.name{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);line-height:1.3;}.loyalty{display:flex;align-items:center;gap:var(--otto-space-2);margin-top:var(--otto-space-1);font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.loyalty svg{flex-shrink:0;}.stars{color:var(--otto-text-primary);--otto-icon-size:12px;}.fare{display:flex;align-items:center;gap:var(--otto-space-5);margin-top:var(--otto-space-7);}.fare .btn{flex:0 0 45%;}.fare>div{min-width:0;}.amount{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.per,.total{font-size:var(--otto-text-md);font-weight:var(--otto-weight-regular);color:var(--otto-text-secondary);}.per{color:inherit;}.total{display:block;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}";
14
+ const CSS = ":root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme=\"light\"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme=\"dark\"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:140px;}.opt{padding:0;overflow:hidden;}.body{display:flex;flex-direction:column;padding:var(--otto-space-7);}.chips{display:flex;flex-wrap:wrap;gap:var(--otto-space-1);margin-bottom:var(--otto-space-3);}.name{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);line-height:1.3;}.loyalty{display:flex;align-items:center;gap:var(--otto-space-2);margin-top:var(--otto-space-1);font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.loyalty svg{flex-shrink:0;}.stars{color:var(--otto-text-primary);--otto-icon-size:12px;}.fare{display:flex;align-items:center;gap:var(--otto-space-5);margin-top:var(--otto-space-7);}.fare .btn{flex:0 0 45%;}.fare>div{min-width:0;}.amount{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.per,.total{font-size:var(--otto-text-md);font-weight:var(--otto-weight-regular);color:var(--otto-text-secondary);}.per{color:inherit;}.total{display:block;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}";
13
15
  const ICONS = {"hotels/check-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M534 132.5C544.8 140.2 547.2 155.2 539.5 166L275.5 534C271.4 539.7 265 543.4 258 543.9C251 544.4 244 542 239 537L103 401C93.6 391.6 93.6 376.4 103 367.1C112.4 357.8 127.6 357.7 136.9 367.1L253 483L500.5 138C508.2 127.2 523.2 124.8 534 132.5z\"/></svg>","hotels/circle-info-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M320 112C434.9 112 528 205.1 528 320C528 434.9 434.9 528 320 528C205.1 528 112 434.9 112 320C112 205.1 205.1 112 320 112zM320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM280 400C266.7 400 256 410.7 256 424C256 437.3 266.7 448 280 448L360 448C373.3 448 384 437.3 384 424C384 410.7 373.3 400 360 400L352 400L352 312C352 298.7 341.3 288 328 288L280 288C266.7 288 256 298.7 256 312C256 325.3 266.7 336 280 336L304 336L304 400L280 400zM320 256C337.7 256 352 241.7 352 224C352 206.3 337.7 192 320 192C302.3 192 288 206.3 288 224C288 241.7 302.3 256 320 256z\"/></svg>","hotels/coin-light-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M192 396.7C225.3 407.5 263.4 414.3 304 415.7L304 479.7C262.3 478.3 224.1 471.6 192 461.4L192 396.7zM96 341.7C112.2 358.2 134.1 372.6 160 384.3L160 449C157.7 447.9 155.5 446.9 153.3 445.8C110.4 424.3 96 400.6 96 383.9L96 341.7zM320 128C178.6 128 64 192.5 64 272L64 384C64 454.7 178.6 512 320 512C461.4 512 576 454.7 576 384L576 272C576 192.5 461.4 128 320 128zM480 384.4C505.9 372.7 527.8 358.2 544 341.8L544 384C544 400.7 529.6 424.4 486.7 445.9C484.5 447 482.3 448.1 480 449.1L480 384.4zM336 415.7C376.6 414.3 414.7 407.5 448 396.7L448 461.4C415.9 471.7 377.7 478.3 336 479.7L336 415.7zM96 272C96 210.1 196.3 160 320 160C443.7 160 544 210.1 544 272C544 333.9 443.7 384 320 384C196.3 384 96 333.9 96 272zM320 352C417.2 352 496 316.2 496 272C496 227.8 417.2 192 320 192C222.8 192 144 227.8 144 272C144 316.2 222.8 352 320 352zM184 272C184 245.5 244.9 224 320 224C395.1 224 456 245.5 456 272C456 298.5 395.1 320 320 320C244.9 320 184 298.5 184 272z\"/></svg>","hotels/star-solid-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M341.5 45.1C337.4 37.1 329.1 32 320.1 32C311.1 32 302.8 37.1 298.7 45.1L225.1 189.3L65.2 214.7C56.3 216.1 48.9 222.4 46.1 231C43.3 239.6 45.6 249 51.9 255.4L166.3 369.9L141.1 529.8C139.7 538.7 143.4 547.7 150.7 553C158 558.3 167.6 559.1 175.7 555L320.1 481.6L464.4 555C472.4 559.1 482.1 558.3 489.4 553C496.7 547.7 500.4 538.8 499 529.8L473.7 369.9L588.1 255.4C594.5 249 596.7 239.6 593.9 231C591.1 222.4 583.8 216.1 574.8 214.7L415 189.3L341.5 45.1z\"/></svg>"};
14
16
  const SYMBOLS = { USD: "$", EUR: "€", GBP: "£", JPY: "¥", CNY: "¥", AUD: "A$", CAD: "C$" };
15
17
  // The widget cannot see when the host's reply ends, so the lock lifts on a timer.
@@ -110,9 +112,7 @@ function mount(root, card, spec) {
110
112
  root.append(el("p", "empty", spec.empty));
111
113
  return;
112
114
  }
113
- const head = el("div", "head");
114
- head.append(el("div", "title", card.title), el("div", "subtitle", card.subtitle));
115
- const row = el("div", "row");
115
+ const row = el("div", spec.bar ? "bar" : "row");
116
116
  for (const item of card.items) {
117
117
  const ui = {
118
118
  chosen: chosen === item.id,
@@ -126,7 +126,12 @@ function mount(root, card, spec) {
126
126
  };
127
127
  row.append(spec.option(item, ui));
128
128
  }
129
- root.append(head, row);
129
+ if (!spec.bar) {
130
+ const head = el("div", "head");
131
+ head.append(el("div", "title", card.title), el("div", "subtitle", card.subtitle));
132
+ root.append(head);
133
+ }
134
+ root.append(row);
130
135
  row.scrollLeft = scroll;
131
136
  root.classList.toggle("busy", busy);
132
137
  root.querySelectorAll("button").forEach((b) => (b.disabled = busy));
@@ -165,7 +170,8 @@ function mount(root, card, spec) {
165
170
  }
166
171
 
167
172
  /* Otto hotel card: OttoHotelCard.render(root, card), card from render_hotels.
168
- * Show rooms sends "Show rooms at …" as the traveler's message. */
173
+ * Show rooms sends "Show rooms at …" as the traveler's message. No photo plate: the
174
+ * host's own map shows the hotels with their photos (see the hotel_rendering_elastic skill). */
169
175
 
170
176
  // Reasons first, then server facts; five lines keep the card short.
171
177
  const MAX_INSIGHTS = 5;
@@ -215,16 +221,13 @@ function render(root, card) {
215
221
 
216
222
  function option(item, ui) {
217
223
  const box = el("div", "opt" + (ui.chosen ? " chosen" : ""));
218
- const photo = el("div", "photo");
219
- photo.append(placeholder("hotel"));
224
+ const body = el("div", "body");
220
225
  const chipLabels = labels.get(item.id) || [];
221
226
  if (chipLabels.length) {
222
227
  const chips = el("div", "chips");
223
228
  for (const label of chipLabels) chips.append(el("span", "chip", label));
224
- photo.append(chips);
229
+ body.append(chips);
225
230
  }
226
-
227
- const body = el("div", "body");
228
231
  body.append(el("div", "name", item.name));
229
232
  if (item.star_rating) {
230
233
  const stars = el("div", "loyalty");
@@ -254,7 +257,7 @@ function render(root, card) {
254
257
  const insights = renderInsights([...(item.reasons || []), ...(item.facts || [])].slice(0, MAX_INSIGHTS));
255
258
  if (insights) body.append(insights);
256
259
 
257
- box.append(photo, body);
260
+ box.append(body);
258
261
  return box;
259
262
  }
260
263
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottotheagent/mcp-card-assets",
3
- "version": "0.7.0",
3
+ "version": "0.10.0",
4
4
  "description": "Renderers, styles, logos and photo placeholders for the Otto flight, hotel, room and car cards that Claude draws in its widget sandbox.",
5
5
  "license": "UNLICENSED",
6
6
  "files": [
package/room-card.css CHANGED
@@ -1 +1 @@
1
- :root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme="dark"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:140px;}.opt{gap:var(--otto-space-4);padding:0 0 var(--otto-space-7);overflow:hidden;}.opt>:not(.photo){margin-inline:var(--otto-space-7);}.photo{position:relative;aspect-ratio:16 / 10;overflow:hidden;background:var(--otto-photo-bg);}.photo img{position:relative;width:100%;height:100%;object-fit:cover;display:block;}.name{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);line-height:1.25;}.facts{display:flex;flex-direction:column;gap:var(--otto-space-2);padding:var(--otto-space-4) 0;border-top:1px solid var(--otto-divider);border-bottom:1px solid var(--otto-divider);}.fact{display:flex;align-items:center;gap:var(--otto-space-3);font-size:var(--otto-text-base);color:var(--otto-text-body);}.fact.caution{color:var(--otto-caution-text);}.fact.positive{color:var(--otto-positive-text);}.glyph{font-size:var(--otto-text-md);}.info{cursor:help;opacity:0.65;--otto-icon-size:12px;}.fare{display:flex;align-items:center;gap:var(--otto-space-5);}.fare .btn{flex:0 0 45%;}.fare>div{min-width:0;}.amount{font-size:17px;font-weight:var(--otto-weight-semibold);}.per,.total{font-size:var(--otto-text-sm);font-weight:var(--otto-weight-regular);color:var(--otto-text-secondary);}.total{display:block;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}
1
+ :root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme="dark"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:0;}.bar{display:flex;flex-wrap:wrap;gap:var(--otto-space-3);}.bar .btn{max-width:100%;}.bar .label{overflow:hidden;text-overflow:ellipsis;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}
package/room-card.js CHANGED
@@ -8,9 +8,11 @@
8
8
  * the current pick and ui.pick(label) makes its Select button.
9
9
  * spec.message(item) is the text Select sends through the host's sendPrompt().
10
10
  * spec.empty is the line shown when the card has no items.
11
+ * spec.bar draws the options as one wrapping row of buttons with no title, for a card
12
+ * whose details the host already shows above it.
11
13
  */
12
- const CSS = ":root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme=\"light\"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme=\"dark\"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:140px;}.opt{gap:var(--otto-space-4);padding:0 0 var(--otto-space-7);overflow:hidden;}.opt>:not(.photo){margin-inline:var(--otto-space-7);}.photo{position:relative;aspect-ratio:16 / 10;overflow:hidden;background:var(--otto-photo-bg);}.photo img{position:relative;width:100%;height:100%;object-fit:cover;display:block;}.name{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);line-height:1.25;}.facts{display:flex;flex-direction:column;gap:var(--otto-space-2);padding:var(--otto-space-4) 0;border-top:1px solid var(--otto-divider);border-bottom:1px solid var(--otto-divider);}.fact{display:flex;align-items:center;gap:var(--otto-space-3);font-size:var(--otto-text-base);color:var(--otto-text-body);}.fact.caution{color:var(--otto-caution-text);}.fact.positive{color:var(--otto-positive-text);}.glyph{font-size:var(--otto-text-md);}.info{cursor:help;opacity:0.65;--otto-icon-size:12px;}.fare{display:flex;align-items:center;gap:var(--otto-space-5);}.fare .btn{flex:0 0 45%;}.fare>div{min-width:0;}.amount{font-size:17px;font-weight:var(--otto-weight-semibold);}.per,.total{font-size:var(--otto-text-sm);font-weight:var(--otto-weight-regular);color:var(--otto-text-secondary);}.total{display:block;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}";
13
- const ICONS = {"hotels/arrow-rotate-left-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M88 64C101.3 64 112 74.7 112 88L112 166.1L139.1 139C239.1 39 401.2 39 501.1 139C601 239 601.1 401.1 501.1 501C401.1 600.9 239 601 139.1 501C124.2 486.1 111.5 469.8 101 452.5C94.1 441.2 97.8 426.4 109.1 419.5C120.4 412.6 135.2 416.3 142.1 427.6C150.6 441.6 160.9 454.9 173.1 467C254.3 548.2 386 548.2 467.3 467C548.6 385.8 548.5 254.1 467.3 172.8C386.1 91.5 254.4 91.6 173.1 172.8L146 199.9L224.2 199.9C237.5 199.9 248.2 210.6 248.2 223.9C248.2 237.2 237.5 247.9 224.2 247.9L88 248C74.7 248 64 237.3 64 224L64 88C64 74.7 74.7 64 88 64z\"/></svg>","hotels/check-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M534 132.5C544.8 140.2 547.2 155.2 539.5 166L275.5 534C271.4 539.7 265 543.4 258 543.9C251 544.4 244 542 239 537L103 401C93.6 391.6 93.6 376.4 103 367.1C112.4 357.8 127.6 357.7 136.9 367.1L253 483L500.5 138C508.2 127.2 523.2 124.8 534 132.5z\"/></svg>","hotels/circle-info-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M320 112C434.9 112 528 205.1 528 320C528 434.9 434.9 528 320 528C205.1 528 112 434.9 112 320C112 205.1 205.1 112 320 112zM320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM280 400C266.7 400 256 410.7 256 424C256 437.3 266.7 448 280 448L360 448C373.3 448 384 437.3 384 424C384 410.7 373.3 400 360 400L352 400L352 312C352 298.7 341.3 288 328 288L280 288C266.7 288 256 298.7 256 312C256 325.3 266.7 336 280 336L304 336L304 400L280 400zM320 256C337.7 256 352 241.7 352 224C352 206.3 337.7 192 320 192C302.3 192 288 206.3 288 224C288 241.7 302.3 256 320 256z\"/></svg>","hotels/wallet-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M152 96C103.4 96 64 135.4 64 184L64 424C64 472.6 103.4 512 152 512L488 512C536.6 512 576 472.6 576 424L576 280C576 231.4 536.6 192 488 192L184 192C170.7 192 160 202.7 160 216C160 229.3 170.7 240 184 240L488 240C510.1 240 528 257.9 528 280L528 424C528 446.1 510.1 464 488 464L152 464C129.9 464 112 446.1 112 424L112 184C112 161.9 129.9 144 152 144L520 144C533.3 144 544 133.3 544 120C544 106.7 533.3 96 520 96L152 96zM448 384C465.7 384 480 369.7 480 352C480 334.3 465.7 320 448 320C430.3 320 416 334.3 416 352C416 369.7 430.3 384 448 384z\"/></svg>"};
14
+ const CSS = ":root{--otto-card-bg:#ffffff;--otto-card-border:#d4d4d4;--otto-btn-border:#525252;--otto-accent:#08c0cc;--otto-divider:#e5e5e5;--otto-strip-bg:#f5f5f5;--otto-fare-bg:#262624;--otto-on-fare:#ffffff;--otto-chip-bg:#ccfbf1;--otto-chip-text:#0a0a0a;--otto-text-primary:#0a0a0a;--otto-text-body:#0a0a0a;--otto-text-secondary:#737373;--otto-link-hover:#0369a1;--otto-banner-bg:#171717;--otto-on-banner:#ffffff;--otto-caution-bg:#fdf0dd;--otto-caution-text:#96590b;--otto-positive-text:#1c7a3e;--otto-photo-bg:#f5f5f5;--otto-font:Inter,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\";--otto-line:1.5;--otto-text-xs:10px;--otto-text-sm:11px;--otto-text-md:12px;--otto-text-base:13px;--otto-text-lg:14px;--otto-text-xl:18px;--otto-text-2xl:19px;--otto-weight-regular:400;--otto-weight-medium:500;--otto-weight-semibold:600;--otto-space-1:2px;--otto-space-2:6px;--otto-space-3:8px;--otto-space-4:10px;--otto-space-5:12px;--otto-space-6:14px;--otto-space-7:16px;--otto-space-8:18px;--otto-radius-card:12px;--otto-radius-fare:10px;--otto-radius-md:8px;--otto-radius-pill:999px;--otto-card-width:304px;--otto-icon-size:14px;--otto-photo-height:170px;--otto-mark-size:24px;--otto-chip-height:24px;--otto-tap-min:44px;}@media (prefers-color-scheme:dark){:root:not([data-theme=\"light\"]){--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}}:root[data-theme=\"dark\"]{--otto-card-bg:#262624;--otto-card-border:#3d3d3a;--otto-btn-border:#a3a3a3;--otto-divider:#3d3d3a;--otto-strip-bg:#1c1c1b;--otto-fare-bg:#3d3d3a;--otto-text-primary:#ffffff;--otto-text-body:#f5f5f5;--otto-text-secondary:#a3a3a3;--otto-link-hover:#38bdf8;--otto-banner-bg:#3d3d3a;--otto-chip-bg:#0d3d3a;--otto-chip-text:#ccfbf1;--otto-caution-bg:#3b2a12;--otto-caution-text:#f5b65b;--otto-positive-text:#5fd08a;--otto-photo-bg:#1c1c1b;}html,body{margin:0;background:transparent;font-family:var(--otto-font);line-height:var(--otto-line);color:var(--otto-text-primary);}#root{min-height:120px;padding:0;}button{font:inherit;}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--otto-icon-size);height:var(--otto-icon-size);line-height:1;}.icon svg{width:100%;height:100%;fill:currentColor;}.head{margin-bottom:var(--otto-space-5);}.title{font-size:var(--otto-text-lg);line-height:22px;font-weight:var(--otto-weight-semibold);}.subtitle,.empty{font-size:var(--otto-text-base);color:var(--otto-text-secondary);}.row{display:flex;gap:var(--otto-space-5);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:var(--otto-space-2);}.opt{flex:0 0 var(--otto-card-width);width:var(--otto-card-width);min-width:0;overflow-wrap:anywhere;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:var(--otto-space-7);background:var(--otto-card-bg);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);}.opt.chosen{border-color:var(--otto-accent);}@media (hover:hover){.opt:hover{border-color:var(--otto-accent);}}.chip{display:inline-flex;align-items:center;height:var(--otto-chip-height);padding:0 var(--otto-space-4);box-sizing:border-box;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-radius:var(--otto-radius-pill);font-size:var(--otto-text-sm);font-weight:var(--otto-weight-semibold);background:var(--otto-chip-bg);color:var(--otto-chip-text);}.chip.caution{background:var(--otto-caution-bg);color:var(--otto-caution-text);}.btn{flex-shrink:0;min-height:41px;padding:9px var(--otto-space-7);box-sizing:border-box;white-space:nowrap;cursor:pointer;border-radius:var(--otto-radius-md);font-size:var(--otto-text-lg);font-weight:var(--otto-weight-medium);display:inline-flex;align-items:center;justify-content:center;gap:var(--otto-space-2);background:transparent;color:var(--otto-text-primary);border:1px solid var(--otto-btn-border);}.btn:disabled{cursor:default;opacity:0.45;}.why{margin-top:var(--otto-space-7);font-size:var(--otto-text-base);color:var(--otto-text-body);}.whyHead{font-weight:var(--otto-weight-semibold);}.whyLine{display:flex;gap:var(--otto-space-3);}.tick{--otto-icon-size:12px;height:19.5px;font-size:var(--otto-text-md);}.tick.note{color:var(--otto-text-secondary);}@media (max-width:480px){.opt{flex-basis:min(var(--otto-card-width),82vw);width:min(var(--otto-card-width),82vw);}.btn{min-height:var(--otto-tap-min);}}#root.busy .opt{cursor:default;}#root.busy .opt:not(.chosen):hover{border-color:var(--otto-card-border);}.btn.chosen:disabled{opacity:1;}#root{min-height:0;}.bar{display:flex;flex-wrap:wrap;gap:var(--otto-space-3);}.bar .btn{max-width:100%;}.bar .label{overflow:hidden;text-overflow:ellipsis;}.btn.chosen{background:var(--otto-text-primary);color:var(--otto-card-bg);border-color:var(--otto-text-primary);}";
15
+ const ICONS = {"hotels/check-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M534 132.5C544.8 140.2 547.2 155.2 539.5 166L275.5 534C271.4 539.7 265 543.4 258 543.9C251 544.4 244 542 239 537L103 401C93.6 391.6 93.6 376.4 103 367.1C112.4 357.8 127.6 357.7 136.9 367.1L253 483L500.5 138C508.2 127.2 523.2 124.8 534 132.5z\"/></svg>","hotels/circle-info-regular-full":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><path d=\"M320 112C434.9 112 528 205.1 528 320C528 434.9 434.9 528 320 528C205.1 528 112 434.9 112 320C112 205.1 205.1 112 320 112zM320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM280 400C266.7 400 256 410.7 256 424C256 437.3 266.7 448 280 448L360 448C373.3 448 384 437.3 384 424C384 410.7 373.3 400 360 400L352 400L352 312C352 298.7 341.3 288 328 288L280 288C266.7 288 256 298.7 256 312C256 325.3 266.7 336 280 336L304 336L304 400L280 400zM320 256C337.7 256 352 241.7 352 224C352 206.3 337.7 192 320 192C302.3 192 288 206.3 288 224C288 241.7 302.3 256 320 256z\"/></svg>"};
14
16
  const SYMBOLS = { USD: "$", EUR: "€", GBP: "£", JPY: "¥", CNY: "¥", AUD: "A$", CAD: "C$" };
15
17
  // The widget cannot see when the host's reply ends, so the lock lifts on a timer.
16
18
  const BUSY_MS = 60000;
@@ -110,9 +112,7 @@ function mount(root, card, spec) {
110
112
  root.append(el("p", "empty", spec.empty));
111
113
  return;
112
114
  }
113
- const head = el("div", "head");
114
- head.append(el("div", "title", card.title), el("div", "subtitle", card.subtitle));
115
- const row = el("div", "row");
115
+ const row = el("div", spec.bar ? "bar" : "row");
116
116
  for (const item of card.items) {
117
117
  const ui = {
118
118
  chosen: chosen === item.id,
@@ -126,7 +126,12 @@ function mount(root, card, spec) {
126
126
  };
127
127
  row.append(spec.option(item, ui));
128
128
  }
129
- root.append(head, row);
129
+ if (!spec.bar) {
130
+ const head = el("div", "head");
131
+ head.append(el("div", "title", card.title), el("div", "subtitle", card.subtitle));
132
+ root.append(head);
133
+ }
134
+ root.append(row);
130
135
  row.scrollLeft = scroll;
131
136
  root.classList.toggle("busy", busy);
132
137
  root.querySelectorAll("button").forEach((b) => (b.disabled = busy));
@@ -164,77 +169,53 @@ function mount(root, card, spec) {
164
169
  return handle;
165
170
  }
166
171
 
167
- /* Otto room card: OttoRoomCard.render(root, card), card from render_hotel_rooms.
168
- * Select sends "Select room …" as the traveler's message. */
169
-
170
- // Fact icon → [icon name, text fallback].
171
- const FACT_ICONS = {
172
- refund: ["hotels/arrow-rotate-left-regular-full", "⊘"],
173
- payment: ["hotels/wallet-regular-full", "▭"],
174
- policy: ["hotels/circle-info-regular-full", "⚑"],
175
- };
172
+ /* Otto room buttons: OttoRoomCard.render(root, card), card from render_elastic_hotel_rooms.
173
+ * One button per room, labeled with its name; a click sends "Select room …" as the
174
+ * traveler's message. The host draws the rooms themselves, with photos, from the same
175
+ * tool result (see the hotel_room_rendering_elastic skill). */
176
176
 
177
177
  function render(root, card) {
178
178
  injectCss("otto-room-card-css");
179
- const money = (value, item) => formatMoney(value, item?.currency || card.currency);
180
179
  const exactMoney = (value, item) => formatExactMoney(value, item?.currency || card.currency);
180
+ const refundText = (item) => (item.facts || []).find((fact) => fact.icon === "refund")?.text || "";
181
181
 
182
182
  // The pick as a traveler would say it: room, hotel, rate, and its refund terms.
183
183
  function describe(item) {
184
184
  const price = exactMoney(item.nightly_rate, item);
185
- const refund = (item.facts || []).find((fact) => fact.icon === "refund");
186
- return [oneLine(item.name), oneLine(card.title), price ? `${price}/night` : "", refund ? oneLine(refund.text) : ""]
185
+ const refund = refundText(item);
186
+ return [oneLine(item.name), oneLine(card.title), price ? `${price}/night` : "", refund ? oneLine(refund) : ""]
187
187
  .filter(Boolean)
188
188
  .join(", ");
189
189
  }
190
190
 
191
- function fact(entry) {
192
- const line = el("div", `fact ${entry.tone}`);
193
- const [name, glyph] = FACT_ICONS[entry.icon] || [null, "·"];
194
- line.append(icon(name, glyph, "icon glyph"), el("span", null, entry.text));
195
- // The supplier's own wording; `title` is the only tooltip the sandbox offers.
196
- if (entry.detail) {
197
- const info = icon("hotels/circle-info-regular-full", "ⓘ", "icon info");
198
- info.removeAttribute("aria-hidden");
199
- info.title = entry.detail;
200
- line.append(info);
201
- }
202
- return line;
191
+ // The room's name, plus the first of rate, refund terms and position that no
192
+ // same-named room shares: hotels often list one room type at several rates.
193
+ function label(item) {
194
+ const name = oneLine(item.name);
195
+ const key = (other) => oneLine(other.name).toLowerCase();
196
+ const twins = card.items.filter((other) => other !== item && key(other) === key(item));
197
+ if (!twins.length) return name;
198
+ const details = (other) => [
199
+ other.nightly_rate === null || other.nightly_rate === undefined ? "" : `${exactMoney(other.nightly_rate, other)}/night`,
200
+ oneLine(refundText(other), 40),
201
+ `No. ${card.items.indexOf(other) + 1}`,
202
+ ];
203
+ const detail = details(item).find((value, i) => value && twins.every((twin) => details(twin)[i] !== value));
204
+ return detail ? `${name} · ${detail}` : name;
203
205
  }
204
206
 
205
207
  function option(item, ui) {
206
- const box = el("div", "opt" + (ui.chosen ? " chosen" : ""));
207
- const photo = el("div", "photo");
208
- photo.append(placeholder("room"));
209
-
210
- const facts = el("div", "facts");
211
- for (const entry of item.facts || []) facts.append(fact(entry));
212
-
213
- const fare = el("div", "fare");
214
- const price = el("div");
215
- if (item.nightly_rate !== null && item.nightly_rate !== undefined) {
216
- const amount = el("div", "amount", money(item.nightly_rate, item));
217
- amount.append(el("span", "per", "/night"));
218
- price.append(amount);
219
- }
220
- if (item.total_rate !== null && item.total_rate !== undefined) {
221
- price.append(el("span", "total", `${money(item.total_rate, item)} ${card.price_label}`));
222
- }
223
- fare.append(ui.pick("Select"), price);
224
-
225
- box.append(photo, el("div", "name", item.name));
226
- if (facts.childElementCount) box.append(facts);
227
- box.append(fare);
228
- const insights = renderInsights(item.reasons);
229
- if (insights) {
230
- insights.style.marginTop = "0";
231
- box.append(insights);
232
- }
233
- return box;
208
+ const button = el("button", "btn" + (ui.chosen ? " chosen" : ""));
209
+ button.dataset.select = item.id;
210
+ button.title = label(item);
211
+ if (ui.chosen) button.append(icon("hotels/check-regular-full", "✓"));
212
+ button.append(el("span", "label", label(item)));
213
+ return button;
234
214
  }
235
215
 
236
216
  mount(root, card, {
237
217
  empty: "No room options to show.",
218
+ bar: true,
238
219
  option,
239
220
  message: (item) => `Select room ${describe(item)}`,
240
221
  });