/* ============================================================
   TestYourLogo — implemented strictly from Figma (node 5605:2361)
   Font: Spline Sans (400 / 500 / 700)
   Tokens: border/radius/xlarge = 24px, border/radius/rnd = pill
   ============================================================ */

/* ---- Self-hosted fonts ----
   Served from our own domain instead of fonts.googleapis.com, so no visitor IP
   is ever shared with a third party (the privacy point behind the German
   LG München Google-Fonts ruling) and the CSP needs no external font hosts.
   These are Google's own variable woff2 builds, split into the same latin /
   latin-ext subsets with the same unicode-ranges, so rendering is identical.
   Spline Sans: one variable file covers weights 300–700 (we use 400–700).
   Bricolage Grotesque: variable on opsz/wdth/wght — the footer wordmark uses
   opsz 96 / wdth 100 / wght 800 via font-variation-settings, unchanged. */
@font-face {
  font-family: "Spline Sans";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("assets/fonts/splinesans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Spline Sans";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("assets/fonts/splinesans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 200 800;
  font-stretch: 75% 100%;
  font-display: swap;
  src: url("assets/fonts/bricolage-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 200 800;
  font-stretch: 75% 100%;
  font-display: swap;
  src: url("assets/fonts/bricolage-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --black: #262626;
  --black-hover: #000000;        /* primary CTA hover — darkest */
  --white: #ffffff;
  --bg: #f5f5f5;                /* page background */
  --radius-xl: 24px;            /* border/radius/xlarge */
  --radius-pill: 999px;         /* border/radius/rnd */
  --track: #e5e5e5;             /* segmented control track */
  --icon-inactive: #a1a1a1;     /* inactive segment icon */
  --icon-hover: #525252;        /* inactive segment icon, hovered (darker) */
  --divider: #d4d4d4;           /* segmented control divider */
  --text-muted: #737373;        /* secondary text (subtitle, hint, filename) */
  --border-muted: #a1a1a1;      /* muted input borders / placeholders */
  --surface-hover: #ececec;     /* hover fill — a touch darker than the #f5f5f5 page so it stays visible */
  --container: 1200px;
  /* Elevation scale — two layered (ambient + key) shadows, used consistently by
     all raised surfaces so modals share one elevation language.
     --elev-1: low (transient cards). --elev-2: high (dialogs). */
  --elev-1: 0 1px 2px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.08);
  --elev-2: 0 4px 12px rgba(0, 0, 0, 0.10), 0 24px 48px rgba(0, 0, 0, 0.16);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* Form controls don't inherit font-family by default — make them use Spline Sans
   like the rest of the page (otherwise button/label text falls back to Arial). */
button, input, textarea, select { font-family: inherit; }

html {
  -webkit-text-size-adjust: 100%;
  /* Kill the grey/black square iOS paints over a tapped button/link. */
  -webkit-tap-highlight-color: transparent;
  /* Without this, the html canvas is transparent → the 3D viewer's dark scrim
     renders pure BLACK in the iOS toolbar/overscroll areas (harsh black bars),
     instead of the same dark grey it shows over the light page. */
  background: var(--bg);
}

body {
  font-family: "Spline Sans", system-ui, sans-serif;
  color: var(--black);
  background: var(--bg);
  line-height: 1.2;
  /* Grayscale (not subpixel) antialiasing → thinner, crisper text. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* With viewport-fit=cover the page extends under the iOS notch/status bar and
     home indicator so the fixed scrim can cover edge-to-edge; this keeps normal
     content clear of those areas. Zero on non-notched/desktop devices. */
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
           env(safe-area-inset-bottom) env(safe-area-inset-left);
}

/* Nicer line breaking (per the linked article): avoid orphaned last words in
   body copy; balance short, centred, heading-style text. */
p { text-wrap: pretty; }
.upload__title { text-wrap: balance; }

.container {
  /* Exactly 1200px content when there is room (matches Figma's 156px gutter at
     1512), collapsing to a 24px gutter on narrow viewports. */
  width: min(100% - 48px, var(--container));
  margin-inline: auto;
}

.icon { width: 20px; height: 20px; display: block; color: var(--black); }

/* Visually hidden but available to screen readers (e.g. the document <h1>). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- Keyboard focus ring (only for keyboard users, via :focus-visible) ----
   Applied site-wide to interactive elements. Rollback: delete this block. */
:where(a, button, [tabindex], .card--3d):focus-visible {
  outline: 2px solid var(--black);
  outline-offset: 2px;
}
/* Cards round to 24px, so the offset ring should hug that radius. */
.card--3d:focus-visible { outline-offset: 3px; border-radius: var(--radius-xl); }
/* Inputs/textareas already signal focus with a darkened border; add an outline
   too so keyboard focus is unmistakable. */
.fb-textarea:focus-visible,
.fb-input:focus-visible { outline: 2px solid var(--black); outline-offset: 2px; }

/* ===================== Main content ===================== */
.content { width: 100%; }
.content__max { display: flex; flex-direction: column; }

/* ---- Upload area (5605:2676 / 5605:2178) ---- */
.upload-area { width: 100%; padding-top: 48px; }  /* 48px above the upload box */

.upload {
  width: 100%;
  /* Unboxed: no container at all — the hero floats on the page. The radius only
     rounds the drag-over fill below. */
  background: transparent;
  border-radius: var(--radius-xl);
  overflow: hidden;
  transition: background-color 0.2s ease;
}
/* Drag-over feedback only (no mouse-hover state on the container — the CTAs carry
   their own hover): a soft fill while a file is held over the zone. */
.upload.is-hover { background: var(--surface-hover); }
.upload.is-populated .upload__content { padding: 0; }

.upload__content {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Frameless cut: air above so the hero owns its space, tight seam to the grid
     below; no side padding (the old 32px was the box's inner inset). */
  padding: 40px 0 16px;
}

/* empty-state texts */
.upload__texts {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.upload__title {
  font-weight: 700;
  font-size: 32px;
  line-height: 40px;
  text-align: center;
  width: 100%;
}
.upload__subtitle {
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  color: var(--text-muted);
  text-align: center;
  width: 100%;
}

.upload__actions {
  display: flex;
  flex-direction: column;
  gap: 24px;
  align-items: center;
  padding-top: 24px;
  width: 100%;
}
/* One line, side by side. Both sit at weight 400 so neither shouts — "Try a
   sample" still reads as interactive from its underline and darker colour,
   without needing extra weight. */
.upload__hint {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  line-height: 16px;
  text-align: center;
  white-space: nowrap;
}
.upload__hint span { font-weight: 400; color: var(--text-muted); }
.upload__hint .link { font-weight: 400; }

/* populated-state visual */
.upload__visual {
  display: flex;
  flex-direction: column;
  gap: 24px;
  align-items: center;
  max-width: 768px; /* 640 (design Visual) + 20% — roomier for wide logos */
  width: 100%;
  padding-bottom: 24px;
}
.upload__preview {
  /* No border/box. Logo is capped in HEIGHT (240px) but free to use the full
     width of the Visual area (up to 640px) so wide logos show large & detailed
     (e.g. Discord). Matches 5614:2361/2377/2393/2408. */
  width: 100%;
  max-height: 240px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.upload__preview img {
  max-width: 100%;
  max-height: 240px;
  width: auto;
  height: auto;
  object-fit: contain;
}
.upload__preview span { font-weight: 400; font-size: 14px; line-height: 20px; }
.upload__filename {
  font-weight: 400;
  font-size: 12px;
  line-height: 16px;
  color: var(--text-muted);
  text-align: center;
  width: 100%;
}
.upload__actions--row {
  flex-direction: row;
  gap: 12px;
  justify-content: center;
  padding-top: 0;
}

/* ---- Buttons ---- */
.btn {
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: 32px;
  border-radius: var(--radius-pill);
  font-weight: 600;
  font-size: 12px;
  line-height: 16px;
  text-transform: uppercase;
  cursor: pointer;
  border: 1px solid transparent;
  white-space: nowrap;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.btn--primary { background: var(--black); color: var(--white); }
/* Subtle hovers: dark buttons lighten a touch; outlined buttons get the same
   light fill used by the cards/tabs. */
.btn--primary:not(:disabled):hover { background: var(--black-hover); }
.btn--secondary:not(:disabled):hover { background: var(--surface-hover); }
/* Populated actions (5669:2388): smaller, outlined, transparent pills. */
.btn--secondary {
  background: transparent;
  color: var(--black);
  border-color: var(--black);
  padding-inline: 24px;
}
.btn--clear { width: 99px; padding-inline: 0; }
/* Populated row: Replace + Clear sit at the full 48px CTA height (matching the
   empty-state "Upload logo"), so every button on the page is the same height. */
.upload__actions--row .btn--primary { padding-inline: 24px; }

.link {
  font-family: inherit;
  font-weight: 500;
  font-size: 12px;
  line-height: 16px;
  color: var(--black);
  background: none;
  border: none;
  text-decoration: underline;
  text-underline-offset: 1px;
  cursor: pointer;
}

/* ---- Selector (5605:2670) ---- */
:root {
  --selector-bottom-gap: 32px; /* gap from viewport bottom while floating */
}
.selector-row {
  display: flex;
  justify-content: center;
  padding-top: 96px;   /* 5605:2678 — 96px above the pill at rest */
  min-height: 144px;   /* selector slot height */
  /* Float pinned to the bottom of the viewport while the slot is still below
     the fold, then settle into the slot and scroll normally. */
  position: sticky;
  bottom: var(--selector-bottom-gap);
  z-index: 20;
  /* Let clicks pass through the empty row to the grid behind it while floating;
     the selector pill itself re-enables pointer events. */
  pointer-events: none;
}
.selector {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  /* iOS-style segmented control (Figma 5668:2222): grey track, no border. With
     no border the content box stays a full 48px tall, so its caps are radius 24
     — matching each icon's center in its 48px button, keeping the white thumb's
     arc aligned with the track cap. */
  background: var(--track);
  border-radius: var(--radius-pill);
  overflow: hidden;
  pointer-events: auto;
}
.selector__highlight {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  /* White thumb with a 2px track-coloured border, so it reads as inset ~2px from
     the track edges (the border blends into the track). */
  background: var(--white);
  border: 2px solid var(--track);
  border-radius: var(--radius-pill);
  z-index: 1;
  transition: left 0.2s ease;
}
/* Centered on each 48px button: the icon is dead-centered in the circle, and at
   the ends the circle's arc coincides with the pill's cap (same radius 24, same
   center) so they share one line — flush, no double stroke, no icon movement. */
.selector[data-selected="3"] .selector__highlight { left: 0; }
.selector[data-selected="2"] .selector__highlight { left: 48px; }
.selector[data-selected="1"] .selector__highlight { left: 96px; }

.selector__btn {
  position: relative;
  z-index: 2;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--black);
  cursor: pointer;
}
.selector__btn .icon { transition: color 0.15s ease; }
.selector__btn[aria-selected="false"] .icon { color: var(--icon-inactive); }
/* Hover affordance on the unselected segments (pointer devices only, so touch
   doesn't leave a sticky-hover state): darken the grey icon toward the active
   colour so it reads as interactive, without looking selected. */
@media (hover: hover) {
  .selector__btn[aria-selected="false"]:hover .icon { color: var(--icon-hover); }
}

.selector__divider {
  position: absolute;
  top: 16px;
  width: 1px;
  height: 16px;
  background: var(--divider);
  z-index: 1;
  display: none;
}
.selector[data-selected="3"] .selector__divider { display: block; left: 95.5px; }
.selector[data-selected="1"] .selector__divider { display: block; left: 47.5px; }

/* ---- Image grid (5605:2371) ---- */
.image-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-content: start;  /* pack rows to the top, even while the grid height is
                            pinned taller during a transition (no mid-row gaps) */
  gap: 16px;
  padding-top: 48px;   /* 5605:2680 */
}
.image-grid[data-cols="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.image-grid[data-cols="1"] { grid-template-columns: repeat(1, minmax(0, 1fr)); }

.card {
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--white);
  border-radius: var(--radius-xl);
  /* No padding: the 3D canvas is full-bleed (like the viewer), so the card and the
     viewer's first frame render the model IDENTICALLY — exact open/close framing parity
     (the old 8px inset was a Figma artefact and forced an approximate camera-dolly comp). */
  overflow: hidden;
  cursor: pointer;
  /* Resting shadow is the hover shadow at zero alpha — same two layers, so the
     hover transition interpolates the colour smoothly instead of snapping from
     `none` (which several browsers refuse to animate). */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0), 0 10px 28px rgba(0, 0, 0, 0);
  transition: box-shadow 0.25s ease;
}
.card__img { width: 100%; height: 100%; pointer-events: none; }
.card__img--cover { object-fit: cover; }
.card__img--contain { object-fit: contain; }

/* 3D model viewer cells (rotation disabled for now) */
.card__canvas {
  width: 100%;
  height: 100%;
  display: block;
  /* No border-radius needed — the card's overflow:hidden clips it to the rounded
     shape. (Avoids a per-frame repaint during the grid transition.) */
  transform-origin: center center;
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Subtle scale-up + soft shadow on hover (Figma 3D "Hover" state). Gated to
   hover-capable (mouse) devices: on touch, :hover sticks after a tap, which left
   the grid card scaled to 1.05 while the viewer closed to 1.0 — a visible pop /
   mismatch when the card was revealed. */
@media (hover: hover) and (pointer: fine) {
  .card--3d:hover { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 28px rgba(0, 0, 0, 0.04); }
  .card--3d:hover .card__canvas { transform: scale(1.05); }
}

/* ---- Loading state: a small, quiet spinner centered in the card while its 3D
   model loads. Rollback: delete this block + the is-loading toggles in
   model-grid.js. ---- */
.card--3d.is-loading { position: relative; }
.card--3d.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  border: 1.5px solid rgba(212, 212, 212, 0.5); /* faint track */
  border-top-color: var(--divider);            /* slightly darker grey arc */
  animation: card-spin 0.8s linear infinite;
}
@keyframes card-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* No spin for reduced-motion users — a gentle pulse instead. */
  .card--3d.is-loading::after {
    animation: card-pulse 1.4s ease-in-out infinite;
    border-top-color: var(--track);
    background: var(--track);
  }
  @keyframes card-pulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.7; } }
}

@media (prefers-reduced-motion: reduce) {
  .card__canvas { transition: none; }
}

/* ---- Spacer (5605:2687) ---- */
.spacer { height: 96px; }

/* ===================== Footer (5605:2688 / mobile 5605:2465) ===================== */
.site-footer {
  width: 100%;
  /* Footer shares the page background and only has bottom padding; its content
     uses the same .container as the rest of the page (no extra side padding), so
     it lines up with the grid above instead of being inset further. */
  background: var(--bg);
  padding: 0 0 96px;
}
.site-footer__row {
  display: flex;
  flex-direction: column;
  gap: 64px;
  align-items: center;
  padding-inline: 0;
}
.site-footer__wordmark {
  /* Bricolage Grotesque at the widest width (wdth 100), heaviest weight (800),
     and largest optical size (96) for the display wordmark. */
  font-family: "Bricolage Grotesque", "Spline Sans", system-ui, sans-serif;
  font-weight: 800;
  font-stretch: 100%;
  font-variation-settings: "opsz" 96, "wdth" 100, "wght" 800;
  /* Fill the width of the grid/container above. --wm-gutter is the TOTAL side
     gutter, kept in lockstep with .container (48px desktop, 32px ≤600px), so the
     wordmark tracks the grid width — except ≤400px where it stays 24px each side
     (48px) so it isn't cramped at the very edges. ~5.96 ≈ rendered-width /
     font-size for this wordmark in Bricolage 800/wdth-100; min() caps at 1200px. */
  --wm-gutter: 48px;
  font-size: min(200px, calc((100vw - var(--wm-gutter)) / 5.96));
  line-height: 1.1;
  text-align: center;
  width: 100%;
  max-width: 100%;
}
.site-footer__tagline {
  font-weight: 400;
  font-size: 24px;
  line-height: 32px;
  text-align: center;
  width: 100%;
  /* Even out the wrapped lines (esp. on mobile where the <br> is dropped). */
  text-wrap: balance;
}
.site-footer__links {
  display: flex;
  flex-direction: column;
  gap: 20px;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.footer-link {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: center;
  color: var(--black);
  text-decoration: none;
  transition: opacity 0.15s ease;
}
/* Hidden for now — footer links show as plain text without their leading icon. */
.footer-link .icon { display: none; }
.footer-link span {
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  text-decoration: underline;
  text-underline-offset: 1px;
  white-space: nowrap;
}
.site-footer__copy {
  font-weight: 400;
  font-size: 12px;
  line-height: 16px;
  text-align: center;
  width: 100%;
}

/* ===================== About modal (5623:2450) ===================== */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;                       /* keeps the card off the edges */
  background: rgba(0, 0, 0, 0.4);      /* scrim */
  /* Fade — same duration & easing both directions */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0s linear 0.25s;
}
.modal-overlay.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.25s ease; /* visibility flips instantly on open */
}
.modal {
  width: 400px;
  max-width: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 24px;
  align-items: center;
  padding: 32px;
  background: var(--white);
  border-radius: var(--radius-xl);
  box-shadow: var(--elev-2); /* About + feedback dialogs */
  text-align: center;
  /* Subtle fade + scale — identical easing/duration to the overlay so open and
     close feel exactly the same. */
  transform: scale(0.94);
  opacity: 0;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.modal-overlay.is-open .modal {
  transform: scale(1);
  opacity: 1;
}
.modal:focus { outline: none; }

.modal__title {
  font-weight: 600;       /* Spline Sans SemiBold */
  font-size: 24px;
  line-height: 32px;
  width: 100%;
}
.modal__body {
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  width: 100%;
}
/* Centred short copy: `balance` evens the line lengths, which reads far better
   here than the global `pretty` (that only pulls back a trailing orphan). The
   browser cap (~6 lines) is well above these paragraphs; past it, it simply
   falls back to normal wrapping. */
.modal__body p { margin-bottom: 20px; text-wrap: balance; }
.modal__body p:last-child { margin-bottom: 0; }
.modal__link {
  color: var(--black);
  text-decoration: underline;
  text-underline-offset: 1px;
  transition: opacity 0.15s ease;
}
.modal__ok { text-transform: none; }   /* design shows "OK", not forced caps */

/* Hover affordance for the dark text links (footer + modal). They're already the
   darkest ink, so they dim slightly on hover rather than darken — same intent as
   the segmented control's hover (clear "this is interactive" feedback). Pointer
   devices only, so touch doesn't leave a sticky dimmed state. */
@media (hover: hover) {
  .footer-link:hover,
  .modal__link:hover { opacity: 0.6; }
}

@media (prefers-reduced-motion: reduce) {
  .modal-overlay,
  .modal { transition: opacity 0.01ms linear; }
  .modal { transform: none; }
}

/* ===================== Give feedback modal (5669:2160 / 2185 / 5681:97527 / 97559) ===================== */
.feedback-modal {
  /* Step 1 (categories) is wide; step 2 (details) is narrow. These are the
     natural/target widths — the width+height morph between them is animated in
     JS (FLIP) so there are no jumps. Sections own their padding, so the modal
     itself has none. */
  width: min(880px, 100%);
  padding: 0;
  gap: 0;
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
}
.feedback-modal[data-step="details"],
.feedback-modal[data-step="success"] { width: min(440px, 100%); }

/* Shared modal close (X) — used by both the feedback and 3D-viewer modals.
   A 16px icon centred in a 32px hover hitbox (8px gap each side). It's positioned
   so the ICON sits 24px from the edges (matching the content inset); the hover
   circle overhangs the extra 8px and is not meant to count toward the spacing. */
.modal-close {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-pill);
  /* White fill (not transparent) so the X stays legible even when it sits over
     the 3D model in the full-bleed viewer; hover deepens it to surface-hover. */
  background: var(--white);
  color: var(--black);
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.modal-close:hover { background: var(--surface-hover); }
.modal-close svg { width: 16px; height: 16px; display: block; }

/* Header reserves the top strip; flex-start + 16px padding lands the 32px button
   16px from the top-right corner, so its icon sits 24px in. */
.fb-header {
  width: 100%;
  height: 56px;
  flex-shrink: 0;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding: 16px 16px 0;
}
/* The confirmation step has no close (X) — only Done or clicking outside closes.
   With the X gone, the 56px header was just dead space up top, leaving the whole
   step top-heavy. Collapse it and balance the spacing (a touch more above the icon
   than below the heavier full-width button reads as centred). */
.feedback-modal[data-step="success"] .fb-close { display: none; }
.feedback-modal[data-step="success"] .fb-header { height: 0; padding: 0; }
.feedback-modal[data-step="success"] .fb-success { padding: 40px 24px 0; gap: 12px; }

.fb-step { width: 100%; display: none; flex-direction: column; align-items: center; transition: opacity 0.25s ease; }
.fb-step--categories { text-align: center; }
.fb-step--details { text-align: left; }
/* Only the form parts fade in during the morph; the icon row is revealed
   instantly as the ghosts land on it (no fade, so no flicker). */
.fb-fields, .fb-actions { transition: opacity 0.15s ease; }
/* Show only the active step. */
.feedback-modal[data-step="categories"] .fb-step--categories,
.feedback-modal[data-step="details"] .fb-step--details,
.feedback-modal[data-step="success"] .fb-step--success { display: flex; }

/* ---- Step 1: title + category cards ---- */
.fb-title {
  width: 100%;
  padding-inline: 24px;
  font-weight: 500;            /* Spline Sans Medium */
  font-size: 28px;
  line-height: 36px;
  text-align: center;
  color: var(--black);
  transition: opacity 0.14s ease;
}
/* Floating clone of the title used to cross-fade it during the morph. */
.fb-title-ghost {
  position: fixed;
  font-weight: 500;
  font-size: 28px;
  line-height: 36px;
  text-align: center;
  color: var(--black);
  z-index: 1050;
  pointer-events: none;
  transition: opacity 0.14s ease;
}
.fb-cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  width: 100%;
  padding: 24px;
}
.fb-card {
  aspect-ratio: 177.6 / 252;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  justify-content: center;
  padding: 16px 8px; /* slim sides: the labels are nowrap (see span below) */
  background: var(--white);
  border: 1.5px solid var(--track);
  border-radius: var(--radius-xl);
  color: var(--black);
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.fb-card:hover { border-color: var(--black); background: var(--surface-hover); }
.fb-card svg { width: 24px; height: 24px; display: block; }
/* Labels never wrap — cards keep their side padding slim enough for the longest
   label ("UFO spotting") to sit on one line at every breakpoint. */
.fb-card span { font-weight: 500; font-size: 14px; line-height: 20px; text-align: center; white-space: nowrap; }

/* ---- Step 2: category icon row ---- */
.fb-icons {
  display: grid;
  grid-template-columns: repeat(4, 56px);
  gap: 12px;
  justify-content: center;
}
.fb-icon {
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--white);
  border: 1.5px solid var(--track);
  border-radius: 16px;
  color: var(--black);
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.fb-icon svg { width: 24px; height: 24px; display: block; }
/* Same hover as the overview cards (dark border + subtle fill); the active
   (dark) tab keeps its filled style. */
.fb-icon:not([aria-selected="true"]):hover { border-color: var(--black); background: var(--surface-hover); }
.fb-icon[aria-selected="true"] { background: var(--black); border-color: var(--black); color: var(--white); }

/* ---- Step 2: fields ---- */
.fb-fields { display: flex; flex-direction: column; gap: 24px; width: 100%; padding: 24px 24px 0; }
.fb-field { display: flex; flex-direction: column; width: 100%; }
/* [hidden] must win over .fb-field's display:flex (email is revealed on opt-in). */
.fb-email[hidden] { display: none; }
.fb-email { transition: opacity 0.25s ease; }
.fb-label {
  font-weight: 600;
  font-size: 12px;
  line-height: 16px;
  text-transform: uppercase;
  padding-bottom: 8px;
  color: var(--black);
}
.fb-textarea,
.fb-input {
  width: 100%;
  background: var(--white);
  border: 1px solid var(--border-muted);
  border-radius: 12px;
  padding: 14px 16px;
  font-family: inherit;
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  color: var(--black);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.fb-textarea {
  min-height: 152px;
  max-height: 280px;
  resize: vertical;
  /* Custom grip tucked inside the rounded corner (the native one pokes out). */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M9 3 3 9M9 6.5 6.5 9' stroke='%23a1a1a1' stroke-width='1.5' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 7px bottom 7px;
}
.fb-textarea::-webkit-resizer { display: none; }
.fb-textarea::placeholder,
.fb-input::placeholder { color: var(--border-muted); }
.fb-textarea:focus,
.fb-input:focus {
  outline: none;
  border-color: var(--black);
  /* Active field reads ~0.5px heavier without a reflow (inset shadow, not a wider
     border that would nudge the text). */
  box-shadow: inset 0 0 0 0.5px var(--black);
}
/* Touch devices (iOS Safari): 16px input text avoids the auto-zoom on focus that
   otherwise leaves the page zoomed in and shifts hit-targets (mis-firing taps).
   Also drop the resize grip — touch can't drag-resize a textarea, so the box
   auto-grows with its content instead (see app.js). */
@media (pointer: coarse) {
  .fb-textarea,
  .fb-input { font-size: 16px; }
  .fb-textarea { resize: none; background-image: none; }
}

/* ---- Step 2: contact checkbox ---- */
.fb-check { display: flex; gap: 8px; align-items: center; cursor: pointer; }
.fb-check__input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.fb-check__box {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border-radius: 8px;
  /* Same resting border as the text inputs (.fb-input) so the form reads as one set. */
  border: 1px solid var(--border-muted);
  background: var(--white);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}
.fb-check__box svg { width: 12px; height: 12px; opacity: 0; transition: opacity 0.12s ease; }
.fb-check__input:checked + .fb-check__box { background: var(--black); border-color: var(--black); }
.fb-check__input:checked + .fb-check__box svg { opacity: 1; }
.fb-check__input:focus-visible + .fb-check__box { outline: 2px solid var(--black); outline-offset: 2px; }
.fb-check__label { font-weight: 400; font-size: 14px; line-height: 20px; color: var(--black); }

/* ---- Step 2: full-width SEND ---- */
.fb-actions { width: 100%; padding: 24px; }
.fb-actions .btn { width: 100%; height: 48px; padding-inline: 24px; }
/* Disabled SEND is a flat grey pill (Figma), not a faded dark one. */
.fb-send:disabled { background: var(--track); color: var(--icon-inactive); cursor: default; pointer-events: none; }

/* ---- Step 3: sent confirmation ---- */
.fb-success {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 16px 24px 0;
  text-align: center;
}
.fb-success__icon {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-pill);
  background: var(--black);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
}
.fb-success__icon svg { width: 28px; height: 28px; display: block; }
.fb-success__title { font-weight: 500; font-size: 24px; line-height: 32px; color: var(--black); }
.fb-success__text {
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  color: var(--text-muted);
  max-width: 280px;
}

/* ---- Cards → icon-row morph: floating "ghosts" that fly between the two
   layouts in viewport space (so the modal resize doesn't drag them around). ---- */
.fb-ghost {
  position: fixed;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--white);
  border: 1.5px solid var(--track);
  color: var(--black);
  overflow: hidden;
  z-index: 1050;
  pointer-events: none;
  transition: top 0.42s cubic-bezier(0.22, 1, 0.36, 1),
              left 0.42s cubic-bezier(0.22, 1, 0.36, 1),
              width 0.42s cubic-bezier(0.22, 1, 0.36, 1),
              height 0.42s cubic-bezier(0.22, 1, 0.36, 1),
              border-radius 0.42s cubic-bezier(0.22, 1, 0.36, 1),
              background-color 0.42s ease, border-color 0.42s ease;
}
.fb-ghost svg { width: 24px; height: 24px; display: block; flex-shrink: 0; }
/* The label collapses its height + margin (not just opacity) so the icon glides
   from above-centre (card) to dead-centre (icon button) with no end snap. */
.fb-ghost__label {
  font-weight: 500;
  font-size: 14px;
  line-height: 20px;
  overflow: hidden;
  white-space: nowrap;
  transition: opacity 0.15s ease,
              height 0.42s cubic-bezier(0.22, 1, 0.36, 1),
              margin-top 0.42s cubic-bezier(0.22, 1, 0.36, 1);
}
.fb-ghost--selected { background: var(--black); border-color: var(--black); color: var(--white); }

/* Mobile: category grid becomes 2×2, and the cards drop the desktop portrait
   aspect — they hug the icon + label with 24px vertical padding instead (sides
   stay slim so the nowrap labels fit on one line in the narrow columns). */
@media (max-width: 600px) {
  .fb-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fb-card { aspect-ratio: auto; padding: 24px 10px; }
}

/* ===================== Drag-and-drop overlay ===================== */
.drop-overlay {
  position: fixed;
  inset: 0;
  z-index: 1300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.45);   /* light scrim, no blur */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0s linear 0.2s;
  pointer-events: none; /* visual only — drag/drop is handled on window */
}
.drop-overlay.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.2s ease;
}
/* One clean card: icon + a single line. */
.drop-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 32px 44px;
  background: var(--white);
  border-radius: var(--radius-xl);
  box-shadow: var(--elev-1);
  transform: scale(0.96);
  transition: transform 0.2s ease;
}
.drop-overlay.is-open .drop-card { transform: scale(1); }
.drop-card__icon { width: 30px; height: 30px; color: var(--black); display: block; }
.drop-card__title { font-weight: 600; font-size: 17px; line-height: 22px; color: var(--black); }

