/* ════════════════════════════════════════════════════════════════════
 * Tag-customizer styles — 1:1 port of the design-system reference at
 *   design-system.animal-id.net/02_Products/_drafts/image-enhancer-tag-flow/
 *
 * Every rule below is a verbatim copy from the prototype's <style> block,
 * with the selector swapped where the Yii-rendered DOM uses different
 * class names than the prototype:
 *
 *   prototype                    →  Yii / our DOM
 *   .preview                     →  .col-lg-6:first-child
 *   .form                        →  .constructor__wrapper
 *   .form h1                     →  .constructor__heading h1 / h3
 *   .field-group                 →  .form-group  (inside .constructor-form)
 *   .field-group__label          →  .form-label  (also .control-label)
 *   .field-group__label .req     →  .form-group.required .form-label::after
 *   .field-group__hint           →  .hint-block, .form-group-hint
 *   .row (2-col form row)        →  .constructor-form  (grid, 2 cols)
 *   .input                       →  .form-input
 *   .field-group--pulse          →  .form-group--pulse
 *   .note                        →  .constructor__info-text
 *   .cta                         →  #addToCart
 *   .upload-btn                  →  .tc-upload-btn  (kept tc-* prefix)
 *   .upload-btn--dragover        →  .tc-upload-btn--dragover
 *   .ai-badge                    →  .tc-ai-badge
 *   .variants / .variant-grid    →  .tc-variants / .tc-variant-grid
 *   .variant family              →  .tc-variant family
 *   .variant--custom             →  .tc-variant--no-ai
 *   .variant--skel               →  .tc-variant--skel
 *   .progress-copy / .progress-bar → .tc-progress-copy / .tc-progress-bar
 *   .error-banner family         →  .tc-error-banner family
 *   .link-btn                    →  .tc-link-btn
 *   .crop-modal family           →  .tc-crop-modal family
 *
 * Customer-requested divergences from the prototype (kept explicit):
 *   - DRAFT chip removed
 *   - .tag-mockup__photo background-size 100% 100% (prototype is 140%)
 *   - Phone gate ≥ 4 chars (handled in JS — matches prototype)
 *
 * Layout notes:
 *   - Prototype uses CSS Grid: `.layout { grid-template-columns: 45% 1fr }`.
 *     We're on Bootstrap so the top of the file resets `.container` /
 *     `.row` / `.col-lg-6` gutters and re-establishes a 50/50 split with
 *     `flex 0 0 50%`. Customer asked for 40% artwork + 5% green padding
 *     on each side, so the tag wrapper is 80% of the 50% column.
 * ──────────────────────────────────────────────────────────────────── */


/* ════════════════════════════════════════════════════════════════════
 * 1. Design tokens (= prototype :root with `--tc-` prefix)
 * ──────────────────────────────────────────────────────────────────── */
:root {
    --tc-green-500: #4E9D57;
    --tc-green-600: #3F8347;
    --tc-green-100: #E4F1E6;
    --tc-green-050: #F2F8F3;
    --tc-dark-700:  #353535;
    --tc-dark-300:  #8A8A8A;
    --tc-dark-100:  #C9C9C9;
    --tc-dark-050:  #E5E5E5;
    --tc-dark-025:  #F4F4F4;
    --tc-yellow-500:#F9B530;
    --tc-danger-500:#D93838;
    --tc-cream-100: #F6EFE0;
    --tc-white:     #FFFFFF;
    --tc-ease:      cubic-bezier(.2, 0, .2, 1);
}


/* ════════════════════════════════════════════════════════════════════
 * 2. Bootstrap container + row reset, column 50/50 split (≥ 992px)
 *
 * Bootstrap's .container caps at 1140px with auto margins. Without this
 * reset our 50/50 column layout runs inside a 1140px-centred frame and
 * leaves big gutters on the sides at wide viewports.
 * ──────────────────────────────────────────────────────────────────── */
