/**
 * touch.css — Stage 1 phone tier (task/verb bar + three-detent sheet).
 * Spec: docs/superpowers/specs/2026-08-15-touch-ui-redesign-design.md
 *
 * Rule: everything lives inside the 700px media query except base rules
 * for brand-new markup with no styles.css presence
 * (#touch-bar). Desktop >700px is pixel-untouched. The tablet tier
 * (Stage 4) will add a coarse-pointer block here.
 */
#touch-bar {
    display: none;
}

/* Base hide outside the media query — desktop must not depend incidentally
   on styles.css flex layout collapsing it. The phone rule inside the media
   query below overrides this with display:block. */
.sheet-grabber {
    display: none;
}

/* Task 6 (retirement of the old Phase-1b phone-tier stylesheet/controller):
   the "hidden above 700px" base state for the header-overflow trigger — the
   one documented exception to "everything lives inside the media query." */
#header-overflow-btn {
    display: none;
}

/* Base hide outside the media query — desktop must not depend incidentally
   on styles.css flex layout or positioning. The phone rule inside the media
   query below overrides this with display:flex. */
.touch-chrome {
    display: none;
}

/* Base rule for brand-new markup with no styles.css presence — the
   documented exception. Unlike the other touch-only chrome above, this
   is NOT gated to the media query: pan-lock is a recognizer-level
   engagement (engagePrimary()) that a desktop-touch (touchscreen laptop)
   drag can also trigger, so the chip must be able to show at any
   viewport width. Declarations cloned from `.cut-result-banner`
   (styles.css ~7714), anchored to the bottom of the preview instead of
   the top so it doesn't collide with the cut-result banner. */
.pan-lock-chip {
    position: absolute;
    bottom: 56px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(8, 145, 178, 0.9);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 4px 12px;
    border-radius: 12px;
    pointer-events: none;
    z-index: 25;
    letter-spacing: 0.5px;
    text-transform: uppercase;
}
.pan-lock-chip.hidden { display: none; }

/* Draw-mode chip (polish): same documented base-rule exception as
   pan-lock-chip above — brand-new markup with no styles.css presence.
   Unlike pan-lock it's tappable (exits line/arc draw back to select),
   and it only ever SHOWS on the touch tiers (App._updateDrawModeChip
   gates on TouchLayout.isActive()), so a base rule is safe. Same slot
   as pan-lock-chip: the two are mutually exclusive (pan-lock needs a
   select-mode shape drag; this chip needs draw mode). */
.draw-mode-chip {
    position: absolute;
    bottom: 56px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(217, 119, 6, 0.92);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 6px 14px;
    border-radius: 12px;
    cursor: pointer;
    z-index: 25;
    letter-spacing: 0.5px;
}
.draw-mode-chip.hidden { display: none; }

/* Stage 3 Task 5: base rule for brand-new markup with no styles.css
   presence — the same documented exception as pan-lock-chip above, but
   unlike pan-lock (all-touch, engagePrimary can fire on a desktop-touch
   drag), the pill is a phone-only feature (spec: shortcuts alongside the
   Size panel, which is where desktop/tablet stays) — so display:flex is
   granted only inside the media query below, not here. */
.dimension-pill {
    display: none;
}

/* Task 6: same base-hide exception as the pill above — the entry popover
   is phone-only, display:flex is granted only inside the media query. */
.dimension-entry {
    display: none;
}

