
/* ---------- Design tokens ---------- */
:root {
    --color-primary: #4fb3dd;
    --color-primary-dark: #2f8fb8;
    --color-primary-darker: #256e8f;
    --color-primary-light: #eaf7fc;
    --color-text: #1f2937;
    --color-text-muted: #6b7280;
    --color-border: #e5e7eb;
    --color-bg: #f2f5f7;
    --color-surface: #ffffff;
    --color-danger: #e0524d;
    --color-success: #1e8a4c;
    --color-success-light: #eafaf0;
    --color-warning: #b45309;
    --color-warning-light: #fef4e3;
    --color-stage: #eef1f4;
    --radius-sm: 8px;
    --radius-md: 14px;
    --radius-lg: 20px;
    --shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.08);
    --shadow-md: 0 10px 30px rgba(15, 23, 42, 0.10);
    --shadow-lg: 0 20px 45px rgba(15, 23, 42, 0.16);
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --header-height: 64px;
    --action-bar-height: 76px;
    --font: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

/* ---------- Reset & base ---------- */
* {
    box-sizing: border-box;
}

body {
    font-family: var(--font);
    color: var(--color-text);
    margin: 0;
    padding: 0;
    background-color: var(--color-bg);
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p {
    margin: 0 0 var(--space-3);
}

img {
    max-width: 100%;
}

.visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---------- Header ---------- */
.site-header {
    position: sticky;
    top: 0;
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-height: var(--header-height);
    padding: var(--space-2) var(--space-5);
    background-color: rgba(255, 255, 255, 0.92);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--color-border);
}

/* The wordmark the rest of the site now uses (canvas-prints-header.php), set
   in the page's own type rather than pulled in as a logo image. */
.brand {
    display: flex;
    flex-direction: column;
    line-height: 1.1;
    text-decoration: none;
}

.brand-name {
    font-size: 21px;
    font-weight: 700;
    color: var(--color-text);
    letter-spacing: 0.5px;
}

.brand:hover .brand-name {
    color: var(--color-primary-darker);
}

.site-nav {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    flex-wrap: wrap;
    justify-content: flex-end;
}

.site-nav a {
    padding: 8px 12px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    transition: background-color 0.2s ease, color 0.2s ease;
}

.site-nav a:hover {
    background-color: var(--color-primary-light);
    color: var(--color-primary-darker);
}

/* ---------- Page heading ---------- */
.page-head {
    margin-bottom: var(--space-5);
}

.page-title {
    margin: 0 0 var(--space-1);
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.2px;
}

.page-subtitle {
    margin: 0;
    font-size: 14px;
    color: var(--color-text-muted);
}

/* ---------- Page shell ---------- */
.page-shell {
    max-width: 720px;
    margin: 0 auto;
    padding: var(--space-5) var(--space-4) calc(var(--action-bar-height) + var(--space-5));
}

.container {
    width: 100%;
    padding: var(--space-5);
    background-color: var(--color-surface);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}

/* ---------- Links & buttons ---------- */
a {
    color: var(--color-text-muted);
    text-decoration: none;
    cursor: pointer;
}

a:active, a:visited {
    color: var(--color-text-muted);
}

a:hover {
    color: var(--color-primary-dark);
}

button {
    font-family: var(--font);
}

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    background-color: var(--color-primary);
    color: white;
    border: none;
    padding: 13px 22px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 16px;
    font-weight: 600;
    min-height: 48px;
    transition: background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease;
}

.btn:hover {
    background-color: var(--color-primary-dark);
}

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

.btn:disabled {
    opacity: 0.65;
    cursor: not-allowed;
    transform: none;
}

.btn-secondary {
    background-color: var(--color-surface);
    color: var(--color-text);
    border: 1.5px solid var(--color-border);
}

.btn-secondary:hover {
    background-color: var(--color-primary-light);
    border-color: var(--color-primary);
    color: var(--color-primary-darker);
}

.btn-block {
    width: 100%;
}

/* ---------- Stepper ---------- */
.stepper {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: var(--space-5);
}

.stepper .step {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    position: relative;
    color: var(--color-text-muted);
}

.stepper .step:not(:last-child)::after {
    content: "";
    position: absolute;
    top: 15px;
    left: 55%;
    width: 90%;
    height: 2px;
    background-color: var(--color-border);
    z-index: 0;
}

.stepper .step-circle {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background-color: var(--color-surface);
    border: 2px solid var(--color-border);
    font-size: 13px;
    font-weight: 700;
    color: var(--color-text-muted);
    margin-bottom: var(--space-2);
    transition: all 0.2s ease;
}