@media (min-width: 992px) {
    .constructorModal { background: #fff; }

    /* Container + row + columns: kill all the Bootstrap gutters so the
       layout fills the viewport edge-to-edge.

       Legacy main.min.css turns `.constructor .container` into a flex
       container (`display: flex; flex-wrap: wrap`). That makes the
       inner `.row` a flex ITEM whose width defaults to `auto` —
       i.e. content-based. The 45%/55% set on the inner columns then
       resolves against the SHRUNK row width instead of the viewport,
       collapsing the whole layout to the intrinsic content size on
       very wide monitors (5K showed a ~10% green strip on the left).
       Force the container back to block AND the row to width: 100%
       so the column percentages reference the full viewport again. */
    .constructor > .container {
        display: block !important;
        max-width: none !important;
        width: 100% !important;
        padding: 0 !important;
    }
    .constructor > .container > .row {
        display: flex !important;
        align-items: stretch !important;
        min-height: 100vh !important;
        margin: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        flex-wrap: nowrap !important;
    }
    /* Reset both padding AND margin on the columns. Bootstrap ships the
       gutter padding; main.min.css ships `.col-sm-offset-1
       { margin-left: 8.33% }` which sits in the form column's class
       list as a smaller-breakpoint helper but still bleeds through at
       lg+ — that 8.33% margin was right-shifting the form panel out of
       the visual centre of the white column. Zero both. */
    .constructor > .container > .row > [class*="col-lg-6"] {
        padding: 0 !important;
        margin: 0 !important;
    }

    /* Legacy .constructor::after painted a 45%-wide green band; we now
       paint backgrounds directly on the columns, so hide it. */
    .constructor::after {
        display: none !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * 3. Preview column = prototype `.preview` (45% of viewport)
     *    Design-system reference: `.layout { grid-template-columns:
     *    45% 1fr }` — the green tag-preview column takes a fixed 45%
     *    of viewport width AT EVERY DESKTOP SIZE, the white form
     *    column takes the remaining 55%. This is what makes the
     *    composition look the same on 1080, 1440, 2560, and 5K.
     * ──────────────────────────────────────────────────────────────── */
    .constructor > .container > .row > [class*="col-lg-6"]:first-child {
        flex: 0 0 45% !important;
        max-width: 45% !important;
        background: var(--tc-green-100) !important;
        display: flex !important;
        align-items: flex-start !important;
        justify-content: center !important;
        padding: 0 !important;
        position: sticky !important;
        top: 0 !important;
        align-self: flex-start !important;
        height: 100vh !important;
        overflow: hidden !important;
    }

    /* Tag-mockup wrapper. Legacy `main.min.css` ships
       `.constructor__badge { width: 458px }` at ≥ 1200px viewports
       and absolutely-positions `.cropResult` over it with fixed px
       offsets (top: 222px, left: 63px). Our earlier override forced
       89% width, which broke the legacy positioning: cropResult-
       content kept its 113-192 px size but landed at the wrong
       coordinates and the per-template `#previewImage` design blew
       out of the white-tag silhouette frame.
       Drop the width override — let legacy CSS keep the 458px badge
       so .cropResult / .copyText / .cropResult-content stay
       coordinated with the tag silhouette. Centre it inside our
       wider preview column with `margin: 0 auto`. */
    .constructor__badge {
        margin: 0 auto !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * 4. Form column = prototype `1fr` track + `.form`
     *    The column itself just centres the form panel horizontally;
     *    `.constructor__wrapper` carries the prototype's `.form` rules.
     * ──────────────────────────────────────────────────────────────── */
    /* Column = CSS Grid centred form-panel track inside the 55%
       white column (= 100% − 45% green preview). Form panel takes
       ~73% of the 55% column = ~40vw absolute, matching the green
       column's ~40vw tag artwork. Geometric centring that's immune
       to float / display / margin-auto overrides leaking in from
       main.min.css (which loads AFTER tag-customizer.css per the
       asset-bundle order — verified via curl). */
    .constructor > .container > .row > [class*="col-lg-6"]:last-child {
        flex: 0 0 55% !important;
        max-width: 55% !important;
        background: #fff !important;
        display: grid !important;
        grid-template-columns: minmax(0, 73%) !important;
        justify-content: center !important;
        align-content: stretch !important;
    }

    /* `.form` (index.html lines 143-150, verbatim) — fills its grid
       track (which is already sized via % in the column rule above);
       no px cap on the wrapper itself any more. */
    .constructor > .container > .row > [class*="col-lg-6"]:last-child > .constructor__wrapper {
        width: 100% !important;
        max-width: 100% !important;
        margin: 0 !important;
        padding: 40px 32px !important;
        display: flex !important;
        flex-direction: column !important;
        justify-content: center !important;
        min-height: 100vh !important;
        background: transparent !important;
        box-sizing: border-box !important;
    }
}


/* Note: an earlier commit added @media (min-width: 1440px) caps
   that fixed the tag at 480px and the form at 640px on wide monitors
   — customer pushback was that the tag and form should keep scaling
   with the viewport on wide screens, not freeze at a px ceiling.
   Reverted those caps; the 80%/85% relative sizing in §3-§4 above is
   the canonical behaviour at every viewport ≥ 992px. If a truly
   absurd monitor (e.g. 3440+) ever needs the cap back, scope it to
   @media (min-width: 2200px) instead. */


/* ════════════════════════════════════════════════════════════════════
 * 5. Tag-mockup family (= prototype `.tag-mockup*`, lines 84-140 verbatim)
 *
 * One customer-requested divergence: background-size on
 * `.tag-mockup__photo` is `100% 100%, cover` rather than the prototype's
 * `140% 140%, cover` (per "Прибери 140% з покращених").
 * ──────────────────────────────────────────────────────────────────── */
.tag-mockup {
    position: relative;
    width: 100%;
    margin: 0 auto;
    filter: drop-shadow(0 18px 40px rgba(0, 0, 0, 0.22));
}
.tag-mockup__base {
    width: 100%;
    display: block;
    user-select: none;
    -webkit-user-drag: none;
}
.tag-mockup__photo {
    position: absolute;
    --tag-photo-left: 14.0%;
    --tag-photo-top:  43.5%;
    --tag-photo-size: 42.5%;
    left:  var(--tag-photo-left);
    top:   var(--tag-photo-top);
    width: var(--tag-photo-size);
    aspect-ratio: 1;
    border-radius: 50%;
    background-image:
        var(--photo-url, none),
        linear-gradient(to right, #FBFBFC 0%, #E7E7E9 50%, #D3D3D5 100%);
    background-size: 100% 100%, cover;          /* customer choice — not prototype's 140% */
    background-position: center, center;
    background-repeat: no-repeat, no-repeat;
    opacity: 0;
    transition: opacity .3s var(--tc-ease);
    pointer-events: none;
}
.tag-mockup__text {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}
.tag-mockup__text text {
    fill: #5C5C5C;
    font-family: 'Roboto', system-ui, sans-serif;
    font-weight: 700;
    font-size: 42px;
    letter-spacing: 3px;
    text-transform: uppercase;
    user-select: none;
}
.tag-mockup__text--placeholder text {
    fill: #BFBFBF;
}
.tag-mockup.has-photo .tag-mockup__photo {
    opacity: 1;
}

/* Legacy preview <img> fallback for card templates not yet migrated to
   `.tag-mockup`. Keeps the old `cropResult-content` flow rendering. */
.cropResult-content #previewImage.tc-has-photo,
#previewImage.tc-has-photo {
    width: 100% !important;
    height: 100% !important;
    object-fit: cover !important;
    border-radius: 50% !important;
    display: block !important;
}


/* ════════════════════════════════════════════════════════════════════
 * 6. Form heading (= prototype `.form h1`, line 155 — the duplicate
 *    declaration that wins by source order: 32px Manrope ExtraBold)
 * ──────────────────────────────────────────────────────────────────── */
.constructor .constructor__heading h1,
.constructor .constructor__heading h3 {
    font: 800 32px/1.15 'Manrope', sans-serif !important;
    color: var(--tc-dark-700) !important;
    margin: 0 0 32px !important;                /* 0 0 var(--s-8) */
    letter-spacing: -.01em !important;
    text-transform: none !important;
}


/* ════════════════════════════════════════════════════════════════════
 * 7. Form group (= prototype `.field-group`, line 156)
 *    Two-column row for nickname + phone: prototype wraps them in `.row`,
 *    we lay them out via the `.constructor-form` grid below.
 * ──────────────────────────────────────────────────────────────────── */
.constructor .constructor-form > .form-group {
    margin-bottom: 10px !important;
}
/* Nickname + phone form-groups — defeat the legacy
   `.constructor .form-group-name, .form-group-number { width: 48%;
   display: inline-block }` rule from main.min.css. `width: auto` +
   `display: block` lets the parent layout (grid on ≥768, flex column
   on mobile) decide the width. The `margin: 0` collapse that puts the
   two inputs on the same row is ONLY safe inside the 2-col grid — on
   mobile (<768) we keep a default bottom gap so they stack with air
   between them; the @media (min-width: 768px) block below collapses
   the gap for the grid context. */
.constructor .constructor-form > .form-group-name,
.constructor .constructor-form > .form-group-number {
    width: auto !important;
    display: block !important;
}

/* `.constructor-form` = 2-col grid (= prototype `.row` for fields).
   Photo + note + button span both columns. Nickname + phone sit
   side-by-side in row 2, each filling its grid cell. Tight 8px gap so
   the two inputs read as a single tight row rather than separated. */
@media (min-width: 768px) {
    .constructor .constructor-form {
        display: grid !important;
        grid-template-columns: 1fr 1fr !important;
        column-gap: 8px !important;
    }
    .constructor .constructor-form > .form-group-tag-customizer,
    .constructor .constructor-form > .constructor__info-text,
    .constructor .constructor-form > .form-group-button {
        grid-column: 1 / -1 !important;
    }
    /* Inside the grid, collapse the form-group bottom margin on nick +
       phone — grid `row-gap` (we don't set one, but margin: 0 keeps the
       row tight against the photo block above). */
    .constructor .constructor-form > .form-group-name,
    .constructor .constructor-form > .form-group-number {
        margin: 0 !important;
    }
}


/* ════════════════════════════════════════════════════════════════════
 * 8. Form label (= prototype `.field-group__label`, line 157)
 *    + required-asterisk (= prototype `.req`, line 158)
 * ──────────────────────────────────────────────────────────────────── */
.constructor .form-label,
.constructor .control-label {
    display: block !important;
    font: 600 14px/1 'Manrope', sans-serif !important;
    color: var(--tc-dark-700) !important;
    margin-bottom: 6px !important;
    text-transform: none !important;
}
.constructor .form-group.required > .form-label::after,
.constructor .form-group.required > .control-label::after {
    content: ' *';
    color: var(--tc-danger-500);
}


/* ════════════════════════════════════════════════════════════════════
 * 9. Hint (= prototype `.field-group__hint`, line 159)
 * ──────────────────────────────────────────────────────────────────── */
.constructor .hint-block,
.constructor .form-group-hint,
.constructor .field-group__hint {
    display: block !important;
    font: 400 14px/1.5 'Manrope', sans-serif !important;
    color: var(--tc-dark-300) !important;
    margin: 0 0 8px !important;                 /* tightened again — customer wants rows closer */
}


/* ════════════════════════════════════════════════════════════════════
 * 10. Upload button (= prototype `.upload-btn`, lines 161-191)
 * ──────────────────────────────────────────────────────────────────── */
.tc-upload-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;                                  /* var(--s-3) */
    width: 100%;
    padding: 22px 24px;
    background: linear-gradient(135deg, var(--tc-green-500), var(--tc-green-600));
    color: var(--tc-white);
    border: 0;
    border-radius: 16px;                        /* var(--r-lg) */
    font: 700 17px/1 'Roboto', sans-serif;
    cursor: pointer;
    letter-spacing: .01em;
    text-transform: none;
    box-shadow:
        0 6px 18px rgba(78, 157, 87, 0.25),
        0 0 0 0 rgba(78, 157, 87, 0);
    animation: tc-upload-attention 3s cubic-bezier(.4, 0, .4, 1) infinite;
    transition: transform 160ms var(--tc-ease),
                box-shadow 160ms var(--tc-ease),
                filter 160ms var(--tc-ease);
}
.tc-upload-btn:hover {
    animation: none;
    transform: translateY(-2px);
    filter: brightness(1.06);
    box-shadow: 0 12px 28px rgba(78, 157, 87, 0.35),
                0 0 0 6px rgba(78, 157, 87, 0.18);
}
.tc-upload-btn--dragover {
    animation: none;
    transform: scale(1.01);
    box-shadow: 0 10px 24px rgba(78, 157, 87, 0.4),
                0 0 0 10px rgba(78, 157, 87, 0.28);
}
.tc-upload-btn svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}
@keyframes tc-upload-attention {
    0%, 100% { box-shadow: 0 6px 18px rgba(78, 157, 87, 0.25), 0 0 0 0   rgba(78, 157, 87, 0.55); }
    50%      { box-shadow: 0 6px 18px rgba(78, 157, 87, 0.25), 0 0 0 14px rgba(78, 157, 87, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .tc-upload-btn {
        animation: none;
        box-shadow: 0 6px 18px rgba(78, 157, 87, 0.25),
                    0 0 0 6px rgba(78, 157, 87, 0.18);
    }
}

/* Hide Yii's native <input type="file"> — the custom button triggers it. */
.tc-native-file {
    display: none !important;
}

/* Re-establish HTML5 [hidden] for our own elements; the legacy
   `.constructor-form *` selectors fight back. */
.tc-variants[hidden],
.tc-progress-copy[hidden],
.tc-progress-bar[hidden],
.tc-error-banner[hidden],
.tc-error-banner__details[hidden],
.tc-upload-btn[hidden] {
    display: none !important;
}


/* ════════════════════════════════════════════════════════════════════
 * 11. AI badge (= prototype `.ai-badge`, lines 193-210)
 * ──────────────────────────────────────────────────────────────────── */
.tc-ai-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;                                   /* var(--s-2) */
    padding: 10px 16px;
    border-radius: 999px;                       /* var(--r-pill) */
    background: linear-gradient(135deg, #FFD24A 0%, #F4A916 100%);
    border: 0;
    font: 700 13px/1 'Manrope', sans-serif;
    color: var(--tc-dark-700);
    letter-spacing: .02em;
    margin-bottom: 16px;                        /* var(--s-4) */
    box-shadow: 0 4px 14px rgba(244, 169, 22, 0.28);
}
.tc-ai-badge__emoji {
    font-size: 16px;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.18));
}
.tc-ai-badge__emoji span {
    animation: tc-emoji-bob 2.6s ease-in-out infinite;
    display: inline-block;
}
.tc-ai-badge__emoji span:nth-child(2) {
    animation-delay: .3s;
}
.tc-ai-badge__sub {
    color: rgba(30, 30, 30, .65);
    font-weight: 500;
    margin-left: 6px;
}
@keyframes tc-emoji-bob {
    0%, 100% { transform: translateY(0) rotate(0); }
    50%      { transform: translateY(-2px) rotate(-4deg); }
}


/* ════════════════════════════════════════════════════════════════════
 * 12. Variants area (= prototype `.variants` / `.variant-grid` /
 *     `.variant` / `.variant--custom` / `.variant--skel`,
 *     lines 212-299)
 * ──────────────────────────────────────────────────────────────────── */
.tc-variants {
    margin-top: 8px;                            /* var(--s-2) */
}
.tc-variant-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
    margin-bottom: 12px;                        /* var(--s-3) */
}
.tc-variant {
    position: relative;
    box-sizing: border-box;
    border-radius: 12px;                        /* var(--r-md) */
    border: none;
    cursor: pointer;
    background: var(--tc-white);
    padding: 16px;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* AI variant tiles use space-between so the image is anchored to
       the top of the tile and the label to the bottom — combined with
       the fixed 2-line label height below, every image's bottom edge
       and every label's centre align across the row regardless of
       label wrap. The Custom Crop tile (.tc-variant--no-ai) keeps its
       `justify-content: center` (overridden below) because it has 3
       children (icon + label + hint) that read better centred. */
    justify-content: space-between;
    gap: 14px;
    transition: transform 120ms var(--tc-ease),
                box-shadow 120ms var(--tc-ease);
}
.tc-variant:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .08);
}
.tc-variant__img {
    width: 100%;
    aspect-ratio: 1;
    border-radius: 50%;
    /* Customer kept the variants at the same framing as the main
       preview (no over-cover) — see tag-mockup__photo above. */
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
    transition: box-shadow 140ms var(--tc-ease);
}
.tc-variant.is-selected .tc-variant__img {
    box-shadow:
        0 0 0 3px var(--tc-green-500),
        0 0 0 7px var(--tc-green-100),
        0 2px 6px rgba(0, 0, 0, 0.06);
}
.tc-variant__label {
    background: transparent;
    color: var(--tc-dark-700);
    font: 600 12px/1.2 'Manrope', sans-serif;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    text-align: center;
    /* Lock the label box height to 2 lines AND vertically-centre the
       text inside it. Why: when a label wraps (e.g. UA "Покращений
       колір" at narrow tile widths), its tile becomes taller, and the
       3-col grid row stretches to match. Single-line labels in the
       stretched short tiles default to top-of-content alignment,
       which made the single-line text sit at the same Y as line 1 of
       the wrapped label — visually placing the wrapped label "higher"
       than its siblings. Fixing the box to 2-line height with centred
       text means a 1-line label appears at the vertical midpoint of
       the box, matching the visual centre of a 2-line wrapped
       sibling. */
    min-height: calc(2 * 1.2em);
    line-height: 1.2;
    width: 100%;
}
.tc-variant__check {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--tc-green-500);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 8px;
    right: 8px;
    opacity: 0;
    transition: opacity 120ms var(--tc-ease);
    box-shadow: 0 2px 6px rgba(0, 0, 0, .25);
    z-index: 2;
}
.tc-variant.is-selected .tc-variant__check {
    opacity: 1;
}
.tc-variant__check svg {
    width: 14px;
    height: 14px;
}

