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

/* Palette — applied from the polished mobile design references under
 * documentation/designs/. Surfaces are cream, primary buttons are forest
 * green pills, accents stay subtle. Delete is the only red. */
:root {
  --cream-bg: #F8F4EA;
  --forest-green: #2F4F35;
  --dark-forest: #14351F;
  --deep-green: #0F2B1A;
  --soft-green-outline: #6F806F;
  --off-white: #FFFDF6;
  --soft-gray: #E8E6DE;
  --dark-text: #102819;
  --muted-text: #6F746A;
  --delete-red: #E74B3C;
}

/* Dark theme (#89). Same variable names; the values flip so the rest of the
 * stylesheet "just works" — no rule changes needed. Surfaces become deep
 * navy/forest, text becomes off-white, accents are lifted versions of the
 * forest greens for adequate contrast.
 *
 * --dark-forest is overloaded in the existing stylesheet: it serves both as
 * an accent (borders, header h1) AND as a panel background (.meme-search,
 * .editor-library-preview). The mid-green value below works for both
 * roles in dark mode — visible enough as an accent against --cream-bg, but
 * not so light that the panels look like light-mode bleed-through
 * (addresses Aila's review on PR #90). */
[data-theme="dark"] {
  --cream-bg: #0F1A23;
  --forest-green: #6BA77C;
  --dark-forest: #6BA77C;
  --deep-green: #0A1116;
  --soft-green-outline: #4A6852;
  --off-white: #16242E;
  --soft-gray: #1F3340;
  --dark-text: #E8EDE4;
  --muted-text: #9BA89E;
  --delete-red: #FF6B5B;
}

.app-title {
  margin: 0;
  display: flex;
  align-items: center;
}

.header-logo-group {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.app-logo {
  height: 44px;
  width: auto;
  display: block;
  object-fit: contain;
}

.meme-search-header .app-title {
  justify-content: center;
  width: 100%;
}

header .app-logo {
  height: 3.8rem;
  width: auto;
  display: block;
}

.meme-search-header .app-logo {
  height: 4rem;
  display: block;
}

/* Smooth fade between themes. Limited to color properties on a handful of
 * common surfaces so the transition is felt but doesn't slow down hover
 * animations or layout changes. */
body,
header,
header h1,
main,
.canvas-container,
.meme-search,
.editor-panel,
.text-box textarea,
.text-box-toolbar,
.upload-btn,
#download-btn,
#share-btn,
#scan-text-btn,
.search-icon-btn,
.settings-btn,
.font-settings-btn,
.font-settings-menu,
.font-settings-item,
.ai-mode-toggle,
.theme-toggle,
.theme-toggle-track,
.theme-toggle-thumb {
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

html,
body {
  max-width: 100%;
  overflow-x: hidden;
}

body {
  font-family: 'Montserrat', Arial, sans-serif;
  background: var(--cream-bg);
  color: var(--dark-text);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
}

header {
  padding: 1rem 1.25rem 0.5rem;
  text-align: left;
  width: 100%;
  max-width: 900px;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

header h1 {
  font-size: 1.6rem;
  color: var(--dark-forest);
  font-weight: 700;
  letter-spacing: 0.01em;
}

/* Circled magnifying-glass icon button — toggles the meme-search overlay on
 * mobile (no-op on desktop where the search section is already visible). */
.search-icon-btn {
  width: 2.5rem;
  height: 2.5rem;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--dark-forest);
  border: 2px solid var(--dark-forest);
  border-radius: 50%;
  cursor: pointer;
  font-size: 1.05rem;
  line-height: 1;
  padding: 0;
  transition: background 0.15s, color 0.15s;
}

.search-icon-btn:hover,
.search-icon-btn:focus-visible {
  background: var(--dark-forest);
  color: var(--off-white);
  outline: none;
}

main {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 0 16px 40px;
  width: 100%;
  max-width: 900px;
}

.controls {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
  width: 100%;
}

.controls-left {
  display: flex;
  justify-content: flex-start;
  align-items: center;
}

.controls-center {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Intentionally empty — mirrors the left column to keep Upload/Download centered. */
.controls-right {
  display: flex;
}

/* Forest-green pill — the primary action button in the polished design. */
.upload-btn {
  display: inline-block;
  padding: 0.625rem 1.5rem;
  background: var(--forest-green);
  color: var(--off-white);
  border-radius: 999px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  transition: background 0.2s, transform 0.1s;
}

.upload-btn:hover {
  background: var(--dark-forest);
}

#download-btn {
  padding: 0.625rem 1.5rem;
  background: var(--forest-green);
  color: var(--off-white);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  transition: background 0.2s;
}

#download-btn:hover:not(:disabled) {
  background: var(--dark-forest);
}

#download-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ── Font picker (pill in the controls-left column) ────────────────────────── */
/* Sits in the left cell of the controls grid; `position: relative` anchors the
   dropdown menu below the button. */
.font-settings {
  position: relative;
  display: inline-flex;
}

/* Forest-green pill matching the Upload / Download buttons. */
.font-settings-btn {
  padding: 0.625rem 1.5rem;
  background: var(--forest-green);
  color: var(--off-white);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1;
  transition: background 0.2s;
}

.font-settings-btn:hover,
.font-settings-btn[aria-expanded="true"] {
  background: var(--dark-forest);
}

/* Dropdown. Position is set by JS (position:fixed on desktop) or by the mobile
   media query (position:absolute below the Fonts button). Visual styles only here. */
.font-settings-menu {
  z-index: 2000;
  margin: 0;
  padding: 4px;
  list-style: none;
  min-width: 11rem;
  max-height: 60vh;
  overflow-y: auto;
  background: var(--off-white);
  border: 1px solid var(--soft-green-outline);
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(16, 40, 25, 0.18);
}

.font-settings-menu[hidden] {
  display: none;
}

.font-settings-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.5rem 0.75rem;
  background: transparent;
  color: var(--dark-text);
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
}

.font-settings-item:hover,
.font-settings-item:focus {
  background: var(--soft-gray);
  outline: none;
}

/* Share button — hidden by default; JS reveals it on mobile/tablet only. */
#share-btn {
  display: none;
  padding: 0.625rem 1.5rem;
  background: var(--forest-green);
  color: var(--off-white);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  transition: background 0.2s;
}

#share-btn:hover:not(:disabled) {
  background: var(--dark-forest);
}

#share-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Small meme library preview shown in the editor screen on mobile.
 * Sits between the canvas area and the fixed bottom-actions row.
 * Uses the same .meme-search-card / .meme-search-name classes as the
 * full Browse Memes page so no duplicate card styles are needed. */