.stepper .step-label {
    font-size: 11px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    font-weight: 600;
}

.stepper[data-step="upload"] .step[data-index="1"],
.stepper[data-step="restore"] .step[data-index="1"],
.stepper[data-step="restore"] .step[data-index="2"],
.stepper[data-step="size"] .step[data-index="1"],
.stepper[data-step="size"] .step[data-index="2"],
.stepper[data-step="size"] .step[data-index="3"],
.stepper[data-step="review"] .step[data-index="1"],
.stepper[data-step="review"] .step[data-index="2"],
.stepper[data-step="review"] .step[data-index="3"],
.stepper[data-step="review"] .step[data-index="4"] {
    color: var(--color-primary-darker);
}

.stepper[data-step="upload"] .step[data-index="1"] .step-circle,
.stepper[data-step="restore"] .step[data-index="1"] .step-circle,
.stepper[data-step="restore"] .step[data-index="2"] .step-circle,
.stepper[data-step="size"] .step[data-index="1"] .step-circle,
.stepper[data-step="size"] .step[data-index="2"] .step-circle,
.stepper[data-step="size"] .step[data-index="3"] .step-circle,
.stepper[data-step="review"] .step[data-index="1"] .step-circle,
.stepper[data-step="review"] .step[data-index="2"] .step-circle,
.stepper[data-step="review"] .step[data-index="3"] .step-circle,
.stepper[data-step="review"] .step[data-index="4"] .step-circle {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: white;
}

/* ---------- Upload dropzone ---------- */
.upload-hint {
    text-align: center;
    color: var(--color-text-muted);
}

.dropzone-button {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    background-color: var(--color-primary-light);
    border: 2px dashed var(--color-primary);
    border-radius: var(--radius-md);
    padding: var(--space-6) var(--space-4);
    color: var(--color-primary-darker);
    transition: background-color 0.2s ease, border-color 0.2s ease;
}

.dropzone-button:hover {
    background-color: #dcf1fa;
}

#upload-section.dragover .dropzone-button {
    background-color: #dcf1fa;
    border-color: var(--color-primary-darker);
}

.dropzone-icon {
    font-size: 30px;
    line-height: 1;
    margin-bottom: var(--space-2);
}

.dropzone-title {
    font-size: 17px;
    font-weight: 700;
    letter-spacing: 0.5px;
}

.dropzone-subtitle {
    font-size: 13px;
    color: var(--color-primary-darker);
    opacity: 0.8;
}

#progress-bar-container {
    width: 100%;
    height: 10px;
    background-color: var(--color-border);
    border-radius: 10px;
    margin-top: var(--space-4);
    overflow: hidden;
}

#progress-bar {
    width: 0;
    height: 100%;
    background-color: var(--color-primary);
    transition: width 0.3s;
}

/* ---------- Resolution readout ---------- */
.quality-hint {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-4);
    padding: 10px var(--space-3);
    border-radius: var(--radius-sm);
    font-size: 13px;
    line-height: 1.35;
    background-color: var(--color-bg);
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
}

.quality-hint::before {
    content: "";
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: currentColor;
}

.quality-hint strong {
    color: inherit;
    font-weight: 700;
}

.quality-hint[data-level="high"] {
    background-color: var(--color-success-light);
    border-color: transparent;
    color: var(--color-success);
}

.quality-hint[data-level="ok"] {
    background-color: var(--color-primary-light);
    border-color: transparent;
    color: var(--color-primary-darker);
}

.quality-hint[data-level="low"] {
    background-color: var(--color-warning-light);
    border-color: transparent;
    color: var(--color-warning);
}

/* ---------- Form fields ---------- */
select {
    font-family: var(--font);
    font-size: 16px;
    padding: 11px 36px 11px 14px;
    border: 1.5px solid var(--color-border);
    border-radius: var(--radius-sm);
    background-color: var(--color-surface);
    color: var(--color-text);
    min-height: 46px;
    width: 100%;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 18px;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

select:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px rgba(79, 179, 221, 0.25);
}

.field-label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin-bottom: 6px;
}

#size-selection-container {
    border: 1px solid var(--color-border);
    padding: var(--space-4);
    margin-bottom: var(--space-4);
    border-radius: var(--radius-md);
}

.field-group {
    margin-bottom: var(--space-3);
}

.field-group:last-child {
    margin-bottom: 0;
}

