@import url('https://fonts.googleapis.com/css2?family=Itim&family=Mali:wght@400;600&family=Prompt:wght@400;600&display=swap');


/* ==========================================================================
   Rosy Blossom — Soft Pastel Pink & Warm Cream theme
   Tailwind (CDN) handles the utility layer; this file holds the few
   hand-written rules: base font size, pastel scrollbars and animations.
   ========================================================================== */

/* ==========================================================================
   📖 Thai reading typography
   --------------------------------------------------------------------------
   Sarabun leads every body / long-form reading surface (the most legible face
   for dense Thai prose), with Prompt as the immediate fallback. It is pulled
   in here as well as in includes/header.php's <link> so the stylesheet keeps
   working even when it is loaded outside the PHP layout (and so the stack is
   documented where the typography is actually declared).
   ========================================================================== */
@import url('https://fonts.googleapis.com/css2?family=Itim&family=Mali:wght@400;500;600&family=Prompt:wght@300;400;500;600&family=Sarabun:ital,wght@0,300;0,400;0,500;0,600;1,400&display=swap');

/* ==========================================================================
   🎨 Theme color presets
   --------------------------------------------------------------------------
   Every palette below defines the same set of custom properties, so switching
   the attribute on <html> (data-theme="rosy-pink" | "lavender" | "peach" |
   "matcha" | "dark-mocha") re-skins the base surfaces of the whole site. The
   value is painted by includes/header.php from the visitor's synchronised
   cookie (localStorage "site_palette") or their saved profile, with the admin's
   get_setting('default_theme') as the fallback.
   ========================================================================== */
:root,
html[data-theme="rosy-pink"] {
  --rosy-bg:            #FFF9F5;
  --rosy-ink:           #4A3F3F;
  --rosy-accent:        #FF9AA2;
  --rosy-accent-soft:   #FFB6C1;
  --rosy-accent-deep:   #D95E6F;
  --rosy-selection-bg:  #FFD3DD;
  --rosy-selection-ink: #7A3B45;
  --rosy-glow-1:        #FFE8EE;
  --rosy-glow-2:        #FFD3DD;
  --rosy-glow-3:        #FFF5F7;
  --rosy-scroll-track:  #FFF5F7;
  --rosy-scroll-thumb:  #FFD3DD;

  /* Semantic text tokens (light defaults). The dark-mode visibility fix near
     the bottom of this file redefines them with high-contrast light colours. */
  --color-text:         #4A3F3F;
  --color-text-muted:   #8A7B7B;
  --color-heading:      #3A2F2F;
}

html[data-theme="lavender"] {
  --rosy-bg:            #FBF8FF;
  --rosy-ink:           #423B52;
  --rosy-accent:        #B79CE8;
  --rosy-accent-soft:   #D4C2F2;
  --rosy-accent-deep:   #7E63B6;
  --rosy-selection-bg:  #E4D8FA;
  --rosy-selection-ink: #5B467F;
  --rosy-glow-1:        #EDE4FB;
  --rosy-glow-2:        #DFD1F7;
  --rosy-glow-3:        #F6F0FE;
  --rosy-scroll-track:  #F4EEFC;
  --rosy-scroll-thumb:  #D4C2F2;
}

html[data-theme="peach"] {
  --rosy-bg:            #FFF8F1;
  --rosy-ink:           #4F3F35;
  --rosy-accent:        #F7A76C;
  --rosy-accent-soft:   #FCC79A;
  --rosy-accent-deep:   #C9763A;
  --rosy-selection-bg:  #FBD9BC;
  --rosy-selection-ink: #8A4E23;
  --rosy-glow-1:        #FDE7D4;
  --rosy-glow-2:        #FBD6B8;
  --rosy-glow-3:        #FDF1E7;
  --rosy-scroll-track:  #FCEFE3;
  --rosy-scroll-thumb:  #FCC79A;
}

html[data-theme="matcha"] {
  --rosy-bg:            #F6FBF3;
  --rosy-ink:           #3B463A;
  --rosy-accent:        #8FBF7F;
  --rosy-accent-soft:   #B4D6A7;
  --rosy-accent-deep:   #5E8A4E;
  --rosy-selection-bg:  #D5E9CC;
  --rosy-selection-ink: #47663C;
  --rosy-glow-1:        #E4F2DC;
  --rosy-glow-2:        #D0E7C5;
  --rosy-glow-3:        #EFF7EA;
  --rosy-scroll-track:  #EAF4E4;
  --rosy-scroll-thumb:  #B4D6A7;
}

html[data-theme="dark-mocha"] {
  --rosy-bg:            #2A211D;
  --rosy-ink:           #F3E9E1;
  --rosy-accent:        #C08A5E;
  --rosy-accent-soft:   #D9B08C;
  --rosy-accent-deep:   #8A5A34;
  --rosy-selection-bg:  #4A382C;
  --rosy-selection-ink: #F7E9DA;
  --rosy-glow-1:        #3A2C24;
  --rosy-glow-2:        #4A382C;
  --rosy-glow-3:        #332720;
  --rosy-scroll-track:  #2A211D;
  --rosy-scroll-thumb:  #4A382C;
  color-scheme: dark;
}

/* 18px base for comfortable readability */
html {
  font-size: 18px;
  -webkit-text-size-adjust: 100%;
}

