/* Structural styles shared by every site. Each site supplies its own
   theme.css (see public/sites/<slug>/theme.css) that overrides these
   variables and is loaded after this file. */
:root {
  --bg: #f6f5f8;
  --surface: #ffffff;
  --border: #e5e1ea;
  --text: #211c29;
  --text-muted: #6f6779;
  --primary: #a3348f;
  --primary-hover: #8a2b78;
  --error: #d64545;
  --success: #1e9e5a;
  --radius: 10px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}

* {
  box-sizing: border-box;
}

/* Now that a real viewport meta tag is present on every page, iOS Safari's
   automatic text-size adjustment on rotation can inflate font sizes past
   what the mobile layout was designed for -- pin it off.

   overflow-x: hidden (preventing any wide content from causing horizontal
   scroll/bounce, mobile in particular) lives here on the root element
   rather than on body below: setting it on body instead makes body itself
   a scroll container, which becomes the nearest positioned ancestor for
   .nav's own position: sticky (a direct child of body) -- sticky then
   tracks body's own box instead of the viewport, and .nav stops sticking
   to the top of the screen on scroll. Here on html it still suppresses
   horizontal overflow the same way, without sitting between .nav and the
   viewport. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  overflow-x: hidden;
}

/* A column flex container, combined with .site-footer's margin-top:auto
   below, is what actually pins the footer (see public/js/footer.js) to
   the bottom of the viewport on a short page -- without this, a page
   whose content doesn't fill the viewport leaves the footer sitting
   wherever the content happens to end, with a gap of bare background
   below it, rather than at the bottom of the page. On a page taller than
   the viewport this has no effect beyond that: the footer still just
   follows normally after all the content once scrolled to, in flow, so
   it can never overlap it either way. */
body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* Plain-text language switcher -- injected by js/landing.js, living
   inside the .nav bar. #language-switcher is just the positioning
   wrapper for the dropdown (needs position: relative so
   #language-switcher-menu below can anchor to it, and margin-left: auto
   to push it to the far right, same mechanism .nav a.nav-logout uses);
   the actual visible control is #language-switcher-toggle. */
#language-switcher {
  position: relative;
  margin-left: auto;
}

#language-switcher-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.5rem;
  font-size: 0.9rem;
  line-height: 1;
  font-family: var(--font);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  color: var(--text);
}

#language-switcher-toggle:hover {
  border-color: var(--primary);
}

/* Sized up from the plain short-code text this used to show -- a flag
   emoji at the old 0.85rem reads too small to tell apart clearly. */
#language-switcher-code {
  font-size: 1.3rem;
  line-height: 1;
}

/* Dropdown list, hidden by default (public/js/landing.js toggles its own
   [hidden] attribute) -- anchored to the wrapper above, not the toggle
   button itself. */
#language-switcher-menu {
  position: absolute;
  top: calc(100% + 0.3rem);
  right: 0;
  min-width: 160px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
  overflow: hidden;
  z-index: 1001;
}

/* Flag + native language name side by side (public/js/landing.js's own
   .language-switcher-option-flag span, then the name as a plain text
   node). */
.language-switcher-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.5rem 0.75rem;
  background: none;
  border: none;
  font-size: 0.95rem;
  font-family: var(--font);
  text-align: left;
  cursor: pointer;
  color: var(--text);
}

.language-switcher-option-flag {
  font-size: 1.2rem;
  line-height: 1;
}

.language-switcher-option:hover {
  background: var(--bg);
}

.nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 1rem 2rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 100;
}

/* FOUC fix: hides the raw, untranslated nav until js/i18n.js's own
   applyStaticTranslations() finishes and sets this back to visible --
   without it, a visitor briefly sees the English nav before it flips to
   the real language. */
nav.nav {
  visibility: hidden;
}

.nav a.brand {
  color: var(--primary);
  font-weight: 800;
  font-size: 1.1rem;
  text-decoration: none;
  /* Reserves the logo's own eventual size (public/js/site-logo.js fills
     this in asynchronously, after the nav has already painted) so the
     rest of the nav doesn't shift right when it loads in. */
  width: 48px;
  height: 48px;
}

/* An admin-uploaded logo (public/js/site-logo.js) replacing the .brand
   link's text content -- sized to fit the nav bar's own height rather
   than the image's natural size, which could be arbitrary. */
.brand-logo {
  display: block;
  height: 48px;
}

.nav a {
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 500;
  font-size: 0.95rem;
}

.nav a:hover {
  color: var(--primary);
}

.nav a.active {
  color: var(--primary);
}

.nav a.nav-logout {
  margin-left: auto;
}

/* Hamburger menu for the authenticated top nav (chats/likes/visits/
   favorites/search/account/buy-credits/home/chat/profile.html) --
   #mobile-nav-menu wraps the items that collapse into it on mobile (see
   the @media block below for that side of this). display: contents here
   keeps desktop completely unaffected: the wrapper itself generates no
   box at all, so its children (Likes Me/Visited Me/My Favourites/Search/
   My Account/Log out) participate directly in .nav's own flex layout,
   exactly as if this wrapper didn't exist. Log out is the only one of
   them that actually needs repositioning -- grouping it with the others
   moved it earlier in the DOM (right after My Account, for the mobile
   menu's own sake), so without this it would render there on desktop
   too instead of staying last (after Buy Credits, pushed flush right by
   its own .nav a.nav-logout margin-left: auto above) like today. Scoped
   to pages that actually have the hamburger button, via the same
   :has() approach index.html's own language-switcher fix already uses,
   so page.html's differently-shaped nav (its own #logout-link, no
   #mobile-nav-toggle) is entirely unaffected. */
#mobile-nav-menu {
  display: contents;
}

.nav:has(#mobile-nav-toggle) #logout-link {
  order: 1;
}

.mobile-nav-toggle {
  display: none;
}

/* width: 100% is required now that body is a flex container (see its own
   comment above, added for the sticky footer): a flex item with `width:
   auto` and auto side margins doesn't stretch to fill the container the
   way a normal block element would -- it shrink-wraps to its content
   instead, so max-width capped it correctly but nothing pushed it out
   to actually reach that cap. Explicit width: 100% (capped by max-width,
   still centered by the auto margins) restores the original fill-then-
   cap-then-center behavior. Without this, any page whose content doesn't
   independently demand the full width -- My Account's photo/fields grid,
   Buy Credits' package grid -- silently collapses to a narrow column
   instead of laying out across the intended max-width. */
.container {
  width: 100%;
  max-width: 480px;
  margin: 2.5rem auto;
  padding: 0 1.5rem;
}

.container.wide {
  max-width: 1100px;
}

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
  gap: 1rem;
}

.page-header h1 {
  margin: 0;
}

.search-input-wrap {
  margin-bottom: 1.5rem;
}

.search-input-wrap input[type="text"] {
  max-width: 360px;
}

.search-name-row {
  margin-bottom: 1rem;
}

.search-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1.25rem;
  margin-bottom: 1.5rem;
}