/* ---------- Ratio-grouped size picker ---------- */
.size-picker {
    margin-bottom: var(--space-1);
}

.ratio-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
}

.ratio-tab {
    flex: 0 0 auto;
    background-color: var(--color-bg);
    color: var(--color-text-muted);
    border: 1.5px solid var(--color-border);
    padding: 8px 14px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    min-height: 36px;
    transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.ratio-tab.active {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: white;
}

.size-chip-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: var(--space-2);
}

.size-chip {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    background-color: var(--color-surface);
    border: 1.5px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 10px 6px;
    cursor: pointer;
    min-height: 56px;
    text-align: center;
    transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
}

.size-chip:hover {
    border-color: var(--color-primary);
}

.size-chip.selected {
    border-color: var(--color-primary);
    background-color: var(--color-primary-light);
    box-shadow: 0 0 0 2px rgba(79, 179, 221, 0.25);
}

.size-chip-dim {
    font-size: 13px;
    font-weight: 700;
    color: var(--color-text);
}

.size-chip-price {
    font-size: 11px;
    color: var(--color-text-muted);
}

.size-chip-price s {
    opacity: 0.7;
    margin-left: 2px;
}

.size-chip.selected .size-chip-dim,
.size-chip.selected .size-chip-price {
    color: var(--color-primary-darker);
}

.size-picker-hint {
    font-size: 12px;
    color: var(--color-text-muted);
    margin: 0 0 var(--space-2);
}

#size-price {
    font-size: 14px;
    font-weight: 700;
    color: var(--color-primary-darker);
    margin: var(--space-3) 0 0;
}

#size-price s {
    font-weight: 400;
    color: var(--color-text-muted);
    opacity: 0.8;
    margin-left: 4px;
}

.dimension-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: flex-end;
}

.dimension-row .field-group {
    flex: 1 1 110px;
    margin-bottom: 0;
}


/* ---------- Action bars ---------- */
.action-bar {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-4);
}

.action-bar .btn {
    flex: 1;
}

/* ---------- Result section ---------- */
#result-section {
    text-align: center;
}

.result-preview {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    margin-bottom: var(--space-4);
    background-color: var(--color-bg);
}

#cropped-image {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-sm);
}

.size-badge {
    display: inline-block;
    background-color: var(--color-primary-light);
    color: var(--color-primary-darker);
    font-weight: 600;
    font-size: 13px;
    padding: 6px 14px;
    border-radius: 999px;
    margin-bottom: var(--space-3);
}