/* ===================== 3D model viewer modal (5623:2469) ===================== */
.viewer-overlay {
  position: fixed;
  inset: 0;
  z-index: 1100;
  display: none; /* toggled to block while open */
}
.viewer-overlay.is-active { display: block; }
.viewer-scrim {
  position: absolute;
  inset: 0;
  /* Same light scrim as the About/Feedback modals (rgba(0,0,0,.4)) so the viewer
     blends with the default light browser chrome instead of fighting it with a dark
     backdrop + theme-color flip (which iOS only half-honours, esp. the bottom bar). */
  background: rgba(0, 0, 0, 0.4); /* fades independently */
  opacity: 0;
  transition: opacity 0.32s ease;
}
.viewer-overlay.is-shown .viewer-scrim { opacity: 1; }
.viewer-modal {
  /* Geometry (left/top/width/height) is driven by JS so the box can morph from
     the card to the centred modal without any distorting transform. */
  position: fixed;
  background: var(--white);
  border-radius: var(--radius-xl);
  box-shadow: var(--elev-2); /* same dialog elevation (subtle over the dark scrim) */
  overflow: hidden;
  will-change: left, top, width, height;
}
.viewer-canvas {
  position: absolute;
  /* Full-bleed: fill the modal so a zoomed-in model clips flush at the modal's
     rounded edge (overflow:hidden + border-radius) instead of at a square,
     8px-inset canvas edge that left a white frame. The grid card's canvas is
     still inset 8px (.card--3d padding); framing parity for a pop-free morph is
     restored in JS by pulling the viewer camera back ~W/(W-16) (see insetComp in
     model-grid.js). NB: <canvas> is a replaced element, so it needs an explicit
     (definite) size — width/height 100% keeps it definite while inset:0 anchors it. */
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none; /* let drag rotate instead of scrolling the page */
  cursor: grab;
}
.viewer-canvas:active { cursor: grabbing; }
.viewer-hint {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  padding: 24px;
  background: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0),
    rgba(255, 255, 255, 0.75)
  );
  pointer-events: none;
}
.viewer-hint p {
  font-weight: 400;
  font-size: 12px;
  line-height: 16px;
  color: rgba(38, 38, 38, 0.33);
  text-align: center;
}
/* Visual (size, transparent fill, hover circle, 16px X) comes from .modal-close.
   Positioned so the icon sits 24px from the corner (the 32px button at 16px). */