.search-filters label {
  margin-top: 0;
}

.search-filters button {
  width: auto;
  padding-left: 2rem;
  padding-right: 2rem;
}

.search-age-range {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.search-age-range input[type="number"] {
  width: 70px;
}

.search-inline-field {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.search-inline-field label {
  margin: 0;
  white-space: nowrap;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2rem;
}

h1 {
  font-size: 1.5rem;
  margin: 0 0 0.5rem;
}

p.subtitle {
  color: var(--text-muted);
  margin-top: 0;
  margin-bottom: 1.5rem;
}

label {
  display: block;
  font-size: 0.9rem;
  font-weight: 600;
  margin-bottom: 0.35rem;
  margin-top: 1rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="date"],
input[type="number"],
select {
  width: 100%;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.95rem;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
}

input:focus,
select:focus {
  outline: 2px solid var(--primary);
  outline-offset: 1px;
}

.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 1.1rem;
}

.checkbox-row input {
  margin-top: 0.2rem;
}

.checkbox-row label {
  margin: 0;
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Matchmaking profile questions (My Account) -- #matchmaking-questions
   itself now reuses .profile-detail-fields' two-column grid (see
   account.html), whose own `gap` handles spacing between questions, so
   .question-field no longer needs its own margin-top the way it did back
   when these were a single stacked column. */
.question-field > label {
  margin-top: 0;
}

/* Hobbies' own custom dropdown (My Account's Matchmaking Profile form,
   see public/js/account.js's renderQuestionField) -- a plain <select
   multiple> reads as a tall always-open listbox, not "a dropdown", so
   this question alone gets a closed/open widget instead: a button
   styled like every other field's <select>/<input> (same border/padding/
   background as the shared rule above) that expands into a checkbox
   list on click. */
.hobbies-dropdown {
  position: relative;
}

.hobbies-dropdown-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.95rem;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
  text-align: left;
}

.hobbies-dropdown-toggle:focus {
  outline: 2px solid var(--primary);
  outline-offset: 1px;
}

.hobbies-dropdown-summary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hobbies-dropdown-arrow {
  flex-shrink: 0;
  color: var(--text-muted);
  font-size: 0.7rem;
}

.hobbies-dropdown-panel {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: 0;
  right: 0;
  z-index: 20;
  max-height: 220px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.hobbies-dropdown-panel[hidden] {
  display: none;
}

.hobbies-dropdown-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.25rem;
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--text);
  cursor: pointer;
}

.hobbies-dropdown-option input {
  margin: 0;
}

/* My Account's About me field (#about-me-field, in the right-hand column
   below the read-only profile info -- see account.html/.about-me-field-col
   above). */
.about-me-field-col .question-field {
  display: flex;
  flex-direction: column;
}

/* No flex-grow here -- its height is instead pinned by JS
   (public/js/account.js's alignAboutMeTextareaHeight) to exactly match the
   bottom of the Edit photo/Settings buttons on the left, which a CSS-only
   fill can't target specifically. No min-height either -- a floor taller
   than the computed height would win over it and break the alignment
   (confirmed: that's what was causing the mismatch); the rows="6" markup
   attribute is the only pre-JS/no-JS fallback sizing now. */
.about-me-textarea {
  width: 100%;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.95rem;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  resize: none;
}

.yes-no-toggle {
  display: flex;
  gap: 1.5rem;
  margin-top: 0.4rem;
}

.yes-no-toggle label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 400;
  font-size: 0.95rem;
  margin-top: 0;
}

button {
  cursor: pointer;
  border: none;
  border-radius: 8px;
  font-size: 0.95rem;
  font-weight: 600;
  padding: 0.7rem 1.25rem;
  background: var(--primary);
  color: white;
  transition: background 0.15s ease;
  width: 100%;
}

button:hover {
  background: var(--primary-hover);
}

button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.btn-danger {
  background: var(--error);
}

.btn-danger:hover {
  background: var(--error);
  opacity: 0.85;
}

.btn-row {
  margin-top: 1.5rem;
}

.language-buttons {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.message {
  margin-top: 1rem;
  padding: 0.65rem 0.85rem;
  border-radius: 8px;
  font-size: 0.9rem;
}

.message.success {
  background: #e6f7ee;
  color: var(--success);
}

.message.error {
  background: #fbeaea;
  color: var(--error);
}

.form-footer {
  margin-top: 1.25rem;
  font-size: 0.88rem;
  color: var(--text-muted);
  text-align: center;
}

.form-footer a {
  color: var(--primary);
  font-weight: 600;
  text-decoration: none;
}

.field-hint {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 0.25rem;
}

/* Profile grid */
.profiles-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1.25rem;
}

.profile-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.profile-photo {
  position: relative;
  aspect-ratio: 3 / 4;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: 800;
  font-size: 2.25rem;
  letter-spacing: 0.02em;
  overflow: hidden;
}

/* Online-status dot (see public/js/profiles-common.js's statusDotHtml) --
   a fictional profile's presence indicator, shown on its photo on its own
   detail page. */
.status-dot {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid var(--surface);
  z-index: 2;
}

/* Card variant (statusDotInlineHtml) -- next to the name instead of on
   the photo, so it lays out inline with the text rather than
   .status-dot's own position:absolute (anchored to .profile-photo,
   which the card's name line isn't). */
.status-dot-inline {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: 0.4rem;
  vertical-align: middle;
}

.status-dot-green {
  background: #2ecc71;
}

.status-dot-yellow {
  background: #f1c40f;
}

.profile-photo img,
.chat-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.profile-photo-link,
.profile-name-link {
  cursor: pointer;
}

.profile-name-link:hover {
  text-decoration: underline;
}

.profile-info {
  padding: 0.85rem 1rem 0.6rem;
}

.profile-name-age {
  font-weight: 700;
  font-size: 1.02rem;
}

/* Grid card + profile detail page's match percentage (see
   routes/profiles.js's computeMatchPercentage and public/js/
   profiles-common.js's matchPercentageHtml). */
.match-percentage {
  color: var(--primary);
  font-size: 0.82rem;
  font-weight: 600;
  margin-top: 0.15rem;
}

.profile-actions {
  display: flex;
  gap: 0.5rem;
  padding: 0 1rem 1rem;
}

.profile-actions button {
  width: auto;
  flex: 1;
  padding: 0.55rem 0.5rem;
  font-size: 0.85rem;
}

.btn-outline {
  background: transparent;
  color: var(--primary);
  border: 1px solid var(--primary);
}

.btn-outline:hover {
  background: var(--bg);
}

.btn-outline.active {
  background: var(--primary);
  color: white;
}

.no-results {
  color: var(--text-muted);
  text-align: center;
  padding: 2rem 0;
}

/* Account settings */
.settings-section {
  margin-bottom: 1.5rem;
}

.settings-section:last-child {
  margin-bottom: 0;
}

.settings-section h2 {
  font-size: 1.1rem;
  margin: 0 0 0.25rem;
}