.spinner {
    border: 4px solid #f3f3f3;
    border-top: 4px solid var(--color-primary);
    border-radius: 50%;
    width: 40px;
    height: 40px;
    animation: spin 1s linear infinite;
    margin: var(--space-5) auto;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* ---------- Overlay / modal (My Images) ---------- */
.overlay {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(15, 23, 42, 0.55);
    transition: opacity 0.3s ease;
    visibility: hidden;
    opacity: 0;
    z-index: 1000;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}

.overlay .popup {
    width: 100%;
    max-height: 85vh;
    margin: 0;
    padding: var(--space-5);
    background: var(--color-surface);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    position: relative;
    box-shadow: var(--shadow-lg);
}

.popup h2 {
    font-size: 18px;
    color: var(--color-text);
    padding-right: var(--space-5);
}

.popup .close {
    position: absolute;
    top: var(--space-4);
    right: var(--space-4);
    font-size: 26px;
    font-weight: 400;
    line-height: 1;
    color: var(--color-text-muted);
}

.popup .close:hover {
    color: var(--color-danger);
}

.popup .content {
    max-height: 65vh;
    overflow: auto;
}

/* ---------- 3D canvas preview ---------- */

.canvas-frame {
    width: 100%;
    max-width: 320px;
    height: 260px;
    margin: var(--space-2) auto;
    position: relative;
    perspective: 1000px;
    /* A wrapped edge crops the front panel narrower than the frame, and
       any size just leaves margin around the centered canvas — without a
       background here that space shows as the popup's plain white,
       looking like a rendering gap rather than a wall behind the canvas. */
    background-color: var(--color-bg);
    border-radius: var(--radius-md);
}

.canvas-panel {
    width: 100%;
    height: 100%;
    position: relative;
    transform-style: preserve-3d;
    transition: transform 0.4s ease;
}

.canvas-panel:hover {
    transform: rotateY(6deg);
}

.canvas-front {
    /* width/height/top/left are computed in JS from the crop's real aspect
       ratio when the preview opens, so the 3D illusion is never stretched. */
    position: absolute;
    overflow: hidden;
    transform: perspective(600px) rotateY(28deg);
    transform-origin: left center;
    /* Rounded on the outer corners only. The left pair butt against the side
       panel, and rounding them cut a notch out of the join that showed the
       frame's background through it as a pale nick at top and bottom. */
    border-radius: 0 2px 2px 0;
    box-shadow: var(--shadow-md);
}

.canvas-front img {
    /* width/height/left are set in JS. For a wrapped edge, the image is
       rendered at its full (uncropped) size but shifted left so the slice
       that wraps onto the side is clipped out of the front panel by the
       overflow: hidden above, instead of appearing on both surfaces.

       max-width has to be released here. The global `img { max-width: 100% }`
       clamped this image to the width of .canvas-front - which for a wrapped
       edge is deliberately NARROWER than the image, since the wrapped slice has
       been subtracted from it. The image therefore rendered short, and shifting
       it left pulled its right edge inside the panel, leaving a white strip
       down the right-hand side of the canvas. */
    position: absolute;
    top: 0;
    max-width: none;
}

.canvas-front.is-floater {
    /* A floater frame is its own physical frame around the canvas — the
       thin dark border here plus the matching solid edge color (set in JS)
       replace the wrap/mirror illusion, which only applies to a bare
       stretched canvas. */
    border: 6px solid #1a1a1a;
}

.canvas-edge {
    /* height/top/left are computed in JS alongside the image above. */
    width: 16px;
    position: absolute;
    overflow: hidden;
    transform: perspective(600px) rotateY(-48deg);
    transform-origin: right center;
    filter: brightness(0.75);
    border-radius: 2px 0 0 2px;
}

.canvas-edge-texture {
    /* background-image/color are set in JS to the edge color or the photo's
       left-edge slice. Kept as its own layer so a mirrored edge can be
       flipped with scaleX(-1) independently of the 3D rotation above. */
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: left center;
}

.canvas-edge-texture.is-mirrored {
    transform: scaleX(-1);
}

.preview-3d-hint {
    text-align: center;
    color: var(--color-text-muted);
    font-size: 13px;
    margin: var(--space-3) 0 0;
}

/* ---------- Option cards (edge / depth) ---------- */
.section-title {
    text-align: left;
    margin-top: var(--space-5);
    margin-bottom: var(--space-3);
    font-size: 15px;
    font-weight: 700;
}

.section {
    border: 1px solid var(--color-border);
    padding: var(--space-4);
    margin-bottom: var(--space-4);
    border-radius: var(--radius-md);
    text-align: left;
}

.option-container {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin: 0;
}

.option {
    flex: 1 1 140px;
    text-align: center;
    box-sizing: border-box;
    border: 2px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: var(--space-2);
    cursor: pointer;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.option:hover {
    border-color: var(--color-primary);
}

.option img {
    width: 100%;
    height: auto;
    max-width: 100%;
    object-fit: contain;
    cursor: pointer;
    border-radius: calc(var(--radius-sm) - 4px);
    box-sizing: border-box;
    display: block;
}

/* Fallback for browsers without :has() support */
.option img.selected {
    box-shadow: 0 0 0 3px var(--color-primary);
}

.option:has(img.selected) {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px rgba(79, 179, 221, 0.2);
}

.option p {
    margin: var(--space-2) 0 0;
    font-size: 14px;
    font-weight: 600;
}

.option .description {
    font-size: 12px;
    font-weight: 400;
    color: var(--color-text-muted);
    margin-top: 2px;
}

#select-color-btn {
    margin-top: var(--space-3);
}

#color-picker-container {
    margin-top: var(--space-3);
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

#edge-color-picker {
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1.5px solid var(--color-border);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.price-addition {
    color: var(--color-primary-darker);
    font-weight: 700;
    font-size: 13px;
}

/* ---------- Price display & order summary ---------- */
#price-and-type-display {
    font-size: 1.15em;
    font-weight: 700;
    margin: var(--space-3) 0 var(--space-4);
    text-align: center;
}

#price-display, #print-type-display {
    display: inline-block;
    margin: 0 var(--space-2);
}

.order-summary {
    text-align: left;
    margin: var(--space-4) 0;
    padding: var(--space-4);
    background-color: var(--color-bg);
    border-radius: var(--radius-md);
    border: 1px solid var(--color-border);
}

.order-summary h3 {
    font-size: 14px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--color-text-muted);
}

.order-summary > div {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--color-border);
}