/* No-AI / Custom Crop tile (= prototype `.variant--custom`, lines 268-288).
   Our markup uses `.tc-variant--no-ai`. */
.tc-variant--no-ai {
    background: var(--tc-cream-100);
    border: 2px dashed var(--tc-dark-100);
    gap: 8px;
    color: var(--tc-dark-700);
    justify-content: center;
}
.tc-variant--no-ai:hover {
    border-color: var(--tc-green-500);
    background: var(--tc-green-050);
}
.tc-variant--no-ai .tc-variant__label {
    font-weight: 700;
}
.tc-variant__hint {
    color: var(--tc-dark-300);
    font: 500 11px/1.3 'Manrope', sans-serif;
    text-align: center;
}

/* Skeleton tile (= prototype `.variant--skel`, lines 291-298) */
.tc-variant--skel {
    cursor: default;
    pointer-events: none;
    min-height: 140px;
}
.tc-variant--skel::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 12px;
    background: linear-gradient(90deg,
        var(--tc-dark-025) 0%,
        #ECECEC 50%,
        var(--tc-dark-025) 100%);
    background-size: 200% 100%;
    animation: tc-shimmer 1.4s ease-in-out infinite;
}
@keyframes tc-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}


/* ════════════════════════════════════════════════════════════════════
 * 13. Progress copy + bar (= prototype `.progress-copy`, `.progress-bar`,
 *     lines 301-334)
 * ──────────────────────────────────────────────────────────────────── */
