mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 00:01:03 +00:00
fc648bc7e4
- native-shell/amount.ts: the no-break spaces in the price pattern were written out as characters; \s already matches them. - widgets.ts: the Live Activity's lead time stays inside the module. - i18n: the count strings of the new plan tiles replace the removed ones on NOT_PLURAL, and the address placeholders of the non-Latin locales are their own instead of English copies. - mobile.css: the side safe-area margins of the floating bars use margin-inline, swapped for right to left, instead of physical sides.
852 lines
39 KiB
CSS
852 lines
39 KiB
CSS
/* ── Mobile rewrite design tokens ─────────────────────────────────────────────
|
|
* Scoped to .m-root (the MobileShell wrapper) so the mobile design system
|
|
* never leaks into desktop chrome. Values are the mobile design ground truth;
|
|
* dark values follow the app-wide `.dark` class strategy.
|
|
*/
|
|
.m-root {
|
|
--m-ink: #101013;
|
|
--m-muted: #68686F;
|
|
--m-faint: #9A9AA1;
|
|
--m-bg: #F3F2EF;
|
|
--m-scr: radial-gradient(120% 80% at 50% -10%, #FBFAF7 0%, #F1EFEA 55%, #E9E7E1 100%);
|
|
--m-glass: rgba(255,255,255,.6);
|
|
/* The dock's glass: denser than --m-glass, so a photo scrolling under it
|
|
* neither turns it muddy nor washes out its icons. */
|
|
--m-dock: rgba(255,255,255,.8);
|
|
--m-gbr: rgba(255,255,255,.75);
|
|
--m-card: rgba(255,255,255,.55);
|
|
--m-cbr: rgba(255,255,255,.75);
|
|
--m-inner: rgba(255,255,255,.68);
|
|
--m-inbr: rgba(255,255,255,.9);
|
|
--m-act: #101013;
|
|
--m-actfg: #fff;
|
|
--m-dim: rgba(16,16,19,.4);
|
|
/* A picture shown full screen (MImageViewer): dark in either scheme, as a photo viewer is. */
|
|
--m-viewer-bg: rgba(0,0,0,.92);
|
|
--m-viewer-ink: #fff;
|
|
--m-viewer-btn: rgba(255,255,255,.15);
|
|
--m-sheet: rgba(250,250,248,.94);
|
|
--m-sheetop: #FAFAF8;
|
|
--m-shbr: rgba(255,255,255,.95);
|
|
--m-ic: rgba(16,16,19,.04);
|
|
--m-avbr: rgba(255,255,255,.9);
|
|
--m-rowbr: rgba(16,16,19,.045);
|
|
--m-conn: #9A9AA1;
|
|
--m-mapb: #E8E6E1;
|
|
--m-mapr: #FBFAF8;
|
|
--m-mapw: #DFE2E4;
|
|
--m-mkr: #101013;
|
|
--m-mkfg: #fff;
|
|
--m-trackoff: rgba(16,16,19,.14);
|
|
/* The edge of an empty checkbox: at least 3:1 against a row, so an open item
|
|
* shows its box rather than reading as plain text. */
|
|
--m-checkbr: rgba(16,16,19,.55);
|
|
|
|
/* Strength of a dayTintProvider plugin's colour on an inactive day chip. A notch
|
|
* above the desktop badge: the chip has no neutral base under it, so a softer tint
|
|
* reads as a rendering artifact rather than a deliberate colour. */
|
|
--day-tint-chip: 20%;
|
|
|
|
/* Toggle knob is white on both themes (off-track is translucent enough). */
|
|
--m-knob: #fff;
|
|
|
|
/* Status color canon — theme independent. */
|
|
--m-st-confirmed: #2FA37A;
|
|
--m-st-pending: #E8A13A;
|
|
--m-st-info: #4A7DDB;
|
|
--m-st-danger: #D6273B;
|
|
--m-st-neutral: #9A9AA1;
|
|
|
|
/* Chrome metrics. --bottom-nav-h stays the single source of truth for
|
|
* content clearance (same value the legacy shell uses). The demo's fixed
|
|
* 52px/26px offsets stand in for the phone status bar / home indicator, so
|
|
* real devices derive them from the safe-area insets. */
|
|
--bottom-nav-h: calc(84px + env(safe-area-inset-bottom, 0px));
|
|
/* The safe area the iOS app measures (TrekBridgeViewController
|
|
* publishSafeArea) is a floor under env(): where a release lays the web view
|
|
* out below the status bar, WebKit reports 0 while a camera in a corner still
|
|
* reaches into the page. In a browser the variables are unset: env() alone. */
|
|
--m-safe-t: max(env(safe-area-inset-top, 0px), var(--trek-native-safe-top, 0px));
|
|
--m-safe-l: max(env(safe-area-inset-left, 0px), var(--trek-native-safe-left, 0px));
|
|
--m-safe-r: max(env(safe-area-inset-right, 0px), var(--trek-native-safe-right, 0px));
|
|
--m-safe-top: calc(var(--m-safe-t) + 12px);
|
|
/* The navigation rail's share of the leading edge (wide windows only), what
|
|
* every full-width fixed bar and the page column start after. */
|
|
--m-rail-w: 0px;
|
|
}
|
|
|
|
/* Wide size class (hooks/sizeClass.ts, written to the shell's root as
|
|
* data-size): the rail on the leading edge takes the dock's place, so nothing
|
|
* docks at the bottom any more and content only clears the home indicator.
|
|
* Asked from <html>, so a sheet's own .m-root (portalled, perhaps to body)
|
|
* reads the same values as the shell. */
|
|
html:has(.m-shell[data-size='wide'], .m-shell[data-size='split']) .m-root {
|
|
--bottom-nav-h: env(safe-area-inset-bottom, 0px);
|
|
--m-rail-w: calc(var(--m-safe-l) + 100px);
|
|
}
|
|
html[dir='rtl']:has(.m-shell[data-size='wide'], .m-shell[data-size='split']) .m-root {
|
|
--m-rail-w: calc(var(--m-safe-r) + 100px);
|
|
}
|
|
|
|
.dark .m-root {
|
|
--m-ink: #F5F5F7;
|
|
--m-muted: #9C9CA3;
|
|
--m-faint: #6E6E76;
|
|
--m-bg: #0A0A0C;
|
|
--m-scr: radial-gradient(120% 80% at 50% -10%, #17171C 0%, #0C0C0F 55%, #08080A 100%);
|
|
--m-glass: rgba(26,26,31,.6);
|
|
--m-dock: rgba(26,26,31,.82);
|
|
--m-gbr: rgba(255,255,255,.12);
|
|
--m-card: rgba(24,24,29,.55);
|
|
--m-cbr: rgba(255,255,255,.1);
|
|
--m-inner: rgba(255,255,255,.07);
|
|
--m-inbr: rgba(255,255,255,.1);
|
|
--m-act: #F5F5F7;
|
|
--m-actfg: #101013;
|
|
--m-dim: rgba(0,0,0,.55);
|
|
--m-sheet: rgba(22,22,27,.94);
|
|
--m-sheetop: #191920;
|
|
--m-shbr: rgba(255,255,255,.14);
|
|
--m-ic: rgba(255,255,255,.09);
|
|
--m-avbr: rgba(255,255,255,.14);
|
|
--m-rowbr: rgba(255,255,255,.07);
|
|
--m-conn: #6E6E76;
|
|
--m-mapb: #121215;
|
|
--m-mapr: #1E1F24;
|
|
--m-mapw: #0D1013;
|
|
--m-mkr: #F5F5F7;
|
|
--m-mkfg: #101013;
|
|
--m-trackoff: rgba(255,255,255,.14);
|
|
--m-checkbr: rgba(255,255,255,.4);
|
|
--day-tint-chip: 28%;
|
|
}
|
|
|
|
/* Geist "subtext" tier weight — single knob. The design's Geist labels/numbers
|
|
* read heavy at their declared bold; render the whole tier at Medium (500) for
|
|
* the fine, low-key look. Higher specificity than Tailwind's font-* utilities,
|
|
* so it caps font-medium / font-semibold / font-bold / font-extrabold on any
|
|
* .font-geist span. Retune here if a weight feels too light or heavy. */
|
|
.m-root .font-geist { font-weight: 500; }
|
|
|
|
/* ── Keyframes ──────────────────────────────────────────────────────────── */
|
|
|
|
/* The floating card pops in place, the way a native alert does. It is opaque
|
|
halfway through the pop, so the page behind never reads through its fields. */
|
|
@keyframes m-sheet-in {
|
|
from { opacity: 0; transform: translateY(10px) scale(.96); }
|
|
45% { opacity: 1; }
|
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
}
|
|
@keyframes m-sheet-out {
|
|
from { opacity: 1; transform: translateY(0) scale(1); }
|
|
to { opacity: 0; transform: translateY(6px) scale(.97); }
|
|
}
|
|
/* The docked sheet rises from below the screen edge and sinks back there: it
|
|
sits above the nav, so it travels its own height plus that gap. */
|
|
@keyframes m-sheet-up {
|
|
from { transform: translateY(calc(100% + var(--bottom-nav-h, 84px) + 24px)); }
|
|
to { transform: translateY(0); }
|
|
}
|
|
@keyframes m-sheet-down {
|
|
from { transform: translateY(0); }
|
|
to { transform: translateY(calc(100% + var(--bottom-nav-h, 84px) + 24px)); }
|
|
}
|
|
@keyframes m-drawer-in {
|
|
from { transform: translateX(-100%); }
|
|
to { transform: translateX(0); }
|
|
}
|
|
@keyframes m-drawer-out {
|
|
from { transform: translateX(0); }
|
|
to { transform: translateX(-100%); }
|
|
}
|
|
@keyframes m-fade-in {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
@keyframes m-fade-out {
|
|
from { opacity: 1; }
|
|
to { opacity: 0; }
|
|
}
|
|
@keyframes m-pop-in {
|
|
from { opacity: 0; transform: translateY(-6px) scale(.97); }
|
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
}
|
|
@keyframes m-toast-in {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
@keyframes m-toast-out {
|
|
from { opacity: 1; transform: translateY(0); }
|
|
to { opacity: 0; transform: translateY(6px); }
|
|
}
|
|
/* A panel at the trailing edge (the assistant on a wide window) slides in from
|
|
that edge, the way Slide Over does. */
|
|
@keyframes m-panel-in {
|
|
from { opacity: 0; transform: translateX(28px); }
|
|
to { opacity: 1; transform: translateX(0); }
|
|
}
|
|
@keyframes m-panel-in-rtl {
|
|
from { opacity: 0; transform: translateX(-28px); }
|
|
to { opacity: 1; transform: translateX(0); }
|
|
}
|
|
|
|
/* A form sheet on a wide window sits mid-screen, so it rises from the bottom
|
|
edge of the window rather than from just below its own resting place. */
|
|
@keyframes m-sheet-up-wide {
|
|
from { transform: translateY(calc(50dvh + 50%)); }
|
|
to { transform: translateY(0); }
|
|
}
|
|
@keyframes m-sheet-down-wide {
|
|
from { transform: translateY(0); }
|
|
to { transform: translateY(calc(50dvh + 50%)); }
|
|
}
|
|
.m-sheet-in { animation: m-sheet-in 260ms var(--ease-out-quint) both; }
|
|
.m-sheet-out { animation: m-sheet-out 220ms var(--ease-drawer) both; }
|
|
.m-sheet-up { animation: m-sheet-up 420ms var(--ease-drawer) both; }
|
|
.m-sheet-down { animation: m-sheet-down 300ms var(--ease-drawer) both; }
|
|
.m-drawer-in { animation: m-drawer-in 280ms var(--ease-drawer) both; }
|
|
.m-drawer-out { animation: m-drawer-out 280ms var(--ease-drawer) both; }
|
|
.m-fade-in { animation: m-fade-in 280ms var(--ease-drawer) both; }
|
|
.m-fade-out { animation: m-fade-out 280ms var(--ease-drawer) both; }
|
|
.m-pop-in { animation: m-pop-in 220ms var(--ease-out-quint) both; }
|
|
.m-toast-in { animation: m-toast-in 260ms var(--ease-out-quint) both; }
|
|
.m-toast-out { animation: m-toast-out 220ms ease both; }
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
html:has(.m-shell[data-size='wide'], .m-shell[data-size='split']) .m-sheet-up { animation-name: m-sheet-up-wide; }
|
|
html:has(.m-shell[data-size='wide'], .m-shell[data-size='split']) .m-sheet-down { animation-name: m-sheet-down-wide; }
|
|
}
|
|
html:has(.m-shell[data-size='wide'], .m-shell[data-size='split']) .m-panel-in { animation: m-panel-in 320ms var(--ease-out-quint) both; }
|
|
html[dir='rtl']:has(.m-shell[data-size='wide'], .m-shell[data-size='split']) .m-panel-in { animation-name: m-panel-in-rtl; }
|
|
@media (prefers-reduced-motion: reduce) {
|
|
html:has(.m-shell[data-size='wide'], .m-shell[data-size='split']) .m-panel-in { animation: m-fade-in 120ms ease-out both; }
|
|
}
|
|
|
|
/* Reduced motion: gentler, not zero (same rule the desktop keyframes follow). */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.m-sheet-in, .m-sheet-up, .m-drawer-in, .m-pop-in, .m-toast-in {
|
|
animation: m-fade-in 120ms ease-out both;
|
|
}
|
|
.m-sheet-out, .m-sheet-down, .m-drawer-out, .m-toast-out {
|
|
animation: m-fade-out 120ms ease-out both;
|
|
}
|
|
}
|
|
|
|
/* ── TREK mascot (empty-state) — logo-as-body character, per-scene props ───── */
|
|
@keyframes trek-idle { 0%, 100% { transform: translateY(0) rotate(-1.1deg); } 50% { transform: translateY(-2px) rotate(1.1deg); } }
|
|
@keyframes trek-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.012, 1.024); } }
|
|
@keyframes trek-shadow { 0%, 100% { transform: scaleX(1); opacity: .15; } 50% { transform: scaleX(1.05); opacity: .17; } }
|
|
/* body hop, occasional: long rest, then anticipation squash → airborne → land → settle */
|
|
@keyframes trek-hop {
|
|
0%, 74%, 100% { transform: translateY(0) scale(1, 1); }
|
|
79% { transform: translateY(1px) scale(1.05, .92); }
|
|
87% { transform: translateY(-10px) scale(.95, 1.07); }
|
|
94% { transform: translateY(0) scale(1.07, .93); }
|
|
98% { transform: translateY(0) scale(.99, 1.01); }
|
|
}
|
|
/* body nod — a pleased look left-and-right, e.g. cheering the poll results */
|
|
@keyframes trek-nod {
|
|
0%, 100% { transform: translateY(0) rotate(0); }
|
|
25% { transform: translateY(-2px) rotate(-4deg); }
|
|
50% { transform: translateY(0) rotate(0); }
|
|
75% { transform: translateY(-2px) rotate(4deg); }
|
|
}
|
|
/* slow blink: eyes squeeze shut for a couple of frames each cycle */
|
|
@keyframes trek-blink { 0%, 90%, 100% { transform: scaleY(1); } 94%, 97% { transform: scaleY(.12); } }
|
|
/* entrance / poke-to-react pop-in (plays on mount and on each remount/tap) */
|
|
@keyframes trek-in {
|
|
0% { opacity: 0; transform: translateY(7px) scale(.85); }
|
|
60% { opacity: 1; }
|
|
100% { opacity: 1; transform: translateY(0) scale(1); }
|
|
}
|
|
/* pupils glancing around — centred most of the time, occasional look down/side */
|
|
@keyframes trek-look {
|
|
0%, 34%, 100% { transform: translate(0, 0); }
|
|
42%, 52% { transform: translate(0, .9px); }
|
|
64%, 72% { transform: translate(.9px, .1px); }
|
|
84%, 90% { transform: translate(-.8px, .2px); }
|
|
}
|
|
/* wheels rolling in place */
|
|
@keyframes trek-roll { to { transform: rotate(360deg); } }
|
|
/* skateboard ollie — body and board pop off the ground together, now and then */
|
|
@keyframes trek-sk-body {
|
|
0%, 68%, 100% { transform: translateY(0) scale(1, 1); }
|
|
73% { transform: translateY(1.5px) scale(1.04, .95); }
|
|
84% { transform: translateY(-9px) scale(.97, 1.05); }
|
|
92% { transform: translateY(0) scale(1.05, .95); }
|
|
96% { transform: translateY(0) scale(1, 1); }
|
|
}
|
|
@keyframes trek-sk-board {
|
|
0%, 68%, 100% { transform: translateY(0) rotate(0); }
|
|
73% { transform: translateY(0) rotate(-6deg); }
|
|
84% { transform: translateY(-8px) rotate(7deg); }
|
|
90% { transform: translateY(-3px) rotate(-3deg); }
|
|
95% { transform: translateY(0) rotate(0); }
|
|
}
|
|
/* guide pennant fluttering — pivots at the pole */
|
|
@keyframes trek-flag {
|
|
0%, 100% { transform: skewY(0) scaleX(1); }
|
|
30% { transform: skewY(-7deg) scaleX(.96); }
|
|
60% { transform: skewY(6deg) scaleX(1.03); }
|
|
}
|
|
/* poll bars staying up, gently rising and settling like live tallies */
|
|
@keyframes trek-bar { 0%, 100% { transform: scaleY(.78); } 50% { transform: scaleY(1); } }
|
|
@keyframes trek-typing { 0%, 70%, 100% { transform: scale(.5); opacity: .4; } 35% { transform: scale(1); opacity: 1; } }
|
|
/* card / note rocking gently */
|
|
@keyframes trek-ticket { 0%, 100% { transform: rotate(-2.5deg); } 50% { transform: rotate(2.5deg); } }
|
|
/* a line being written: draws in from the left, holds, then a fresh pass */
|
|
@keyframes trek-write {
|
|
0% { transform: scaleX(0); }
|
|
30%, 82% { transform: scaleX(1); }
|
|
92%, 100% { transform: scaleX(0); }
|
|
}
|
|
/* pencil scribbling as the lines appear */
|
|
@keyframes trek-pencil {
|
|
0%, 100% { transform: translate(0, 0) rotate(0); }
|
|
20% { transform: translate(-1.5px, 1.5px) rotate(-4deg); }
|
|
45% { transform: translate(1.5px, -.5px) rotate(3deg); }
|
|
70% { transform: translate(-1px, 1px) rotate(-2deg); }
|
|
}
|
|
/* suitcase rocking on its wheels, restless to get going */
|
|
@keyframes trek-suitcase { 0%, 100% { transform: rotate(-2.5deg); } 50% { transform: rotate(2.5deg); } }
|
|
/* suitcase handle telescoping up and down */
|
|
@keyframes trek-handle { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }
|
|
/* collections map pin bobbing */
|
|
@keyframes trek-pin { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
|
|
/* Chain: the pair sways, and the near link slides out along its own axis and
|
|
clicks back in. Moving only the near one keeps the joint readable at 88px. */
|
|
@keyframes trek-chain { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(-6deg); } }
|
|
@keyframes trek-link { 0%, 62%, 100% { transform: translateX(0); } 30% { transform: translateX(2.6px); } }
|
|
/* atlas globe meridian drifting to fake a slow spin */
|
|
@keyframes trek-globe { 0%, 100% { transform: translateX(-2px); } 50% { transform: translateX(2px); } }
|
|
/* costs top coin bouncing on */
|
|
@keyframes trek-coin { 0%, 100% { transform: translateY(0); } 30% { transform: translateY(-5px); } 55% { transform: translateY(0); } 70% { transform: translateY(-2px); } 84% { transform: translateY(0); } }
|
|
/* journey page turning over the spine */
|
|
@keyframes trek-page { 0%, 55%, 100% { transform: scaleX(1); } 78% { transform: scaleX(-.9); } }
|
|
/* dashboard paper plane bobbing in flight */
|
|
@keyframes trek-plane { 0%, 100% { transform: translate(0, 0) rotate(0); } 25% { transform: translate(-1px, -2px) rotate(-4deg); } 50% { transform: translate(0, 0) rotate(0); } 75% { transform: translate(1px, 2px) rotate(4deg); } }
|
|
/* notifications Zzz drifting up and fading */
|
|
@keyframes trek-z { 0% { opacity: 0; transform: translate(0, 4px) scale(.7); } 30% { opacity: .9; } 100% { opacity: 0; transform: translate(3px, -8px) scale(1.1); } }
|
|
/* search magnifier scanning */
|
|
@keyframes trek-magnifier { 0%, 100% { transform: translateX(-3px) rotate(-3deg); } 50% { transform: translateX(3px) rotate(3deg); } }
|
|
/* to-do checkmark ticking on, holding, then resetting */
|
|
@keyframes trek-check { 0%, 8% { transform: scale(0); } 22% { transform: scale(1.2); } 32%, 88% { transform: scale(1); } 96%, 100% { transform: scale(0); } }
|
|
/* tours: a walking step, the pole swinging forward and planting, the pack riding along */
|
|
@keyframes trek-walk { 0%, 50%, 100% { transform: translateY(0); } 25%, 75% { transform: translateY(-1.6px); } }
|
|
@keyframes trek-pole { 0%, 100% { transform: rotate(-9deg); } 45% { transform: rotate(7deg); } 55% { transform: rotate(7deg); } }
|
|
@keyframes trek-pack { 0%, 50%, 100% { transform: rotate(0); } 25%, 75% { transform: rotate(-3deg); } }
|
|
@keyframes trek-trail { to { stroke-dashoffset: 8; } }
|
|
|
|
.trek { animation: trek-in 480ms cubic-bezier(.34, 1.4, .64, 1) both; transform-origin: 50% 100%; }
|
|
.trek-root { animation: trek-idle 3.4s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-body { animation: trek-breathe 3.8s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-shadow { animation: trek-shadow 3.4s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 50%; opacity: .15; filter: blur(1px); }
|
|
/* eyes read as cutouts in the surface behind the mascot, so they stay visible on the monochrome body */
|
|
.trek-eye { fill: var(--m-bg); }
|
|
.trek-eyes { animation: trek-blink 5.2s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-pupils { animation: trek-look 7s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
/* body bounce — per scene */
|
|
.trek-bounce { transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-bounce--idle { animation: trek-hop 4.6s ease-in-out infinite; }
|
|
.trek-bounce--polls { animation: trek-nod 3s ease-in-out infinite; }
|
|
.trek-bounce--transport { animation: trek-sk-body 3.6s ease-in-out infinite; }
|
|
.trek-bounce--tours { animation: trek-walk 1.6s ease-in-out infinite; }
|
|
/* per-scene props */
|
|
.trek-board { animation: trek-sk-board 3.6s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 65%; }
|
|
.trek-wheel { animation: trek-roll 1.1s linear infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-flag { animation: trek-flag 1.8s ease-in-out infinite; transform-box: fill-box; transform-origin: 0% 50%; }
|
|
.trek-ticket { animation: trek-ticket 3.6s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-note { animation: trek-ticket 3.9s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-pencil { animation: trek-pencil 1.15s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-write { animation: trek-write 3.8s ease-in-out infinite; transform-box: fill-box; transform-origin: 0% 50%; }
|
|
.trek-bar { animation: trek-bar 2.6s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-dot { animation: trek-typing 1.4s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 50%; }
|
|
.trek-suitcase { animation: trek-suitcase 1.5s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-handle { animation: trek-handle 1.5s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-pin { animation: trek-pin 2.2s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-chain { animation: trek-chain 3s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-link { animation: trek-link 2.4s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-globe { animation: trek-globe 3s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-coin { animation: trek-coin 2s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 100%; }
|
|
.trek-page { animation: trek-page 3.4s ease-in-out infinite; transform-box: fill-box; transform-origin: 0% 50%; }
|
|
.trek-plane { animation: trek-plane 3s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-magnifier { animation: trek-magnifier 2.4s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-z { animation: trek-z 2.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-check { animation: trek-check 3s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
|
.trek-pole { animation: trek-pole 1.6s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 0%; }
|
|
.trek-pack { animation: trek-pack 1.6s ease-in-out infinite; transform-box: fill-box; transform-origin: 90% 10%; }
|
|
.trek-trail { animation: trek-trail .8s linear infinite; }
|
|
/* staggered starts for written lines / poll bars / Zzz */
|
|
.trek-d2, .trek-z2 { animation-delay: .5s; }
|
|
.trek-d3, .trek-z3 { animation-delay: 1s; }
|
|
|
|
/* Mobile range slider — the --m-* twin of the desktop .trek-range. */
|
|
.m-range {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 100%;
|
|
height: 5px;
|
|
border-radius: 999px;
|
|
background: linear-gradient(to right, var(--m-act) var(--fill, 50%), var(--m-trackoff) var(--fill, 50%));
|
|
outline: none;
|
|
cursor: pointer;
|
|
}
|
|
.m-range::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
background: var(--m-knob);
|
|
border: 1px solid var(--m-rowbr);
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
|
|
transition: transform .12s ease;
|
|
}
|
|
.m-range::-webkit-slider-thumb:hover { transform: scale(1.1); }
|
|
.m-range::-webkit-slider-thumb:active { transform: scale(1.18); }
|
|
.m-range::-moz-range-thumb {
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
background: var(--m-knob);
|
|
border: 1px solid var(--m-rowbr);
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
|
|
}
|
|
.m-range::-moz-range-track { height: 5px; border-radius: 999px; background: transparent; }
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.trek, .trek-root, .trek-body, .trek-shadow, .trek-eyes, .trek-pupils,
|
|
.trek-bounce--idle, .trek-bounce--polls, .trek-bounce--transport, .trek-bounce--tours, .trek-pole, .trek-pack, .trek-trail,
|
|
.trek-board, .trek-wheel, .trek-flag, .trek-ticket, .trek-note, .trek-pencil, .trek-write, .trek-bar,
|
|
.trek-suitcase, .trek-handle, .trek-pin, .trek-globe, .trek-coin, .trek-page, .trek-plane,
|
|
.trek-magnifier, .trek-z, .trek-check, .trek-chain, .trek-link { animation: none; }
|
|
/* Zzz would sit invisible at opacity 0 without their animation — pin them visible */
|
|
.trek-z { opacity: 1; }
|
|
}
|
|
|
|
/* The phone map carries no visible credit.
|
|
*
|
|
* Both engines park a credit at the bottom of the map, and on a phone the map is the
|
|
* whole screen, so whatever is put there sits in the middle of the picture. It has been
|
|
* a band across the bottom (#2022) and then a round (i) beside the locate button, and
|
|
* neither reads as part of this shell: a lone control under the locate button is the
|
|
* first thing the eye lands on over a full-bleed map.
|
|
*
|
|
* So on the phone it is off. The desktop map is untouched and still shows the full
|
|
* credit with its links, which is where this belongs; `.m-root` is the phone shell only.
|
|
* Both engines and both corners, because a phone meets either depending on the screen,
|
|
* and the GL containers hold nothing else — MapViewGL adds no control of its own, so
|
|
* hiding the corner hides the credit and the wordmark and nothing besides.
|
|
*
|
|
* Owner's call, made knowingly: OSM and the tile vendors ask for a visible credit. */
|
|
.m-root .leaflet-control-attribution,
|
|
.m-root .maplibregl-ctrl-bottom-left,
|
|
.m-root .mapboxgl-ctrl-bottom-left,
|
|
.m-root .maplibregl-ctrl-bottom-right,
|
|
.m-root .mapboxgl-ctrl-bottom-right {
|
|
display: none;
|
|
}
|
|
|
|
/* Text fields carry the row's background, not their own.
|
|
*
|
|
* iOS paints its own background inside a text control, and under
|
|
* `color-scheme: dark` that fill is darker than the card the field sits on —
|
|
* so every field showed as a dark rectangle inside a lighter row (#2028). The
|
|
* mobile fields are deliberately `bg-transparent` because the row around them
|
|
* supplies the surface, which is exactly why the native fill has nothing
|
|
* covering it here and shows through; a desktop field hides it behind its own
|
|
* background. Dropping the native appearance is what actually removes it.
|
|
*
|
|
* Only text entry. `file`, `color`, `range` and the date/time pickers need
|
|
* their native chrome to stay usable, and `select` keeps its own arrow. */
|
|
.m-root input:not([type]),
|
|
.m-root input[type="text"],
|
|
.m-root input[type="password"],
|
|
.m-root input[type="email"],
|
|
.m-root input[type="url"],
|
|
.m-root input[type="search"],
|
|
.m-root textarea {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
}
|
|
|
|
/* Horizontal scroller without a visible scrollbar, for control rows that must
|
|
survive narrow viewports and large system font scales (dashboard filter). */
|
|
.m-hscroll {
|
|
overflow-x: auto;
|
|
overscroll-behavior-x: contain;
|
|
scrollbar-width: none;
|
|
}
|
|
.m-hscroll::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
/* ── Page transitions (mobile/routeTransition.ts) ───────────────────────────
|
|
The way a native navigation stack moves: forward slides the new page in from
|
|
the trailing edge over the old one, which drifts a little the other way and
|
|
dims; back is the mirror; switching between the dock's sections only
|
|
cross-fades. --m-vt-dir flips the travel for right-to-left languages. The
|
|
dock keeps its own name, so it stands still while the pages move under it. */
|
|
:root {
|
|
--m-vt-dir: 1;
|
|
/* The company-holiday tint of the Vacay cells (vacayDayModel), for the legend chip. */
|
|
--m-vacay-company: #f5d9a6;
|
|
}
|
|
:root[dir='rtl'] {
|
|
--m-vt-dir: -1;
|
|
}
|
|
.m-dock-vt {
|
|
view-transition-name: m-dock;
|
|
}
|
|
@keyframes m-vt-in-trailing {
|
|
from {
|
|
transform: translateX(calc(100% * var(--m-vt-dir)));
|
|
}
|
|
}
|
|
@keyframes m-vt-out-leading {
|
|
to {
|
|
transform: translateX(calc(-30% * var(--m-vt-dir)));
|
|
filter: brightness(0.82);
|
|
}
|
|
}
|
|
@keyframes m-vt-in-leading {
|
|
from {
|
|
transform: translateX(calc(-30% * var(--m-vt-dir)));
|
|
filter: brightness(0.82);
|
|
}
|
|
}
|
|
@keyframes m-vt-out-trailing {
|
|
to {
|
|
transform: translateX(calc(100% * var(--m-vt-dir)));
|
|
}
|
|
}
|
|
:root[data-route-transition='forward']::view-transition-old(root) {
|
|
animation: m-vt-out-leading 0.4s cubic-bezier(0.32, 0.72, 0, 1) both;
|
|
}
|
|
:root[data-route-transition='forward']::view-transition-new(root) {
|
|
animation: m-vt-in-trailing 0.4s cubic-bezier(0.32, 0.72, 0, 1) both;
|
|
box-shadow: -16px 0 40px -12px rgb(0 0 0 / 0.35);
|
|
}
|
|
:root[data-route-transition='back']::view-transition-old(root) {
|
|
z-index: 1;
|
|
animation: m-vt-out-trailing 0.34s cubic-bezier(0.32, 0.72, 0, 1) both;
|
|
box-shadow: -16px 0 40px -12px rgb(0 0 0 / 0.35);
|
|
}
|
|
:root[data-route-transition='back']::view-transition-new(root) {
|
|
animation: m-vt-in-leading 0.34s cubic-bezier(0.32, 0.72, 0, 1) both;
|
|
}
|
|
:root[data-route-transition='tab']::view-transition-old(root),
|
|
:root[data-route-transition='tab']::view-transition-new(root) {
|
|
animation-duration: 0.18s;
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
::view-transition-group(*),
|
|
::view-transition-old(*),
|
|
::view-transition-new(*) {
|
|
animation: none !important;
|
|
}
|
|
}
|
|
|
|
/* A scroll box under a fixed header: content fades out over the padding at its
|
|
edges instead of being cut off in a hard line right under the header. */
|
|
.m-scroll-fade {
|
|
-webkit-mask-image: linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%);
|
|
mask-image: linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%);
|
|
}
|
|
|
|
/* The shared pickers (select, date, time) inside the phone UI look like the
|
|
sheets' own fields: a 42 px round field on the field surface. They style
|
|
themselves inline for the desktop, hence the !important. */
|
|
.m-root [data-field-trigger] {
|
|
min-height: 42px;
|
|
border-radius: 9999px !important;
|
|
border: 1px solid var(--m-rowbr) !important;
|
|
background: var(--m-ic) !important;
|
|
}
|
|
.m-root button[data-field-trigger],
|
|
.m-root [data-field-trigger='pad'] {
|
|
padding-inline: 14px !important;
|
|
}
|
|
|
|
/* ── The page column on a wide window ───────────────────────────────────────
|
|
Fixed bars span the page, not the window: they start after the navigation
|
|
rail (--m-rail-w, 0 on a phone, so these are the phone's insets there). */
|
|
.m-bar-x {
|
|
inset-inline-start: calc(var(--m-rail-w, 0px) + 16px);
|
|
inset-inline-end: 16px;
|
|
}
|
|
.m-span-x {
|
|
inset-inline-start: var(--m-rail-w, 0px);
|
|
inset-inline-end: 0;
|
|
}
|
|
/* Without the rail the floating bars keep clear of the sides' safe area too:
|
|
a camera in a corner can come as a side inset. The insets are the screen's,
|
|
not the text direction's, so right to left takes them the other way round.
|
|
On a wide window the rail takes the leading side and a bar may centre itself
|
|
(mx-auto). */
|
|
html:not(:has(.m-shell[data-size='wide'], .m-shell[data-size='split'])) :is(.m-bar-x, .m-span-x) {
|
|
margin-inline: var(--m-safe-l, 0px) var(--m-safe-r, 0px);
|
|
}
|
|
html[dir='rtl']:not(:has(.m-shell[data-size='wide'], .m-shell[data-size='split'])) :is(.m-bar-x, .m-span-x) {
|
|
margin-inline: var(--m-safe-r, 0px) var(--m-safe-l, 0px);
|
|
}
|
|
/* A screen whose background is a map reaches under the rail: the map runs to
|
|
the window's edge and the rail floats over it like the top controls do. Its
|
|
own bars then keep to the page column with m-bar-x / m-span-x. */
|
|
.m-bleed {
|
|
margin-inline-start: calc(var(--m-rail-w, 0px) * -1);
|
|
}
|
|
/* The trip's pane beside the map (split size class, MTripShell): a third of
|
|
the window, within what a list and its rows need. On a foldable that reports
|
|
its fold (two horizontal viewport segments), the pane takes the first half
|
|
to the fold exactly, so no row runs across the crease and the map has the
|
|
other half to itself. */
|
|
.m-trip {
|
|
--trip-pane-open: clamp(360px, 38vw, 460px);
|
|
}
|
|
@media (horizontal-viewport-segments: 2) {
|
|
.m-trip {
|
|
--trip-pane-open: calc(env(viewport-segment-width 0 0, 50vw) - var(--m-rail-w, 0px));
|
|
}
|
|
}
|
|
|
|
/* The leading pane of a two-pane screen (a tab's summary, a list beside its
|
|
detail): about a third of the page, within what its cards need. On a
|
|
foldable that reports its fold it ends at the fold, like the trip's pane. */
|
|
.m-root {
|
|
--m-pane-aside-w: clamp(320px, 34vw, 420px);
|
|
--m-pane-trail-w: clamp(340px, 36vw, 440px);
|
|
}
|
|
@media (horizontal-viewport-segments: 2) {
|
|
.m-root {
|
|
--m-pane-aside-w: calc(env(viewport-segment-width 0 0, 50vw) - var(--m-rail-w, 0px));
|
|
--m-pane-trail-w: env(viewport-segment-width 1 0, 50vw);
|
|
}
|
|
}
|
|
/* The Android app reports a foldable's fold itself (native/windowFold.ts):
|
|
MobileShell marks a vertical fold that separates the window with data-fold
|
|
and its place in --m-fold-start/--m-fold-end, and the panes meet there. The
|
|
leading side follows the reading direction, the fold's place does not. */
|
|
.m-root[data-fold='vertical'] {
|
|
--m-pane-aside-w: calc(var(--m-fold-start) - var(--m-rail-w, 0px));
|
|
--m-pane-trail-w: calc(100vw - var(--m-fold-end));
|
|
}
|
|
.m-root[data-fold='vertical'] .m-trip {
|
|
--trip-pane-open: calc(var(--m-fold-start) - var(--m-rail-w, 0px));
|
|
}
|
|
:root[dir='rtl'] .m-root[data-fold='vertical'] {
|
|
--m-pane-aside-w: calc(100vw - var(--m-fold-end) - var(--m-rail-w, 0px));
|
|
--m-pane-trail-w: var(--m-fold-start);
|
|
}
|
|
:root[dir='rtl'] .m-root[data-fold='vertical'] .m-trip {
|
|
--trip-pane-open: calc(100vw - var(--m-fold-end) - var(--m-rail-w, 0px));
|
|
}
|
|
.m-pane-aside {
|
|
width: var(--m-pane-aside-w);
|
|
}
|
|
/* The trailing pane: what accompanies the page rather than leads it, like the
|
|
trip chat beside its notes and polls. */
|
|
.m-pane-trail {
|
|
width: var(--m-pane-trail-w);
|
|
}
|
|
|
|
/* The phone rules of index.css, for the app. It shows this shell at every
|
|
size, an iPad's included (mobile/useIsPhone.ts), where their media query does
|
|
not match; the shell's root carries .m-shell, so they follow it there: the
|
|
document is the scroller, the shell paints the background, no zoom buttons
|
|
on a touch map, no scrollbars. */
|
|
html:has(.m-shell) {
|
|
overflow: visible;
|
|
overscroll-behavior: none;
|
|
}
|
|
/* In the iOS app the page bounces at its top and bottom, as every iOS list
|
|
does; the fixed bars stay where they are. The rule above is for the browser,
|
|
where a pull at the top reloads an installed PWA, which the app cannot do.
|
|
Not on Android, whose stretch scales the bars too (MainActivity turns it
|
|
off), and not sideways. A screen with pull-to-refresh bounces too; its pull
|
|
holds only the move down from the top (mobile/usePullToRefresh.ts). */
|
|
html[data-native-ios],
|
|
html[data-native-ios]:has(.m-shell) {
|
|
overscroll-behavior-y: auto;
|
|
}
|
|
html:has(.m-shell) body {
|
|
height: auto;
|
|
overflow: visible;
|
|
overflow-x: clip;
|
|
background-color: transparent;
|
|
}
|
|
/* The few routes without a phone screen of their own (a plugin's page, help,
|
|
a trip's files) still render through PageShell. Inside the touch shell its
|
|
desktop navbar, shown from 768px up, would stand over the rail and repeat the
|
|
navigation, and --nav-h would keep 64px of room for it: the shell has its own
|
|
navigation, so neither is there. */
|
|
html:has(.m-shell) {
|
|
--nav-h: 0px;
|
|
}
|
|
html:has(.m-shell) [data-desktop-navbar] {
|
|
display: none;
|
|
}
|
|
/* A plugin's page inside the touch shell: below the status bar, above the dock,
|
|
after the rail, and exactly the visible height (100dvh), so the plugin's own
|
|
document is what scrolls instead of the app stretching past the screen. */
|
|
html:has(.m-shell) [data-plugin-page] {
|
|
--plugin-page-h: calc(100dvh - var(--bottom-nav-h, 0px));
|
|
box-sizing: border-box;
|
|
padding-top: env(safe-area-inset-top, 0px);
|
|
padding-inline-start: var(--m-rail-w, 0px);
|
|
}
|
|
html:has(.m-shell) .leaflet-control-zoom {
|
|
display: none !important;
|
|
}
|
|
html:has(.m-shell) * {
|
|
scrollbar-width: none;
|
|
}
|
|
html:has(.m-shell) ::-webkit-scrollbar {
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
/* While the trip's map runs under the rail (MTripShell, --trip-map-inset), the
|
|
controls both engines draw in its corners step clear of the rail. A margin on
|
|
the leading side moves only the corner next to the rail, in either reading
|
|
direction: the corner on the far side is anchored there and does not move. */
|
|
.m-trip .leaflet-control-container > div,
|
|
.m-trip .maplibregl-control-container > div,
|
|
.m-trip .mapboxgl-control-container > div {
|
|
margin-inline-start: var(--trip-map-inset, 0px);
|
|
}
|
|
|
|
/* The strip under a floating dock (components/MDockScrim.tsx): solid across
|
|
the gap below the dock, clear from about its middle up. */
|
|
.m-dock-scrim {
|
|
-webkit-mask-image: linear-gradient(to top, #000 calc(env(safe-area-inset-bottom, 0px) + 12px), transparent calc(env(safe-area-inset-bottom, 0px) + 40px));
|
|
mask-image: linear-gradient(to top, #000 calc(env(safe-area-inset-bottom, 0px) + 12px), transparent calc(env(safe-area-inset-bottom, 0px) + 40px));
|
|
}
|
|
|
|
/* A horizontal strip with more behind an edge fades out there
|
|
(components/useEdgeFade.ts), so a cut chip reads as "the row goes on". */
|
|
.m-fade-s,
|
|
.m-fade-e,
|
|
.m-fade-se {
|
|
/* A strip with a chevron over its faded end fades wider (--m-fade-reach). */
|
|
--m-fade-w: var(--m-fade-reach, 28px);
|
|
-webkit-mask-image: var(--m-fade-mask);
|
|
mask-image: var(--m-fade-mask);
|
|
}
|
|
.m-fade-s {
|
|
--m-fade-mask: linear-gradient(var(--m-fade-dir, 90deg), transparent 0, #000 var(--m-fade-w));
|
|
}
|
|
.m-fade-e {
|
|
--m-fade-mask: linear-gradient(var(--m-fade-dir, 90deg), #000 calc(100% - var(--m-fade-w)), transparent 100%);
|
|
}
|
|
.m-fade-se {
|
|
--m-fade-mask: linear-gradient(var(--m-fade-dir, 90deg), transparent 0, #000 var(--m-fade-w), #000 calc(100% - var(--m-fade-w)), transparent 100%);
|
|
}
|
|
:root[dir='rtl'] .m-fade-s,
|
|
:root[dir='rtl'] .m-fade-e,
|
|
:root[dir='rtl'] .m-fade-se {
|
|
--m-fade-dir: 270deg;
|
|
}
|
|
|
|
/* The same for a vertical scroller, a form sheet's body above all: it fades
|
|
out at the top once scrolled and at the foot while more waits below, so a
|
|
field cut by the footer reads as "the form goes on". Never mirrored. */
|
|
.m-fade-t,
|
|
.m-fade-b,
|
|
.m-fade-tb {
|
|
--m-fade-h: var(--m-fade-reach-y, 24px);
|
|
-webkit-mask-image: var(--m-fade-vmask);
|
|
mask-image: var(--m-fade-vmask);
|
|
}
|
|
.m-fade-t {
|
|
--m-fade-vmask: linear-gradient(to bottom, transparent 0, #000 var(--m-fade-h));
|
|
}
|
|
.m-fade-b {
|
|
--m-fade-vmask: linear-gradient(to bottom, #000 calc(100% - var(--m-fade-h)), transparent 100%);
|
|
}
|
|
.m-fade-tb {
|
|
--m-fade-vmask: linear-gradient(to bottom, transparent 0, #000 var(--m-fade-h), #000 calc(100% - var(--m-fade-h)), transparent 100%);
|
|
}
|
|
|
|
/* A hint longer than its field ends in an ellipsis instead of being cut off
|
|
mid-word at the field's edge ("Код аэропорта или город (нап"). */
|
|
.m-root input:placeholder-shown {
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
/* ── Sheets ─────────────────────────────────────────────────────────────────
|
|
A sheet takes the focus itself when it opens (modalFocus.ts), so a screen
|
|
reader starts inside it. The app-wide focus ring (index.css) is meant for
|
|
controls: on the sheet it drew a 2px frame round the whole dialog and
|
|
squared its corners off to 4px. */
|
|
.m-root [data-m-sheet]:focus-visible {
|
|
outline: none;
|
|
border-radius: var(--m-sheet-r);
|
|
}
|
|
|
|
/* A form sheet (MSheet `form`) on a window as short as a phone on its side
|
|
fills the height, and its actions move up beside the title the way an
|
|
iPhone form carries Cancel and Save in its bar, so the fields get every
|
|
line in between instead of a slot 140px tall. Cancel then does what the
|
|
header's close button did, so that one goes. */
|
|
@media (max-height: 500px) {
|
|
/* Row 1: the title and the actions. Row 2: whatever a form keeps fixed
|
|
under its title (the transport's manual/automated switch). Row 3: the
|
|
fields, which take the rest. Doubled class: the panel's own `flex`
|
|
utility must not win the display back. */
|
|
.m-form-sheet.m-form-sheet {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
grid-template-rows: auto auto minmax(0, 1fr);
|
|
align-items: center;
|
|
}
|
|
.m-form-sheet > * {
|
|
grid-column: 1 / -1;
|
|
}
|
|
.m-form-sheet > .m-fs-head {
|
|
grid-area: 1 / 1;
|
|
}
|
|
.m-form-sheet > .m-fs-foot {
|
|
grid-area: 1 / 2;
|
|
border-top: 0;
|
|
padding-block: 16px 8px;
|
|
padding-inline: 0 18px;
|
|
}
|
|
.m-form-sheet > .m-fs-body {
|
|
grid-row: 3;
|
|
align-self: stretch;
|
|
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
|
|
}
|
|
.m-form-sheet:has(> .m-fs-foot) > .m-fs-head .m-fs-close {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* A segmented control in the trip's top bar that does not fit between the back
|
|
button and the trailing button draws its segments tighter first
|
|
(MTripShellParts GlassSegment), and scrolls only where that is not enough. */
|
|
.m-seg[data-tight='true'] > * {
|
|
padding-inline: 10px;
|
|
}
|
|
/* A one-line Markdown caption (MarkdownText clamp: an address, a note in a
|
|
row) ends in an ellipsis. The clamp sits on the wrapper, but the text is in
|
|
the paragraph inside it, so the line was cut off mid-letter at the row's
|
|
edge or under its buttons with nothing to say it went on. */
|
|
.m-root .collab-note-md.truncate > * {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* The trip's shared links in the touch shell (CollabLinks in MCollabTab). Their
|
|
edit, pin and delete buttons are always out on a touch screen, so a link in
|
|
one of two columns kept a sliver for its title ("JR seat / reservations") and
|
|
cut its host. One column, until the board has room for two links side by
|
|
side with their titles on one line. */
|
|
.m-root .collab-link-grid__cells {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
@container (min-width: 720px) {
|
|
.m-root .collab-link-grid__cells {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|