.order-summary > div:last-child {
    border-bottom: none;
    padding-top: var(--space-3);
    font-size: 1.1em;
}

/* ---------- Loading overlay ---------- */
#loading-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(15, 23, 42, 0.55);
    backdrop-filter: blur(2px);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 9999;
}

#loading-message {
    background-color: var(--color-surface);
    padding: var(--space-4) var(--space-5);
    border-radius: var(--radius-md);
    font-size: 15px;
    font-weight: 600;
    box-shadow: var(--shadow-lg);
}

/* ---------- My images grid ---------- */
.image-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: var(--space-3);
}

.image-item {
    position: relative;
    cursor: pointer;
    border-radius: var(--radius-sm);
    overflow: hidden;
    border: 1px solid var(--color-border);
}

.image-item img {
    width: 100%;
    height: auto;
    display: block;
    aspect-ratio: 1 / 1;
    object-fit: cover;
}

.image-item .delete-btn {
    position: absolute;
    top: 4px;
    right: 4px;
    background-color: rgba(15, 23, 42, 0.55);
    color: white;
    border: none;
    border-radius: 50%;
    font-size: 18px;
    line-height: 24px;
    text-align: center;
    cursor: pointer;
    padding: 0;
    width: 26px;
    height: 26px;
}

.image-item .delete-btn:hover {
    background-color: var(--color-danger);
}

/* ---------- Spinners on buttons ---------- */
.button-spinner {
    width: 18px;
    height: 18px;
    border-width: 2px;
    margin: 0;
    display: inline-block;
    vertical-align: middle;
}

#finish-button:disabled {
    opacity: 0.7;
    cursor: not-allowed;
}

.crop-button-spinner {
    display: inline-block;
    width: 18px;
    height: 18px;
    border: 3px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    border-top-color: #fff;
    animation: crop-spin 1s ease-in-out infinite;
    vertical-align: middle;
}

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

#crop-button {
    display: flex;
    align-items: center;
    justify-content: center;
}

#crop-button:disabled {
    opacity: 0.7;
    cursor: not-allowed;
}

/* ---------- Breakpoints ---------- */

/* Small phones */
@media (max-width: 380px) {
    .dimension-row .field-group {
        flex: 1 1 100%;
    }
}

/* Sticky bottom action bar on phones/small tablets */
@media (max-width: 640px) {
    .page-shell {
        /* The fixed bar grows by the home-indicator inset on notched phones,
           so the reserved space underneath the page has to grow with it or the
           last line of content hides behind the bar. */
        padding: var(--space-4) var(--space-3) calc(var(--action-bar-height) + var(--space-4) + env(safe-area-inset-bottom));
    }

    .container {
        padding: var(--space-4);
        border-radius: var(--radius-md);
    }

    .site-header {
        padding: var(--space-2) var(--space-3);
    }

    .brand-name {
        font-size: 18px;
    }

    .site-nav {
        gap: 2px;
    }

    .site-nav a {
        padding: 6px 8px;
        font-size: 10px;
        letter-spacing: 0.8px;
    }

    .page-title {
        font-size: 19px;
    }

    .stepper .step-label {
        display: none;
    }

    #size-section .action-bar,
    #result-section .action-bar {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 400;
        margin: 0;
        padding: var(--space-3) var(--space-3) calc(var(--space-3) + env(safe-area-inset-bottom));
        background-color: rgba(255, 255, 255, 0.96);
        backdrop-filter: blur(6px);
        box-shadow: 0 -6px 20px rgba(15, 23, 42, 0.12);
    }

    .option {
        flex: 1 1 calc(50% - var(--space-3));
    }
}

@media (min-width: 641px) {
    .page-shell {
        padding-top: var(--space-6);
    }

    .option {
        flex: 1 1 calc(33.33% - var(--space-3));
    }

    .overlay {
        align-items: center;
    }

    .overlay .popup {
        width: min(480px, 90vw);
        border-radius: var(--radius-lg);
        max-height: 80vh;
    }
}

/* ---------- Previously uploaded images, under the upload button ---------- */

#upload-gallery {
    margin-top: var(--space-6);
    padding-top: var(--space-5);
    border-top: 1px solid var(--color-border);
}
.upload-gallery-title {
    margin: 0 0 var(--space-4);
    font-size: 16px;
    font-weight: 600;
    color: var(--color-text);
    text-align: center;
}
/* The grid markup is shared with the My Images popup, where it sits in a
   narrow column. Out here there is room for more per row. */
#upload-gallery-grid .image-grid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}

/* ---------- Canvas mock-up on the size step ---------- */

