/* ---------------------------------------------------------------- foundations */

html, body {
    margin: 0;
    font-family: 'Inter', 'Segoe UI', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6, .mud-typography-h1, .mud-typography-h2,
.mud-typography-h3, .mud-typography-h4, .mud-typography-h5, .mud-typography-h6 {
    font-family: 'Outfit', 'Inter', 'Segoe UI', sans-serif;
}

h1:focus, h1:focus-visible {
    outline: none;
}

/* The page reaches under the status bar (viewport-fit=cover), and iOS keeps taps in that strip for
   itself: the menu and theme buttons sat under the clock and battery and did nothing. The bar grows
   by the inset so its buttons land below it, and everything positioned under the bar moves down with
   it. env() is zero wherever there is no inset, so desktop browsers are unaffected. */
.studio-appbar {
    backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid var(--mud-palette-divider);
    padding-top: env(safe-area-inset-top, 0px);
}

.studio-drawer.mud-drawer-fixed {
    margin-top: env(safe-area-inset-top, 0px);
}

.studio-main.mud-main-content {
    margin-top: env(safe-area-inset-top, 0px);
}

.studio-drawer {
    border-right: 1px solid var(--mud-palette-divider);
}

.studio-main {
    position: relative;
    min-height: 100vh;
    overflow-x: hidden;
}

.brand-mark {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 12px;
    color: #fff;
    background: linear-gradient(135deg, var(--mud-palette-primary), var(--mud-palette-tertiary));
    box-shadow: 0 6px 18px -8px var(--mud-palette-primary);
}

.brand-name { line-height: 1.1; }
.brand-sub { line-height: 1.1; opacity: .7; }

.nav-note {
    border: 1px solid var(--mud-palette-divider);
    border-radius: 14px;
    background: var(--mud-palette-background-gray);
}

/* --------------------------------------------------------- ambient background */

.studio-aurora {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;
}

.studio-aurora::before,
.studio-aurora::after {
    content: '';
    position: absolute;
    width: 46vw;
    height: 46vw;
    border-radius: 50%;
    filter: blur(90px);
    opacity: .30;
    will-change: transform;
}

.studio-aurora::before {
    top: -14vw;
    right: -10vw;
    background: radial-gradient(circle, var(--mud-palette-primary) 0%, transparent 68%);
    animation: drift-a 26s ease-in-out infinite alternate;
}

.studio-aurora::after {
    bottom: -18vw;
    left: -12vw;
    background: radial-gradient(circle, var(--mud-palette-tertiary) 0%, transparent 68%);
    animation: drift-b 32s ease-in-out infinite alternate;
}

@keyframes drift-a {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(-6vw, 5vh, 0) scale(1.12); }
}

@keyframes drift-b {
    from { transform: translate3d(0, 0, 0) scale(1.05); }
    to   { transform: translate3d(7vw, -6vh, 0) scale(.94); }
}

.studio-page {
    position: relative;
    z-index: 1;
}

/* ------------------------------------------------------------------ step rail */

.step-rail {
    display: flex;
    gap: .5rem;
    flex-wrap: wrap;
}

.step-chip {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .55rem .95rem;
    border-radius: 999px;
    border: 1px solid var(--mud-palette-divider);
    background: var(--mud-palette-surface);
    cursor: pointer;
    transition: transform .22s cubic-bezier(.2, .8, .3, 1), border-color .22s, box-shadow .22s, opacity .22s;
    user-select: none;
}

.step-chip:hover:not(.is-locked) {
    transform: translateY(-2px);
    box-shadow: 0 10px 26px -18px rgba(0, 0, 0, .8);
}

.step-chip.is-active {
    border-color: var(--mud-palette-primary);
    box-shadow: inset 0 0 0 1px var(--mud-palette-primary);
}

.step-chip.is-done .step-index {
    background: var(--mud-palette-success);
    color: #fff;
}

.step-chip.is-locked {
    opacity: .45;
    cursor: not-allowed;
}

.step-index {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    font-size: .75rem;
    font-weight: 600;
    border-radius: 50%;
    background: var(--mud-palette-background-gray);
    color: var(--mud-palette-text-secondary);
}

.step-chip.is-active .step-index {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
}

/* --------------------------------------------------------------- photo canvas */

.photo-stage {
    display: flex;
    justify-content: center;
}

