/* =============================================================
   MOBILE V2: design system.
   Additive only: loads after styles.css, only when window.AIOS_VARIANT
   is 'm2' (set by the static build configuration). Every current tenant
   build ships without this file, so it changes nothing until asked for by
   name. See docs/plan_2026-07-27_reference-mobile-v1.md section 4 for the
   reasoning behind each rule below; this file is that section made real.

   Everything here is scoped under body.aios-variant-m2 (set by
   applyVariant() in plate.js), so a rule can restyle a shared element (the
   dock, a card grid) without ever touching or overriding the current
   mobile's CSS in place. Naming: every custom property and class below is
   prefixed m2- and namespaced away from the unprefixed convention the
   current styles use, on purpose, so the two systems never collide and are
   always easy to tell apart by name alone.

   Scope of this file, stage 1 only: colour roles, the type ladder, the
   spacing/radius scale, the three shared components (card, row, button),
   the eyebrow label, and motion. Screen-specific chrome (masthead, dock,
   navigation overlay, the landing gate) is stage 2; content cards
   (floorplan, amenities, community detail) are stage 3. Building those
   here now would be building screens before the system they're supposed
   to be assembled from is reviewed, which is the mistake this whole
   system exists to avoid.
   ============================================================= */

body.aios-variant-m2 {

  /* ---- 4.1 colour, expressed as roles, not values ----
     Every colour below resolves to a variable styles.css already declares
     per tenant (styles.css:26-66). No hex value is written in this file:
     that is what lets one stylesheet serve every tenant's own brand. */
  /* Fallback values are copied verbatim from styles.css:36-42 so a tenant that
     somehow loads this file without the core stylesheet degrades to the real
     palette rather than a near-miss one. An earlier pass had four of these five
     subtly wrong (#E4E7EC vs the real #E5E7EB, and so on), which would have
     painted an off-palette silently instead of failing visibly. */
  --m2-structure: var(--aios-blue, var(--accent));
  --m2-structure-dark: var(--aios-blue-dark, var(--aios-blue, var(--accent)));
  --m2-surface: #fff;
  --m2-page: var(--bg-tint, #F8F9FB);
  --m2-hairline: var(--line, #E5E7EB);
  --m2-hairline-soft: var(--line-soft, #F1F3F5);
  --m2-ink: var(--ink, #1A1A1A);
  --m2-muted: var(--muted, #6B7280);
  --m2-on-structure: #fff;

  /* ---- 4.2 type ladder: seven steps, and nothing outside them.
     size / weight / line-height. Today's mobile tunes every surface
     separately (a 16-18px greeting, 11.5px card titles, 13px chips, 9px
     attribution: no shared ladder at all); every m2 surface pulls from
     exactly one of these seven. ---- */
  /* C2 (owner-approved 2026-07-28): the ladders are fluid below 390px.
     Every clamp is clamp(floor, calc(today + (100vw - 390px) * slope), today):
     at 390vw the calc equals today's value exactly, above 390 it exceeds it
     and the max pins it, below 375 the floor holds. So 390 and wider render
     byte-for-byte as the owner approved, and only 320-389 changes, shrinking
     toward tasteful floors reached at 375. Slopes are exact fractions,
     (today - floor) / 15, so the pin is arithmetic, not rounding.

     Verified by two harnesses: element-by-element parity of the built file
     against a pre-change snapshot at 390/430/440 (must be identical), and the
     token-mutation probe (elements on the ladder must move when it moves).

     Deliberately NOT fluid:
     - label and legal: 11px is the D3 accessibility floor (AODA note below);
       shrinking the two smallest steps would cross it.
     - line-heights: unitless, and clamp() cannot mix a unitless value with a
       vw calc. Costs about 2px of the narrow-screen recovery. Revisit only if
       the ladder ever moves to length line-heights.
     - weights and tracking: size is what wraps text, weight is identity. */
  --m2-display-size: clamp(22px, calc(24px + (100vw - 390px) * 2 / 15), 24px); --m2-display-weight: 700; --m2-display-lh: 1.25;
  --m2-lead-size: clamp(17px, calc(18px + (100vw - 390px) * 1 / 15), 18px);    --m2-lead-weight: 700;    --m2-lead-lh: 1.3;
  --m2-title-size: clamp(16px, calc(17px + (100vw - 390px) * 1 / 15), 17px);   --m2-title-weight: 700;   --m2-title-lh: 1.35;
  --m2-body-size: clamp(14px, calc(15px + (100vw - 390px) * 1 / 15), 15px);    --m2-body-weight: 400;    --m2-body-lh: 1.55;
  --m2-meta-size: clamp(12.5px, calc(13px + (100vw - 390px) * 1 / 30), 13px);  --m2-meta-weight: 400;    --m2-meta-lh: 1.4;
  --m2-label-size: 11px;   --m2-label-weight: 700;   --m2-label-lh: 1.3; --m2-label-tracking: 0.08em;
  /* D3 (owner-approved deviation, 2026-07-27): the deck draws legal type at
     roughly 8px. That is below any practical accessibility floor, and
     reference carries its own AODA commitment in its footer. Held at 11px,
     matching --m2-meta, so it reads as "quiet" without being unreadable. */
  --m2-legal-size: 11px;   --m2-legal-weight: 400;   --m2-legal-lh: 1.45;

  /* ---- 4.3 space and shape: one scale, everywhere ----
     Fluid on the same 375-to-390 ramp as the type, same pin at 390.
     Radii are shape, not rhythm, and stay static. */
  --m2-space-1: clamp(3px, calc(4px + (100vw - 390px) * 1 / 15), 4px);
  --m2-space-2: clamp(7px, calc(8px + (100vw - 390px) * 1 / 15), 8px);
  --m2-space-3: clamp(10px, calc(12px + (100vw - 390px) * 2 / 15), 12px);
  --m2-space-4: clamp(14px, calc(16px + (100vw - 390px) * 2 / 15), 16px);
  --m2-space-5: clamp(17px, calc(20px + (100vw - 390px) * 3 / 15), 20px);
  --m2-space-6: clamp(21px, calc(24px + (100vw - 390px) * 3 / 15), 24px);
  --m2-space-8: clamp(28px, calc(32px + (100vw - 390px) * 4 / 15), 32px);
  --m2-gutter: var(--m2-space-4);
  --m2-card-gap: var(--m2-space-3);
  --m2-section-gap: var(--m2-space-6);

  --m2-radius-card: 14px;
  --m2-radius-image: 10px;
  --m2-radius-input: 16px;
  --m2-radius-pill: 999px;

  /* ---- 4.7 motion: one duration, one curve. Sheets/overlays slide from
     their edge (stage 2 uses --m2-ease-slide); everything else fades. ---- */
  --m2-duration: 200ms;
  --m2-ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* No font-family here on purpose. styles.css already sets Inter on the plain
     `body` selector; declaring font-family: inherit on this more specific
     body.aios-variant-m2 selector would win the cascade and override it, and
     since <html> itself carries no font-family, "inherit" would then resolve
     to the browser's bare default (serif) for the entire app. Leaving this
     undeclared means the existing Inter rule simply applies, untouched. */
  color: var(--m2-ink);
}

/* ================= type ladder utilities =================
   Exposed as their own classes (not just baked into components) so the
   swatch board can show the ladder on its own, and so any future m2
   surface can reach a step directly without inventing a new size. */
body.aios-variant-m2 .m2-text-display { font-size: var(--m2-display-size); font-weight: var(--m2-display-weight); line-height: var(--m2-display-lh); margin: 0; }
body.aios-variant-m2 .m2-text-lead    { font-size: var(--m2-lead-size);    font-weight: var(--m2-lead-weight);    line-height: var(--m2-lead-lh);    margin: 0; }
body.aios-variant-m2 .m2-text-title   { font-size: var(--m2-title-size);   font-weight: var(--m2-title-weight);   line-height: var(--m2-title-lh);   margin: 0; }
body.aios-variant-m2 .m2-text-body    { font-size: var(--m2-body-size);    font-weight: var(--m2-body-weight);    line-height: var(--m2-body-lh);    margin: 0; color: var(--m2-ink); }
body.aios-variant-m2 .m2-text-meta    { font-size: var(--m2-meta-size);    font-weight: var(--m2-meta-weight);    line-height: var(--m2-meta-lh);    margin: 0; color: var(--m2-muted); }
body.aios-variant-m2 .m2-text-legal   { font-size: var(--m2-legal-size);   font-weight: var(--m2-legal-weight);   line-height: var(--m2-legal-lh);   margin: 0; color: var(--m2-muted); }

/* The eyebrow: the deck's one consistent structural device ("SUGGESTED NEXT
   STEPS", "PRICING & AVAILABILITY", "2-BEDROOM LAYOUTS (3)"). Used to mark
   every section, including ones the deck never drew. */
body.aios-variant-m2 .m2-eyebrow {
  font-size: var(--m2-label-size);
  font-weight: var(--m2-label-weight);
  line-height: var(--m2-label-lh);
  letter-spacing: var(--m2-label-tracking);
  text-transform: uppercase;
  color: var(--m2-muted);
  margin: 0 0 var(--m2-space-2);
}

/* ================= 4.4 the three components ================= */

/* The card: white fill, hairline border, 14px radius, 16px padding.
   Community cards, floorplan cards, detail cards, response bubbles, the
   registration block and out-of-scope answers are all this one component,
   per the plan's layout law: content stacks full width, it doesn't
   invent a bespoke container per screen. */
body.aios-variant-m2 .m2-card {
  background: var(--m2-surface);
  border: 1px solid var(--m2-hairline);
  border-radius: var(--m2-radius-card);
  padding: var(--m2-space-4);
}
body.aios-variant-m2 .m2-card + .m2-card { margin-top: var(--m2-card-gap); }
body.aios-variant-m2 .m2-card > .m2-card-title { font-size: var(--m2-title-size); font-weight: var(--m2-title-weight); line-height: var(--m2-title-lh); margin: 0 0 var(--m2-space-2); }
body.aios-variant-m2 .m2-card > .m2-card-body  { font-size: var(--m2-body-size); font-weight: var(--m2-body-weight); line-height: var(--m2-body-lh); margin: 0; color: var(--m2-ink); }
/* Card image: 10px radius, never the card's own 14px, so a photo never
   reads as sharing an edge with the card's outer corner. */
body.aios-variant-m2 .m2-card-image { display: block; width: 100%; border-radius: var(--m2-radius-image); overflow: hidden; }
body.aios-variant-m2 .m2-card-image img { display: block; width: 100%; height: auto; }

/* The row: minimum 56px, label left, value or chevron right, hairline
   separator between rows in the same group. The area picker, the amenity
   list, navigation rows, and the Studio/1 Bed/2 Bed and floorplan spec
   tables are all this one component, not five different implementations
   that happen to look similar. */
body.aios-variant-m2 .m2-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--m2-space-3);
  min-height: 56px;
  padding: 0 var(--m2-space-4);
}
body.aios-variant-m2 .m2-row-group { background: var(--m2-surface); border: 1px solid var(--m2-hairline); border-radius: var(--m2-radius-card); overflow: hidden; }
body.aios-variant-m2 .m2-row-group .m2-row + .m2-row { border-top: 1px solid var(--m2-hairline-soft); }
body.aios-variant-m2 .m2-row-label { font-size: var(--m2-body-size); font-weight: 400; color: var(--m2-ink); }
body.aios-variant-m2 .m2-row-value { font-size: var(--m2-body-size); font-weight: 600; color: var(--m2-ink); text-align: right; }
body.aios-variant-m2 .m2-row-chevron { flex: 0 0 auto; width: 18px; height: 18px; color: var(--m2-muted); }

/* The button, in exactly three styles, at one minimum height. Selected
   simply means primary: Rent vs Purchase, a floorplan layout chip, and the
   language picker all express "selected" the same way, because they are
   all this one component with one modifier changed. */
body.aios-variant-m2 .m2-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--m2-space-2);
  min-height: 48px;
  padding: 0 var(--m2-space-4);
  border-radius: var(--m2-radius-pill);
  font-size: var(--m2-body-size);
  font-weight: 600;
  line-height: 1;
  border: 1.5px solid transparent;
  cursor: pointer;
  transition: background var(--m2-duration) var(--m2-ease), color var(--m2-duration) var(--m2-ease), border-color var(--m2-duration) var(--m2-ease);
}
body.aios-variant-m2 .m2-btn-primary {
  background: var(--m2-structure);
  color: var(--m2-on-structure);
  border-color: var(--m2-structure);
}
body.aios-variant-m2 .m2-btn-secondary {
  background: var(--m2-surface);
  color: var(--m2-structure);
  border-color: var(--m2-structure);
}
body.aios-variant-m2 .m2-btn-quiet {
  background: transparent;
  color: var(--m2-structure);
  border-color: transparent;
  padding-left: var(--m2-space-2);
  padding-right: var(--m2-space-2);
  min-height: auto;
  font-weight: 400;
  text-decoration: underline;
}
/* The one context rule in the system: on a brand-coloured surface (the
   landing cards), secondary inverts to a white border with white text,
   the deck's "Choose" button. Everywhere else, secondary is the plain
   white/brand-border version above. */
body.aios-variant-m2 .m2-on-color .m2-btn-secondary {
  background: transparent;
  color: var(--m2-on-structure);
  border-color: var(--m2-on-structure);
}

/* Text never truncates on m2. It wraps. The current mobile's starter-card
   titles use nowrap + ellipsis and fit English by roughly seventeen
   pixels, silently cutting off in French, Arabic, and every other
   language this fleet ships. No m2 rule anywhere sets white-space:nowrap
   on user-visible text. */

/* =============================================================
   STAGE 2 (a): THE LANDING GATE. Deck page 2.

   Approach: style the AUTHORED toggle screen (public/index.html
   #toggleScreen) for phone, rather than building a bespoke replacement
   in JS the way the current mobile does. Three things fall out of that
   for free, all of which are defects in the current phone gate:
     - it translates (every node is data-i18n driven; the current phone
       gate is hardcoded English and does not translate at all),
     - it survives a resize (no JS build step to re-run), and
     - it needs no height-freezing JS, because the deck's gate scrolls
       rather than being pinned to one viewport.

   Paired change: the hides in plate.js injectChromeStyles() are scoped
   with body:not(.aios-variant-m2), and ensureMobileGateway() returns
   early. Those two and this block are one unit; splitting them leaves
   no way into AiOS at phone width.
   ============================================================= */

@media (max-width: 767px) {

  /* The page scrolls. The current mobile pins the gate to 100svh and
     freezes its height in JS to stop the URL bar resizing it; the deck
     scrolls the gate with the site footer below, so that whole problem
     disappears rather than needing to be solved. */
  body.aios-variant-m2 #toggleScreen {
    padding: 0 0 var(--m2-space-8);
    background: var(--m2-page);
  }
  body.aios-variant-m2 #toggleScreen .toggle-screen {
    display: block;
    max-width: 100%;
    margin: 0;
    padding: var(--m2-space-6) var(--m2-gutter) var(--m2-space-4);
    text-align: center;
  }

  /* Headline and subhead: display and body from the ladder, nothing new. */
  body.aios-variant-m2 #toggleScreen .toggle-headline {
    font-size: var(--m2-display-size);
    font-weight: var(--m2-display-weight);
    line-height: var(--m2-display-lh);
    color: var(--m2-structure);
    margin: 0 0 var(--m2-space-2);
    text-wrap: balance;
  }
  body.aios-variant-m2 #toggleScreen .toggle-subhead {
    font-size: var(--m2-body-size);
    line-height: var(--m2-body-lh);
    color: var(--m2-muted);
    margin: 0 0 var(--m2-space-5);
  }

  /* Two cards, stacked, full width. Deck: card 1 is a brand-filled
     surface, card 2 is a white surface with a brand keyline. Both are
     the system card with a different fill, not two bespoke components. */
  body.aios-variant-m2 #toggleScreen .toggle-cards {
    display: flex;
    flex-direction: column;
    gap: var(--m2-card-gap);
    width: 100%;
    margin: 0;
  }
  body.aios-variant-m2 #toggleScreen .toggle-card {
    width: 100%;
    max-width: none;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--m2-space-2);
    padding: var(--m2-space-5) var(--m2-space-4);
    border-radius: var(--m2-radius-card);
    box-shadow: none;
  }
  /* Both cards are brand-filled, per the deck. .m2-on-color is applied to
     both by mobile2.js, so the system's one context rule (a secondary
     button on a brand surface inverts to white border + white text)
     supplies the "Choose" buttons without restating them per card. */
  body.aios-variant-m2 #toggleScreen .toggle-card {
    background: var(--m2-structure);
    border: 1px solid var(--m2-structure);
    color: var(--m2-on-structure);
  }

  body.aios-variant-m2 #toggleScreen .toggle-card-title {
    font-size: var(--m2-title-size);
    font-weight: var(--m2-title-weight);
    line-height: var(--m2-title-lh);
    margin: 0;
  }
  body.aios-variant-m2 #toggleScreen .toggle-card-title { color: var(--m2-on-structure); }

  body.aios-variant-m2 #toggleScreen .toggle-card-persona {
    font-size: var(--m2-meta-size);
    font-weight: 600;
    line-height: var(--m2-meta-lh);
    margin: 0;
  }
  body.aios-variant-m2 #toggleScreen .toggle-card-body {
    font-size: var(--m2-body-size);
    font-weight: var(--m2-body-weight);
    line-height: var(--m2-body-lh);
    margin: 0;
  }
  body.aios-variant-m2 #toggleScreen .toggle-card-persona,
  body.aios-variant-m2 #toggleScreen .toggle-card-body { color: var(--m2-on-structure); }

  /* The deck puts the language selector inside card 1, which is also what
     the authored markup already does. It is a white pill on the brand
     field. The current mobile moves this node into its masthead, which is
     why that masthead differs from every other screen's. */
  body.aios-variant-m2 #toggleScreen .toggle-language-picker {
    width: 100%;
    display: flex;
    justify-content: center;
    margin: var(--m2-space-2) 0 0;
  }
  body.aios-variant-m2 #toggleScreen .toggle-language-button {
    min-height: 48px;
    padding: 0 var(--m2-space-4);
    border-radius: var(--m2-radius-pill);
    background: var(--m2-surface);
    color: var(--m2-structure);
    border: 1px solid var(--m2-surface);
    font-size: var(--m2-body-size);
    font-weight: 600;
  }

  /* Both Choose buttons are the system secondary button. On card 1 the
     context rule inverts it to white-on-brand; on card 2 it is the plain
     brand-outline version. One component, two contexts, no new styles. */
  body.aios-variant-m2 #toggleScreen .toggle-card-button {
    width: 100%;
    min-height: 48px;
    margin-top: var(--m2-space-2);
    border-radius: var(--m2-radius-pill);
    font-size: var(--m2-body-size);
    font-weight: 600;
    background: transparent;
    cursor: pointer;
  }
  body.aios-variant-m2 #toggleScreen .toggle-card-button {
    color: var(--m2-on-structure);
    border: 1.5px solid var(--m2-on-structure);
  }

  /* Don't-ask row: the deck's circle, and a real 44px target. The row is
     the whole control (the authored markup wires onclick on the row). */
  body.aios-variant-m2 #toggleScreen .toggle-dontask {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--m2-space-2);
    min-height: 44px;
    margin: var(--m2-space-5) 0 0;
    font-size: var(--m2-body-size);
    font-weight: 600;
    color: var(--m2-ink);
    cursor: pointer;
  }
  body.aios-variant-m2 #toggleScreen .toggle-dontask-circle {
    width: 22px; height: 22px;
    border-radius: 50%;
    border: 1.5px solid var(--m2-muted);
    flex: 0 0 auto;
  }
  body.aios-variant-m2 #toggleScreen .toggle-dontask.checked .toggle-dontask-circle {
    border-color: var(--m2-structure);
    background: var(--m2-structure);
    box-shadow: inset 0 0 0 4px var(--m2-surface);
  }
  body.aios-variant-m2 #toggleScreen [data-i18n="toggle_change_anytime"] {
    font-size: var(--m2-legal-size) !important;
    line-height: var(--m2-legal-lh);
    color: var(--m2-muted) !important;
    margin-top: var(--m2-space-1) !important;
  }

  /* Attribution shown in full rather than collapsed behind a chevron
     (deck page 2), at the legal step of the ladder, which per D3 is held
     at a readable floor rather than the deck's roughly 8px. */
  body.aios-variant-m2 #toggleScreen .toggle-poweredby {
    display: block;
    margin: var(--m2-space-5) auto 0;
    padding-top: var(--m2-space-4);
    border-top: 1px solid var(--m2-hairline);
    font-size: 9.9px;
    line-height: var(--m2-legal-lh);
    color: var(--m2-muted);
    max-width: 34em;
  }
}