/* A heading with an action button to its right (currently just
   Matchmaking Profile's "About my match", see account.html/account.js)
   instead of the heading alone -- the row itself carries the heading's
   own bottom margin now, and the button overrides the base button's
   width:100% so it sits at its natural size next to the heading instead
   of filling the row. */
.section-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin: 0 0 0.25rem;
}

.section-header-row h2 {
  margin: 0;
}

.section-header-row button {
  width: auto;
  flex-shrink: 0;
}

/* Credits badge (shown in nav on authenticated pages) */
.credits-badge {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--primary);
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
}

/* Chat page */
.chat-shell {
  max-width: 640px;
  margin: 2rem auto;
  padding: 0 1.5rem;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 140px);
}

.chat-header {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding-bottom: 1rem;
}

.chat-avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: 800;
  flex-shrink: 0;
  overflow: hidden;
}

.chat-header h1 {
  margin: 0;
  font-size: 1.15rem;
}

.chat-header .subtitle {
  margin: 0.1rem 0 0;
}

.chat-window {
  flex: 1;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.bubble {
  max-width: 75%;
  padding: 0.6rem 0.85rem;
  border-radius: 14px;
  font-size: 0.92rem;
  line-height: 1.35;
  word-wrap: break-word;
}

.bubble .meta {
  display: block;
  font-size: 0.72rem;
  opacity: 0.7;
  margin-bottom: 0.15rem;
}

/* Display only -- an agent-attached photo in a message. Right-click and
   drag-to-save are blocked client-side (see public/js/chat.js/chats.js);
   this is a reasonable deterrent for casual saving, not a real technical
   guarantee against a determined user (e.g. a screenshot), which no
   client-side measure can actually prevent. */
.bubble img {
  max-width: 220px;
  max-height: 220px;
  border-radius: 8px;
  display: block;
  margin-top: 0.3rem;
  object-fit: cover;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.bubble.from-user {
  align-self: flex-end;
  background: var(--primary);
  color: white;
  border-bottom-right-radius: 4px;
}

.bubble.from-profile {
  align-self: flex-start;
  background: var(--bg);
  color: var(--text);
  border-bottom-left-radius: 4px;
}

.chat-input-row {
  display: flex;
  gap: 0.6rem;
  margin-top: 1rem;
}

.chat-input-row input[type="text"] {
  flex: 1;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.95rem;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
}

.chat-input-row button {
  width: auto;
  flex-shrink: 0;
}

.chat-attach-btn {
  background: transparent;
  color: var(--primary);
  border: 1px solid var(--primary);
  padding: 0.55rem 0.7rem;
  font-size: 1rem;
  line-height: 1;
  /* Fixed height + flex centering, rather than relying on padding and the
     glyph's own line-height to size the box: the attach button's 📎 and
     the emoji picker's 😀 (also .chat-attach-btn, see chat.html/chats.js)
     render at different natural glyph heights at the same font-size, so
     without this the two buttons ended up different heights even though
     every other property matched. */
  height: 2.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.chat-attach-btn:hover {
  background: var(--bg);
}

/* Shown between the message window and the input row once a photo is
   picked (see the attach button above) -- a small preview of exactly
   what's about to be uploaded/sent, with a way to back out before
   sending. Hidden (via the [hidden] attribute) when nothing is selected. */
.chat-photo-preview {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.75rem;
}

/* .chat-photo-preview's own `display: flex` above is a class selector, so
   it beats the browser's default `[hidden] { display: none }` rule on
   specificity -- without this override, toggling the element's `hidden`
   property in JS (chat.js/chats.js) has no visual effect and the preview
   row stays visible even with nothing selected. */
.chat-photo-preview[hidden] {
  display: none;
}

.chat-photo-preview img {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
}

.chat-photo-preview button {
  width: auto;
  padding: 0.35rem 0.7rem;
  font-size: 0.8rem;
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
}

/* Buy Credits page -- #current-credits-balance reuses .credits-badge
   as-is (same pink pill as the nav's own credits badge) for its actual
   look; this only adds the layout it needs to sit as its own block
   between the page header and the packages grid, since .credits-badge
   itself is an inline span sized for sitting inline in the nav row. */
#current-credits-balance {
  display: inline-block;
  margin-bottom: 1.5rem;
}

.packages-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1.25rem;
  margin-bottom: 2rem;
}

.package-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.5rem;
  text-align: center;
}

.package-card.selected {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}

.package-credits {
  font-size: 1.6rem;
  font-weight: 800;
}

.package-name {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin: 0.15rem 0 0.75rem;
}

.package-price {
  font-size: 1.15rem;
  font-weight: 700;
  margin-bottom: 1rem;
}

.package-card button {
  width: 100%;
}

.checkout-form {
  max-width: 420px;
}

.checkout-form .sandbox-note {
  font-size: 0.82rem;
  color: var(--text-muted);
  background: var(--bg);
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 0.65rem 0.85rem;
  margin-bottom: 0.5rem;
}

.checkout-row {
  display: flex;
  gap: 1rem;
}

.checkout-row > div {
  flex: 1;
}

/* Nav notification badges */
.nav-badge {
  display: inline-block;
  background: #e0303d;
  color: white;
  font-size: 0.7rem;
  font-weight: 700;
  min-width: 16px;
  height: 16px;
  line-height: 16px;
  text-align: center;
  border-radius: 999px;
  margin-left: 0.3rem;
  padding: 0 4px;
}

.nav-badge[hidden] {
  display: none;
}

/* Desktop-only "someone liked/visited you" toast notifications
   (public/js/notifications.js's showToast, driven by its own poll of
   GET /notifications/summary). #toast-container is created lazily by
   that script on every page it runs on -- there's nothing to add to
   any individual page's own HTML. New toasts are appended, so they
   stack downward below existing ones, most-recent last. Hidden below
   the same 640px breakpoint used elsewhere in this file for "mobile" --
   notifications.js's own isDesktopViewport() checks the same value so
   a narrow viewport skips fetching the toast data entirely, not just
   its display. */
#toast-container {
  position: fixed;
  top: 84px;
  right: 20px;
  z-index: 500;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  pointer-events: none;
}

@media (max-width: 640px) {
  #toast-container {
    display: none;
  }
}

.toast {
  pointer-events: auto;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  width: 320px;
  padding: 0.9rem 1.1rem;
  background: var(--surface);
  border: 2px solid #e53e3e;
  border-radius: var(--radius);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  transform: translateX(120%);
  transition: transform 0.35s ease;
}

.toast-visible {
  transform: translateX(0);
}

.toast-avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1.1rem;
  color: #ffffff;
  overflow: hidden;
}

.toast-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.toast-body {
  min-width: 0;
}

.toast-name {
  font-weight: 600;
  font-size: 1.05rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.toast-action {
  font-size: 0.95rem;
  color: var(--text-muted);
}

/* Likes Me / Visited Me pages */
.people-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1.25rem;
}

.people-list .profile-card .profile-meta {
  color: var(--text-muted);
  font-size: 0.8rem;
  padding: 0 1rem 0.6rem;
  margin-top: -0.5rem;
}

