@ottotheagent/mcp-card-assets 0.15.0 → 0.16.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 +1 -1
- package/package.json +1 -1
- package/seat-map.css +1 -1
- package/seat-map.js +138 -47
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Renderers, logos and photo placeholders for the Otto flight, hotel, room and car
|
|
|
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. 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. Since 0.15.0 the seat map draws each lavatory in `card.lavs` as one labeled block over its seat columns.
|
|
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. Since 0.15.0 the seat map draws each lavatory in `card.lavs` as one labeled block over its seat columns. Since 0.16.0 it draws a whole party (`card.travelers`): a side panel lists each traveler's seat, a change shows as "23A → 24C" without sending anything, and **Update seats** sends every change in one message. A lone traveler keeps the one-seat layout.
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottotheagent/mcp-card-assets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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);}.smLav{box-sizing:border-box;flex-shrink:0;height:var(--sm-seat);display:grid;place-items:center;overflow:hidden;border:1px dashed var(--otto-card-border);border-radius:6px;background:var(--otto-strip-bg);color:var(--otto-text-secondary);font-size:var(--otto-text-sm);white-space:nowrap;}.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;}
|
|
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);}.smBody{display:flex;flex-wrap:wrap;align-items:flex-start;gap:var(--otto-space-5);}.smBody .smScroll{flex:0 1 auto;min-width:0;}.smSide{flex:0 0 168px;display:flex;flex-direction:column;gap:var(--otto-space-2);}.smSideHead{font-size:var(--otto-text-sm);color:var(--otto-text-secondary);}.smTraveler{box-sizing:border-box;padding:var(--otto-space-2) var(--otto-space-3);border:1px solid var(--otto-card-border);border-radius:8px;cursor:pointer;user-select:none;}.smTraveler:hover,.smTraveler.active{border-color:var(--otto-accent);}.smTraveler:focus-visible{outline:2px solid var(--otto-accent);outline-offset:1px;}.smTravelerName{font-size:var(--otto-text-md);color:var(--otto-text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.smTravelerSeat{display:flex;align-items:baseline;gap:var(--otto-space-2);font-size:var(--otto-text-base);font-weight:var(--otto-weight-semibold);font-variant-numeric:tabular-nums;}.smTravelerSeat .smFrom{color:var(--otto-text-secondary);font-weight:var(--otto-weight-medium);text-decoration:line-through;}.smTravelerSeat .smArrow{color:var(--otto-text-secondary);}.smTravelerSeat .smTo{color:var(--otto-accent);}.smTravelerNote{font-size:var(--otto-text-sm);color:var(--otto-text-secondary);}.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);}.smLav{box-sizing:border-box;flex-shrink:0;height:var(--sm-seat);display:grid;place-items:center;overflow:hidden;border:1px dashed var(--otto-card-border);border-radius:6px;background:var(--otto-strip-bg);color:var(--otto-text-secondary);font-size:var(--otto-text-sm);white-space:nowrap;}.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.mine.active{outline:2px solid var(--otto-text-primary);outline-offset:1px;}.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);}.smLav{box-sizing:border-box;flex-shrink:0;height:var(--sm-seat);display:grid;place-items:center;overflow:hidden;border:1px dashed var(--otto-card-border);border-radius:6px;background:var(--otto-strip-bg);color:var(--otto-text-secondary);font-size:var(--otto-text-sm);white-space:nowrap;}.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;}";
|
|
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);}.smBody{display:flex;flex-wrap:wrap;align-items:flex-start;gap:var(--otto-space-5);}.smBody .smScroll{flex:0 1 auto;min-width:0;}.smSide{flex:0 0 168px;display:flex;flex-direction:column;gap:var(--otto-space-2);}.smSideHead{font-size:var(--otto-text-sm);color:var(--otto-text-secondary);}.smTraveler{box-sizing:border-box;padding:var(--otto-space-2) var(--otto-space-3);border:1px solid var(--otto-card-border);border-radius:8px;cursor:pointer;user-select:none;}.smTraveler:hover,.smTraveler.active{border-color:var(--otto-accent);}.smTraveler:focus-visible{outline:2px solid var(--otto-accent);outline-offset:1px;}.smTravelerName{font-size:var(--otto-text-md);color:var(--otto-text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.smTravelerSeat{display:flex;align-items:baseline;gap:var(--otto-space-2);font-size:var(--otto-text-base);font-weight:var(--otto-weight-semibold);font-variant-numeric:tabular-nums;}.smTravelerSeat .smFrom{color:var(--otto-text-secondary);font-weight:var(--otto-weight-medium);text-decoration:line-through;}.smTravelerSeat .smArrow{color:var(--otto-text-secondary);}.smTravelerSeat .smTo{color:var(--otto-accent);}.smTravelerNote{font-size:var(--otto-text-sm);color:var(--otto-text-secondary);}.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);}.smLav{box-sizing:border-box;flex-shrink:0;height:var(--sm-seat);display:grid;place-items:center;overflow:hidden;border:1px dashed var(--otto-card-border);border-radius:6px;background:var(--otto-strip-bg);color:var(--otto-text-secondary);font-size:var(--otto-text-sm);white-space:nowrap;}.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.mine.active{outline:2px solid var(--otto-text-primary);outline-offset:1px;}.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.
|
|
@@ -171,16 +171,21 @@ function mount(root, card, spec) {
|
|
|
171
171
|
|
|
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
|
-
* and cabin draws the same way. Collapsed, it shows the
|
|
175
|
-
* side; Expand shows the whole cabin.
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
174
|
+
* and cabin draws the same way. Collapsed, it shows the travelers' rows and one row either
|
|
175
|
+
* side; Expand shows the whole cabin. A lavatory (card.lavs) draws as one labeled block over its
|
|
176
|
+
* seat columns; a lavatory row without seats becomes a row of its own.
|
|
177
|
+
*
|
|
178
|
+
* One traveler: clicking another seat marks it New selection and shows "Update seat to 15D",
|
|
179
|
+
* which sends "Update seat to 15D (SEA → JFK)" as the traveler's message.
|
|
180
|
+
* A party (2+ card.travelers): a side panel lists each traveler and seat. Click a party seat,
|
|
181
|
+
* then a new seat: nothing is sent, the panel shows "23A → 24C". "Update seats" sends every
|
|
182
|
+
* change at once: "Update seats (SEA → JFK): Ana 23A → 24C, Ben 23B → 24D". */
|
|
179
183
|
|
|
180
184
|
function render(root, card) {
|
|
181
185
|
injectCss("otto-seat-map-css");
|
|
182
186
|
if (root.__ottoCard) root.__ottoCard.cancel();
|
|
183
|
-
|
|
187
|
+
const travelers = (card && card.travelers) || [];
|
|
188
|
+
if (!card || !card.rows || !card.rows.length || !travelers.length) {
|
|
184
189
|
root.replaceChildren(el("p", "empty", "No seat map to show."));
|
|
185
190
|
return;
|
|
186
191
|
}
|
|
@@ -194,20 +199,29 @@ function render(root, card) {
|
|
|
194
199
|
rows.sort((a, b) => a.number - b.number);
|
|
195
200
|
const seats = new Map();
|
|
196
201
|
rows.forEach((row, index) => row.seats.forEach((seat) => seat && seats.set(seat.id, { seat, index })));
|
|
197
|
-
const home = seats.get(card.current_seat)?.index ?? 0;
|
|
198
202
|
const aisles = new Set(card.aisles_after || []);
|
|
203
|
+
|
|
204
|
+
const party = travelers.length > 1;
|
|
205
|
+
const owner = new Map(travelers.map((traveler, i) => [traveler.seat, i])); // seat id → traveler index
|
|
206
|
+
const homes = travelers.map((t) => seats.get(t.seat)?.index ?? 0);
|
|
207
|
+
const first = Math.min(...homes);
|
|
208
|
+
const last = Math.max(...homes);
|
|
209
|
+
|
|
199
210
|
let expanded = false;
|
|
200
|
-
let pick = null;
|
|
201
211
|
let busy = false;
|
|
202
212
|
let timer = null;
|
|
213
|
+
let pick = null; // one traveler: the new seat
|
|
214
|
+
let active = null; // a party: the traveler whose seat is being moved
|
|
215
|
+
const moves = new Map(); // a party: traveler index → new seat id
|
|
203
216
|
|
|
204
217
|
const letter = (id) => String(id).replace(/^\d+/, "");
|
|
205
218
|
const cost = (seat) => (seat.price ? formatExactMoney(seat.price, card.currency || "USD") : "free");
|
|
206
219
|
const describe = (seat) => [seat.id, seat.type, cost(seat)].filter(Boolean).join(" · ");
|
|
220
|
+
const movedTo = (id) => [...moves.entries()].find(([, to]) => to === id)?.[0];
|
|
207
221
|
|
|
208
222
|
function seatClass(seat) {
|
|
209
|
-
if (seat.id ===
|
|
210
|
-
if (seat.id === pick) return "pick";
|
|
223
|
+
if (owner.has(seat.id)) return "mine" + (party && active === owner.get(seat.id) ? " active" : "");
|
|
224
|
+
if (party ? movedTo(seat.id) !== undefined : seat.id === pick) return "pick";
|
|
211
225
|
return { free: "free", paid: "paid", unavailable: "off" }[seat.state] || "off";
|
|
212
226
|
}
|
|
213
227
|
|
|
@@ -230,11 +244,13 @@ function render(root, card) {
|
|
|
230
244
|
button.setAttribute("role", "button");
|
|
231
245
|
button.tabIndex = busy ? -1 : 0;
|
|
232
246
|
button.dataset.seat = seat.id;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
247
|
+
const who = party && owner.has(seat.id) ? travelers[owner.get(seat.id)].name : null;
|
|
248
|
+
button.title =
|
|
249
|
+
[who, describe(seat), seat.extra_legroom && "extra legroom", ...facts.map((f) => f.toLowerCase())]
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
.join(" · ") + (cls === "pick" ? " · new selection" : "");
|
|
236
252
|
button.setAttribute("aria-label", button.title);
|
|
237
|
-
button.setAttribute("aria-pressed", String(cls
|
|
253
|
+
button.setAttribute("aria-pressed", String(cls.startsWith("mine") || cls === "pick"));
|
|
238
254
|
return button;
|
|
239
255
|
}
|
|
240
256
|
|
|
@@ -290,7 +306,7 @@ function render(root, card) {
|
|
|
290
306
|
function legend() {
|
|
291
307
|
const states = new Set([...seats.values()].map(({ seat }) => seat.state));
|
|
292
308
|
const keys = [
|
|
293
|
-
["mine", "Your seat", true],
|
|
309
|
+
["mine", party ? "Your party" : "Your seat", true],
|
|
294
310
|
["pick", "New selection", true],
|
|
295
311
|
["free", "Free", states.has("free")],
|
|
296
312
|
["paid", "Paid", states.has("paid")],
|
|
@@ -313,15 +329,66 @@ function render(root, card) {
|
|
|
313
329
|
return box;
|
|
314
330
|
}
|
|
315
331
|
|
|
332
|
+
// A party's side panel: each traveler's seat, or "23A → 24C" with the new seat's details.
|
|
333
|
+
function sidePanel() {
|
|
334
|
+
const side = el("div", "smSide");
|
|
335
|
+
const hint =
|
|
336
|
+
active !== null ? `Pick a new seat for ${travelers[active].name}` : moves.size ? "Seat changes" : "Tap a seat to change it";
|
|
337
|
+
side.append(el("div", "smSideHead", hint));
|
|
338
|
+
travelers.forEach((traveler, i) => {
|
|
339
|
+
const to = moves.get(i);
|
|
340
|
+
const row = el("div", "smTraveler" + (active === i ? " active" : ""));
|
|
341
|
+
row.setAttribute("role", "button");
|
|
342
|
+
row.tabIndex = busy ? -1 : 0;
|
|
343
|
+
row.dataset.traveler = String(i);
|
|
344
|
+
const seatLine = el("div", "smTravelerSeat");
|
|
345
|
+
if (to) {
|
|
346
|
+
seatLine.append(el("span", "smFrom", traveler.seat), el("span", "smArrow", "→"), el("span", "smTo", to));
|
|
347
|
+
} else {
|
|
348
|
+
seatLine.append(el("span", null, traveler.seat));
|
|
349
|
+
}
|
|
350
|
+
row.append(el("div", "smTravelerName", traveler.name), seatLine);
|
|
351
|
+
const target = to && seats.get(to)?.seat;
|
|
352
|
+
if (target) row.append(el("div", "smTravelerNote", [target.type, cost(target)].filter(Boolean).join(" · ")));
|
|
353
|
+
side.append(row);
|
|
354
|
+
});
|
|
355
|
+
return side;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function footer() {
|
|
359
|
+
const foot = el("div", "smFoot");
|
|
360
|
+
if (party) {
|
|
361
|
+
const count = moves.size;
|
|
362
|
+
foot.append(el("span", null, count ? `${count} seat change${count === 1 ? "" : "s"}` : ""));
|
|
363
|
+
if (count) {
|
|
364
|
+
const update = el("button", "btn", "Update seats");
|
|
365
|
+
update.type = "button";
|
|
366
|
+
update.dataset.update = "party";
|
|
367
|
+
foot.append(update);
|
|
368
|
+
}
|
|
369
|
+
return foot;
|
|
370
|
+
}
|
|
371
|
+
const mine = seats.get(travelers[0].seat)?.seat;
|
|
372
|
+
const chosen = pick && seats.get(pick)?.seat;
|
|
373
|
+
foot.append(el("span", null, chosen ? `New selection: ${describe(chosen)}` : mine ? `Your seat: ${describe(mine)}` : ""));
|
|
374
|
+
if (chosen) {
|
|
375
|
+
const update = el("button", "btn", `Update seat to ${chosen.id}`);
|
|
376
|
+
update.type = "button";
|
|
377
|
+
update.dataset.update = chosen.id;
|
|
378
|
+
foot.append(update);
|
|
379
|
+
}
|
|
380
|
+
return foot;
|
|
381
|
+
}
|
|
382
|
+
|
|
316
383
|
function draw() {
|
|
317
384
|
const scroll = root.querySelector(".smScroll")?.scrollLeft || 0;
|
|
318
|
-
const box = el("div", `sm ${card.size || "standard"}${busy ? " busy" : ""}`);
|
|
385
|
+
const box = el("div", `sm ${card.size || "standard"}${party ? " party" : ""}${busy ? " busy" : ""}`);
|
|
319
386
|
|
|
320
387
|
const head = el("div", "smHead");
|
|
321
388
|
const text = el("div");
|
|
322
389
|
text.append(el("div", "title", card.title), el("div", "subtitle", card.subtitle));
|
|
323
390
|
head.append(text);
|
|
324
|
-
if (rows.length > 3) {
|
|
391
|
+
if (rows.length > last - first + 3) {
|
|
325
392
|
const toggle = el("button", "btn smToggle", expanded ? "Collapse" : "Expand");
|
|
326
393
|
toggle.type = "button";
|
|
327
394
|
toggle.dataset.toggle = "1";
|
|
@@ -329,24 +396,16 @@ function render(root, card) {
|
|
|
329
396
|
head.append(toggle);
|
|
330
397
|
}
|
|
331
398
|
|
|
332
|
-
const visible = expanded ? rows : rows.slice(Math.max(0,
|
|
399
|
+
const visible = expanded ? rows : rows.slice(Math.max(0, first - 1), last + 2);
|
|
333
400
|
const grid = el("div", "smGrid");
|
|
334
401
|
grid.append(lettersRow(), ...visible.map(seatRow));
|
|
335
402
|
const scroller = el("div", "smScroll");
|
|
336
403
|
scroller.append(grid);
|
|
404
|
+
const body = el("div", "smBody");
|
|
405
|
+
body.append(scroller);
|
|
406
|
+
if (party) body.append(sidePanel());
|
|
337
407
|
|
|
338
|
-
|
|
339
|
-
const mine = seats.get(card.current_seat)?.seat;
|
|
340
|
-
const chosen = pick && seats.get(pick)?.seat;
|
|
341
|
-
foot.append(el("span", null, chosen ? `New selection: ${describe(chosen)}` : mine ? `Your seat: ${describe(mine)}` : ""));
|
|
342
|
-
if (chosen) {
|
|
343
|
-
const update = el("button", "btn", `Update seat to ${chosen.id}`);
|
|
344
|
-
update.type = "button";
|
|
345
|
-
update.dataset.update = chosen.id;
|
|
346
|
-
foot.append(update);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
box.append(head, scroller, legend(), foot);
|
|
408
|
+
box.append(head, body, legend(), footer());
|
|
350
409
|
root.replaceChildren(box);
|
|
351
410
|
scroller.scrollLeft = scroll;
|
|
352
411
|
root.querySelectorAll("button").forEach((b) => (b.disabled = busy));
|
|
@@ -358,38 +417,70 @@ function render(root, card) {
|
|
|
358
417
|
draw();
|
|
359
418
|
}
|
|
360
419
|
|
|
420
|
+
function send(message) {
|
|
421
|
+
busy = true;
|
|
422
|
+
timer = setTimeout(unlock, BUSY_MS);
|
|
423
|
+
try {
|
|
424
|
+
const sent = window.sendPrompt(message);
|
|
425
|
+
if (sent && typeof sent.catch === "function") sent.catch(unlock);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
unlock();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// A party: a party seat (or its row in the panel) picks who moves; another seat is the new
|
|
432
|
+
// seat; clicking a pending new seat again cancels that change.
|
|
433
|
+
function partyClick(id) {
|
|
434
|
+
if (owner.has(id)) {
|
|
435
|
+
const i = owner.get(id);
|
|
436
|
+
active = active === i ? null : i;
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const cancelled = movedTo(id);
|
|
440
|
+
if (cancelled !== undefined) {
|
|
441
|
+
moves.delete(cancelled);
|
|
442
|
+
active = null;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (active === null) return;
|
|
446
|
+
moves.set(active, id);
|
|
447
|
+
active = null;
|
|
448
|
+
}
|
|
449
|
+
|
|
361
450
|
// Assigned, not added: a re-mount replaces the handler instead of stacking another.
|
|
362
451
|
root.onclick = (event) => {
|
|
363
452
|
if (busy) return;
|
|
364
|
-
const target = event.target.closest("button, [data-seat]");
|
|
453
|
+
const target = event.target.closest("button, [data-seat], [data-traveler]");
|
|
365
454
|
if (!target) return;
|
|
366
455
|
if (target.dataset.toggle) {
|
|
367
456
|
expanded = !expanded;
|
|
457
|
+
} else if (target.dataset.traveler) {
|
|
458
|
+
const i = Number(target.dataset.traveler);
|
|
459
|
+
active = active === i ? null : i;
|
|
368
460
|
} else if (target.dataset.seat) {
|
|
369
461
|
const id = target.dataset.seat;
|
|
370
|
-
|
|
462
|
+
if (party) partyClick(id);
|
|
463
|
+
else pick = id === travelers[0].seat || id === pick ? null : id;
|
|
464
|
+
} else if (target.dataset.update === "party") {
|
|
465
|
+
const changes = travelers
|
|
466
|
+
.map((traveler, i) => moves.has(i) && `${traveler.name} ${traveler.seat} → ${moves.get(i)}`)
|
|
467
|
+
.filter(Boolean);
|
|
468
|
+
send(`Update seats (${card.route}): ${changes.join(", ")}`);
|
|
371
469
|
} else if (target.dataset.update) {
|
|
372
|
-
|
|
373
|
-
timer = setTimeout(unlock, BUSY_MS);
|
|
374
|
-
try {
|
|
375
|
-
const sent = window.sendPrompt(`Update seat to ${target.dataset.update} (${card.route})`);
|
|
376
|
-
if (sent && typeof sent.catch === "function") sent.catch(unlock);
|
|
377
|
-
} catch (error) {
|
|
378
|
-
unlock();
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
470
|
+
send(`Update seat to ${target.dataset.update} (${card.route})`);
|
|
381
471
|
}
|
|
382
472
|
draw();
|
|
383
473
|
};
|
|
384
474
|
|
|
385
|
-
// Enter and Space
|
|
475
|
+
// Enter and Space act as a click on a seat or a traveler, as they would on a native button.
|
|
386
476
|
root.onkeydown = (event) => {
|
|
387
|
-
|
|
477
|
+
const data = event.target.dataset || {};
|
|
478
|
+
if ((event.key === "Enter" || event.key === " ") && (data.seat || data.traveler)) {
|
|
388
479
|
event.preventDefault();
|
|
389
|
-
const
|
|
480
|
+
const selector = data.seat ? `[data-seat="${data.seat}"]` : `[data-traveler="${data.traveler}"]`;
|
|
390
481
|
event.target.click();
|
|
391
|
-
// draw() replaced the node; keep keyboard focus on the same
|
|
392
|
-
root.querySelector(
|
|
482
|
+
// draw() replaced the node; keep keyboard focus on the same one.
|
|
483
|
+
root.querySelector(selector)?.focus();
|
|
393
484
|
}
|
|
394
485
|
};
|
|
395
486
|
|