@media (max-width: 700px), (min-width: 700.02px) and (pointer: coarse) {
    body.layout-touch {
        /* 44px targets + 4px padding top/bottom = 52px row, plus the
           home-indicator inset (carried by the bar, which touches the
           physical bottom edge). */
        --touchbar-h: calc(52px + env(safe-area-inset-bottom, 0px));
        --touch-verb-accent: #e8a33d; /* amber — verb state is visually distinct (spec) */
    }

    /* ---- header (absorbed from the old phone-tier stylesheet, Task 6) ----
       Shrink the logo and tighten the surviving controls' gap so the
       always-visible cluster (logo, ⌄ menu button, .user-menu) fits
       without wrapping. Everything else in .header-controls is re-parented
       into #header-overflow-panel by TouchPanels' MOVE_TABLE (the menu
       rows, absorbed from the old phone-layout controller's re-parent step
       in Task 6; #mode-toggle is now a menu row). */
    body.layout-touch .app-header .app-title {
        font-size: 1.05rem;
    }

    body.layout-touch .header-controls {
        gap: var(--spacing-sm);
    }

    /* Replicated from the deleted styles.css `@media (max-width: 480px)`
       rule (see the Task 2 report): .user-menu stays visible on the
       phone tier, but its "Login" text is dropped to save header width —
       480px is a subset of 700px, so this rule fully subsumes the old
       one's effect. */
    body.layout-touch .user-btn span {
        display: none;
    }

    /* The overflow trigger: same visual language as the other circular
       header icon buttons (c.f. .help-btn), just shown only here. */
    body.layout-touch #header-overflow-btn {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 32px;
        height: 32px;
        padding: 0;
        background: var(--surface-light);
        border: 1px solid var(--border-color);
        border-radius: 50%;
        color: var(--text-secondary);
        font-size: 1.1rem;
        font-weight: 700;
        line-height: 1;
        cursor: pointer;
    }

    body.layout-touch #header-overflow-btn[aria-expanded="true"] {
        background: var(--primary-color);
        border-color: var(--primary-color);
        color: #fff;
    }

    /* Anchored dropdown under the header, right-aligned. .app-header is
       position:sticky, which establishes the positioning context for this
       absolutely-positioned panel regardless of how deep it sits inside
       .header-controls. */
    body.layout-touch #header-overflow-panel {
        position: absolute;
        top: calc(100% + var(--spacing-xs));
        right: var(--spacing-md);
        z-index: 200;
        display: flex;
        flex-direction: column;
        min-width: 200px;
        max-width: calc(100vw - 2 * var(--spacing-md));
        background: var(--surface);
        border: 1px solid var(--border-color);
        border-radius: var(--radius);
        box-shadow: var(--shadow-pop);
        padding: var(--spacing-xs);
        gap: var(--spacing-xs);
    }

    /* The `hidden` attribute's UA-stylesheet `display:none` loses to the
       `display:flex` above on specificity alone — restate it explicitly
       so the attribute keeps doing its job. */
    body.layout-touch #header-overflow-panel[hidden] {
        display: none;
    }

    /* Moved controls render as full-width rows with a real tap target,
       instead of their regular-layout inline/flex sizing. */
    body.layout-touch #header-overflow-panel > * {
        min-height: 44px;
        width: 100%;
        box-sizing: border-box;
    }

    /* ---- canvas chrome containers (2.3) ---- */
    /* Hide the desktop preview controls and hint — they stay in the DOM
       for app.js dereferencing, just invisible on the phone tier. */
    body.layout-touch .preview-controls {
        display: none;
    }
    body.layout-touch .preview-hint {
        display: none;
    }

    /* The `hidden` attribute's UA-stylesheet `display:none` must apply
       in phone tier — restate it explicitly so the attribute works
       to hide inactive containers. */
    body.layout-touch .touch-chrome[hidden] {
        display: none;
    }

    /* Canvas chrome containers: show active ones, position absolutely,
       z-index above the .canvas-view-bar (z:20). */
    body.layout-touch .touch-chrome:not([hidden]) {
        display: flex;
        position: absolute;
        z-index: 25;
        background: var(--surface);
        border: 1px solid var(--border-color);
        border-radius: var(--radius);
        padding: 6px;
        gap: 6px;
    }

    /* Pill container: vertical flex at left center, offset from edge. */
    body.layout-touch #touch-pill:not([hidden]) {
        flex-direction: column;
        left: 8px;
        top: 50%;
        transform: translateY(-50%);
    }

    /* View cluster: horizontal flex at top-right. */
    body.layout-touch #touch-view-cluster:not([hidden]) {
        flex-direction: row;
        top: 8px;
        right: 8px;
    }

    /* Undo chips: horizontal flex at top-left. */
    body.layout-touch #touch-undo-chips:not([hidden]) {
        flex-direction: row;
        top: 8px;
        left: 8px;
    }

    /* Buttons inside chrome containers: enforce 44×44 minimum tap target. */
    body.layout-touch .touch-chrome button {
        min-width: 44px;
        min-height: 44px;
    }

    /* The View pill (styles.css: top 8px centered) shares the top row with
       the chips (left) and view cluster (right) — at ≤393px they overlap.
       On phone it drops to a second row below the chrome. */
    body.layout-touch .canvas-view-bar {
        top: 60px;
    }

    /* Dimension pill (Stage 3 Task 5): tracks the selection's top-center
       edge — glued via preview.js's redraw-tail hook (_updateDimensionPill,
       called at the end of redraw()). Palette matches drawDimensionLabel's
       on-canvas dimension text (preview.js ~1551: dark plate, #3b82f6
       text) so it reads as the same "dimension" affordance whether it's
       drawn on the canvas (during shape creation) or floating above a
       selection. */
    body.layout-touch .dimension-pill:not(.hidden) {
        display: flex;
    }
    body.layout-touch .dimension-pill {
        position: absolute;
        z-index: 25;
        transform: translateX(-50%);
        align-items: center;
        padding: 4px 12px;
        min-height: 32px;
        background: rgba(15, 23, 42, 0.9);
        color: #3b82f6;
        font-weight: 600;
        font-size: 0.8rem;
        border-radius: 999px;
        border: 1px solid rgba(59, 130, 246, 0.4);
        pointer-events: auto;
        cursor: pointer;
        white-space: nowrap;
    }
    /* Task 6 stub: multi-select is display-only (plan ruling — resize_shapes
       scales EVERY targeted shape to the given W×H individually, which
       isn't "resize the selection's bounding box" for a multi-select, so
       tap-to-type stays single-select-only). */
    body.layout-touch .dimension-pill[data-multi] {
        opacity: 0.85;
        cursor: default;
    }

    /* Task 6: tap-to-type popover. Same plate palette as the pill (dark
       plate, #3b82f6 accents) so it reads as the pill's own edit state;
       z-index 26 sits above the pill (25) since the pill itself is hidden
       while this is open (positioned near-identically, just offset up). */
    body.layout-touch .dimension-entry:not(.hidden) {
        display: flex;
    }
    body.layout-touch .dimension-entry {
        position: absolute;
        z-index: 26;
        transform: translateX(-50%);
        align-items: center;
        gap: 6px;
        padding: 6px 8px;
        background: rgba(15, 23, 42, 0.95);
        border: 1px solid rgba(59, 130, 246, 0.4);
        border-radius: 10px;
        pointer-events: auto;
    }
    body.layout-touch .dimension-entry input[type="number"] {
        width: 72px;
        min-height: 40px;
        background: rgba(255, 255, 255, 0.08);
        border: 1px solid rgba(148, 163, 184, 0.4);
        border-radius: 6px;
        color: #e2e8f0;
        font-size: 0.9rem;
        text-align: center;
        padding: 0 4px;
    }
    body.layout-touch .dimension-entry .dim-entry-x {
        color: #3b82f6;
        font-weight: 600;
    }
    body.layout-touch .dimension-entry button {
        min-width: 40px;
        min-height: 40px;
        border-radius: 6px;
        border: 1px solid rgba(59, 130, 246, 0.4);
        background: rgba(59, 130, 246, 0.15);
        color: #e2e8f0;
        font-size: 0.85rem;
        font-weight: 600;
    }
    body.layout-touch .dimension-entry #dim-entry-cancel {
        background: rgba(148, 163, 184, 0.1);
        border-color: rgba(148, 163, 184, 0.3);
    }

    /* ---- bottom bar ---- */
    body.layout-touch #touch-bar {
        display: block;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 70; /* above the sheet (60) and scrim (50) */
        height: var(--touchbar-h);
        background: var(--surface);
        border-top: 1px solid var(--border-color);
    }

    /* Task bar and verb bar are stacked layers inside the bar; the
       body.touch-verbs class crossfades between them (~150ms, spec). */
    body.layout-touch .touch-taskbar,
    body.layout-touch .touch-verbbar {
        position: absolute;
        inset: 0;
        display: flex;
        align-items: stretch;
        justify-content: space-around;
        padding: 4px 4px calc(4px + env(safe-area-inset-bottom, 0px));
        transition: opacity 150ms ease, transform 150ms ease;
    }
    body.layout-touch .touch-taskbar { opacity: 1; transform: translateY(0); }
    body.layout-touch .touch-verbbar { opacity: 0; transform: translateY(6px); pointer-events: none; }
    body.layout-touch.touch-verbs .touch-taskbar { opacity: 0; transform: translateY(-6px); pointer-events: none; }
    /* Two verb bars stack in the same layer; only the one matching the
       current selection context (single vs. multi, driven by the
       verb-context effect / body.touch-multi) crossfades in. The other
       stays under the default hidden rule above (opacity 0, pointer-events
       none). */
    body.layout-touch.touch-verbs:not(.touch-multi) .touch-verbbar[data-ctx="single"],
    body.layout-touch.touch-verbs.touch-multi .touch-verbbar[data-ctx="multi"] {
        opacity: 1;
        transform: translateY(0);
        pointer-events: auto;
        background: rgba(232, 163, 61, 0.12);
        background: color-mix(in srgb, var(--touch-verb-accent) 12%, var(--surface));
        border-top: 2px solid var(--touch-verb-accent);
        margin-top: -2px; /* border sits on the bar's own border line */
    }

    body.layout-touch .touch-root,
    body.layout-touch .touch-verb {
        min-width: 44px;
        min-height: 44px;
        flex: 1 1 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 1px;
        padding: 2px 4px;
        background: none;
        border: none;
        border-radius: var(--radius);
        color: var(--text-secondary);
        font-size: 1.05rem;
        line-height: 1.1;
        cursor: pointer;
    }
    body.layout-touch .touch-root span {
        font-size: 0.62rem;
        letter-spacing: 0.01em;
    }
    body.layout-touch .touch-root[aria-expanded="true"] {
        color: var(--primary-color);
        background: var(--surface-light);
    }
    body.layout-touch .touch-verbbar .touch-verb {
        font-size: 0.72rem;
        font-weight: 600;
        color: var(--text-primary);
    }

    /* Overflow popover (long-press verbs) floats above the bar. */
    body.layout-touch .touch-overflow {
        position: absolute;
        bottom: calc(100% + 8px);
        left: 8px;
        right: 8px;
        z-index: 71;
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 4px;
        padding: 6px;
        background: var(--surface);
        border: 1px solid var(--touch-verb-accent);
        border-radius: var(--radius);
        box-shadow: var(--shadow-pop);
    }
    body.layout-touch .touch-overflow[hidden] {
        display: none;
    }
    /* Verbs greyed by _updateOverflowEnablement (would no-op right now) */
    body.layout-touch .touch-overflow .touch-verb:disabled {
        opacity: 0.4;
    }

    /* ---- canvas reclaims the column (carried over from the Phase 1b phone stylesheet) ---- */
    body.layout-touch .preview-container {
        max-height: none;
        aspect-ratio: auto;
        flex: 1 1 auto;
        min-height: 0;
    }
    body.layout-touch .app-container {
        padding-bottom: var(--touchbar-h);
    }
    /* Shell composition (same double-inset fix as the Phase 1b phone
       stylesheet's Task-3 block): the shell's .app-container is already shrunk by the
       bottom inset, so only the 52px bar overlap remains to pad. */
    body.capacitor-shell.layout-touch .app-container {
        padding-bottom: 52px;
    }
    /* The floating nudge toolbar is superseded by the verb bar on phones. */
    body.layout-touch #touch-toolbar {
        display: none;
    }
    /* Support-chat launcher (chat.css: bottom:20px, z-index:9999) would sit
       directly on the ⇪ Export root and float above every touch layer —
       covering sheet content and the bar's bottom-right controls. On the
       phone tier it rides above the bar instead, and slots UNDER the
       scrim/sheet/bar (45 < 50/60/70): with any panel open it tucks away
       behind the sheet; close the sheet to reach support chat. */
    body.layout-touch #chat-widget {
        bottom: calc(var(--touchbar-h) + 12px);
        right: 12px;
        z-index: 45;
    }

    /* ---- the sheet ---- */
    /* .sidebar-tabs rises from the bar's top edge (bottom: var(--touchbar-h)),
       so every detent must be measured as the total occupied viewport from
       the PHYSICAL bottom edge — bar height plus the visible sheet slice —
       not from the sheet's own bottom. Height is trimmed by the bar so the
       FULL detent's top edge (and grabber) lands at ~5dvh from the physical
       top, not above the viewport. transform-only animation (spec). */
    body.layout-touch .sidebar-tabs {
        position: fixed;
        left: 0;
        right: 0;
        bottom: var(--touchbar-h);
        z-index: 60;
        height: calc(95vh - var(--touchbar-h));
        height: calc(95dvh - var(--touchbar-h));
        background: var(--surface);
        border-top: 1px solid var(--border-color);
        border-radius: 12px 12px 0 0;
        transform: translateY(100%);
        /* visibility flips AFTER the slide-down finishes (0s + 200ms delay),
           so the close animates; on open the delay is zeroed below. */
        transition: transform 200ms ease, visibility 0s linear 200ms;
        visibility: hidden; /* closed sheet is fully out of the a11y/hit tree */
    }
    body.layout-touch.touch-sheet-open .sidebar-tabs {
        visibility: visible;
        transition: transform 200ms ease, visibility 0s linear 0s;
    }
    /* vh fallback first, then dvh (repo pairing convention). */
    body.layout-touch.touch-detent-peek .sidebar-tabs {
        transform: translateY(calc(100% - 35vh + var(--touchbar-h)));
        transform: translateY(calc(100% - 35dvh + var(--touchbar-h)));
    }
    body.layout-touch.touch-detent-work .sidebar-tabs {
        transform: translateY(calc(100% - 70vh + var(--touchbar-h)));
        transform: translateY(calc(100% - 70dvh + var(--touchbar-h)));
    }
    body.layout-touch.touch-detent-full .sidebar-tabs {
        transform: translateY(0);
    }
    /* While the grabber drag is live (Task 6), JS drives an inline
       transform; kill the transition so the sheet tracks the finger. */
    body.layout-touch .sidebar-tabs.sheet-dragging {
        transition: none;
    }

    /* Tabs are switched by the roots now — the old tab bar is not part of
       the phone UI (desktop >700px keeps it untouched). */
    body.layout-touch .tab-buttons {
        display: none;
    }

    /* Grabber: always visible at the sheet top. Full-width 28px hit strip
       (the 44px HIG minimum is met by width; height is padded by the
       sheet's rounded head), visual pill via ::after. touch-action:none =
       the drag never fights page scroll (spec). */
    body.layout-touch .sheet-grabber {
        display: block;
        width: 100%;
        height: 28px;
        touch-action: none;
        cursor: grab;
    }
    body.layout-touch .sheet-grabber::after {
        content: '';
        display: block;
        width: 44px;
        height: 5px;
        border-radius: 3px;
        background: var(--border-color);
        margin: 12px auto 0;
    }

    body.layout-touch .tab-content {
        flex: 1 1 auto;
        min-height: 0;
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }

    /* WORK/PEEK translate part of the fixed-height sheet below the screen.
       Reserve that same distance outside the scroller, or its final controls
       can only scroll to an invisible area behind/below the task bar. The
       tablet rail does not translate vertically and must keep its full height. */
    body.layout-touch:not(.layout-rail).touch-detent-work .tab-content {
        margin-bottom: 25vh;
        margin-bottom: 25dvh;
    }
    body.layout-touch:not(.layout-rail).touch-detent-peek .tab-content {
        margin-bottom: 60vh;
        margin-bottom: 60dvh;
    }

    /* Scrim: WORK and FULL only. At PEEK the canvas stays fully
       interactive (spec) — TouchLayout keeps #sheet-scrim[hidden] there.
       z-order: canvas < scrim(50) < sheet(60) < bar(70). */
    body.layout-touch.touch-sheet-open #sheet-scrim {
        display: block;
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        inset: 0;
        z-index: 50;
        background: rgba(0, 0, 0, 0.45);
    }
    body.layout-touch.touch-sheet-open #sheet-scrim[hidden] {
        display: none;
    }

    /* ---- Stage 2.1: real panels ---- */
    /* The desktop tab panels and top strips do not render on the phone
       tier; their contents have been re-parented into the touch panels. */
    body.layout-touch .tab-content > .tab-panel {
        display: none;
    }
    body.layout-touch .shape-selector,
    body.layout-touch .trace-image-bar {
        display: none;
    }
    body.layout-touch .touch-panel {
        display: block;
        padding: 0 12px;
    }
    body.layout-touch .touch-panel[hidden] {
        display: none;
    }
    /* Inter-section rhythm: replicates .tab-panel > * + * (styles.css:~755). */
    body.layout-touch .touch-panel > * + * {
        margin-top: 12px;
    }
    /* Card surface + typography for moved sections: declarations copied
       verbatim from styles.css:9990-10054 (#tab-design/#tab-output
       visual-refresh blocks — identical bodies in both), re-scoped under
       .touch-panel ancestors. */
    body.layout-touch .touch-panel .param-section,
    body.layout-touch .touch-panel > details {
        background: var(--surface-2);
        border: 1px solid var(--border-soft);
        border-radius: var(--radius);
        padding: 14px;
        margin-bottom: 12px;
    }
    body.layout-touch .touch-panel summary,
    body.layout-touch .touch-panel h3,
    body.layout-touch .touch-panel .section-title {
        font-family: 'Barlow', sans-serif;
        font-weight: 700;
        font-size: 0.95rem;
        letter-spacing: -0.005em;
    }
    body.layout-touch .touch-panel input[type="number"] {
        font-family: 'JetBrains Mono', ui-monospace, monospace;
    }
    body.layout-touch .touch-panel .code-input {
        font-family: 'JetBrains Mono', ui-monospace, monospace;
        background: var(--background);
        color: var(--text-primary);
    }
    body.layout-touch .touch-panel .section-divider {
        border: 0;
        border-top: 1px solid var(--border-soft);
        margin: 10px 0;
    }
    /* The re-parented shape strip renders as a wrap grid of 44pt tiles.
       (0,3,0) specificity out-scores the `body.layout-touch .shape-selector
       { display:none }` hider above (0,2,0), so the strip is hidden at its
       desktop position and visible once re-parented into a touch panel. */
    body.layout-touch .touch-panel .shape-selector {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        overflow: visible;
    }
    body.layout-touch .touch-panel .shape-selector .shape-btn {
        min-width: 44px;
        min-height: 44px;
        flex: 0 0 auto;
    }
    /* The re-parented import bar renders as a vertical list of full-width
       rows (no horizontal scrolling — the Stage-2 point). Same
       out-specificity argument as .shape-selector above. */
    body.layout-touch .touch-panel .trace-image-bar {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
        overflow: visible;
    }
    body.layout-touch .touch-panel .trace-image-bar > * {
        min-height: 44px;
    }

    /* Trace controls: allow the six-control row to wrap — at SE-class
       widths its un-wrappable content (~347px + gaps) doesn't fit one row. */
    body.layout-touch .trace-controls {
        flex-shrink: 1;
        flex-wrap: wrap;
    }

    /* Icon-over-label for the five import-bar buttons that carry an icon
       (the three upload labels — Import SVG, Import DXF, Trace Image —
       plus AI Generate and the Library chip): stack icon above text
       instead of the regular-layout inline row, so each becomes a narrow
       tile instead of a ~140px-wide inline pill. `.dxf-heal-label`
       (the "Heal paths" checkbox) has no icon and is left in its normal
       row layout. Absorbed from the old phone-tier stylesheet (Task 6) —
       the same buttons now render inside the import touch panel's vertical
       list. */
    body.layout-touch .trace-upload-btn,
    body.layout-touch .ai-generate-btn,
    body.layout-touch .design-library-btn {
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        padding: 6px 10px;
        font-size: 0.65rem;
        text-align: center;
    }

    /* Wizard button compacts to icon + short label — stop consuming ~140px
       of the shape row. Padding/font-size shrink (not icon-over-label:
       styles.css already lays it out as a single-line inline-flex pill,
       and that's kept, just tightened). Absorbed from the old phone-tier
       stylesheet (Task 6) — #new-sign-wizard-btn now lives in the menu;
       the rule keeps its 44px height there. */
    body.layout-touch .wizard-launch-btn {
        padding: 0 10px;
        font-size: 13px;
        height: 44px;
    }

    /* Generic modal fit: every plain `.modal-content` (no bespoke shell)
       gets a real compact-viewport box instead of relying on its authored
       max-width, which was sized for desktop. The five bespoke shells —
       `.help-modal`, `.profile-modal`, `.machine-modal`, `.pricing-modal`,
       `.twofa-modal` (the class living directly on `.modal-content`, not
       the `-v2` wrapper class on the outer `.modal`) — are excluded; see
       the old phone-tier stylesheet's Task 4 report/fix-report for the
       measured findings on why each one needs to keep its own authored box.

       IMPORTANT — `:where()`, not `:not()`, wraps the exclusion list.
       `:not()`'s argument contributes its own specificity (a fix-loop
       Critical from review: `:not(.help-modal)` alone made this selector
       `(0,3,1)`, which *out-ranks* the `-v2` shells' own
       `(0,3,0)` rules — reintroducing exactly the double-scroll/box
       override this exclusion exists to prevent, just for a different
       set of modals). `:where()` has zero specificity regardless of its
       argument's complexity, so wrapping the whole `:not()` chain in it
       keeps this selector's specificity at the original, intended
       `body.layout-touch .modal-content` = (0,2,1) — low enough that
       all five bespoke shells' own (0,3,0)-or-higher rules still win
       outright wherever they overlap, while every other (plain,
       unwrapped) `.modal-content` still gets this rule at full strength.
       Absorbed from the old phone-tier stylesheet (Task 6), re-keyed
       verbatim. */
    body.layout-touch .modal-content:where(:not(.help-modal):not(.profile-modal):not(.machine-modal):not(.pricing-modal):not(.twofa-modal)) {
        width: calc(100vw - 16px);
        max-width: none;
        /* vh fallback (M1) before the dvh version, same pairing convention
           as everywhere else in this file / styles.css. */
        max-height: calc(100vh - 24px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
        max-height: calc(100dvh - 24px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
        overflow-y: auto;
    }

    /* ---- Stage 2.4 Task 7: stacked cards (Size/Align/Boolean/Layer) ----
       #touch-card-host carries .touch-panel, so it inherits that class's
       display/padding/[hidden] rules for free (same container contract as
       the five root panels) — only the card-specific head row and button
       grids need new rules here. */
    body.layout-touch .touch-card-head {
        display: flex;
        align-items: center;
        gap: 8px;
        min-height: 44px;
    }
    body.layout-touch #touch-card-back {
        min-width: 44px;
        min-height: 44px;
        background: none;
        border: none;
        color: var(--text-primary);
        font-size: 0.95rem;
        cursor: pointer;
    }
    body.layout-touch #touch-card-title {
        font-family: 'Barlow', sans-serif;
        font-weight: 700;
        font-size: 1rem;
    }
    body.layout-touch .touch-card {
        margin-top: 12px;
    }
    body.layout-touch .touch-card-grid,
    body.layout-touch #touch-card-boolean,
    body.layout-touch .touch-card-align-target {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 8px;
    }
    body.layout-touch .touch-card-align-target {
        margin-bottom: 8px;
    }
    body.layout-touch .touch-card-btn,
    body.layout-touch .touch-card-layer-row {
        min-height: 44px;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 6px;
        background: var(--surface-2);
        border: 1px solid var(--border-soft);
        border-radius: var(--radius);
        color: var(--text-primary);
        font-size: 0.85rem;
        cursor: pointer;
    }
    body.layout-touch .touch-card-btn[data-align-target].active {
        background: var(--primary-color);
        color: #fff;
        border-color: var(--primary-color);
    }
    body.layout-touch #touch-card-layer-list {
        display: flex;
        flex-direction: column;
        gap: 8px;
    }
    body.layout-touch .touch-card-layer-row {
        justify-content: flex-start;
        width: 100%;
        padding: 0 12px;
    }
    body.layout-touch .touch-card-layer-swatch {
        width: 16px;
        height: 16px;
        border-radius: 4px;
        flex: 0 0 auto;
    }
}

