
/* ---------- 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="crop"] .step[data-index="1"],
.stepper[data-step="crop"] .step[data-index="2"],
.stepper[data-step="review"] .step[data-index="1"],
.stepper[data-step="review"] .step[data-index="2"],
.stepper[data-step="review"] .step[data-index="3"] {
    color: var(--color-primary-darker);
}

.stepper[data-step="upload"] .step[data-index="1"] .step-circle,
.stepper[data-step="crop"] .step[data-index="1"] .step-circle,
.stepper[data-step="crop"] .step[data-index="2"] .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 {
    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;
}

/* ---------- Crop tool ---------- */
#crop-tool-container {
    border: 1px solid var(--color-border);
    padding: var(--space-3);
    margin-bottom: var(--space-3);
    border-radius: var(--radius-md);
    /* A flat neutral stage rather than the page background, so the photo reads
       as the subject and the crop handles stay visible over pale edges. */
    background-color: var(--color-stage);
    box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.06);
}

#image-container {
    max-width: 100%;
    margin-bottom: 0;
}

#image {
    max-width: 100%;
    height: auto;
}

/* ---------- 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);
}

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

#crop-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;
}

.dimension-row .icon-button {
    flex: 0 0 auto;
}

.icon-button {
    background-color: var(--color-surface);
    color: var(--color-text);
    border: 1.5px solid var(--color-border);
    min-height: 46px;
    padding: 0 var(--space-4);
    border-radius: var(--radius-sm);
    font-size: 14px;
    font-weight: 600;
}

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

/* ---------- 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;
    border-radius: 2px;
    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%;
    }

    .dimension-row .icon-button {
        width: 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;
    }

    #crop-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;
    }
}

/* ---------- AI style filter ---------- */

/* The AI style button sits under the canvas preview. */
.result-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.ai-style-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
}
.ai-style-btn-icon {
    width: 18px;
    height: 18px;
    flex: 0 0 auto;
    fill: currentColor;
}
/* Set once a style is applied, so the button doubles as the indicator that the
   image on screen is no longer the customer's original. */
.ai-style-btn.has-style {
    border-color: var(--color-primary);
    background-color: var(--color-primary-light);
    color: var(--color-primary-darker);
}

.ai-style-intro {
    margin: 0 0 var(--space-4);
    font-size: 14px;
    color: var(--color-text-muted);
}

/* Wider than the generic .overlay .popup cap (min(480px, 90vw)) - at 480px
   the custom-style row below squeezed its input down to a sliver next to the
   Apply button. More specific than that shared rule, so it wins without
   touching any other overlay (My Images, colour picker, ...) that reuses it. */
@media (min-width: 641px) {
    #ai-style-overlay .popup {
        width: min(720px, 92vw);
    }
}

.ai-style-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: var(--space-3);
    margin-bottom: var(--space-5);
}

.ai-style-tile {
    display: block;
    width: 100%;
    padding: 0;
    border: 2px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    overflow: hidden;
    cursor: pointer;
    text-align: center;
    transition: border-color 0.15s ease, transform 0.15s ease;
}
.ai-style-tile:hover:not(:disabled) {
    border-color: var(--color-primary);
    transform: translateY(-2px);
}
.ai-style-tile.selected {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px var(--color-primary-light);
}
.ai-style-tile:disabled {
    opacity: 0.5;
    cursor: default;
}

.ai-style-tile-img {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    background: var(--color-bg);
}
/* Stand-in until make_style_samples.php has been run. Deliberately plain: it
   should read as "thumbnail missing", not as a style in its own right. */
.ai-style-tile-placeholder {
    background: linear-gradient(135deg, var(--color-bg), var(--color-border));
}

.ai-style-tile-label {
    display: block;
    padding: var(--space-2) var(--space-1);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.2;
    color: var(--color-text);
}

/* Its own card, not just a label under the tile grid - on a page full of
   image tiles this was the one text-based option and easy to skim past. */
.ai-style-custom {
    padding: var(--space-4);
    margin-bottom: var(--space-4);
    background: var(--color-primary-light);
    border: 1.5px solid var(--color-primary);
    border-radius: var(--radius-md);
}
.ai-style-custom label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: 15px;
    font-weight: 700;
    color: var(--color-primary-darker);
}
.ai-style-custom-row {
    display: flex;
    gap: var(--space-2);
}
.ai-style-custom-row input {
    flex: 1;
    min-width: 0;
    min-height: 48px;
    padding: 0 var(--space-3);
    border: 1.5px solid var(--color-primary);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    font-family: var(--font);
    font-size: 15px;
}
.ai-style-custom-row input:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px rgba(79, 179, 221, 0.25);
}
.ai-style-custom-row .btn {
    flex: 0 0 auto;
}

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