/* =============================================================
   STAGE 2 (b): THE MASTHEAD, made constant. Deck pages 2 to 9.

   The deck draws the same bar on every screen: brand fill, logo left,
   hamburger right, nothing else. The current mobile has two different
   mastheads, because its bespoke landing gateway builds its own
   (carrying a language pill that no other screen has). Mobile V2 uses
   the shell's real navbar everywhere, so there is only one to style
   and the language selector sits inside the landing card where the
   deck puts it.
   ============================================================= */

@media (max-width: 767px) {

  /* Greeting promoted to the lead step of the ladder. The current mobile
     clamps it to 16-18px to make room for the cramped tile grid. */
  body.aios-variant-m2 .aios-welcome {
    font-size: var(--m2-lead-size) !important;
    font-weight: var(--m2-lead-weight) !important;
    line-height: var(--m2-lead-lh) !important;
    color: var(--m2-structure) !important;
    text-wrap: balance;
    margin-bottom: var(--m2-space-4) !important;
  }

  /* The carousel. Full-bleed so the neighbouring cards peek at the screen
     edges, which is the affordance that says "these swipe". */
  body.aios-variant-m2 .welcome-actions {
    display: flex !important;
    grid-template-columns: none !important;
    flex-wrap: nowrap !important;
    gap: var(--m2-card-gap) !important;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-padding-left: var(--m2-gutter);
    margin: 0 calc(-1 * var(--m2-gutter)) !important;
    padding: var(--m2-space-1) var(--m2-gutter) var(--m2-space-3) !important;
    -webkit-overflow-scrolling: touch;
  }

  body.aios-variant-m2 .welcome-action {
    flex: 0 0 78vw !important;
    max-width: 78vw !important;
    min-height: 0 !important;
    scroll-snap-align: start;
    display: flex !important;
    flex-direction: column !important;
    align-items: flex-start !important;
    text-align: left !important;
    /* space-1 gap + space-3 vertical padding, not space-2/space-4: sized so
       the card stack leaves the dots visible on a real phone's ~660px of
       usable height (Safari's bars eat ~150px), owner review 2026-07-28.
       The dots are the only signal the cards swipe; a taller card that
       hides them reads as a single static card. */
    gap: var(--m2-space-1) !important;
    padding: var(--m2-space-3) var(--m2-space-4) !important;
    background: var(--m2-surface) !important;
    border: 1px solid var(--m2-hairline) !important;
    border-radius: var(--m2-radius-card) !important;
    box-shadow: none !important;
  }

  /* Deck order: icon, then title, then description. The current mobile
     reverses the first two and drops the third. */
  body.aios-variant-m2 .welcome-action-icon {
    order: 0 !important;
    width: 32px !important;
    height: 32px !important;
    border-radius: var(--m2-radius-image) !important;
    background: none !important;
    color: var(--m2-structure) !important;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    margin: 0 !important;
  }
  body.aios-variant-m2 .welcome-action-icon:has(img) {
    background: none !important;
  }
  body.aios-variant-m2 .welcome-action-title {
    order: 1 !important;
    font-size: var(--m2-title-size) !important;
    font-weight: var(--m2-title-weight) !important;
    line-height: var(--m2-title-lh) !important;
    color: var(--m2-ink) !important;
    /* Wraps. Never nowrap + ellipsis: that is what silently truncates
       every non-English label in the current build. */
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
  }
  body.aios-variant-m2 .welcome-action-sub {
    display: block !important;
    order: 2 !important;
    font-size: var(--m2-body-size) !important;
    font-weight: var(--m2-body-weight) !important;
    line-height: var(--m2-body-lh) !important;
    color: var(--m2-muted) !important;
    white-space: normal !important;
  }

  /* Start-screen vertical budget (owner review 2026-07-28, on-phone): with
     Safari's bars a phone shows ~660px, and the greeting-to-dots stack ran
     418px against a ~353px scroller, hiding the dots, the only signal that
     the cards swipe. Everything trimmed here is empty space between blocks;
     no text, control or card content shrinks. */
  body.aios-variant-m2.aios-active .aios-container { padding-top: var(--m2-space-2) !important; }
  body.aios-variant-m2 #welcomeHeader.aios-welcome { margin-bottom: var(--m2-space-1) !important; }
  body.aios-variant-m2 #orbWrapperLarge { margin-bottom: 0 !important; }
  body.aios-variant-m2 .welcome-actions { padding-top: 0 !important; padding-bottom: var(--m2-space-1) !important; }

  /* On genuinely short viewports (SE-class phones behind Safari's bars) no
     spacing trim can fit greeting + orb + card + dots; something must go, and
     the orb is decoration while the dots are function. The greeting and cards
     are identity and content, so the orb yields. */
  @media (max-height: 620px) {
    body.aios-variant-m2 #orbWrapperLarge { display: none !important; }
  }

  /* Dot pagination, built by mobile2.js. Nothing in the product has this
     today; it is the one genuinely new mechanic in the redesign. */
  body.aios-variant-m2 .m2-dots {
    display: flex;
    justify-content: center;
    align-items: center;
    /* No gap. The targets below tile edge to edge, so every point in the row
       belongs to exactly one dot and there is no dead space between them. The
       visible separation comes from the transparent padding, not from a gap. */
    gap: 0;
    /* The padding below adds 9px above and below the visible dots. Taking it
       back out of the margins keeps this row occupying the same vertical space
       it did when the dots were bare 7px circles, so making them tappable does
       not push the composer down. */
    margin: calc(var(--m2-space-1) - 9px) 0 calc(var(--m2-space-4) - 9px);
  }
  /* Tap targets.
     The visible dot is 7px, far below any usable target, so the BUTTON is
     enlarged with transparent padding and the 7px circle is painted into its
     content box. 7 + 9 + 9 = 25px square, which clears the WCAG 2.2 minimum of
     24px, and because the buttons tile with no gap, neighbours never overlap.
     What this replaces was worse than no fix at all: each dot carried an
     absolutely positioned 44px ::before with no left/top, so it was centred on
     the dot's corner rather than the dot, and at a 15px pitch each one covered
     three neighbours. Later siblings paint on top, so the LAST dot's hit area
     sat over all five others and swallowed every tap. Measured with
     elementFromPoint: five of the six dots could not be tapped at all, and
     every tap jumped to the final card. A hit area that overlaps its
     neighbours is not a bigger target, it is a broken one. */
  body.aios-variant-m2 .m2-dot {
    box-sizing: content-box;
    width: 7px;
    height: 7px;
    padding: 9px;
    background-clip: content-box;
    border-radius: 50%;
    background-color: var(--m2-hairline);
    border: 0;
    cursor: pointer;
    transition: background-color var(--m2-duration) var(--m2-ease), transform var(--m2-duration) var(--m2-ease);
  }
  body.aios-variant-m2 .m2-dot.is-active {
    background-color: var(--m2-structure);
    transform: scale(1.25);
  }
}