.viewer-close,
.viewer-download {
  position: absolute;
  top: 16px;
  opacity: 0; /* fades in once the grow finishes (.is-settled) */
  transition: opacity 0.2s ease, background-color 0.15s ease;
}
.viewer-close { right: 16px; }
.viewer-download { left: 16px; } /* opposite corner from the close X */
.viewer-modal.is-settled .viewer-close,
.viewer-modal.is-settled .viewer-download { opacity: 1; }

/* "Reset view" — a quiet pill at the bottom centre, shown only once the model has been
   moved (.is-moved, toggled by the viewer loop). Sits where the first-open hint does, but
   they're mutually exclusive (moving dismisses the hint). */
.viewer-reset {
  position: absolute;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%) translateY(6px);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 38px;
  padding: 0 16px;
  background: var(--white);
  color: var(--black);
  border: 1px solid var(--track);
  border-radius: var(--radius-pill);
  font-family: inherit;
  font-weight: 500;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease, background-color 0.15s ease;
}
.viewer-reset svg { width: 15px; height: 15px; display: block; }
.viewer-reset:hover { background: var(--surface-hover); }
.viewer-modal.is-moved .viewer-reset {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}
/* The "drag to rotate" hint is shown by JS only on the first object opened this
   session (.is-hint), then fades out on its own. */