#size-preview {
    margin-bottom: var(--space-5);
    text-align: center;
}
/* The mock-up is drawn into a fixed-size stage; the canvas inside it is
   positioned by renderCanvasPreview() against these dimensions. */
#size-canvas-preview {
    width: 100%;
    max-width: none;
    height: 34vh;
    min-height: 200px;
    margin: 0 auto var(--space-2);
}
#size-preview .preview-3d-hint {
    margin: 0;
    font-size: 12px;
    color: var(--color-text-muted);
    text-align: center;
}

/* ---------- Change size, on the review screen ---------- */

#result-size-picker {
    margin: var(--space-4) 0 var(--space-5);
}
#result-size-picker .field-label {
    margin-bottom: var(--space-3);
}

/* ---------- Size screen layout ---------- */
/* .crop-layout is kept as the class name: it is the same two-column
   arrangement upload-order-1.3 uses, and renaming it here would leave the two
   stylesheets gratuitously out of step. */

/* Single column by default. The picture is pinned to the top of the viewport so
   it stays in sight while the sizes scroll beneath it - previously choosing a
   size meant scrolling the photo off screen, then back up to see the effect. */
.crop-layout-media {
    position: sticky;
    top: var(--header-height);
    z-index: 2;
    padding-top: var(--space-3);
    margin-bottom: var(--space-4);
    background-color: var(--color-surface);
}
/* Capped so the pinned picture cannot eat a small screen and leave no room for
   the sizes underneath it. */
.crop-layout-media #size-canvas-preview {
    height: 30vh;
}

@media (min-width: 900px) {
    /* Only the size step needs the extra room; the other steps read better
       in a narrower column. */
    .page-shell.is-wide {
        max-width: 1060px;
    }

    /* Side by side: picture left, sizes right, so neither has to be scrolled
       away to use the other. */
    .crop-layout {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(320px, 380px);
        gap: var(--space-5);
        align-items: start;
    }
    .crop-layout-media {
        /* Sticky in its own column, so a long list of sizes scrolls against a
           picture that stays put. */
        position: sticky;
        top: var(--space-5);
        margin-bottom: 0;
        padding-top: 0;
    }
    .crop-layout-media #size-canvas-preview {
        height: 46vh;
        min-height: 300px;
    }
    .crop-layout-controls #size-selection-container {
        margin-bottom: 0;
    }
}

/* ---------- Review screen layout ---------- */

/* Mirrors the size step: the print pinned in sight while the edge, depth and
   size controls scroll beneath it. Choosing an edge is a visual decision, so
   the picture has to stay visible while it is made. */
.result-layout-media {
    position: sticky;
    top: var(--header-height);
    z-index: 2;
    padding-top: var(--space-3);
    margin-bottom: var(--space-4);
    background-color: var(--color-surface);
}
.result-layout-media .result-preview img {
    max-height: 30vh;
    width: auto;
    margin: 0 auto;
}

@media (min-width: 900px) {
    .result-layout {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(340px, 420px);
        gap: var(--space-5);
        align-items: start;
    }
    .result-layout-media {
        position: sticky;
        top: var(--space-5);
        margin-bottom: 0;
        padding-top: 0;
    }
    .result-layout-media .result-preview img {
        max-height: 56vh;
    }
    /* The options already sit in their own bordered blocks; the outer padding
       would double up against the column gap. */
    .result-layout-controls #options-section {
        margin-bottom: 0;
    }
}

/* ---------- Always-on canvas preview (review screen) ---------- */

/* The flat crop stays in the page because the 3D preview is drawn from it, the
   AI styler reads it, and it is what goes to the cart - but the customer sees
   the canvas mock-up instead, so it is hidden rather than removed. */
.result-layout-media .result-preview {
    display: none;
}

#result-canvas-preview {
    width: 100%;
    max-width: none;
    height: 42vh;
    min-height: 240px;
    margin: 0 auto var(--space-2);
}
.result-layout-media .preview-3d-hint {
    margin: 0 0 var(--space-4);
    font-size: 12px;
    color: var(--color-text-muted);
    text-align: center;
}

@media (min-width: 900px) {
    #result-canvas-preview {
        height: 52vh;
        min-height: 320px;
    }
}

/* ---------- Production polish ---------- */

/* Add to Cart is the sale; Change size is an escape hatch. Give the primary
   button the width, especially in the fixed mobile bar where the two used
   to split it evenly. */
#result-section .action-bar #finish-button {
    flex: 2.5;
}
#result-section .action-bar #back-to-size-btn {
    flex: 1;
}