/* =============================================================
   STAGE 2 (e): THE DOCK CONTRACT. Deck pages 4 to 9.

   The deck ends every conversational screen the same way: composer,
   then one row with the attribution on the LEFT and the Site Plan
   control on the RIGHT. It never changes shape between screens, which
   is what makes it read as chrome rather than content.

   Current mobile puts them the other way round (Site Plan order 5,
   attribution order 6) and collapses the attribution behind a chevron
   at 9px. The deck shows it in full. Per the owner-approved D3
   deviation the type is held at the ladder's legal step (11px) rather
   than the roughly 8px drawn, because reference publishes its own AODA
   commitment and 8px is under any practical floor.
   ============================================================= */

@media (max-width: 767px) {
  /* One row: attribution takes the left, Site Plan holds the right. The
     attribution is width-capped so it cannot wrap the row and push Site
     Plan onto a line of its own. */
  /* The dock centres its children and wraps. For the attribution and the
     Site Plan control to share ONE row, flush to the composer edges, the
     attribution must GROW to fill (flex: 1 1 0) rather than size to its
     text; otherwise it floats centred and indented. And Site Plan must not
     carry margin-left: auto, which resolved to 265px and pushed itself onto
     a line of its own. Measured, not guessed: before this the attribution
     sat at x=59 against the composer at x=16, on a different row. */
  body.aios-variant-m2.aios-active .aios-dock .aios-attribution {
    order: 5 !important;
    flex: 1 1 0 !important;
    min-width: 0 !important;
    max-width: none !important;
    margin: 0 !important;
    text-align: left !important;
  }
  body.aios-variant-m2.aios-active .aios-dock .attr-unit {
    width: 100% !important;
    max-width: none !important;
  }
  body.aios-variant-m2.aios-active .aios-footer-toggle {
    order: 6 !important;
    flex: 0 0 auto !important;
    margin: 0 0 0 var(--m2-space-3) !important;
  }
  /* Attribution shown in full rather than behind a disclosure chevron. */
  body.aios-variant-m2 .aios-dock .attr-unit {
    align-items: flex-start !important;
    text-align: left !important;
  }
  body.aios-variant-m2 .aios-dock .attr-expanded {
    display: block !important;
    font-size: 9.9px !important;
    line-height: var(--m2-legal-lh) !important;
    margin: 0 !important;
    padding: 0 !important;
    text-align: left !important;
  }
  /* Keep the "Powered by Gallea Ai, IBM Silver Partner" line: hiding the
     collapsed button dropped the attribution itself, leaving only the IBM
     sentence. It stays visible as a static line; only the disclosure
     chevron goes, since nothing is hidden any more. */
  body.aios-variant-m2 .aios-dock .attr-collapsed {
    padding: 0 !important;
    text-align: left !important;
    cursor: default !important;
  }
  body.aios-variant-m2 .aios-dock .attr-chevron { display: none !important; }
  body.aios-variant-m2 .aios-dock .attr-collapsed-label {
    font-size: var(--m2-legal-size) !important;
  }
  /* Site Plan keeps its quiet label but gains a real target, which the
     deck's own drawing does not have and which it needs as primary
     navigation. */
  body.aios-variant-m2 .aios-footer-toggle {
    font-size: 9.9px !important;
    min-height: 44px !important;
    display: inline-flex !important;
    align-items: center;
  }
}