.editor-library-preview {
  display: none; /* hidden on desktop; mobile block below enables it */
  width: 100%;
  background: var(--dark-forest);
  border-radius: 1rem;
  overflow: hidden;
}

/* Search input inside the compact editor preview */
.editor-library-search-input {
  width: 100%;
  padding: 0.5rem 0.875rem;
  background: transparent;
  color: var(--off-white);
  border: 1.5px solid rgba(255, 253, 246, 0.45);
  border-radius: 999px;
  font-size: 0.875rem;
  outline: none;
  transition: border-color 0.15s;
}

.editor-library-search-input::placeholder {
  color: var(--soft-green-outline);
}

.editor-library-search-input:focus {
  border-color: var(--off-white);
}

.editor-library-results {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  align-items: start;
  align-content: start;
  max-height: clamp(12rem, 32vh, 20rem);
  overflow-y: auto;
  padding: 0.75rem;
}

/* Scan Text — persistent "add text box to the meme" affordance below the
 * canvas. Forest green pill flanked by soft green dividers on either side.
 * Disabled until an image is loaded, matching the download/share pattern. */
#scan-text-btn {
  padding: 0.625rem 1.75rem;
  background: var(--forest-green);
  color: var(--off-white);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  transition: background 0.2s;
}

#scan-text-btn:hover:not(:disabled) {
  background: var(--dark-forest);
}

#scan-text-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.scan-text-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  width: 100%;
}

.scan-text-row::before,
.scan-text-row::after {
  content: '';
  flex: 1 1 0;
  max-width: 6rem;
  height: 1px;
  background: var(--dark-forest);
}

.canvas-container {
    position: relative;
    display: inline-block;
    background: var(--off-white);
    border: 1.5px solid var(--dark-forest);
    border-radius: 1.5rem;
    min-width: 400px;
    min-height: 300px;
    max-width: 100%;
    overflow: visible;
    cursor: pointer;
}

.canvas-container.has-image {
  cursor: default;
  border-color: var(--dark-forest);
  min-width: 0;
  min-height: 0;
  /* Drive the container's height from its width using the image aspect ratio
   * set by JS as --canvas-aspect-ratio. Text boxes (position: absolute) are
   * then placed relative to a container whose dimensions match the displayed
   * canvas, so export scaling stays correct. */
  aspect-ratio: var(--canvas-aspect-ratio, 1 / 1);
  /* Prevent the canvas bitmap from visually spilling outside the rounded
   * border. Text boxes are direct children of .canvas-container (not
   * .canvas-frame), so their controls are unaffected by this clip. */
  overflow: hidden;
}



/* Mobile fonts row — off-screen on desktop (not display:none so that its
   position:fixed child #font-settings-menu can still appear at JS-computed
   viewport coords). Shown in normal flow on mobile. */
.mobile-fonts-row {
  position: fixed;
 
  visibility: hidden;
}

.canvas-container.drag-over {
  border-style: dashed;
  border-color: var(--forest-green);
  background: var(--soft-gray);
}

.canvas-container.drag-over .placeholder {
  color: var(--forest-green);
}

/* Inner wrapper that clips the canvas layer to the rounded border.
 * overflow: hidden on the frame (not the container) so absolutely-positioned
 * text-box controls that extend outside the frame are not clipped. */
.canvas-frame {
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: inherit;
}

/* Copy-to-clipboard button — small, semi-transparent pill at the canvas
 * area's top-right. #canvas-container already has position: relative, so
 * the absolute coords here are container-local. z-index sits above the
 * canvas frame but below the AI panel (z:60+) so it never blocks the
 * AI dialog. Resting opacity is reduced so it doesn't compete with the
 * meme; hover/focus restores full opacity for accessibility. */
.copy-btn {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 3.1875rem;
  height: 3.1875rem;
  border-radius: 999px;
  border: 1px solid var(--forest-green);
  background: rgba(255, 255, 255, 0.72);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  opacity: 0.78;
  z-index: 50;
  box-shadow: 0 1px 4px rgba(15, 43, 26, 0.18);
  transition: opacity 0.15s, transform 0.1s, box-shadow 0.15s, background 0.15s;
}

.copy-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.copy-btn:not(:disabled):hover,
.copy-btn:not(:disabled):focus-visible {
  opacity: 1;
  background: var(--off-white);
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(15, 43, 26, 0.28);
  outline: none;
}

[data-theme="dark"] .copy-btn:not(:disabled):hover,
[data-theme="dark"] .copy-btn:not(:disabled):focus-visible {
  background: #FFFDF6;
}

.copy-btn:not(:disabled):active {
  transform: translateY(0);
}

/* During an active move-handle drag, take the copy button out of the
 * hit-test so it can't intercept the pointer when the dragged text box
 * passes underneath. body.is-moving-text-box is toggled on by
 * DragResize.js's pointerdown/up cycle. visibility + opacity make the
 * removal obvious; pointer-events is the load-bearing part. The button
 * returns to its resting style the moment the drag ends. */
body.is-moving-text-box .copy-btn {
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
}

#meme-canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

.placeholder {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.05rem;
  color: var(--muted-text);
  pointer-events: none;
  user-select: none;
  text-align: center;
  width: 90%;
}

.hint {
  color: var(--muted-text);
  font-size: 0.9rem;
}

/* Text Box */
.text-box {
  position: absolute;
  min-width: 80px;
  min-height: 40px;
  cursor: default;
  z-index: 10;
}

.text-box.selected {
  z-index: 20;
  border: 2px dashed;
}

.text-box textarea {
  width: 100%;
  height: 100%;
  background: transparent;
  border: none;
  color: #fff;
  font-family: Impact, sans-serif;
  font-size: 24px;
  padding: 4px 6px;
  resize: none;
  outline: none;
  overflow: hidden;
  word-wrap: break-word;
  overflow-wrap: break-word;
  white-space: pre-wrap;
  cursor: text;
  -webkit-text-stroke: 1px black;
  text-shadow:
    -1px -1px 0 #000,
     1px -1px 0 #000,
    -1px  1px 0 #000,
     1px  1px 0 #000;
}

.text-box textarea::placeholder {
  color: #fff;
  opacity: 1;
}

.text-box.selected textarea {
  border-color: var(--forest-green);
  
}

/* Toolbar — dark forest panel with cream-outlined controls, matching the
 * polished mobile mockup (Screen 6). The base block sets the look; the
 * @media (max-width: 768px) block lower in the file re-anchors it to the
 * viewport bottom for mobile. */