/* Chats page (accordion) */
.chats-list {
  max-width: 700px;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.chat-item {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.chat-item-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.9rem 1rem;
  cursor: pointer;
}

.chat-item-header .chat-avatar {
  width: 40px;
  height: 40px;
  font-size: 0.9rem;
}

.chat-item-summary {
  flex: 1;
  min-width: 0;
}

.chat-item-name {
  font-weight: 700;
  font-size: 0.95rem;
}

.chat-item-preview {
  color: var(--text-muted);
  font-size: 0.85rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chat-item-time {
  color: var(--text-muted);
  font-size: 0.78rem;
  flex-shrink: 0;
}

.chat-item-chevron {
  flex-shrink: 0;
  color: var(--text-muted);
  transition: transform 0.15s ease;
}

.chat-item.expanded .chat-item-chevron {
  transform: rotate(90deg);
}

.chat-item-body {
  border-top: 1px solid var(--border);
  padding: 1rem;
}

.chat-item-body .chat-window {
  max-height: 320px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: 0.85rem;
}

/* Promotional splash overlay -- deliberately minimal, see
   public/js/promo-offer.js: "simple for now, design comes later." */
#promo-splash-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
}

#promo-splash-card {
  position: relative;
  background: var(--surface);
  color: var(--text);
  padding: 2rem 2.5rem;
  border-radius: var(--radius);
  text-align: center;
  max-width: 320px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
}

#promo-splash-close {
  position: absolute;
  top: 0.4rem;
  right: 0.7rem;
  width: auto;
  background: none;
  border: none;
  font-size: 1.4rem;
  cursor: pointer;
  color: var(--text-muted);
  line-height: 1;
  padding: 0.2rem 0.4rem;
}

#promo-splash-message {
  font-weight: 700;
  font-size: 1.1rem;
  margin: 0 0 1rem;
  color: var(--text);
}

#promo-splash-timer {
  font-size: 2rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--primary);
}

#promo-splash-buy {
  display: block;
  margin-top: 1.25rem;
  padding: 0.65rem 1.2rem;
  border-radius: 8px;
  background: var(--primary);
  color: white;
  font-weight: 700;
  text-decoration: none;
}

/* Purchase-confirmation splash (see js/purchase-splash.js) -- same
   overlay/card treatment as the promo splash above, own ids so the two
   can never collide if both were ever somehow triggered at once. */
#purchase-splash-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
}

#purchase-splash-card {
  position: relative;
  background: var(--surface);
  color: var(--text);
  padding: 2rem 2.5rem;
  border-radius: var(--radius);
  text-align: center;
  max-width: 320px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
}

#purchase-splash-close {
  position: absolute;
  top: 0.4rem;
  right: 0.7rem;
  width: auto;
  background: none;
  border: none;
  font-size: 1.4rem;
  cursor: pointer;
  color: var(--text-muted);
  line-height: 1;
  padding: 0.2rem 0.4rem;
}

#purchase-splash-icon {
  width: 56px;
  height: 56px;
  margin: 0 auto 0.85rem;
  border-radius: 50%;
  background: var(--success);
  color: white;
  font-size: 1.6rem;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}

#purchase-splash-message {
  font-weight: 700;
  font-size: 1.15rem;
  margin: 0 0 0.4rem;
  color: var(--text);
}

#purchase-splash-detail {
  color: var(--text-muted);
  margin: 0;
}

#purchase-splash-ok {
  margin-top: 1.25rem;
}

/* Landing page background -- a tiled grid of real profile photos (see
   GET /api/profiles/preview, populated by public/js/landing.js) behind
   the existing .card, with a scrim over it so the card and hero text
   stay legible regardless of what photo ends up behind them. Both fixed
   and negative-z-indexed so they sit behind the page's normal content
   (which needs no z-index of its own) without affecting its layout. */
.landing-photo-grid {
  position: fixed;
  inset: 0;
  z-index: -2;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  grid-auto-rows: 160px;
  gap: 4px;
  overflow: hidden;
}

.landing-photo-grid img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(35%) blur(1px);
}

.landing-photo-scrim {
  position: fixed;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--bg) 50%, transparent);
}

.landing-hero {
  padding-top: 6rem;
}

.landing-hero .card {
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.12);
}

/* Three alternating content rows below the landing page's hero section
   (public/index.html, public/js/landing-content.js, admin-edited via
   Settings > Index page) -- image+text side by side, in flow (not fixed
   like the hero's own photo grid/scrim), each row its own white card
   floating over the page background. .landing-content-row-reverse flips
   row 2 to text-left/image-right by reversing the flex order, without
   changing the underlying image-then-text markup order. Stacks to one
   column below 640px, image on top, same breakpoint as .profile-detail. */
.landing-content-rows {
  max-width: 1100px;
  margin: 0 auto;
  padding: calc(100vh - 26rem) 1.5rem 4rem;
  display: flex;
  flex-direction: column;
  gap: 4rem;
}

.landing-content-row {
  display: flex;
  align-items: center;
  gap: 3rem;
  padding: 2.5rem;
  background: #ffffff;
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}

.landing-content-row-reverse {
  flex-direction: row-reverse;
}

.landing-content-row-image,
.landing-content-row-text {
  flex: 1 1 0;
  min-width: 0;
}

.landing-content-row-image img {
  width: 100%;
  border-radius: var(--radius);
  display: block;
}

.landing-content-row-heading {
  margin: 0 0 1rem;
}

.landing-content-row-body {
  margin: 0;
  color: var(--text-muted);
  line-height: 1.6;
}

@media (max-width: 640px) {
  .landing-content-row,
  .landing-content-row-reverse {
    flex-direction: column;
  }
}

/* Profile detail page -- photo on the left, all profile info (basic
   fields + every enabled profile_questions row, see public/js/profile.js)
   on the right. Stacks to one column below 640px rather than squeezing a
   photo column and a two-field-wide grid into a narrow viewport. */
.profile-detail {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 2rem;
}

@media (max-width: 640px) {
  .profile-detail {
    grid-template-columns: 1fr;
  }
}

.profile-detail-photo {
  border-radius: 12px;
}

/* My Account's own photo, while a newly uploaded one is still awaiting
   admin approval (see routes/auth.js's PATCH /me/photo, which always
   sets photo_approved = false on a new upload) -- hidden again the
   moment GET /me reports it approved (public/js/account.js's own
   renderAccountPhoto). Same yellow as .status-dot-yellow's "in between"
   meaning elsewhere in this file. */
.photo-pending-label {
  margin: 0.4rem 0 0;
  padding: 0.3rem 0.6rem;
  background: rgba(241, 196, 15, 0.15);
  border: 1px solid #f1c40f;
  border-radius: 6px;
  color: #b8890a;
  font-size: 0.78rem;
  font-weight: 700;
  text-align: center;
}

.profile-detail-name {
  margin-top: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
}

/* Send-gift button (profile detail page) / Gifts button (My Account) --
   sits inline with the name via .profile-detail-name's own flex layout
   above. */