/* Fingers, not pointers: the gallery delete control was 26px, well under a
   comfortable touch target. Only bumped where the primary input is touch so
   the desktop grid keeps its tidier corner button. */
@media (pointer: coarse) {
    .image-item .delete-btn {
        width: 36px;
        height: 36px;
        font-size: 20px;
        line-height: 32px;
    }
}

/* ---------- Mobile: pin only the preview, not the whole media column -------
   The full column (preview + hint + buttons) pinned with a solid background
   was swallowing taps: the edge and depth options scroll underneath it, and a
   tap on an option sitting under the pinned block hits the preview instead of
   the option - which reads as "it won't let me change the edge". Pinning just
   the canvas frame, slightly smaller, keeps the picture in sight while
   roughly doubling the tappable window below it. Desktop keeps the full
   sticky column - there is room for it there. */
@media (max-width: 899px) {
    .result-layout-media,
    .crop-layout-media {
        position: static;
        padding-top: 0;
    }
    #result-canvas-preview,
    #size-canvas-preview {
        position: sticky;
        top: calc(var(--header-height) + var(--space-2));
        z-index: 2;
        height: 26vh;
        min-height: 170px;
    }
}

/* ---------- Colour swatch inside the Select Colour button ---------- */

/* The picked colour lives in this dot, not in the button's background, so the
   label keeps normal button contrast whatever colour is chosen. The inner ring
   keeps a white or very pale swatch visible against the button surface. */
.colour-swatch {
    display: inline-block;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background-color: #000;
    box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.25);
    flex: 0 0 auto;
}

/* ---------- Restore step ---------- */

/* Same two-column idea as the size step, but the proportions are reversed:
   there the picture supports a decision made in the right-hand column, here
   the picture IS the decision - it is the only evidence the customer has that
   the restoration is any good - so it gets the larger share. */
.restore-layout-media {
    margin-bottom: var(--space-5);
}

@media (min-width: 900px) {
    .restore-layout {
        display: grid;
        grid-template-columns: minmax(0, 1.35fr) minmax(300px, 360px);
        gap: var(--space-5);
        align-items: start;
    }
    .restore-layout-media {
        margin-bottom: 0;
    }
    .restore-layout-controls {
        position: sticky;
        top: var(--space-5);
    }
}

/* ---------- Before / after comparison ---------- */

/* The two images are stacked in the same box and the top one is clipped, so
   the slider genuinely reveals one over the other rather than swapping two
   pictures side by side.

   They are NOT the same shape: Gemini snaps its output to one of a fixed set
   of aspect ratios, so a restoration can come back very slightly wider or
   taller than the photo it came from. The "before" therefore sets the box and
   the "after" is object-fit: cover inside it - a fraction of an edge is hidden
   rather than the whole comparison shearing, which would look like the
   restoration had moved everything. */
.restore-compare {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-md);
    background-color: var(--color-stage);
    box-shadow: var(--shadow-md);
    line-height: 0;
}

.restore-compare-img {
    display: block;
    width: 100%;
    height: auto;
}

.restore-compare-after {
    position: absolute;
    inset: 0;
    /* Overwritten inline by setComparePosition(). The default reveals the
       restored half, so a freshly finished restoration is already showing
       before anything is dragged. */
    clip-path: inset(0 50% 0 0);
}

.restore-compare-after .restore-compare-img {
    /* Pinned to the box rather than laid out, so clipping the layer cannot
       reflow the picture inside it. */
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.restore-compare-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    margin-left: -1px;
    background-color: #fff;
    box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.25);
    pointer-events: none;
}

.restore-compare-handle::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 34px;
    height: 34px;
    margin: -17px 0 0 -17px;
    border-radius: 50%;
    background-color: #fff;
    box-shadow: var(--shadow-md);
}

/* The range input is the real control and covers the whole picture, so the
   slider can be grabbed anywhere on it rather than only on the 2px line. It is
   made invisible rather than hidden, because a display:none input is not
   focusable and the comparison would stop working from the keyboard. */
.restore-compare-range {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    opacity: 0;
    cursor: ew-resize;
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
}

.restore-compare-range:focus-visible {
    outline: none;
}
/* Focus has to be visible somewhere, and the input itself is invisible - so it
   is shown on the box instead. */
.restore-compare-range:focus-visible ~ .restore-compare-handle::after {
    box-shadow: 0 0 0 3px var(--color-primary), var(--shadow-md);
}