.text-box-toolbar {
  display: none;
  position: absolute;
  bottom: 100%;
  left: 0;
  background: var(--dark-forest);
  border-radius: 1rem;
  padding: 0.4rem 0.5rem;
  gap: 6px;
  align-items: center;
  white-space: nowrap;
  margin-bottom: 4px;
  z-index: 30;
  max-width: 100vw;
}

.text-box.selected .text-box-toolbar {
  display: none;
}

.text-box-toolbar select {
  padding: 0.3rem 0.5rem;
  border-radius: 0.5rem;
  border: 1px solid var(--off-white);
  background: transparent;
  color: var(--off-white);
  font-size: 0.8rem;
  cursor: pointer;
}

.text-box-toolbar select option {
  background: var(--dark-forest);
  color: var(--off-white);
}

.text-box-toolbar button {
  padding: 0.3rem 0.75rem;
  border-radius: 0.5rem;
  border: 1px solid var(--off-white);
  background: transparent;
  color: var(--off-white);
  font-size: 0.8rem;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.text-box-toolbar button:hover {
  background: var(--off-white);
  color: var(--dark-forest);
}

.text-box-toolbar .delete-btn {
  color: var(--delete-red);
  border-color: var(--delete-red);
  font-weight: 700;
}

.text-box-toolbar .delete-btn:hover {
  background: var(--delete-red);
  color: var(--off-white);
}

/* Resize Handles */
.resize-handle {
  display: none;
  position: absolute;
  width: 12px;
  height: 12px;
  background: var(--forest-green);
  border: 1px solid var(--off-white);
  border-radius: 2px;
  z-index: 25;
}

/* Show the resize handles when the text box is selected. */
.text-box.selected .resize-handle {
  display: block;
}

.resize-handle.top-left {
  top: -6px;
  left: -6px;
  cursor: nw-resize;
}

.resize-handle.top-right {
  top: -6px;
  right: -6px;
  cursor: ne-resize;
}

.resize-handle.bottom-left {
  bottom: -6px;
  left: -6px;
  cursor: sw-resize;
}

.resize-handle.bottom-right {
  bottom: -6px;
  right: -6px;
  cursor: se-resize;
}

.text-box.selected {
  cursor: move;
}

.text-box.selected textarea {
  cursor: move;
}

.text-box.selected textarea:focus {
  cursor: text;
}

.text-box-toolbar,
.text-box-toolbar *,
.resize-handle {
  touch-action: none;
}

/* Visual separator between toolbar groups */
.toolbar-sep {
  display: inline-block;
  width: 1px;
  height: 16px;
  background: var(--soft-green-outline);
  align-self: center;
}

/* Font-size A− / display / A+ controls */
.text-box-toolbar .font-size-btn {
  font-weight: bold;
  font-size: 0.825rem;
  min-width: 2.25rem;
  padding: 0.3rem 0.5rem;
}

.font-size-display {
  display: inline-block;
  min-width: 2.5rem;
  text-align: center;
  font-size: 0.7rem;
  color: var(--off-white);
  background: var(--deep-green);
  border: 1px solid var(--off-white);
  border-radius: 0.4rem;
  padding: 0.2rem 0.3rem;
  pointer-events: none;
  user-select: none;
}

/* ── Meme Search ──
 * Forest-green rounded panel containing the search input + template cards,
 * matching the polished mockup (Screen 5). On mobile the panel is hidden
 * by default and opens as a slide-up overlay when the user taps the
 * header search icon. On desktop it stays visible inline below the editor. */

.meme-search {
  width: 100%;
  margin-top: 1.25rem;
  padding: 1.25rem 1rem 1rem;
  background: var(--dark-forest);
  color: var(--off-white);
  border-radius: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  position: relative;
}

/* Browse panel header: back button sits absolute-left so the title text
 * stays truly centered regardless of whether the button is visible. */
.meme-search-header {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
}

/* Override the base absolute positioning so the button anchors to the
 * header row instead of floating over the full panel. */
.meme-search-header .mobile-home-back-btn {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}

.meme-search-title {
  font-size: 1.05rem;
  color: var(--off-white);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.meme-search-input {
  width: 100%;
  padding: 0.625rem 0.875rem;
  background: transparent;
  color: var(--off-white);
  border: 1.5px solid var(--off-white);
  border-radius: 999px;
  font-size: 1rem;
  outline: none;
  transition: border-color 0.15s;
}

.meme-search-input::placeholder {
  color: var(--soft-green-outline);
}

.meme-search-input:focus {
  border-color: var(--cream-bg);
}

.meme-search-status {
  color: var(--soft-green-outline);
  font-size: 0.9rem;
  min-height: 1.2em;
}

/* Fixed 3-column grid. align-content: start packs rows to the top so no
 * gaps appear between rows when the container is taller than the content. */
.meme-search-results {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.625rem;
  align-items: start;
  align-content: start;
}

.meme-search-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  align-self: start;
  gap: 0.375rem;
  padding: 0.5rem;
  background: transparent;
  border: 1.5px solid var(--off-white);
  border-radius: 1rem;
  cursor: pointer;
  color: var(--off-white);
  font-family: inherit;
  text-align: left;
  transition: background 0.15s, transform 0.1s;
}

.meme-search-card:hover {
  background: rgba(255, 253, 246, 0.06);
}

.meme-search-card:active {
  transform: scale(0.98);
}

.meme-search-card img {
  width: 100%;
  aspect-ratio: 1 / 1;
  height: auto;
  object-fit: cover;
  background: var(--deep-green);
  border-radius: 0.5rem;
}

.meme-search-name {
  display: none;
  font-size: 0.8rem;
  color: var(--off-white);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Selected state for AI suggestion cards in the under-editor preview. */
.meme-search-card.selected {
  border-color: var(--forest-green);
  outline: 2px solid var(--forest-green);
  outline-offset: -1px;
}

/* Show card names in the editor AI preview (normally hidden in search grid). */
.editor-library-results .meme-search-name {
  display: block;
}

/* Scale caption text to fit the smaller preview cards in the under-editor grid. */
.editor-ai-preview-card .ai-preview-caption {
  font-size: 1rem;
}

/* Empty-state message inside the editor library results (AI mode). */
.editor-library-empty {
  color: var(--off-white);
  font-size: 0.875rem;
  padding: 0.75rem 0.5rem;
  text-align: center;
  grid-column: 1 / -1;
  opacity: 0.75;
}

/* Mobile overlay state for the meme-search panel. Triggered by the
 * header search icon adding .is-open to the section. The chevron handle
 * at the top doubles as a close affordance. Desktop never sees this — the
 * @media (min-width: 769px) block forces the inline layout there. */
.meme-search-close {
  position: absolute;
  top: 0.4rem;
  left: 50%;
  transform: translateX(-50%);
  width: 2rem;
  height: 1.25rem;
  background: transparent;
  border: none;
  color: var(--off-white);
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  padding: 0;
  display: none;
  line-height: 1;
}

/* Bottom row inside the meme-search home view: search input on the left,
 * circular + upload button on the right. Pinned to the foot of the panel
 * via the mobile @media block; on desktop it flows naturally inline. */
.meme-search-bottom-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
}

.meme-search-bottom-row .meme-search-input {
  flex: 1 1 auto;
}

/* Circular + button to the right of the search input. Reuses the existing
 * #image-input file picker (wired in app.js). */
.mobile-home-plus-btn {
  flex: 0 0 auto;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
}

.mobile-home-plus-btn:focus-visible {
  outline: 2px solid var(--forest-green);
}

/* AI toggle button — bottom-left of the browse panel, mirrors the
 * upload + button at bottom-right. */
.ai-panda-btn {
  display: none;
  position: absolute;
  bottom: 0.5rem;
  left: 0.5rem;
  background: transparent;
  border: none;
  padding: 0;
  z-index: 5;
  cursor: pointer;
}

.ai-panda-btn:focus-visible {
  outline: 2px solid var(--forest-green);
  border-radius: 50%;
}

.ai-panda-btn:active {
  transform: scale(0.96);
}

.ai-panda-btn img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Desktop AI button — sits in the top .controls row next to Upload/Download.
 * Hidden by default; the desktop @media block lower in this file reveals it.
 * Previously floated at the editor panel's top-left edge; now an inline
 * control pill so the canvas alignment stays predictable. */
.desktop-ai-btn {
  display: none;
  width: 3rem;
  min-width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: var(--forest-green);
  color: var(--off-white);
  border: 2px solid var(--off-white);
  font-size: 1.4rem;
  line-height: 1;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-shadow: 0 2px 8px rgba(15, 43, 26, 0.4);
  cursor: pointer;
  transition: transform 0.1s, box-shadow 0.15s;
}

.desktop-ai-btn:hover {
  transform: scale(1.06);
  box-shadow: 0 4px 12px rgba(15, 43, 26, 0.55);
}

.desktop-ai-btn:active {
  transform: scale(0.96);
}

/* Empty / loading / error message rendered into the desktop meme library
 * (#meme-search-results) when the user has flipped the panel into AI mode.
 * Spans the full grid width so it always sits centered inside the panel. */
.ai-library-message {
  grid-column: 1 / -1;
  color: var(--off-white);
  text-align: center;
  padding: 1.5rem 1rem;
  font-size: 0.95rem;
  margin: 0;
  opacity: 0.9;
}

/* "Back to templates" link rendered above the AI suggestion cards so the
 * user can return to the normal meme library at any time. */
.ai-library-back {
  grid-column: 1 / -1;
  background: transparent;
  color: var(--off-white);
  border: 1.5px solid var(--off-white);
  border-radius: 999px;
  padding: 0.4rem 0.875rem;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  margin-bottom: 0.5rem;
  transition: background 0.15s, color 0.15s;
}

.ai-library-back:hover,
.ai-library-back:focus-visible {
  background: var(--off-white);
  color: var(--dark-forest);
  outline: none;
}

/* Mobile back-to-editor button — sits at the top-left of the home view
 * (Screen 12) when there is editor state to return to. Hidden via the
 * `hidden` attribute on initial load; JS removes the attribute the first
 * time an image is successfully loaded. */
.mobile-home-back-btn {
  position: absolute;
  top: 1rem;
  left: 1rem;
  width: 3rem;
  height: 3rem;
  background: transparent;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
  z-index: 1;
}

.mobile-home-back-btn img {
  width: 3rem;
  height: 3rem;
  object-fit: cover;
  display: block;
}

.mobile-home-back-btn:focus-visible {
  outline: 2px solid var(--off-white);
}

/* Ensure [hidden] beats the display: inline-flex above. Author-CSS specificity
 * (0,2,0) > the base rule (0,1,0) so this reliably wins in all browsers. */
.mobile-home-back-btn[hidden] {
  display: none;
}

/* Mobile-only X delete button inside each text box. Visible on mobile when
 * the text box is selected. Hidden on desktop — the toolbar handles delete there. */
.mobile-delete-btn {
  display: none;
  position: absolute;
  top: -0.625rem;
  right: -0.625rem;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--delete-red);
  color: #fff;
  border: 2px solid #fff;
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  z-index: 30;
  padding: 0;
  touch-action: manipulation;
}