.tc-progress-copy {
    font: 500 13px/1.4 'Manrope', sans-serif;
    color: var(--tc-dark-300);
    margin: 0 0 8px;
}
.tc-progress-copy strong {
    color: var(--tc-dark-700);
    font-weight: 600;
}
.tc-progress-bar {
    position: relative;
    height: 6px;
    background: var(--tc-dark-100);
    border-radius: 999px;
    overflow: hidden;
    margin-bottom: 16px;                        /* var(--s-4) */
    container-type: inline-size;
}
.tc-progress-bar__fill {
    position: absolute;
    inset: 0;
    right: auto;
    width: 0%;
    background: linear-gradient(90deg,
        #F4A916 0%,
        #F4A916 15%,
        var(--tc-green-500) 75%,
        var(--tc-green-500) 100%);
    background-size: 100cqw 100%;
    background-repeat: no-repeat;
    background-position: left center;
    border-radius: 999px;
    transition: width 200ms linear;
}
.tc-progress-bar__fill::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(255, 255, 255, .55) 50%,
        transparent 100%);
    animation: tc-progress-shimmer 1.4s ease-in-out infinite;
}
@keyframes tc-progress-shimmer {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(100%); }
}


/* ════════════════════════════════════════════════════════════════════
 * 14. Error banner (= prototype `.error-banner`, lines 337-350)
 * ──────────────────────────────────────────────────────────────────── */
.tc-error-banner {
    background: #FBE2E2;
    border: 1px solid #F0BCBC;
    color: #8B1F1F;
    padding: 14px 16px;
    border-radius: 12px;                        /* var(--r-md) */
    font: 500 14px/1.5 'Manrope', sans-serif;
    margin-bottom: 12px;                        /* var(--s-3) */
}
.tc-error-banner strong {
    display: block;
    margin-bottom: 4px;
    font-weight: 700;
}
.tc-error-banner__cta {
    color: var(--tc-green-600);
    font-weight: 700;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}
.tc-error-banner__details {
    margin-top: 8px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
    background: rgba(0, 0, 0, .06);
    padding: 8px 10px;
    border-radius: 6px;
    color: #5C5C5C;
    max-height: 80px;
    overflow: auto;
    word-break: break-all;
}
.tc-error-banner__toggle {
    font-size: 12px;
    color: #8B1F1F;
    cursor: pointer;
    opacity: .75;
    margin-top: 4px;
    display: inline-block;
}
.tc-error-banner__toggle:hover {
    opacity: 1;
}


/* ════════════════════════════════════════════════════════════════════
 * 15. Inputs (= prototype `.input`, lines 354-363) +
 *     field-pulse (= prototype `.field-group--pulse`, lines 365-375)
 * ──────────────────────────────────────────────────────────────────── */
.constructor .form-input,
.constructor input[type="text"],
.constructor input[type="tel"],
.constructor input[type="email"],
.constructor input.text-upper,
.constructor .form-input.text-upper {
    width: 100% !important;
    /* Customer clarified: inputs should be WIDER, not taller. Reset
       height to a sane prototype-spec size — legacy `height: 40px`
       was too clamped, my previous 64px was too tall. ~50px split
       the difference (matches prototype's natural padding+line-height). */
    height: auto !important;
    min-height: 50px !important;
    padding: 14px 16px !important;              /* prototype value */
    border: 1.5px solid var(--tc-dark-050) !important;
    border-radius: 12px !important;
    font: 400 16px/1.4 'Roboto', system-ui, sans-serif !important;
    line-height: 1.4 !important;                /* legacy ships line-height: 14px */
    color: var(--tc-dark-700) !important;
    background: #fff !important;
    text-transform: none !important;
    transition: border-color 160ms var(--tc-ease),
                box-shadow 160ms var(--tc-ease),
                background 160ms var(--tc-ease) !important;
}
/* Each browser's placeholder pseudo gets its own rule. Grouped
   selectors with vendor-prefixed pseudos are silently dropped by some
   engines if one of the names is unrecognised — split out so the
   recognised pseudo for the current browser always applies, both for
   the colour AND the text-transform: none override (legacy main.min.css
   has lowercase-forcing rules on -moz/-ms placeholders that were
   turning "Buddy" into "buddy"). */
.constructor .form-input::placeholder,
.constructor input[type="text"]::placeholder,
.constructor input[type="tel"]::placeholder {
    color: var(--tc-dark-300) !important;
    text-transform: none !important;
}
.constructor .form-input::-webkit-input-placeholder,
.constructor input[type="text"]::-webkit-input-placeholder,
.constructor input[type="tel"]::-webkit-input-placeholder {
    color: var(--tc-dark-300) !important;
    text-transform: none !important;
}
.constructor .form-input::-moz-placeholder,
.constructor input[type="text"]::-moz-placeholder,
.constructor input[type="tel"]::-moz-placeholder {
    color: var(--tc-dark-300) !important;
    text-transform: none !important;
    opacity: 1 !important;                      /* Firefox dims placeholders by default */
}
.constructor .form-input:-ms-input-placeholder,
.constructor input[type="text"]:-ms-input-placeholder,
.constructor input[type="tel"]:-ms-input-placeholder {
    color: var(--tc-dark-300) !important;
    text-transform: none !important;
}
.constructor .form-input::-ms-input-placeholder,
.constructor input[type="text"]::-ms-input-placeholder,
.constructor input[type="tel"]::-ms-input-placeholder {
    color: var(--tc-dark-300) !important;
    text-transform: none !important;
}
.constructor .form-input:focus,
.constructor input[type="text"]:focus,
.constructor input[type="tel"]:focus,
.constructor input[type="email"]:focus {
    outline: none !important;
    border-color: var(--tc-green-500) !important;
    background: #fff !important;
    box-shadow: 0 0 0 4px rgba(78, 157, 87, .18) !important;
}
.constructor .form-input:not(:placeholder-shown),
.constructor input[type="text"]:not(:placeholder-shown),
.constructor input[type="tel"]:not(:placeholder-shown) {
    background: var(--tc-green-050) !important;
    border-color: var(--tc-green-100) !important;
}