/* The dots belong to the starter carousel and must disappear with it.
   The engine hides .welcome-actions once the canvas has messages, but the
   dots are a SIBLING of that row (inserted after it), so they survived into
   the conversation and rendered as six stray dots under the masthead.
   Caught by looking at the A/B contact sheet, not by any probe: the dots
   existed and were "correct", just in the wrong place. */

@media (max-width: 767px) {
  body.aios-variant-m2.aios-active .aios-dock {
    row-gap: var(--m2-space-6) !important;
    padding-bottom: calc(var(--m2-space-3) + env(safe-area-inset-bottom, 0px)) !important;
  }
}

/* The Gallea Ai + IBM Silver Partner lockup on the landing screen and in
   the footer (deck p2). Sized for phone; the sentence sits beneath it. */
@media (max-width: 767px) {
  body.aios-variant-m2 .aios-attribution-marks {
    display: flex !important;
    /* Row, not column. Something upstream sets column direction, which
       stacked the two marks; the deck (p2) sets them side by side and they
       total only 163px in a 358px container, so there is ample room. */
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    align-items: center !important;
    justify-content: center !important;
    gap: var(--m2-space-3) !important;
    margin: 0 0 var(--m2-space-2) !important;
  }
  body.aios-variant-m2 .aios-attribution-marks .attr-mark-gallea { height: 20px !important; width: auto !important; }
  body.aios-variant-m2 .aios-attribution-marks .attr-mark-ibm { height: 23.4px !important; width: auto !important; }
}