/* Mobile back button — only visible on mobile in editor view (body.editor-open).
 * Sits at the top-left of the header. Matches the search-icon-btn shape so
 * the header chrome stays balanced. Hidden by [hidden] attr by default. */
.mobile-back-btn {
  width: 2.5rem;
  height: 2.5rem;
  flex: 0 0 auto;
  display: none;
  background: transparent;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
}

.mobile-back-btn img {
  width: 2.5rem;
  height: 2.5rem;
  object-fit: cover;
  display: block;
}

.mobile-back-btn:focus-visible {
  outline: 2px solid var(--dark-forest);
}

/* Theme toggle (#89): pill-shaped sliding switch with ☀️ and 🌙.
 * Sits at the right edge of the header just before the settings gear.
 * Claims the margin-left: auto slot so the right-aligned cluster starts
 * here. The thumb slides via translateX when [data-theme="dark"]. */
.theme-toggle {
  flex: 0 0 auto;
  margin-left: auto;
  padding: 0;
  background: transparent;
  border: none;
  cursor: pointer;
}

.theme-toggle-track {
  position: relative;
  display: inline-flex;
  width: 3.5rem;
  height: 1.75rem;
  background: var(--soft-gray);
  border: 1.5px solid var(--soft-green-outline);
  border-radius: 999px;
  overflow: hidden;
}

.theme-toggle-icon {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  line-height: 1;
  z-index: 1;
  position: relative;
  pointer-events: none;
}

.theme-toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(50% - 2px);
  height: calc(100% - 4px);
  background: var(--off-white);
  border-radius: 999px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}

[data-theme="dark"] .theme-toggle-thumb {
  transform: translateX(100%);
}

.theme-toggle:hover .theme-toggle-track,
.theme-toggle:focus-visible .theme-toggle-track {
  border-color: var(--dark-forest);
  outline: none;
}

/* Settings gear button (Screen 13). Same circular shape as the legacy
 * search-icon-btn; the theme toggle now claims the margin-left: auto slot,
 * so the gear sits naturally to the toggle's right. */