.photo-frame {
    position: relative;
    display: inline-block;
    max-width: 100%;
    line-height: 0;
    border-radius: 16px;
    overflow: hidden;
    border: 1px solid var(--mud-palette-divider);
    background-color: #ffffff;
    background-image:
        linear-gradient(45deg, #dfe3ee 25%, transparent 25%),
        linear-gradient(-45deg, #dfe3ee 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, #dfe3ee 75%),
        linear-gradient(-45deg, transparent 75%, #dfe3ee 75%);
    background-size: 18px 18px;
    background-position: 0 0, 0 9px, 9px -9px, -9px 0;
}

.photo-frame img {
    display: block;
    max-width: 100%;
    height: auto;
}

.photo-placeholder {
    display: block;
}

.photo-frame.is-clickable { cursor: crosshair; }

.photo-frame .guide {
    position: absolute;
    left: 0;
    right: 0;
    height: 0;
    border-top: 1.5px dashed rgba(76, 91, 212, .95);
    pointer-events: none;
}

.photo-frame .guide::after {
    content: attr(data-label);
    position: absolute;
    top: -1.4em;
    left: .5rem;
    font-size: .68rem;
    line-height: 1.7;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: #fff;
    background: rgba(76, 91, 212, .95);
    padding: .05rem .45rem;
    border-radius: 5px;
}

.photo-frame .guide.is-chin { border-top-color: rgba(249, 122, 90, .95); }
.photo-frame .guide.is-chin::after { background: rgba(249, 122, 90, .95); }

/* The face-centre line. Thicker than the horizontal guides and carrying a white halo, because it is
   the one guide with no label to anchor it and it has to read against hair, skin and backdrop
   alike - on a phone the original hairline dash was effectively invisible. */
.photo-frame .guide-x {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0;
    border-left: 2px dashed rgba(18, 165, 148, 1);
    filter: drop-shadow(0 0 1px rgba(255, 255, 255, .9)) drop-shadow(0 0 2px rgba(0, 0, 0, .35));
    pointer-events: none;
}

/* A label like the other two guides have, so it reads as the centre line rather than a stray mark. */
.photo-frame .guide-x::after {
    content: "Centre";
    position: absolute;
    top: .4rem;
    left: 50%;
    transform: translateX(-50%);
    font-size: .68rem;
    line-height: 1.7;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: #fff;
    background: rgba(18, 165, 148, .95);
    padding: .05rem .45rem;
    border-radius: 5px;
    white-space: nowrap;
}

.photo-frame .seed-dot {
    position: absolute;
    width: 14px;
    height: 14px;
    margin: -7px 0 0 -7px;
    border-radius: 50%;
    border: 2px solid #fff;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
    pointer-events: none;
    animation: pop-in .3s cubic-bezier(.2, 1.2, .4, 1);
}

.seed-dot.is-subject { background: #12A594; }
.seed-dot.is-background { background: #F97A5A; }

@keyframes pop-in {
    from { transform: scale(0); opacity: 0; }
    to   { transform: scale(1); opacity: 1; }
}

.photo-busy {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    gap: .75rem;
    background: rgba(12, 14, 20, .55);
    backdrop-filter: blur(3px);
    color: #fff;
    z-index: 3;
}

/* ------------------------------------------------------------------- exports */

.spec-card {
    border: 1px solid var(--mud-palette-divider);
    border-radius: 16px;
    transition: transform .22s cubic-bezier(.2, .8, .3, 1), border-color .22s, box-shadow .22s;
    cursor: pointer;
    height: 100%;
}

.spec-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 18px 40px -28px rgba(0, 0, 0, .85);
}

.spec-card.is-selected {
    border-color: var(--mud-palette-primary);
    box-shadow: inset 0 0 0 1px var(--mud-palette-primary);
}

.spec-thumb {
    display: grid;
    place-items: center;
    border-radius: 12px;
    background: var(--mud-palette-background-gray);
    padding: .5rem;
}

.spec-thumb img {
    max-height: 170px;
    border-radius: 6px;
    box-shadow: 0 8px 22px -14px rgba(0, 0, 0, .9);
}

.swatch {
    width: 34px;
    height: 34px;
    border-radius: 10px;
    border: 2px solid transparent;
    box-shadow: inset 0 0 0 1px rgba(128, 128, 128, .35);
    cursor: pointer;
    transition: transform .18s ease, border-color .18s ease;
}

.swatch:hover { transform: scale(1.1); }
.swatch.is-active { border-color: var(--mud-palette-primary); }

.swatch.is-transparent {
    background-color: #fff;
    background-image:
        linear-gradient(45deg, #cfd4e4 25%, transparent 25%),
        linear-gradient(-45deg, #cfd4e4 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, #cfd4e4 75%),
        linear-gradient(-45deg, transparent 75%, #cfd4e4 75%);
    background-size: 12px 12px;
    background-position: 0 0, 0 6px, 6px -6px, -6px 0;
}

/* ----------------------------------------------------------------- animations */

.fade-up {
    animation: fade-up .45s cubic-bezier(.2, .8, .3, 1) both;
}

.fade-up-delay-1 { animation-delay: .06s; }
.fade-up-delay-2 { animation-delay: .12s; }
.fade-up-delay-3 { animation-delay: .18s; }

@keyframes fade-up {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}

.dropzone {
    border: 2px dashed var(--mud-palette-divider);
    border-radius: 20px;
    padding: 3.2rem 1.5rem;
    text-align: center;
    transition: border-color .2s ease, background .2s ease, transform .2s ease;
    background: var(--mud-palette-surface);
}

.dropzone:hover {
    border-color: var(--mud-palette-primary);
    transform: translateY(-2px);
}

.dropzone .dropzone-icon {
    animation: float 4.5s ease-in-out infinite;
}

@keyframes float {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-8px); }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
}

/* ------------------------------------------------------- blazor error overlay */

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

/* Shown in place of the dropzone while the cut-out runs. Matches the dropzone's own shape so the
   panel does not jump when one swaps for the other. */
.processing-card {
    border: 1px solid var(--mud-palette-divider);
    border-radius: 20px;
    padding: 2rem 1.75rem;
    background: var(--mud-palette-surface);
}

/* An indeterminate bar: the work has no measurable progress, so this paces the wait without
   claiming to know how far along it is. */
.processing-track {
    position: relative;
    height: 4px;
    border-radius: 999px;
    overflow: hidden;
    background: var(--mud-palette-divider);
}

.processing-track span {
    position: absolute;
    inset: 0 auto 0 0;
    width: 40%;
    border-radius: 999px;
    background: linear-gradient(90deg,
        transparent,
        var(--mud-palette-primary),
        transparent);
    animation: processing-sweep 1.4s ease-in-out infinite;
}

@keyframes processing-sweep {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}

@media (prefers-reduced-motion: reduce) {
    .processing-track span {
        animation: none;
        width: 100%;
        background: var(--mud-palette-primary);
        opacity: .35;
    }
}

/* Numbered slider stops. MudBlazor hangs the tick labels below the track without reserving space
   for them, so the slider needs a margin or the labels land on whatever follows. */
.step-slider .mud-slider {
    margin-bottom: 1.4rem;
}

.step-slider {
    padding-bottom: .25rem;
}

.step-slider .mud-slider-tick-label {
    font-size: .72rem;
    opacity: .6;
}

/* The brush sweep drawn over the photo while it is being dragged. The viewBox carries the photo's
   own shape, so a round brush stays round instead of stretching into an ellipse. */
.eraser-overlay {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* A round brush is drawn as one stroked line: a disc swept along a path is exactly that. */
.eraser-overlay polyline {
    fill: none;
    stroke: rgba(var(--mud-palette-secondary-rgb), .55);
    stroke-linecap: round;
    stroke-linejoin: round;
    /* The width comes from the component, in viewBox units, so it matches what is rubbed out. */
}

/* The other shapes are stamped footprints rather than a path, so they are filled, not stroked.
   They overlap heavily, so they must not be translucent individually or the sweep would darken
   wherever stamps pile up and read as blotchy. */
.eraser-overlay .eraser-stamps {
    opacity: .55;
}

.eraser-overlay rect {
    fill: rgb(var(--mud-palette-secondary-rgb));
    stroke: none;
}

/* While drawing, the cursor says so and the browser must not try to pan or select instead. */
.photo-frame.is-drawing {
    cursor: crosshair;
    touch-action: none;
    user-select: none;
}

/* The camera preview on the upload step. */
.camera-stage {
    position: relative;
    width: 100%;
    max-width: 520px;
    margin-inline: auto;
    border-radius: 14px;
    overflow: hidden;
    background: #000;
    aspect-ratio: 4 / 3;
}

.camera-view {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;

    /* A mirrored view is what people expect of themselves. The captured frame is not flipped. */
    transform: scaleX(-1);
}

/* A head-and-shoulders guide, so the photo arrives roughly framed rather than needing the crop to
   rescue it. Deliberately loose: it is a hint, not a mask. */
.camera-guide {
    position: absolute;
    left: 50%;
    top: 46%;
    width: 46%;
    height: 76%;
    transform: translate(-50%, -50%);
    border: 2px dashed rgba(255, 255, 255, .75);
    border-radius: 50%;
    pointer-events: none;
}

/* "or" between the two ways of getting a photo in. */
.or-divider {
    display: flex;
    align-items: center;
    gap: 1rem;
    color: var(--mud-palette-text-secondary);
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .08em;
}

.or-divider::before,
.or-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--mud-palette-divider);
}

/* Sits under whichever page is showing, quiet enough not to compete with it. */
.studio-footer {
    margin-top: 4rem;
    padding: 1.5rem 0 2rem;
    border-top: 1px solid var(--mud-palette-divider);
    text-align: center;
    color: var(--mud-palette-text-secondary);
}

/* The eraser's instruction, pinned to the top of the photo while the brush is on. */
.cutout-photo {
    position: relative;
    scroll-margin-top: calc(72px + env(safe-area-inset-top, 0px));
}

.erase-banner {
    position: sticky;
    top: calc(64px + env(safe-area-inset-top, 0px));
    z-index: 5;
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
    padding: 6px 8px 6px 12px;
    border-radius: 10px;
    background: rgb(var(--mud-palette-secondary-rgb));
    color: var(--mud-palette-secondary-text);
    font-size: .9rem;
    font-weight: 500;
}

.erase-banner span {
    flex: 1;
}

/* Zoomed, the stage is a window onto a photo larger than it, scrolled with one finger when the
   eraser is off and with two when it is on. */
.photo-stage.is-zoomed {
    display: block;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    border-radius: 16px;
    border: 1px solid var(--mud-palette-divider);
}

.photo-stage.is-zoomed .photo-frame {
    display: block;
    border: none;
    border-radius: 0;
}

.zoom-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
}

/* The eraser magnifier: a circle of the photo under the finger, in the corner the hand is away from.
   It sits above the picture and must never take a pointer event, or it would cut the stroke short.
   It hangs off the wrapper rather than the photo: zoomed in, the photo is larger than its window and
   scrolls inside it, and a magnifier pinned to the photo scrolls out of sight with it. */
.photo-canvas {
    position: relative;
}

.loupe {
    position: absolute;
    top: 10px;
    width: 128px;
    height: 128px;
    border-radius: 50%;
    overflow: hidden;
    background: #fff;
    border: 2px solid var(--mud-palette-primary);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .28);
    pointer-events: none;
    z-index: 6;
}

.loupe.is-left { left: 10px; }
.loupe.is-right { right: 10px; }

.loupe img {
    display: block;
    max-width: none;
}

/* The brush footprint, pinned to the middle of the circle - the point under the finger. */
.loupe-brush {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    border: 2px solid rgba(249, 122, 90, .95);
    background: rgba(249, 122, 90, .18);
}

.loupe-brush.is-round { border-radius: 50%; }

/* Crosshair, so the exact point is readable even where the brush is large. */
.loupe::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 13px;
    height: 13px;
    transform: translate(-50%, -50%);
    background:
        linear-gradient(var(--mud-palette-primary), var(--mud-palette-primary)) center / 1px 100% no-repeat,
        linear-gradient(var(--mud-palette-primary), var(--mud-palette-primary)) center / 100% 1px no-repeat;
    opacity: .9;
}

/* The Share button only exists where the browser can actually share a file - a phone, essentially.
   Hidden by default and revealed by a class studio.js puts on <html>, so export cards rendered
   later are covered without re-checking support for each one. */
.share-button { display: none !important; }
html.can-share .share-button { display: inline-flex !important; }

/* ------------------------------------------------------------------- theme menu */

.theme-menu .mud-list-item { padding-top: .5rem; padding-bottom: .5rem; }

.theme-choice {
    display: flex;
    align-items: center;
    gap: .75rem;
    min-width: 230px;
}

.theme-choice-text {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
    margin-right: auto;
}

/* The swatch carries the theme's own two accents, so the menu shows the look rather than naming
   it. The inner ring keeps a pale swatch from dissolving into a light surface. */
.theme-dot {
    flex: none;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .35), 0 2px 8px -2px rgba(0, 0, 0, .45);
}