/* Field-pulse — JS toggles `.form-group--pulse` on the next-up field. */
.constructor .form-group--pulse .form-input,
.constructor .form-group--pulse input[type="text"],
.constructor .form-group--pulse input[type="tel"] {
    border-color: var(--tc-green-500) !important;
    animation: tc-field-pulse 1.6s ease-in-out infinite;
}
@keyframes tc-field-pulse {
    0%, 100% { box-shadow: 0 0 0 0   rgba(78, 157, 87, 0.42); }
    50%      { box-shadow: 0 0 0 8px rgba(78, 157, 87, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .constructor .form-group--pulse .form-input,
    .constructor .form-group--pulse input[type="text"],
    .constructor .form-group--pulse input[type="tel"] {
        animation: none;
        box-shadow: 0 0 0 4px rgba(78, 157, 87, 0.22) !important;
    }
}


/* ════════════════════════════════════════════════════════════════════
 * 16. Note (= prototype `.note` + `.note::before`, lines 377-378)
 * ──────────────────────────────────────────────────────────────────── */
.constructor .constructor__info-text {
    font: 400 13px/1.5 'Manrope', sans-serif !important;
    color: var(--tc-green-600) !important;
    margin: 16px 0 !important;                  /* var(--s-4) 0 */
    display: flex !important;
    align-items: center !important;
    gap: 8px !important;
    border: 0 !important;
    background: transparent !important;
    padding: 0 !important;
}
.constructor .constructor__info-text::before {
    content: 'ⓘ';
    font-size: 14px;
    color: var(--tc-green-600);
    flex-shrink: 0;
}
/* Hide Yii's legacy <img class="lazyload"> icon — our ⓘ glyph replaces it. */
.constructor .constructor__info-text img {
    display: none !important;
}


/* ════════════════════════════════════════════════════════════════════
 * 17. CTA (= prototype `.cta`, lines 381-392)
 *
 * The prototype's `.cta` is `display: inline-flex` but the parent
 * `.form` is `flex-direction: column` with `align-items: stretch`
 * default, so it renders full-width. Our `.form-group-button` wrapper
 * is plain block; setting `width: 100%` explicitly gets the same look.
 * ──────────────────────────────────────────────────────────────────── */
/* CTA — prototype canon padding (18px 36px, ~56px natural height)
   stretched to the form content width per customer preference.
   Earlier iterations bounced between `inline-flex` (Dima's index.html
   reference, content-fit) and `flex + width 100%` (customer wants
   the row-spanning visual weight). Settled state:
     • display: flex          → block-line CTA
     • width: 100%            → fills the form-row content width
     • padding: 18px 36px     → prototype canonical vertical rhythm
     • min-height: 0          → drop earlier 72px artificial floor
   No wrapper text-align centring needed — the button itself is now
   full-width so it occupies the row entirely. */
.constructor .form-group-button {
    /* Legacy main.min.css paints a `border-top: 1px solid #d0d0d5;
       padding-top: 40px` separator above the CTA — the design-system
       prototype has no such rule. Strip both so the CTA sits flush
       against the info-text + 16px breathing room. */
    border-top: 0 !important;
    padding-top: 0 !important;
    margin-top: 16px !important;
}
.constructor #addToCart,
.constructor .form-group-button button {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 8px !important;                        /* var(--s-2) */
    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;                    /* defeat legacy fixed-height */
    min-height: 0 !important;
    padding: 18px 36px !important;              /* prototype canon */
    background: linear-gradient(135deg, var(--tc-green-500), var(--tc-green-600)) !important;
    color: #fff !important;
    border: 0 !important;
    border-radius: 999px !important;            /* var(--r-pill) */
    font: 700 16px/1 'Manrope', sans-serif !important;
    cursor: pointer !important;
    text-align: center !important;
    text-transform: none !important;
    letter-spacing: .01em !important;
    box-shadow: 0 8px 22px rgba(78, 157, 87, 0.3) !important;
    transition: transform 160ms var(--tc-ease),
                box-shadow 160ms var(--tc-ease),
                filter 160ms var(--tc-ease) !important;
}
.constructor #addToCart:hover:not(:disabled),
.constructor .form-group-button button:hover:not(:disabled) {
    transform: translateY(-2px) !important;
    filter: brightness(1.05) !important;
    box-shadow: 0 12px 28px rgba(78, 157, 87, 0.4) !important;
}

/* Pull the legacy `.btnArrow-right::after` arrow into the flex flow
   so it sits next to the text via the button's `gap: 8px`, instead
   of `position: absolute; right: 16px` which floated it to the right
   edge of the row. The legacy class can't be dropped from the markup
   without rippling other site rules, so override here. */
.constructor #addToCart::after,
.constructor .form-group-button button::after {
    position: static !important;
    transform: none !important;
    top: auto !important;
    right: auto !important;
    margin-left: 0 !important;            /* flex gap handles spacing */
    width: 14px !important;
    height: 14px !important;
    background-position: center !important;
    background-repeat: no-repeat !important;
    background-size: contain !important;
    flex-shrink: 0;
}
.constructor #addToCart:disabled,
.constructor .form-group-button button:disabled {
    opacity: .35 !important;
    cursor: not-allowed !important;
    box-shadow: none !important;
}


/* ════════════════════════════════════════════════════════════════════
 * 18. Link button (= prototype `.link-btn`, lines 394-398)
 * ──────────────────────────────────────────────────────────────────── */
.tc-link-btn {
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    color: var(--tc-green-600);
    font: 600 13px/1 'Manrope', sans-serif;
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* Back-button at the top-left of the constructor.
   Prototype's `.back-btn` (index.html lines 42-50).

   The container needs an override of its own — main.min.css ships
   `.constructor__back { position: absolute; top: 15px; left: 15px;
   width: auto; font-size: 0; background: #fff; border-radius: 18px }`,
   which is fine geometrically but sits behind a SECOND rule
   `.constructor__back button.btnStyle.btnTransparent-left { height:
   32px; width: 115px; font-size: 11px; padding: 0 7px 0 20px;
   line-height: 1em }`. That 115px width is too narrow for "todos
   los diseños" / "alle Designs" — text wraps onto two lines and
   the fixed 32px height crops the second line.

   The selector below matches the same compound the legacy uses so
   it wins on equal specificity via `!important`, AND we cover
   `.constructor__back a#constructorClose` for the constructor.php
   anchor variant. */
.constructor__back {
    position: absolute !important;
    top: 16px !important;
    left: 16px !important;
    width: auto !important;
    background: transparent !important;
    border-radius: 0 !important;
    font-size: 0 !important;
    z-index: 199 !important;
}
.constructor__back button.btnStyle.btnTransparent-left,
.constructor__back a.btnStyle.btnTransparent-left,
.constructor #constructorClose,
.constructor a#constructorClose,
.constructor button#constructorClose {
    display: inline-flex !important;
    align-items: center !important;
    gap: 8px !important;
    background: var(--tc-white) !important;
    color: var(--tc-dark-700) !important;
    border: 1px solid var(--tc-dark-050) !important;
    height: auto !important;
    min-height: 40px !important;
    width: auto !important;
    /* Extra-wide left padding so the ::after arrow icon (positioned
       at `left: 12px`, width 10px) doesn't push into the start of the
       text. Customer flagged the icon overlapping the first letter. */
    padding: 10px 20px 10px 36px !important;
    border-radius: 999px !important;
    font: 600 13px/1.4 'Manrope', sans-serif !important;
    cursor: pointer !important;
    white-space: nowrap !important;
    text-transform: none !important;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .05) !important;
    letter-spacing: 0 !important;
}
.constructor__back button.btnStyle.btnTransparent-left:hover,
.constructor__back a.btnStyle.btnTransparent-left:hover,
.constructor #constructorClose:hover,
.constructor a#constructorClose:hover,
.constructor button#constructorClose:hover {
    border-color: var(--tc-dark-100) !important;
}
/* The legacy `.btnTransparent-left::after` paints a left-arrow icon
   via `background-image` at `left: 0` / `left: 8px` of the button.
   Move it a couple px inside the new padding so it sits inside the
   pill instead of touching the border. */
.constructor__back button.btnStyle.btnTransparent-left::after,
.constructor__back a.btnStyle.btnTransparent-left::after {
    left: 12px !important;
}

/* DRAFT chip removed per customer — was a side-by-side comparison marker,
   not a final-design element. */
.constructor__draft-chip {
    display: none !important;
}


/* While we're showing the variants area, hide the upload button (JS
   does the toggle too, this is the defensive CSS hint). */
.tc-variants:not([hidden]) ~ .tc-upload-btn,
.tc-variants:not([hidden]) + .tc-upload-btn {
    display: none;
}


/* ════════════════════════════════════════════════════════════════════
 * 19. Crop modal (= prototype `.crop-modal` family, lines 400-425)
 * ──────────────────────────────────────────────────────────────────── */
.tc-crop-modal {
    position: fixed;
    inset: 0;
    /* Legacy `.constructorModal` ships z-index: 300; our cropper
       opens ON TOP of an already-open constructor, so it needs to
       beat that. 1000 leaves headroom for any other future overlay
       (cart popup is z-index: 200, intercom widget is much higher
       but we hide that via the .tc-modal-open scope-block below). */
    z-index: 1000;
    background: rgba(0, 0, 0, .55);
    display: none;
    align-items: center;
    justify-content: center;
    padding: 16px;                              /* var(--s-4) */
    /* Mobile-only safeguard: even with `touch-action: none` on the
       stage, single-finger swipes in the modal's empty regions (above
       the title, around the slider) could trigger pull-to-refresh /
       page-scroll on iOS. `touch-action: none` here neutralises that
       throughout the dialog area. */
    touch-action: none;
    overscroll-behavior: contain;
}
.tc-crop-modal.is-open {
    display: flex;
}
/* Scroll-lock the underlying page while the cropper is open. JS
   toggles `tc-modal-open` on <html> in openCropModal/closeCropModal.
   Without this, on mobile the body keeps scrolling under the dialog
   when the customer scrolls inside the modal. */
html.tc-modal-open,
html.tc-modal-open body {
    overflow: hidden !important;
    overscroll-behavior: none;
    /* Belt-and-braces: prevent the address bar UI from jumping the
       viewport height while pinch-zoom happens inside the cropper. */
    touch-action: pan-x pan-y;
}