/* The language dropdown opens UPWARD (bottom: calc(100% + 6px)), which
   works in the current design because its trigger sits in the masthead
   with the whole screen below it. In Mobile V2 the trigger moved inside
   the first card, where the deck puts it, so opening upward ran the list
   off the top of the screen behind the masthead and clipped it.

   Opened downward instead: below the trigger there is the rest of the
   card and the whole second card, so a 13-language list has room. Height
   is capped and the list scrolls internally rather than pushing the card
   apart, and the panel is width-bounded to the card. */
@media (max-width: 767px) {
  body.aios-variant-m2 #toggleScreen .lang-dropdown {
    bottom: auto !important;
    top: calc(100% + var(--m2-space-2)) !important;
    max-height: min(46vh, 320px) !important;
    overflow-y: auto !important;
    -webkit-overflow-scrolling: touch;
    width: max-content !important;
    max-width: min(88vw, 320px) !important;
    border-radius: var(--m2-radius-card) !important;
  }
  body.aios-variant-m2 #toggleScreen .lang-dropdown-item {
    min-height: 44px !important;
    display: flex !important;
    align-items: center;
    font-size: var(--m2-body-size) !important;
  }
}

/* =============================================================
   The composer, matched to the deck's own drawing of it.
   Owner review, 2026-07-27, against the deck's chat box:
     "there is no Tap to speak ... the Ask Max anything is aligned
      in top [left], we need to make sure that is implemented"
   ============================================================= */
@media (max-width: 767px) {
  /* No "Tap to speak". The deck's composer has no microcopy above the
     mic; the label is injected by ensureMicMicrocopy() in init.js for
     the current design. The mic icon is self-explanatory and the deck
     leaves it to speak for itself. */
  body.aios-variant-m2 .composer .mic-microcopy,
  body.aios-variant-m2 .aios-dock .mic-microcopy { display: none !important; }

  /* The placeholder sits at the TOP of the box, flush with the same left
     edge as the version label beneath it. It was indented a further 20px
     and vertically centred inside a 49px input, so it read as floating in
     the middle rather than anchored to the top corner. */
  body.aios-variant-m2 .composer .composer-input-row {
    align-items: flex-start !important;
    padding: 0 !important;
    margin: 0 !important;
    width: 100%;
  }
  /* #messageInput as well as .input-text. plate.js targets BOTH, and the
     element carries that id, so its ID selector (1,2,0) outranked a
     class-only rule however many classes were stacked on it. This is why
     an earlier attempt at padding:0 !important silently did nothing while
     min-height from the same block applied. */
  body.aios-variant-m2 .composer .input-text,
  body.aios-variant-m2 .composer #messageInput {
    padding: 0 !important;
    margin: 0 !important;
    height: auto !important;
    min-height: 0 !important;
    line-height: var(--m2-body-lh) !important;
    align-self: flex-start !important;
  }
  /* The rail carries a 20px left padding, which pushed the version label
     out of alignment with the placeholder above it. The deck has both on
     the same left edge. */
  body.aios-variant-m2 .composer .composer-rail {
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  body.aios-variant-m2 .composer .composer-version {
    margin-left: 0 !important;
    padding-left: 0 !important;
  }
}

/* =============================================================
   THE m2 ATTRIBUTION, ONE LINE. Owner ruling, 4 August 2026, walked on a real
   phone at 390. NOT ADAM'S CODE.

   The ruling supersedes the locked-chrome rule FOR THE m2 VARIANT ONLY. The
   text is written once, in mobile2.js; this block is only its geometry.

   Adam's rules above expand the engine's collapsed two-part widget in place, on
   the assumption that his own JS had already replaced it with a single
   sentence. His JS is not here (it wrote to the attribution, which was out of
   scope when this branch was reapplied), so those rules were expanding a widget
   that was never replaced, and the result was the column the owner walked. The
   widget's parts no longer exist once mobile2.js has run, so the rules below
   set the geometry of the one line that does.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2 .aios-attribution {
    display: block !important;
    /* Its own full-width row in the wrapping dock. Without a basis the flex
       container squeezed it to 46px while the text kept its natural width and
       painted outside the box, which is how one line still read as a collision
       with the links beside it. Measured: box 46px, text about 244px. */
    flex: 0 0 100% !important;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 0 !important;
    height: auto !important;
    margin: 0 !important;
    padding: 0 !important;
    text-align: left !important;
    /* One line at 390, per the ruling. The string measures about 244px at the
       legal step, inside a 358px content box, so nowrap cannot clip it here and
       makes the no-wrapping requirement structural rather than incidental. */
    white-space: nowrap !important;
    overflow: visible !important;
  }
  /* Specificity, not order. Adam's dock rule above is
     body.aios-variant-m2.aios-active .aios-dock .aios-attribution, which carries
     flex: 1 1 0 !important and outranks a two-selector rule however late it
     appears. Matched here so the full-width row actually applies in the dock. */
  body.aios-variant-m2.aios-active .aios-dock .aios-attribution {
    flex: 0 0 100% !important;
    width: 100% !important;
    max-width: 100% !important;
    text-align: left !important;
    white-space: nowrap !important;
  }
  body.aios-variant-m2 .m2-attribution-line {
    display: block !important;
    font-size: 9.9px !important;
    line-height: var(--m2-legal-lh) !important;
    color: var(--m2-muted) !important;
    white-space: nowrap !important;
  }
  /* The widget's parts are gone once mobile2.js has run. These keep the frame
     between first paint and that first pass from showing the old column. */
  body.aios-variant-m2 .aios-attribution .attr-unit,
  body.aios-variant-m2 .aios-attribution .attr-collapsed,
  body.aios-variant-m2 .aios-attribution .attr-expanded,
  body.aios-variant-m2 .aios-attribution .attr-chevron,
  body.aios-variant-m2 .aios-attribution .attr-collapsed-label,
  body.aios-variant-m2 .aios-attribution .aios-attribution-marks { display: none !important; }
}

/* =============================================================
   THE m2 FOOTER ROW. Owner ruling, 4 August 2026, walked at 390. NOT ADAM'S.

   Ruled: "Lay the footer out so nothing overlaps at 390: attribution, then the
   two links, clear of each other."

   The dock is a centred wrapping flex row holding, in DOM order, the composer,
   then .powered-by-gallea.aios-attribution, then .aios-footer-toggle (Site
   Plan), then a.aios-traditional-link (Return to traditional website). Adam's
   rules put the attribution and Site Plan on one shared row at order 5 and 6,
   which is what pushed the return link into the attribution's ink.

   Three explicit orders instead, so the stack is stated rather than emergent:
   the attribution takes a full row of its own, then the two links share the row
   below it, pushed to opposite ends by an auto margin.

   The gutter is applied to the children, not to the dock, deliberately: the
   composer is also a child of this dock at flex 0 0 100%, and padding on the
   dock would have moved it too, which is not what was ruled.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2.aios-active .aios-dock .aios-attribution {
    order: 5 !important;
    padding-inline: var(--m2-gutter) !important;
  }
  body.aios-variant-m2.aios-active .aios-dock .aios-traditional-link {
    order: 6 !important;
    flex: 0 0 auto !important;
    margin-inline-start: var(--m2-gutter) !important;
    /* Pushes Site Plan to the far end of the same row. At 320 the pair is wider
       than the viewport and wraps to two rows, which is still clear of each
       other and still in the ruled order. */
    margin-inline-end: auto !important;
  }
  body.aios-variant-m2.aios-active .aios-dock .aios-footer-toggle {
    order: 7 !important;
    flex: 0 0 auto !important;
    margin: 0 !important;
    margin-inline-end: var(--m2-gutter) !important;
  }
}