.gift-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  width: auto;
  padding: 0.4rem 0.9rem;
  font-size: 0.85rem;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
}

.gift-btn:hover {
  background: var(--bg);
}

.gift-btn-icon {
  width: 20px;
  height: 20px;
}

/* Send-gift popup's grid of 15 catalog gifts (public/js/profile.js's
   ensureGiftModal) -- wider than the default .modal-panel (420px), same
   "needs more room" reasoning as #settings-modal's own override below.
   position: relative so #gift-modal-close below can anchor to this
   panel (top-right corner) rather than the full-screen overlay behind
   it. */
#gift-modal .modal-panel {
  position: relative;
  max-width: 820px;
  max-height: 90vh;
  overflow-y: auto;
}

/* Closes the popup without sending a gift -- same top-right "X" treatment
   as #purchase-splash-close. */
#gift-modal-close {
  position: absolute;
  top: 0.4rem;
  right: 0.7rem;
  width: auto;
  background: none;
  border: none;
  font-size: 1.4rem;
  cursor: pointer;
  color: var(--text-muted);
  line-height: 1;
  padding: 0.2rem 0.4rem;
}

.gift-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 1rem;
  margin: 1rem 0;
}

.gift-item {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 1.1rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.gift-item:hover {
  border-color: var(--primary);
}

.gift-item img {
  width: 90px;
  height: 90px;
}

/* A gift attached to a chat message (public/js/profiles-common.js's
   giftMessageHtml) -- same inline-image treatment as a photo message,
   just smaller since it's a fixed icon rather than a full photo. */
.gift-message-img {
  display: block;
  width: 72px;
  height: 72px;
  margin-top: 0.4rem;
}

/* My Account's Gifts popup -- one row per sent/received gift
   (public/js/account.js). */
.gift-list-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}

/* Unlike every other .profile-name-link usage (a plain <div> with a JS
   click handler, see public/js/profiles-common.js/people-list-common.js),
   this one row (public/js/account.js's renderGiftList, used for both the
   Sent and Received lists) is a real <a href> -- so it needs its own
   explicit override for the browser's default blue/underlined link look,
   which .profile-name-link's own shared rule never had to account for.
   Still underlines on hover via that same shared :hover rule -- only the
   resting state changes here. */
.gift-list-row .profile-name-link {
  color: inherit;
}

.gift-list-row .profile-name-link:not(:hover) {
  text-decoration: none;
}

.gift-list-row .gift-message-img {
  width: 36px;
  height: 36px;
  margin-top: 0;
  flex-shrink: 0;
}

/* My Account's Gifts popup -- Received/Sent each scroll independently
   once there are more rows than fit, rather than growing the whole
   modal (and the page behind it) without limit. */
.gift-scroll-list {
  max-height: 240px;
  overflow-y: auto;
}

/* My Account's right-hand column -- stretched by .profile-detail's grid
   (default align-items: stretch) to match the photo column's height, then
   laid out as a flex column so #about-me-field can grow to fill the
   leftover space and its bottom lines up with the photo's bottom. */
.profile-detail-info-col {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.about-me-field-col {
  display: flex;
  flex-direction: column;
  flex: 1;
  margin-top: 1.25rem;
}

.profile-detail-fields {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.1rem 1.5rem;
  margin-top: 1rem;
}

@media (max-width: 480px) {
  .profile-detail-fields {
    grid-template-columns: 1fr;
  }
}

.profile-detail-field dt {
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
}

.profile-detail-field dd {
  margin: 0.2rem 0 0;
  font-size: 0.95rem;
}

/* About me -- free text, typically longer than any other field here, so
   it spans both columns instead of being squeezed into one (see
   public/js/profile.js's renderField fullWidth argument). */
.profile-detail-field-full {
  grid-column: 1 / -1;
}

.profile-detail-field-full dd {
  white-space: pre-wrap;
}

/* My Account's two editable fields (Looking for, City -- see
   public/js/account.js) -- a select/input plus a Save button in one row,
   overriding the global input/select/button `width: 100%` (base.css)
   that would otherwise stack them full-width inside the narrow grid
   column. */
.inline-edit-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.inline-edit-row select,
.inline-edit-row input[type="text"] {
  width: auto;
  flex: 1;
}

.inline-edit-row button {
  width: auto;
  flex-shrink: 0;
}

.profile-detail-field-editable .message {
  margin-top: 0.4rem;
}

/* Profile detail page's photo column -- Favourite/Report/Block sit
   directly under the photo (see public/js/profile.js), stacked above the
   rest of the profile-detail grid's second column. */
.profile-detail-photo-col {
  display: flex;
  flex-direction: column;
}

/* My Account's photo-upload form (see public/js/account.js), revealed
   below the photo/actions row when "Edit photo" is clicked -- unchanged
   markup/behavior from before this redesign, just now toggled rather
   than always visible. */
.photo-edit-section {
  margin-top: 0.75rem;
}

.profile-detail-actions {
  padding: 0;
  margin-top: 0.85rem;
}

/* Profile detail page's About me text -- moved below the photo/action
   buttons (public/js/profile.js), out of the profile-detail-fields grid
   it used to render in. */
.profile-detail-about-me {
  margin: 0.85rem 0 0;
  color: var(--text);
  font-size: 0.9rem;
  line-height: 1.5;
}

.btn-outline.btn-danger-outline {
  color: var(--error);
  border-color: var(--error);
}

.btn-outline.btn-danger-outline:hover {
  background: #fbeaea;
}

.btn-outline.btn-danger-outline.active {
  background: var(--error);
  color: white;
}

/* Simple modal, used by the profile detail page's Report dialog (see
   public/js/profile.js) and the emoji picker's photo-lightbox-style
   overlay isn't needed here -- just a centered card over a dim scrim. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 1000;
}

/* .modal-overlay's own `display: flex` above is a class selector, so it
   beats the browser's default `[hidden] { display: none }` rule on
   specificity -- without this override, setting the element's `hidden`
   property in JS (public/js/profile.js's Report modal) has no visual
   effect and the overlay stays visible even when "closed". Same fix as
   .chat-photo-preview[hidden] / .emoji-picker-popup[hidden] elsewhere in
   this codebase. */
.modal-overlay[hidden] {
  display: none;
}

.modal-panel {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 1.5rem;
  max-width: 420px;
  width: 100%;
}

.modal-panel h2 {
  margin-top: 0;
}

/* My Account's Settings popup -- wider than the default .modal-panel
   (420px), since it stacks four full settings-sections (Notifications,
   Change email, Change password, Delete Account) rather than one short
   form like every other modal on the site. Also capped to the viewport
   height and made scrollable -- unlike every other modal here, its
   content can be taller than the screen (e.g. a narrow/short viewport),
   and .modal-overlay itself doesn't scroll. */
#settings-modal .modal-panel {
  max-width: 560px;
  max-height: 90vh;
  overflow-y: auto;
}