.settings-btn {
  width: 2.5rem;
  height: 2.5rem;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--dark-forest);
  border: 2px solid var(--dark-forest);
  border-radius: 50%;
  cursor: not-allowed;
  font-size: 1.1rem;
  line-height: 1;
  padding: 0;
  opacity: 0.85;
}

.settings-btn[disabled],
.settings-btn[aria-disabled="true"] {
  opacity: 0.5;
}

/* AI Mode toggle pill (#87). Header button that swaps the meme library for
 * the AI suggestions panel. Shown on both desktop and mobile (it replaced
 * the inert ⚙ settings button on mobile). */
.ai-mode-toggle {
  display: inline-block;
  flex: 0 0 auto;
  padding: 0.4rem 0.875rem;
  background: transparent;
  color: var(--dark-forest);
  border: 2px solid var(--dark-forest);
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1;
}

.ai-mode-toggle:hover,
.ai-mode-toggle:focus-visible {
  background: var(--dark-forest);
  color: var(--off-white);
  outline: none;
}

.ai-mode-toggle[aria-pressed="true"] {
  background: var(--forest-green);
  color: var(--off-white);
  border-color: var(--forest-green);
}

/* "Fonts" pill row — the primary font-dropdown trigger sits below the
 * canvas on both desktop and mobile. Inline-flex so the wrapper shrinks
 * around the button; the parent (main / .editor-panel) already centers
 * children via align-items: center. Keeps `position: relative` from the
 * co-applied .font-settings class so the dropdown menu anchors to the
 * button instead of the page. */
.fonts-row {
  display: inline-flex;
  align-self: center;
}

.fonts-btn {
  padding: 0.4rem 1.5rem;
  background: var(--forest-green);
  color: var(--off-white);
  border: none;
  border-radius: 999px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
}

.fonts-btn:hover,
.fonts-btn:focus-visible {
  background: var(--dark-forest);
  outline: none;
}

/* Browse Memes pill — matches the Share/Upload pill style. Triggers the
 * browse overlay (sets body.browse-open). */
.browse-memes-btn {
  padding: 0.625rem 1.5rem;
  background: var(--forest-green);
  color: var(--off-white);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  transition: background 0.2s;
}

.browse-memes-btn:hover,
.browse-memes-btn:focus-visible {
  background: var(--dark-forest);
  outline: none;
}

/* Bottom action row (Screen 13). On mobile this is the primary place for
 * Browse Memes + Share; desktop hides the row via @media (min-width: 769px)
 * since Share/Download still live in the top .controls there. */
.bottom-actions {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: auto;
}


/* ── Quick-action menu (mobile long-press) ──
 * Appears when the user holds on a text box without dragging (DragResize.js
 * calls textBox.showQuickActions()). Replaces the floating toolbar visually
 * while open so the two never overlap. Sized in rem/clamp so it stays
 * usable on any phone size, and clamped to the viewport so it cannot push
 * the page wider. Hidden entirely on desktop by the @media override below. */
.quick-action-menu {
  display: none;
}

.quick-action-menu.is-open {
  display: flex;
  position: fixed;
  left: 0.5rem;
  right: 0.5rem;
  bottom: 0.5rem;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.625rem;
  background: var(--dark-forest);
  border-radius: 1.25rem;
  max-width: calc(100vw - 1rem);
  box-shadow: 0 -2px 8px rgba(15, 43, 26, 0.45);
  z-index: 40;
}

