:root {
  /* WHITE AND PINK is the brand, so the page ground is not grey and not a
     neutral off-white -- it carries a whisper of the accent, and surfaces sit
     on top of it in true white. A flat #FFF on #FFF has no depth; this gives
     cards an edge without needing a heavy border. */
  --ground:     #FFF8FA;
  --surface:    #FFFFFF;
  --surface-2:  #FFF1F5;
  --surface-3:  #FFE1EB;

  /* Ink is warm and pink-biased rather than pure black -- a #111 headline on
     a pink ground reads as two unrelated designs. */
  --ink:        #2A1A21;
  --ink-2:      #6B4E58;
  --ink-3:      #856874;   /* 4.53:1 on the pink wash -- #9C818C was 3.23 */

  --line:       #FADCE5;
  --line-soft:  #FDEDF2;

  /* The legacy site's one accent was #e93f63. Kept as the brand pink, with a
     deeper tone for text and hover so it clears contrast on white. */
  --pink:       #E93F63;
  --pink-ink:   #C21E48;
  --pink-deep:  #9E1039;
  --pink-wash:  #FFF0F4;
  --accent:     var(--pink);
  --accent-ink: var(--pink-ink);
  --accent-wash:var(--pink-wash);

  /* The header and footer GROUND, which is a different job from --pink-ink.
     --pink-ink is a text colour: it is deliberately inverted for dark mode
     (dark ink on light ground becomes light ink on dark ground). The header
     and footer used it as a background under white text, so in dark mode they
     flipped to a pale pink and the white text on them measured 2.00:1 --
     against a 4.5:1 minimum. This token does not invert. */
  --brand-bg:   #C21E48;      /* white on this: 5.88:1 */
  --brand-line: #9E1039;
  --brand-bg-hover: #9E1039;  /* white on this: 8.86:1 */

  /* VVIP is the one place that is deliberately NOT pink. A premium tier that
     uses the same colour as everything else does not read as premium; gold
     against a pink page does, and it is the only gold on the site. */
  --gold:       #986F10;   /* 4.55:1 on white */
  --gold-mid:   #BA8E29;   /* 3.00:1 -- the AA floor for a graphical element */
  --gold-wash:  #FFF8E8;
  --gold-line:  #F0DCA8;

  --ok:         #1F7A5A;
  --ok-wash:    #E6F5EF;

  --shadow-sm: 0 1px 2px rgba(148, 32, 68, .06), 0 1px 3px rgba(148, 32, 68, .05);
  --shadow-md: 0 2px 8px rgba(148, 32, 68, .09), 0 12px 28px rgba(148, 32, 68, .08);
  --shadow-gold: 0 2px 8px rgba(169, 123, 18, .14), 0 12px 28px rgba(169, 123, 18, .12);

  --r-sm: 8px;
  --r:    12px;
  --r-lg: 18px;
  --r-pill: 999px;

  --wrap: 1200px;
}

/*
   No prefers-color-scheme switch: the brand is white and pink, and a page that
   silently turns charcoal on half the visitors is a different brand. The dark
   token set below is kept so a deliberate toggle is one attribute away, but
   nothing sets it automatically. Every colour above is painted explicitly, so
   the page never inherits a host background either way.
*/
:root[data-theme="dark"] {
  --ground:#161013; --surface:#211820; --surface-2:#2C1F28; --surface-3:#3A2833;
  --ink:#F7ECF1; --ink-2:#C9B3BE; --ink-3:#9A8390;
  --line:#3A2833; --line-soft:#2A1E26;
  --pink:#FF6E90; --pink-ink:#FF9AB2; --pink-deep:#FFB9C9; --pink-wash:#3A1926;
  --accent: var(--pink); --accent-ink: var(--pink-ink); --accent-wash: var(--pink-wash);
  /* Deeper, not lighter. See --brand-bg above: white on this is 12.16:1. */
  --brand-bg:#6B0F2A; --brand-line:#8A1536; --brand-bg-hover:#8A1536;
  --gold:#E9C46A; --gold-mid:#C9A24A; --gold-wash:#332812; --gold-line:#5A4620;
  --ok:#5FC79E; --ok-wash:#0F2E23;
  --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
  --shadow-md: 0 2px 8px rgba(0,0,0,.5), 0 12px 28px rgba(0,0,0,.4);
  --shadow-gold: 0 2px 8px rgba(0,0,0,.5), 0 12px 28px rgba(0,0,0,.4);
}

/* -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  /* Noto Sans Thai first: it positions Thai tone marks correctly, which the
     Windows and Android defaults often do not. Latin falls through to the
     platform UI face so it looks native rather than webfont-ish. */
  font-family: "Noto Sans Thai", -apple-system, BlinkMacSystemFont, "Segoe UI",
               "Sarabun", "Leelawadee UI", Tahoma, sans-serif;
  font-size: 15px;
  line-height: 1.75;          /* Thai needs the room; see the header comment */
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
a:hover { color: var(--accent-ink); }

h1, h2, h3 { line-height: 1.35; margin: 0; font-weight: 700; text-wrap: balance; }
h1 { font-size: 1.55rem; letter-spacing: -.01em; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }
p  { margin: 0 0 1rem; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 1rem; }

.num { font-variant-numeric: tabular-nums; }

.skip {
  position: absolute; left: -9999px;
  background: var(--surface); color: var(--ink);
  padding: .6rem 1rem; border-radius: var(--r-sm); z-index: 100;
}
.skip:focus { left: 1rem; top: .5rem; }

/* ---------- header -------------------------------------------------------- */

/* The header and footer are the brand pink, painted from --brand-bg.
   #C21E48, not --pink (#E93F63): white text on the brand pink is 3.92:1, which
   is below AA for body text. On #C21E48 it is 5.88:1. The brighter pink stays
   what it has always been -- the accent on white.

   It reads --brand-bg rather than --pink-ink because those two tokens have
   opposite jobs. --pink-ink is INK: it inverts for dark mode, going light so it
   stays readable on a dark ground. Used here it inverted a BACKGROUND, turning
   the header pale pink under white text at 2.00:1. Only reachable through the
   data-theme toggle, which nothing sets today -- but the toggle is documented
   above as one attribute away. */