/* =============================================================
   THE m2 ENTRY GATE CARDS, SIZED TO THEIR CONTENT. Owner ruling, 4 August 2026,
   walked at 390. NOT ADAM'S CODE.

   Ruled: each card sized to its content plus padding, both fitting on one
   390-wide screen without scrolling.

   THE CAUSE WAS NOT IN THIS FILE AND NOT IN ADAM'S WORK.
   engine/app/opening-lock.css carries, at every width and with no media query:


@media (max-width: 767px) {
  body.aios-variant-m2 #toggleScreen .toggle-card {
    height: auto !important;
    min-height: 0 !important;
    max-height: none !important;
    overflow: visible !important;
  }
  /* The unclassed flex-grow spacer the engine puts before the Choose button.
     Its whole job is to push that button to the bottom of a fixed-height card,
     and it measured 195px of empty brand colour inside card two. With the card
     sized to content there is no free space for it to take, but it is pinned to
     zero rather than left to depend on that. */
  body.aios-variant-m2 #toggleScreen .toggle-card > div:not([class]) {
    flex: 0 0 auto !important;
    min-height: 0 !important;
  }
}

/* =============================================================
   THE m2 ENTRY GATE SCROLLS. Owner ruling, 4 August 2026. Blocker.

   styles.css carries, for every v2 tenant:
     [data-visual="v2"] #toggleScreen:not(.hidden) {
       position: fixed; inset: 0; z-index: 500; overflow: hidden;
       display: flex; align-items: center; }
   Fixed to the viewport AND overflow:hidden, so anything taller than the
   screen is unreachable: no scroll, no way to the second card's button.

   TWO THINGS HAD TO CHANGE, NOT ONE. Turning overflow to auto is not enough
   while the container is a centred flex box: a flex item taller than its
   centring container overflows equally in BOTH directions, and the overflow
   above the top edge cannot be scrolled to. So the box becomes a plain block
   with its own padding, and the stage is released from max-height.

   m2 only, by specificity plus !important. The default gate keeps its fixed,
   non-scrolling, centred geometry on every tenant.
   ============================================================= */
@media (max-width: 767px) {
  /* :not(.hidden) ONLY, and this bit is load bearing. A first pass also listed
     the bare `body.aios-variant-m2 #toggleScreen`, whose display:block
     !important beat the .hidden rule that takes the gate away once the visitor
     enters AiOS. The gate stayed on screen underneath the conversation with the
     composer painted across it. Caught by looking at the render, not by any
     measurement in this file. */
  body.aios-variant-m2 #toggleScreen:not(.hidden) {
    overflow-y: auto !important;
    overflow-x: hidden !important;
    -webkit-overflow-scrolling: touch !important;
    display: block !important;
    align-items: flex-start !important;
    padding: var(--m2-space-4) 0 var(--m2-space-6) !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-screen {
    max-height: none !important;
    height: auto !important;
  }
}

/* =============================================================
   THE m2 DOCK GIVES ITS SLACK BACK TO THE CARDS. Owner ruling, 4 August 2026:
   the composer sits too high over the starter cards.

   MEASURED FIRST, because the obvious reading was wrong. The dock is already
   flush to the bottom of the viewport at 844, 740, 667 and 600, and the starter
   card is not clipped at any of them. It is in flow, not floating over the
   canvas, so there was nothing to "move down". What was true is that the dock
   was taking 235px of a short screen, and at 667 the swipe dots under the card
   fell below the scroll box and vanished, which is what reads as the composer
   sitting on top of the cards.

   So the dock keeps its position and gives back its own padding instead. Only
   the dock's internal spacing changes; the composer, the links and the canvas
   are untouched.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2.aios-active .aios-dock {
    padding-top: var(--m2-space-2) !important;
    padding-bottom: var(--m2-space-2) !important;
    row-gap: var(--m2-space-2) !important;
  }
}

/* =============================================================
   NO VERSION MARK INSIDE THE m2 COMPOSER. Owner ruling, 4 August 2026.

   "reference AiOS 2.4" is a .composer-version span the engine puts in the
   composer's own rail, beside the mic. The top bar's pill is a different node,
   .aios-version-mark, and is deliberately not touched: the ruling is to remove
   it from the composer and leave the pill alone.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2 .composer .composer-version { display: none !important; }
}

/* =============================================================
   THE m2 COMPOSER IS ONE ROW. Owner ruling, 4 August 2026.

   The engine builds the composer as a flex COLUMN: an input row holding the
   textarea, then a rail below it holding the version mark, the mic and the
   send button. That second row is what read as an empty second line, and it
   cost 132px of a phone screen.

   One row instead: the textarea takes the space, the rail sits beside it, and
   the two controls come down from 44px to 40px. Measured 132px before.

   TAP TARGETS, stated rather than buried: 40px is under the 44px this design
   uses elsewhere for touch targets. The ruling asked for the controls to shrink
   proportionally with the composer, so 40px is the floor taken rather than
   anything smaller, and it is worth an owner glance if the variant ever ships.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2 .composer {
    /* Adam's rule above sets min-height: 132px deliberately, with a comment
       saying the height is what signals "you can say a lot here". The owner has
       now ruled the opposite, so the floor is released here rather than edited
       out of his block, which keeps the original intent readable. */
    min-height: 0 !important;
    height: auto !important;
    flex-direction: row !important;
    align-items: center !important;
    gap: var(--m2-space-2) !important;
    padding: 12px 12px 12px var(--m2-space-3) !important;
  }
  body.aios-variant-m2 .composer .composer-input-row {
    flex: 1 1 auto !important;
    min-width: 0 !important;
    height: auto !important;
    min-height: 0 !important;
    margin: 0 !important;
  }
  /* One line. The auto-grow script writes an inline height; an !important
     declaration in a stylesheet outranks a non-important inline style, so this
     holds without touching that script. */
  body.aios-variant-m2 .composer .input-text,
  body.aios-variant-m2 .composer #messageInput {
    height: 26px !important;
    min-height: 26px !important;
    max-height: 26px !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    overflow-y: hidden !important;
    resize: none !important;
  }
  body.aios-variant-m2 .composer .composer-rail {
    flex: 0 0 auto !important;
    height: auto !important;
    min-height: 0 !important;
    width: auto !important;
    margin: 0 !important;
    padding: 0 !important;
    gap: var(--m2-space-2) !important;
  }
  body.aios-variant-m2 .composer .mic-affordance,
  body.aios-variant-m2 .composer .input-mic,
  body.aios-variant-m2 .composer .input-send {
    width: 40px !important;
    min-width: 40px !important;
    height: 40px !important;
    min-height: 40px !important;
    flex: 0 0 auto !important;
  }
}

/* =============================================================
   THE m2 HAMBURGER IS VISIBLE. Owner ruling, 4 August 2026: the icon has a hit
   area but no glyph at 390.

   It was never missing, it was white on white. Adam's Stage 2 (b) rule above
   sets color: var(--m2-on-structure), which is #fff, and it is right for the
   masthead he drew: a brand-filled bar with a knockout logo. The bar this
   variant actually renders on today's tree is the light .aios-topbar that
   trail.js builds, so the glyph painted white on a near-white bar. The SVG
   strokes with currentColor, so the colour is the whole fix.

   Set to the structure colour, which resolves to the tenant's own --aios-blue,
   the same navy as the reference wordmark beside it. Not a hardcoded hex, so every
   tenant's hamburger matches its own mark.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2 .aios-topbar .navbar-hamburger,
  body.aios-variant-m2 .aios-topbar .navbar-hamburger svg {
    color: var(--m2-structure) !important;
    stroke: currentColor !important;
  }
}

/* =============================================================
   THE m2 GATE CARD HEADERS ARE HEADERS. Owner ruling, 4 August 2026.

   They were rendering as 12.24px uppercase labels, smaller than the 15px body
   text beneath them, so each card read as a caption above a paragraph rather
   than a titled choice.

   Two sources had to be answered. The size came from the m2 eyebrow step, which
   is the right token for a label and the wrong one for a title. The GEOMETRY
   came from engine/app/opening-lock.css, which pins the title to a two-line box
   with height, min-height and max-height all 62.4px plus a -webkit-line-clamp
   of 2, so the reserved box did not move when the type did. Both are answered
   here, m2 only; the lock is not edited and the default gate is untouched.

   Releasing the two-line box also gives height back, which fix 5 needs: card
   one's title is one line at this size, not two.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2 #toggleScreen .toggle-card-title {
    font-size: var(--m2-title-size, 20px) !important;
    line-height: 1.2 !important;
    font-weight: 700 !important;
    letter-spacing: 0.01em !important;
    text-transform: none !important;
    /* Released from the lock's fixed two-line box. */
    height: auto !important;
    min-height: 0 !important;
    max-height: none !important;
    overflow: visible !important;
    display: block !important;
    -webkit-line-clamp: none !important;
    margin-bottom: var(--m2-space-1) !important;
  }
}