.quick-action-btn {
  flex: 1 1 4.5rem;
  min-height: clamp(2.5rem, 8vw, 3.25rem);
  padding: 0.5rem 0.75rem;
  background: transparent;
  color: var(--off-white);
  border: 1.5px solid var(--off-white);
  border-radius: 999px;
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.quick-action-btn:hover,
.quick-action-btn:focus-visible {
  background: var(--off-white);
  color: var(--dark-forest);
  outline: none;
}

.quick-action-btn.quick-action-delete {
  color: var(--delete-red);
  border-color: var(--delete-red);
}

.quick-action-btn.quick-action-delete:hover,
.quick-action-btn.quick-action-delete:focus-visible {
  background: var(--delete-red);
  color: var(--off-white);
}

/* Toggle button used to collapse/expand the meme-search section on mobile —
 * scaled in rem so it stays a comfortable tap target across phone sizes. */
.meme-search-toggle {
  display: block;
  width: 100%;
  margin-top: 0.625rem;
  padding: 0.625rem 0.75rem;
  background: transparent;
  color: var(--off-white);
  border: 1.5px solid var(--off-white);
  border-radius: 999px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  min-height: 2.75rem;
}

.meme-search-toggle:hover,
.meme-search-toggle:focus-visible {
  background: var(--off-white);
  color: var(--dark-forest);
  outline: none;
}

/* ── Mobile (≤768 px) ──
 * Hides the Move button (replaced by press-and-hold in DragResize.js),
 * gates unfocused-textarea touches through to .text-box for hold-to-move,
 * and uses scalable units so the layout adapts to any phone size.
 * Desktop never enters this block. */
@media (max-width: 768px) {
  body {
    overflow-x: hidden;
  }

  header {
    padding: 0.875rem;
    justify-content: space-between;
  }

  header h1 {
    font-size: 1.5rem;
    text-align: center;
    flex: 1 1 auto;
  }
  /* Mobile full-bleed positioning is scoped to the mobile panel only so the
   * desktop panel (lives inside .canvas-container) is not overridden when
   * the same .ai-suggestions-panel base class is loaded at narrow widths. */
  .mobile-ai-panel {
    position: fixed;
    inset: 1rem;
    width: auto;
    max-height: calc(100vh - 2rem);
    transform: none;
    z-index: 1000;
  }
  main {
    /* Extra bottom padding clears the fixed .bottom-actions row. */
    padding: 0 0.75rem 5rem;
    gap: 0.75rem;
  }

  /* The .editor-panel wrapper is transparent on mobile — its children
   * behave as direct flex children of main. */
  .editor-panel {
    display: contents;
  }

  /* Hide the desktop resize handle on mobile. */
  .resizer {
    display: none;
  }

  /* Hide corner resize squares on mobile — dotted border + X button are sufficient. */
  .text-box.selected .resize-handle {
    display: none;
  }

  .controls {
    width: 100%;
    flex-wrap: wrap;
    justify-content: center;
  }

  
  .upload-btn,
  #share-btn,
  #scan-text-btn,
  #browse-memes-btn,
  #download-btn {
    flex: 1 1 8.75rem;
    min-height: clamp(2.5rem, 8vw, 3.25rem);
    padding: 0.75rem 1rem;
    text-align: center;
  }

  /* Hide the top Upload / Download row on mobile. */
  .controls {
    display: none;
  }

  /* Hide Scan Text on mobile. */
  .scan-text-row {
    display: none;
  }

  /* Hide legacy header buttons on mobile. */
  .mobile-back-btn,
  .search-icon-btn {
    display: none;
  }

  /* Full Browse Memes/search: hidden by default; shown as fullscreen
   * overlay when the user opens Browse Memes (body.browse-open). */
  .meme-search {
    display: none;
  }

  body.browse-open .meme-search {
    display: flex;
    position: fixed;
    inset: 0;
    margin: 0;
    border-radius: 0;
    z-index: 50;
    padding: 1.25rem 1rem 1rem;
    overflow: hidden;
  }

  /* ── Mobile AI mode (#87) ──
   * Mobile has no side-by-side panels, so AI mode slides the suggestions panel
   * up as a bottom sheet that temporarily takes the place of the Browse Memes
   * button row. Triggered by the header AI pill that replaced the ⚙ button. */

  /* The panel lives inside .meme-search, which is display:none on mobile until
   * Browse is open — and a display:none ancestor would keep the panel hidden.
   * In AI mode, make .meme-search a transparent pass-through so the fixed
   * bottom-sheet panel renders, while its own library children stay hidden. */
  body.ai-mode .meme-search {
    display: contents;
  }

  body.ai-mode .meme-search-header,
  body.ai-mode #meme-search-input,
  body.ai-mode #meme-search-status,
  body.ai-mode #meme-search-results,
  body.ai-mode #mobile-home-plus-btn {
    display: none;
  }

  body.ai-mode #desktop-ai-suggestions-panel,
  body.ai-mode #desktop-ai-suggestions-panel[hidden] {
    display: flex !important;
    position: fixed;
    top: 50%;
    left: 50%;
    right: auto;
    bottom: auto;

    width: min(90vw, 28rem);
    max-height: 80vh;

    transform: translate(-50%, -50%);
    overflow-y: auto;

    border-radius: 1.25rem;
    z-index: 1001;
  }

  /* While the AI sheet is up it replaces the Browse Memes / Share row. */
  body.ai-mode .bottom-actions {
    display: none;
  }

  /* After the user selects a suggestion: hide the popup overlay without
   * exiting AI mode — body.ai-popup-dismissed is toggled by app.js. */
  body.ai-mode.ai-popup-dismissed #desktop-ai-suggestions-panel,
  body.ai-mode.ai-popup-dismissed #desktop-ai-suggestions-panel[hidden] {
    display: none !important;
  }

  /* Show the under-editor AI preview section when ai-mode is active. */
  body.ai-mode .editor-library-preview {
    display: block;
  }

  /* Hide the search input inside the preview — AI preview doesn't need it. */
  body.ai-mode .editor-library-search-input {
    display: none;
  }

  .meme-search-title {
    text-align: center;
    font-size: 1.4rem;
  }

  .canvas-container {
    min-width: 0;
    width: 100%;
    max-width: 100%;
    touch-action: none;
  }

  .canvas-container:not(.has-image) {
    width: min(100%, 34rem);
    min-height: 18rem;
  }

  .canvas-container.has-image {
    width: min(100%, calc(60vh * var(--canvas-ratio-num, 1)));
    max-height: 60vh;
    min-height: 0;
  }

  .placeholder {
    font-size: 1rem;
    text-align: center;
    width: 90%;
  }

  /* Show the compact editor library preview on mobile in editor view. */
  .editor-library-preview {
    display: none;
  }

  .text-box.menu-open .text-box-toolbar {
    display: none;
  }
  /* Hide the bottom text toolbar on mobile entirely — X button + gestures
   * replace it. */
  .text-box.selected .text-box-toolbar {
    display: none;
  }

  .text-box-delete-btn {
    display: none;
    position: absolute;
    top: -10px;
    right: -10px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 1px solid #fff8e8;
    background: #143d22;
    color: #fff8e8;
    font-size: 16px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    cursor: pointer;
    z-index: 20;
    padding: 0;
  }

  .text-box.selected .text-box-delete-btn {
    display: block;
  }

  .text-box-delete-btn:hover {
    background: #0d2c19;
  }

  /* Show the mobile X delete button when a text box is selected. */
  .text-box.selected .mobile-delete-btn {
    display: flex;
  }

  .text-box {
    max-width: 100%;
  }

  /* Route touches on unfocused textarea to the text-box parent so
   * press-and-hold-to-move works. */
  .text-box .text-content:not(:focus) {
    pointer-events: none;
  }

  .text-box.hold-active {
    box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.6);
  }

  .text-box.menu-open .text-box-toolbar {
    display: none;
  }

  /* Results fill the panel height and scroll internally; rows pack to top. */
  body.browse-open .meme-search-results {
    flex: 1 1 0;
    min-height: 0;
    max-height: none;
    overflow-y: auto;
    padding-bottom: 5rem;
    align-content: start;
  }

  /* Floating upload + button anchored bottom-right of the browse panel. */
  .mobile-home-plus-btn {
    position: absolute;
    bottom: 2rem;
    right: 2rem;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: 50%;
    background: transparent;
    border: none;
    padding: 0;
    flex: none;
    z-index: 5;
    overflow: hidden;
  }

  .mobile-home-plus-btn img {
    width: 5rem;
    height: 5rem;
    object-fit: cover;
    display: block;
  }

  /* AI toggle button shown bottom-left of the browse panel. */
  body.browse-open .ai-panda-btn {
    display: block;
    width: 15rem;
    height: 7rem;
  }

  /* Show the Share button via CSS — JS inline override is not needed. */
  #share-btn {
    display: block;
  }

  /* Font picker lives in controls-left — hidden on mobile (use .mobile-fonts-row instead). */
  .controls-left {
    display: none;
  }

  /* Mobile fonts row — in normal flow below the canvas. position:relative makes
     it the containing block for the absolutely-positioned #font-settings-menu. */
  .mobile-fonts-row {
    position: relative;
    display: flex;
    justify-content: center;
    margin-top: 0.75rem;
    visibility: visible;
  }

  /* Font-settings menu: anchored directly below the Fonts button. */
  #font-settings-menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    left: 50%;
    right: auto;
    bottom: auto;
    transform: translateX(-50%);
    visibility: visible;
    width: min(90vw, 18rem);
    max-height: 45vh;
    overflow-y: auto;
  }

  /* Browse Memes | Share fixed at viewport bottom on mobile (2 items). */
  .bottom-actions {
    position: fixed;
    left: clamp(1rem, 5vw, 2rem);
    right: clamp(1rem, 5vw, 2rem);
    bottom: clamp(1rem, 4vh, 2rem);
    margin-top: 0;
    z-index: 10;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(0.5rem, 2vw, 0.75rem);
    align-items: center;
    max-width: 90vw;
    box-sizing: border-box;
  }

  .bottom-actions > button {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
  }

  body:has(.text-box.selected) .bottom-actions {
    display: none;
  }
}

