/* Portrait My Account: mobile responsive layer
 *
 * One home for every mobile style in the customer portal. See
 * RESPONSIVE_UI_STANDARDS.md in the repository root for the rules this file
 * implements and how to use the utilities it provides.
 *
 * THE DESKTOP RULE. Everything here sits inside
 * `@media (max-width: 1023.98px)`. At 1024px and above nothing applies, so
 * desktop rendering is exactly what it was before this file existed.
 *
 * Customers use this on phones more than anywhere else, so this layer is the
 * stricter of the two. Tokens only: colours come from --pos-* in
 * portal_tokens.css. No new palette, no new fonts.
 *
 * The portal already ships some narrow-width rules (720px and 620px
 * breakpoints in portal.css). Those are left alone; this file adds the
 * foundations rather than rewriting them.
 */

@media (max-width: 1023.98px) {

  /* ---------------------------------------------------------------- *
   * 1. No horizontal panning, ever.
   * ---------------------------------------------------------------- */

  /* No overflow-x: hidden here. It stops the page panning but it also clips
     content that is genuinely too wide, so a broken layout looks fine to the
     automated check while the user simply cannot see the right-hand column.
     Overflow is fixed at its source instead. */
  html, body {
    max-width: 100%;
  }

  .pt-page {
    /* break-word, never `anywhere`: `anywhere` feeds into intrinsic sizing,
       so a flex row will squeeze a text child to one character wide and print
       it vertically. This breaks only a word that would actually overflow. */
    overflow-wrap: break-word;
  }

  .pt-page img,
  .pt-page svg,
  .pt-page video,
  .pt-page canvas,
  .pt-page iframe {
    max-width: 100%;
    height: auto;
  }

  /* ---------------------------------------------------------------- *
   * 2. Header: sticky, safe-area aware, and never taller than it needs.
   *
   * The nav strip already scrolls horizontally as one row and must keep
   * doing exactly that. What it gains here is a proper touch height and an
   * inset that clears the notch.
   * ---------------------------------------------------------------- */

  /* The header carries a wordmark, the nav, a bell, a company picker, a theme
     button, the user name and a sign-out form. That is more than fits on one
     390px row, and forcing one row pushed the nav and sign-out off the right
     edge. It wraps: the controls stay on the first row and the nav takes the
     full width of a second row, where it still scrolls as one line. */
  .pt-header {
    position: sticky;
    top: 0;
    z-index: 900;
    flex-wrap: wrap;
    height: auto;
    row-gap: 0.375rem;
    gap: 0.375rem;
    padding-top: calc(0.5rem + env(safe-area-inset-top));
    padding-left: max(0.5rem, env(safe-area-inset-left));
    padding-right: max(0.5rem, env(safe-area-inset-right));
  }

  /* The company name and the signed-in user are the first things to give up
     room; the controls beside them are not. */
  .pt-header > .pt-user,
  .pt-header > .pt-company {
    max-width: 10ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .pt-wordmark .sub {
    display: none;          /* "My Account" is implied by the page itself */
  }

  /* The nav is the flexible part of the header: it takes what room is left
     and scrolls its own links, so the sign-out control and the bell stay on
     screen instead of being pushed off the right edge. flex-basis 0 with
     min-width 0 is what lets it shrink below its content width. */
  .pt-nav {
    flex: 1 1 100%;
    order: 10;              /* below the control row, whatever the source order */
    min-width: 0;
    max-width: 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin-left: 0;
  }

  .pt-spacer {
    display: none;
  }

  /* Everything else in the header keeps its size and stays put. */
  .pt-header > form,
  .pt-header > .pt-iconbtn,
  .pt-header > .pt-bell,
  .pt-header > .pt-wordmark {
    flex: 0 0 auto;
  }

  /* The assistant widget is fixed chrome and must not widen the page. */
  #pixel-assistant,
  .pixel-assistant,
  #pixel-widget {
    max-width: 100vw;
    right: max(0.75rem, env(safe-area-inset-right));
    left: auto;
  }

  .pt-nav::-webkit-scrollbar {
    display: none;
  }

  /* flex: 0 0 auto is load-bearing. As flex items these links shrink by
     default, and in a 390px row they were being squeezed to 44px each, so
     their labels overflowed and ran into one another. They keep their natural
     width and the row scrolls, which is the single-row rule working. */
  .pt-nav a,
  .pt-nav .pt-dd > summary {
    flex: 0 0 auto;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }

  /* A landlord account's grouped dropdown.
     base.html sets `.pt-nav { overflow: visible }` for landlord accounts so
     this menu can escape the nav. That also stops the nav scrolling, so at
     360 and 375 the whole strip pushed the page 16 to 31px sideways.
     Both needs are met by scrolling the nav again and lifting the menu out of
     it entirely, as a bottom sheet: no clipping, no page overflow, and a menu
     a thumb can actually reach without stretching to the top of the screen. */
  html body .pt-nav {
    overflow-x: auto !important;
    overflow-y: hidden;
  }

  /* Scoped to [open]. Taking the menu out of flow unconditionally made the
     CLOSED menu visible: <details> hides its children by display, and a
     fixed-position child escaped that, so every landlord page rendered the
     dropdown contents pinned to the bottom of the screen. */
  html body .pt-dd[open] > .pt-dd-menu {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    width: auto;
    min-width: 0;
    max-width: 100vw;
    max-height: 70dvh;
    overflow-y: auto;
    border-radius: 14px 14px 0 0;
    padding: 0.5rem 0.75rem calc(0.75rem + env(safe-area-inset-bottom));
    box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.45);
    z-index: 1200;
  }

  .pt-dd-menu a {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  /* The staff preview banner is fixed chrome; it must not cover the header. */
  .pt-preview-banner {
    flex-wrap: wrap;
    gap: 0.25rem;
    padding-top: calc(0.375rem + env(safe-area-inset-top));
    font-size: 0.8125rem;
  }

  /* ---------------------------------------------------------------- *
   * 3. Page content and safe areas.
   * ---------------------------------------------------------------- */

  .pt-page {
    /* The assistant launcher is fixed over the bottom right corner, so the
       page needs room underneath its last line of content. */
    padding: 0.75rem max(0.75rem, env(safe-area-inset-left))
             calc(4.5rem + env(safe-area-inset-bottom))
             max(0.75rem, env(safe-area-inset-right));
  }

  .pt-grid,
  .pt-grid-2,
  .pt-grid-3,
  .pt-cards,
  .pt-stats,
  .pt-kpis {
    grid-template-columns: 1fr !important;
  }

  .pt-row,
  .pt-actions,
  .pt-toolbar {
    flex-wrap: wrap;
  }

  /* ---------------------------------------------------------------- *
   * 4. Touch targets: 44 x 44 minimum.
   * ---------------------------------------------------------------- */

  .pt-btn,
  .pt-iconbtn,
  .pt-schedule,
  button,
  input[type="submit"],
  input[type="button"],
  .pt-page a.pt-btn {
    min-height: 44px;
  }

  .pt-iconbtn {
    min-width: 44px;
  }

  /* Found by the Phase 1 sweep: these appear on every route, so each one is a
     defect repeated 39 times. */
  .pt-bell,
  .pt-schedule,
  .pt-user,
  .pt-header > a {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* A journey step is a tap target, not a label. */
  .pt-jstep {
    min-height: 44px;
    display: flex;
    align-items: center;
  }


  /* Standalone links are controls and need a thumb-sized target.
     "Standalone" means a direct child of a layout container rather than a link
     inside a sentence: prose links live in p, span or td and are deliberately
     not matched, so body text keeps flowing normally. */
  .pt-page div > a,
  .pt-page li > a,
  .pt-page nav > a,
  .pt-page h1 > a,
  .pt-page h2 > a,
  .pt-page h3 > a,
  .pt-page strong > a,
  .pt-panel a,
  td > a,
  th > a {
    min-height: 44px !important;
    min-width: 44px;
    white-space: normal;
    overflow-wrap: break-word;
  }

  /* A link that is the only thing in its paragraph is an action, not prose.
     "Back to sign in" sitting alone in a <p> is a control and needs the
     target; a link inside a sentence still does not. */
  .pt-page p > a:only-child, .pt-authcard p > a:only-child {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* The received / not-available sliders must be usable by thumb. */
  .pt-slider,
  .pt-toggle,
  .pt-switch {
    min-width: 52px;
    min-height: 32px;
  }

  .pt-slider input,
  .pt-toggle input,
  .pt-switch input {
    min-height: 32px;
  }


  /* Controls need 44px in BOTH dimensions. A short label like "Edit" or "Add"
     produced a 39px wide control that was tall enough and still too small to
     hit. Named broadly because these classes appear across the estate. */
  /* Size only. Do NOT force a display here.
     A button is already inline-block and a summary is a list-item, so
     min-height works on both as they are. Forcing inline-flex turned their
     inline children into flex items, and flex items lose the whitespace
     between them: the stacking plan started reading "Unit AOccupied". */
  button,
  summary,
  .pt-btn,
  a.pt-btn,
  .ghost-button,
  .pt-iconbtn,
  .pt-chip,
  [role="button"] {
    min-height: 44px;
    min-width: 44px;
  }

  /* An anchor is the exception. It is a non-replaced inline element, where
     min-height has no effect at all, so it needs a display that gives it one.
     Anchors have no inline children to space out, so nothing is lost. */
  a.pt-btn,
  a.ghost-button,
  a[role="button"] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* A details summary is a control wherever it appears. */
  details > summary {
    min-height: 44px;
    display: flex;
    align-items: center;
  }


  /* A control must not be squeezed narrower than its own label.
     On the weekly review the "Mark review" button was compressed to its 44px
     minimum by the title beside it, so the label overflowed the card. Action
     clusters keep their width and the row wraps instead, which it already
     does under the inline-flex rule above. */
  /* A control squeezed by its neighbour wraps its label rather than clipping
     it. Refusing to shrink was the first attempt and it pushed pages sideways
     wherever a label was long. */
  button,
  .pt-btn,
  a.pt-btn,
  .ghost-button {
    white-space: normal;
    overflow-wrap: break-word;
    height: auto;
  }


  /* Pill-shaped badges used as links.
     Giving a small pill a 44px minimum height turns it into a tall ellipse,
     and with the text on its first line it sat at the top and escaped the
     curve, colliding with the field above it. A badge holds a single piece of
     text, so centring it with inline-flex is safe here: there are no sibling
     inline children whose whitespace could be dropped. */
  .pt-page a.pt-badge,
  .pt-page a.pos-chip,
  .pt-page a.pos-pill,
  .pt-page a.pt-chip,
  .pt-page a.pos-badge,
  .pt-page a.pt-badge {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    width: auto;
    /* The pill has to be wide enough for its own label, or the text sits
       proud of the rounded shape on both sides. */
    white-space: nowrap;
    padding-inline: 0.75rem;
    text-align: center;
  }


  /* ---------------------------------------------------------------- *
   * 5. Forms: no iOS zoom-jump.
   *
   * px deliberately, so no font preference can take an input back under the
   * 16px threshold at which iOS zooms on focus.
   * ---------------------------------------------------------------- */

  /* !important is deliberate and confined to this media query: component
     styles carry class-level specificity a bare element selector cannot beat,
     and 16px is an accessibility floor, not a preference. Below it iOS Safari
     zooms the page on focus. */
  input:not([type="checkbox"]):not([type="radio"]),
  select,
  textarea {
    font-size: 16px !important;
    /* !important on the height for the same reason as the font size: scoped
       component rules such as `.cuh-layout select` set an explicit 42px that a
       bare element selector cannot beat, and 44px is the floor. */
    min-height: 44px !important;
    min-width: 44px;
    max-width: 100%;
    /* Padding and border must count inside the width, or a select set to the
       container width overflows it by its own padding. */
    box-sizing: border-box;
  }

  textarea {
    min-height: 88px;
  }

  /* Drawn size unchanged, hit area expanded to 44px by a transparent border. */
  /* A checkbox is drawn at a comfortable 22px, not blown up to 44. A native
     control paints its background across the whole border box, so sizing the
     input to 44px renders a solid block. The hit area for a checkbox is its
     label, and the label rule below carries it. */
  html body input[type="checkbox"],
  html body input[type="radio"] {
    width: 22px !important;
    height: 22px !important;
    flex: none !important;
  }

  summary {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  /* The tap target for a checkbox is its label. .pt-check is the hook for a
     standalone checkbox that has no label text of its own. */
  html body label:has(> input[type="checkbox"]),
  html body label:has(> input[type="radio"]),
  html body .pt-check {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
  }

  /* A checkbox row where the label is a sibling (<div class="pt-check"><input id>
     <label for>) leaves the real target only as tall as one line of text. The
     label is what a tap lands on, so it fills the row. */
  html body .pt-check > label,
  html body .pt-check label {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  html body .pt-check {
    min-height: 44px;
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  td a,
  th a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* The header wraps on a phone, so its height is not a constant: 194px on
     Perspective and 163px on the portal, against the 5rem (80px) guess that
     was here before. A field scrolled into view was landing up to 114px
     behind it. The behaviour layer measures the real header and publishes it
     as --pt-sticky-h; the fallback only applies if that has not run yet. */
  input, select, textarea {
    scroll-margin-top: calc(var(--pt-sticky-h, 5rem) + 0.75rem);
  }

  /* A file input is how a customer sends a photo of a bill; it needs room. */
  input[type="file"] {
    width: 100%;
    padding: 0.625rem 0;
  }


  /* A flex item defaults to min-width:auto, which stops it shrinking below its
     own min-content. A label wrapping a select therefore refuses to go
     narrower than the select's intrinsic width and pushes out of its flex row,
     which is what took the contracts form 22px past the screen edge. */
  .pt-page label,
  .pt-page form label,
  .pt-page fieldset {
    min-width: 0;
    max-width: 100%;
  }

  /* A control inside a label fills it rather than setting its own width. */
  .pt-page label > select,
  .pt-page label > input,
  .pt-page label > textarea {
    width: 100%;
  }

  /* ---------------------------------------------------------------- *
   * 6. Tables: the same two strategies as Perspective.
   *    .pt-table-scroll wraps a wide table; .pt-table-cards stacks rows.
   * ---------------------------------------------------------------- */

  .pt-table-scroll {
    position: relative;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    max-width: 100%;
  }

  .pt-table-scroll > table {
    min-width: max-content;
  }

  .pt-table-scroll th:first-child,
  .pt-table-scroll td:first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--pos-panel);
  }

  .pt-table-scroll::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    width: 28px;
    height: 100%;
    pointer-events: none;
    background: linear-gradient(to right, transparent, var(--pos-bg));
    transition: opacity 0.15s ease;
  }

  .pt-table-scroll.is-scrolled-end::after {
    opacity: 0;
  }

  .pt-table-cards,
  .pt-table-cards thead,
  .pt-table-cards tbody,
  .pt-table-cards tr,
  .pt-table-cards td {
    display: block;
    width: 100%;
  }

  .pt-table-cards thead {
    display: none;
  }

  .pt-table-cards tr {
    margin: 0 0 0.625rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--pos-line);
    border-radius: 10px;
    background: var(--pos-panel);
  }

  .pt-table-cards td {
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.375rem 0;
    border: 0;
    text-align: right;
  }

  .pt-table-cards td::before {
    content: attr(data-label);
    flex: 0 0 42%;
    text-align: left;
    color: var(--pos-dim);
    font-weight: 600;
  }

  .pt-table-cards td:empty {
    display: none;
  }

  /* ---------------------------------------------------------------- *
   * 7. Modals, panels and iframes become full-screen sheets.
   *
   * Includes the Schedule a Call modal, the contract-signing iframe and the
   * unit drawer in Landlord Mode.
   * ---------------------------------------------------------------- */

  .pt-modal,
  .pt-drawer,
  .pt-sheet,
  .pt-overlay-panel,
  .pf-drawer {
    position: fixed;
    inset: 0;
    z-index: 1100;
    display: flex;
    flex-direction: column;
    width: 100vw;
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    border: 0;
    border-radius: 0;
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
    background: var(--pos-bg);
  }

  .pt-sheet-header,
  .pt-modal-header,
  .pt-drawer-header {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 0 0 auto;
    min-height: 52px;
    padding: 0.5rem 0.75rem;
    background: var(--pos-bg2);
    border-bottom: 1px solid var(--pos-line);
  }

  .pt-sheet-title,
  .pt-modal-title,
  .pt-drawer-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 1rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .pt-sheet-close,
  .pt-modal-close,
  .pt-drawer-close {
    flex: 0 0 44px;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--pos-text);
    font-size: 20px;
    line-height: 1;
  }

  .pt-sheet-body,
  .pt-modal-body,
  .pt-drawer-body {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 0.75rem;
  }

  /* The contract-signing iframe fills the sheet. Signing is by finger, so it
     needs the whole screen. */
  .pt-sheet-body > iframe,
  .pt-modal-body > iframe,
  .pt-drawer-body > iframe {
    width: 100%;
    height: 100%;
    min-height: 60dvh;
    border: 0;
  }

  .pt-sheet-actions {
    flex: 0 0 auto;
    display: flex;
    gap: 0.5rem;
    padding: 0.625rem 0.75rem calc(0.625rem + env(safe-area-inset-bottom));
    background: var(--pos-bg2);
    border-top: 1px solid var(--pos-line);
  }

  .pt-sheet-actions > * {
    flex: 1 1 auto;
    min-height: 44px;
  }

  body.pt-sheet-open {
    overflow: hidden;
  }

  /* ---------------------------------------------------------------- *
   * 8. Tab rows are one scrollable row and never wrap.
   * ---------------------------------------------------------------- */

  .pt-tabs,
  .pt-tabbar,
  .pt-subnav {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    position: relative;
  }

  .pt-tabs::-webkit-scrollbar,
  .pt-tabbar::-webkit-scrollbar,
  .pt-subnav::-webkit-scrollbar {
    display: none;
  }

  .pt-tabs > *,
  .pt-tabbar > *,
  .pt-subnav > * {
    flex: 0 0 auto;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    scroll-snap-align: start;
    white-space: nowrap;
  }

  /* ---------------------------------------------------------------- *
   * 9. The onboarding journey stepper.
   *
   * The dot centring, the state colours and the disconnected-label logic are
   * load-bearing and are NOT changed here. The stepper simply goes vertical
   * on a phone, which is the only way the labels stay readable.
   * ---------------------------------------------------------------- */

  .pt-stepper {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .pt-stepper .pt-step {
    display: grid;
    grid-template-columns: 32px 1fr;
    align-items: start;
    gap: 0.625rem;
    min-height: 44px;
    text-align: left;
  }

  /* The connector runs down the left instead of across. */
  .pt-stepper .pt-step-line {
    width: 2px;
    height: 100%;
    min-height: 18px;
    margin: 0 auto;
  }

  .pt-stepper .pt-step-dot {
    margin: 0 auto;
  }

  .pt-stepper .pt-step-label {
    padding-bottom: 0.75rem;
  }



  /* Inline flex rows wrap on a phone.
     Templates across the estate declare `style="display:flex"` for a row of
     badges, figures or actions sized for a desktop column. None of them wrap,
     so each one pushes the page sideways. The scrolling rows are excluded:
     a tab bar or sub-nav must stay on one line and scroll instead. */
  [style*="display:flex"]:not(.pt-tabs):not(.pt-tabbar):not(.pt-subnav):not(.pt-nav),
  [style*="display: flex"]:not(.pt-tabs):not(.pt-tabbar):not(.pt-subnav):not(.pt-nav) {
    flex-wrap: wrap !important;
    min-width: 0;
  }

  /* ---------------------------------------------------------------- *
   * 9b. Inline grids.
   *
   * 73 templates declare their columns in an inline style, typically
   * `repeat(auto-fit, minmax(300px, 1fr))` or `2fr 1fr`. Every one of them
   * assumes desktop width, and at 390px they are the single largest cause of
   * horizontal overflow. On a phone they all become one column.
   *
   * !important is unavoidable: an inline style beats any stylesheet rule
   * without it, and the alternative is editing 73 templates by hand.
   * ---------------------------------------------------------------- */

  /* minmax(0, 1fr), not 1fr. A grid track sized 1fr still refuses to go below
     its item's min-content width, and a grid item defaults to min-width:auto,
     so a single-column grid holding a wide panel still pushed the page 280px
     sideways. minmax(0, ...) lets the track shrink, and the min-width:0 below
     lets the item inside it shrink too. */
  [style*="grid-template-columns"] {
    grid-template-columns: minmax(0, 1fr) !important;
  }

  /* Grid and flex items default to min-width:auto and will not shrink below
     their own min-content. That is the single most common structural cause of
     sideways scroll on this estate. */
  /* Targeting the inline-style containers alone was not enough: plenty of
     grids and flex rows are declared in a class (.assist-grid, .cuh-layout),
     and CSS cannot select on computed display. So every ordinary container in
     the content area is made shrinkable. min-width:0 is a no-op for a block
     element, which already shrinks, and is exactly what a grid or flex item
     needs to stop refusing to go below its own min-content. max-width keeps a
     column-flex child from sizing itself to its content.

     Tables are deliberately not in this list: one inside .pos-table-scroll
     needs min-width:max-content to scroll. */
  .pt-page div,
  .pt-page section,
  .pt-page article,
  .pt-page aside,
  .pt-page li,
  .pt-page label,
  .pt-page form,
  .pt-page fieldset {
    min-width: 0;
    max-width: 100%;
  }

  /* Same reasoning for inline fixed widths and min-widths that exceed the
     screen. */
  /* Controls are excluded. Stripping an inline min-width off a text input in
     a flex row let it shrink to 28px, which is narrower than the thumb it is
     meant to be typed into. Containers shrink; controls keep their floor. */
  [style*="min-width"]:not(input):not(select):not(textarea):not(button):not(a) {
    min-width: 0 !important;
  }

  /* ---------------------------------------------------------------- *
   * 10. The AI assistant collapses to a launcher and opens as a sheet.
   * ---------------------------------------------------------------- */

  .pixel-launcher,
  .pt-assistant-launcher {
    width: 52px;
    height: 52px;
    right: max(0.75rem, env(safe-area-inset-right));
    bottom: calc(0.75rem + env(safe-area-inset-bottom));
  }

  .pixel-panel,
  .pt-assistant-panel {
    position: fixed;
    inset: 0;
    width: 100vw;
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    padding-bottom: env(safe-area-inset-bottom);
  }

  /* ---------------------------------------------------------------- *
   * 11. Landlord Mode surfaces.
   *
   * The stacking plan fits the viewport width and is pinch/drag capable.
   * Hover cards are unreachable on touch, so the tap card is what shows.
   * ---------------------------------------------------------------- */

  .pt-stacking-plan,
  .pt-plan-viewport {
    width: 100%;
    max-width: 100%;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    touch-action: pan-x pan-y pinch-zoom;
  }

  .pt-plan-unit {
    min-height: 44px;
  }

  /* A card that desktop shows on hover is shown on tap here. */
  .pt-hovercard {
    display: none;
  }

  .pt-hovercard.is-tapped {
    display: block;
    position: fixed;
    left: 0.75rem;
    right: 0.75rem;
    bottom: calc(0.75rem + env(safe-area-inset-bottom));
    z-index: 1050;
    max-height: 50dvh;
    overflow-y: auto;
  }

  /* Maps and floor plans fit the viewport. */
  .pt-map,
  #map,
  .map-container,
  .pt-floorplan {
    width: 100%;
    max-width: 100%;
    height: 60dvh;
    touch-action: pan-x pan-y pinch-zoom;
  }

  /* ---------------------------------------------------------------- *
   * 12. Calendar renders as the stacked agenda, as specced.
   * ---------------------------------------------------------------- */

  .pt-calendar-grid {
    display: block;
  }

  .pt-calendar-grid .pt-cal-week {
    display: block;
  }

  .pt-calendar-agenda {
    display: block;
  }

  .pt-cal-day {
    min-height: 44px;
  }
}

/* Landscape phones. */
@media (max-width: 1023.98px) and (orientation: landscape) and (max-height: 480px) {
  .pt-header {
    min-height: 44px;
  }

  .pt-map,
  #map,
  .map-container,
  .pt-floorplan {
    height: 75dvh;
  }
}

/* The assistant launcher is fixed above everything, so inside an open sheet it
   sits on top of that sheet's own actions. The brief is explicit that it must
   never cover a page action, so it stands down while a sheet is open and comes
   back when the sheet closes. */
@media (max-width: 1023.98px) {
  body.pt-sheet-open .pixel-launcher,
  body.pt-sheet-open .pt-assistant-launcher,
  body.pt-sheet-open #pixel-assistant,
  body.pt-sheet-open .pixel-panel,
  /* An open landlord dropdown is a bottom sheet too, and the launcher sat on
     top of its last item. */
  body:has(.pt-dd[open]) .pixel-launcher,
  body:has(.pt-dd[open]) .pt-assistant-launcher,
  body:has(.pt-dd[open]) #pixel-assistant {
    display: none !important;
  }
}