.ai-style-error {
    display: none;
    margin: var(--space-3) 0 0;
    padding: var(--space-3);
    border-radius: var(--radius-sm);
    background: #fdecec;
    color: var(--color-danger);
    font-size: 13px;
}
.ai-style-error.visible {
    display: block;
}

.ai-style-actions {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-5);
}
.ai-style-actions .btn {
    flex: 1;
}

/* Covers the popup body while a call is in flight. Absolute rather than a
   separate overlay so the popup keeps its size and the grid does not reflow
   when it appears. */
.ai-style-working {
    display: none;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-5);
    background: rgba(255, 255, 255, 0.95);
    border-radius: inherit;
    text-align: center;
}
.ai-style-working.visible {
    display: flex;
}
.ai-style-working p {
    margin: 0;
}
.ai-style-spinner {
    width: 38px;
    height: 38px;
    border: 3px solid var(--color-border);
    border-top-color: var(--color-primary);
    border-radius: 50%;
    animation: ai-style-spin 0.9s linear infinite;
}
@keyframes ai-style-spin {
    to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
    .ai-style-spinner {
        animation-duration: 3s;
    }
    .ai-style-tile:hover:not(:disabled) {
        transform: none;
    }
}

/* ---------- 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));
}

/* ---------- Keep a copy of the styled image ---------- */

.ai-style-save {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin-top: var(--space-4);
    font-size: 13px;
    line-height: 1.4;
    color: var(--color-text-muted);
    cursor: pointer;
}
.ai-style-save input {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin: 0;
    accent-color: var(--color-primary);
}

/* ---------- Still preview, shown while the crop tool is collapsed ---------- */

#crop-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. */
#crop-canvas-preview {
    width: 100%;
    max-width: none;
    height: 34vh;
    min-height: 200px;
    margin: 0 auto var(--space-2);
}
#crop-preview .preview-3d-hint {
    margin: 0;
    font-size: 12px;
    color: var(--color-text-muted);
    text-align: center;
}
#crop-toggle-row {
    margin-bottom: var(--space-5);
    text-align: center;
}
 /* Sits with the crop tool it explains, so it is centred to match the
    toggle beneath it rather than running flush left against the card. */
#shape-lock-note {
    margin: 0 auto var(--space-4);
    max-width: 46ch;
    text-align: center;
}
#crop-toggle-row .btn {
    display: inline-flex;
}
#crop-toggle-hint {
    margin: var(--space-2) 0 0;
}

/* ---------- 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);
}

/* ---------- Note shown when the crop tool opens by itself ---------- */


/* ---------- Crop screen layout ---------- */

/* 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 #crop-canvas-preview {
    height: 30vh;
}

/* Dragging a crop needs room and needs to stay still, so the pin is released
   while the tool is open. */
.crop-layout.is-cropping .crop-layout-media {
    position: static;
}
.crop-layout.is-cropping .crop-layout-media #crop-canvas-preview {
    height: auto;
}

@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 #crop-canvas-preview {
        height: 46vh;
        min-height: 300px;
    }
    .crop-layout.is-cropping .crop-layout-media {
        position: sticky;
    }
    .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; Re-crop 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 #re-crop-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,
    #crop-canvas-preview {
        position: sticky;
        top: calc(var(--header-height) + var(--space-2));
        z-index: 2;
        height: 26vh;
        min-height: 170px;
    }
}

/* ---------- "Apply this crop" prominence while the tool is open ---------- */

/* While cropping there is exactly one next step, so the toggle stops being a
   quiet grey control and becomes the page's obvious action: full width under
   the tool the customer is dragging, filled in the brand colour (the class
   swap happens in openCropTool/collapseCropTool). */
.crop-layout.is-cropping #crop-toggle-row {
    position: sticky;
    bottom: calc(var(--action-bar-height) + var(--space-2) + env(safe-area-inset-bottom));
    z-index: 3;
}
.crop-layout.is-cropping #crop-toggle-btn {
    width: 100%;
    max-width: 460px;
    box-shadow: var(--shadow-md);
}
@media (min-width: 900px) {
    /* Desktop bars are not fixed, so the sticky offset anchors to the
       viewport bottom instead. */
    .crop-layout.is-cropping #crop-toggle-row {
        bottom: var(--space-3);
    }
}

/* ---------- 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;
}

/* ---------- Rotate Selection only while actually cropping ---------- */

/* Rotate swaps the crop selection's orientation - an adjustment to a selection
   the customer cannot see unless the crop tool is open. Shown outside crop
   mode it reads as page clutter and invites changing the crop blind, so it
   follows the tool's own open/closed state (the is-cropping class). */
#crop-section #rotate-button {
    display: none;
}
/* Anchored under #crop-section too, or the two-ID hide rule above would
   outweigh this and keep the button hidden while cropping. */
#crop-section .crop-layout.is-cropping #rotate-button {
    display: inline-block;
}