/* Hide ALL bottom-right floating widgets while the cropper modal is
   open. Project-specific selectors:
     • .basket / .basket__* — header cart widget
     • Intercom chat launcher (covers any .intercom-* class variant)
     • Mautic / HubSpot widget catch-alls
     • Generic .cart-fab / [data-fab] / position-fixed selectors
   The modal already covers the viewport visually; these widgets aren't
   useful while the dialog is open, so neutralise them. They reappear
   on close (JS removes `tc-modal-open` from <html>). */
html.tc-modal-open .basket,
html.tc-modal-open .basket__icon-wrap,
html.tc-modal-open .basket__icon,
html.tc-modal-open .basket__content,
html.tc-modal-open .intercom-lightweight-app,
html.tc-modal-open .intercom-app,
html.tc-modal-open .intercom-namespace,
html.tc-modal-open .intercom-launcher,
html.tc-modal-open .intercom-launcher-frame,
html.tc-modal-open [class^="intercom-"],
html.tc-modal-open [class*=" intercom-"],
html.tc-modal-open #intercom-container,
html.tc-modal-open #intercom-frame,
html.tc-modal-open #mtcWidgetWrapper,
html.tc-modal-open .mtc-chat,
html.tc-modal-open .hs-message-frame,
html.tc-modal-open .hs-shadow-container,
html.tc-modal-open .cart-fab,
html.tc-modal-open [data-fab],
html.tc-modal-open iframe[name^="intercom-"],
html.tc-modal-open iframe[title*="chat" i],
html.tc-modal-open iframe[src*="intercom"],
html.tc-modal-open iframe[src*="mautic"] {
    display: none !important;
    visibility: hidden !important;
    opacity: 0 !important;
    pointer-events: none !important;
}
.tc-crop-modal__shell {
    background: #fff;
    border-radius: 16px;                        /* var(--r-lg) */
    width: min(640px, 100%);
    /* Cap shell height to viewport so on short windows the action
       buttons stay reachable (Maks reported they could fall below
       the fold on smaller resolutions). 32px = the modal's outer
       16px padding × 2. The shell becomes scrollable via overflow-y
       on viewports too short to fit title + stage + slider +
       actions outright. */
    max-height: calc(100vh - 32px);
    overflow-y: auto;
    padding: 24px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, .3);
    display: flex;
    flex-direction: column;
}
.tc-crop-modal__shell::-webkit-scrollbar {
    width: 6px;
}
.tc-crop-modal__shell::-webkit-scrollbar-thumb {
    background: var(--tc-dark-100);
    border-radius: 3px;
}
.tc-crop-modal__title {
    font: 700 22px/1.2 'Manrope', sans-serif;
    margin: 0 0 16px;                           /* 0 0 var(--s-4) */
}
.tc-crop-stage {
    position: relative;
    /* Cap stage WIDTH (not height) so the square never exceeds
       60vh — otherwise on a 600px-tall window the 640px shell width
       would translate to 640px stage height via aspect-ratio: 1 and
       push title + slider + actions below the fold. max-width with
       aspect-ratio preserves the square mask + drag geometry the
       cropping math depends on. */
    width: 100%;
    max-width: min(100%, 60vh);
    aspect-ratio: 1;
    margin: 0 auto;
    flex-shrink: 0;
    background: #1F1F1F;
    border-radius: 12px;                        /* var(--r-md) */
    overflow: hidden;
    cursor: grab;
    user-select: none;
    -webkit-user-select: none;
    /* CRITICAL for mobile gestures: take over ALL touches so the
       browser doesn't intercept single-finger drag as page-scroll OR
       two-finger pinch as native page-zoom. Without this, the pointer
       handlers in tags.templates.js never see consistent events on
       mobile Safari / Chrome. */
    touch-action: none;
    -ms-touch-action: none;
    overscroll-behavior: contain;
    -webkit-tap-highlight-color: transparent;
}
.tc-crop-stage.is-dragging,
.tc-crop-stage:active {
    cursor: grabbing;
}
.tc-crop-stage img {
    position: absolute;
    user-select: none;
    -webkit-user-select: none;
    pointer-events: none;
    -webkit-user-drag: none;
    /* Defeat legacy main.min.css `img { max-width: 100%; height: auto }`
       — that rule was clamping the inline `width: <px>` set by
       openCropModal() for landscape photos (where image natural width
       > height): the inline width got clipped to stage width while
       the inline height stayed untouched (no max-height in legacy),
       producing a horizontally-squished render. Setting max-* to
       `none` here means JS-set explicit width/height in px are
       respected verbatim, preserving the source aspect ratio. */
    max-width: none !important;
    max-height: none !important;
    min-width: 0 !important;
    min-height: 0 !important;
}
/* Dim mask OUTSIDE the crop circle so the customer sees what will
   be kept (inside the white guide) vs cut (the dark area outside).
   Hard radial-gradient stop (no smooth transition) avoids the
   faint second-ring artefact at the gradient interpolation edge.

   Geometry trap: `radial-gradient(circle at center, ...)` defaults
   to `farthest-corner` extent — 100% = stage diagonal / 2 = ~70.7%
   of width. With that default a "transparent 40%" stop would sit
   at 28.3% of stage width, way smaller than the guide circle
   (80% width = 40% radius). Result: dim mask intrudes INTO the
   guide circle, customer sees a tiny clear hole.

   Fix: pin the gradient extent to `closest-side` so 100% = half
   stage width. Then `transparent 80%` resolves to 40% of stage
   width — exactly matching the guide circle's 40% radius. */
.tc-crop-stage::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(
        circle closest-side at center,
        transparent 80%,
        rgba(0, 0, 0, .55) calc(80% + 1px)
    );
}
.tc-crop-circle-guide {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 80%;            /* must match --d above */
    aspect-ratio: 1;
    border: 2px solid #fff;
    border-radius: 50%;
    pointer-events: none;
}
.tc-crop-zoom {
    display: flex;
    align-items: center;
    gap: 12px;                                  /* var(--s-3) */
    margin: 16px 0;                             /* var(--s-4) 0 */
    font: 500 13px/1 'Manrope', sans-serif;
    color: var(--tc-dark-300);
}
.tc-crop-zoom input[type="range"] {
    flex: 1;
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    height: 24px;
    cursor: pointer;
}
.tc-crop-zoom input[type="range"]::-webkit-slider-runnable-track {
    height: 4px;
    background: var(--tc-dark-100);
    border-radius: 999px;
}
.tc-crop-zoom input[type="range"]::-moz-range-track {
    height: 4px;
    background: var(--tc-dark-100);
    border-radius: 999px;
}
.tc-crop-zoom input[type="range"]::-webkit-slider-thumb {
    appearance: none;
    -webkit-appearance: none;
    width: 18px;
    height: 18px;
    background: var(--tc-green-500);
    border-radius: 50%;
    margin-top: -7px;
    cursor: grab;
}
.tc-crop-zoom input[type="range"]::-moz-range-thumb {
    width: 18px;
    height: 18px;
    background: var(--tc-green-500);
    border-radius: 50%;
    border: 0;
    cursor: grab;
}
.tc-crop-zoom input[type="range"]:active::-webkit-slider-thumb { cursor: grabbing; }
.tc-crop-zoom input[type="range"]:active::-moz-range-thumb     { cursor: grabbing; }
.tc-crop-actions {
    display: flex;
    justify-content: space-between;
    gap: 12px;                                  /* var(--s-3) */
}
.tc-crop-actions__cancel {
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    color: var(--tc-dark-300);
    font: 600 13px/1 'Manrope', sans-serif;
    text-decoration: underline;
    text-underline-offset: 3px;
}
.tc-crop-actions__cancel:hover {
    color: var(--tc-dark-700);
}
.tc-crop-actions__apply {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 14px 28px;
    background: linear-gradient(135deg, var(--tc-green-500), var(--tc-green-600));
    color: #fff;
    border: 0;
    border-radius: 999px;
    font: 700 14px/1 'Manrope', sans-serif;
    cursor: pointer;
    box-shadow: 0 8px 22px rgba(78, 157, 87, 0.3);
    transition: transform 160ms var(--tc-ease),
                box-shadow 160ms var(--tc-ease),
                filter 160ms var(--tc-ease);
}
.tc-crop-actions__apply:hover {
    transform: translateY(-2px);
    filter: brightness(1.05);
    box-shadow: 0 12px 28px rgba(78, 157, 87, 0.4);
}