/* ---------------------------------------------------------------------- *
 * The stacking plan as a drag surface.
 *
 * Below 768px the portal replaces the SVG plan with the per-floor card list,
 * which is its own pre-existing choice and a good one on a phone. At tablet
 * widths the SVG is shown and arrange mode is reachable, and there it has to
 * behave like a drag surface under a finger:
 *
 *   - touch-action: none, or the browser claims the gesture as a scroll and
 *     the block never moves;
 *   - user-select: none, or the browser claims it as a text selection and the
 *     page just highlights, which is exactly what the first touch-drag test
 *     of this run produced.
 * ---------------------------------------------------------------------- */
@media (max-width: 1023.98px) {
  .pf-plan,
  .pf-plan svg,
  .pf-block,
  .pf-unit {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
  }

  svg.arranging,
  svg.arranging .pf-block {
    touch-action: none;
  }

  svg.arranging .pf-block {
    cursor: grab;
  }

  svg.arranging .pf-block.dragging {
    cursor: grabbing;
  }
  /* Exact-letter viewer: all toolbar actions retain a full thumb target. */
  #fs-viewer .fv-btn { min-width: 44px; min-height: 44px; flex-shrink: 0; }
  #fs-viewer .fv-tools { flex-wrap: wrap; justify-content: flex-start; }
  .pt-postal-envelope .pt-btn { margin: 4px 4px 4px 0; }
}

/* Keep restored and reference contract copies readable without hiding actions. */
@media (max-width: 1023.98px) {
  .pt-contract-documents td { white-space: normal; }
  .pt-contract-documents td > span { min-width: 0; overflow-wrap: anywhere; }
}

/* Account identity has its own rows: labels must remain readable beside controls. */
@media (max-width: 1023.98px) {
  .pt-header > .pt-user {
    display: block;
    flex: 1 0 100%;
    order: 5;
    min-width: 0;
    min-height: 0;
    max-width: 100%;
    white-space: normal;
    overflow: visible;
    overflow-wrap: anywhere;
    text-align: left;
  }
  .pt-header > .pt-company {
    flex: 1 1 100%;
    order: 5;
    min-width: 0;
    max-width: 100%;
    min-height: 44px;
    font-size: 16px;
  }
}
