@ottotheagent/mcp-card-assets 0.12.0 → 0.14.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 +2 -2
- package/flight-card.js +4 -1
- package/package.json +1 -1
- package/seat-map.css +1 -1
- package/seat-map.js +38 -14
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @ottotheagent/mcp-card-assets
|
|
2
2
|
|
|
3
|
-
**Created**: 2026-10-02 | **Last Updated**: 2026-10-
|
|
3
|
+
**Created**: 2026-10-02 | **Last Updated**: 2026-10-07
|
|
4
4
|
|
|
5
5
|
Renderers, logos and photo placeholders for the Otto flight, hotel, room and car cards and the seat map. Each `render_*` MCP tool returns card data and a snippet. The host's model mounts the card in its own widget sandbox, which loads files only from a few public CDNs, so the assets are served from jsDelivr.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
9
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, and since 0.11.0 `car-card.js` draws only a row of car buttons: the host draws the rooms or cars, with photos, from the same tool's MCP Apps resource.
|
|
10
|
-
- `seat-map.js` exposes `OttoSeatMap.render(rootElement, card)` (since 0.12.0). It draws one flight's cabin from `render_elastic_seat_map`. The server lays out the columns, aisles and rows for the aircraft and cabin, so the script only draws them. Update
|
|
10
|
+
- `seat-map.js` exposes `OttoSeatMap.render(rootElement, card)` (since 0.12.0). It draws one flight's cabin from `render_elastic_seat_map`. The server lays out the columns, aisles and rows for the aircraft and cabin, so the script only draws them. Clicking a seat marks it New selection without sending anything; since 0.13.0 the **Update seat to 15D** button then sends "Update seat to 15D (route)". Since 0.14.0 it tags exit rows and bulkhead rows, from the card's `exit` and `bulkhead` row flags. Also since 0.14.0, each row of the flight card's fare list starts its facts line with the fare's refund wording.
|
|
11
11
|
- 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 "Select car …". The message carries no id.
|
|
12
12
|
- `*-card.css` holds each card's CSS as a separate file, for reference.
|
|
13
13
|
- `marks/<IATA>.png` holds airline logos, and `marks/cars/<code>.svg` holds car-company logos.
|
package/flight-card.js
CHANGED
|
@@ -185,7 +185,10 @@
|
|
|
185
185
|
const row = el("div", "ladderRow" + (fare.recommended ? " recommended" : ""));
|
|
186
186
|
const label = el("div", "ladderLabel");
|
|
187
187
|
label.append(el("div", "ladderName", fare.name + (fare.recommended ? " · Recommended" : "")));
|
|
188
|
-
|
|
188
|
+
// Refund wording leads the facts line; it comes from the fare, never inferred.
|
|
189
|
+
const facts = [oneLine(fare.refundable), ...(fare.facts || []), fare.within_policy === false ? "Out of policy" : ""]
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.join(" · ");
|
|
189
192
|
if (facts) label.append(el("div", "ladderFacts", facts));
|
|
190
193
|
const right = el("div", "ladderRight");
|
|
191
194
|
right.append(el("div", "ladderAmount", money(fare.price)), selectButton("btn", fare));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottotheagent/mcp-card-assets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Renderers, styles, logos and photo placeholders for the Otto flight, hotel, room and car cards and the seat map that Claude draws in its widget sandbox.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"files": [
|
package/seat-map.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);}}.sm{--sm-seat:28px;--sm-gap:4px;--sm-aisle:16px;box-sizing:border-box;max-width:100%;padding:var(--otto-space-7);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);background:var(--otto-card-bg);color:var(--otto-text-primary);}.sm.roomy{--sm-seat:34px;}.sm.suite{--sm-seat:40px;--sm-gap:6px;--sm-aisle:22px;}.smHead{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--otto-space-5);margin-bottom:var(--otto-space-5);}.smHead .title{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.smHead .subtitle{font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.smToggle{min-height:32px;padding:4px var(--otto-space-5);font-size:var(--otto-text-md);}.smScroll{overflow-x:auto;}.smGrid{display:inline-flex;flex-direction:column;gap:var(--otto-space-2);}.smRow{display:flex;align-items:center;gap:var(--sm-gap);}.smNum{width:28px;flex-shrink:0;font-size:var(--otto-text-md);color:var(--otto-text-secondary);font-variant-numeric:tabular-nums;}.smNum.exit{color:var(--otto-caution-text);font-weight:var(--otto-weight-semibold);}.smAisle{width:var(--sm-aisle);flex-shrink:0;}.smLetter,.smSeat,.smEmpty{width:var(--sm-seat);height:var(--sm-seat);flex-shrink:0;}.smLetter{height:auto;text-align:center;font-size:var(--otto-text-sm);color:var(--otto-text-secondary);}.smSeat{box-sizing:border-box;display:grid;place-items:center;padding:0;border:1px solid var(--otto-card-border);border-radius:6px;background:var(--otto-card-bg);color:var(--otto-text-body);font-size:var(--otto-text-md);font-weight:var(--otto-weight-medium);line-height: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);}}.sm{--sm-seat:28px;--sm-gap:4px;--sm-aisle:16px;box-sizing:border-box;max-width:100%;padding:var(--otto-space-7);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);background:var(--otto-card-bg);color:var(--otto-text-primary);}.sm.roomy{--sm-seat:34px;}.sm.suite{--sm-seat:40px;--sm-gap:6px;--sm-aisle:22px;}.smHead{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--otto-space-5);margin-bottom:var(--otto-space-5);}.smHead .title{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.smHead .subtitle{font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.smToggle{min-height:32px;padding:4px var(--otto-space-5);font-size:var(--otto-text-md);}.smScroll{overflow-x:auto;}.smGrid{display:inline-flex;flex-direction:column;gap:var(--otto-space-2);}.smRow{display:flex;align-items:center;gap:var(--sm-gap);}.smNum{width:28px;flex-shrink:0;font-size:var(--otto-text-md);color:var(--otto-text-secondary);font-variant-numeric:tabular-nums;}.smNum.exit{color:var(--otto-caution-text);font-weight:var(--otto-weight-semibold);}.smRow.bulkhead{padding-top:var(--otto-space-2);border-top:3px solid var(--otto-text-secondary);}.smTag{flex-shrink:0;margin-left:var(--otto-space-2);font-size:var(--otto-text-sm);color:var(--otto-text-secondary);white-space:nowrap;}.smTag.exit{color:var(--otto-caution-text);font-weight:var(--otto-weight-semibold);}.smAisle{width:var(--sm-aisle);flex-shrink:0;}.smLetter,.smSeat,.smEmpty{width:var(--sm-seat);height:var(--sm-seat);flex-shrink:0;}.smLetter{height:auto;text-align:center;font-size:var(--otto-text-sm);color:var(--otto-text-secondary);}.smSeat{box-sizing:border-box;display:grid;place-items:center;padding:0;border:1px solid var(--otto-card-border);border-radius:6px;background:var(--otto-card-bg);color:var(--otto-text-body);font-size:var(--otto-text-md);font-weight:var(--otto-weight-medium);line-height:1;}.smSeat[role="button"]{cursor:pointer;user-select:none;}.smSeat[role="button"]:hover{border-color:var(--otto-accent);}.smSeat[role="button"]:focus-visible{outline:2px solid var(--otto-accent);outline-offset:1px;}.smSeat.paid{background:var(--otto-caution-bg);}.smSeat.legroom{border-top-width:3px;}.smSeat.off{border-color:var(--otto-divider);background:var(--otto-strip-bg);color:var(--otto-text-secondary);cursor:default;}.smSeat.mine{border-color:var(--otto-accent);background:var(--otto-accent);color:#0a0a0a;font-weight:var(--otto-weight-semibold);}.smSeat.pick{border:2px solid var(--otto-accent);background:var(--otto-chip-bg);color:var(--otto-chip-text);font-weight:var(--otto-weight-semibold);}.smLegend{display:flex;flex-wrap:wrap;gap:var(--otto-space-2) var(--otto-space-5);margin-top:var(--otto-space-5);font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.smKey{display:inline-flex;align-items:center;gap:var(--otto-space-2);}.smKey .smSeat{--sm-seat:16px;font-size:9px;border-radius:4px;}.smFoot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--otto-space-5);margin-top:var(--otto-space-5);font-size:var(--otto-text-base);}.smFoot .btn{border:1px solid var(--otto-btn-border);color:var(--otto-text-primary);}.smFoot .btn:hover:not(:disabled){border-color:var(--otto-accent);}.sm.busy button,.sm.busy .smSeat{cursor:default;}
|
package/seat-map.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* spec.bar draws the options as one wrapping row of buttons with no title, for a card
|
|
12
12
|
* whose details the host already shows above it.
|
|
13
13
|
*/
|
|
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);}}.sm{--sm-seat:28px;--sm-gap:4px;--sm-aisle:16px;box-sizing:border-box;max-width:100%;padding:var(--otto-space-7);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);background:var(--otto-card-bg);color:var(--otto-text-primary);}.sm.roomy{--sm-seat:34px;}.sm.suite{--sm-seat:40px;--sm-gap:6px;--sm-aisle:22px;}.smHead{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--otto-space-5);margin-bottom:var(--otto-space-5);}.smHead .title{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.smHead .subtitle{font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.smToggle{min-height:32px;padding:4px var(--otto-space-5);font-size:var(--otto-text-md);}.smScroll{overflow-x:auto;}.smGrid{display:inline-flex;flex-direction:column;gap:var(--otto-space-2);}.smRow{display:flex;align-items:center;gap:var(--sm-gap);}.smNum{width:28px;flex-shrink:0;font-size:var(--otto-text-md);color:var(--otto-text-secondary);font-variant-numeric:tabular-nums;}.smNum.exit{color:var(--otto-caution-text);font-weight:var(--otto-weight-semibold);}.smAisle{width:var(--sm-aisle);flex-shrink:0;}.smLetter,.smSeat,.smEmpty{width:var(--sm-seat);height:var(--sm-seat);flex-shrink:0;}.smLetter{height:auto;text-align:center;font-size:var(--otto-text-sm);color:var(--otto-text-secondary);}.smSeat{box-sizing:border-box;display:grid;place-items:center;padding:0;border:1px solid var(--otto-card-border);border-radius:6px;background:var(--otto-card-bg);color:var(--otto-text-body);font-size:var(--otto-text-md);font-weight:var(--otto-weight-medium);line-height:1;}.
|
|
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);}}.sm{--sm-seat:28px;--sm-gap:4px;--sm-aisle:16px;box-sizing:border-box;max-width:100%;padding:var(--otto-space-7);border:1px solid var(--otto-card-border);border-radius:var(--otto-radius-card);background:var(--otto-card-bg);color:var(--otto-text-primary);}.sm.roomy{--sm-seat:34px;}.sm.suite{--sm-seat:40px;--sm-gap:6px;--sm-aisle:22px;}.smHead{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--otto-space-5);margin-bottom:var(--otto-space-5);}.smHead .title{font-size:var(--otto-text-lg);font-weight:var(--otto-weight-semibold);}.smHead .subtitle{font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.smToggle{min-height:32px;padding:4px var(--otto-space-5);font-size:var(--otto-text-md);}.smScroll{overflow-x:auto;}.smGrid{display:inline-flex;flex-direction:column;gap:var(--otto-space-2);}.smRow{display:flex;align-items:center;gap:var(--sm-gap);}.smNum{width:28px;flex-shrink:0;font-size:var(--otto-text-md);color:var(--otto-text-secondary);font-variant-numeric:tabular-nums;}.smNum.exit{color:var(--otto-caution-text);font-weight:var(--otto-weight-semibold);}.smRow.bulkhead{padding-top:var(--otto-space-2);border-top:3px solid var(--otto-text-secondary);}.smTag{flex-shrink:0;margin-left:var(--otto-space-2);font-size:var(--otto-text-sm);color:var(--otto-text-secondary);white-space:nowrap;}.smTag.exit{color:var(--otto-caution-text);font-weight:var(--otto-weight-semibold);}.smAisle{width:var(--sm-aisle);flex-shrink:0;}.smLetter,.smSeat,.smEmpty{width:var(--sm-seat);height:var(--sm-seat);flex-shrink:0;}.smLetter{height:auto;text-align:center;font-size:var(--otto-text-sm);color:var(--otto-text-secondary);}.smSeat{box-sizing:border-box;display:grid;place-items:center;padding:0;border:1px solid var(--otto-card-border);border-radius:6px;background:var(--otto-card-bg);color:var(--otto-text-body);font-size:var(--otto-text-md);font-weight:var(--otto-weight-medium);line-height:1;}.smSeat[role=\"button\"]{cursor:pointer;user-select:none;}.smSeat[role=\"button\"]:hover{border-color:var(--otto-accent);}.smSeat[role=\"button\"]:focus-visible{outline:2px solid var(--otto-accent);outline-offset:1px;}.smSeat.paid{background:var(--otto-caution-bg);}.smSeat.legroom{border-top-width:3px;}.smSeat.off{border-color:var(--otto-divider);background:var(--otto-strip-bg);color:var(--otto-text-secondary);cursor:default;}.smSeat.mine{border-color:var(--otto-accent);background:var(--otto-accent);color:#0a0a0a;font-weight:var(--otto-weight-semibold);}.smSeat.pick{border:2px solid var(--otto-accent);background:var(--otto-chip-bg);color:var(--otto-chip-text);font-weight:var(--otto-weight-semibold);}.smLegend{display:flex;flex-wrap:wrap;gap:var(--otto-space-2) var(--otto-space-5);margin-top:var(--otto-space-5);font-size:var(--otto-text-md);color:var(--otto-text-secondary);}.smKey{display:inline-flex;align-items:center;gap:var(--otto-space-2);}.smKey .smSeat{--sm-seat:16px;font-size:9px;border-radius:4px;}.smFoot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--otto-space-5);margin-top:var(--otto-space-5);font-size:var(--otto-text-base);}.smFoot .btn{border:1px solid var(--otto-btn-border);color:var(--otto-text-primary);}.smFoot .btn:hover:not(:disabled){border-color:var(--otto-accent);}.sm.busy button,.sm.busy .smSeat{cursor:default;}";
|
|
15
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>"};
|
|
16
16
|
const SYMBOLS = { USD: "$", EUR: "€", GBP: "£", JPY: "¥", CNY: "¥", AUD: "A$", CAD: "C$" };
|
|
17
17
|
// The widget cannot see when the host's reply ends, so the lock lifts on a timer.
|
|
@@ -172,8 +172,9 @@ function mount(root, card, spec) {
|
|
|
172
172
|
/* Otto seat map: OttoSeatMap.render(root, card), card from render_elastic_seat_map.
|
|
173
173
|
* The server lays the cabin out (columns, aisles, rows aligned to columns), so any aircraft
|
|
174
174
|
* and cabin draws the same way. Collapsed, it shows the traveler's row and one row either
|
|
175
|
-
* side; Expand shows the whole cabin.
|
|
176
|
-
* "Update seat to 15D (SEA → JFK)" as the
|
|
175
|
+
* side; Expand shows the whole cabin. Clicking another seat only marks it New selection and
|
|
176
|
+
* shows "Update seat to 15D"; that button sends "Update seat to 15D (SEA → JFK)" as the
|
|
177
|
+
* traveler's message. */
|
|
177
178
|
|
|
178
179
|
function render(root, card) {
|
|
179
180
|
injectCss("otto-seat-map-css");
|
|
@@ -202,7 +203,10 @@ function render(root, card) {
|
|
|
202
203
|
return { free: "free", paid: "paid", unavailable: "off" }[seat.state] || "off";
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
+
// Row facts come only from the card (the seats table's is_exit_row / is_bulkhead).
|
|
207
|
+
const rowFacts = (row) => [row.exit && "Exit row", row.bulkhead && "Bulkhead"].filter(Boolean);
|
|
208
|
+
|
|
209
|
+
function seatNode(seat, facts) {
|
|
206
210
|
const cls = seatClass(seat);
|
|
207
211
|
const legroom = seat.extra_legroom && cls !== "off" ? " legroom" : "";
|
|
208
212
|
if (cls === "off") {
|
|
@@ -212,10 +216,15 @@ function render(root, card) {
|
|
|
212
216
|
node.title = `${seat.id} · unavailable`;
|
|
213
217
|
return node;
|
|
214
218
|
}
|
|
215
|
-
|
|
216
|
-
|
|
219
|
+
// A span, not a <button>: host sandboxes restyle every button (background, border,
|
|
220
|
+
// padding), which hid the seat states and pushed the letter off center.
|
|
221
|
+
const button = el("span", `smSeat ${cls}${legroom}`, letter(seat.id));
|
|
222
|
+
button.setAttribute("role", "button");
|
|
223
|
+
button.tabIndex = busy ? -1 : 0;
|
|
217
224
|
button.dataset.seat = seat.id;
|
|
218
|
-
button.title = describe(seat)
|
|
225
|
+
button.title = [describe(seat), seat.extra_legroom && "extra legroom", ...facts.map((f) => f.toLowerCase())]
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
.join(" · ") + (cls === "pick" ? " · new selection" : "");
|
|
219
228
|
button.setAttribute("aria-label", button.title);
|
|
220
229
|
button.setAttribute("aria-pressed", String(cls === "mine" || cls === "pick"));
|
|
221
230
|
return button;
|
|
@@ -231,15 +240,19 @@ function render(root, card) {
|
|
|
231
240
|
return line;
|
|
232
241
|
}
|
|
233
242
|
|
|
243
|
+
// An exit row or bulkhead row gets a tag after its last seat; a bulkhead also draws the wall above it.
|
|
234
244
|
function seatRow(row) {
|
|
235
|
-
const
|
|
245
|
+
const facts = rowFacts(row);
|
|
246
|
+
const line = el("div", "smRow" + (row.bulkhead ? " bulkhead" : ""));
|
|
236
247
|
const number = el("span", "smNum" + (row.exit ? " exit" : ""), row.number);
|
|
237
|
-
if (
|
|
248
|
+
if (facts.length) number.title = facts.join(" · ");
|
|
238
249
|
line.append(number);
|
|
239
250
|
row.seats.forEach((seat, i) => {
|
|
240
|
-
line.append(seat ? seatNode(seat) : el("span", "smEmpty"));
|
|
251
|
+
line.append(seat ? seatNode(seat, facts) : el("span", "smEmpty"));
|
|
241
252
|
if (aisles.has(i)) line.append(el("span", "smAisle"));
|
|
242
253
|
});
|
|
254
|
+
if (row.exit) line.append(el("span", "smTag exit", "Exit"));
|
|
255
|
+
if (row.bulkhead) line.append(el("span", "smTag", "Bulkhead"));
|
|
243
256
|
return line;
|
|
244
257
|
}
|
|
245
258
|
|
|
@@ -248,7 +261,7 @@ function render(root, card) {
|
|
|
248
261
|
const states = new Set([...seats.values()].map(({ seat }) => seat.state));
|
|
249
262
|
const keys = [
|
|
250
263
|
["mine", "Your seat", true],
|
|
251
|
-
["pick", "New
|
|
264
|
+
["pick", "New selection", true],
|
|
252
265
|
["free", "Free", states.has("free")],
|
|
253
266
|
["paid", "Paid", states.has("paid")],
|
|
254
267
|
["off", "Unavailable", states.has("unavailable")],
|
|
@@ -295,9 +308,9 @@ function render(root, card) {
|
|
|
295
308
|
const foot = el("div", "smFoot");
|
|
296
309
|
const mine = seats.get(card.current_seat)?.seat;
|
|
297
310
|
const chosen = pick && seats.get(pick)?.seat;
|
|
298
|
-
foot.append(el("span", null, chosen ? `New
|
|
311
|
+
foot.append(el("span", null, chosen ? `New selection: ${describe(chosen)}` : mine ? `Your seat: ${describe(mine)}` : ""));
|
|
299
312
|
if (chosen) {
|
|
300
|
-
const update = el("button", "btn", `Update seat
|
|
313
|
+
const update = el("button", "btn", `Update seat to ${chosen.id}`);
|
|
301
314
|
update.type = "button";
|
|
302
315
|
update.dataset.update = chosen.id;
|
|
303
316
|
foot.append(update);
|
|
@@ -318,7 +331,7 @@ function render(root, card) {
|
|
|
318
331
|
// Assigned, not added: a re-mount replaces the handler instead of stacking another.
|
|
319
332
|
root.onclick = (event) => {
|
|
320
333
|
if (busy) return;
|
|
321
|
-
const target = event.target.closest("button");
|
|
334
|
+
const target = event.target.closest("button, [data-seat]");
|
|
322
335
|
if (!target) return;
|
|
323
336
|
if (target.dataset.toggle) {
|
|
324
337
|
expanded = !expanded;
|
|
@@ -339,6 +352,17 @@ function render(root, card) {
|
|
|
339
352
|
draw();
|
|
340
353
|
};
|
|
341
354
|
|
|
355
|
+
// Enter and Space pick a seat, as they would on a native button.
|
|
356
|
+
root.onkeydown = (event) => {
|
|
357
|
+
if ((event.key === "Enter" || event.key === " ") && event.target.dataset?.seat) {
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
const id = event.target.dataset.seat;
|
|
360
|
+
event.target.click();
|
|
361
|
+
// draw() replaced the node; keep keyboard focus on the same seat.
|
|
362
|
+
root.querySelector(`[data-seat="${id}"]`)?.focus();
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
342
366
|
draw();
|
|
343
367
|
root.__ottoCard = { cancel: () => clearTimeout(timer) };
|
|
344
368
|
}
|