.modal-panel textarea {
  width: 100%;
  min-height: 100px;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.95rem;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  resize: vertical;
}

.btn-row-split {
  display: flex;
  gap: 0.75rem;
}

.btn-row-split button {
  flex: 1;
  width: auto;
}

/* Emoji picker, shared by chat.html and chats.js's per-conversation input
   row -- the full standard emoji table (EMOJI_CATEGORIES in
   profiles-common.js), grouped into category tabs above a scrollable
   grid of the active category's emoji. */
.emoji-picker-wrap {
  position: relative;
  flex-shrink: 0;
}

.emoji-picker-popup {
  position: absolute;
  bottom: 100%;
  left: 0;
  margin-bottom: 0.4rem;
  display: flex;
  flex-direction: column;
  width: 300px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
  z-index: 20;
}

/* .emoji-picker-popup's own `display: flex` above is a class selector, so
   it beats the browser's default `[hidden] { display: none }` rule on
   specificity -- without this override, toggling the element's `hidden`
   property in JS has no visual effect and the popup stays visible even
   when closed. */
.emoji-picker-popup[hidden] {
  display: none;
}

.emoji-picker-search {
  width: 100%;
  flex-shrink: 0;
  margin-bottom: 0.4rem;
  padding: 0.4rem 0.6rem;
  font-size: 0.85rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
}

.emoji-picker-tabs {
  display: flex;
  gap: 0.15rem;
  overflow-x: auto;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.4rem;
  margin-bottom: 0.4rem;
  flex-shrink: 0;
}

.emoji-picker-tab {
  width: auto;
  flex-shrink: 0;
  background: transparent;
  border: none;
  border-radius: 6px;
  font-size: 1.2rem;
  line-height: 1;
  padding: 0.3rem 0.4rem;
  cursor: pointer;
}

.emoji-picker-tab:hover {
  background: var(--bg);
}

.emoji-picker-tab.active {
  background: var(--bg);
  outline: 1px solid var(--border);
}

.emoji-picker-grid {
  display: grid;
  /* 6 columns at this width gives each item roughly a 44px-wide cell -- a
     comfortable mobile tap target. */
  grid-template-columns: repeat(6, 1fr);
  gap: 0.15rem;
  max-height: 220px;
  overflow-y: auto;
}

.emoji-picker-item {
  width: auto;
  background: transparent;
  border: none;
  border-radius: 6px;
  font-size: 1.6rem;
  line-height: 1;
  padding: 0.5rem;
  cursor: pointer;
}

.emoji-picker-item:hover {
  background: var(--bg);
}

/* My Account's Blocked section -- a plain list, simpler than the grid
   pages (no photo/chat button, just a name and an Unblock action). */
.blocked-list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border);
}

.blocked-list-item:last-child {
  border-bottom: none;
}

.blocked-list-item button {
  width: auto;
}

/* Site-wide footer (see public/js/footer.js) -- appended to every
   user-facing page. A plain in-flow block at the end of the body, not
   fixed/sticky, so it never overlaps page content. margin-top:auto (with
   body's own flex-column rule above) is what pushes it down to the
   bottom of the viewport when the page's content is shorter than one
   screen; on a taller page there's no free space left to distribute, so
   it just sits directly after the content, same as a plain in-flow
   element normally would. */
.site-footer {
  margin-top: auto;
  padding: 1.5rem;
  background: #ffffff;
  border-top: 1px solid var(--border);
}

/* Three admin-configured columns (see public/js/footer.js and the site
   admin's Pages section) -- left/right stack their content vertically
   and align to their own outer edge; center keeps the old single
   centered-row look, since every pre-existing page defaults into it. */
.site-footer-columns {
  max-width: 960px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

@media (max-width: 640px) {
  .site-footer-columns {
    grid-template-columns: 1fr;
    text-align: center;
  }
}

.site-footer-col-left {
  text-align: left;
}

.site-footer-col-center {
  text-align: center;
}

.site-footer-col-right {
  text-align: right;
}

.site-footer-logo {
  display: inline-block;
  max-height: 40px;
  max-width: 100%;
  margin-bottom: 0.6rem;
}

.site-footer-text {
  color: var(--text-muted);
  font-size: 0.88rem;
  line-height: 1.55;
  margin-bottom: 0.6rem;
}

.site-footer-text :is(h1, h2, h3) {
  color: var(--text);
  font-size: 1rem;
  margin: 0 0 0.3rem;
}

.site-footer-text a {
  color: var(--primary);
}

.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.2rem;
}

.site-footer-col-left .site-footer-links {
  flex-direction: column;
  align-items: flex-start;
}

.site-footer-col-center .site-footer-links {
  justify-content: center;
}

.site-footer-col-right .site-footer-links {
  flex-direction: column;
  align-items: flex-end;
}

@media (max-width: 640px) {
  .site-footer-col-left .site-footer-links,
  .site-footer-col-right .site-footer-links {
    align-items: center;
  }
  .site-footer-links {
    justify-content: center;
  }
}

.site-footer-links a {
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.88rem;
}

.site-footer-links a:hover {
  color: var(--primary);
  text-decoration: underline;
}

/* public/page.html's content -- body is stored as plain text (see
   db/site-schema.sql's pages table comment), so pre-wrap is what makes it
   "formatted": line breaks/paragraphs the admin typed are preserved,
   without allowing arbitrary HTML. Same convention as a fictional
   profile's about_me field (.profile-detail-field-full dd). */
.page-body {
  margin-top: 1rem;
  line-height: 1.6;
}

/* Landing page's cookie consent banner (public/js/cookie-consent.js) --
   fixed so it stays visible regardless of scroll position until
   dismissed. */
.cookie-consent-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.9rem 1.25rem;
  background: var(--surface);
  border-top: 1px solid var(--border);
}

.cookie-consent-text {
  color: var(--text);
  font-size: 0.9rem;
}

.cookie-consent-accept {
  flex-shrink: 0;
  width: auto;
  padding: 0.5rem 1.2rem;
  background: var(--primary);
  color: white;
  border: none;
  border-radius: var(--radius);
  font-weight: 600;
  cursor: pointer;
}

.cookie-consent-accept:hover {
  background: var(--primary-hover);
}


/* ==========================================================================
   Mobile-first redesign, screens under 768px. Desktop is completely
   untouched -- every rule below lives inside this one breakpoint, on top
   of (never replacing) the desktop rules above. No HTML/JS changed for
   any of this: everything here targets markup that already exists on
   every page, using :has()/[hidden]-aware selectors instead of new ids or
   classes, so every existing behavior (auth-gated nav links, JS-toggled
   [hidden] states, badge counts, form submission, etc.) keeps working
   exactly as before -- only how it's drawn changes below 768px.
   ========================================================================== */
:root {
  /* No more fixed bottom tab bar (see the hamburger menu below) --
     kept at 0 rather than removed outright so the profile-detail/chat
     fixed action bars further down, which position themselves relative
     to this variable, still dock flush to the bottom instead of leaving
     a stale gap. */
  --tab-bar-height: 0px;
}