/* ════════════════════════════════════════════════════════════════════
 * 20. Tablet + Mobile (< 992px) — 1:1 port of mobile.html
 *
 * Strategy:
 *   • @media (max-width: 991.98px) → tablet AND mobile:
 *       kill the 50/50 split, reset Bootstrap col-sm-offset/-10 indent,
 *       paint the green preview at full width with the tag flush at top,
 *       paint the white form panel at full width directly underneath.
 *   • @media (max-width: 767.98px) → true mobile (mobile.html parity):
 *       border-radius card overlap, stacked inputs, downsized
 *       variant/AI-badge/CTA, env(safe-area-inset-bottom) for notch.
 *
 * Legacy traps cleared here:
 *   1. `.col-sm-offset-1 col-sm-10` on the form column → margin-left 8.33%
 *      indent on tablets. Zeroed.
 *   2. `.constructor::after` legacy 45% green band repaint → hidden on
 *      both desktop AND mobile (was only hidden in the ≥992 block).
 *   3. `.form-group-name, .form-group-number { width: 48%; display:
 *      inline-block }` from main.min.css → full-width stack on mobile.
 *   4. `.btnStyle.btnTransparent-left { height: 32px; width: 115px }`
 *      shrunk back-btn — mobile gets its own 36px-min-height variant.
 * ──────────────────────────────────────────────────────────────────── */

/* `.constructor::after` paints the legacy 45% green band — desktop
   block already hides it, but it leaks through on tablets/mobile
   where the new column backgrounds take over. Hide unconditionally. */
.constructor::after {
    display: none !important;
}

@media (max-width: 991.98px) {
    /* Container + row — kill Bootstrap's 1140px cap and side gutters
       so the green preview + white form fill the viewport edge-to-edge. */
    .constructor > .container {
        max-width: none !important;
        width: 100% !important;
        padding: 0 !important;
    }
    .constructor > .container > .row {
        margin: 0 !important;
        display: flex !important;
        flex-direction: column !important;
    }

    /* Preview column — full width, green-100 background, tag at top. */
    .constructor > .container > .row > [class*="col-lg-6"]:first-child {
        flex: 0 0 100% !important;
        max-width: 100% !important;
        width: 100% !important;
        background: var(--tc-green-100) !important;
        padding: 0 !important;
        margin: 0 !important;
        display: flex !important;
        justify-content: center !important;
        align-items: flex-start !important;
    }

    /* Form column — full width, white background, zero offsets.
       The legacy `col-sm-offset-1 col-sm-10` was indenting it 8.33% on
       tablets; explicit max-width: 100% + zero margin defeats both. */
    .constructor > .container > .row > [class*="col-lg-6"]:last-child {
        flex: 0 0 100% !important;
        max-width: 100% !important;
        width: 100% !important;
        background: #fff !important;
        padding: 0 !important;
        margin: 0 !important;
    }

    /* Reset the badge wrapper — mobile lets legacy CSS keep the
       290 / 390 / 458 px width responsive ladder so .cropResult,
       .cropResult-content and .copyText positioning stays
       coordinated with the tag silhouette. We only centre it. */
    .constructor__badge {
        margin: 0 auto !important;
        display: block !important;
    }
}