/* =============================================================
   THE m2 GATE FITS WITHOUT SCROLLING. Owner ruling, 4 August 2026: both cards
   on one 390 screen at 844, 740 and 667, neither Choose button needing a
   scroll.

   MEASURED, NOT GUESSED. The gate stood at 803px against a 667px screen, so
   136px had to come out. Taken from spacing and body type only, in the ruling's
   own order, and nothing was taken from the card headers fix 4 just enlarged:

     gate padding      40 -> 20     20
     stage padding     72 -> 28     44
     logo + its margin 80 -> 52     28
     subhead margin    20 -> 10     10
     headline margin    8 ->  6      2
     card padding    2 x 20 -> 16   16
     card body      15 -> 13.5px    16
     gap between cards 32 -> 20     12
                                   ---
                                   148 available against 136 needed

   SCROLLING STAYS. The overflow-y: auto and the block display from the earlier
   gate fix are untouched, so a screen shorter than 667, or a tenant whose copy
   runs longer, still scrolls rather than trapping the visitor. This block only
   removes the NEED to scroll at the three ruled heights; it does not reintroduce
   the lock.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2 #toggleScreen:not(.hidden) {
    padding: var(--m2-space-2) 0 var(--m2-space-3) !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-screen {
    padding: var(--m2-space-3) var(--m2-gutter) !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-logo {
    height: 40px !important;
    margin-bottom: var(--m2-space-3) !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-logo img,
  body.aios-variant-m2 #toggleScreen .toggle-logo svg { height: 100% !important; width: auto !important; }
  body.aios-variant-m2 #toggleScreen .toggle-headline { margin-bottom: var(--m2-space-1) !important; }
  body.aios-variant-m2 #toggleScreen .toggle-subhead { margin-bottom: var(--m2-space-2) !important; }
  body.aios-variant-m2 #toggleScreen .toggle-cards { gap: var(--m2-space-4) !important; }
  body.aios-variant-m2 #toggleScreen .toggle-card { padding: var(--m2-space-3) var(--m2-space-3) !important; }
  /* Body type down one notch. The headers fix 4 set stay at 17px, so the gap
     between title and body widens rather than narrows. */
  body.aios-variant-m2 #toggleScreen .toggle-card-body {
    font-size: 13.5px !important;
    line-height: 1.45 !important;
  }
}

/* =============================================================
   MOBILE V2, ITEM 1: THE ENTRY GATE. Owner ruling, 4 August 2026 (evening),
   after the chairman reviewed the gate on a phone: "Everything's too big. It
   just seems crowded." The AiOS card stood 252px against the traditional card's
   150px, which is what read as broken.

   Ruled: both cards exactly the same height, both whole on one screen at 844,
   740 and 667 with no scrolling, the Gallea Ai wordmark and IBM attribution
   back on the gate as one small line at the bottom, and the card headers kept
   as headers.

   EQUAL HEIGHT IS STRUCTURAL, NOT A NUMBER. The card row becomes a single
   column grid with grid-auto-rows: 1fr, so the two cards are the same height
   because the grid makes them so, at every viewport, in every language, however
   long a tenant's copy runs. No height is written down anywhere, so nothing
   here can go stale.

   WHAT PAID FOR IT, measured at 667 before the change: the header block took
   185px, the cards 418px, and the last card ended at 603px with 64px of slack.
   Equalising costs about 100px, and the attribution line costs about 30px more,
   so roughly 66px had to come out of the two cards. Taken entirely from empty
   space and control heights, in this order, and nothing was taken from the card
   titles or from the body copy:

     card padding      12 -> 10 both sides      8
     stack gap between card children 8 -> 6     8
     language button   48 -> 42                 6
     Choose button     48 -> 44 x 2             8
     Choose top margin  8 -> 4  x 2             8
     picker top margin  8 -> 2                  6
     gap between cards 16 -> 12                 4
     logo              40 -> 34                 6
     logo margin       12 -> 8                  4
     headline line-height 1.25 -> 1.18          8
                                               ---
                                                66

   NO COPY IS TRIMMED. The mission allows it, but the gate's body text lives in
   the tenant's own i18n files, which this session may not touch, and cutting a
   sentence in CSS means clamping it mid-word. The space came out of the spacing
   instead, which is what the chairman actually asked for anyway.

   SCROLLING STAYS AVAILABLE. The overflow-y: auto from the earlier gate fix is
   untouched, so a shorter screen, or a tenant whose copy runs longer, still
   scrolls rather than trapping the visitor. This block removes the NEED to
   scroll at the three ruled heights.
   ============================================================= */
@media (max-width: 767px) {
  /* Equal height, structurally. */
  body.aios-variant-m2 #toggleScreen .toggle-cards {
    display: grid !important;
    grid-template-columns: 1fr !important;
    grid-auto-rows: 1fr !important;
    gap: 10px !important;
  }
  /* The cards are grid items now, so they must still lay their own children out
     as a column with the Choose button held at the bottom. */
  body.aios-variant-m2 #toggleScreen .toggle-card {
    display: flex !important;
    flex-direction: column !important;
    gap: 6px !important;
    padding: 8px var(--m2-space-3) !important;
    height: auto !important;
    min-height: 0 !important;
  }
  /* The unclassed spacer the engine puts before card two's Choose button now has
     free space to take again, and taking it is exactly right: it holds the
     button at the bottom of the taller card, so the two Choose buttons are the
     only thing at the foot of each card. */
  body.aios-variant-m2 #toggleScreen .toggle-card > div:not([class]) {
    flex: 1 1 auto !important;
    min-height: 0 !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-card-button {
    min-height: 44px !important;
    margin-top: var(--m2-space-1) !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-language-picker {
    margin-top: 2px !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-language-button {
    min-height: 42px !important;
  }
  /* The header block above the cards gives back a little of what the cards
     need, without touching the headline's size or the subhead's. */
  body.aios-variant-m2 #toggleScreen .toggle-logo {
    height: 34px !important;
    margin-bottom: var(--m2-space-2) !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-headline {
    line-height: 1.18 !important;
  }
  /* The last 8px, found by measuring at 667 rather than by arithmetic: the gate
     stood 5px over its own screen with everything above already trimmed. */
  body.aios-variant-m2 #toggleScreen:not(.hidden) {
    padding: var(--m2-space-2) 0 var(--m2-space-2) !important;
  }
  body.aios-variant-m2 #toggleScreen .toggle-subhead {
    margin-bottom: var(--m2-space-1) !important;
  }

  /* The attribution line, built by mobile2.js from the engine's own marks.
     One line, small, centred, below both cards and clear of them. */
  /* Inline flow, not flex. A flex row puts its gap between every child, which
     set the sentence's own full stop 5px away from the wordmark and made it
     read as a stray dot. Inline keeps "Powered by [wordmark]." tight and the
     IBM mark spaced by its own margin. */
  body.aios-variant-m2 #toggleScreen .m2-gate-attribution {
    display: block !important;
    text-align: center !important;
    margin: var(--m2-space-2) 0 0 !important;
    font-size: 10px !important;
    line-height: 1.2 !important;
    color: var(--m2-muted) !important;
    white-space: nowrap !important;
  }
  body.aios-variant-m2 #toggleScreen .m2-gate-attribution .attr-mark-gallea {
    height: 9px !important; width: auto !important;
    display: inline-block !important; vertical-align: middle !important;
    margin: 0 1px !important;
  }
  body.aios-variant-m2 #toggleScreen .m2-gate-attribution .attr-mark-ibm {
    height: 15px !important; width: auto !important;
    display: inline-block !important; vertical-align: middle !important;
    margin-left: 8px !important;
  }
}