.site-header {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--brand-bg) 94%, transparent);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--brand-line);
  color: #fff;
}
/* The wordmark is white lettering with a dark-grey mark on transparency, so it
   cannot sit on a solid pink ground as-is: the grey half falls to 1.87:1 and
   effectively disappears. Rendered white, the whole mark is 5.88:1. One line
   to remove if the artwork is ever redrawn for a dark ground. */
.site-header .brand img, .site-footer .brand img { filter: brightness(0) invert(1); }
.site-header a { color: #fff; }
.site-header .btn-ghost { border-color: rgba(255,255,255,.45); color: #fff; }
.site-header .btn-ghost:hover { border-color: #fff; background: rgba(255,255,255,.12); color: #fff; }
/* The search field keeps a white ground: an input tinted to match the header
   stops looking like somewhere you can type. */
.site-header .search input, .site-header .search select { background: var(--surface); color: var(--ink); }
.header-bar {
  display: flex; align-items: center; gap: 1rem;
  min-height: 60px; padding: .5rem 0;
}
.brand {
  font-weight: 700; font-size: 1.15rem; letter-spacing: -.02em;
  display: inline-flex; align-items: center; gap: .5rem; flex: 0 0 auto;
}
/* The wordmark. Height-constrained rather than width-constrained: the source
   is 800x300, and fixing the height is what keeps the header bar a constant
   size if the artwork is ever replaced with a different aspect. */
.brand img { height: 34px; width: auto; display: block; }
@media (max-width: 560px) { .brand img { height: 28px; } }
.brand:hover { color: inherit; }

.header-search { flex: 1 1 auto; min-width: 0; max-width: 34rem; }
.header-actions { display: flex; align-items: center; gap: .5rem; flex: 0 0 auto; }

@media (max-width: 720px) {
  .header-search { display: none; }        /* moves into the page body below */
}

/* ---------- buttons ------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  font: inherit; font-weight: 600; line-height: 1;
  padding: .62rem 1rem; border-radius: var(--r-pill);
  border: 1px solid transparent; cursor: pointer;
  transition: background-color .15s, border-color .15s, color .15s, transform .06s;
  white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
/* --pink is the brand at 3.92:1 against white, which is below AA for text.
   The brand colour stays exactly as it was for borders, focus rings and washes;
   anything with white text on it uses the deeper tone, which is 5.88:1. */
.btn-primary { background: var(--brand-bg); color: #fff; }
.btn-primary:hover { background: var(--brand-bg-hover); color: #fff; }
.btn-ghost { border-color: var(--line); color: var(--ink-2); background: transparent; }
.btn-ghost:hover { border-color: var(--ink-3); color: var(--ink); }
.btn-line { background: #06C755; color: #fff; }
.btn-line:hover { background: #05a948; color: #fff; }
/* Tap targets. Lighthouse wants 24px minimum with spacing; these were 26-28px,
   which passes but is tight on a phone. Raised to ~30px. */
.btn-sm { padding: .5rem .85rem; font-size: .85rem; }
.btn-block { width: 100%; }

/* ---------- search -------------------------------------------------------- */

.search {
  display: flex; align-items: center; gap: .5rem;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: .3rem .3rem .3rem .9rem;
}
.search:focus-within { border-color: var(--accent); background: var(--surface); }
.search input, .search select {
  font: inherit; color: inherit; background: transparent;
  border: 0; outline: 0; min-width: 0;
}
.search input { flex: 1 1 auto; padding: .35rem 0; }
.search select {
  flex: 0 0 auto; max-width: 9rem; color: var(--ink-2);
  border-left: 1px solid var(--line); padding-left: .6rem;
}
.search select option { background: var(--surface); color: var(--ink); }

/* ---------- page furniture ------------------------------------------------ */

main { padding: 1.5rem 0 4rem; }

.crumbs {
  display: flex; flex-wrap: wrap; gap: .4rem;
  font-size: .85rem; color: var(--ink-3); margin-bottom: 1rem;
}
.crumbs a { color: var(--ink-3); }
.crumbs a:hover { color: var(--accent-ink); }
.crumbs li + li::before { content: "/"; margin-right: .4rem; color: var(--line); }
.crumbs ol { display: flex; flex-wrap: wrap; gap: .4rem; list-style: none; margin: 0; padding: 0; }

.section { margin: 2.25rem 0; }
.section:first-of-type { margin-top: 1rem; }
.section-head {
  display: flex; align-items: baseline; gap: .75rem;
  margin-bottom: 1rem; padding-bottom: .5rem;
  border-bottom: 1px solid var(--line);
}
.section-head h2 { flex: 0 0 auto; }
.section-head .more { font-size: .875rem; color: var(--accent-ink); font-weight: 600; margin-left: auto; }

/* A count beside the heading, so "VVIP Zone" is followed by how many there
   actually are rather than implying a full shelf. */
.section-count {
  font-size: .75rem; font-weight: 700; line-height: 1;
  padding: .28rem .5rem; border-radius: var(--r-pill);
  background: var(--surface-2); color: var(--ink-2); border: 1px solid var(--line);
}
.section-note { font-size: .82rem; color: var(--ink-3); }

/* The two paid tiers get a coloured rule so the shelves are distinguishable
   while scrolling, not only by their headings. */
.section-head-vvip {
  border-bottom-color: var(--gold-line);
}
.section-head-vvip h2 {
  background: linear-gradient(100deg, #8A5F06, var(--gold-mid) 55%, #8A5F06);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--gold);
  letter-spacing: .01em;
}
.section-head-vvip .section-count {
  background: var(--gold-wash); color: var(--gold); border-color: var(--gold-line);
}
.section-head-premium { border-bottom-color: var(--surface-3); }
.section-head-premium h2 { color: var(--pink-ink); }
.section-head-premium .section-count {
  background: var(--pink-wash); color: var(--pink-ink); border-color: var(--surface-3);
}

.page-title { margin-bottom: .25rem; }
.page-sub { color: var(--ink-2); margin: 0 0 1.25rem; }

/* ---------- listing grid --------------------------------------------------

   Three densities, because the three tiers are not equally scarce:

     .grid        the workhorse -- 2 up on a phone, 5 on a desktop
     .grid-vvip   1 VVIP listing exists today. A five-column grid holding one
                  card looks broken, so this one is wide and deliberately
                  sparse: whether it holds one or six, it reads as a showcase.

   -------------------------------------------------------------------------- */

.grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 560px)  { .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 860px)  { .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1080px) { .grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }

.grid-vvip { grid-template-columns: repeat(1, minmax(0, 1fr)); gap: 1.25rem; }
@media (min-width: 560px)  { .grid-vvip { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 900px)  { .grid-vvip { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ---------- the card ------------------------------------------------------

   The one component the whole site is made of. Everything below serves one of
   three jobs: make the photograph the subject, make the tier legible at a
   glance, and keep the text block a fixed height so a grid of cards lines up
   whatever the titles do.
   -------------------------------------------------------------------------- */

.card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
  display: flex; flex-direction: column;
  transition: border-color .18s, box-shadow .22s, transform .22s;
}
.card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
  transform: translateY(-3px);
}
.card:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.card-media {
  position: relative;
  aspect-ratio: 3 / 4;              /* portraits, not a generic 16:9 */
  background: var(--surface-2);
  overflow: hidden;
}
.card-media img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .5s cubic-bezier(.2, .7, .3, 1);
}
.card:hover .card-media img { transform: scale(1.06); }

/* A scrim, not a flat overlay: the badges sit on photographs whose top-left
   corner might be anything, and a pill on a pale background needs the contrast
   without dimming the whole image. */
.card-media::after {
  content: ""; position: absolute; inset: 0 0 55% 0;
  background: linear-gradient(180deg, rgba(20, 6, 12, .38), transparent);
  pointer-events: none;
}
/* A file that has gone missing draws the same empty state as a listing with
   no photograph at all -- see the error handler in layouts/app.blade.php. */
.card-media.is-empty::before,
.gallery-main.is-empty::before {
  content: "ไม่มีรูปภาพ";
  position: absolute; inset: 0; z-index: 1;
  display: grid; place-items: center;
  color: var(--ink-3); font-size: .8rem;
  background: radial-gradient(circle at 50% 38%, var(--surface-3), var(--surface-2) 70%);
}
.gallery-main { position: relative; }
.gallery-thumbs.is-empty { display: none; }

.card-media .placeholder {
  width: 100%; height: 100%;
  display: grid; place-items: center; gap: .35rem;
  color: var(--ink-3); font-size: .8rem;
  background:
    radial-gradient(circle at 50% 38%, var(--surface-3), var(--surface-2) 70%);
}

.card-flags {
  position: absolute; top: .55rem; left: .55rem; right: .55rem; z-index: 2;
  display: flex; flex-wrap: wrap; gap: .3rem;
}
.card-flags .spacer { flex: 1 1 auto; }

.card-body {
  padding: .75rem .85rem .85rem;
  display: flex; flex-direction: column; gap: .35rem; flex: 1 1 auto;
}
.card-title {
  font-weight: 600; font-size: .95rem; line-height: 1.45;
  /* Two lines, always: a one-line title and a two-line title must not make
     two neighbouring cards different heights. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; min-height: 2.9em;
}
.card-meta {
  display: flex; align-items: center; gap: .35rem;
  font-size: .8rem; color: var(--ink-3); min-width: 0;
}
.card-meta .place {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  display: inline-flex; align-items: center; gap: .25rem;
}
.card-meta svg { flex: 0 0 auto; opacity: .8; }

.card-foot {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .5rem; margin-top: auto; padding-top: .35rem;
  border-top: 1px solid var(--line-soft);
}
.price { font-weight: 700; color: var(--accent-ink); font-size: 1rem; }
.price small { font-weight: 500; font-size: .72rem; color: var(--ink-3); margin-left: .1rem; }

/* ---------- tiers ---------------------------------------------------------

   post_level is the tier: 2 VVIP, 1 premium, 0 normal. `supper_star` is not
   consulted -- it is set on exactly the 637 rows where post_level >= 1, so it
   carries no information the tier does not already give.
   -------------------------------------------------------------------------- */

.card-premium { border-color: var(--surface-3); }
.card-premium:hover { border-color: var(--accent); }

.card-vvip {
  border-color: var(--gold-line);
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    linear-gradient(135deg, var(--gold-mid), #F6E3AE 45%, var(--gold-mid)) border-box;
  border: 2px solid transparent;
}
.card-vvip:hover { box-shadow: var(--shadow-gold); border-color: transparent; }
.card-vvip .price { color: var(--gold); }

/* The VVIP showcase card is bigger and shows more, because there are very few
   of them and each one is a paid placement. */
.grid-vvip .card-media { aspect-ratio: 4 / 5; }
.grid-vvip .card-title { font-size: 1.05rem; -webkit-line-clamp: 1; min-height: 0; }
.grid-vvip .card-body { padding: .95rem 1.05rem 1.05rem; gap: .45rem; }
.grid-vvip .price { font-size: 1.15rem; }

/* ---------- pills and ratings --------------------------------------------- */

.pill {
  display: inline-flex; align-items: center; gap: .28rem;
  font-size: .7rem; font-weight: 700; line-height: 1;
  padding: .32rem .55rem; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .92); color: var(--ink-2);
  border: 1px solid transparent;
  backdrop-filter: blur(6px);
  white-space: nowrap;
}
.pill-verified { background: var(--ok); color: #fff; }
.pill-premium  { background: var(--brand-bg); color: #fff; }
.pill-vvip {
  /* The only gradient on the site, on the only tier that is scarce. */
  /* The midpoint has to carry the text too: #FFF8E3 on #D4A63C is 2.12:1.
     Darkened so the whole sweep clears AA. */
  background: linear-gradient(135deg, #6E4C05, #8A5F06 55%, #6E4C05);
  color: #FFF8E3; letter-spacing: .04em;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
.pill-quiet { background: rgba(255, 255, 255, .92); color: var(--ink-2); border-color: var(--line); }

/* Ratings are shown only where they exist -- 22 of 1,996 listings have any
   review, so a default "0.0" on every card would be noise pretending to be
   information. */
.stars { color: var(--gold-mid); letter-spacing: .06em; font-size: .8rem; white-space: nowrap; }
.stars .off { color: var(--surface-3); }

/* ---------- chips (location nav) ------------------------------------------ */

.chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.chip {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .5rem .9rem; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--line);
  font-size: .875rem; color: var(--ink-2);
  transition: border-color .15s, color .15s, background-color .15s;
}
.chip:hover { border-color: var(--accent); color: var(--accent-ink); background: var(--accent-wash); }
.chip .count { font-size: .75rem; color: var(--ink-3); }
.chip-active { background: var(--accent); border-color: var(--accent); color: #fff; }
.chip-active .count { color: rgba(255,255,255,.8); }

.region { margin-bottom: 1.25rem; }
.region-name {
  font-size: .78rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: .5rem;
}

/* ---------- hero ---------------------------------------------------------- */

.hero {
  background:
    radial-gradient(1100px 320px at 12% -8%, var(--accent-wash), transparent 62%),
    radial-gradient(900px 300px at 88% 0%, var(--surface-2), transparent 60%);
  border-bottom: 1px solid var(--line);
  padding: 2.5rem 0 2rem;
}
.hero h1 { font-size: clamp(1.5rem, 4.2vw, 2.2rem); margin-bottom: .5rem; }
.hero p { color: var(--ink-2); max-width: 42rem; margin-bottom: 1.25rem; }
.hero .search { max-width: 40rem; background: var(--surface); }
.hero-stats {
  display: flex; flex-wrap: wrap; gap: 1.5rem; margin-top: 1.25rem;
  font-size: .85rem; color: var(--ink-3);
}
.hero-stats b { display: block; font-size: 1.25rem; color: var(--ink); font-weight: 700; }

/* ---------- detail page --------------------------------------------------- */

.detail { display: grid; gap: 1.75rem; grid-template-columns: 1fr; }
@media (min-width: 900px) { .detail { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); } }

.gallery-main {
  aspect-ratio: 3 / 4; background: var(--surface-2);
  border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--line);
}
.gallery-main img { width: 100%; height: 100%; object-fit: cover; }
.gallery-thumbs {
  display: flex; gap: .5rem; margin-top: .5rem;
  overflow-x: auto; padding-bottom: .25rem;
  scrollbar-width: thin;
}
.gallery-thumbs img {
  width: 72px; height: 96px; object-fit: cover; flex: 0 0 auto;
  border-radius: var(--r-sm); border: 1px solid var(--line); cursor: pointer;
  transition: border-color .15s, opacity .15s;
  opacity: .75;
}
.gallery-thumbs img:hover, .gallery-thumbs img[aria-current="true"] { opacity: 1; border-color: var(--accent); }

.facts { display: grid; grid-template-columns: auto 1fr; gap: .5rem 1.25rem; margin: 1.25rem 0; }
.facts dt { color: var(--ink-3); font-size: .875rem; }
.facts dd { margin: 0; font-weight: 500; }

.panel {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); padding: 1rem 1.15rem;
}
.notice {
  background: var(--gold-wash); border: 1px solid color-mix(in srgb, var(--gold) 30%, transparent);
  color: var(--gold); border-radius: var(--r); padding: .8rem 1rem;
  font-size: .9rem;
}
.alert {
  background: var(--accent-wash); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  color: var(--accent-ink); border-radius: var(--r); padding: .8rem 1rem; font-size: .9rem;
}

.review { border-top: 1px solid var(--line-soft); padding: .9rem 0; }
.review:first-of-type { border-top: 0; }
.review-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.review-head strong { font-size: .95rem; }
.review-head time { font-size: .8rem; color: var(--ink-3); }

/* ---------- forms --------------------------------------------------------- */

.form-shell {
  max-width: 27rem; margin: 2.5rem auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.75rem; box-shadow: var(--shadow-sm);
}
.field { margin-bottom: 1rem; }
.field label { display: block; font-size: .875rem; font-weight: 600; margin-bottom: .3rem; }
.field .hint { font-size: .8rem; color: var(--ink-3); margin-top: .3rem; }
.input, .select {
  width: 100%; font: inherit; color: inherit;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: .6rem .75rem;
}
.input:focus, .select:focus { outline: 0; border-color: var(--accent); background: var(--surface); }
.check { display: flex; align-items: center; gap: .5rem; font-size: .9rem; }

/* ---------- pagination ---------------------------------------------------- */

.pager { display: flex; flex-wrap: wrap; gap: .35rem; justify-content: center; margin-top: 2rem; }
.pager a, .pager span {
  min-width: 2.5rem; padding: .55rem .7rem; text-align: center;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: .875rem; color: var(--ink-2); background: var(--surface);
}
.pager a:hover { border-color: var(--accent); color: var(--accent-ink); }
.pager .current { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.pager .disabled { opacity: .45; }

/* ---------- empty / footer ------------------------------------------------ */

.empty {
  text-align: center; padding: 3.5rem 1rem; color: var(--ink-3);
  border: 1px dashed var(--line); border-radius: var(--r-lg);
}

/* The age statement and safety notice, above the footer for signed-out
   visitors. Deliberately quiet: it carries the platform's terms and a warning
   about being asked for money in advance, and a loud banner on every page is
   a banner people stop reading. The left rule and the tinted ground mark it
   as the site speaking rather than a listing. */
.site-notice {
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  margin-top: 3rem; padding: 1.5rem 0;
}
.site-notice .wrap { border-left: 3px solid var(--pink); padding-left: 1rem; }
.site-notice-title {
  margin: 0 0 .4rem; font-weight: 650; color: var(--ink); font-size: .95rem;
}
.site-notice-body { color: var(--ink-2); font-size: .87rem; max-width: 60ch; }
.site-notice-body p { margin: .3rem 0; }

/* It replaces the footer's own top margin when present, so the two do not
   stack into a gap the width of the viewport. */
.site-notice + .site-footer { margin-top: 0; }

.site-footer {
  border-top: 1px solid var(--brand-line); background: var(--brand-bg);
  padding: 2rem 0; margin-top: 3rem;
  font-size: .875rem; color: rgba(255,255,255,.82);
}
.site-footer a { color: #fff; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
.footer-grid { display: grid; gap: 1.5rem; grid-template-columns: 1fr; }
@media (min-width: 700px) { .footer-grid { grid-template-columns: 2fr 1fr 1fr; } }
.site-footer h3 { font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; color: rgba(255,255,255,.6); margin-bottom: .5rem; }
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { padding: .15rem 0; }
/* The second `.site-footer a:hover` that used to sit here set the colour to
   var(--accent-ink) -- which IS the footer's own background. It won the cascade
   over the white rule above it, so every footer link turned the colour of the
   surface behind it on hover: a contrast ratio of exactly 1.00:1, in both
   themes. The white-plus-underline rule above is the one that was meant. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
  .card:hover { transform: none; }
  .card:hover .card-media img { transform: none; }
}

/* ---------------------------------------------------------------------------
   Advanced filters and sorting.

   The panel is a <details>, so it needs no JavaScript and has no state to get
   out of step with the URL. It is opened from the server when a filter is
   already applied -- arriving on a filtered link and seeing a closed, empty
   panel beside narrowed results is the confusing case.
   --------------------------------------------------------------------------- */
.search-block { max-width: 46rem; margin-bottom: 1.5rem; }

.filters-panel { margin-top: .75rem; }
.filters-panel > summary {
  cursor: pointer; display: inline-flex; align-items: center; gap: .4rem;
  font-size: .875rem; font-weight: 600; color: var(--ink-2);
  padding: .4rem .7rem; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface); list-style: none; user-select: none;
}
.filters-panel > summary::-webkit-details-marker { display: none; }
.filters-panel > summary::after {
  content: "▾"; font-size: .7rem; transition: transform .15s ease;
}
.filters-panel[open] > summary::after { transform: rotate(180deg); }
.filters-panel > summary:hover { border-color: var(--accent); color: var(--accent-ink); }
.filters-panel > summary .count {
  background: var(--accent); color: #fff; border-radius: 999px;
  padding: 0 .4rem; font-size: .72rem; line-height: 1.5;
}

.filters-form {
  margin-top: .75rem; padding: 1rem; border: 1px solid var(--line);
  border-radius: var(--r-lg); background: var(--surface);
  display: grid; gap: 0 1rem; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}
.filters-form .field { margin-bottom: .75rem; }
.field-range .range { display: flex; align-items: center; gap: .4rem; }
.field-range .range .input { min-width: 0; }
.field-range .range span { color: var(--ink-3); }
.filters-actions {
  grid-column: 1 / -1; display: flex; gap: .5rem; align-items: center;
  border-top: 1px solid var(--line-soft, var(--line)); padding-top: .75rem;
}

/* Each chip removes only itself, so the × is the affordance that matters. */
.filter-chips { margin-bottom: 1rem; }
.chip-remove span[aria-hidden] { color: var(--ink-3); font-size: 1.05em; line-height: 1; }
.chip-remove:hover span[aria-hidden] { color: var(--accent-ink); }

.result-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .75rem;
  justify-content: space-between; margin-bottom: 1rem;
  padding-bottom: .6rem; border-bottom: 1px solid var(--line);
}
.result-count { color: var(--ink-2); font-size: .9rem; }
.sort-form { display: flex; align-items: center; gap: .5rem; }
.sort-form label { font-size: .85rem; color: var(--ink-3); }
.sort-form .select { width: auto; padding-block: .35rem; font-size: .875rem; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Sortable table headers. The whole header is the target, not just the arrow:
   a 12px glyph is not a tap target. */
.th-sort {
  display: inline-flex; align-items: center; gap: .3rem;
  color: inherit; text-decoration: none; white-space: nowrap;
}
.th-sort:hover { color: var(--accent-ink); }
.th-sort .th-arrow { font-size: .7em; color: var(--ink-3); }
/* The idle glyph is faint until hover, so a row of them does not read as
   noise -- but it is always present, so the column does not look unsortable
   until you happen to hover it. */
.th-sort:not(.is-on) .th-arrow { opacity: .35; }
.th-sort:hover .th-arrow { opacity: 1; }
.th-sort.is-on { color: var(--accent-ink); font-weight: 700; }
.th-sort.is-on .th-arrow { color: var(--accent); opacity: 1; }

/* A value that breaks a rule the site states publicly. */
.tbl td.is-flagged {
  color: var(--pink-ink); font-weight: 700;
  background: var(--crit-wash, var(--surface-2));
}

/* ---------- the filter and sort bar on a narrow screen -------------------- */
@media (max-width: 560px) {
  /* One column: at 320px a two-up grid gives each number input about 60px,
     which is not enough for "1,500". */
  .filters-form { grid-template-columns: 1fr; }

  /* The count and the sort stack rather than squeeze, and the select takes
     the width it needs -- a 90px select showing "ราคาต่ำไปสูง" truncates to
     something unreadable. */
  .result-bar { flex-direction: column; align-items: stretch; gap: .5rem; }
  .sort-form { justify-content: space-between; }
  .sort-form .select { flex: 1 1 auto; min-width: 0; }

  /* 44px minimum, because these are the controls people use one-handed. */
  .filters-panel > summary { padding: .6rem .9rem; }
  .chip-remove { padding: .45rem .7rem; }
}

/* A sorted admin table on a phone scrolls sideways inside .tbl-wrap, so the
   header must not wrap to two lines and double the row height. */
.tbl th { white-space: nowrap; }

/* ---------- news ---------------------------------------------------------- */
.news-list { display: grid; gap: 1.25rem; max-width: 46rem; }
.news-item { padding-bottom: 1.25rem; border-bottom: 1px solid var(--line); }
.news-item:last-child { border-bottom: 0; padding-bottom: 0; }
.news-item h2 { font-size: 1.15rem; margin: 0 0 .25rem; }
.news-item h2 a { color: var(--ink); text-decoration: none; }
.news-item h2 a:hover { color: var(--accent-ink); }
.news-meta { font-size: .8rem; color: var(--ink-3); margin: 0 0 .4rem; }
.news-excerpt { color: var(--ink-2); margin: 0 0 .4rem; }
.news-more { font-size: .875rem; font-weight: 600; }

/* An article is the one thing here that is read rather than scanned, so the
   measure is narrower than the rest of the site and the leading is looser. */
.news-article { max-width: 42rem; }
.news-body { font-size: 1.02rem; line-height: 1.75; color: var(--ink); }
.news-body p { margin: 0 0 1rem; }
.news-body h2 { font-size: 1.2rem; margin: 1.75rem 0 .5rem; }
.news-body h3 { font-size: 1.05rem; margin: 1.5rem 0 .5rem; }
.news-body ul, .news-body ol { margin: 0 0 1rem; padding-left: 1.4rem; }
.news-body li { margin-bottom: .3rem; }
.news-body blockquote {
  margin: 0 0 1rem; padding: .2rem 0 .2rem 1rem;
  border-left: 3px solid var(--pink); color: var(--ink-2);
}
.news-body a { color: var(--accent-ink); }

/* ---------- homepage shortcuts and section subtitles ---------------------- */
/* The advanced filters are worth nothing if nobody finds them, and they sit
   behind a collapsed panel one page away. These put the three questions the
   data can answer where a visitor lands. */
.quick-filters {
  display: flex; flex-wrap: wrap; gap: .5rem;
  margin: 1.25rem 0 .25rem;
}
.section-sub { font-size: .8rem; color: var(--ink-3); }
.news-item h3 { font-size: 1.02rem; margin: 0 0 .25rem; }
.news-item h3 a { color: var(--ink); text-decoration: none; }
.news-item h3 a:hover { color: var(--accent-ink); }

/* ---------- advertising ---------------------------------------------------
   The slot renders nothing at all when nothing is booked -- an empty bordered
   box labelled "advertisement" is worse than blank space. aspect-ratio
   reserves the box before the image arrives, so a banner cannot push the page
   down as it loads. */
.ad-slot { position: relative; display: block; margin: 1.5rem 0; }
.ad-slot img {
  display: block; width: 100%; height: auto; border-radius: var(--r);
  border: 1px solid var(--line);
}
.ad-home_top img, .ad-home_mid img, .ad-province_top img { aspect-ratio: 6 / 1; object-fit: cover; }
.ad-listing_side img { aspect-ratio: 6 / 5; object-fit: cover; }

/* Labelled, quietly but unmissably. Paid placement that is not marked as paid
   is the thing regulators and readers both object to, and the label costs a
   corner of the image. */
.ad-label {
  position: absolute; top: .4rem; right: .4rem;
  font-size: .62rem; letter-spacing: .08em; text-transform: uppercase;
  background: rgba(0,0,0,.55); color: #fff;
  padding: .1rem .35rem; border-radius: 3px; pointer-events: none;
}

/* the rate card */
.slot-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.slot-card { border: 1px solid var(--line); border-radius: var(--r-lg); padding: 1.1rem 1.2rem; background: var(--surface); }
.slot-card h3 { margin: 0 0 .3rem; font-size: 1rem; }
.slot-size { font-family: ui-monospace, monospace; font-size: .85rem; color: var(--ink-2); }
.slot-status { font-size: .78rem; margin-top: .5rem; }
.slot-status.is-free { color: var(--ok, #1B6440); }
.slot-status.is-taken { color: var(--ink-3); }

/* ---------- floating side rails -------------------------------------------
   Only above 1500px: the content column is 1120px, so below that there is no
   room beside it and a fixed panel would sit ON the page rather than next to
   it. calc() positions them from the viewport edge relative to the centred
   column, so they slide off rather than over as the window narrows.

   The element starts [hidden] and the script reveals it once the width is
   known -- a rail that flashes on a phone and then disappears is worse than
   one that never rendered. */
.ad-rail { display: none; }

@media (min-width: 1500px) {
  .ad-rail:not([hidden]) {
    display: block;
    position: fixed;
    top: 50%;
    transform: translateY(-50%);
    width: 160px;
    z-index: 30;          /* under the sticky header at 40 */
  }
  .ad-rail-left  { left:  calc((100vw - 1120px) / 2 - 180px); }
  .ad-rail-right { right: calc((100vw - 1120px) / 2 - 180px); }
}

.ad-rail img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r); border: 1px solid var(--line);
}
.ad-rail .ad-label { top: .3rem; right: .3rem; }

/* The close control. 28px is above the 24px minimum for a secondary control
   and sits outside the image so it never covers what was paid for. */
.ad-rail-close {
  position: absolute; top: -10px; right: -10px;
  width: 28px; height: 28px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-2);
  font-size: 1rem; line-height: 1; cursor: pointer; z-index: 1;
  box-shadow: var(--shadow-sm, 0 1px 3px rgba(0,0,0,.12));
}
.ad-rail-close:hover { color: var(--pink-ink); border-color: var(--pink); }
.ad-rail-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- an unsold slot -------------------------------------------------
   Deliberately not dressed as an advertisement. A dashed outline and muted
   text read as the site talking; a placeholder that imitates a real banner
   teaches people to ignore the real ones, which spends the value of the space
   it is trying to sell.

   It reserves the same box as the banner it replaces, so a slot filling later
   does not change the page's layout. */
.ad-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .4rem; text-align: center;
  margin: 1.5rem 0; padding: 1.1rem 1rem;
  border: 1px dashed var(--line); border-radius: var(--r);
  background: var(--pink-wash);
}
.ad-empty-title {
  margin: 0; font-weight: 700; font-size: .95rem; color: var(--ink-2);
}
.ad-empty-note { margin: 0; font-size: .85rem; color: var(--ink-3); }
.ad-empty-size {
  font-family: ui-monospace, monospace; font-size: .78rem;
  color: var(--ink-3); margin-left: .35rem;
}
.ad-empty-actions { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: center; margin-top: .3rem; }

/* The same aspect the sold banner would occupy, so the page does not shift
   when the slot is filled. min-height rather than aspect-ratio: the content is
   text, and a 6:1 box at 1120px is 186px of mostly empty space. */
.ad-empty.ad-home_top, .ad-empty.ad-home_mid, .ad-empty.ad-province_top { min-height: 120px; }
.ad-empty.ad-listing_side { min-height: 180px; }

/* ---------- card: stretched link, plus a second action ---------------------
   The card is an <article>, not one big <a>, because it now has two
   destinations and an anchor cannot contain another anchor. .card-link covers
   the card so the whole surface still opens the listing; the LINE button sits
   above it on z-index and wins its own clicks. */
.card-link {
  position: absolute; inset: 0; z-index: 1;
  border-radius: inherit;
}
.card-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* The hover that used to belong to .card:hover, now driven by the overlay. */
.card:has(.card-link:hover) {
  border-color: var(--accent); box-shadow: var(--shadow-md); transform: translateY(-3px);
}

/* The LINE button. Full width at the foot of the card, in LINE's own green,
   so it reads as a channel rather than as one more pink control. */
.card-line {
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: center; gap: .35rem;
  padding: .5rem; margin: 0 .6rem .6rem;
  border-radius: var(--r-pill);
  background: #06C755; color: #fff;
  font-size: .82rem; font-weight: 700; line-height: 1;
  text-decoration: none;
}
.card-line:hover { background: #05a948; color: #fff; }
.card-line:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* การันตีตรงปก. Green, and the only green on a page of pink and gold -- a
   guarantee that looked like every other pill would not read as one. #1B6440
   is 6.4:1 on white, so the text passes AA rather than relying on the colour
   alone to carry the meaning. */
.pill-guarantee {
  background: #DDEEE3; color: #145232; border: 1px solid #9FCDB4;
}
/* Two separate blocks. A selector cannot be comma-joined to an @media rule --
   the whole declaration is discarded, silently, and the badge keeps its light
   colours on a dark ground. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .pill-guarantee {
    background: #12301F; color: #7FD6A4; border-color: #245C3D;
  }
}
:root[data-theme="dark"] .pill-guarantee {
  background: #12301F; color: #7FD6A4; border-color: #245C3D;
}

/* The admin's issue-guarantee button. Green, because it does the one thing on
   the moderation row that makes a public promise -- the approval buttons beside
   it only decide whether a listing is visible. */
.btn-guarantee {
  background: var(--ok-bg, #E8F6EE);
  color: var(--ok-ink, #1B7A45);
  border: 1px solid var(--ok-line, #A8DCC0);
}
.btn-guarantee:hover { background: #D6EFE1; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .btn-guarantee {
    background: #12301F; color: #7FD6A4; border-color: #245C3D;
  }
  :root:not([data-theme="light"]) .btn-guarantee:hover { background: #17402A; }
}
:root[data-theme="dark"] .btn-guarantee {
  background: #12301F; color: #7FD6A4; border-color: #245C3D;
}
:root[data-theme="dark"] .btn-guarantee:hover { background: #17402A; }

/* ---------------------------------------------------------------------------
   Account settings, the listing form's preview panel, and the photo viewer.
   --------------------------------------------------------------------------- */

.settings-grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}
.panel-title { margin: 0 0 .85rem; font-size: 1.02rem; }
.hint[data-error] { color: var(--pink-ink); }
.input[aria-invalid="true"] { border-color: var(--pink-ink); }

/* Form on the left, preview on the right. One column until there is room for
   both -- below that the preview would sit under a long form where nobody
   would see it while typing, so it is hidden rather than stacked. */
.form-layout { display: grid; gap: 1.5rem; align-items: start; }
@media (min-width: 62rem) {
  .form-layout { grid-template-columns: minmax(0, 44rem) 17rem; }
}
.form-preview { display: none; }
@media (min-width: 62rem) {
  .form-preview { display: block; position: sticky; top: 1rem; }
}
.form-preview .card { margin-top: .6rem; pointer-events: none; }
.form-preview .card-media img { width: 100%; height: 100%; object-fit: cover; }

/* The viewer. Fixed, above everything, its own stacking context. */
.lightbox {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .88);
  padding: 1rem;
}
.lightbox[hidden] { display: none; }
body.has-lightbox { overflow: hidden; }

.lightbox-image {
  max-width: min(100%, 62rem); max-height: 86vh;
  object-fit: contain; border-radius: .4rem;
}
.lightbox-nav, .lightbox-close {
  position: absolute; background: rgba(255, 255, 255, .12); color: #fff;
  border: 0; border-radius: 999px; cursor: pointer; line-height: 1;
  display: grid; place-items: center;
}
.lightbox-nav { top: 50%; transform: translateY(-50%); width: 3rem; height: 3rem; font-size: 2rem; }
.lightbox-prev { left: .75rem; }
.lightbox-next { right: .75rem; }
.lightbox-close { top: .75rem; right: .75rem; width: 2.5rem; height: 2.5rem; font-size: 1.6rem; }
.lightbox-nav:hover, .lightbox-close:hover { background: rgba(255, 255, 255, .26); }
.lightbox-nav:focus-visible, .lightbox-close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.lightbox-count {
  position: absolute; bottom: 1rem; left: 50%; transform: translateX(-50%);
  color: rgba(255, 255, 255, .82); font-size: .85rem;
}

/* On a phone the arrows would sit on top of the photograph, so they move to
   the bottom corners where a thumb already is. */
@media (max-width: 34rem) {
  .lightbox-nav { top: auto; bottom: 1rem; transform: none; width: 2.75rem; height: 2.75rem; }
  .lightbox-count { bottom: 1.6rem; }
}

.gallery-main img { cursor: zoom-in; }

/* The preview swaps between the chosen photograph and the empty state with the
   `hidden` attribute. `.card-media img` sets width and height, and a sizing
   rule does not beat `display: none` -- but stating it here means a later
   author rule that DOES set display cannot silently un-hide either one. */
.form-preview [hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
   /my -- the account header, the quota strip, and the tabs.
   --------------------------------------------------------------------------- */

.account-head {
  display: grid; gap: 1rem; margin: 1rem 0 1.25rem;
  padding: 1.1rem; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface);
}
@media (min-width: 48rem) {
  .account-head { grid-template-columns: 1fr auto; align-items: center; }
}

.account-roles { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
.chip-role { background: var(--surface-2); }
.chip-ok   { background: var(--ok-bg, #E8F6EE); color: var(--ok-ink, #1B7A45); }
.chip-warn { background: var(--surface-2); color: var(--pink-ink); }

.account-quota { display: grid; gap: .6rem; justify-items: start; }
@media (min-width: 48rem) { .account-quota { justify-items: end; } }

.quota-head {
  display: flex; gap: .6rem; align-items: baseline;
  font-size: .82rem; color: var(--ink-3);
}
.quota-days { color: var(--ink-2); }

/* Three fixed columns rather than auto-fit: the tiers are always the same
   three, and letting them reflow makes the numbers jump between renders. */
.quota-tiers { display: grid; grid-template-columns: repeat(3, minmax(4.5rem, 1fr)); gap: .5rem; }
.quota-tier {
  display: grid; justify-items: center; gap: .1rem;
  padding: .5rem .4rem; border-radius: .5rem;
  background: var(--surface-2); border: 1px solid var(--line);
}
.quota-tier.is-blocked { opacity: .55; }
.quota-n { font-size: 1.35rem; line-height: 1; }
.quota-label { font-size: .72rem; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); }
.quota-sub { font-size: .7rem; color: var(--ink-3); }
.quota-refusal { margin: 0; font-size: .82rem; color: var(--pink-ink); }

/* The tablist scrolls sideways on a narrow screen instead of wrapping into a
   second row that pushes the panel down. */
.tablist {
  display: flex; gap: .25rem; overflow-x: auto;
  border-bottom: 1px solid var(--line); margin-bottom: 1rem;
  scrollbar-width: none;
}
.tablist::-webkit-scrollbar { display: none; }

.tab {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: .4rem;
  padding: .6rem .85rem; border: 0; background: none; cursor: pointer;
  color: var(--ink-3); font: inherit; font-size: .92rem;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .15s, border-color .15s;
}
.tab:hover { color: var(--ink); }
.tab.is-active { color: var(--pink-ink); border-bottom-color: var(--pink-ink); }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tab-count {
  font-size: .74rem; padding: .05rem .38rem; border-radius: 999px;
  background: var(--surface-2); color: var(--ink-3);
}
.tab.is-active .tab-count { background: var(--brand-bg); color: #fff; }

.tabpanel[hidden] { display: none; }
.tabpanel > .section:first-child { margin-top: 0; }

.package-top { display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between; align-items: flex-start; }
.package-name { font-size: 1.05rem; font-weight: 600; }
.package-days { text-align: right; }
.package-days .num { font-size: 1.5rem; line-height: 1; display: block; }
.package-days small { font-size: .74rem; color: var(--ink-3); }

/* The bin, the row actions, and the package countdown. */
.row-actions { display: flex; flex-wrap: wrap; gap: .3rem; align-items: center; }
.btn-danger { color: var(--pink-ink); }
.btn-danger:hover { background: var(--surface-2); border-color: var(--pink-ink); }

/* Tabular figures and a reserved width, so a countdown ticking once a second
   does not shift everything beside it on each repaint. */
.quota-days {
  font-variant-numeric: tabular-nums;
  min-width: 8.5ch; display: inline-block; text-align: right;
}
.quota-days.is-soon { color: var(--pink-ink); font-weight: 600; }
.quota-days.is-expired { color: var(--pink-ink); }

/* A photograph on the edit page, with its own remove control. */
.edit-image { position: relative; margin: 0; }
.edit-image img {
  width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: .5rem;
  display: block;
}
.edit-image figcaption { font-size: .72rem; color: var(--ink-3); margin-top: .2rem; }
.edit-image-del { position: absolute; top: .3rem; right: .3rem; margin: 0; }
.edit-image-del button {
  width: 1.6rem; height: 1.6rem; border-radius: 999px; cursor: pointer;
  border: 0; background: rgba(0, 0, 0, .6); color: #fff;
  font-size: 1.05rem; line-height: 1; display: grid; place-items: center;
}
.edit-image-del button:hover { background: var(--brand-bg); }
.edit-image-del button:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }

/* The buy-a-package call to action. Not full width: at a container width it
   would read as a form submit for the page rather than a link off it. */
.btn-contact {
  display: inline-flex; align-items: center; gap: .45rem;
  margin-top: 1rem; padding: .6rem 1.1rem; font-size: .95rem;
}

/* Long-form pages: the disclaimer and the how-to. A measure near 65 characters
   is where continuous prose stops being tiring to read. */
.prose { max-width: 44rem; }
.prose h2 { font-size: 1.08rem; margin: 1.8rem 0 .5rem; }
.prose p { color: var(--ink-2); line-height: 1.75; margin: 0 0 .8rem; }
.prose a { color: var(--pink-ink); }
.prose .pill { vertical-align: middle; }

.steps { list-style: none; counter-reset: none; padding: 0; margin: 1.25rem 0 0; }
.step {
  padding: 1rem 0 1.1rem;
  border-top: 1px solid var(--line);
}
.step:first-child { border-top: 0; padding-top: 0; }
.step h2 { margin: 0 0 .45rem; font-size: 1.05rem; }
.step .btn { margin-right: .35rem; margin-top: .3rem; }

/* The permanent delete, folded away behind a disclosure. It is the only
   action on the site with no undo, so it does not sit open beside the
   reversible ones. */
.danger-zone > summary { cursor: pointer; color: var(--pink-ink); font-size: .85rem; }
.danger-zone form { margin-top: .4rem; display: grid; gap: .3rem; max-width: 15rem; }
.danger-zone label { font-size: .74rem; color: var(--ink-3); }
.danger-zone .input { font-size: .8rem; padding: .3rem .45rem; }

/* The homepage "load more". Centred and given room, so it reads as the end of
   the grid rather than another card in it. */
.load-more-wrap { display: flex; justify-content: center; margin-top: 1.25rem; }
.load-more { min-width: 14rem; }
.load-more.is-loading { opacity: .65; pointer-events: none; }