.text-box-delete-btn {
  display: none;
  position: absolute;
  top: -14px;
  right: -14px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid #ffffff;
  background: #e74b3c;
  color: #ffffff;
  font-size: 20px;
  font-weight: 800;
  line-height: 22px;
  text-align: center;
  cursor: pointer;
  z-index: 40;
  padding: 0;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
  touch-action: manipulation;
}

.text-box.selected .text-box-delete-btn {
  display: flex;
  align-items: center;
  justify-content: center;
}

.text-box-delete-btn:hover,
.text-box-delete-btn:focus-visible {
  background: #c93125;
  outline: none;
}

/* ── Desktop (>768 px) ──
 * Two-column layout: editor panel on the left, meme search panel on the
 * right, separated by a draggable resizer (LayoutManager.js). */
@media (min-width: 769px) {
  html, body {
    height: 100vh;
    overflow: hidden;
  }

  body {
    align-items: stretch;
  }

  header {
    max-width: none;
    border-bottom: 1px solid var(--soft-gray);
    flex-shrink: 0;
  }

  /* Mobile-only affordances stay hidden on desktop. */
  .meme-search-toggle {
    display: none;
  }

  .quick-action-menu.is-open {
    display: none;
  }

  .mobile-home-plus-btn,
  .mobile-back-btn,
  .mobile-home-back-btn,
  .search-icon-btn,
  .bottom-actions,
  .settings-btn,
  .ai-panda-btn,
  .editor-library-preview {
    display: none;
  }

  main {
    flex: 1;
    min-height: 0;
    flex-direction: row !important;
    align-items: stretch !important;
    max-width: none !important;
    padding: 0 !important;
    gap: 0 !important;
  }

  /* Left editor panel.
   * overflow: visible keeps the panel from owning a scrollbar — any
   * extra height flows up to <body>, which is the existing page
   * scroller. This is deliberate: when the panel managed its own
   * overflow, a textbox move-handle drag could push the selected
   * toolbar past the panel's edge, the browser flipped the panel's
   * scrollbar on/off mid-drag, and the resulting inner-width shift
   * jumped the drag math. With overflow: visible the panel never
   * toggles, so move math (container.offsetWidth/Height) stays stable.
   * position + low z-index put the panel UNDER the right-side library
   * so any toolbar that visually crosses the panel edge is covered. */
  .editor-panel {
    flex: 1;
    overflow: visible;
    padding: 2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    position: relative;
  }

  /* Reveal the desktop AI button on wide viewports. Mobile keeps its
   * `display: none` from the base rule so the panda inside Browse Memes
   * stays the only mobile entry point. */
  .desktop-ai-btn {
    display: inline-flex;
  }

  /* Right meme search panel — display: flex !important overrides mobile
   * display: none so it stays permanently visible on desktop.
   * min-width allows the grid to shrink to 1 card; max-width lets the user
   * drag up to 50 % of the viewport (LayoutManager enforces the same limit). */
  .meme-search {
    width: 380px;
    min-width: 160px;
    max-width: 50vw;
    height: 100%;
    margin-top: 0 !important;
    border-radius: 0 !important;
    overflow-y: auto;
    flex-shrink: 0;
    display: flex !important;
    position: relative;
    z-index: 100;
  }

  /* Canvas container is centered inside the editor panel. min-height and
   * width for the empty state are handled by .canvas-container:not(.has-image). */
 .canvas-container {
        display: block;
        align-self: stretch;
        min-width: 0;
        max-width: 100%;
        min-height: clamp(18rem, 40vh, 28rem);
        box-sizing: border-box;
  }

  /* Once an image is loaded, constrain width so tall/wide images don't
   * overflow the panel. 100% ensures it never exceeds the panel width even
   * when the panel is narrower than 90% of the viewport height * ratio. */
  .canvas-container.has-image {
    min-height: 0;
    align-self: center;
    width: min(90%, 100%, calc(72vh * var(--canvas-ratio-num, 1)));
    max-height: 72vh;
  }

  /* Draggable vertical resizer between the two panels.
   * position: relative + z-index: 101 places the handle above both the
   * editor panel (z:1) and the meme-search library (z:100), so the
   * resize-grip stays grabbable even when a toolbar visually extends
   * underneath the library. */
  .resizer {
    display: block;
    width: 8px;
    background-color: var(--soft-gray);
    cursor: col-resize;
    transition: background-color 0.2s;
    flex-shrink: 0;
    position: relative;
    z-index: 101;
    border-left: 1px solid rgba(0, 0, 0, 0.04);
    border-right: 1px solid rgba(0, 0, 0, 0.04);
  }

  .resizer:hover,
  .resizer:active {
    background-color: var(--forest-green);
  }

  /* Enable container queries on the meme-search panel so the grid adapts
   * as the user drags the resizer. */
  .meme-search {
    container-type: inline-size;
  }

  .meme-search-results {
    grid-template-columns: repeat(1, minmax(0, 1fr));
    gap: 10px;
  }

  @container (min-width: 16rem) {
    .meme-search-results {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }

  @container (min-width: 24rem) {
    .meme-search-results {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }
  }

  @container (min-width: 32rem) {
    .meme-search-results {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }
  }

  /* ── Desktop AI mode (#87) ──
   * When body.ai-mode is on, the right-side .meme-search panel swaps its
   * library content for the inline AI suggestions panel. The layout is
   * unchanged: the editor/canvas stays on the LEFT and the AI panel takes
   * the RIGHT panel in place of the template library. */

  /* Hide the library content so the AI panel can take over the right panel. */
  body.ai-mode .meme-search-header,
  body.ai-mode #meme-search-input,
  body.ai-mode #meme-search-status,
  body.ai-mode #meme-search-results {
    display: none;
  }

  /* Force the AI panel visible (overrides [hidden]) and unwind the centered
   * canvas-overlay positioning of .desktop-ai-panel so it lays out inline
   * inside .meme-search instead of floating. */
  body.ai-mode #desktop-ai-suggestions-panel,
  body.ai-mode #desktop-ai-suggestions-panel[hidden] {
    display: flex;
    position: static;
    top: auto;
    left: auto;
    transform: none;
    width: auto;
    max-height: none;
    overflow: visible;
    box-shadow: none;
    border: none;
    background: transparent;
    padding: 0;
    z-index: auto;
  }

  /* The header toggle controls visibility, so the modal close × is redundant. */
  body.ai-mode #desktop-ai-suggestions-panel .ai-close-btn {
    display: none;
  }

  /* The panel now sits on .meme-search's --dark-forest background, so switch
   * the panel text from the default forest-green palette to light colors so
   * it stays readable (matches how .meme-search styles its own content). */
  body.ai-mode #desktop-ai-suggestions-panel .ai-suggestions-title,
  body.ai-mode #desktop-ai-suggestions-panel .ai-label,
  body.ai-mode #desktop-ai-suggestions-panel .ai-change-key,
  body.ai-mode #desktop-ai-suggestions-panel .ai-note a {
    color: var(--off-white);
  }

  body.ai-mode #desktop-ai-suggestions-panel .ai-note,
  body.ai-mode #desktop-ai-suggestions-panel .ai-status {
    color: var(--soft-gray);
  }

  /* In AI mode the canvas is a blank target for the chosen suggestion — hide
   * the upload placeholder so it looks intentionally empty. */
  body.ai-mode .canvas-container:not(.has-image) .placeholder {
    display: none;
  }
}