.restore-compare-tag {
    position: absolute;
    bottom: var(--space-3);
    padding: 4px 10px;
    border-radius: 999px;
    background-color: rgba(15, 23, 42, 0.65);
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    line-height: 1.4;
    pointer-events: none;
}
.restore-compare-tag-before { left: var(--space-3); }
.restore-compare-tag-after  { right: var(--space-3); }

/* Before a restoration exists there is only one picture, so everything that
   implies a comparison is hidden rather than shown against nothing. */
.restore-compare[data-has-after="0"] .restore-compare-after,
.restore-compare[data-has-after="0"] .restore-compare-handle,
.restore-compare[data-has-after="0"] .restore-compare-range,
.restore-compare[data-has-after="0"] .restore-compare-tag {
    display: none;
}

.restore-compare-hint {
    margin: var(--space-2) 0 0;
    font-size: 12px;
    color: var(--color-text-muted);
    text-align: center;
}

/* ---------- Restore options ---------- */

.restore-mode-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.restore-mode {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background-color: var(--color-surface);
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}

.restore-mode:hover {
    border-color: var(--color-primary);
}

/* :has() keeps the checked state on the card without a JS class swap. Browsers
   without it simply show an unhighlighted card with a checked radio in it,
   which is still perfectly usable. */
.restore-mode:has(input:checked) {
    border-color: var(--color-primary);
    background-color: var(--color-primary-light);
}

.restore-mode input {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin: 2px 0 0;
    accent-color: var(--color-primary);
}

.restore-mode-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.restore-mode-title {
    font-weight: 600;
    font-size: 14px;
    color: var(--color-text);
}

.restore-mode-desc {
    font-size: 12px;
    line-height: 1.45;
    color: var(--color-text-muted);
}

.field-label-optional {
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    color: var(--color-text-muted);
}

#restore-notes {
    width: 100%;
}

/* An action bar that belongs inside a column, rather than the fixed bar the
   step's own footer uses. */
.action-bar-inline {
    position: static;
    padding: 0;
    margin: var(--space-4) 0 0;
    border: 0;
    background: none;
    box-shadow: none;
}

.restore-error {
    display: none;
    margin: var(--space-3) 0 0;
    padding: 10px var(--space-3);
    border-radius: var(--radius-sm);
    background-color: var(--color-warning-light);
    color: var(--color-warning);
    font-size: 13px;
    line-height: 1.4;
}

/* ---------- Working overlay ---------- */

/* Covers the whole step rather than only the button. A restoration takes the
   better part of a minute, and a page that still looks interactive during it
   invites a second click - which would be a second charge. */
.restore-working {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 60;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-5);
    text-align: center;
    background-color: rgba(255, 255, 255, 0.94);
}

.restore-working.is-visible {
    display: flex;
}

.restore-working p {
    margin: 0;
    max-width: 42ch;
}

#restore-working-text {
    font-size: 16px;
    font-weight: 600;
    color: var(--color-text);
}

.restore-spinner {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 4px solid var(--color-border);
    border-top-color: var(--color-primary);
    animation: restore-spin 0.9s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
    .restore-spinner {
        animation-duration: 2.4s;
    }
}

/* ---------- Trim warning on the size step ---------- */

/* Deliberately louder than the other hints around it. With no crop tool on
   this page, this line is the only warning a customer gets that part of their
   photograph is about to be cut off. */
.trim-note {
    padding: 10px var(--space-3);
    border-radius: var(--radius-sm);
    background-color: var(--color-warning-light);
    color: var(--color-warning);
    border: 1px solid transparent;
    text-align: left;
}

.trim-note strong {
    font-weight: 700;
}

/* Escalated wording gets escalated colour, so a heavy crop does not look the
   same as a trivial one. */
.trim-note.is-severe {
    font-weight: 500;
}

/* The way out, inline in the sentence. A button rather than a link because it
   acts on this page instead of going anywhere, but it has to read as something
   you can press - a plain <button> inside a warning box reads as decoration. */
.trim-note-link {
    display: inline;
    padding: 0;
    margin: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.trim-note-link:hover,
.trim-note-link:focus-visible {
    text-decoration-thickness: 2px;
}

/* ---------- "Restored" badge in the image galleries ---------- */

.image-item {
    position: relative;
}

.image-item-tag {
    position: absolute;
    left: 6px;
    bottom: 6px;
    padding: 2px 7px;
    border-radius: 999px;
    background-color: rgba(15, 23, 42, 0.7);
    color: #fff;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    pointer-events: none;
}