@media (max-width: 768px) {
  /* Safety net first, before anything below touches `display` on a nav
     child: an element a page's own JS hides via the `hidden` property
     (page.html's auth-gated links, index.html's login/continue swap,
     every [hidden] badge) must stay hidden no matter what display value
     a more specific rule further down would otherwise give it. */
  .nav [hidden] {
    display: none !important;
  }

  /* index.html's own nav (identified by #language-switcher, the one
     thing only that page's nav has -- same :has() scoping approach as
     the rest of this stylesheet, no new markup/classes needed) is short
     enough that login/register/the language switcher should never need
     to wrap at all -- forcing the brand onto its own full-width line
     first guarantees the other three always land together on the line
     below it, rather than splitting unpredictably between the two. */
  .nav:has(#language-switcher) .brand {
    flex-basis: 100%;
  }

  /* Hamburger menu -- mobile only. #mobile-nav-menu becomes an absolute-
     positioned dropdown panel here (out of flex flow either way, so it
     never affects the main bar's own layout regardless of open/closed)
     -- same toggled-via-its-own-hidden-attribute pattern as index.html's
     own #language-switcher-menu (see public/js/mobile-nav.js). The brand
     stays visible here (unlike everything moved into the menu itself --
     Likes Me/Visited Me/My Favourites/Search/My Account/Log out, already
     out of the bar via that wrapper) so the logo still shows in the top
     nav on mobile while logged in. Scoped via :has(#mobile-nav-toggle) so
     index.html's own nav (no such button) gets the separate rule right
     below instead. .nav itself needs no position rule here at all --
     the base .nav { position: sticky } above already applies at every
     width, and position: sticky establishes a containing block for an
     absolutely-positioned descendant exactly like position: fixed did,
     so this dropdown still anchors to it correctly. */
  .nav:has(#mobile-nav-toggle) #mobile-nav-menu {
    display: none;
    position: absolute;
    top: 100%;
    right: 1rem;
    flex-direction: column;
    min-width: 200px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
    padding: 0.5rem 0;
    z-index: 1001;
    gap: 0;
  }

  .nav:has(#mobile-nav-toggle) #mobile-nav-menu:not([hidden]) {
    display: flex;
  }

  .nav:has(#mobile-nav-toggle) #mobile-nav-menu a {
    padding: 0.75rem 1rem;
    margin-left: 0;
  }

  /* Credits balance is the first item in the collapsed dropdown, above
     Likes Me/Visited Me/etc. It's placed last in the DOM (after Log out,
     still inside #mobile-nav-menu -- see each page's own nav markup)
     rather than first, so that on desktop -- where #mobile-nav-menu's
     own display: contents merges its children back into .nav's plain
     DOM order -- it lands in exactly the same spot it always has (right
     before Buy Credits, since Log out's own order: 1 below already sorts
     it after everything else regardless of DOM position), with no
     separate desktop-only rule needed. order: -1 here is what actually
     makes it render first within the dropdown's own flex column on
     mobile, lower than every other item's default order: 0. */
  .nav:has(#mobile-nav-toggle) #credits-badge {
    order: -1;
  }

  /* Buy Credits stays in the main collapsed bar (not the dropdown),
     reordered ahead of the credits balance that used to sit next to it
     there -- desktop is untouched: #mobile-nav-menu's own
     display: contents means desktop still lays it out in plain DOM
     order. Explicit order on the toggle too, now that its neighbor no
     longer defaults to order: 0 -- without it, it would sort back before
     it instead of staying last/flush right. */
  .nav:has(#mobile-nav-toggle) a[href="buy-credits.html"] {
    order: 1;
  }

  .nav:has(#mobile-nav-toggle) .mobile-nav-toggle {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    margin-left: auto;
    background: none;
    border: none;
    font-size: 1.4rem;
    width: auto;
    padding: 0.3rem 0.5rem;
    color: var(--text);
    cursor: pointer;
    order: 3;
  }

  /* --------------------------------------------------------------------
     7. Touch targets -- every button and nav link at least 48px tall,
     site-wide (not just the sections called out individually below).
     -------------------------------------------------------------------- */
  button,
  .btn-outline,
  .nav a {
    min-height: 48px;
  }

  .checkbox-row {
    min-height: 48px;
    align-items: center;
  }

  /* --------------------------------------------------------------------
     9. Larger, more readable typography. 16px (1rem) on every text input
     specifically also stops iOS Safari's own auto-zoom-on-focus, which
     otherwise kicks in below that size.
     -------------------------------------------------------------------- */
  body {
    font-size: 16px;
  }

  h1 {
    font-size: 1.7rem;
  }

  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="date"],
  input[type="number"],
  select,
  textarea {
    font-size: 1rem;
  }

  .profile-name-age {
    font-size: 1.1rem;
  }

  .profile-detail-field dd {
    font-size: 1rem;
  }

  .bubble {
    font-size: 1rem;
  }

  /* --------------------------------------------------------------------
     2. Home page (and every other page sharing the same .profile-card,
     e.g. Search/My Favourites/Likes Me/Visited Me) -- two cards per row,
     each a smaller version of the same card. name/age/status previously
     sat pulled up over the photo's own bottom edge as a scrim overlay --
     tuned for the old full-width single card's much taller (60vh) photo.
     At two per row the photo uses desktop's own shorter 3/4 aspect
     ratio, and that same overlay pull-up put the text well inside it,
     covering it instead of overlaying it legibly. No override here now,
     so .profile-info (and the name/age/status inside it) falls back to
     its plain desktop styling -- in normal flow below the photo, same
     as desktop, never over it.
     -------------------------------------------------------------------- */
  .profiles-grid,
  .people-list {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
  }

  .profile-card {
    border-radius: 16px;
  }

  .profile-card > .profile-actions {
    padding: 0.85rem 1rem 1rem;
    justify-content: center;
  }

  .profile-card > .profile-actions button {
    flex: none;
    font-size: 0.8rem;
    min-height: 38px;
    padding: 0.44rem 0.4rem;
  }

  /* Likes Me/Visited Me's own "Liked/Visited you on <date>" line --
     a normal-flow row below .profile-info, not part of it. */
  .people-list .profile-card .profile-meta {
    padding: 0 1rem;
  }

  /* --------------------------------------------------------------------
     3. Profile detail page -- full-width photo taking half the screen,
     the rest of the info scrolling underneath, action buttons docked to
     the bottom instead of sitting in the photo column. Scoped to
     #profile-detail-card so My Account's own reuse of these same classes
     (item 5 below) is unaffected.
     -------------------------------------------------------------------- */
  .profile-detail {
    grid-template-columns: 1fr;
  }

  #profile-detail-card .profile-photo.profile-detail-photo {
    aspect-ratio: auto;
    height: 50vh;
  }

  #profile-detail-card {
    padding-bottom: calc(var(--tab-bar-height) + 7rem);
  }

  #profile-detail-card .profile-detail-actions {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--tab-bar-height) + 3.5rem);
    z-index: 900;
    margin: 0;
    padding: 0.6rem 1rem;
    background: var(--surface);
    border-top: 1px solid var(--border);
  }

  #profile-detail-card .btn-row:has(#chat-btn) {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--tab-bar-height);
    z-index: 900;
    margin: 0;
    padding: 0.6rem 1rem;
    background: var(--surface);
    border-top: 1px solid var(--border);
  }

  #profile-detail-card .btn-row:has(#chat-btn) button {
    min-height: 48px;
  }

  /* --------------------------------------------------------------------
     4. Chat page -- full screen, input bar docked above the tab bar.
     position: fixed on the input row (rather than sticky) is what lets
     a mobile browser's own virtual keyboard push it up correctly, the
     same way any other fixed-to-viewport element behaves when the
     keyboard opens.
     -------------------------------------------------------------------- */
  .chat-shell {
    max-width: none;
    margin: 0;
    padding: 0 1rem;
    height: 100vh;
    height: 100dvh;
  }

  .chat-header {
    padding: 1rem 0;
  }

  /* Scoped to .chat-shell (unique to chat.html) rather than the bare
     .chat-window/.chat-input-row classes -- chats.html's own expanded
     conversation rows reuse those same two classes for a very different,
     list-item-embedded layout, where more than one could be open/visible
     at once; fixing every instance of them to the viewport bottom would
     make multiple simultaneously-expanded chats.html rows fight over the
     same fixed position. Bottom padding sized for the input row's own
     two-row height below (was 5rem for its old one-row height), so the
     last message never sits behind the now-taller fixed bar. */
  .chat-shell .chat-window {
    padding-bottom: 8rem;
  }

  .chat-shell .chat-input-row {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--tab-bar-height);
    z-index: 900;
    margin: 0;
    padding: 0.6rem 1rem;
    background: var(--surface);
    border-top: 1px solid var(--border);
  }

  .chat-shell .chat-input-row input[type="text"] {
    min-height: 48px;
  }

  /* Text input on its own full-width row below the emoji/attach buttons
     (left) and send button (right), rather than squeezed between them --
     bare .chat-input-row (not .chat-shell-scoped) so this also applies
     to chats.html's own expanded-conversation rows, not just the
     standalone chat page. order: 1 (default is 0 for the other three
     children) is what puts the text input last in flex layout order, so
     the wrap happens after attach/emoji/send rather than after
     attach/emoji only -- without it, the send button would wrap onto a
     third row of its own instead of staying on the first. */
  .chat-input-row {
    flex-wrap: wrap;
  }

  .chat-input-row input[type="text"] {
    order: 1;
    flex: 1 1 100%;
  }

  .chat-input-row button[type="submit"] {
    margin-left: auto;
  }

  /* --------------------------------------------------------------------
     5. My Account -- single column (already covered by .profile-detail
     above, which this page's markup reuses too), large photo up top.
     -------------------------------------------------------------------- */
  #account-photo-preview.profile-detail-photo {
    aspect-ratio: auto;
    height: 45vh;
  }

  /* --------------------------------------------------------------------
     6. Landing page -- the join/registration card takes up most of the
     screen; the photo grid is still the same fixed full-viewport
     background (see .landing-photo-grid above), but forced to a fixed
     4-column layout here instead of desktop's auto-fill -- at a mobile
     viewport width, auto-fill's 160px minimum often only fits a single
     column, stretching each cell (and the photo filling it, via
     object-fit: cover) far wider than tall and making it look zoomed
     in rather than a grid of cards. Smaller (not just shorter) cells are
     what keeps each photo itself square instead of a distorted
     rectangle, same as desktop's own square 160px cells, just zoomed out
     -- grid-auto-rows is left to size itself from the image's own
     aspect-ratio: 1 / 1 below rather than a fixed px row height, so
     every cell (whatever its actual column width ends up being) is
     always exactly as tall as it is wide. Four narrow columns are also
     what gets at least six rows visible on a typical mobile viewport
     height.
     -------------------------------------------------------------------- */
  .landing-photo-grid {
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: auto;
  }

  .landing-photo-grid img {
    height: auto;
    aspect-ratio: 1 / 1;
  }

  .landing-hero {
    padding-top: 1.5rem;
  }

  .landing-hero .container {
    max-width: none;
    padding: 0 1rem;
  }

  .landing-hero .card {
    padding: 1.5rem 1.25rem;
    border-radius: 16px;
  }

  /* --------------------------------------------------------------------
     10. Buy Credits page -- two package cards per row instead of
     auto-fill's own 220px minimum, which only ever fits one at a mobile
     width. Padding/gap trimmed down a bit too, so two cards actually fit
     side by side with room to breathe rather than touching edge to edge.
     -------------------------------------------------------------------- */
  .packages-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
  }

  .package-card {
    padding: 1rem;
  }

  /* --------------------------------------------------------------------
     11. Promotional splash (first purchase offer / date offer, see
     js/promo-offer.js) -- the desktop card's fixed padding, with no
     overflow handling on the card itself, meant a longer message (a
     longer translation, or an admin-configured one) needing more height
     than a small phone's viewport has left nothing to scroll -- clipping
     the close button and/or the buy button clean off-screen instead of
     just wrapping onto another line. A little side margin on the overlay
     and less padding on the card make more room to begin with;
     overflow-y: auto + max-height is the actual fix -- a card still
     taller than the viewport scrolls instead of getting cut off.
     -------------------------------------------------------------------- */
  #promo-splash-overlay {
    padding: 1rem;
  }

  #promo-splash-card {
    max-height: 100%;
    overflow-y: auto;
    padding: 1.5rem 1.25rem;
  }
}