/* On very narrow screens (<=360 px) fall back to 2 columns. */
@media (max-width: 360px) {
  .meme-search-results {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }
}

/* On narrow screens, tighten toolbar controls. */
@media (max-width: 480px) {
  .text-box-toolbar button,
  .text-box-toolbar select {
    font-size: 0.8125rem;
    padding: 0.375rem 0.5rem;
  }

  /* On the smallest phones drop the read-only size readout to free space.
   * A- / A+ still work. */
  .text-box-toolbar .font-size-display {
    display: none;
  }
}

/* ── AI Suggestions ──────────────────────────────────────────────────────── */
/* ── AI Suggestions ──────────────────────────────────────────────────────── */
.ai-suggestions-panel {
  padding: 20px;
  background: var(--off-white);
  border: 1px solid var(--soft-green-outline);
  border-radius: 1.5rem;
  box-shadow: 0 8px 24px rgba(15, 43, 26, 0.25);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ai-suggestions-panel[hidden] {
  display: none;
}

.desktop-ai-panel {
  position: absolute;
  top: 50%;
  left: 50%;
  /* Sized to fit comfortably inside the canvas container even when the
   * library has been dragged narrow. min() lets the panel shrink with the
   * container while never blowing past 460px / 420px on wide screens. */
  width: min(460px, calc(100% - 3rem));
  max-height: min(420px, calc(100% - 3rem));
  transform: translate(-50%, -50%);
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  z-index: 60;
}

.desktop-ai-panel::-webkit-scrollbar {
  display: none;
}

.mobile-ai-panel {
  position: fixed;
  top: 50%;
  left: 50%;
  width: min(90vw, 34rem);
  max-height: min(80vh, 38rem);
  transform: translate(-50%, -50%);
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  z-index: 1000;
}

.mobile-ai-panel::-webkit-scrollbar {
  display: none;
}

@media (max-width: 768px) {
  .desktop-ai-panel {
    display: none !important;
  }
}

@media (min-width: 769px) {
  .mobile-ai-panel {
    display: none !important;
  }
}

.ai-suggestions-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.ai-close-btn {
  background: transparent;
  border: none;
  color: var(--soft-green-outline);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 6px;
  border-radius: 4px;
}

.ai-close-btn:hover {
  color: var(--forest-green);
  background: rgba(47, 79, 53, 0.1);
}



.ai-suggestions {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--soft-green-outline);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ai-suggestions-title {
  font-size: 1.1rem;
  color: var(--forest-green);
  font-weight: 600;
}

.ai-get-btn,
.ai-generate-btn {
  align-self: flex-start;
  padding: 10px 20px;
  background: var(--forest-green);
  color: var(--off-white);
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  transition: background 0.2s, opacity 0.2s;
}

.ai-get-btn:hover,
.ai-generate-btn:hover:not(:disabled) {
  background: var(--deep-green);
}

[data-theme="dark"] .ai-generate-btn:hover:not(:disabled) {
  color: var(--forest-green);
}

.ai-generate-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ai-form,
.ai-key-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ai-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ai-label {
  color: var(--forest-green);
  font-size: 0.9rem;
}

.ai-input {
  width: 100%;
  padding: 10px 14px;
  background: var(--cream-bg);
  color: var(--forest-green);
  border: 1px solid var(--soft-green-outline);
  border-radius: 6px;
  font-size: 1rem;
  outline: none;
  transition: border-color 0.15s;
}

.ai-input:focus {
  border-color: #e94560;
}

.ai-note {
  color: var(--soft-green-outline);
  font-size: 0.8rem;
}

.ai-note a {
  color: var(--forest-green);
}

.ai-status {
  color: var(--soft-green-outline);
  font-size: 0.9rem;
  min-height: 1.2em;
}

.ai-change-key {
  align-self: flex-start;
  color: var(--forest-green);
  font-size: 0.85rem;
  text-decoration: underline;
}

.ai-results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 12px;
}

.ai-result-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 8px;
  background: var(--cream-bg);
  border: 1px solid var(--soft-green-outline);
  border-radius: 6px;
  cursor: pointer;
  color: var(--forest-green);
  font-family: inherit;
  text-align: left;
  transition: border-color 0.15s, transform 0.1s;
}

.ai-result-card:hover {
  border-color: #e94560;
}

.ai-result-card:active {
  transform: scale(0.98);
}

.ai-result-name {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--dark-forest);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Caption overlay wrapper — positions captions on top of the preview image. */
.ai-preview-wrap {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 0.4rem;
  background: var(--off-white);
}

.ai-preview-wrap img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 0;
}

.ai-preview-caption {
  position: absolute;
  left: 5%;
  right: 5%;
  z-index: 2;
  color: white;
  font-family: Impact, Anton, sans-serif;
  font-weight: 900;
  font-size: 1rem;
  line-height: 1.15;
  text-align: center;
  text-transform: uppercase;
  -webkit-text-stroke: 0.5px black;
  text-shadow:
    1px 1px 0 black,
    -1px 1px 0 black,
    1px -1px 0 black,
    -1px -1px 0 black;
  overflow-wrap: break-word;
  pointer-events: none;
}

.ai-preview-caption-top {
  top: 5%;
}

.ai-preview-caption-bottom {
  bottom: 5%;
}

.ai-preview-caption-middle {
  top: 50%;
  transform: translateY(-50%);
}

/* Keep caption list in DOM for screen readers but hide it visually. */
.ai-caption-list.visually-hidden,
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* On narrow screens, single-column AI results. */
@media (max-width: 480px) {
  .ai-results {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem;
  }
}
