/* The customer approval pages (/a/{token}). Light only, mobile first (360 px and up), no framework.
   The tokens are copied from the client's wwwroot/css/app.css :root (plus the ring and foreground fallbacks its
   components use), the motion tokens and --pressed included; keep them in step when the app's tokens change. These
   pages don't load app.css, so this file has its own reduced-motion block (at the end). */
:root {
    --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
    sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';

    --primary: black;
    --primary-foreground: white;
    --accent: #f4f4f5;
    --off-white: #f5f5f5;
    --border-colour: #e5e5e5;
    --border-colour-darker: #a9a9a9;
    --muted-foreground: #737373;
    /* Muted text on the off-white page (#f5f5f5) or the #f4f4f5 pill fill: #737373 is 4.3:1 there, #666666 is 5.3:1 (CP-2.2,
       4.5:1). Muted text inside a white card keeps --muted-foreground (4.7:1). */
    --muted-on-off-white: #666666;
    --foreground: #0a0a0a;
    --ring: #a1a1aa;

    --destructive: #dc2626;
    --destructive-foreground: #fef2f2;
    --warning: #d97706;
    --warning-foreground: #fffbeb;
    --success: #16a34a;
    --success-foreground: #f0fdf4;
    /* The thank-you banner's border and its text on --success-foreground (7:1; --success itself is only 3.1:1). */
    --success-border: #86efac;
    --success-text: #14532d;

    --radius: 0.625rem;
    --card-radius: 1.125rem;
    --spacing: 0.25rem;

    --text-sm: 0.875rem;
    --text-xs: 0.75rem;

    /* Motion (Specs/Spec - Quiet Delight.md §4): the micro tier for a state changing in place, the base tier for
       something entering. Transitions here are the micro tier (colours, opacity, transform); keyframe animations
       move only opacity and transform (QD-M1). */
    --motion-fast: 120ms;
    --motion-base: 180ms;
    --ease-out: cubic-bezier(0.2, 0, 0, 1);
    --ease-in: cubic-bezier(0.4, 0, 1, 1);
    --pressed: #e4e4e7;
}

*, *::before, *::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--off-white);
    color: var(--foreground);
    font-family: var(--font-sans);
    line-height: 1.5;
    overflow-x: clip;
}

button, input, textarea {
    font: inherit;
    color: inherit;
}

a {
    color: inherit;
}

h1, h2, p, ul, fieldset {
    margin: 0;
}

/* `hidden` always wins, so a script-only element (Approve all, the bar's status line) stays out of a no-JS page even
   when its class sets a display. */
[hidden] {
    display: none !important;
}