/* ==== rail tier (Stage 4) ==== */
/* Geometry OVERRIDES only. Components (buttons, panels, cards, chrome
   styling) come from the shared block above — rail re-lays them out.
   Every selector is body.layout-rail-gated: desktop never has the class. */
@media (min-width: 700.02px) and (pointer: coarse) {
    body.layout-rail {
        --rail-w: calc(56px + env(safe-area-inset-left, 0px));
        --rail-panel-w: min(320px, 40vw);
    }
    /* Task bar becomes the vertical left rail. Phone's shared rule sets
       display:block (base rule above the media query) — flex must be
       restated explicitly here for flex-direction/gap to take effect. */
    body.layout-rail #touch-bar {
        display: flex;
        top: 0;
        bottom: 0;
        left: 0;
        right: auto;
        width: var(--rail-w);
        height: auto;
        flex-direction: column;
        justify-content: flex-start;
        padding: 8px 0 8px env(safe-area-inset-left, 0px);
        gap: 4px;
    }
    /* .touch-taskbar is already display:flex (shared block, position:
       absolute; inset:0) — only the axis flips to vertical here. */
    body.layout-rail #touch-bar .touch-taskbar {
        flex-direction: column;
        width: 100%;
    }
    /* .touch-root is already display:flex; flex-direction:column (shared
       block) — just widen to fill the rail's cross-axis. */
    body.layout-rail .touch-root {
        width: 100%;
        min-height: 56px;
    }
    /* Canvas reclaims everything right of the rail */
    body.layout-rail .preview-container {
        margin-left: var(--rail-w);
        margin-bottom: 0;
    }
    /* Final-review fix S2: the shared block's .app-container padding-bottom
       (both the plain and .capacitor-shell variants) reserves space for the
       phone's bottom bar; the rail moved the bar to the left edge, so the
       reservation must be zeroed here. */
    body.layout-rail .app-container {
        padding-bottom: 0;
    }
    body.layout-rail.capacitor-shell .app-container {
        padding-bottom: 0;
    }
    /* Final-review fix S1: the shared block's phone crossfade
       (body.layout-touch.touch-verbs .touch-taskbar, touch.css:348) fades
       the rail's root buttons to opacity 0 / pointer-events none whenever a
       selection opens the verb bar. On rail the verb strip floats over the
       canvas instead of replacing the bar in place, so the rail itself must
       stay visible and tappable. Mirrors exactly the three properties :348
       sets (opacity, transform, pointer-events); source order after the
       shared block breaks the (0,3,1)-vs-(0,3,1) specificity tie in the
       rail's favor. */
    body.layout-rail.touch-verbs .touch-taskbar {
        opacity: 1;
        transform: none;
        pointer-events: auto;
    }
    /* Sheet becomes the docked side panel: closed = off-canvas left.
       .sidebar-tabs is already display:flex; flex-direction:column
       (styles.css base) — only positioning is overridden here. */
    body.layout-rail .sidebar-tabs {
        top: 0;
        bottom: 0;
        left: var(--rail-w);
        right: auto;
        width: var(--rail-panel-w);
        height: auto;
        max-height: none;
        border-radius: 0;
        transform: translateX(calc(-1 * (var(--rail-w) + var(--rail-panel-w))));
        transition: transform 0.2s ease;
    }
    body.layout-rail.touch-sheet-open .sidebar-tabs {
        transform: translateX(0);
    }
    /* Detents/grabber/scrim are phone concepts */
    body.layout-rail .sheet-grabber {
        display: none;
    }
    body.layout-rail #sheet-scrim {
        display: none;
    }
    /* Verb bars float above the selection (positioned by preview.js
       redraw-tail via --verb-strip-x/y; hidden until first position).
       .touch-verbbar is already display:flex (shared block) — only the
       positioning model flips from absolute/inset to fixed/coords. */
    body.layout-rail .touch-verbbar {
        position: fixed;
        left: var(--verb-strip-x, -9999px);
        top: var(--verb-strip-y, -9999px);
        right: auto;
        bottom: auto;
        width: auto;
        border-radius: 10px;
        box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
    }
    /* Final-review fix S3: position comes from --verb-overflow-x/y, computed
       and clamped in preview.js's _updateVerbStrip (via _verbOverflowPos)
       from the strip's actual position/height and the viewport, instead of
       a fixed +52px offset off the (already separately clamped) strip
       coordinates — that offset assumed a 52px strip and could clip off the
       bottom/right or overlap a taller strip. .touch-overflow is already
       display:grid (shared block) — only the positioning model flips here. */
    body.layout-rail .touch-overflow {
        position: fixed;
        left: var(--verb-overflow-x, -9999px);
        top: var(--verb-overflow-y, -9999px);
        right: auto;
        bottom: auto;
        width: 300px;
    }
    /* Chrome insets shift right of the rail */
    body.layout-rail #touch-undo-chips {
        left: calc(var(--rail-w) + 12px);
    }
    body.layout-rail .canvas-view-bar {
        top: 8px;
    }
}