@media (max-width: 767.98px) {
    /* ────────────────────────────────────────────────────────────────
     * Topbar back-button (= mobile.html `.topbar` + `.back-btn`,
     * lines 50-59). Closer to the corner, slightly smaller pill so
     * it doesn't dominate the narrow viewport.
     * ──────────────────────────────────────────────────────────── */
    .constructor__back {
        top: 14px !important;
        left: 14px !important;
    }
    .constructor__back button.btnStyle.btnTransparent-left,
    .constructor__back a.btnStyle.btnTransparent-left,
    .constructor #constructorClose,
    .constructor a#constructorClose,
    .constructor button#constructorClose {
        padding: 8px 14px 8px 32px !important;
        min-height: 36px !important;
        font: 600 13px/1 'Manrope', sans-serif !important;
        box-shadow: 0 2px 8px rgba(0, 0, 0, .05) !important;
    }
    .constructor__back button.btnStyle.btnTransparent-left::after,
    .constructor__back a.btnStyle.btnTransparent-left::after {
        left: 10px !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * Tag mockup (= mobile.html `.tag-mockup`, lines 88-93). Capped
     * width with negative bottom margin so the white form panel can
     * overlap the bottom of the tag for the "card" look.
     * ──────────────────────────────────────────────────────────── */
    .tag-mockup {
        width: min(380px, 100%) !important;
        max-width: 100% !important;
        margin: 0 auto -24px !important;
        filter: drop-shadow(0 18px 40px rgba(0, 0, 0, .22)) !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * Form panel = mobile.html `.form` (lines 147-154):
     *   • white card with rounded top corners
     *   • padding: var(--s-6) var(--s-4) calc(var(--s-8) + safe-area-bottom)
     *   • position: relative + z-index: 1 → sits above the tag's
     *     negative margin (which is what creates the overlap)
     * The desktop-only `min-height: 100vh` from §4 doesn't apply at
     * this width but we still zero margin / max-width defensively. The
     * `:last-child > .constructor__wrapper` selector cascades from
     * the column reset above; we duplicate the plain selector too so
     * neither parent path is required to win specificity. */
    .constructor__wrapper,
    .constructor > .container > .row > [class*="col-lg-6"]:last-child > .constructor__wrapper {
        background: #fff !important;
        border-radius: 24px 24px 0 0 !important;
        padding: 24px 16px calc(32px + env(safe-area-inset-bottom, 0px)) !important;
        position: relative !important;
        z-index: 1 !important;
        margin: 0 !important;
        min-height: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        display: flex !important;
        flex-direction: column !important;
        box-sizing: border-box !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * Heading (= mobile.html `.form h1`, line 155).
     * ──────────────────────────────────────────────────────────── */
    .constructor .constructor__heading h1,
    .constructor .constructor__heading h3 {
        font: 800 28px/1.15 'Manrope', sans-serif !important;
        margin: 0 0 24px !important;             /* var(--s-6) */
        letter-spacing: -.01em !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * Field-groups (= mobile.html `.field-group`, line 156).
     * ──────────────────────────────────────────────────────────── */
    .constructor .constructor-form > .form-group {
        margin-bottom: 24px !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * Stack nickname + phone (= mobile.html `.row { flex-direction:
     * column; gap: var(--s-4) }`, line 355).
     *
     * Override the desktop 2-col grid AND the legacy `width: 48%;
     * display: inline-block` from main.min.css. Single-column flex
     * with 16px gap, each field full-width.
     * ──────────────────────────────────────────────────────────── */
    .constructor .constructor-form {
        display: flex !important;
        flex-direction: column !important;
        grid-template-columns: none !important;
        column-gap: 0 !important;
    }
    .constructor .constructor-form > .form-group-name,
    .constructor .constructor-form > .form-group-number {
        width: 100% !important;
        max-width: 100% !important;
        display: block !important;
        margin: 0 0 16px !important;             /* var(--s-4) */
    }

    /* Hint sits closer to the input on mobile (no 8px label spacer
       like desktop because field-group margin handles the gap). */
    .constructor .hint-block,
    .constructor .form-group-hint,
    .constructor .field-group__hint {
        margin: 0 0 16px !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * Inputs (= mobile.html `.input`, lines 356-362).
     * 16px font ensures iOS Safari does NOT zoom-in on focus.
     * ──────────────────────────────────────────────────────────── */
    .constructor .form-input,
    .constructor input[type="text"],
    .constructor input[type="tel"],
    .constructor input[type="email"] {
        padding: 14px 16px !important;
        font: 400 16px/1.4 'Roboto', system-ui, sans-serif !important;
        min-height: 48px !important;
        border-radius: 12px !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * Upload button (= mobile.html `.upload-btn`, lines 162-167):
     * smaller padding + Roboto 16px (vs desktop 17px-700 Roboto).
     * ──────────────────────────────────────────────────────────── */
    .tc-upload-btn {
        padding: 18px 20px;
        font: 700 16px/1 'Roboto', sans-serif;
    }

    /* ────────────────────────────────────────────────────────────────
     * AI badge (= mobile.html `.ai-badge`, lines 194-202):
     * 8/12 padding, 12px Manrope-Bold, 11px sub-line.
     * ──────────────────────────────────────────────────────────── */
    .tc-ai-badge {
        padding: 8px 12px;
        font: 700 12px/1 'Manrope', sans-serif;
        margin-bottom: 12px;
    }
    .tc-ai-badge__emoji { font-size: 14px; gap: 2px; }
    .tc-ai-badge__sub { font-size: 11px; margin-left: 4px; }

    /* ────────────────────────────────────────────────────────────────
     * Variant grid + tiles (= mobile.html `.variant-grid`/`.variant`,
     * lines 214-266):
     *   • grid gap 8px (was 12px desktop)
     *   • tile padding 10px 8px, gap 8px
     *   • label 10px font
     *   • check 18px, top:4 right:4 (was 22px / top-right 8px)
     *   • custom-icon 60% size, svg 16px
     *   • custom-hint 9px font
     * ──────────────────────────────────────────────────────────── */
    .tc-variant-grid {
        gap: 8px;
        margin-bottom: 12px;
    }
    .tc-variant {
        padding: 10px 8px;
        gap: 8px;
    }
    .tc-variant__label {
        font: 600 10px/1.2 'Manrope', sans-serif;
    }
    .tc-variant__check {
        width: 18px;
        height: 18px;
        top: 4px;
        right: 4px;
    }
    .tc-variant__check svg {
        width: 12px;
        height: 12px;
    }
    .tc-variant--no-ai {
        gap: 8px;
    }
    .tc-variant__hint {
        font: 500 9px/1.3 'Manrope', sans-serif;
    }
    /* Skeleton tile a bit shorter — 140px desktop, ~96px mobile. */
    .tc-variant--skel {
        min-height: 96px;
    }

    /* ────────────────────────────────────────────────────────────────
     * Progress copy + error banner (mobile.html lines 304, 339-343).
     * ──────────────────────────────────────────────────────────── */
    .tc-progress-copy {
        font: 500 13px/1.4 'Manrope', sans-serif;
        margin: 0 0 8px;
    }
    .tc-error-banner {
        padding: 14px 16px;
        font: 500 14px/1.5 'Manrope', sans-serif;
        margin-bottom: 12px;
    }

    /* ────────────────────────────────────────────────────────────────
     * Note (= mobile.html `.note`, line 379).
     * ──────────────────────────────────────────────────────────── */
    .constructor .constructor__info-text {
        font: 400 13px/1.5 'Manrope', sans-serif !important;
        margin: 16px 0 !important;
        gap: 8px !important;
    }

    /* ────────────────────────────────────────────────────────────────
     * CTA (= mobile.html `.cta`, lines 382-393):
     * Tighter horizontal padding on mobile (24px vs desktop's 36px)
     * because the form row is narrower. Width / display / min-height
     * inherit from the desktop rule (which is already full-width).
     * ──────────────────────────────────────────────────────────── */
    .constructor #addToCart,
    .constructor .form-group-button button {
        padding: 18px 24px !important;
    }

}

/* ════════════════════════════════════════════════════════════════════
 * 20b. Crop modal — non-desktop sizing (≤ 991.98px)
 *
 * Why a separate breakpoint: the row 768-991 (tablet portrait, large
 * phones in landscape, foldables) was hitting the desktop crop-button
 * padding (28px) which combined with a localized "Crop and confirm"
 * label overflowed past the modal shell's right edge. Moved to a
 * wider breakpoint so the same defensive sizing covers ALL non-
 * desktop widths.
 *
 * Strategy: STACK the actions vertically. Apply full-width on top
 * (impossible to overflow), Close-link below as a tertiary action.
 * Far more robust than trying to fit two side-by-side buttons whose
 * widths depend on the localized label length.
 * ──────────────────────────────────────────────────────────────────── */
@media (max-width: 991.98px) {
    .tc-crop-modal {
        padding: 16px !important;
    }
    .tc-crop-modal__shell {
        padding: 16px !important;
        box-sizing: border-box !important;
        width: 100% !important;
        max-width: min(640px, calc(100vw - 32px)) !important;
    }
    .tc-crop-modal__title {
        font: 700 18px/1.2 'Manrope', sans-serif !important;
        margin: 0 0 12px !important;
    }

    /* Stack actions vertically — Apply primary (full-width pill),
       Close as a centred link below. Removes ALL flex-direction-row
       overflow concerns regardless of label length / viewport width. */
    .tc-crop-actions {
        display: flex !important;
        flex-direction: column-reverse !important;  /* Close drops below */
        align-items: stretch !important;
        gap: 12px !important;
        justify-content: flex-start !important;
        width: 100% !important;
    }
    .tc-crop-actions__apply {
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        width: 100% !important;
        max-width: 100% !important;
        min-width: 0 !important;
        padding: 14px 20px !important;
        font: 700 14px/1 'Manrope', sans-serif !important;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        box-sizing: border-box !important;
    }
    .tc-crop-actions__cancel {
        align-self: center !important;
        padding: 8px 16px !important;
        background: transparent !important;
    }
}


/* ════════════════════════════════════════════════════════════════════
 * 21. Defeat legacy global SVG fill — icon scope ONLY
 *
 * main.min.css ships an unconditional `svg { fill: var(--color-gray-900) }`
 * rule that overrides the SVG-attribute `fill="none"` on our stroke-only
 * outline icons (per SVG2 spec, CSS beats presentation attributes).
 * Symptom: upload arrow, variant check, CTA arrow, crop icon, AI badge
 * paint as a solid dark blob instead of a thin stroked outline.
 *
 * IMPORTANT: this override is SCOPED TO ICON SVGs ONLY — explicitly
 * NOT to `.tag-mockup__text` (the arched PET NAME / PHONE NUMBER
 * SVG). That SVG renders <text> glyphs which REQUIRE fill to be
 * visible (without fill, the glyph outlines render as invisible
 * paths and the customer types into the form but the tag stays
 * blank). An earlier broader `.constructor svg *` rule killed that
 * text rendering — narrow it back to the icon hosts here.
 * ──────────────────────────────────────────────────────────────────── */
.tc-upload-btn svg,
.tc-upload-btn svg *,
.tc-variant__check svg,
.tc-variant__check svg *,
.tc-variant--no-ai svg,
.tc-variant--no-ai svg *,
.tc-ai-badge svg,
.tc-ai-badge svg *,
.tc-error-banner svg,
.tc-error-banner svg *,
.tc-crop-modal svg,
.tc-crop-modal svg *,
.tc-crop-actions svg,
.tc-crop-actions svg *,
.constructor #addToCart svg,
.constructor #addToCart svg *,
.constructor .form-group-button svg,
.constructor .form-group-button svg * {
    fill: none !important;
}


/* ════════════════════════════════════════════════════════════════════
 * 22. Legacy CircleType arc text — placeholder hint when empty
 *
 * The legacy `.copyText` divs (#copyText-top, #copyText-bottom) carry
 * the CircleType-rendered arc text on the tag preview. When the
 * customer hasn't typed yet, tags.templates.js renders the
 * "PET NAME" / "PHONE NUMBER" placeholder copy through CircleType
 * and toggles `.is-placeholder` on the host element so we can dim
 * it slightly — it's a hint, not actual content.
 * ──────────────────────────────────────────────────────────────────── */
.copyText.is-placeholder {
    opacity: .55;
}


/* ════════════════════════════════════════════════════════════════════
 * 23. Helper
 * ──────────────────────────────────────────────────────────────────── */
[hidden] {
    display: none !important;
}