.approval {
    display: flex;
    flex-direction: column;
    gap: calc(var(--spacing) * 4);
    width: 100%;
    max-width: 40rem;
    margin: 0 auto;
    padding: calc(var(--spacing) * 4) 1rem calc(var(--spacing) * 10);
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.muted {
    color: var(--muted-foreground);
    font-size: var(--text-sm);
}

.pre-wrap {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Money, codes and numbers never break in the middle (X1). */
.nowrap {
    white-space: nowrap;
}

:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* S2d: the line above the photos and the Documents card while they can't be shown (_FilesUnavailable). It sits on the
   off-white page, so it takes the darker muted text (4.5:1 there); its spacing is .approval's gap. */
.files-unavailable {
    color: var(--muted-on-off-white);
    overflow-wrap: break-word;
}

/* --- Shop header --- */

/* The logo on its own row at its own proportions, then the name, address and contact under it (CP-1, CP-4). */
.shop {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: calc(var(--spacing) * 3);
}

/* Drawn as uploaded: up to 200 x 56 px (a square logo is 56 px), no tile behind it, no cropping. The width and height
   aren't stored with the logo, so the first, uncached view may shift the page by at most 56 px once. */
.shop-logo {
    display: block;
    width: auto;
    height: auto;
    max-width: min(200px, 100%);
    max-height: 56px;
}

.shop-details {
    min-width: 0;
}

.shop-name {
    font-size: 1.125rem;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.shop-address {
    color: var(--muted-on-off-white);
    font-size: var(--text-sm);
    overflow-wrap: break-word;
}

.shop-address-line {
    display: block;
}

.shop-contact {
    display: flex;
    flex-wrap: wrap;
    column-gap: calc(var(--spacing) * 3);
    color: var(--muted-on-off-white);
    font-size: var(--text-sm);
}

.shop-contact a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    overflow-wrap: anywhere;
}

/* --- Non-fiscal mark (an AR shop's "X": the letter in a box over the legend, at the top centre) --- */

.document-mark {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(var(--spacing) * 1);
    text-align: center;
}

/* Square corners, so it reads as the legal mark rather than a badge. */
.document-mark-letter {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 2px solid var(--foreground);
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1;
}

.document-mark-legend {
    max-width: 100%;
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    overflow-wrap: anywhere;
}

/* --- Cards --- */

.card {
    min-width: 0;
    padding: calc(var(--spacing) * 4);
    border: 1px solid var(--border-colour);
    border-radius: var(--card-radius);
    background: white;
}

.card-title {
    margin-bottom: calc(var(--spacing) * 2);
    font-size: 1rem;
    font-weight: 600;
}

.notice-title,
.pin-title,
.intro-title {
    font-size: 1.25rem;
    font-weight: 600;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

.notice p,
.pin p {
    margin-top: calc(var(--spacing) * 2);
}

/* The reminder unsubscribe page's Confirm card: its button sits as far from the sentence as the PIN form's does. */
.notice form {
    margin-top: calc(var(--spacing) * 4);
}

/* A dead end's one action (_ShopAction): the Call or Email button, then the email under a Call button. */
.notice-actions {
    display: flex;
    flex-direction: column;
    gap: calc(var(--spacing) * 2);
    margin-top: calc(var(--spacing) * 4);
}

.notice-actions .notice-email {
    margin-top: 0;
    font-size: var(--text-sm);
    text-align: center;
}

.notice-email a {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    min-height: 44px;
    overflow-wrap: anywhere;
}

.intro .greeting {
    color: var(--muted-on-off-white);
}

.intro .meta {
    margin-top: calc(var(--spacing) * 1);
    color: var(--muted-on-off-white);
    font-size: var(--text-sm);
    overflow-wrap: anywhere;
}

/* The thank-you after an answer: a green banner, so it isn't lost among the page's other lines. */
.intro-success {
    display: flex;
    align-items: center;
    gap: calc(var(--spacing) * 3);
    padding: calc(var(--spacing) * 4);
    border: 1px solid var(--success-border);
    border-radius: var(--card-radius);
    background: var(--success-foreground);
}

.intro-icon {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background: var(--success);
    color: var(--success-foreground);
}

.intro-text {
    min-width: 0;
}

.intro-success .intro-title,
.intro-success .meta {
    color: var(--success-text);
}

/* --- Buttons --- */

.button {
    -webkit-tap-highlight-color: transparent;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 1.25rem;
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out),
        color var(--motion-fast) var(--ease-out), opacity var(--motion-fast) var(--ease-out),
        transform var(--motion-fast) var(--ease-out);
}

/* Pressed: buttons scale, they don't tint (QD-S1). */
.button:active:not(:disabled) {
    transform: scale(0.98);
}

.button-primary {
    background: var(--primary);
    color: var(--primary-foreground);
}

@media (hover: hover) {
    .button-primary:hover:not(:disabled) {
        background-color: color-mix(in srgb, var(--primary) 90%, transparent);
    }
}

.button-primary:disabled {
    opacity: 0.5;
    cursor: default;
}

/* A text button (Approve all): no fill, the foreground colour, still 44 px tall (T1). Its small inline padding is pulled
   back by an equal negative margin, so the label lines up with the cards' edge. The tint and underline only where
   hover exists (Q7); the pressed scale comes from .button:active. */
.button-text {
    align-self: flex-start;
    padding-inline: calc(var(--spacing) * 2);
    margin-inline: calc(var(--spacing) * -2);
    background: transparent;
    color: var(--foreground);
    font-weight: 500;
    text-underline-offset: 0.2em;
}

@media (hover: hover) {
    .button-text:hover {
        background-color: var(--accent);
        text-decoration: underline;
    }
}

/* A full-width button's label may be long (a notice's "Call {shop}"): it wraps, centred, inside the button. */
.button-block {
    width: 100%;
    padding-block: calc(var(--spacing) * 2);
    text-align: center;
    overflow-wrap: anywhere;
}

/* A two-line button (Call the shop, with the number under it): the lines stack, centred. Only a button that holds a
   detail line: the Send button's busy spinner (::before) sits beside its label in the row. */
.button-block:has(.button-detail) {
    flex-direction: column;
}

.button-detail {
    font-size: var(--text-sm);
    font-weight: 400;
}

/* Busy (approval.js, on submit): disabled so it can't send twice, but at full opacity, since the faded "disabled"
   look reads as dead. The label says what's happening at once; the spinner beside it shows after 150 ms (QD-P1),
   as the app's Button does: it starts transparent and a 1 ms animation delayed by 150 ms turns it on (`forwards`
   holds only an opacity). */
.button[aria-busy="true"],
.button[aria-busy="true"]:disabled {
    opacity: 1;
    cursor: progress;
}

.button[aria-busy="true"]::before {
    content: "";
    flex: none;
    width: 1em;
    height: 1em;
    margin-inline-end: calc(var(--spacing) * 2);
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 9999px;
    opacity: 0;
    animation: approval-spin 600ms linear infinite, approval-reveal 1ms linear 150ms forwards;
}

@keyframes approval-spin {
    to {
        transform: rotate(360deg);
    }
}

@keyframes approval-reveal {
    to {
        opacity: 1;
    }
}

/* D12's line under Send my answer: how to change the answer later. Not a target; wraps with a long shop name. */
.submit-note {
    margin-top: calc(var(--spacing) * -2);
    color: var(--muted-on-off-white);
    text-align: center;
    overflow-wrap: anywhere;
}

.form-error {
    color: var(--destructive);
    font-size: var(--text-sm);
    font-weight: 500;
    /* The form posts to #form-error: the re-rendered page lands with a little room above the error line. */
    scroll-margin-top: 1rem;
}

/* --- PIN --- */

.pin-form {
    display: flex;
    flex-direction: column;
    gap: calc(var(--spacing) * 4);
    margin-top: calc(var(--spacing) * 4);
}

.pin-boxes {
    display: flex;
    gap: calc(var(--spacing) * 3);
    padding: 0;
    border: 0;
}

.pin-box {
    width: 3.25rem;
    height: 3.5rem;
    border: 1px solid var(--border-colour-darker);
    border-radius: var(--radius);
    background: white;
    font-size: 1.5rem;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.pin-box:focus-visible {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 40%, transparent);
}

/* A wrong or incomplete PIN: the border says so, focused or not (nothing shakes). */
.pin-box[aria-invalid="true"] {
    border-color: var(--destructive);
}

/* --- Groups --- */

.decision-form {
    display: flex;
    flex-direction: column;
    gap: calc(var(--spacing) * 4);
}

.group {
    display: flex;
    flex-direction: column;
    gap: calc(var(--spacing) * 3);
}

/* Floated, a legend lays out like a normal block instead of sitting on the fieldset's border. */
legend.group-legend {
    float: left;
    width: 100%;
    padding: 0;
}

legend.group-legend + * {
    clear: both;
}

.group-legend {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: calc(var(--spacing) * 3);
}

.group-name {
    min-width: 0;
    font-size: 1rem;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.group-description {
    color: var(--muted-foreground);
    font-size: var(--text-sm);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.pill,
.decision {
    flex: none;
    padding: 0.125rem 0.625rem;
    border-radius: 9999px;
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: 1.25rem;
    white-space: nowrap;
}

.pill-critical {
    background: var(--destructive);
    color: var(--destructive-foreground);
}

.pill-recommended {
    background: var(--warning);
    color: var(--warning-foreground);
}

.pill-optional {
    background: var(--accent);
    color: var(--muted-on-off-white);
}

.decision-approved {
    background: var(--success-foreground);
    color: var(--success);
}

.decision-declined {
    background: var(--accent);
    color: var(--muted-on-off-white);
}

.group-declined {
    color: var(--muted-foreground);
}

/* After the server's "decisions missing" error (no JS needed): each unanswered service gets a 2 px red edge (the border
   plus a 1 px ring, so nothing moves) and the words "Not answered" under its choices, never colour alone (CP-8.2).
   Both go once a choice is ticked. No transition: it changes in place at once (QD-S2). */
.group-unanswered:not(:has(input:checked)) {
    border-color: var(--destructive);
    box-shadow: 0 0 0 1px var(--destructive);
}

.group-unanswered-note {
    color: var(--destructive);
    font-size: var(--text-sm);
    font-weight: 500;
}

.group:has(input:checked) .group-unanswered-note {
    display: none;
}

/* The answer as text for paper: shown by the print block only. */
.print-decision {
    display: none;
}

/* --- Photos --- */

.photos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
    gap: calc(var(--spacing) * 2);
    padding: 0;
    list-style: none;
}

.photo,
.photo-placeholder {
    display: block;
    aspect-ratio: 1;
    overflow: hidden;
    border-radius: var(--radius);
    background: var(--accent);
}

.photo {
    -webkit-tap-highlight-color: transparent;
    transition: transform var(--motion-fast) var(--ease-out);
}

.photo:active {
    transform: scale(0.98);
}

.photo img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* A photo that failed to load (e.g. its object is gone after a restore): a plain tile with a crossed frame. */
.photo-placeholder {
    background:
        linear-gradient(to top right, transparent calc(50% - 1px), var(--border-colour-darker) 50%, transparent calc(50% + 1px)),
        var(--accent);
}

/* --- Items --- */

.items {
    display: flex;
    flex-direction: column;
    padding: 0;
    list-style: none;
    font-size: var(--text-sm);
}

.item {
    display: grid;
    grid-template-columns: 1fr auto;
    column-gap: calc(var(--spacing) * 3);
    padding: calc(var(--spacing) * 2) 0;
    border-top: 1px solid var(--border-colour);
}

.item-description {
    grid-column: 1 / -1;
    overflow-wrap: anywhere;
}

.item-quantity {
    color: var(--muted-foreground);
    font-variant-numeric: tabular-nums;
}

.item-amount {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

@media (width >= 600px) {
    .item {
        grid-template-columns: 1fr auto 7rem;
    }

    .item-description {
        grid-column: auto;
    }
}

.row {
    display: flex;
    justify-content: space-between;
    gap: calc(var(--spacing) * 3);
    font-variant-numeric: tabular-nums;
}

.row > :last-child {
    flex: none;
    text-align: right;
}

.group-total {
    padding-top: calc(var(--spacing) * 2);
    border-top: 1px solid var(--border-colour);
    font-weight: 600;
}

/* --- Approve / Decline: two radios styled as a segmented control --- */

.choice {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: calc(var(--spacing) * 2);
}

.choice-option {
    position: relative;
    display: flex;
}

.choice-option input {
    -webkit-tap-highlight-color: transparent;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

.choice-option span {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    border: 1px solid var(--border-colour-darker);
    border-radius: var(--radius);
    background: white;
    font-weight: 500;
    /* A choice changes colour at the micro tier (QD-S2), and scales when pressed. */
    transition: background-color var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out),
        color var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}

.choice-option:active span {
    transform: scale(0.98);
}

@media (hover: hover) {
    .choice-option:hover input:not(:checked) + span {
        background: var(--accent);
    }
}

.choice-option input:focus-visible + span {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.choice-approve input:checked + span {
    border-color: var(--success);
    background: var(--success);
    color: var(--success-foreground);
}

.choice-decline input:checked + span {
    border-color: var(--foreground);
    background: var(--foreground);
    color: white;
}

/* A declined group is muted, so the approved work stands out; declining and changing your mind fade at the micro tier. */
.group .group-name,
.group .items,
.group .group-total,
.group .photos {
    transition: opacity var(--motion-fast) var(--ease-out);
}

.group:has(input[value="Declined"]:checked) .group-name,
.group:has(input[value="Declined"]:checked) .items,
.group:has(input[value="Declined"]:checked) .group-total,
.group:has(input[value="Declined"]:checked) .photos {
    opacity: 0.55;
}

/* --- Totals, comment, disclaimer --- */

.totals {
    display: flex;
    flex-direction: column;
    gap: calc(var(--spacing) * 2);
}

.row-total {
    padding-top: calc(var(--spacing) * 2);
    border-top: 1px solid var(--border-colour);
    font-size: 1.125rem;
    font-weight: 700;
}

.comment-label {
    display: block;
    margin-bottom: calc(var(--spacing) * 2);
    font-weight: 500;
}

textarea {
    display: block;
    width: 100%;
    min-height: 5rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border-colour-darker);
    border-radius: var(--radius);
    background: white;
    resize: vertical;
}

.disclaimer {
    padding: calc(var(--spacing) * 3) calc(var(--spacing) * 4);
    border: 1px solid var(--warning);
    border-radius: var(--radius);
    background: var(--warning-foreground);
    font-size: var(--text-sm);
}

.contact {
    color: var(--muted-on-off-white);
    font-size: var(--text-sm);
    text-align: center;
    overflow-wrap: anywhere;
}

.contact-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    column-gap: calc(var(--spacing) * 4);
}

.contact-links a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

/* --- The bar (S1t.20): the answered count, Approved so far, the missing-answer hint and Send my answer. On a desktop it's
   the plain end of the form; on phones it sticks to the bottom (below). --- */

.submit-bar {
    display: flex;
    flex-direction: column;
    gap: calc(var(--spacing) * 2);
}

/* One row that wraps: on a narrow phone the approved part goes under the count as a whole, its amount never broken. */
.submit-bar-status {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    column-gap: calc(var(--spacing) * 3);
    font-size: var(--text-sm);
}

.submit-bar-count {
    color: var(--muted-on-off-white);
}

.submit-bar-approved {
    font-weight: 600;
    /* The running total changes with every tap: fixed-width figures keep it from jittering. */
    font-variant-numeric: tabular-nums;
}

/* Empty until a tap on Send finds a service unanswered (approval.js sets its text). */
.submit-bar-hint:empty {
    display: none;
}

/* Sticky only at the phone layout's width, and never on a short touch screen (a phone in landscape: L3). Full-bleed
   to the viewport's edges (the form is centred, so its half-width against the viewport's says how far; between 641 and
   768 px .approval's max-width leaves more than its 1rem gutter), white, with the home indicator's inset below it (L5).
   A sticky element keeps its place in the flow, so turning it static moves nothing else. body's overflow-x: clip guards
   L1 against a desktop scrollbar's few pixels. */
@media screen and (width <= 768px) and (not ((pointer: coarse) and (height < 500px))) {
    /* Only the page that has the bar. Keyboard focus and the script's scroll stay clear of it (its height at 360 px
       with the status line wrapped and the hint shown, 148 px). */
    html:has(.submit-bar) {
        scroll-padding-bottom: 9.25rem;
    }

    .submit-bar {
        position: sticky;
        bottom: 0;
        z-index: 1;
        margin-inline: calc(50% - 50vw);
        padding: calc(var(--spacing) * 3) calc(50vw - 50%) max(calc(var(--spacing) * 3), env(safe-area-inset-bottom));
        border-top: 1px solid var(--border-colour);
        background: white;
    }

    /* With the comment field focused (the keyboard up), the bar goes back to its place so it never covers the field
       (L3). */
    .decision-form:has(textarea:focus) .submit-bar {
        position: static;
    }
}

/* --- Lightbox --- */

.lightbox {
    width: min(100vw - 2rem, 60rem);
    max-height: calc(100dvh - 2rem);
    padding: 0;
    border: 0;
    border-radius: var(--radius);
    background: black;
}

.lightbox::backdrop {
    background: rgb(0 0 0 / 0.8);
}

/* Opening fades in at the base tier (the dialog from a 0.98 scale); closing is instant. `backwards` leaves no transform
   behind. */
.lightbox[open] {
    animation: approval-lightbox-in var(--motion-base) var(--ease-out) backwards;
}

.lightbox[open]::backdrop {
    animation: approval-fade-in var(--motion-base) var(--ease-out) backwards;
}

@keyframes approval-lightbox-in {
    from {
        opacity: 0;
        transform: scale(0.98);
    }
}

@keyframes approval-fade-in {
    from {
        opacity: 0;
    }
}

.lightbox-image {
    display: block;
    width: 100%;
    max-height: calc(100dvh - 2rem);
    object-fit: contain;
}

.lightbox-close {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 9999px;
    background: rgb(0 0 0 / 0.6);
    color: white;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background-color var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}

.lightbox-close:active {
    transform: scale(0.98);
}

@media (hover: hover) {
    .lightbox-close:hover {
        background: rgb(0 0 0 / 0.8);
    }
}

/* --- Documents (the job's completed inspections) --- */

.document-links {
    display: flex;
    flex-direction: column;
    padding: 0;
    list-style: none;
}

.document-links a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    overflow-wrap: anywhere;
}

/* --- "Powered by GarageSuite" (Plans/Plan - Powered By.md): a centred, muted line at the bottom of the page, linking
   to the landing page. The icon is inline SVG with fill="currentColor", so it takes the link's grey. --- */

.powered-by {
    padding-top: calc(var(--spacing) * 2);
    text-align: center;
    font-size: var(--text-xs);
    color: var(--muted-on-off-white);
}

/* A 44 px target (T1) with the line looking as before. */
.powered-by a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: calc(var(--spacing) * 2);
    color: inherit;
    text-decoration: none;
}

.powered-by a:focus-visible {
    text-decoration: underline;
}

@media (hover: hover) {
    .powered-by a:hover {
        text-decoration: underline;
    }
}

.powered-by-mark {
    white-space: nowrap;
}

.powered-by-icon {
    width: 1.15em;
    height: 1.15em;
    vertical-align: -0.2em;
    margin-right: 0.2em;
    fill: currentColor;
}

/* --- Paper (CP-8.2, CP-10): the page as a clean document. No controls, no bar, no comment box; each service's answer
   is printed as text, worked out from the radios by :has (no script); pills and decisions keep a border instead of a
   fill, since a browser drops backgrounds when printing (a critical pill's near-white text would vanish). --- */

@media print {
    @page {
        margin: 1.5cm;
    }

    body {
        background: white;
    }

    .approval {
        max-width: none;
        padding: 0;
    }

    .button,
    .choice,
    .submit-bar,
    .submit-note,
    .comment,
    [data-no-js-note],
    .form-error,
    .group-unanswered-note,
    .files-unavailable,
    .lightbox {
        display: none !important;
    }

    .print-decision {
        display: block;
        font-weight: 600;
        color: var(--foreground);
    }

    .print-decision span {
        display: none;
    }

    .group:has(input[value="Approved"]:checked) .print-approved,
    .group:has(input[value="Declined"]:checked) .print-declined,
    .group:not(:has(input:checked)) .print-unanswered {
        display: inline;
    }

    .group,
    .totals,
    .disclaimer {
        break-inside: avoid;
    }

    .group-legend {
        break-after: avoid;
    }

    /* A service never prints its on-screen "unanswered" outline. */
    .group-unanswered:not(:has(input:checked)) {
        border-color: var(--border-colour);
        box-shadow: none;
    }

    .pill,
    .decision {
        background: none;
        color: var(--foreground);
        border: 1px solid currentColor;
    }
}

/* --- Reduced motion (QD-M6, QD-M9): every transition instant, no press scale, no lightbox animation. The spinner is
   the only sign of progress, so it keeps turning, slowly, as the app's Button does. The bar's one movement, the
   scroll to an unanswered service, is instant under reduced motion in approval.js. --- */

@media (prefers-reduced-motion: reduce) {
    .button,
    .choice-option span,
    .photo,
    .lightbox-close,
    .group .group-name,
    .group .items,
    .group .group-total,
    .group .photos {
        transition: none;
    }

    .button:active:not(:disabled),
    .choice-option:active span,
    .photo:active,
    .lightbox-close:active {
        transform: none;
    }

    .lightbox[open],
    .lightbox[open]::backdrop {
        animation: none;
    }

    .button[aria-busy="true"]::before {
        animation-duration: 1.5s, 1ms;
    }
}