body {
  /* 📖 Sarabun first: the primary body / long-form reading face for legible
     Thai text, with Prompt (+ Mitr) as the fallbacks. Tailwind's `font-sans`
     utility on <body> (see the fontFamily config in includes/header.php)
     carries the very same stack. */
  font-family: 'Sarabun', 'Prompt', 'Mitr', system-ui, -apple-system, 'Segoe UI', sans-serif;
  color: var(--rosy-ink, #4A3F3F);
  background-color: var(--rosy-bg, #FFF9F5);
  background-image:
    radial-gradient(circle at 8% 4%, var(--rosy-glow-1, #FFE8EE) 0%, transparent 45%),
    radial-gradient(circle at 92% 12%, var(--rosy-glow-2, #FFD3DD) 0%, transparent 42%),
    radial-gradient(circle at 50% 100%, var(--rosy-glow-3, #FFF5F7) 0%, transparent 55%);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ==========================================================================
   🧱 Baseline global layout — pure CSS that needs NO Tailwind build
   --------------------------------------------------------------------------
   The structural rules the whole site stands on: the page box (body) and the
   single centred content column (`<main id="main-content" class="main-layout …">`,
   see includes/header.php). Every value is the exact equivalent of the Tailwind
   utility it mirrors (`max-w-7xl`, `mx-auto`, `px-4`, `sm:px-6`, `pt-6`,
   `lg:pt-8`, `pb-16`), so the page keeps a centred, padded, scrollable layout on
   a cold first paint — before the Tailwind CDN stylesheet has been evaluated
   (fresh cache, slow/blocked CDN) — and is pixel-identical once it has.
   ========================================================================== */

/* The browser's default 8px body margin is normally removed by Tailwind's
   preflight; leaving it in would offset every cold first paint and can force a
   horizontal scrollbar, which in turn makes the fixed header look crooked. */
body {
  margin: 0;
  min-height: 100vh;               /* min-h-screen */
}

.main-layout {
  display: block;
  width: 100%;                     /* w-full       */
  max-width: 80rem;                /* max-w-7xl    */
  margin-left: auto;               /* mx-auto      */
  margin-right: auto;
  padding: 1.5rem 1rem 4rem;       /* pt-6 px-4 pb-16 */
  box-sizing: border-box;
}

@media (min-width: 640px) {
  .main-layout { padding-left: 1.5rem; padding-right: 1.5rem; }   /* sm:px-6 */
}

@media (min-width: 1024px) {
  .main-layout { padding-top: 2rem; }                            /* lg:pt-8 */
}

::selection {
  background: var(--rosy-selection-bg, #FFD3DD);
  color: var(--rosy-selection-ink, #7A3B45);
}

:focus-visible {
  outline: 2px solid var(--rosy-accent, #FF9AA2);
  outline-offset: 2px;
  border-radius: .75rem;
}

/* --- pastel scrollbar ---------------------------------------------------- */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--rosy-scroll-track, #FFF5F7); }
::-webkit-scrollbar-thumb {
  background: var(--rosy-scroll-thumb, #FFD3DD);
  border-radius: 999px;
  border: 2px solid var(--rosy-scroll-track, #FFF5F7);
}
::-webkit-scrollbar-thumb:hover { background: var(--rosy-accent-soft, #FFB6C1); }

/* --- toast entrance ------------------------------------------------------ */
@keyframes rosy-rise {
  from { opacity: 0; transform: translateY(-10px) scale(.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
[data-toast] { animation: rosy-rise .35s ease-out both; }

/* --- modal --------------------------------------------------------------- */
[data-modal] { position: fixed; inset: 0; z-index: 60; display: flex; align-items: flex-start; justify-content: center; overflow-y: auto; padding: 5vh 1rem 2rem; }
[data-modal].hidden { display: none !important; }
[data-modal] > [data-modal-backdrop] { position: fixed; inset: 0; background: rgba(74, 63, 63, .38); backdrop-filter: blur(3px); }
body.rosy-modal-open,
body[data-modal-open="1"] { overflow: hidden; }

@keyframes rosy-pop {
  from { opacity: 0; transform: translateY(14px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
[data-modal] > [data-modal-panel] { position: relative; animation: rosy-pop .28s ease-out both; }

/* --------------------------------------------------------------------------
   ✏️ Edit-profile modal — mobile-safe scroll shell (VERTICAL only)
   --------------------------------------------------------------------------
   The panel is a bounded flex column that can NEVER grow taller than the
   viewport: a fixed header, a momentum-scrolling body (every field, including
   the “Allowed viewers” @mention box, lives here) and a pinned action bar
   holding “Save changes”. Previously the whole [data-modal] wrapper scrolled
   while the panel had no height cap, so on a phone the panel could outgrow the
   screen — the bottom action bar slid off-screen and the modal felt
   frozen/unscrollable right after a member was picked from the @mention list.
   Capping the panel and scrolling only the inner body keeps “Save changes”
   reachable on every screen size and keyboard state.

   HORIZONTAL LOCK — a box that sets `overflow-y` has its `overflow-x` computed
   to `auto` too, so a single stray pixel of child overflow became a sideways
   swipe (the mobile “wiggle”). Every layer of the modal is now pinned to the
   viewport width, and every field is a border-box control that may shrink below
   its intrinsic size (`min-width: 0`) — file inputs and textareas otherwise
   keep their default content width and push the card past 100vw. Only VERTICAL
   scrolling survives.
   ========================================================================== */
.edit-profile-modal {
  width: 100%;
  max-width: 100vw;
  box-sizing: border-box;
  overflow-x: hidden;   /* lock the fixed wrapper against sideways swipes */
}

.edit-profile-modal > [data-modal-panel] {
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  /* min(36rem, 100%) keeps the familiar card cap on desktop while never
     letting the panel grow wider than the (already padded) mobile viewport. */
  width: 100%;
  max-width: min(36rem, 100%);
  box-sizing: border-box;
  overflow-x: hidden;
}
@supports (height: 100dvh) {
  /* dvh tracks the *visual* viewport, so the panel shrinks with the mobile
     browser chrome / on-screen keyboard instead of burying the action bar. */
  .edit-profile-modal > [data-modal-panel] { max-height: 85dvh; }
}

.edit-profile-modal .edit-profile-body {
  flex: 1 1 0%;
  min-height: 0;
  min-width: 0;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  overflow-y: auto;
  overflow-x: hidden;                  /* no sideways swipe inside the body */
  -webkit-overflow-scrolling: touch;   /* momentum scrolling on iOS */
  overscroll-behavior: contain;        /* never chain-scroll to the page */
}

/* Every direct field/card in the body is a shrinkable border-box so a wide
   file input or textarea can never widen the grid track past the card. */
.edit-profile-modal .edit-profile-body > * {
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

.edit-profile-modal .edit-profile-body label {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

.edit-profile-modal .edit-profile-body input:not([type="checkbox"]):not([type="radio"]),
.edit-profile-modal .edit-profile-body textarea,
.edit-profile-modal .edit-profile-body select {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  overflow-x: hidden;   /* long values clip instead of widening the card */
}

/* File inputs are the usual offenders: clamp the control and clip the inner
   file button rather than let it push the card past the viewport. */
.edit-profile-modal .edit-profile-body input[type="file"] {
  overflow: hidden;
}

.edit-profile-modal .edit-profile-actions {
  flex: 0 0 auto;
  position: sticky;
  bottom: 0;
  z-index: 5;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  background: rgba(255, 255, 255, .96);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  padding-bottom: calc(1rem + env(safe-area-inset-bottom));
}

html.dark .edit-profile-modal .edit-profile-actions {
  background: rgba(36, 31, 51, .96);
  border-color: #43354F;
}

/* --------------------------------------------------------------------------
   ✏️ Edit-profile media rows — avatar / cover “delete” buttons
   --------------------------------------------------------------------------
   The current picture, its hint and the red delete button share a wrapping
   flex row. The button is `flex: 0 0 auto` with `white-space: nowrap` and an
   explicit horizontal writing mode, so its Thai label (🗑️ ลบรูปโปรไฟล์ /
   🗑️ ลบรูปคัฟเวอร์) always sits on ONE horizontal line and can never be
   squeezed into a vertical column of glyphs when the card gets narrow. */
.edit-profile-media-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}
.edit-profile-media-delete {
  flex: 0 0 auto;
  white-space: nowrap;
  writing-mode: horizontal-tb;
  text-orientation: mixed;
  word-break: keep-all;
  overflow-wrap: normal;
}

/* --- media --------------------------------------------------------------- */
.rosy-media {
  max-height: 36rem;
  object-fit: cover;
  width: 100%;
}

/* --- profile avatar frame -------------------------------------------------
   A dedicated square frame so an uploaded portrait can never be flex-shrunk
   into a sliver: the box keeps a 1:1 aspect ratio and the photo is
   centre-cropped inside it.
   ------------------------------------------------------------------------- */
.rosy-avatar-frame {
  position: relative;
  display: block;
  flex: 0 0 auto;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  isolation: isolate;
}

.rosy-avatar-frame > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* --- post photo grids -----------------------------------------------------
   1 photo stretches to the full width, 2–3 photos tile into squares. Kept in
   plain CSS as well so the grid still looks right before the CDN CSS loads.
   ------------------------------------------------------------------------- */
.rosy-photo-grid {
  display: grid;
  gap: .25rem;
  width: 100%;
  background-color: #FFF5F7;
}

.rosy-photo-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.rosy-photo-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.rosy-photo-grid > img,
.rosy-photo-grid > a,
.rosy-photo-grid > button {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
}

.rosy-photo-grid img {
  display: block;
  width: 100%;
  object-fit: cover;
}

.rosy-photo-grid--2 img,
.rosy-photo-grid--3 img { aspect-ratio: 1 / 1; }

.rosy-photo-grid > button { cursor: zoom-in; }

html.dark .rosy-photo-grid { background-color: #2B2338; }

/* --- misc ---------------------------------------------------------------- */
.rosy-line-3 {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.rosy-hero-blob {
  position: absolute;
  border-radius: 999px;
  filter: blur(52px);
  opacity: .55;
  pointer-events: none;
}

/* ==========================================================================
   Readability — 20px post bodies, comment lists, comment text & inputs
   (Tailwind's text-[20px] utility also sets these; the rules below keep the
    size correct even when the CDN stylesheet has not loaded yet)
   ========================================================================== */
body .rosy-post-body,
body [data-post-body] {
  font-size: 20px;
  line-height: 1.75;
}

body .rosy-comment-list,
body [data-comment-list],
body .rosy-comment-body,
body [data-comment-body],
body .rosy-comment-input {
  font-size: 20px;
  line-height: 1.7;
}

/* A reusable helper for long-form prose passages (novels, quizzes, CMS pages).
   Sarabun + a roomy line-height keep dense Thai prose comfortable to read. */
.reading-content {
  font-family: 'Sarabun', 'Prompt', 'Mitr', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 20px;
  line-height: 1.75;
}

/* ==========================================================================
   🔘 Post action bar — Like / Comment / Share / Open stay STRICTLY horizontal
   --------------------------------------------------------------------------
   Every control in a feed card (includes/post_card.php → index.php /
   profile.php), on the single-post page (post.php) and in the profile archive
   keeps its label on ONE line: `inline-flex` + `flex-direction: row` +
   `white-space: nowrap` — all `!important`, so neither a Tailwind utility nor
   an inherited `word-break` / `overflow-wrap` from the prose containment layer
   (includes/header.php) can squeeze “0 Likes” / “0 Comments” onto a second
   line and stretch the button downwards.
   ========================================================================== */
.post-actions,
.rosy-post-actions,
.like-btn,
.comment-btn,
.share-btn,
.rosy-action-btn {
  display: inline-flex !important;
  flex-direction: row !important;
  align-items: center !important;
  white-space: nowrap !important;
  word-break: normal !important;
  overflow-wrap: normal !important;
}

.like-btn *,
.comment-btn *,
.share-btn * {
  white-space: nowrap !important;
  word-break: normal !important;
}

/* The bar itself still spans the whole card width — the top hairline runs edge
   to edge and “Open →” (ml-auto) stays pinned to the right — while the buttons
   above never break their own label. `flex-wrap` lets a whole button drop to a
   second line on a very narrow phone instead of being squeezed flat, so the
   row stays readable at every viewport. */
.post-actions,
.rosy-post-actions {
  width: 100% !important;
  box-sizing: border-box !important;
  flex-wrap: wrap !important;
  justify-content: flex-start !important;
}

/* Anything inside an action control (icon span, count, badge) is nowrap too,
   and never shrinks — a squeezed control wraps onto the next line as a whole
   (see `flex-wrap` above) instead of crushing its own label. */
.post-actions > *,
.rosy-post-actions > *,
.rosy-action-btn > * {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0 !important;
  white-space: nowrap !important;
  word-break: normal !important;
  overflow-wrap: normal !important;
}

/* ==========================================================================
   🏷️ Feed post badges — room / pin / status chips (includes/post_card.php)
   --------------------------------------------------------------------------
   On a narrow phone the metadata row (a wrapping flex row) can squeeze a chip
   so hard that its Thai label wraps one glyph per line (e.g. “👻 เรื่องลี้ลับ
   และโลกคู่ขนาน” or “📌 ปักหมุดในห้อง” collapsing into a vertical column of
   letters). Every chip is an inline-flex box that never shrinks and never
   breaks its own text, so a chip that no longer fits drops to the NEXT line as
   a WHOLE unit (the row already sets `flex-wrap`) instead of being crushed. */
.rosy-post-badge {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  white-space: nowrap;
  word-break: normal;
  overflow-wrap: normal;
}

/* ==========================================================================
   🕒 Feed post timestamp — "🕒 2h ago" must never wrap vertically
   --------------------------------------------------------------------------
   The metadata row (a wrapping flex row, see .rosy-post-badge above) can
   shrink the time badge on a narrow phone until the clock glyph, the number
   and the unit each land on their own line — "🕒" / "2h" / "ago" stacked into
   a vertical column. The badge is an inline-flex box that never shrinks and
   never breaks, so the whole "🕒 2h ago" stays on ONE horizontal line at every
   viewport; the 4px gap keeps the icon and the label apart without relying on
   a whitespace character. */
.rosy-post-time {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
  flex-shrink: 0;
  word-break: normal;
  overflow-wrap: normal;
}


/* ==========================================================================
   Theme icons (Sun / Moon) + live-post entrance
   ========================================================================== */
[data-theme-icon="dark"] { display: none; }
html.dark [data-theme-icon="light"] { display: none; }
html.dark [data-theme-icon="dark"] { display: grid; }

@keyframes rosy-fresh {
  from { opacity: 0; transform: translateY(-14px) scale(.985); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.rosy-fresh-post { animation: rosy-fresh .5s ease-out both; }

/* ==========================================================================
   "Pastel Dark Mode" — soft dark plum (#1A1625) with pastel pink highlights.
   Toggled by the Sun/Moon button; the choice is stored in localStorage by the
   boot script inside includes/header.php, which adds the `dark` class (and
   data-mode="dark") so Tailwind's own dark: variants work too. The palette is
   kept separately on <html data-theme="…">.
   ========================================================================== */
html.dark { color-scheme: dark; background-color: #1A1625; }

/* --- dark-mode text variables --------------------------------------------
   Redefined with high-contrast light colours so any element that reads these
   tokens flips cleanly when the `dark` class is present. `!important` guards
   against a stale light value winning the cascade. ------------------------ */
.dark, html.dark {
  --color-text: #f3f4f6 !important;
  --color-text-muted: #9ca3af !important;
  --color-heading: #ffffff !important;
}

html.dark body {
  color: #EFE7F5;
  background-color: #1A1625;
  background-image:
    radial-gradient(circle at 8% 4%, #2B2239 0%, rgba(43, 34, 57, 0) 45%),
    radial-gradient(circle at 92% 12%, #3A2A3D 0%, rgba(58, 42, 61, 0) 42%),
    radial-gradient(circle at 50% 100%, #241C31 0%, rgba(36, 28, 49, 0) 55%);
}

html[data-theme] body { transition: background-color .3s ease, color .3s ease; }

html.dark ::selection { background: #FFA6B3; color: #2A1B24; }
html.dark ::-webkit-scrollbar-track { background: #1A1625; }
html.dark ::-webkit-scrollbar-thumb { background: #4A3A57; border-color: #1A1625; }
html.dark ::-webkit-scrollbar-thumb:hover { background: #FFA6B3; }
html.dark [data-modal] > [data-modal-backdrop] { background: rgba(11, 7, 17, .74); }
html.dark .rosy-hero-blob { opacity: .34; }

/* --- surfaces ------------------------------------------------------------ */
html.dark .bg-cream { background-color: #1A1625; }
html.dark .bg-white { background-color: #241F33; }
html.dark .bg-white\/85 { background-color: rgb(36 31 51 / .85); }
html.dark .bg-white\/80 { background-color: rgb(36 31 51 / .8); }
html.dark .bg-white\/70 { background-color: rgb(36 31 51 / .7); }
html.dark .bg-white\/60 { background-color: rgb(36 31 51 / .6); }

html.dark .bg-blush-50 { background-color: #2B2338; }
html.dark .bg-blush-50\/80 { background-color: rgb(43 35 56 / .8); }
html.dark .bg-blush-50\/70 { background-color: rgb(43 35 56 / .7); }
html.dark .bg-blush-50\/60 { background-color: rgb(43 35 56 / .6); }
html.dark .bg-blush-50\/50 { background-color: rgb(43 35 56 / .5); }
html.dark .bg-blush-50\/40 { background-color: rgb(43 35 56 / .4); }
html.dark .bg-blush-100 { background-color: #352B45; }
html.dark .bg-blush-200 { background-color: #43354F; }

/* flash toasts & warnings keep their meaning on a dark background */
html.dark .bg-emerald-50 { background-color: #14291F; }
html.dark .bg-rose-50 { background-color: #33202A; }
html.dark .bg-rose-100 { background-color: #402732; }
html.dark .bg-rose-200 { background-color: #4E2F3C; }
html.dark .bg-red-50 { background-color: #33202A; }
html.dark .bg-red-100 { background-color: #402732; }
html.dark .bg-amber-50 { background-color: #322718; }
html.dark .bg-amber-100 { background-color: #40321D; }

/* --- gradient stops ------------------------------------------------------ */
html.dark .from-white { --tw-gradient-from: #241F33 var(--tw-gradient-from-position); --tw-gradient-to: rgb(36 31 51 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .to-white { --tw-gradient-to: #241F33 var(--tw-gradient-to-position); }
html.dark .via-blush-50 { --tw-gradient-stops: var(--tw-gradient-from), #2B2338 var(--tw-gradient-via-position), var(--tw-gradient-to); }
html.dark .from-blush-50 { --tw-gradient-from: #2B2338 var(--tw-gradient-from-position); --tw-gradient-to: rgb(43 35 56 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .to-blush-50 { --tw-gradient-to: #201A2C var(--tw-gradient-to-position); }
html.dark .from-blush-100 { --tw-gradient-from: #352B45 var(--tw-gradient-from-position); --tw-gradient-to: rgb(53 43 69 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .to-blush-100 { --tw-gradient-to: #352B45 var(--tw-gradient-to-position); }
html.dark .from-blush-200 { --tw-gradient-from: #43354F var(--tw-gradient-from-position); --tw-gradient-to: rgb(67 53 79 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .to-blush-200 { --tw-gradient-to: #43354F var(--tw-gradient-to-position); }
html.dark .to-blush-300 { --tw-gradient-to: #6B4C63 var(--tw-gradient-to-position); }
html.dark .from-violet-100 { --tw-gradient-from: #322A4C var(--tw-gradient-from-position); --tw-gradient-to: rgb(50 42 76 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .to-violet-200 { --tw-gradient-to: #453A66 var(--tw-gradient-to-position); }
html.dark .from-rose-100 { --tw-gradient-from: #3B2530 var(--tw-gradient-from-position); --tw-gradient-to: rgb(59 37 48 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .from-amber-100 { --tw-gradient-from: #3D301C var(--tw-gradient-from-position); --tw-gradient-to: rgb(61 48 28 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .from-emerald-100 { --tw-gradient-from: #1C3329 var(--tw-gradient-from-position); --tw-gradient-to: rgb(28 51 41 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .from-sky-100 { --tw-gradient-from: #1F3040 var(--tw-gradient-from-position); --tw-gradient-to: rgb(31 48 64 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
html.dark .from-fuchsia-100 { --tw-gradient-from: #3A2540 var(--tw-gradient-from-position); --tw-gradient-to: rgb(58 37 64 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }

/* --- text ---------------------------------------------------------------- */
html.dark .text-ink { color: #EFE7F5; }
html.dark .text-ink\/90 { color: rgb(239 231 245 / .9); }
html.dark .text-ink\/85 { color: rgb(239 231 245 / .85); }
html.dark .text-ink\/80 { color: rgb(239 231 245 / .8); }
html.dark .text-ink\/75 { color: rgb(239 231 245 / .75); }
html.dark .text-ink\/70 { color: rgb(239 231 245 / .7); }
html.dark .text-ink\/60 { color: rgb(239 231 245 / .62); }
html.dark .text-ink\/55 { color: rgb(239 231 245 / .58); }
html.dark .text-ink\/50 { color: rgb(239 231 245 / .52); }
html.dark .text-ink\/45 { color: rgb(239 231 245 / .46); }
html.dark .text-ink\/40 { color: rgb(239 231 245 / .4); }

html.dark .text-blush-700 { color: #FFC6D3; }
html.dark .text-blush-600 { color: #FFB0BE; }
html.dark .text-blush-500 { color: #FF9AA2; }
html.dark .text-blush-400 { color: #E08F9E; }
html.dark .text-blush-300 { color: #8E7280; }
html.dark .placeholder-blush-400::placeholder { color: #9C8290; }

html.dark .text-emerald-800 { color: #A7F3D0; }
html.dark .text-rose-800 { color: #FECDD3; }
html.dark .text-amber-900 { color: #FDE68A; }

/* --- borders & rings ----------------------------------------------------- */
html.dark .border-blush-100 { border-color: #352B45; }
html.dark .border-blush-200 { border-color: #43354F; }
html.dark .border-blush-300 { border-color: #5A4767; }
html.dark .border-blush-400 { border-color: #FF9AA2; }
html.dark .border-rose-200 { border-color: #6B3B4B; }
html.dark .border-emerald-200 { border-color: #2A5B48; }
html.dark .border-amber-300 { border-color: #7A6134; }

html.dark .ring-blush-100 { --tw-ring-color: #352B45; }
html.dark .ring-blush-200 { --tw-ring-color: #43354F; }
html.dark .ring-blush-400 { --tw-ring-color: #FF9AA2; }
html.dark .ring-white { --tw-ring-color: #241F33; }

/* --- form controls (the Tailwind forms plugin paints them white) --------- */
html.dark input:not([type="checkbox"]):not([type="radio"]),
html.dark textarea,
html.dark select {
  background-color: #2B2338;
  border-color: #43354F;
  color: #EFE7F5;
}

html.dark input::placeholder,
html.dark textarea::placeholder { color: #9C8290; }
html.dark select option { background-color: #241F33; color: #EFE7F5; }
html.dark input[type="file"]::file-selector-button {
  background-color: #352B45;
  color: #FFC6D3;
}

/* --- hover / focus states ------------------------------------------------- */
html.dark .hover\:bg-blush-50:hover { background-color: #332A42; }
html.dark .hover\:bg-blush-100:hover { background-color: #3D3150; }
html.dark .hover\:bg-white:hover { background-color: #2E2740; }
html.dark .hover\:bg-rose-50:hover { background-color: #40293A; }
html.dark .hover\:bg-amber-100:hover { background-color: #4A3A22; }
html.dark .focus\:bg-white:focus { background-color: #2E2740; }

/* --- depth --------------------------------------------------------------- */
html.dark .shadow-card { box-shadow: 0 10px 30px -18px rgba(0, 0, 0, .9); }
html.dark .shadow-soft { box-shadow: 0 16px 36px -18px rgba(0, 0, 0, .85); }
html.dark .shadow-sm { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .45); }

/* keep intentionally transparent fields transparent */
html.dark input.bg-transparent,
html.dark textarea.bg-transparent,
html.dark select.bg-transparent { background-color: transparent; }

/* the admin dashboard table + composer preview read better with a soft edge */
html.dark table tbody tr:hover { background-color: rgb(255 255 255 / .03); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}

/* ==========================================================================
   Post composer (index.php / profile.php) — the composer is a NATIVE,
   auto-growing <textarea> now (markup in includes/composer.php): no Quill, no
   contenteditable and no rich-text HTML in the payload. The card skin itself
   lives in the “📝 POST COMPOSER CARD” block further down this file.
   Private Mail deliberately does NOT use a rich-text editor either: its reply
   box is a plain, auto-growing textarea (.rosy-chat-input further down).
   ========================================================================== */

/* Instantly hide a hint painted over the field on focus, so a mobile IME can
   never show the placeholder on top of the first Thai characters. */
[contenteditable="true"]:focus::before,
[contenteditable="true"][data-placeholder-active="true"]::before,
.post-editor:focus::before,
.post-editor[data-placeholder-active="true"]::before {
  display: none !important;
  opacity: 0 !important;
  content: "" !important;
}

/* Same safeguard for the composer's native auto-growing textarea. */
#composerModal .composer-textarea:focus::placeholder {
  color: transparent !important;
  opacity: 0 !important;
}

/* ==========================================================================
   📝 POST COMPOSER CARD — ONE INLINE SINGLE-CARD COMPOSER (top of the feed)
   --------------------------------------------------------------------------
   The composer is an ordinary IN-FLOW card above the feed. It is NOT a modal,
   NOT a full-screen sheet, NOT a 2-step wizard and has NO rich-text editor:
   every control (native textarea · photos · AI helper · room picker · privacy ·
   🌸 publish) lives in ONE rounded card, so there is no overlay to get stuck
   behind, no body scroll lock, no step state and no third-party editor script
   to re-initialise after a ⚡ PJAX swap — the exact same simple DOM renders
   identically in Chrome, Edge, Safari, mobile browsers and incognito windows.

   Hooks (see includes/composer.php):
     #composerModal      → the card element (kept as the JS hook name)
     .composer-card      → the card skin
     .composer-header-row · .composer-editor-zone · .composer-toolbar
     .composer-actions   · .composer-settings · .composer-publish
     .composer-textarea  · .composer-tool-btn · .composer-ai-pill
     .composer-room-select · .composer-privacy · .composer-publish-btn
   ========================================================================== */

/* ---- 1 · fallback skin (bare class selectors) ----------------------------
   Every surface in includes/composer.php carries its Tailwind utilities, but
   this hand-written copy paints the very same pink card from the first paint:
   no flash of raw, unstyled HTML while the Tailwind CDN build is still being
   evaluated (slow network / blocked CDN / a cold first load). The scoped
   `#composerModal …` rules in section 2 then refine the details + dark mode. */
.composer-card {
  display: block;
  width: 100%;
  margin: 0 0 1.5rem;                              /* mb-6            */
  padding: 1rem;                                   /* p-4             */
  border: 1px solid #FCE7F3;                       /* border-pink-100 */
  border-radius: 1rem;                             /* rounded-2xl     */
  background-color: #fff;                          /* bg-white        */
  box-shadow: 0 1px 2px 0 rgba(74, 63, 63, .06);   /* shadow-sm       */
  box-sizing: border-box;
}

.composer-textarea {
  display: block;
  width: 100%;
  min-width: 0;
  padding: .75rem;                                 /* p-3             */
  border: 1px solid #FBCFE8;                       /* border-pink-200 */
  border-radius: .75rem;                           /* rounded-xl      */
  background-color: #fff;
  color: #374151;                                  /* text-gray-700   */
  font-family: inherit;
  font-size: .875rem;                              /* text-sm         */
  line-height: 1.6;
  resize: none;                                    /* resize-none     */
  box-sizing: border-box;
  transition: border-color .15s ease, box-shadow .15s ease;
}

.composer-textarea::placeholder { color: #F9A8D4; }  /* placeholder-pink-300 */

.composer-textarea:focus {
  outline: none;
  border-color: #F9A8D4;                           /* focus:border-pink-300 */
  box-shadow: 0 0 0 3px rgba(249, 168, 212, .35);  /* focus:ring-pink-300   */
}

.composer-tool-btn,
.composer-ai-pill {
  display: inline-flex;
  align-items: center;
  gap: .375rem;                                    /* gap-1.5         */
  padding: .375rem .75rem;                         /* px-3 py-1.5     */
  border: 1px solid #FCE7F3;                       /* border-pink-100 */
  border-radius: .75rem;                           /* rounded-xl      */
  background-color: #FDF2F8;                       /* bg-pink-50      */
  color: #DB2777;                                  /* text-pink-600   */
  font-family: inherit;
  font-size: .75rem;                               /* text-xs         */
  font-weight: 500;                                /* font-medium     */
  line-height: 1.2;
  cursor: pointer;                                 /* cursor-pointer  */
  transition: background-color .15s ease, color .15s ease;
}

.composer-tool-btn:hover,
.composer-ai-pill:hover { background-color: #FCE7F3; }   /* hover:bg-pink-100 */

.composer-room-select,
.composer-privacy {
  border: 1px solid #FBCFE8;                       /* border-pink-200 */
  border-radius: .75rem;                           /* rounded-xl      */
  background-color: #fff;                          /* bg-white        */
}

.composer-publish-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  margin-left: auto;                               /* ml-auto         */
  padding: .5rem 1.25rem;                          /* px-5 py-2       */
  border: 0;
  border-radius: .75rem;                           /* rounded-xl      */
  background-image: linear-gradient(to right, #EC4899, #FB7185);   /* from-pink-500 to-rose-400 */
  color: #fff;
  font-family: inherit;
  font-size: .875rem;                              /* text-sm         */
  font-weight: 500;                                /* font-medium     */
  box-shadow: 0 1px 2px 0 rgba(74, 63, 63, .08);   /* shadow-sm       */
  cursor: pointer;
  transition: box-shadow .15s ease, opacity .15s ease;
}

.composer-publish-btn:hover {
  box-shadow: 0 4px 6px -1px rgba(74, 63, 63, .12);  /* hover:shadow-md   */
  opacity: .95;                                      /* hover:opacity-95  */
}

/* ---- 2 · the scoped card skin (authoritative) ---------------------------- */

#composerModal.composer-card {
  position: relative;
  display: block;
  width: 100%;
  /* mb-6 lives on the card itself (see the class list in composer.php). */
  margin: 0 0 1.5rem;
  padding: 1rem;
  border: 1px solid #FCE7F3;
  border-radius: 1.5rem;
  background: #fff;
  box-shadow: 0 18px 40px -32px rgba(217, 94, 111, .75);
  box-sizing: border-box;
  overflow: visible;
}

/* The card owns its bottom rhythm, so the feed directly below it drops the
   `space-y-5` / `mt-5` margin it would otherwise stack on top: the composer
   therefore sits exactly like the post cards underneath it. */
#composerModal.composer-card + #feed { margin-top: 0; }

@media (min-width: 640px) {
  /* `!important` too — the vanilla lock-in in section 3 locks the base padding,
     so the tablet/desktop rhythm has to be raised to the same layer. */
  #composerModal.composer-card { padding: 1.25rem !important; }
}

/* The card is ALWAYS visible on the feed: a stale cached copy of the old modal
   markup — or any opener that still carries data-modal-open="composerModal" —
   can never hide it. */
#composerModal.composer-card.hidden { display: block !important; }

#composerModal .composer-form {
  display: grid;
  gap: .75rem;
  width: 100%;
  min-width: 0;
}

/* ---- header · avatar + title -------------------------------------------- */
#composerModal .composer-header-row {
  display: flex;
  align-items: center;
  gap: .75rem;
  min-width: 0;
}

#composerModal .composer-title {
  font-weight: 600;
  line-height: 1.3;
  color: #C2415A;
}

#composerModal .composer-user {
  font-size: .75rem;
  color: rgba(74, 63, 63, .55);
}

/* ---- editor · native auto-growing textarea ------------------------------ */
#composerModal .composer-editor-zone {
  display: grid;
  gap: .5rem;
  min-width: 0;
}

#composerModal .composer-textarea {
  display: block;
  width: 100%;
  min-width: 0;
  min-height: 5.5rem;
  max-height: 60vh;
  padding: .9rem 1rem;
  border: 1px solid #FBCFE8;   /* border-pink-200 */
  border-radius: .75rem;       /* rounded-xl      */
  background: #fff;
  color: #4A3F3F;
  font-family: inherit;
  /* Deliberately one notch above the Tailwind `text-sm` utility: the site
     reads at 18px, so the composer types at the same comfortable size. */
  font-size: 1.05rem;
  line-height: 1.65;
  resize: none;               /* app.js grows it to fit the draft */
  overflow-y: auto;
  box-sizing: border-box;
  transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
}

#composerModal .composer-textarea::placeholder { color: #F9A8D4; }   /* placeholder-pink-300 */

#composerModal .composer-textarea:focus {
  outline: none;
  border-color: #F9A8D4;                              /* focus:border-pink-300 */
  background: #fff;
  box-shadow: 0 0 0 3px rgba(249, 168, 212, .35);    /* focus:ring-pink-300   */
}

#composerModal .composer-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  font-size: .75rem;
  color: rgba(74, 63, 63, .45);
}

/* ---- toolbar · 🖼️ attach photos · 🪄 AI helper -------------------------- */
#composerModal .composer-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  min-width: 0;
}

#composerModal .composer-tool-btn {
  display: inline-flex;
  flex: 0 0 auto;
  min-width: 0;
  cursor: pointer;
  align-items: center;
  gap: .375rem;
  padding: .375rem .75rem;
  border: 1px solid #FCE7F3;
  border-radius: .75rem;
  background: #FDF2F8;
  color: #DB2777;
  font-size: .75rem;
  font-weight: 500;
  line-height: 1.2;
  transition: background .15s ease;
}

#composerModal .composer-tool-btn:hover { background: #FCE7F3 !important; }

#composerModal .composer-ai-pill {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: .375rem;
  padding: .375rem .75rem;
  border: 1px solid #FCE7F3;
  border-radius: .75rem;
  background: #FDF2F8;
  color: #DB2777;
  font-size: .75rem;
  font-weight: 500;
  white-space: nowrap;
  transition: background .15s ease;
}

#composerModal .composer-ai-pill:hover { background: #FCE7F3 !important; }

#composerModal .composer-hint {
  font-size: .7rem;
  line-height: 1.6;
  color: rgba(74, 63, 63, .45);
}

/* Smooth reveal for the 🪄 AI sub-panel when the pill is tapped. */
@keyframes rosy-ai-open {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ✨ AI Article Writer modal (composer) — markup in includes/composer.php,
   behaviour in the “✨ AI Article Writer” module of assets/js/app.js. The modal
   is a normal fixed overlay; Tailwind supplies most utilities, this block is the
   no-flash fallback so the dialog is never rendered unstyled. */
.ai-writer-modal {
  position: fixed;
  inset: 0;
  z-index: 80;
}

.ai-writer-modal.hidden { display: none !important; }

#aiWriterModal [data-ai-writer-dialog] {
  animation: rosy-ai-open .2s ease-out both;
}

/* ✨ Article body produced by the AI assistant and stored in a post — the
   .ai-hero-banner / .ai-hero-img / .ai-inline-img classes survive
   sanitize_post_html() (they are class attributes, never inline styles), so the
   generated hero callout keeps a real CSS-gradient background and the embedded
   R2 images stay rounded and responsive. */
.ai-hero-banner {
  border-radius: 1rem;
  border: 1px solid #F7D6E6;
  background: linear-gradient(135deg, #FDE7F1 0%, #F3E7FF 100%);
  padding: .9rem 1.1rem;
  margin: .6rem 0;
  font-size: .95rem;
}

.ai-hero-img,
.ai-inline-img {
  display: block;
  width: 100%;
  max-height: 30rem;
  object-fit: cover;
  border-radius: 1rem;
  margin: .75rem 0;
  background: #FDF2F8;
}

.ai-inline-img { max-height: 22rem; }

/* ---- bottom action bar · 🏷️ room · 🔒 privacy · 🌸 publish --------------
   ONE flex row (justify-between) under a hairline divider: the room picker +
   privacy selector on the left, the 🌸 publish button on the right. It simply
   wraps onto its own line on a narrow phone, so nothing is ever squeezed out. */
#composerModal .composer-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  margin-top: .75rem;
  padding-top: .75rem;
  border-top: 1px solid #FFF5F7;      /* border-pink-50 */
}

#composerModal .composer-actions .composer-settings {
  flex: 1 1 auto;
  min-width: 0;
}

#composerModal .composer-actions .composer-publish {
  flex: 0 0 auto;
  margin-left: auto;
}

/* ---- settings · 🏷️ room picker + 🔒 privacy ---------------------------- */
#composerModal .composer-settings {
  display: grid;
  grid-template-columns: 1fr;
  gap: .75rem;
  min-width: 0;
}

@media (min-width: 640px) {
  #composerModal .composer-settings { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

#composerModal .composer-field {
  display: grid;
  gap: .4rem;
  min-width: 0;
}

#composerModal .composer-label {
  font-size: .8rem;
  font-weight: 600;
  color: rgba(74, 63, 63, .8);
}

#composerModal .composer-room-select {
  display: flex;
  width: 100%;
  min-width: 0;
  align-items: center;
  justify-content: space-between;
  gap: .4rem;
  padding: .375rem .75rem;
  border: 1px solid #FBCFE8;
  border-radius: .75rem;
  background: #fff;
  color: #4B5563;
  font-family: inherit;
  font-size: .75rem;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background .15s ease;
}

#composerModal .composer-room-select:hover { background: #FDF2F8; }

#composerModal .composer-privacy {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .375rem .75rem;
  border: 1px solid #FBCFE8;
  border-radius: .75rem;
  background: #fff;
}

#composerModal .composer-privacy-select {
  width: 100%;
  min-width: 0;
  cursor: pointer;
  border: 0;
  background: transparent;
  color: #4B5563;
  font-family: inherit;
  font-size: .75rem;
  font-weight: 500;
}

/* ---- publish bar · 🌸 โพสต์เลย ------------------------------------------ */
#composerModal .composer-publish {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: .5rem;
}

#composerModal .composer-publish-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  margin-left: auto;
  padding: .5rem 1.25rem;
  border: 0;
  border-radius: .75rem;
  background-image: linear-gradient(to right, #EC4899, #FB7185);
  color: #fff;
  font-family: inherit;
  font-size: .875rem;
  font-weight: 500;
  box-shadow: 0 1px 2px 0 rgba(217, 94, 111, .3);
  cursor: pointer;
  transition: box-shadow .15s ease, opacity .15s ease;
}

#composerModal .composer-publish-btn:hover {
  box-shadow: 0 8px 18px -8px rgba(217, 94, 111, .85);
  opacity: .95;
  transform: none;
}
#composerModal .composer-publish-btn:disabled { cursor: progress !important; opacity: .7; transform: none; }

/* Every control keeps its full size, so nothing is ever squeezed out of the
   card on a narrow phone (the rows simply wrap instead). */
#composerModal .composer-tool-btn,
#composerModal .composer-ai-pill,
#composerModal .composer-room-select,
#composerModal .composer-privacy-select,
#composerModal .composer-publish-btn {
  flex-shrink: 0;
}

/* ---- dark-theme parity -------------------------------------------------- */
html.dark #composerModal.composer-card { background: #241F33 !important; border-color: #43354F !important; }
html.dark #composerModal .composer-title { color: #FFB0BE; }

html.dark #composerModal .composer-label,
html.dark #composerModal .composer-user,
html.dark #composerModal .composer-meta,
html.dark #composerModal .composer-hint { color: rgba(231, 217, 245, .7); }

html.dark #composerModal .composer-textarea {
  background: #241F33 !important;
  border-color: #43354F !important;
  color: #EFE7F5 !important;
}

html.dark #composerModal .composer-textarea:focus {
  background: #2B2338 !important;
  border-color: #6B5480 !important;
}

html.dark #composerModal .composer-tool-btn,
html.dark #composerModal .composer-room-select {
  background: #2B2338 !important;
  border-color: #43354F !important;
  color: #FFB0BE !important;
}

html.dark #composerModal .composer-tool-btn:hover,
html.dark #composerModal .composer-room-select:hover { background: #382F4D !important; }

html.dark #composerModal .composer-ai-pill {
  background: #2B2338 !important;
  border-color: #43354F !important;
  color: #FFB0BE !important;
}

html.dark #composerModal .composer-ai-pill:hover { background: #382F4D !important; }

html.dark #composerModal .composer-actions { border-top-color: #382F4D !important; }

html.dark #composerModal .composer-privacy { background: #241F33; border-color: #43354F; }
html.dark #composerModal .composer-privacy-select { color: #E7D9F5; }

/* ---- 3 · ✋ vanilla-CSS lock-in (authoritative · needs NO Tailwind CDN) ---
   The card MUST render as a properly constrained white/pink card even when the
   Tailwind CDN build never ran — a completely fresh browser with an empty
   cache, a slow / blocked CDN or a runtime PHP page that is painted before the
   JIT stylesheet exists. Every rule below is plain, standard, hand-written CSS
   and is marked `!important`, so it beats the utility classes in
   includes/composer.php and Tailwind's own CDN preflight from the very first
   paint — identically in Chrome, Edge, Safari and Firefox.

   Because `!important` out-ranks specificity, every composer rule further up
   this file that paints a STATE (hover / focus / disabled / min-width media
   query) or a dark-mode surface was raised to `!important` as well, so those
   states keep winning by specificity instead of being flattened by the base
   declarations below. Dark mode therefore still wins: both layers are
   `!important`, and `html.dark #composerModal …` is the more specific selector.
   ------------------------------------------------------------------------- */

/* Standard Pure CSS for Composer Card */
#composerModal.composer-card {
  background-color: #ffffff !important;
  border: 1px solid #fce7f3 !important;
  border-radius: 1rem !important;
  padding: 1rem !important;
  margin-bottom: 1.5rem !important;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important;
  box-sizing: border-box !important;
  width: 100% !important;
}

.composer-textarea {
  width: 100% !important;
  border: 1px solid #fbcfe8 !important;
  border-radius: 0.75rem !important;
  padding: 0.75rem !important;
  font-size: 0.95rem !important;
  color: #374151 !important;
  background-color: #ffffff !important;
  box-sizing: border-box !important;
  resize: none !important;
  outline: none !important;
}
.composer-textarea:focus {
  border-color: #f472b6 !important;
  box-shadow: 0 0 0 3px rgba(244, 114, 182, 0.2) !important;
}

.composer-actions {
  display: flex !important;
  flex-wrap: wrap !important;
  align-items: center !important;
  justify-content: space-between !important;
  gap: 0.75rem !important;
  border-top: 1px solid #fdf2f8 !important;
  padding-top: 0.75rem !important;
  margin-top: 0.75rem !important;
}

.composer-publish-btn {
  background: linear-gradient(to right, #ec4899, #fb7185) !important;
  color: #ffffff !important;
  font-weight: 500 !important;
  padding: 0.5rem 1.25rem !important;
  border-radius: 0.75rem !important;
  border: none !important;
  cursor: pointer !important;
  font-size: 0.875rem !important;
  margin-left: auto !important;
}

.composer-tool-btn,
.composer-ai-pill {
  display: inline-flex !important;
  align-items: center !important;
  gap: 0.375rem !important;
  background-color: #fdf2f8 !important;
  color: #db2777 !important;
  padding: 0.375rem 0.75rem !important;
  border-radius: 0.75rem !important;
  font-size: 0.75rem !important;
  font-weight: 500 !important;
  border: 1px solid #fce7f3 !important;
  cursor: pointer !important;
}

/* The hover tint also has to live in the `!important` layer, or the locked
   background above would freeze the pill on hover. */
.composer-tool-btn:hover,
.composer-ai-pill:hover { background-color: #fce7f3 !important; }

/* 🌙 Dark-mode parity for the lock-in (same layer → higher specificity wins). */
html.dark #composerModal.composer-card {
  background-color: #241F33 !important;
  border-color: #43354F !important;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5) !important;
}

html.dark #composerModal .composer-textarea {
  background-color: #241F33 !important;
  border: 1px solid #43354F !important;
  color: #EFE7F5 !important;
}
html.dark #composerModal .composer-textarea:focus {
  background-color: #2B2338 !important;
  border-color: #6B5480 !important;
  box-shadow: 0 0 0 3px rgba(255, 154, 162, 0.28) !important;
}

html.dark #composerModal .composer-actions { border-top-color: #382F4D !important; }

html.dark #composerModal .composer-tool-btn,
html.dark #composerModal .composer-ai-pill {
  background-color: #2B2338 !important;
  border: 1px solid #43354F !important;
  color: #FFB0BE !important;
}
html.dark #composerModal .composer-tool-btn:hover,
html.dark #composerModal .composer-ai-pill:hover { background-color: #382F4D !important; }

/* ---- 4 · the composer / feed column (pure-CSS width guard) ----------------
   Tailwind's `mx-auto max-w-2xl` is only applied once the CDN build runs, so on
   a cold first paint the in-flow card would stretch across the whole viewport.
   `.composer-column` reproduces that exact constraint in plain CSS (42rem =
   max-w-2xl, centred) and is additive: when Tailwind IS present the values are
   identical, so nothing shifts. */
.composer-column {
  width: 100%;
  max-width: 42rem;
  margin-left: auto;
  margin-right: auto;
  box-sizing: border-box;
}

/* ==========================================================================
   Auto-embedded YouTube media — responsive 16:9 (fallback for aspect-video)
   ========================================================================== */
.rosy-post-body .aspect-video,
[data-post-body] .aspect-video {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
}

.rosy-post-body .aspect-video iframe,
[data-post-body] .aspect-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* ==========================================================================
   Rich-text post bodies — Tailwind's preflight resets headings & list markers,
   so restore them here for composer-formatted posts.
   ========================================================================== */
.rosy-post-body h1,
[data-post-body] h1 { font-size: 1.6em; font-weight: 700; line-height: 1.3; margin: .5em 0 .35em; }

.rosy-post-body h2,
[data-post-body] h2 { font-size: 1.35em; font-weight: 700; line-height: 1.35; margin: .5em 0 .35em; }

.rosy-post-body h3,
[data-post-body] h3 { font-size: 1.15em; font-weight: 600; line-height: 1.4; margin: .5em 0 .3em; }

.rosy-post-body ul,
[data-post-body] ul { list-style: disc; padding-left: 1.5rem; margin: .4em 0; }

.rosy-post-body ol,
[data-post-body] ol { list-style: decimal; padding-left: 1.5rem; margin: .4em 0; }

.rosy-post-body li,
[data-post-body] li { margin: .2em 0; }

.rosy-post-body blockquote,
[data-post-body] blockquote {
  border-left: 3px solid #FFD3DD;
  padding-left: 1rem;
  margin: .5em 0;
  color: #6B5B5B;
}

.rosy-post-body strong, [data-post-body] strong { font-weight: 700; }
.rosy-post-body em, [data-post-body] em { font-style: italic; }
.rosy-post-body u, [data-post-body] u { text-decoration: underline; }
.rosy-post-body a, [data-post-body] a { color: #D95E6F; text-decoration: underline; }

html.dark .rosy-post-body blockquote,
html.dark [data-post-body] blockquote { border-left-color: #43354F; color: #C9BDD4; }
html.dark .rosy-post-body a,
html.dark [data-post-body] a { color: #FFB0BE; }

/* ==========================================================================
   🌙 Dark-mode text visibility fix
   --------------------------------------------------------------------------
   Card surfaces turn dark in dark mode, but several text elements kept their
   light-theme dark-grey colour (Tailwind's `text-gray-800` / `text-ink`
   utilities, Quill-rendered post HTML and sidebar widgets), leaving the copy
   invisible until it was selected with the mouse. The rules below (a) force
   every post-body / rich-text / widget surface to a high-contrast light
   colour and (b) neutralise inline styles and Tailwind dark-text classes so
   the body text is always readable. `!important` is required so the override
   beats Tailwind's own utility classes.
   ========================================================================== */

/* Force post content & rich-text editor output to light text. */
.dark .post-content,
.dark .post-body,
.dark .post-content p,
.dark .post-body p,
.dark .post-content span,
.dark .post-body span,
.dark .card,
.dark .widget-content {
  color: #f3f4f6 !important;
}

/* Ensure links and tags remain legible. */
.dark .post-content a,
.dark .post-body a {
  color: #f472b6 !important; /* Soft pink for dark mode links */
}

/* --- the site's own wrappers (see post_card.php, post.php, page.php) ------ */
html.dark .rosy-post-body,
html.dark [data-post-body],
html.dark .rosy-comment-body,
html.dark [data-comment-body],
html.dark .post-content,
html.dark .post-body,
html.dark [data-post-content] {
  color: #f3f4f6 !important;
}

/* Block / inline elements Quill emits inherit the wrapper colour, but make the
   guarantee explicit so a stray light-theme utility can never re-darken them. */
html.dark .rosy-post-body p,   html.dark [data-post-body] p,
html.dark .rosy-post-body span, html.dark [data-post-body] span,
html.dark .rosy-post-body strong, html.dark [data-post-body] strong,
html.dark .rosy-post-body em,   html.dark [data-post-body] em,
html.dark .rosy-post-body u,    html.dark [data-post-body] u,
html.dark .rosy-post-body li,   html.dark [data-post-body] li,
html.dark .rosy-post-body h1,   html.dark [data-post-body] h1,
html.dark .rosy-post-body h2,   html.dark [data-post-body] h2,
html.dark .rosy-post-body h3,   html.dark [data-post-body] h3,
html.dark .rosy-post-body h4,   html.dark [data-post-body] h4,
html.dark .rosy-post-body code, html.dark [data-post-body] code {
  color: #f3f4f6;
}

/* Headings read a touch brighter than body copy. */
html.dark .rosy-post-body h1, html.dark [data-post-body] h1,
html.dark .rosy-post-body h2, html.dark [data-post-body] h2,
html.dark .rosy-post-body h3, html.dark [data-post-body] h3,
html.dark .rosy-post-body h4, html.dark [data-post-body] h4 {
  color: var(--color-heading, #ffffff);
}

/* Links & tags stay pink and legible inside post bodies. */
html.dark .rosy-post-body a,
html.dark [data-post-body] a,
html.dark .rosy-post-body a *,
html.dark [data-post-body] a * {
  color: #f472b6 !important;
}

/* (3) Inline-style safety net — if rich-text HTML ever smuggles an inline
   `style="color: …"` through, force it back to the readable light colour.
   (The PHP sanitiser normally strips inline styles, so this is a last resort.) */
html.dark .post-content [style*="color"],
html.dark .post-body [style*="color"],
html.dark .rosy-post-body [style*="color"],
html.dark [data-post-body] [style*="color"],
html.dark .rosy-comment-body [style*="color"],
html.dark [data-comment-body] [style*="color"] {
  color: #f3f4f6 !important;
}

/* (3) Tailwind neutral text utilities used across cards, feeds and sidebars —
   they shipped with no dark-mode counterpart, so they are mapped here. */
html.dark .text-black,
html.dark .text-gray-900,
html.dark .text-gray-800,
html.dark .text-gray-700,
html.dark .text-slate-900,
html.dark .text-slate-800,
html.dark .text-slate-700 { color: #f3f4f6; }

html.dark .text-gray-600,
html.dark .text-gray-500,
html.dark .text-gray-400,
html.dark .text-slate-600,
html.dark .text-slate-500,
html.dark .text-slate-400 { color: #9ca3af; }

/* ==========================================================================
   📹 Google Meet action card — a pasted Meet link is auto-linked into this
   green call to action by format_post_content() (includes/functions.php).
   Painted in plain CSS so it looks right on the first paint, before the
   Tailwind CDN build has been evaluated, and so the site's own link colours
   (.rosy-post-body a, and the dark-mode “links stay pink” rules) can never
   repaint the white label — those carry !important, hence the ones here.
   ========================================================================== */
.rosy-meet-card,
.rosy-meet-card:hover,
.rosy-meet-card:focus,
.rosy-meet-card:visited,
.rosy-meet-card * {
  color: #ffffff !important;
  text-decoration: none !important;
}

.rosy-meet-card {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
  background-color: #059669;                 /* emerald-600 */
  border: 1px solid rgba(16, 185, 129, .3);  /* emerald-500/30 */
  border-radius: 1rem;
  padding: .75rem 1rem;
  font-weight: 500;
  vertical-align: middle;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .06);
  transition: background-color .2s ease, box-shadow .2s ease;
}

.rosy-meet-card:hover,
.rosy-meet-card:focus-visible {
  background-color: #047857;                 /* emerald-700 */
  box-shadow: 0 4px 12px rgba(4, 120, 87, .28);
}

/* Dark mode re-assertion: `html.dark [data-post-body] a *` forces pink on every
   descendant of a link, so the card's own colours are re-stated with a higher
   specificity (three classes + !important). */
html.dark .rosy-post-body .rosy-meet-card,
html.dark .rosy-post-body .rosy-meet-card *,
html.dark [data-post-body] .rosy-meet-card,
html.dark [data-post-body] .rosy-meet-card *,
html.dark .rosy-comment-body .rosy-meet-card,
html.dark .rosy-comment-body .rosy-meet-card *,
html.dark [data-comment-body] .rosy-meet-card,
html.dark [data-comment-body] .rosy-meet-card *,
html.dark .post-content .rosy-meet-card,
html.dark .post-content .rosy-meet-card *,
html.dark .post-body .rosy-meet-card,
html.dark .post-body .rosy-meet-card * {
  color: #ffffff !important;
  text-decoration: none !important;
}

html.dark .rosy-post-body .rosy-meet-card,
html.dark [data-post-body] .rosy-meet-card,
html.dark .rosy-comment-body .rosy-meet-card,
html.dark [data-comment-body] .rosy-meet-card,
html.dark .post-content .rosy-meet-card,
html.dark .post-body .rosy-meet-card {
  background-color: #059669;
}

/* Narrow phones: the call to action is long and Thai carries no word spaces, so
   the card wraps instead of spilling past the column it lives in. */
@media (max-width: 480px) {
  .rosy-meet-card {
    white-space: normal !important;
    text-align: center;
  }
  .rosy-meet-card span {
    overflow-wrap: anywhere !important;
    word-break: normal !important;
  }
}

/* ==========================================================================
   Global image lightbox — a full-screen preview of any [data-lightbox] photo
   (gallery tiles, post photos). Deliberately dark in both themes so the image
   is the only bright thing on screen.
   ========================================================================== */
.rosy-lightbox {
  align-items: center;
  padding: 1rem;
}

.rosy-lightbox > [data-modal-backdrop] {
  background: rgba(20, 14, 24, .9);
  backdrop-filter: blur(4px);
}

/* Keep the lightbox backdrop dark in dark mode too (the dark-theme modal rule
   is more specific, so it has to be repeated with the same specificity). */
html.dark .rosy-lightbox > [data-modal-backdrop] {
  background: rgba(20, 14, 24, .92);
}

.rosy-lightbox > [data-modal-panel] {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .6rem;
  width: 100%;
  max-width: 64rem;
}

.rosy-lightbox-figure { margin: 0; }

.rosy-lightbox img {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  max-height: 74vh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 1rem;
  background: rgba(0, 0, 0, .25);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .9);
}

.rosy-lightbox-caption {
  margin: .7rem 0 0;
  text-align: center;
  font-size: .82rem;
  line-height: 1.5;
  color: #FFE8EE;
}

.rosy-lightbox-counter {
  margin: 0;
  font-size: .72rem;
  color: rgba(255, 232, 238, .72);
}

.rosy-lightbox-nav,
.rosy-lightbox-close {
  position: absolute;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 10px 24px -14px rgba(0, 0, 0, .9);
  transition: background-color .2s ease, transform .2s ease;
}

.rosy-lightbox-nav:hover,
.rosy-lightbox-close:hover { background: rgba(255, 255, 255, .34); }

.rosy-lightbox-nav {
  top: 50%;
  transform: translateY(-50%);
  width: 2.6rem;
  height: 2.6rem;
  font-size: 1.6rem;
  line-height: 1;
}

.rosy-lightbox-nav--prev { left: .4rem; }
.rosy-lightbox-nav--next { right: .4rem; }

.rosy-lightbox-close {
  top: .5rem;
  right: .5rem;
  width: 2.2rem;
  height: 2.2rem;
  font-size: 1.05rem;
  line-height: 1;
}

@media (min-width: 768px) {
  .rosy-lightbox-nav--prev { left: -3.2rem; }
  .rosy-lightbox-nav--next { right: -3.2rem; }
}

/* --- profile / cover photo lightbox --------------------------------------
   Built client-side by assets/js/app.js (see “profile / cover lightbox”). The
   floating overlay carries Tailwind utility classes, so — exactly like the
   [data-modal] rule above — the `hidden` state is forced here to guarantee the
   preview is reliably dismissed with ✕ / backdrop / Esc regardless of the
   utility order the Tailwind CDN emits. */
[data-profile-lightbox].hidden { display: none !important; }

/* ===========================================================================
   Postcard Mailbox (ตู้โปสการ์ด) — cozy template postcards
   The very same card markup is used by the composer preview, the mailbox
   gallery (compact) and the full-size modal, so it is styled once here.
   ========================================================================== */
/* --- 🃏 the 3D flip shell -------------------------------------------------
   The card is a real two-faced 3D flip: `.rosy-postcard-flip` is the
   perspective container, `.rosy-postcard-inner` the rotating wrapper, and the
   `.rosy-postcard-front` (template artwork) / `.rosy-postcard-back` (the ruled
   note) the two hidden back-faces. Setting data-flipped="true" on the container
   rotates the wrapper 180° to reveal the note. */
.rosy-postcard-shell {
  position: relative;
  width: 100%;
}

.rosy-postcard-flip {
  position: relative;
  width: 100%;
  perspective: 1000px;
}

.rosy-postcard-inner {
  position: relative;
  width: 100%;
  /* The wrapper alone owns the card's size; both faces are pinned to it. */
  aspect-ratio: 3 / 2;
  transform-style: preserve-3d;
  -webkit-transform-style: preserve-3d;
  transition: transform 0.6s; /* exact spec timing */
  -webkit-transition: -webkit-transform 0.6s;
}

.rosy-postcard-flip[data-flipped="true"] .rosy-postcard-inner {
  transform: rotateY(180deg);
  -webkit-transform: rotateY(180deg);
}

/* Explicit face stacking (a belt-and-braces companion to backface-visibility).
   At rest the FRONT is on top; flipping swaps the two so the face that is
   actually turned toward the viewer is always the one painted last. This keeps
   the artwork from ever bleeding through the note — and the note from covering
   the artwork — even on engines/repaints where `backface-visibility` alone is
   momentarily not honoured. */
.rosy-postcard-flip[data-flipped="false"] .rosy-postcard-front { z-index: 2; }
.rosy-postcard-flip[data-flipped="false"] .rosy-postcard-back  { z-index: 1; }
.rosy-postcard-flip[data-flipped="true"]  .rosy-postcard-front { z-index: 1; }
.rosy-postcard-flip[data-flipped="true"]  .rosy-postcard-back  { z-index: 2; }

/* Both faces carry the card chrome; the INNER wrapper's aspect-ratio gives the
   card its height, and each face is absolutely pinned to fill it. */
.rosy-postcard-front,
.rosy-postcard-back {
  position: absolute;
  inset: 0;
  border: 1px solid #FFD3DD;
  border-radius: 1.5rem;
  background-color: #FFF5F7;
  box-shadow: 0 18px 40px -26px rgba(255, 154, 162, .95);
  color: #4A3F3F;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* --- part 1 (front) · the template artwork, seal & 100×100 photo stamp -----
   The artwork is painted with `background-image` (set inline by PHP/JS), so the
   real template picture — e.g. the cat graphic templates — ALWAYS fills this
   face. `cover` + `center` + `no-repeat` keep it crisp and undistorted; the
   pastel gradient is only the fallback shown for a card with no template. */
.rosy-postcard-front {
  z-index: 2;
  display: flex;
  flex-direction: column;
  background-image: linear-gradient(180deg, rgba(255, 245, 247, .96) 0%, rgba(255, 211, 221, .9) 100%);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  overflow: hidden;
  /* An explicit resting 3D transform so the artwork face is a true 3D plane
     (many engines only honour backface-visibility on a transformed element). */
  transform: rotateY(0deg);
  -webkit-transform: rotateY(0deg);
}

.rosy-postcard--empty .rosy-postcard-front {
  background-image: linear-gradient(135deg, #FFE8EE 0%, #FFF5F7 55%, #FFD3DD 100%);
}

/* --- part 2 (back) · the ruled note, recipient/sender tags & timestamp ----- */
.rosy-postcard-back {
  z-index: 1;
  display: flex;
  transform: rotateY(180deg);
  -webkit-transform: rotateY(180deg);
  overflow: hidden;
}

/* --- the ruled note on the back face ---------------------------------------
   The note fills the whole back face and is drawn as a ruled note pad — the
   handwriting sits on the faint pink lines. */
.rosy-postcard-note {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: .5rem;
  width: 100%;
  padding: .95rem 1.15rem 1.05rem;
  background-color: #FFF9FB;
  background-image: repeating-linear-gradient(
    180deg,
    transparent 0,
    transparent 1.62rem,
    rgba(255, 211, 221, .8) 1.62rem,
    rgba(255, 211, 221, .8) calc(1.62rem + 1px)
  );
  background-position: 0 0;
}

.rosy-postcard-to,
.rosy-postcard-from,
.rosy-postcard-stamp {
  align-self: flex-start;
  max-width: 100%;
  padding: .2rem .6rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .82);
  color: #8A3B48;
  font-size: .72rem;
  font-weight: 600;
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The “ถึง” (recipient) pill lives with “จาก” (sender) + timestamp in the note
   footer — part 2 of the stacked card. It keeps the shared pill styling above. */
.rosy-postcard-to { max-width: 100%; }

.rosy-postcard-text {
  flex: 1 1 auto;
  margin: 0;
  padding: 0 .1rem;
  color: #4A3F3F;
  font-family: 'Itim', cursive, sans-serif;
  /* 📮 The note size follows the site-wide Body Typography — Admin → 🌐
     Language, Theme & Site Branding → 🎨 Site Typography → Body font size.
     Driving it from --site-body-size keeps every font on the site in step from
     ONE setting (the old standalone “Postcard content font size” was removed). */
  font-size: var(--site-body-size, 1.05rem);
  font-weight: 500;
  line-height: 1.62rem;      /* sit the handwriting exactly on the ruled lines */
  text-align: left;
  min-width: 0;
  word-wrap: break-word;
  overflow-wrap: anywhere;
  word-break: break-word;
  white-space: pre-wrap;
}

.rosy-postcard-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem .5rem;
}

.rosy-postcard-stamp { margin-left: auto; }

.rosy-postcard-badge {
  position: absolute;
  left: .75rem;
  bottom: .75rem;
  z-index: 2;
  max-width: calc(100% - 1.5rem);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: .2rem .6rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .85);
  color: #B25A68;
  font-size: .65rem;
  font-weight: 700;
}

.rosy-postcard-new {
  position: absolute;
  right: .75rem;
  bottom: .75rem;
  z-index: 2;
  padding: .2rem .55rem;
  border-radius: 999px;
  background: linear-gradient(135deg, #FF9AA2, #FFB6C1);
  color: #fff;
  font-size: .6rem;
  font-weight: 800;
  letter-spacing: .06em;
  box-shadow: 0 6px 14px -8px rgba(255, 105, 180, .9);
}

/* --- photo attachment · the 📷 100 × 100 px mini postage stamp -------------
   A chosen photo is pinned on the artwork’s top-right corner as a real
   postage stamp: STRICTLY 100 × 100 px (object-fit: cover), so it never
   distorts and never hides or replaces the template artwork behind it.
   Tapping it opens the full-resolution picture in the global lightbox. */
.rosy-postcard-stamp-badge {
  position: absolute;
  right: .6rem;
  top: .6rem;
  z-index: 4;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100px;
  height: 100px;
  padding: 3px;
  border: 2px solid #fff;
  border-radius: .85rem;
  background: #FFF5F7;
  box-shadow: 0 10px 22px -12px rgba(138, 59, 72, .95);
  overflow: hidden;
  cursor: zoom-in;
  transition: transform .16s ease, box-shadow .16s ease;
}
.rosy-postcard-stamp-badge img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: .6rem;
}
.rosy-postcard-stamp-badge__mark {
  position: absolute;
  right: -.18rem;
  bottom: -.18rem;
  font-size: .68rem;
  line-height: 1;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .25));
}
button.rosy-postcard-stamp-badge:hover,
button.rosy-postcard-stamp-badge:focus-visible {
  transform: translateY(-2px) rotate(-2deg);
  box-shadow: 0 12px 22px -10px rgba(138, 59, 72, .95);
}
button.rosy-postcard-stamp-badge:focus-visible { outline: 2px solid #FF9AA2; outline-offset: 2px; }
.rosy-postcard-stamp-badge--static { cursor: default; }

/* Compact gallery thumbnail: a smaller, decorative stamp. */
.rosy-postcard--thumb .rosy-postcard-stamp-badge {
  width: 1.7rem;
  height: 1.7rem;
  right: .35rem;
  top: .35rem;
  border-radius: .55rem;
}
.rosy-postcard--thumb .rosy-postcard-stamp-badge__mark { font-size: .54rem; }


/* --- gallery thumbnail (3-up grid, compact card) --------------------------- */
.rosy-postcard--thumb .rosy-postcard-front,
.rosy-postcard--thumb .rosy-postcard-back {
  border-radius: 1.1rem;
}
.rosy-postcard--thumb .rosy-postcard-note {
  gap: .3rem;
  padding: .45rem .55rem .55rem;
  background-image: repeating-linear-gradient(
    180deg,
    transparent 0,
    transparent 1.2rem,
    rgba(255, 211, 221, .8) 1.2rem,
    rgba(255, 211, 221, .8) calc(1.2rem + 1px)
  );
}
.rosy-postcard--thumb .rosy-postcard-to,
.rosy-postcard--thumb .rosy-postcard-from,
.rosy-postcard--thumb .rosy-postcard-stamp { font-size: .6rem; padding: .12rem .42rem; }
.rosy-postcard--thumb .rosy-postcard-badge { left: .35rem; bottom: .35rem; font-size: .55rem; }
.rosy-postcard--thumb .rosy-postcard-new { right: .35rem; bottom: .35rem; }
.rosy-postcard--thumb .rosy-postcard-text {
  font-size: .78rem;
  line-height: 1.2rem;
}

/* --- gallery tile (the clickable wrapper around a thumbnail) -------------- */
.rosy-postcard-tile {
  display: block;
  padding: 0;
  border: 0;
  border-radius: 1.25rem;
  background: transparent;
  cursor: zoom-in;
  transition: transform .18s ease, filter .18s ease;
}
.rosy-postcard-tile:hover { transform: translateY(-3px) rotate(-.35deg); filter: brightness(1.03); }
.rosy-postcard-tile:focus-visible { outline: 2px solid #FF9AA2; outline-offset: 3px; border-radius: 1.25rem; }

/* --- google-style pagination bar ----------------------------------------- */
.rosy-page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.25rem;
  padding: .4rem .7rem;
  border: 1px solid #FFD3DD;
  border-radius: .85rem;
  background: #fff;
  color: #8A3B48;
  font-size: .78rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  transition: background .16s ease, color .16s ease, border-color .16s ease;
}
.rosy-page-btn:hover { background: #FFF0F3; }
.rosy-page-btn[aria-current="page"] {
  background: linear-gradient(135deg, #FFB6C1, #FF9AA2);
  border-color: transparent;
  color: #fff;
}
.rosy-page-btn--gap { border-color: transparent; background: transparent; color: #B25A68; }
.rosy-page-btn--disabled { opacity: .45; cursor: not-allowed; }

/* --- composer preview frame ---------------------------------------------- */
.rosy-postcard-stage {
  padding: 1rem;
  border: 1px dashed #FFD3DD;
  border-radius: 1.75rem;
  background: linear-gradient(180deg, #FFF9F5 0%, #FFF5F7 100%);
}

/* --- 🔄 flip toggle (below the card in the preview & thread modals) -------- */
.rosy-postcard-flip-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  width: max-content;
  margin: .85rem auto 0;
  padding: .45rem 1.05rem;
  border: 1px solid #FFD3DD;
  border-radius: 999px;
  background: #fff;
  color: #8A3B48;
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .16s ease, transform .16s ease, border-color .16s ease;
}
.rosy-postcard-flip-toggle:hover { background: #FFF0F3; transform: translateY(-1px); }
.rosy-postcard-flip-toggle:focus-visible { outline: 2px solid #FF9AA2; outline-offset: 2px; }
.rosy-postcard-flip-toggle[aria-pressed="true"] {
  border-color: #FF9AA2;
  background: linear-gradient(135deg, #FFB6C1, #FF9AA2);
  color: #fff;
}

/* --- template picker ------------------------------------------------------ */
.rosy-template-pick {
  display: block;
  width: 100%;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 1rem;
  overflow: hidden;
  background: #FFF5F7;
  cursor: pointer;
  transition: border-color .16s ease, transform .16s ease;
}
.rosy-template-pick img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}
.rosy-template-pick[aria-pressed="true"] { border-color: #FF9AA2; transform: translateY(-2px); }
.rosy-template-pick:focus-visible { outline: 2px solid #FF9AA2; outline-offset: 2px; }

/* --- category tabs ------------------------------------------------------- */
.rosy-postcard-tab {
  display: inline-block;
  padding: .4rem .85rem;
  border: 1px solid #FFD3DD;
  border-radius: 999px;
  background: #fff;
  color: #8A3B48;
  font-size: .75rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .16s ease, color .16s ease;
}
.rosy-postcard-tab[aria-selected="true"] {
  background: linear-gradient(135deg, #FFB6C1, #FF9AA2);
  border-color: transparent;
  color: #fff;
}

.rosy-postcard-hidden { display: none !important; }

/* --- 🏷️ custom postal seal · a hand-stamped ring centred on the top edge --- */
.rosy-postcard-seal {
  position: absolute;
  top: .6rem;
  left: 50%;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 3.4rem;
  height: 3.4rem;
  border: 2px dashed rgba(138, 59, 72, .55);
  border-radius: 50%;
  background: radial-gradient(circle at 32% 26%, rgba(255, 255, 255, .96), rgba(255, 240, 245, .78));
  color: #B25A68;
  text-align: center;
  line-height: 1;
  transform: translateX(-50%) rotate(-6deg);
  box-shadow: 0 8px 18px -12px rgba(138, 59, 72, .85);
  pointer-events: none;
}
.rosy-postcard-seal__emoji { font-size: 1rem; }
.rosy-postcard-seal__text {
  margin-top: .12rem;
  max-width: 92%;
  overflow: hidden;
  font-size: .48rem;
  font-weight: 700;
  letter-spacing: .02em;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 12 seal hues — one per postcard_seal_options() entry. */
.rosy-postcard-seal--love      { border-color: rgba(217, 94, 111, .62); color: #D95E6F; }
.rosy-postcard-seal--thanks    { border-color: rgba(178, 90, 154, .55); color: #B25A9A; }
.rosy-postcard-seal--miss      { border-color: rgba(120, 108, 191, .55); color: #6F63BF; }
.rosy-postcard-seal--congrats  { border-color: rgba(217, 152, 60, .6);  color: #C98A2E; }
.rosy-postcard-seal--cheer     { border-color: rgba(70, 158, 122, .55); color: #3E9E7A; }
.rosy-postcard-seal--sorry     { border-color: rgba(191, 108, 140, .55); color: #BF6C8C; }
.rosy-postcard-seal--goodnight { border-color: rgba(91, 111, 181, .55);  color: #5B6FB5; }
.rosy-postcard-seal--always    { border-color: rgba(217, 94, 111, .62);  color: #D95E6F; }
.rosy-postcard-seal--secret    { border-color: rgba(91, 111, 181, .55);  color: #5B6FB5; }
.rosy-postcard-seal--birthday  { border-color: rgba(214, 106, 158, .6);  color: #D66A9E; }
.rosy-postcard-seal--happy     { border-color: rgba(76, 158, 191, .55);  color: #4C9EBF; }
.rosy-postcard-seal--special   { border-color: rgba(62, 142, 98, .55);   color: #3E8E62; }

/* Compact gallery thumbnail: a smaller, decorative seal (still top-centre). */
.rosy-postcard--thumb .rosy-postcard-seal {
  width: 2rem;
  height: 2rem;
  top: .42rem;
  border-width: 1px;
}
.rosy-postcard--thumb .rosy-postcard-seal__emoji { font-size: .68rem; }
.rosy-postcard--thumb .rosy-postcard-seal__text { font-size: .3rem; }

/* --- 🔍 zoom cue on the mini photo stamp ----------------------------------- */
.rosy-postcard-stamp-badge__zoom {
  position: absolute;
  left: -.18rem;
  bottom: -.18rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1rem;
  height: 1rem;
  padding: 0 .1rem;
  border-radius: 999px;
  background: #fff;
  font-size: .56rem;
  line-height: 1;
  box-shadow: 0 2px 6px -2px rgba(0, 0, 0, .4);
}
.rosy-postcard--thumb .rosy-postcard-stamp-badge__zoom {
  min-width: .8rem;
  height: .8rem;
  font-size: .44rem;
}

/* --- 🔍 / 💡 helper micro-copy below a card -------------------------------- */
.rosy-postcard-hint {
  margin-top: .5rem;
  font-size: .68rem;
  line-height: 1.45;
  color: #B25A68;
}

/* --- 🔴 recall button — the red “ลบโปสการ์ด” in a modal footer --------------
   Rendered for every card the viewer may delete (sender, receiver or admin)
   and deliberately independent of the optional 🖼️ photo stamp, so it sits at
   the bottom-right of the modal footer whether or not the card has an
   attachment. The wrapping form is `flex justify-end`, so it clings to the
   bottom-right of the modal footer. */
.rosy-postcard-delete {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .42rem .9rem;
  border: 1px solid #FCA5A5;
  border-radius: .85rem;
  background: #fff;
  color: #DC2626;
  font-size: .72rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  transition: background .16s ease, border-color .16s ease, color .16s ease;
}
.rosy-postcard-delete:hover { background: #FEF2F2; border-color: #F87171; color: #B91C1C; }
.rosy-postcard-delete:focus-visible { outline: 2px solid #F87171; outline-offset: 2px; }

/* ✍️ The single handwriting face every postcard note uses. */
.rosy-font-itim { font-family: 'Itim', cursive, sans-serif; }

/* An empty card (no note written) shows a soft placeholder. */
.rosy-postcard-text--empty { color: #B25A68; font-style: italic; }

/* --- counters ------------------------------------------------------------ */
.rosy-mailbox-count {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .75rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .85);
  color: #B25A68;
  font-size: .72rem;
  font-weight: 600;
}

/* --- dark theme ---------------------------------------------------------- */
html.dark .rosy-postcard-front,
html.dark .rosy-postcard-back {
  border-color: #43354F;
  background-color: #241F33;
  color: #F3ECF7;
  box-shadow: 0 18px 40px -26px rgba(0, 0, 0, .95);
}
html.dark .rosy-postcard-front {
  background-image: linear-gradient(180deg, #33283F 0%, #241F33 100%);
}
html.dark .rosy-postcard--empty .rosy-postcard-front {
  background-image: linear-gradient(135deg, #33283F 0%, #241F33 55%, #43354F 100%);
}
html.dark .rosy-postcard-note {
  background-color: #1F1B2E;
  background-image: repeating-linear-gradient(
    180deg,
    transparent 0,
    transparent 1.62rem,
    rgba(67, 53, 79, .9) 1.62rem,
    rgba(67, 53, 79, .9) calc(1.62rem + 1px)
  );
}
html.dark .rosy-postcard-text {
  color: #F3ECF7;
}
html.dark .rosy-postcard-flip-toggle {
  border-color: #43354F;
  background: #241F33;
  color: #FFC9D4;
}
html.dark .rosy-postcard-flip-toggle:hover { background: #2E2740; }
html.dark .rosy-postcard-to,
html.dark .rosy-postcard-from,
html.dark .rosy-postcard-stamp,
html.dark .rosy-postcard-badge {
  background: rgba(26, 22, 37, .82);
  color: #FFC9D4;
}
html.dark .rosy-postcard-stamp-badge {
  border-color: #241F33;
  background: #241F33;
  box-shadow: 0 8px 18px -10px rgba(0, 0, 0, .95);
}
html.dark .rosy-postcard-stage {
  border-color: #43354F;
  background: linear-gradient(180deg, #1A1625 0%, #241F33 100%);
}
html.dark .rosy-postcard-seal {
  border-color: rgba(255, 201, 212, .45);
  background: radial-gradient(circle at 32% 26%, rgba(36, 31, 51, .96), rgba(26, 22, 37, .8));
  color: #FFC9D4;
  box-shadow: 0 8px 18px -12px rgba(0, 0, 0, .95);
}
html.dark .rosy-postcard-text--empty { color: #FFC9D4; }
html.dark .rosy-postcard-hint { color: #FFC9D4; }
html.dark .rosy-postcard-delete { border-color: #7F1D1D; background: #2A1620; color: #FCA5A5; }
html.dark .rosy-postcard-delete:hover { background: #3A1B28; border-color: #B91C1C; color: #FECACA; }
html.dark .rosy-postcard-stamp-badge__zoom { background: #241F33; color: #FFC9D4; }
html.dark .rosy-template-pick { background: #241F33; }
html.dark .rosy-postcard-tab { background: #241F33; border-color: #43354F; color: #FFC9D4; }
html.dark .rosy-postcard-tab[aria-selected="true"] {
  background: linear-gradient(135deg, #B25A68, #8A3B48);
  color: #FFE3E9;
}
html.dark .rosy-mailbox-count { background: rgba(26, 22, 37, .85); color: #FFC9D4; }
html.dark .rosy-page-btn { background: #241F33; border-color: #43354F; color: #FFC9D4; }
html.dark .rosy-page-btn:hover { background: #2E2740; }
html.dark .rosy-page-btn[aria-current="page"] {
  background: linear-gradient(135deg, #B25A68, #8A3B48);
  border-color: transparent;
  color: #FFE3E9;
}
html.dark .rosy-page-btn--gap { background: transparent; border-color: transparent; color: #FFC9D4; }

/* ==========================================================================
   @mention autocomplete — the small member picker that pops up under the post
   composer textarea and the comment textarea while typing “@name”. Built by the
   vanilla layer in assets/js/app.js; themed to match the pastel surfaces with a
   soft shadow, rounded corners and an active-row highlight.
   ========================================================================== */
.rosy-mention-dropdown {
  position: fixed;
  z-index: 70;               /* above the [data-modal] layer (z-index: 60) */
  min-width: 15rem;
  max-width: 22rem;
  max-height: 18rem;
  overflow-y: auto;
  padding: .35rem;
  border: 1px solid #FFD3DD;
  border-radius: 1.25rem;
  background: #fff;
  box-shadow: 0 18px 42px -20px rgba(138, 59, 72, .45);
  animation: rosy-mention-pop .16s ease-out both;
}
.rosy-mention-dropdown.hidden { display: none; }

/* The Edit-profile “Allowed viewers” member list (#userMentionDropdown) is a
   body-level portal whose geometry is written by the “allowed-viewers @mention
   autocomplete” module in assets/js/app.js. `position: fixed` keeps it out of
   the modal's scrollable body (no clipping) and away from the panel's rosy-pop
   transform (no drift); z-index 70 clears the [data-modal] layer (60). */
#userMentionDropdown {
  position: fixed;
  z-index: 70;
}
#userMentionDropdown.hidden { display: none; }

@keyframes rosy-mention-pop {
  from { opacity: 0; transform: translateY(-6px) scale(.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.rosy-mention-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rosy-mention-item {
  display: flex;
  align-items: center;
  gap: .65rem;
  width: 100%;
  padding: .45rem .6rem;
  border: 0;
  border-radius: .9rem;
  background: transparent;
  color: #4A3F3F;
  text-align: left;
  cursor: pointer;
  transition: background .14s ease, color .14s ease;
}
.rosy-mention-item:hover,
.rosy-mention-item.is-active {
  background: linear-gradient(135deg, #FFE8EE, #FFD3DD);
  color: #8A3B48;
}
.rosy-mention-item:focus-visible { outline: 2px solid #FF9AA2; outline-offset: 2px; }

.rosy-mention-avatar {
  width: 2.1rem;
  height: 2.1rem;
  flex: 0 0 auto;
  border-radius: .8rem;
  object-fit: cover;
  background: #FFF5F7;
  box-shadow: inset 0 0 0 1px rgba(255, 154, 162, .35);
}

.rosy-mention-meta {
  display: flex;
  min-width: 0;
  flex-direction: column;
  line-height: 1.25;
}
.rosy-mention-name {
  font-size: .86rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rosy-mention-handle {
  font-size: .72rem;
  color: #B25A68;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rosy-mention-status {
  margin: 0;
  padding: .6rem .75rem;
  color: #B25A68;
  font-size: .75rem;
  text-align: center;
}
.rosy-mention-status.hidden { display: none; }

/* --- dark theme ---------------------------------------------------------- */
html.dark .rosy-mention-dropdown {
  border-color: #43354F;
  background: #241F33;
  box-shadow: 0 20px 46px -22px rgba(0, 0, 0, .95);
}
html.dark .rosy-mention-item { color: #EFE7F5; }
html.dark .rosy-mention-item:hover,
html.dark .rosy-mention-item.is-active {
  background: linear-gradient(135deg, #3D3150, #4A3A57);
  color: #FFC9D4;
}
html.dark .rosy-mention-avatar { background: #332A42; }
html.dark .rosy-mention-handle { color: #FFA6B3; }
html.dark .rosy-mention-status { color: #FFC9D4; }

/* ==========================================================================
   🧭 Sticky top header — dominant navigation rail
   --------------------------------------------------------------------------
   The top bar (includes/header.php) is ONE `flex-nowrap` row: logo · main
   navigation · avatar / theme / ☰ controls. The navigation is the bar's
   primary visual focus: it grows (`flex: 1` in the markup) to fill every
   remaining pixel and only falls back to a horizontally scrollable rail
   (hidden scrollbar) when it genuinely runs out of room.
   ========================================================================== */
.rosy-header-nav {
  min-width: 0;
  pointer-events: auto;
  overflow-x: auto;
  overflow-y: hidden;
  -ms-overflow-style: none;
  scrollbar-width: none;
}
.rosy-header-nav::-webkit-scrollbar { display: none; }

/* ==========================================================================
   📮 Postcard Mailbox menu link — the longest label in the rail
   --------------------------------------------------------------------------
   The label must always render in full: it never wraps, never truncates with
   an ellipsis and never gets squeezed by its flex neighbours. The link opts
   out of the rail's shrinking (flex: 0 0 auto + white-space: nowrap) so a
   crowded bar turns the rail into its horizontal scroll instead of clipping
   the title, while the unread bubble keeps its own footprint.
   ========================================================================== */
.rosy-nav-postcard {
  flex: 0 0 auto;
  white-space: nowrap;
  overflow: visible;
  text-overflow: clip;
  max-width: none;
}

.rosy-nav-postcard-label {
  flex: 0 0 auto;
  white-space: nowrap;
  overflow: visible;
  text-overflow: clip;
  max-width: none;
}

.rosy-nav-postcard > [data-unread-badge] { flex: 0 0 auto; }


/* ==========================================================================
   🔔 Notification atoms — the banner rows shared by the notification bar
   below the header (see .rosy-notif-strip). Each row pairs a tappable body
   (a link to the target post / comment / mailbox card) with a “✕” dismiss
   button that removes the banner instantly and AJAX-purges the record
   (action=dismiss_notif, notifications.php). Themed to match the pastel
   surfaces: soft shadow, rounded corners and a blush hover highlight. Built in
   includes/header.php and driven by the notification module in assets/js/app.js.
   ========================================================================== */
@keyframes rosy-notif-pop {
  from { opacity: 0; transform: translateY(-6px) scale(.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.rosy-notification-item {
  display: flex;
  align-items: flex-start;
  gap: .65rem;
  padding: .55rem .65rem;
  border-radius: .9rem;
  color: #4A3F3F;
  text-decoration: none;
  transition: background .14s ease, color .14s ease, opacity .2s ease, transform .2s ease;
}
.rosy-notification-item:hover {
  background: linear-gradient(135deg, #FFE8EE, #FFD3DD);
  color: #8A3B48;
}
.rosy-notification-item:focus-visible { outline: 2px solid #FF9AA2; outline-offset: 2px; }
.rosy-notification-item.is-removing { opacity: 0; transform: translateX(6px); }

.rosy-notification-avatar {
  width: 2.4rem;
  height: 2.4rem;
  flex: 0 0 auto;
  border-radius: 9999px;
  object-fit: cover;
  background: #FFF5F7;
  box-shadow: inset 0 0 0 1px rgba(255, 154, 162, .35);
}

.rosy-notification-body {
  display: flex;
  min-width: 0;
  flex: 1 1 auto;
  flex-direction: column;
  gap: .15rem;
  line-height: 1.3;
  text-align: left;
}
.rosy-notification-text { font-size: .82rem; word-break: break-word; }
.rosy-notification-text strong { color: #B25A68; font-weight: 700; }
.rosy-notification-time { font-size: .7rem; color: #B25A68; }

/* One banner = the tappable body (a link to the target) + the “✕” dismiss. */
.rosy-notification-row {
  display: flex;
  align-items: stretch;
  gap: .15rem;
}
.rosy-notification-row > .rosy-notification-item {
  flex: 1 1 auto;
  min-width: 0;
}
.rosy-notification-dismiss {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  min-width: 2rem;
  border: 0;
  border-radius: .75rem;
  background: transparent;
  color: #B25A68;
  font-size: .82rem;
  line-height: 1;
  cursor: pointer;
  transition: background .14s ease, color .14s ease;
}
.rosy-notification-dismiss:hover { background: rgba(255, 154, 162, .24); color: #8A3B48; }
.rosy-notification-dismiss:focus-visible { outline: 2px solid #FF9AA2; outline-offset: 2px; }

/* --- dark theme ---------------------------------------------------------- */
html.dark .rosy-notification-item { color: #EFE7F5; }
html.dark .rosy-notification-item:hover {
  background: linear-gradient(135deg, #3D3150, #4A3A57);
  color: #FFC9D4;
}
html.dark .rosy-notification-text strong { color: #FFA6B3; }
html.dark .rosy-notification-time { color: #FFC9D4; }
html.dark .rosy-notification-avatar { background: #332A42; }
html.dark .rosy-notification-dismiss { color: #FFC9D4; }
html.dark .rosy-notification-dismiss:hover { background: rgba(122, 78, 100, .5); color: #FFDCE2; }

/* ==========================================================================
   🔔 Notification banner bar — the stacked banner cards rendered directly
   below the main header, in normal document flow. Replaces the former bell
   dropdown: each banner links straight to the target post / comment / mailbox
   card, and its “✕” button AJAX-purges the row (action=dismiss_notif). “ล้าง
   ทั้งหมด” still submits the clear_notifs POST form. Reuses the
   .rosy-notification-item / -avatar / -body / -text / -time atoms. Built in
   includes/header.php.
   ========================================================================== */
.rosy-notif-strip {
  overflow: hidden;
  border: 1px solid #FFD3DD;
  border-radius: 1.25rem;
  background: linear-gradient(135deg, #FFF7F9, #FFE8EE);
  box-shadow: 0 8px 22px -14px rgba(178, 90, 104, .45);
}
.rosy-notif-strip-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  border-bottom: 1px solid #FFD3DD;
  padding: .6rem 1rem;
}
.rosy-notif-strip-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: .35rem;
}

/* --- dark theme ---------------------------------------------------------- */
html.dark .rosy-notif-strip {
  border-color: #43354F;
  background: linear-gradient(135deg, #2A2338, #241F33);
  box-shadow: 0 20px 46px -26px rgba(0, 0, 0, .95);
}
html.dark .rosy-notif-strip-head { border-color: #43354F; }

/* --- small screens -------------------------------------------------------- */
@media (max-width: 640px) {
  /* Rows — slightly tighter rhythm and smaller avatars so the text keeps the
     room it needs on narrow screens. */
  .rosy-notification-item {
    gap: .55rem;
    padding: .5rem .55rem;
  }
  .rosy-notification-avatar {
    width: 2.15rem;
    height: 2.15rem;
  }

  /* Long handles / words wrap inside the card instead of overflowing it. */
  .rosy-notification-text {
    word-break: break-word;
    overflow-wrap: anywhere;
  }
}

/* ==========================================================================
   ☰ Floating menu panel (#rosy-mobile-menu) — palette selector + sign-out
   --------------------------------------------------------------------------
   The ☰ panel lives INSIDE the sticky header (see includes/header.php) as the
   last child of its `position: relative` container, absolutely positioned just
   below the top navigation bar. Because it belongs to the sticky header, it
   travels with the bar as the page scrolls: opening it while scrolled down
   still shows the panel pinned directly beneath the header, entirely inside the
   viewport and fully clickable — no need to scroll back to the top. As an
   absolutely-positioned popover it floats OVER the page content, so opening it
   never pushes the layout down. On phones it is a full-width floating card; on
   desktop a compact 320px popover anchored top-right. The member's palette
   switcher and its sign-out action render as soft pills matching the Sun/Moon
   control above them.
   ========================================================================== */
.rosy-drawer-palette select {
  /* Never let a long, translated palette name stretch the pill: the control is
     allowed to shrink and keeps its dropdown arrow clear of the right edge. */
  min-width: 0;
  max-width: 100%;
  text-overflow: ellipsis;
}

/* A soft rose tint that lifts the sign-out row away from the pastel links — a
   hand-written twin of the Tailwind utilities on the element, so it keeps its
   colour even while the CDN build is still being generated. */
.rosy-drawer-signout:hover { background-color: #FFF1F2; }                       /* rose-50     */
html.dark .rosy-drawer-signout { color: #FB7185; }                              /* rose-400    */
html.dark .rosy-drawer-signout:hover { background-color: rgba(76, 5, 25, .3); } /* rose-950/30 */

/* --- the floating panel itself -------------------------------------------
   A rounded pastel popover card anchored to the header's `position: relative`
   container. `top: calc(100% + 0.25rem)` places it just below the 4.3rem top
   bar (100% of the container's height) with a small breathing gap, and the
   `absolute` placement keeps it OUT of normal flow so it never shifts the page.
   Nothing here touches `display`, so the Tailwind `hidden` class app.js
   toggles is all that shows / hides it. */
#rosy-mobile-menu {
  position: absolute !important;
  top: calc(100% + 0.25rem) !important;
  z-index: 100 !important;
  padding: 1rem;
  background-color: var(--color-card-bg, #ffffff);
  border: 1px solid rgba(244, 208, 214, 0.8);
  border-radius: 1rem;
  box-shadow: 0 15px 30px -5px rgba(0, 0, 0, 0.12), 0 8px 10px -6px rgba(0, 0, 0, 0.05);
  /* The 3-line menu is a momentum-scrolling drawer: it scrolls vertically with
     native touch inertia and never swallows or delays a finger drag, so the
     list no longer feels "stiff" on a phone. No fixed pixel heights and no
     pointer-events tricks — the Tailwind `hidden` class is the only thing that
     shows / hides it. */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  overscroll-behavior: contain;
}

/* Dark-theme twin of the pastel panel. */
html.dark #rosy-mobile-menu {
  background-color: #241F33;
  border-color: #43354F;
  box-shadow: 0 15px 30px -5px rgba(0, 0, 0, 0.5), 0 8px 10px -6px rgba(0, 0, 0, 0.35);
}

/* --- 📱 Mobile (< 768px) — full-width floating card ----------------------
   The panel stretches from 1rem off the left edge to 1rem off the right edge of
   the header container, so it reads as a neat floating card over the content.
   A viewport-relative max-height keeps every row reachable on short screens. */
@media (max-width: 767px) {
  #rosy-mobile-menu {
    left: 1rem !important;
    right: 1rem !important;
    width: calc(100% - 2rem) !important;
    max-height: calc(100vh - 5rem) !important;
    max-height: calc(100dvh - 5rem) !important;
    overflow-y: auto !important;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-y;
  }
}

/* --- 🖥️ Desktop (>= 768px) — compact top-right popover -------------------
   A 320px card anchored under the header's top-right zone (the ☰ button). The
   max-height + overflow keep every row reachable on short laptop screens. */
@media (min-width: 768px) {
  #rosy-mobile-menu {
    right: 1.5rem !important;
    left: auto !important;
    width: 20rem !important; /* 320px compact width */
    max-height: calc(100vh - 6rem) !important;
    max-height: calc(100dvh - 6rem) !important;
    overflow-y: auto !important;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-y;
  }
}

/* --- neat, full-width stacked list ---------------------------------------
   Search, Language, the Theme toggle, the Palette selector, every Navigation
   link and Sign out render as comfortable full-width rows with clear icons and
   generous touch padding, so the whole list is easy to tap on a phone. */
#rosy-mobile-menu .rosy-drawer-body { display: grid; gap: .25rem; }
#rosy-mobile-menu .rosy-drawer-body > a {
  display: flex;
  width: 100%;
  align-items: center;
  gap: .5rem;
  padding: .65rem .8rem;
  border-radius: .75rem;
}


/* ==========================================================================
   🏷️ Composer category picker (inline panel)
   --------------------------------------------------------------------------
   The IN-FLOW room picker inside the post composer (markup in
   includes/composer.php — #composerCategoryPanel, behaviour in assets/js/app.js
   — the “composer category picker” module). It is a normal in-flow box inside
   the composer dock, so this file only carries: the scrollbar helper, the v4
   blur fallback, a small reveal animation and dark-theme parity.
   ========================================================================== */
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }

/* v4-only blur utility (v3 CDN has backdrop-blur-sm, not -xs). */
.backdrop-blur-xs {
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

/* Gentle reveal for the inline category panel when the trigger expands it. */
@keyframes rosy-cat-panel-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
#composerCategoryPanel:not(.hidden) { animation: rosy-cat-panel-in .18s ease-out both; }

/* Smooth reveal for the Edit-profile “Allowed viewers” row when the Profile
   privacy select is switched to จำกัด / Restricted (2). */
#allowedViewersContainer:not(.hidden) { animation: rosy-cat-panel-in .18s ease-out both; }

/* Dark-theme parity for the inline category panel surfaces (the trigger button
   already reads well on the pink-50 tool strip). */
html.dark #composerCategoryPanel { background: #2B2338; border-color: #43354F; }
html.dark #composerCategoryPanel .cat-accordion,
html.dark #composerCategoryPanel .cat-select-btn { background: #241F33; border-color: #43354F; }
html.dark #composerCategoryPanel .cat-accordion-toggle,
html.dark #composerCategoryPanel .cat-select-btn { color: #E7D9F5; }
html.dark #composerCategoryPanel .cat-accordion-toggle:hover,
html.dark #composerCategoryPanel .cat-select-btn:hover { background: #33263E; }
html.dark #composerCategoryPanel .cat-select-btn.bg-white { background: #241F33; }

/* ==========================================================================
   ⚡ Instant navigation (SPA-like soft page swaps)
   --------------------------------------------------------------------------
   Internal link clicks are fetched and the <main id="main-content"> wrapper is
   swapped in place (see the "instant navigation" module in assets/js/app.js).
   The rules below (a) draw a slim pastel progress bar while a swap is loading
   and (b) fade the freshly swapped content in, so the previous page never
   blinks through as a white flash between navigations.
   ========================================================================== */
.rosy-nav-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 0;
  z-index: 80;
  border-radius: 0 999px 999px 0;
  background: linear-gradient(90deg, var(--rosy-accent-soft, #FFB6C1), var(--rosy-accent, #F472B6));
  box-shadow: 0 0 8px rgba(244, 114, 182, .55);
  opacity: 0;
  pointer-events: none;
  transition: width .4s ease, opacity .25s ease;
}

.rosy-nav-bar.is-loading {
  width: 82%;
  opacity: 1;
}

/* While a swap is in flight the page shows a gentle "busy" affordance. */
html.rosy-nav-loading { cursor: progress; }

/* Fade-in for freshly swapped page content — eliminates the white flash.
   NOTE: the fill mode is `backwards`, never `forwards`/`both`: a retained
   `transform` on #main-content would turn it into the containing block of every
   `position: fixed` descendant (the bottom sheet, the modals, …) and pin them to
   the document instead of the viewport. The keyframes already end on the element's
   natural state (opacity 1 · no transform), so nothing has to be retained. */
#main-content.rosy-swap-in {
  animation: rosy-swap-fade .28s ease-out backwards;
}

@keyframes rosy-swap-fade {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  #main-content.rosy-swap-in { animation: none; }
  .rosy-nav-bar { transition: opacity .25s ease; }
}

/* ==========================================================================
   🎠 Hybrid story carousel + 🧁 sub-room accordion (homepage Sections 1 & 3)
   --------------------------------------------------------------------------
   Dark-mode-aware components. The carousel is a horizontal snap-scroll rail;
   the accordion animates height + opacity through the .is-open class toggled by
   app.js (see the “sub-room accordion” module in assets/js/app.js).
   ========================================================================== */

/* ---- Section 1 · horizontal story rail ---------------------------------- */
.rosy-story-rail {
  display: flex;
  gap: 0.9rem;
  overflow-x: auto;
  padding: 0.35rem 0.25rem 0.9rem;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.rosy-story-rail::-webkit-scrollbar { height: 8px; }
.rosy-story-rail::-webkit-scrollbar-track { background: var(--rosy-scroll-track, #FFF5F7); border-radius: 999px; }
.rosy-story-rail::-webkit-scrollbar-thumb { background: var(--rosy-scroll-thumb, #FFD3DD); border-radius: 999px; }

/* Hide the scrollbar entirely while keeping the element scrollable and smooth
   to drag. Applied to the compact homepage Section 1 member rail (and the
   sub-room breadcrumb) so no horizontal scrollbar line ever shows while touch /
   mouse drag scrolling keeps working. */
.scrollbar-none {
  -ms-overflow-style: none;           /* IE / old Edge */
  scrollbar-width: none;              /* Firefox */
  -webkit-overflow-scrolling: touch;  /* momentum scrolling on iOS */
  overscroll-behavior-inline: contain;
}
.scrollbar-none::-webkit-scrollbar { display: none; }  /* Chrome / Safari / new Edge */

.rosy-story-card {
  scroll-snap-align: start;
  flex: 0 0 auto;
  width: 12.5rem;
  min-height: 11rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  border: 1px solid #FFE8EE;
  border-radius: 1.5rem;
  background: #ffffff;
  padding: 1rem;
  box-shadow: 0 18px 40px -30px rgba(255, 154, 162, .8);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.rosy-story-card:hover { transform: translateY(-2px); border-color: #FFD3DD; }

.rosy-story-emoji { font-size: 1.9rem; line-height: 1; }
.rosy-story-title { font-size: 0.92rem; font-weight: 600; color: #B03A54; }
.rosy-story-sub   { font-size: 0.78rem; line-height: 1.4; color: rgba(74, 63, 63, .62); }

.rosy-story-card--cta {
  background: linear-gradient(160deg, #FFE8EE 0%, #FFD3DD 100%);
  color: #7A3B45;
  justify-content: center;
}
.rosy-story-btn {
  margin-top: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: linear-gradient(90deg, #FFB6C1, #E88493);
  color: #fff;
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0.55rem 0.9rem;
  box-shadow: 0 12px 24px -14px rgba(217, 94, 111, .9);
}

.rosy-story-card--prompt { background: linear-gradient(160deg, #FFF9F5 0%, #FFF1F5 100%); }
.rosy-story-chip {
  margin-top: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  align-self: flex-start;
  border-radius: 999px;
  border: 1px solid #FFD3DD;
  background: #fff;
  color: #B03A54;
  font-weight: 600;
  font-size: 0.82rem;
  padding: 0.5rem 0.85rem;
  cursor: pointer;
  transition: background .18s ease, transform .18s ease;
}
.rosy-story-chip:hover { background: #FFE8EE; transform: translateY(-1px); }

.rosy-story-card--postcard { padding: 0.6rem; width: 11.5rem; }
.rosy-story-art {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 1rem;
  background: #FFF1F5;
}
.rosy-story-card--postcard .rosy-story-sub { padding: 0 0.25rem; }

.rosy-story-card--members { width: 15rem; }
.rosy-story-avatars { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: auto; }
.rosy-story-avatar {
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 0.7rem;
  object-fit: cover;
  box-shadow: 0 0 0 1px #FFE8EE;
}

/* ---- dark-mode parity · carousel ---------------------------------------- */
html.dark .rosy-story-card { background: #241F33; border-color: #43354F; box-shadow: 0 18px 40px -30px rgba(0, 0, 0, .7); }
html.dark .rosy-story-card--cta { background: linear-gradient(160deg, #3A2B44 0%, #43354F 100%); color: #FFC9D4; }
html.dark .rosy-story-card--prompt { background: linear-gradient(160deg, #2B2338 0%, #33263E 100%); }
html.dark .rosy-story-title { color: #FFC9D4; }
html.dark .rosy-story-sub { color: rgba(245, 235, 240, .62); }
html.dark .rosy-story-chip { background: #2B2338; border-color: #43354F; color: #FFC9D4; }
html.dark .rosy-story-chip:hover { background: #43354F; }
html.dark .rosy-story-art { background: #2B2338; }
html.dark .rosy-story-avatar { box-shadow: 0 0 0 1px #43354F; }

/* ---- Section 3 · category accordion ------------------------------------- */
.rosy-acc {
  border: 1px solid #FFE8EE;
  border-radius: 1.25rem;
  background: #ffffff;
  overflow: hidden;
  box-shadow: 0 14px 34px -30px rgba(255, 154, 162, .8);
  transition: border-color .2s ease, box-shadow .2s ease;
}
.rosy-acc.is-open { border-color: #FFD3DD; box-shadow: 0 20px 44px -30px rgba(217, 94, 111, .7); }

.rosy-acc-head {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  background: transparent;
  cursor: pointer;
  text-align: left;
}
.rosy-acc-chevron {
  margin-left: auto;
  color: #E88493;
  font-size: 1rem;
  transition: transform .3s ease;
}
.rosy-acc.is-open .rosy-acc-chevron { transform: rotate(180deg); }

/* Smooth height + opacity reveal — only the open category stays visible. */
.rosy-acc-panel {
  max-height: 0;
  opacity: 0;
  visibility: hidden;
  overflow: hidden;
  transition: max-height .38s cubic-bezier(.4, 0, .2, 1), opacity .28s ease, visibility 0s linear .38s;
}
.rosy-acc.is-open .rosy-acc-panel {
  max-height: 60rem;
  opacity: 1;
  visibility: visible;
  transition: max-height .42s cubic-bezier(.4, 0, .2, 1), opacity .3s ease;
}
.rosy-acc-panel-inner {
  padding: 0 1rem 1rem;
  border-top: 1px dashed #FFE8EE;
}

.rosy-subroom-link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  border-radius: 1rem;
  border: 1px solid #FFE8EE;
  background: #FFF9F5;
  padding: 0.6rem 0.75rem;
  transition: background .18s ease, border-color .18s ease, transform .18s ease;
}
.rosy-subroom-link:hover { background: #FFE8EE; border-color: #FFD3DD; transform: translateY(-1px); }
.rosy-subroom-link--all { background: linear-gradient(120deg, #FFF1F5, #FFE8EE); border-color: #FFD3DD; }
.rosy-subroom-emoji { font-size: 1.15rem; line-height: 1; }
.rosy-subroom-desc {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.72rem;
  line-height: 1.35;
  color: rgba(74, 63, 63, .55);
}

/* ---- Section 3 · pill-style sub-room chips ------------------------------ */
/* The expanded sub-rooms render as sleek, clickable pills: a soft round emoji
   badge, the sub-room name and a one-line description, laid out in a wrapping
   flex row so they stay tidy on both the mobile accordion and the desktop
   sticky sidebar. */
.rosy-subroom-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.rosy-subroom-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  max-width: 100%;
  border-radius: 9999px;
  border: 1px solid #FFE8EE;
  background: #FFF9F5;
  padding: 0.4rem 0.9rem 0.4rem 0.5rem;
  transition: background .18s ease, border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.rosy-subroom-chip:hover {
  background: #FFE8EE;
  border-color: #FFD3DD;
  transform: translateY(-1px);
  box-shadow: 0 12px 22px -16px rgba(217, 94, 111, .85);
}
.rosy-subroom-chip:active { transform: translateY(0); }

.rosy-subroom-chip-emoji {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 9999px;
  background: #FFF1F5;
  font-size: 1rem;
  line-height: 1;
}
.rosy-subroom-chip-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-width: 13rem;
}
.rosy-subroom-chip-name {
  overflow: hidden;
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.25;
  color: rgba(74, 63, 63, .92);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rosy-subroom-chip-desc {
  overflow: hidden;
  font-size: 0.68rem;
  line-height: 1.3;
  color: rgba(74, 63, 63, .5);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rosy-subroom-chip--all {
  background: linear-gradient(120deg, #FFF1F5, #FFE8EE);
  border-color: #FFD3DD;
}

/* ---- dark-mode parity · accordion --------------------------------------- */
html.dark .rosy-acc { background: #241F33; border-color: #43354F; }
html.dark .rosy-acc.is-open { border-color: #5A4668; }
html.dark .rosy-acc-panel-inner { border-top-color: #43354F; }
html.dark .rosy-subroom-link { background: #2B2338; border-color: #43354F; }
html.dark .rosy-subroom-link:hover { background: #43354F; border-color: #5A4668; }
html.dark .rosy-subroom-link--all { background: linear-gradient(120deg, #33263E, #3A2B44); border-color: #5A4668; }
html.dark .rosy-subroom-desc { color: rgba(245, 235, 240, .55); }
html.dark .rosy-subroom-chip { background: #2B2338; border-color: #43354F; }
html.dark .rosy-subroom-chip:hover { background: #43354F; border-color: #5A4668; }
html.dark .rosy-subroom-chip-emoji { background: #33263E; }
html.dark .rosy-subroom-chip-name { color: rgba(245, 235, 240, .92); }
html.dark .rosy-subroom-chip-desc { color: rgba(245, 235, 240, .5); }
html.dark .rosy-subroom-chip--all { background: linear-gradient(120deg, #33263E, #3A2B44); border-color: #5A4668; }

@media (prefers-reduced-motion: reduce) {
  .rosy-acc-panel,
  .rosy-acc-chevron,
  .rosy-story-card,
  .rosy-subroom-chip { transition: none; }
}
/* ==========================================================================
   📱 Custom PWA install banner (assets/js/pwa-install.js)
   --------------------------------------------------------------------------
   A cross-platform “ติดตั้ง” pill rendered by includes/footer.php. It never
   relies on Chrome's automated mini-infobar: the script reveals it only when a
   native install is available (beforeinstallprompt) or when an iOS Safari
   visitor can still “Add to Home Screen”. It starts `hidden` and the
   `!important` guard below — mirroring the [data-modal].hidden rule — makes
   sure no Tailwind utility-order quirk can flash it before the script decides.
   ========================================================================== */
.rosy-pwa-banner.hidden { display: none !important; }

/* ==========================================================================
   🎮 AI Life-Sim Game Engine — components
   --------------------------------------------------------------------------
   Every value below resolves from the SAME theme custom properties the rest of
   the site uses (var(--rosy-accent), var(--rosy-bg), var(--color-text), …), so
   play_game.php, game_builder.php and the admin tools inherit the exact font,
   palette and dark/light mode automatically — for 100% visual consistency.
   ========================================================================== */

/* Typography lock-in: INHERIT the site font family & size (set on <body> by the
   theme/typography layer) so the game is pixel-consistent with every page. */
.rosy-game-shell {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  color: var(--color-text, #4A3F3F);
}

/* --- progress bar (ช่วงวัย · ความก้าวหน้า % ) ------------------------------ */
.rosy-game-bar {
  position: relative;
  display: block;
  width: 100%;
  height: .6rem;
  overflow: hidden;
  border-radius: 999px;
  background: var(--rosy-glow-2, #FFD3DD);
}

.rosy-game-bar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background-image: linear-gradient(to right, var(--rosy-accent-soft, #FFB6C1), var(--rosy-accent, #FF9AA2));
  transition: width .35s ease;
}

/* --- choice cards --------------------------------------------------------- */
.rosy-game-card {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  width: 100%;
  padding: .75rem .875rem;
  text-align: left;
  font-family: inherit;
  font-size: var(--font-size-base, .95rem);
  line-height: 1.5;
  color: var(--color-text, #4A3F3F);
  background: var(--rosy-bg, #FFF9F5);
  border: 1px solid var(--rosy-glow-2, #FFD3DD);
  border-radius: 1rem;
  box-shadow: 0 1px 2px rgba(217, 94, 111, .12);
  cursor: pointer;
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}

.rosy-game-card:hover:not(:disabled) {
  transform: translateY(-2px);
  border-color: var(--rosy-accent, #FF9AA2);
  box-shadow: 0 10px 24px -14px var(--rosy-accent-deep, #D95E6F);
}

.rosy-game-card:disabled { cursor: progress; opacity: .6; transform: none; }

.rosy-game-badge {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 700;
  color: var(--rosy-accent-deep, #D95E6F);
  background: var(--rosy-selection-bg, #FFD3DD);
}

/* --- memory drawer -------------------------------------------------------- */
.rosy-game-drawer { background: var(--rosy-bg, #FFF9F5); }
.rosy-game-chip {
  display: inline-flex;
  align-items: center;
  padding: .15rem .6rem;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 500;
  color: var(--rosy-accent-deep, #D95E6F);
  background: var(--rosy-glow-1, #FFE8EE);
}

/* --- dark-mode parity ----------------------------------------------------- */
html.dark .rosy-game-shell { color: rgba(245, 235, 240, .92); }
html.dark .rosy-game-card {
  background: #2B2338;
  border-color: #43354F;
  color: rgba(245, 235, 240, .92);
}
html.dark .rosy-game-card:hover:not(:disabled) { border-color: #6B5480; }
html.dark .rosy-game-badge { color: #FFB0BE; background: #43354F; }
html.dark .rosy-game-bar { background: #43354F; }
html.dark .rosy-game-drawer { background: #241F33; }
html.dark .rosy-game-chip { color: #FFB0BE; background: #33263E; }

@media (prefers-reduced-motion: reduce) {
  .rosy-game-bar-fill,
  .rosy-game-card { transition: none; }
}


/* Keep the pill clear of the phone's home indicator. */
.rosy-pwa-banner {
  padding-bottom: calc(0.625rem + env(safe-area-inset-bottom));
}

/* Pastel dark-mode parity for the pill (the bar sits above the page). */
html.dark .rosy-pwa-banner {
  background-color: rgba(36, 31, 51, .97) !important;
  border-color: #43354F;
}
html.dark .rosy-pwa-banner .rosy-pwa-banner-title { color: #F3EBF8; }
html.dark .rosy-pwa-banner #pwa-install-close { color: rgba(245, 235, 240, .55); }
html.dark .rosy-pwa-banner #pwa-install-close:hover { background: #33263E; }

/* The iOS guidance modal reuses the shared [data-modal] shell; this only names
   the two numbered step badges so they survive dark mode. */
.rosy-pwa-step-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.85rem;
  font-size: 1.05rem;
  background: #FFF5F7;
}
html.dark .rosy-pwa-step-icon { background: #33263E; }

/* ==========================================================================
   📱 Mobile room selector — compact pill bar (.rosy-roombar) + the modern
   TWO-COLUMN category BOTTOM SHEET (.rosy-sheet)
   --------------------------------------------------------------------------
   Replaces the tall vertical category accordion on phones / tablets ONLY: the
   markup is `lg:hidden` in index.php, so from `lg` the desktop LEFT sidebar tree
   stays the one and only navigation surface — it is never touched. The pill bar
   is a sleek, sticky blush capsule showing the active room; tapping its
   “📂 เลือกห้อง ▾” button slides the bottom sheet up from the viewport bottom.
   `transform: translateY(100% → 0)` gives the silky motion, the soft dark
   backdrop blurs the page behind it, the LEFT column lists the main categories
   and the RIGHT column shows the selected category's sub-rooms (swapped
   instantly by app.js → “mobile room bottom sheet”). Dark-mode aware.
   ========================================================================== */
.rosy-roombar {
  position: sticky;
  top: 4.3rem;                  /* sits right under the 4.3rem sticky header */
  z-index: 30;
  display: flex;
  align-items: stretch;
  gap: .5rem;
  margin-bottom: 1rem;
}

/* ---- the active-room pill ----------------------------------------------- */
.rosy-roombar-pill {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-width: 0;
  flex: 1;
  padding: .6rem .95rem;
  border: 1px solid #FFE8EE;
  border-radius: 9999px;
  background: linear-gradient(120deg, #FFF6F9, #FFE8EE);
  color: #B03A54;
  box-shadow: 0 14px 30px -26px rgba(217, 94, 111, .9);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.rosy-roombar-emoji { flex-shrink: 0; font-size: .95rem; line-height: 1; }
.rosy-roombar-text {
  display: flex;
  align-items: baseline;
  gap: .35rem;
  min-width: 0;
  flex: 1;
  font-size: .85rem;
  font-weight: 600;
}
.rosy-roombar-kicker { flex-shrink: 0; color: #E88493; }
.rosy-roombar-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- the tap-friendly trigger button ------------------------------------ */
.rosy-roombar-btn {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  flex-shrink: 0;
  padding: .6rem .95rem;
  border: 1px solid #FFD3DD;
  border-radius: 9999px;
  background: linear-gradient(120deg, #FF9FB4, #E8607F);
  color: #FFFFFF;
  font-size: .82rem;
  font-weight: 700;
  white-space: nowrap;
  box-shadow: 0 16px 30px -20px rgba(217, 94, 111, .95);
  transition: filter .18s ease, transform .18s ease;
}
.rosy-roombar-btn:hover { filter: brightness(1.05); }
.rosy-roombar-btn:active { transform: scale(.97); }
.rosy-roombar-caret { font-size: .75rem; line-height: 1; }

/* ---- the bottom-sheet shell ---------------------------------------------
   Fixed to the viewport bottom and hidden until [data-roomsheet] carries
   `.is-open` (toggled by app.js). The panel rides up from `translateY(100%)`
   while the backdrop fades in with a soft dark tint + 6px blur. */
.rosy-sheet {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  pointer-events: none;
}
.rosy-sheet-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(58, 30, 40, .38);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity .28s ease;
}
.rosy-sheet-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 34rem;             /* never a full desktop-width slab */
  max-height: min(82vh, 40rem);
  padding-top: .4rem;
  border: 1px solid #FFE8EE;
  border-bottom: 0;
  border-radius: 1.6rem 1.6rem 0 0;
  background: #FFFFFF;
  box-shadow: 0 -20px 50px -18px rgba(217, 94, 111, .5);
  transform: translateY(100%);
  transition: transform .34s cubic-bezier(.32, .72, 0, 1);
  will-change: transform;
}
.rosy-sheet.is-open { pointer-events: auto; }
.rosy-sheet.is-open .rosy-sheet-backdrop { opacity: 1; }
.rosy-sheet.is-open .rosy-sheet-panel { transform: translateY(0); }

/* ---- sheet chrome · grab handle, title bar, ✕ --------------------------- */
.rosy-sheet-grab {
  width: 2.6rem;
  height: .28rem;
  flex-shrink: 0;
  margin: 0 auto .35rem;
  border-radius: 9999px;
  background: #FFD3DD;
}
.rosy-sheet-head {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-shrink: 0;
  padding: 0 .95rem .6rem;
  border-bottom: 1px solid #FFF1F5;
}
.rosy-sheet-title {
  min-width: 0;
  flex: 1;
  font-size: 1rem;
  font-weight: 700;
  color: #B03A54;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rosy-sheet-all {
  flex-shrink: 0;
  padding: .3rem .7rem;
  border: 1px solid #FFD3DD;
  border-radius: 9999px;
  background: #FFF6F9;
  color: #DB2777;
  font-size: .75rem;
  font-weight: 600;
  white-space: nowrap;
  transition: background .18s ease;
}
.rosy-sheet-all:hover { background: #FFE8EE; }
.rosy-sheet-close {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 9999px;
  background: #FFF1F5;
  color: #B03A54;
  font-size: .95rem;
  line-height: 1;
  transition: background .18s ease;
}
.rosy-sheet-close:hover { background: #FFE8EE; }

/* ---- body · the 2-column split (LEFT ≈45% · RIGHT ≈55%) ------------------ */
.rosy-sheet-body {
  display: flex;
  flex: 1;
  gap: .5rem;
  min-height: 0;
  padding: .7rem .7rem calc(.7rem + env(safe-area-inset-bottom, 0px));
}
.rosy-sheet-col {
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.rosy-sheet-cats { flex: 0 0 44%; max-width: 45%; }
.rosy-sheet-subs { flex: 1 1 56%; min-width: 0; }
/* ---- LEFT column · the scrollable main-category list -------------------- */
.rosy-sheet-cat {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: .7rem .75rem;
  border: 0;
  border-radius: .9rem;
  background: transparent;
  color: #4A3F3F;
  font-size: .85rem;
  font-weight: 600;
  text-align: left;
  transition: background .16s ease, color .16s ease;
}
.rosy-sheet-cat + .rosy-sheet-cat { margin-top: .2rem; }
.rosy-sheet-cat:hover { background: #FFF6F9; }
.rosy-sheet-cat.is-active {
  background: linear-gradient(120deg, #FFE8EE, #FFD3DD);
  color: #B03A54;
  font-weight: 700;
}
.rosy-sheet-cat-emoji { flex-shrink: 0; font-size: 1.05rem; line-height: 1; }
.rosy-sheet-cat-name {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rosy-sheet-cat-count {
  flex-shrink: 0;
  font-size: .68rem;
  font-weight: 700;
  color: #E88493;
}
.rosy-sheet-cat.is-active .rosy-sheet-cat-count { color: #B03A54; }

/* ---- RIGHT column · the sub-rooms of the active main category -----------
   Only the panel of the highlighted LEFT category is visible; app.js flips
   `.is-active` the instant a main category is tapped. */
.rosy-sheet-panelbox { display: none; }
.rosy-sheet-panelbox.is-active { display: block; }
.rosy-sheet-link {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: .65rem .7rem;
  border: 1px solid transparent;
  border-radius: .9rem;
  color: rgba(74, 63, 63, .88);
  font-size: .83rem;
  font-weight: 500;
  transition: background .16s ease, border-color .16s ease, color .16s ease;
}
.rosy-sheet-link + .rosy-sheet-link { margin-top: .2rem; }
.rosy-sheet-link:hover { background: #FFF6F9; border-color: #FFE8EE; }
.rosy-sheet-link.is-current {
  background: #FFF1F5;
  border-color: #FFD3DD;
  color: #B03A54;
  font-weight: 700;
}
.rosy-sheet-link-emoji { flex-shrink: 0; font-size: 1rem; line-height: 1; }
.rosy-sheet-link-name {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rosy-sheet-link-count {
  flex-shrink: 0;
  font-size: .68rem;
  font-weight: 700;
  line-height: 1.25;
  color: #DB2777;
  background: rgba(252, 231, 243, .85);
  border: 1px solid rgba(251, 207, 232, .6);
  border-radius: 9999px;
  padding: .1rem .5rem;
}
.rosy-sheet-empty {
  padding: .7rem .75rem;
  font-size: .78rem;
  color: rgba(74, 63, 63, .55);
}
/* ---- RIGHT column · the single “all” action card -------------------------
   The default panel no longer re-lists every main category (they already own
   the LEFT column); instead it shows ONE full-width card that reads the
   combined feed. Tapping it is a plain <a>, so app.js closes the sheet. */
.rosy-sheet-action {
  display: flex;
  align-items: center;
  gap: .75rem;
  width: 100%;
  padding: .95rem 1rem;
  border: 1px solid #FFD3DD;
  border-radius: 1rem;
  background: linear-gradient(120deg, #FFF6F9, #FFE8EE);
  color: #B03A54;
  transition: transform .16s ease, background .16s ease, border-color .16s ease;
}
.rosy-sheet-action:hover {
  background: linear-gradient(120deg, #FFE8EE, #FFD3DD);
  border-color: #FFC9D4;
}
.rosy-sheet-action:active { transform: scale(.98); }
.rosy-sheet-action-text {
  display: flex;
  flex-direction: column;
  gap: .15rem;
  min-width: 0;
  flex: 1;
}
.rosy-sheet-action-title { font-size: .9rem; font-weight: 700; line-height: 1.25; }
.rosy-sheet-action-desc {
  font-size: .75rem;
  font-weight: 500;
  line-height: 1.35;
  color: rgba(74, 63, 63, .7);
}
.rosy-sheet-action-arrow { flex-shrink: 0; font-size: 1.1rem; line-height: 1; }
/* ---- desktop guard -------------------------------------------------------
   From `lg` the LEFT sidebar tree owns navigation; the pill bar + the bottom
   sheet are hidden exactly like the old deck was. The `lg:hidden` utility in
   index.php does the same, so this stays correct even before the Tailwind CDN
   build has been generated. */
@media (min-width: 1024px) {
  .rosy-roombar,
  .rosy-sheet { display: none; }
}

/* ---- dark-mode parity --------------------------------------------------- */
html.dark .rosy-roombar-pill {
  background: linear-gradient(120deg, #2B2338, #33263E);
  border-color: #43354F;
  color: #FFC9D4;
}
html.dark .rosy-sheet-panel { background: #241F33; border-color: #43354F; }
html.dark .rosy-sheet-grab { background: #5A4668; }
html.dark .rosy-sheet-head { border-bottom-color: #33263E; }
html.dark .rosy-sheet-title { color: #FFC9D4; }
html.dark .rosy-sheet-all {
  background: #33263E;
  border-color: #43354F;
  color: #FFC9D4;
}
html.dark .rosy-sheet-all:hover { background: #3A2B44; }
html.dark .rosy-sheet-close { background: #33263E; color: #FFC9D4; }
html.dark .rosy-sheet-close:hover { background: #3A2B44; }
html.dark .rosy-sheet-cat { color: rgba(245, 235, 240, .85); }
html.dark .rosy-sheet-cat:hover { background: #2B2338; }
html.dark .rosy-sheet-cat.is-active {
  background: linear-gradient(120deg, #33263E, #3A2B44);
  color: #FFC9D4;
}
html.dark .rosy-sheet-cat-count { color: #E88493; }
html.dark .rosy-sheet-link { color: rgba(245, 235, 240, .78); }
html.dark .rosy-sheet-link:hover { background: #2B2338; border-color: #43354F; }
html.dark .rosy-sheet-link.is-current { background: #33263E; border-color: #43354F; color: #FFC9D4; }
html.dark .rosy-sheet-link-count {
  color: #FFC9D4;
  background: rgba(219, 39, 119, .18);
  border-color: rgba(219, 39, 119, .32);
}
html.dark .rosy-sheet-empty { color: rgba(245, 235, 240, .55); }
html.dark .rosy-sheet-action {
  background: linear-gradient(120deg, #33263E, #3A2B44);
  border-color: #43354F;
  color: #FFC9D4;
}
html.dark .rosy-sheet-action:hover {
  background: linear-gradient(120deg, #3A2B44, #43354F);
  border-color: #5A4668;
}
html.dark .rosy-sheet-action-desc { color: rgba(245, 235, 240, .7); }

@media (prefers-reduced-motion: reduce) {
  .rosy-sheet-backdrop,
  .rosy-sheet-panel,
  .rosy-roombar-btn { transition: none; }
}

/* ==========================================================================
   📱 Story Circles — the mobile feature-shortcut rail (.rosy-circles)
   --------------------------------------------------------------------------
   A scroll-snapping row of circular feature shortcuts (Story-highlight style: a
   gradient ring around a soft pastel disc, caption underneath) rendered right
   below the header on phones / tablets ONLY (`lg:hidden` in index.php — from `lg`
   the header nav owns these links). Native touch inertia, no visible scrollbar,
   full dark-mode parity.
   ========================================================================== */
.rosy-circles {
  display: flex;
  align-items: flex-start;
  gap: .85rem;
  margin: 0 0 1rem;
  padding: .15rem .1rem .35rem;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.rosy-circle {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  width: 4.5rem;
  scroll-snap-align: start;
  text-align: center;
  text-decoration: none;
}
/* The gradient ring (Instagram / Lemon8 highlight) — a 2px frame around the disc. */
.rosy-circle-ring {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  padding: 2px;
  border-radius: 9999px;
  background: linear-gradient(135deg, #FFC2D1, #FF9FB4, #F6A5C0);
  box-shadow: 0 10px 22px -16px rgba(217, 94, 111, .95);
  transition: transform .18s ease;
}
.rosy-circle-ring--novels { background: linear-gradient(135deg, #FFE0B2, #FFB37B); }
.rosy-circle-ring--tarot  { background: linear-gradient(135deg, #D9C2FF, #B79CE8); }
/* ⭐ ดวง 12 ราศี — celestial gold → violet, distinct from the 🎴 tarot ring. */
.rosy-circle-ring--horoscope { background: linear-gradient(135deg, #FFE9A8, #C9A6F2); }
.rosy-circle-ring--quiz   { background: linear-gradient(135deg, #BFE3FF, #8FC3F2); }
/* ⚙️ Ring colours driven by feature_registry() in includes/functions.php. */
.rosy-circle-ring--postcard { background: linear-gradient(135deg, #FFD1E8, #F58CB8); }
.rosy-circle-ring--games    { background: linear-gradient(135deg, #FFC2D1, #FF9FB4, #F6A5C0); }
/* 🤖 แชต AI — magenta → soft rose, distinct from every other ring. */
.rosy-circle-ring--chat     { background: linear-gradient(135deg, #E8609F, #F7A8C6, #FFD1E8); }
.rosy-circle-emoji {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  border-radius: 9999px;
  background: #FFF6F9;
  font-size: 1.4rem;
  line-height: 1;
}
.rosy-circle-label {
  max-width: 100%;
  font-size: .67rem;
  font-weight: 600;
  line-height: 1.15;
  color: #B03A54;
  overflow-wrap: break-word;
}
.rosy-circle:hover .rosy-circle-ring { transform: translateY(-2px); }
.rosy-circle:active .rosy-circle-ring { transform: scale(.95); }
.rosy-circle:focus-visible .rosy-circle-ring { box-shadow: 0 0 0 3px rgba(255, 159, 180, .55); }

/* ---- desktop guard — the header nav owns these features from `lg` -------- */
@media (min-width: 1024px) {
  .rosy-circles { display: none; }
}

/* ---- dark-mode parity --------------------------------------------------- */
html.dark .rosy-circle-emoji { background: #2B2338; }
html.dark .rosy-circle-label { color: #FFC9D4; }
html.dark .rosy-circle-ring--novels { background: linear-gradient(135deg, #6B4B2E, #C08A5E); }
html.dark .rosy-circle-ring--tarot  { background: linear-gradient(135deg, #3A2B44, #7E63B6); }
html.dark .rosy-circle-ring--horoscope { background: linear-gradient(135deg, #4A3B1E, #6E4FA8); }
html.dark .rosy-circle-ring--quiz   { background: linear-gradient(135deg, #24384A, #4A6E8A); }
html.dark .rosy-circle-ring--postcard { background: linear-gradient(135deg, #4A2138, #B2548A); }
html.dark .rosy-circle-ring--games    { background: linear-gradient(135deg, #4A2138, #C56B7E); }
html.dark .rosy-circle-ring--chat     { background: linear-gradient(135deg, #4A1030, #A64D82); }

/* ==========================================================================
   📱 Mobile quick composer — the compact capsule (.rosy-quickbar) + the
   collapsed / expanded composer card
   --------------------------------------------------------------------------
   Below `lg` the tall composer card is replaced by ONE capsule row and the card
   itself collapses through max-height / opacity / visibility (the same technique
   as the sub-room accordion), expanding while it carries `.is-open` — toggled by
   app.js (see the “mobile quick composer” module).

   NOTHING here may create a containing block for a `position: fixed` descendant:
   the card deliberately keeps `transform: none` (no transform / filter /
   will-change), so the ✨ #aiWriterModal overlay nested inside it still covers
   the whole viewport exactly as it always has.

   From `lg` every rule in this block is inert — the classic in-flow card and the
   desktop rhythm stay 100% untouched.
   ========================================================================== */
@media (max-width: 1023.98px) {
  /* ---- the capsule ------------------------------------------------------- */
  .rosy-quickbar {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 0 0 1rem;
    padding: .35rem .35rem .35rem .5rem;
    border: 1px solid #FFE8EE;
    border-radius: 9999px;
    background: #FFFFFF;
    box-shadow: 0 16px 34px -28px rgba(217, 94, 111, .95);
  }
  /* The capsule steps aside while the composer is expanded. */
  .rosy-quickbar.is-hidden { display: none; }
  .rosy-quickbar-open {
    display: flex;
    align-items: center;
    gap: .6rem;
    min-width: 0;
    flex: 1;
    height: 2.6rem;
    padding: 0;
    border: 0;
    background: transparent;
    text-align: left;
    cursor: pointer;
  }
  .rosy-quickbar-avatar {
    flex-shrink: 0;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 9999px;
    object-fit: cover;
    box-shadow: 0 0 0 2px #FFE8EE;
  }
  .rosy-quickbar-text {
    min-width: 0;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: .85rem;
    font-weight: 500;
    color: rgba(74, 63, 63, .55);
  }
  .rosy-quickbar-actions { display: flex; align-items: center; gap: .3rem; flex-shrink: 0; }
  .rosy-quickbar-icon {
    display: grid;
    place-items: center;
    width: 2.3rem;
    height: 2.3rem;
    border: 1px solid #FFE8EE;
    border-radius: 9999px;
    background: #FFF6F9;
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
    transition: background .18s ease, transform .18s ease;
  }
  .rosy-quickbar-icon:hover { background: #FFE8EE; }
  .rosy-quickbar-icon:active { transform: scale(.94); }

  /* ---- the collapsed card (expands with .is-open) ------------------------ */
  #composerModal.composer-card {
    max-height: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
    border-width: 0 !important;
    border-radius: 1.5rem;
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    transition: max-height .42s cubic-bezier(.4, 0, .2, 1), opacity .26s ease, visibility 0s linear .42s;
  }
  #composerModal.composer-card.is-open {
    max-height: 400rem !important;
    margin: 0 0 1.5rem !important;
    padding: 1rem !important;
    border-width: 1px !important;
    opacity: 1;
    visibility: visible;
    transition: max-height .46s cubic-bezier(.4, 0, .2, 1), opacity .3s ease;
  }
  /* The collapsed card contributes no rhythm: the post below it hugs the capsule. */
  #composerModal.composer-card + #feed { margin-top: 0; }

  /* ---- ✕ ยกเลิก (mobile-only; lives in the composer header row) ---------- */
  .rosy-composer-cancel {
    flex-shrink: 0;
    margin-left: auto;
    padding: .4rem .8rem;
    border: 1px solid #FFD3DD;
    border-radius: 9999px;
    background: #FFF6F9;
    color: #B03A54;
    font-size: .75rem;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
    transition: background .18s ease;
  }
  .rosy-composer-cancel:hover { background: #FFE8EE; }
}

/* ---- dark-mode parity · mobile quick composer --------------------------- */
@media (max-width: 1023.98px) {
  html.dark .rosy-quickbar { background: #241F33; border-color: #43354F; }
  html.dark .rosy-quickbar-text { color: rgba(245, 235, 240, .6); }
  html.dark .rosy-quickbar-avatar { box-shadow: 0 0 0 2px #43354F; }
  html.dark .rosy-quickbar-icon { background: #33263E; border-color: #43354F; }
  html.dark .rosy-quickbar-icon:hover { background: #3A2B44; }
  html.dark .rosy-composer-cancel { background: #33263E; border-color: #43354F; color: #FFC9D4; }
  html.dark .rosy-composer-cancel:hover { background: #3A2B44; }
}

@media (prefers-reduced-motion: reduce) {
  .rosy-circle-ring,
  .rosy-quickbar-icon { transition: none; }
}

@media (max-width: 1023.98px) and (prefers-reduced-motion: reduce) {
  #composerModal.composer-card,
  #composerModal.composer-card.is-open { transition: none; }
}

/* ==========================================================================
   📱 Mobile floating bottom navigation — glassmorphism capsule + SMART AUTO-HIDE
   --------------------------------------------------------------------------
   Phones / tablets ONLY (`lg:hidden` + the `max-width: 1023.98px` query below),
   so the desktop chrome — the header rail and the LEFT category sidebar — is
   100% untouched. The markup lives in includes/footer.php, deliberately OUTSIDE
   the <main id="main-content"> swap target, so it survives every ⚡ soft page
   swap; the behaviour lives in assets/js/app.js → “mobile floating bottom nav”.

   Visuals: one centred, blur-backed pill with a soft blush hairline and a gentle
   elevation shadow. `.is-hidden` glides it down out of view (the smart auto-hide
   driven by the scroll listener). Full dark-mode parity further below.
   ========================================================================== */
/* Hidden by default: the mobile media query below opts the bar in, so it can
   never flash on desktop while the Tailwind CDN (which paints `lg:hidden`) is
   still loading on a cold first paint. */
.rosy-bottom-nav { display: none; }

@media (max-width: 1023.98px) {
  /* ---- the floating glass capsule ---------------------------------------- */
  .rosy-bottom-nav {
    position: fixed;
    left: 50%;
    bottom: calc(14px + env(safe-area-inset-bottom, 0px));
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: space-around;
    gap: .15rem;
    width: calc(100% - 28px);
    max-width: 430px;
    height: 58px;
    padding: 0 .45rem;
    box-sizing: border-box;
    transform: translateX(-50%);
    border: 1px solid rgba(255, 182, 193, .45);
    border-radius: 9999px;
    background: rgba(255, 255, 255, .82);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    box-shadow: 0 10px 25px -5px rgba(225, 29, 72, .12), 0 8px 10px -6px rgba(0, 0, 0, .04);
    transition: transform .3s cubic-bezier(.16, 1, .3, 1), opacity .25s ease;
    will-change: transform;
  }

  /* ---- smart auto-hide — the bar glides down out of the way -------------- */
  .rosy-bottom-nav.is-hidden {
    transform: translate(-50%, calc(100% + 24px));
    opacity: 0;
    pointer-events: none;
  }

  /* ---- one tab: emoji over a micro-label -------------------------------- */
  .rosy-bottom-nav-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-width: 3.6rem;
    height: 100%;
    padding: 0 .3rem;
    border: 0;
    border-radius: 9999px;
    background: transparent;
    color: rgba(74, 63, 63, .5);
    font-family: inherit;
    font-size: .66rem;
    font-weight: 600;
    line-height: 1.1;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: color .18s ease, transform .18s ease;
  }
  .rosy-bottom-nav-item:active { transform: scale(.95); }
  .rosy-bottom-nav-item.is-active { color: #D95E6F; }
  .rosy-bottom-nav-emoji {
    font-size: 1.2rem;
    line-height: 1;
    transition: transform .18s ease;
  }
  .rosy-bottom-nav-item.is-active .rosy-bottom-nav-emoji { transform: translateY(-1px); }
  .rosy-bottom-nav-label { font-size: .66rem; }

  /* ---- the prominent centre “➕ โพสต์” button ---------------------------- */
  .rosy-bottom-nav-post {
    flex-shrink: 0;
    height: 3rem;
    min-width: 3.3rem;
    margin: 0 .15rem;
    border-radius: 9999px;
    background: linear-gradient(135deg, #FF9FB4, #E8607F);
    color: #FFFFFF;
    box-shadow: 0 10px 20px -10px rgba(217, 94, 111, .95);
  }
  .rosy-bottom-nav-post .rosy-bottom-nav-emoji { font-size: 1.05rem; }

  /* ---- safe bottom spacers — the capsule never hides the last card, the
          comment box or the footer actions, on ANY page. ------------------- */
  main.main-layout { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
  footer.rosy-site-footer { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
  /* …and the 📱 install banner sits ABOVE the bar instead of underneath it. */
  #pwa-install-banner { bottom: calc(84px + env(safe-area-inset-bottom, 0px)); }
}

/* ---- dark-mode parity · mobile floating bottom navigation ---------------- */
@media (max-width: 1023.98px) {
  html.dark .rosy-bottom-nav {
    background: rgba(36, 31, 51, .82);
    border-color: rgba(255, 182, 193, .2);
    box-shadow: 0 10px 25px -5px rgba(0, 0, 0, .55), 0 8px 10px -6px rgba(0, 0, 0, .4);
  }
  html.dark .rosy-bottom-nav-item { color: rgba(245, 235, 240, .6); }
  html.dark .rosy-bottom-nav-item.is-active { color: #FFC9D4; }
  html.dark .rosy-bottom-nav-post {
    background: linear-gradient(135deg, #F0778D, #C94E6C);
    color: #FFFFFF;
    box-shadow: 0 10px 20px -10px rgba(0, 0, 0, .85);
  }
}

@media (max-width: 1023.98px) and (prefers-reduced-motion: reduce) {
  .rosy-bottom-nav,
  .rosy-bottom-nav-item,
  .rosy-bottom-nav-emoji { transition: none; }
}

/* ==========================================================================
   🌐 Ecosystem footer bar — “เว็บไซต์ในเครือ (Rosy Network)”
   --------------------------------------------------------------------------
   The cross-domain link bar printed inside <footer> (includes/footer.php →
   get_network_sites()). The Tailwind CDN utilities carry the layout — a single
   column on phones, two from the `md` breakpoint — while the rules below add
   the calm hover lift, the round icon chip and the dark-mode parity the utility
   layer cannot cover on its own. Everything flips natively on html.dark.
   ========================================================================== */
.rosy-network-sites {
  background: linear-gradient(180deg, rgba(255, 245, 247, .55) 0%, rgba(255, 249, 245, 0) 100%);
}

.rosy-network-card {
  position: relative;
  outline: none;
  will-change: transform;
}

/* Keyboard users get the same “lit up” cue as a mouse hover. */
.rosy-network-card:focus-visible {
  box-shadow: 0 0 0 3px rgba(255, 154, 162, .55);
}

/* The round emoji chip on the left of every card. */
.rosy-network-icon {
  display: inline-grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 9999px;
  background: linear-gradient(135deg, #FFE8EE, #FFD3DD);
  font-size: 1.25rem;
  line-height: 1;
  transition: transform .18s ease;
}
.rosy-network-card:hover .rosy-network-icon { transform: scale(1.08) rotate(-4deg); }

/* --- dark-mode parity ---------------------------------------------------- */
html.dark .rosy-network-sites {
  background: linear-gradient(180deg, rgba(43, 35, 56, .55) 0%, rgba(26, 22, 37, 0) 100%);
}
html.dark .rosy-network-icon {
  background: linear-gradient(135deg, #43354F, #5A4767);
}

@media (prefers-reduced-motion: reduce) {
  .rosy-network-card,
  .rosy-network-icon { transition: none; }
  .rosy-network-card:hover .rosy-network-icon { transform: none; }
}

/* ==========================================================================
   🤖 AI Chatroom (chat.php) — conversation bubbles & avatars
   --------------------------------------------------------------------------
   The bubbles deliberately carry NO font-size utility class: they inherit the
   site-wide reading metrics the administrator tunes in
   Admin → 🌐 Language, Theme & Site Branding → 🎨 Site Typography
   (--site-body-size / --site-line-height, painted on <body> by
   includes/header.php). Raising “Body font size” therefore scales the chatroom
   text in step with every other reading surface of the site.
   ========================================================================== */
.ai-chat-bubble {
  font-family: var(--site-font-family, inherit);
  /* `--body-font-size` is honoured first (a deployment may alias the body size
     under that name); the typography layer's --site-body-size is the canonical
     source, with a 1rem / 1.625 fallback for the cold first paint. */
  font-size: var(--body-font-size, var(--site-body-size, 1rem));
  line-height: var(--body-line-height, var(--site-line-height, 1.625));
  overflow-wrap: anywhere;   /* a pasted URL can never stretch the bubble */
  min-width: 0;
}

/* The stylized letter chip that stands in for a missing / unloadable avatar
   (and for a bot that has no picture at all). `[hidden]` outranks the `grid`
   utility, so the inline error handler on the <img> — or ai_chat.js — only has
   to clear the attribute to reveal it. */
.ai-chat-avatar-fallback[hidden] { display: none; }