/* =============================================================
   MOBILE V2, ITEM 2: THE TOP BAR, ON EVERY AiOS SCREEN. Owner ruling,
   4 August 2026 (evening). "Somewhere in the top masthead has to be reference."
   "Do we really need reference AiOS 2.4 at all on this?"

   Ruled: the reference mark top left on every screen, the decision indicator
   immediately right of it, the version pill gone, "Traditional Website" in that
   space, the hamburger visible, and all four fitting at 390 with real gaps.

   WHY THE MARK WAS MISSING, measured rather than assumed. It is not hidden by a
   stylesheet. trail.js measure() writes visibility:hidden and maxWidth:0px
   INLINE on .aios-topbar-brand whenever the breadcrumb has ink and there is
   less than MIN_MARK_PX of room before the trail's first ink. At 390 there
   never is, because .trailslot is position:absolute across the WHOLE bar and
   its row starts at the content gutter, to the left of the mark. So on every
   screen with a decision in it, the mark yielded the entire slot. That code is
   right for the design it was written for, and it is not this session's file.

   THE FIX IS TO STOP THE TWO FIGHTING OVER ONE STRIP, m2 only. The trail stops
   being an absolute overlay and becomes a normal flex item BETWEEN the brand
   and the controls, which is also exactly where the ruling puts it: immediately
   right of the logo. With the trail no longer lying across the mark there is
   nothing for the mark to give way to, so the inline cap is overridden and the
   mark stays. Neither trail.js nor styles.css is edited; both keep their
   behaviour on every default build.

   THE ROW AT 390, measured after: 12px inset, a 24px mark, the breadcrumb
   taking what is left and truncating into it, the Traditional Website link, a
   40px hamburger, 12px inset. Nothing is flush to an edge.
   ============================================================= */

@media (max-width: 767px) {
  body.aios-variant-m2 .aios-dock .aios-footer-toggle,
  body.aios-variant-m2.aios-active .aios-dock .aios-footer-toggle,
  body.aios-variant-m2 .aios-dock .aios-traditional-link,
  body.aios-variant-m2.aios-active .aios-dock .aios-traditional-link {
    display: none !important;
  }
  /* The dock's padding was sized for two rows under the composer. With nothing
     below it, the composer sits on its own with even space around it. */
  body.aios-variant-m2.aios-active .aios-dock {
    padding-bottom: calc(var(--m2-space-3) + env(safe-area-inset-bottom, 0px)) !important;
    row-gap: var(--m2-space-2) !important;
  }
}

/* =============================================================
   MOBILE V2, ITEM 4: THE SIX STARTING CARDS. Owner ruling, 4 August 2026
   (evening): "Get rid of the copy underneath and just have the six buttons."
   Icon and bold header only, two columns by three rows, all six visible, no
   scrolling and no carousel, sized to fit at 667 with the greeting and the bar.

   THIS REVERSES ADAM'S ONE DELIBERATE EXCEPTION, and it is worth saying why
   rather than quietly overwriting it. His Stage 2 (d) block above makes the
   starter journeys the only horizontally scrolling row in the product, on the
   reasoning that six full-width cards WITH their descriptions would push the
   composer below the fold. The premise was the descriptions. With those gone
   the six fit on one screen as a grid, so the exception it paid for is no
   longer needed, and the dots that signalled the swipe go with it. His rules
   are left in place and overridden here, so the original intent stays readable.

   The dot row is also stood down in mobile2.js, so nothing builds it at all.
   ============================================================= */
@media (max-width: 767px) {
  body.aios-variant-m2 .welcome-actions {
    display: grid !important;
    grid-template-columns: 1fr 1fr !important;
    grid-auto-rows: auto !important;
    gap: 10px !important;
    /* The row is a flex child of the welcome block and was growing to fill it:
       measured 656px tall, which the grid then shared out as three 212px rows,
       so the cards stayed carousel-sized long after the carousel was gone.
       Sizing to content and packing the rows at the top is what makes the six
       buttons small. */
    flex: 0 0 auto !important;
    height: auto !important;
    align-content: start !important;
    /* No scroller, no snap, no full-bleed negative margins: all three belong to
       the carousel this replaces. */
    overflow: visible !important;
    scroll-snap-type: none !important;
    margin: 0 !important;
    padding: 0 !important;
  }
  body.aios-variant-m2 .welcome-action {
    flex: none !important;
    width: auto !important;
    max-width: none !important;
    min-height: 0 !important;
    scroll-snap-align: none !important;
    /* The card resolved to 212px tall against 80px of content, which then set
       the grid's row height, which is what kept the six buttons carousel-sized.
       Releasing the height is the whole fix; the grid's own stretch still makes
       the two cards in a row match each other. */
    height: auto !important;
    align-items: flex-start !important;
    justify-content: flex-start !important;
    gap: var(--m2-space-2) !important;
    padding: var(--m2-space-3) !important;
  }
  /* The copy underneath. Hidden rather than deleted, because it is the button's
     accessible description in the shell's own markup and the default build
     still uses it. */
  body.aios-variant-m2 .welcome-action-sub { display: none !important; }
  body.aios-variant-m2 .welcome-action-icon {
    width: 28px !important;
    height: 28px !important;
  }
  body.aios-variant-m2 .welcome-action-icon svg { width: 18px !important; height: 18px !important; }
  body.aios-variant-m2 .welcome-action-icon img { width: 100% !important; height: 100% !important; object-fit: contain; display: block; }
  /* The header stays a header: bold, and it WRAPS. Two columns at 390 leave
     about 160px of measure, and half these labels are three words, so nowrap
     with an ellipsis here would silently cut them, in English before any
     other language. */
  body.aios-variant-m2 .welcome-action-title {
    font-size: 14px !important;
    font-weight: 700 !important;
    line-height: 1.25 !important;
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
  }
  /* The carousel's dots. Nothing builds them any more; this is the belt to that
     braces, so a cached mobile2.js can never leave a stray row of six dots
     under the grid. */
  body.aios-variant-m2 .m2-dots { display: none !important; }
}

/* =============================================================
   MOBILE V2, ITEM 5: EVERYTHING IS TOO BIG. Owner ruling, 4 August 2026
   (evening), reading the chairman: "Everything's too big. It just seems
   crowded. It seems overwhelming." He named the "11 RENTAL BUILDINGS" pill,
   which wrapped onto two lines inside itself, and the suggested next steps.

   MEASURED ON THE REAL ANSWER, NOT ON A MOUNTED COMPONENT. The catalogue
   rendered inside an answer is 60px narrower than the same catalogue mounted on
   its own, which is exactly why the pill wrapped for him and not in an earlier
   probe. One live turn was driven through the product's own path and its
   rendered canvas saved to fixtures/reference/rent-answer.html; every measurement
   here is off that surface, replayed.

   BEFORE, at 390 (reports/mobile-v2/before-answer.json):

     "11 RENTAL BUILDINGS" pill        9.5px, 2 lines, 35px tall
     "HOMES & COMMUNITIES" heading     21px,  2 lines, 63px tall
     answer prose                      16px,  5 lines, 128px for one paragraph
     filter pills                      12px,  30px tall
     card image                        aspect 5/3, 168px tall
     card name / address / status      15px / 11.5px / 11.5px
     suggested next step chips         13px,  44px tall each
     chrome above the first result     278px

   These are all one class of change: type comes down one step, and the space
   between things stays or grows. Nothing is removed and no wording changes.
   ============================================================= */