/* --------------------------------------------------------------------
   Tablet/iPad-width landing page background photos (769px-1366px, between
   the mobile grid fix above and desktop's own auto-fill columns --
   1366px rather than 1024px so this covers every common iPad width,
   portrait or landscape, including a 12.9" iPad Pro's own 1366px
   landscape viewport, not just the up-to-1024px portrait/smaller-model
   range). Same problem as mobile's own fix above, same fix:
   .landing-photo-grid's base auto-fill columns (minmax(160px, 1fr)) only
   fit a handful of columns at these widths, so the leftover space
   stretches each one noticeably wider than the fixed 160px row height,
   and object-fit: cover (left as-is here, same as mobile) then crops
   hard into that mismatch -- photos read as zoomed in rather than
   framed. A fixed 4-column layout with grid-auto-rows sized from the
   image's own aspect-ratio: 1 / 1, exactly like the mobile block above,
   keeps every cell exactly as tall as it is wide regardless of the
   actual column width, so cover has nothing to crop into. Below 769px
   the mobile block already handles this; above 1366px enough auto-fill
   columns fit that the stretch is negligible.
   -------------------------------------------------------------------- */
@media (min-width: 769px) and (max-width: 1366px) {
  .landing-photo-grid {
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: auto;
  }

  .landing-photo-grid img {
    height: auto;
    aspect-ratio: 1 / 1;
  }
}