.viewer-hint { opacity: 0; transition: opacity 0.45s ease; }
.viewer-modal.is-hint .viewer-hint { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .viewer-scrim { transition: opacity 0.01ms linear; }
  .viewer-close,
  .viewer-hint { transition: opacity 0.01ms linear; }
}

/* ===================== Responsive ===================== */

/* Tablet & below — selector offers only 2- and 1-column (3-column hidden, no
   divider). The grid follows the selector and defaults to 2 columns (app.js).
   Mobile selector matches Figma "Device=Mobile" (5605:2225). */
@media (max-width: 1024px) {
  .selector__btn[data-cols="3"] { display: none; }
  .selector[data-selected] .selector__divider { display: none; }
  /* Two buttons: centered on each 48px button — circle shares the pill cap's arc
     at the ends (flush, single line) with the icon centered. */
  .selector[data-selected="2"] .selector__highlight { left: 0; }
  .selector[data-selected="1"] .selector__highlight { left: 48px; }

  /* Footer content already uses .container (24px gutter), which matches the
     rest of the page — so the footer needs no extra horizontal padding here,
     otherwise the two insets stack (48px) and the footer looks narrower. */
  .site-footer { --footer-pad-x: 0px; }
}

/* Phones — tighten spacing. */
@media (max-width: 600px) {
  .upload__title { font-size: 24px; line-height: 30px; }
  /* Drop the tagline's forced <br> so it wraps naturally and balances. */
  .site-footer__tagline br { display: none; }
  /* Wordmark tracks the grid's 16px gutters here (container = 100% - 32px). */
  .site-footer__wordmark { --wm-gutter: 32px; }
}

/* Very small phones — keep the big wordmark at 24px gutters (48px total) so it
   isn't cramped at the edges, even though the grid stays at 16px. */
@media (max-width: 400px) {
  .site-footer__wordmark { --wm-gutter: 48px; }
}

/* Phones — tighten the page gutter to 16px each side. */
@media (max-width: 600px) {
  .container { width: min(100% - 32px, var(--container)); }
}

