/* Wishlist — one stylesheet, shared by the public list and the admin panel.
 *
 * The whole look rests on three rules, and breaking any one of them is what
 * would make this read as generic product UI instead of a printed catalogue:
 *
 *   1. No cards. Items are separated by 1px hairlines, never by borders,
 *      shadows, or rounded boxes. This does most of the work.
 *   2. Hierarchy comes from size, letter-spacing and colour — never from bold.
 *      Inter is used at 400 and 500 only.
 *   3. Exactly one typographic exception exists: the guest's city pseudonym,
 *      set in Cormorant Garamond italic. Nothing else in the product is ever
 *      set in a serif. That restraint is what keeps the one playful element
 *      from reading as decoration.
 *
 * Warm greys and a warm near-black (not #FFF/#000) plus an oxide-red accent
 * are deliberate: pure black-on-white with a saturated blue is the universal
 * SaaS signature, and this should feel like ink on paper.
 */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-500-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cormorant';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/cormorant-italic-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Cormorant';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/cormorant-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --paper: #f7f6f3;
  --paper-sunk: #efede8;
  --ink: #16150f;
  --ink-60: #6e6b62;
  --ink-30: #b4b0a6;
  --rule: #e2dfd8;
  --accent: #8a3b2e;

  --serif: 'Cormorant', Georgia, 'Times New Roman', serif;
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  --gutter: 24px;
  --ease: cubic-bezier(.2, 0, 0, 1);
  --dur: 140ms;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-variant-numeric: tabular-nums;
}

img { max-width: 100%; display: block; }
a { color: inherit; }

::selection { background: var(--ink); color: var(--paper); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.shell {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* --- shared type ---------------------------------------------------------- */

.label {
  font-size: .6875rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-60);
  font-weight: 400;
}

.city {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: .01em;
}

.muted { color: var(--ink-60); }

/* --- masthead ------------------------------------------------------------- */

.masthead {
  padding: clamp(56px, 12vw, 128px) 0 clamp(36px, 7vw, 72px);
  border-bottom: 1px solid var(--rule);
}

.masthead__meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin-bottom: clamp(28px, 6vw, 56px);
}

.masthead h1 {
  margin: 0;
  font-size: clamp(2.75rem, 11vw, 6.5rem);
  line-height: .95;
  letter-spacing: -.035em;
  font-weight: 400;
}

.masthead__intro {
  margin: clamp(20px, 4vw, 32px) 0 0;
  max-width: 44ch;
  color: var(--ink-60);
  font-size: 1rem;
}

/* --- gift grid ------------------------------------------------------------ */

/* Hairlines, not cards. The 1px gap plus a background on the container is a
 * deliberate trick: it draws a continuous rule between every neighbour at any
 * column count, which per-item borders can't do without doubling up. */
.grid {
  display: grid;
  /* min(288px, 100%) rather than a bare 288px: on a 320px-wide phone the
     available width after gutters is under 288, and a hard minimum would make
     every column overflow the viewport instead of collapsing to one. */
  grid-template-columns: repeat(auto-fill, minmax(min(288px, 100%), 1fr));
  gap: 1px;
  background: var(--rule);
  border-bottom: 1px solid var(--rule);
}

.item {
  background: var(--paper);
  padding: 28px 0 36px;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* The hairline must not run edge-to-edge into the page gutter — inset it so
 * the grid reads as a table of contents, not a wireframe. */
.grid > .item { padding-left: clamp(0px, 2vw, 28px); padding-right: clamp(0px, 2vw, 28px); }

.item__index {
  font-size: .6875rem;
  letter-spacing: .12em;
  color: var(--ink-30);
  margin-bottom: 14px;
}

/* Square. A fixed ratio for every photo is what keeps the grid from going
 * ragged, and 1:1 is the one that costs least: product shots from shops are
 * overwhelmingly square already, so they land uncropped, while a portrait or
 * landscape photo loses its edges rather than the whole grid losing its
 * rhythm. (Was 4:5, which quietly cropped the top and bottom off every square
 * shop image.) */
.item__figure {
  aspect-ratio: 1 / 1;
  background: var(--paper-sunk);
  overflow: hidden;
  border-radius: 2px;
  margin-bottom: 18px;
}

.item__figure img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity var(--dur) var(--ease);
}

.item__figure--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-30);
  font-size: .6875rem;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.item__title {
  margin: 0 0 6px;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Product names arrive with long model strings that have nowhere to break. */
  overflow-wrap: anywhere;
}

.item__price {
  font-size: 1rem;
  color: var(--ink);
  margin-bottom: 16px;
}

.item__price--none { color: var(--ink-30); }

.item__foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 6px 12px;
  flex-wrap: wrap;
}

.item__status {
  font-size: .6875rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-30);
}

.item__status .city {
  font-size: .95rem;
  text-transform: none;
  letter-spacing: .01em;
  color: var(--accent);
}

.item--taken .item__figure img { opacity: .45; }
.item--taken .item__title,
.item--taken .item__price { color: var(--ink-30); }

.item--mine { }

@media (hover: hover) {
  .item__open:hover ~ .item__figure img,
  .item__figure:hover img { opacity: .88; }
}

/* Whole-item tap target, kept behind the real controls so links and buttons
 * still win the click. */
.item__open {
  position: absolute;
  inset: 0;
  z-index: 1;
  border: 0;
  background: transparent;
  cursor: pointer;
  font: inherit;
  color: transparent;
  padding: 0;
}

.item__foot, .item__actions { position: relative; z-index: 2; }

/* --- buttons -------------------------------------------------------------- */

.btn {
  font: inherit;
  font-size: .8125rem;
  letter-spacing: .02em;
  padding: 11px 18px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  border-radius: 2px;
  cursor: pointer;
  transition: opacity var(--dur) var(--ease), background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover { opacity: .84; }
.btn:disabled { opacity: .35; cursor: not-allowed; }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule);
}
.btn--ghost:hover { border-color: var(--ink); opacity: 1; }

.btn--quiet {
  background: transparent;
  border-color: transparent;
  color: var(--ink-60);
  padding: 11px 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--rule);
}
.btn--quiet:hover { color: var(--ink); text-decoration-color: var(--ink-30); opacity: 1; }

.btn--danger { border-color: var(--accent); color: var(--accent); background: transparent; }

.btn--full { width: 100%; }

.linkout {
  font-size: .8125rem;
  color: var(--ink-60);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 1px;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.linkout:hover { color: var(--ink); border-color: var(--ink-30); }

/* A button that reads as a link — used for secondary actions that shouldn't
   compete with the filled primary button. */
.link-btn {
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  font-size: .8125rem;
  color: var(--ink-60);
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--rule);
  transition: color var(--dur) var(--ease);
}
.link-btn:hover { color: var(--ink); }
.link-btn--danger:hover { color: var(--accent); }

/* Reserve button on the card. Full width so it reads as the card's action
   rather than a stray control floating next to the price. */
.item__reserve { width: 100%; }

/* Detail sheet: the photo is bounded by viewport height, not sheet width, so
   the price and the reserve button stay on screen without scrolling. */
.sheet__figure {
  max-height: 34vh;
  margin: 0 auto 18px;
  border-radius: 2px;
  overflow: hidden;
  background: var(--paper-sunk);
  display: flex;
  justify-content: center;
}
.sheet__figure img {
  max-height: 34vh;
  width: auto;
  object-fit: contain;
  transition: opacity var(--dur) var(--ease);
}
/* When the gift has a store link the photo is that link — the thing people
   instinctively tap. Kept as a plain <a> so it gets middle-click, "open in new
   tab" and a real status-bar URL, which a click handler wouldn't. */
a.sheet__figure { cursor: pointer; display: block; }
a.sheet__figure img { margin: 0 auto; }
@media (hover: hover) {
  a.sheet__figure:hover img { opacity: .88; }
}

/* --- my reservations ------------------------------------------------------ */

.mine-row {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  gap: 16px;
  padding: 16px 0;
  border-top: 1px solid var(--rule);
}
.mine-row:last-child { border-bottom: 1px solid var(--rule); }
.mine-row__thumb {
  width: 64px;
  aspect-ratio: 1/1;
  border-radius: 2px;
  overflow: hidden;
  background: var(--paper-sunk);
}
.mine-row__thumb img { width: 100%; height: 100%; object-fit: cover; }
.mine-row__title { font-weight: 500; line-height: 1.3; overflow-wrap: anywhere; }
.mine-row__meta { font-size: .8125rem; color: var(--ink-60); margin: 2px 0 8px; }
.mine-row__links { display: flex; gap: 16px; flex-wrap: wrap; align-items: baseline; }

/* --- forms ---------------------------------------------------------------- */

.field { margin-bottom: 20px; }

.field > label {
  display: block;
  font-size: .6875rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-60);
  margin-bottom: 8px;
}

input[type=text], input[type=url], input[type=tel], input[type=password],
input[type=number], input[type=date], textarea {
  width: 100%;
  font: inherit;
  font-size: 1rem;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 10px 0;
  transition: border-color var(--dur) var(--ease);
  -webkit-appearance: none;
  appearance: none;
}
input:focus, textarea:focus {
  outline: none;
  border-bottom-color: var(--ink);
}
input::placeholder, textarea::placeholder { color: var(--ink-30); }
textarea { resize: vertical; min-height: 68px; }

/* Honeypot: hidden from humans, irresistible to naive bots. Not display:none —
 * some bots skip those; this is off-screen but still "visible" to a scraper. */
.trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- sheet (modal) -------------------------------------------------------- */

.sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(22, 21, 15, .34);
  z-index: 40;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
.sheet-backdrop.open { opacity: 1; pointer-events: auto; }

.sheet {
  position: fixed;
  z-index: 41;
  background: var(--paper);
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  padding: 36px;
  border-radius: 2px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
.sheet.open { opacity: 1; pointer-events: auto; }

@media (max-width: 640px) {
  .sheet {
    left: 0;
    top: auto;
    bottom: 0;
    transform: none;
    width: 100%;
    max-height: 88vh;
    padding: 28px 20px calc(28px + env(safe-area-inset-bottom));
    border-radius: 2px 2px 0 0;
  }
}

.sheet__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin-bottom: 24px;
}

.sheet__title { margin: 0; font-size: 1.25rem; font-weight: 500; letter-spacing: -.01em; }

.sheet__close {
  border: 0;
  background: transparent;
  font: inherit;
  font-size: .6875rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-60);
  cursor: pointer;
  padding: 4px 0;
}
.sheet__close:hover { color: var(--ink); }

/* --- the reveal ----------------------------------------------------------- */

/* The one moment the product allows itself. Everything is stripped away so a
 * single word carries the screen — which is exactly why it reads as a gift
 * rather than a gimmick. */
.reveal { text-align: center; padding: 24px 0 8px; }

.reveal__label {
  font-size: .6875rem;
  text-transform: uppercase;
  letter-spacing: .18em;
  color: var(--ink-60);
  margin-bottom: 18px;
}

.reveal__city {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(3rem, 14vw, 4.75rem);
  line-height: 1;
  letter-spacing: .005em;
  margin: 0 0 22px;
  animation: fade var(--dur) var(--ease) both;
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.reveal__note {
  color: var(--ink-60);
  font-size: .9375rem;
  max-width: 34ch;
  margin: 0 auto 28px;
}

/* --- toast ---------------------------------------------------------------- */

/* Anchored to the top, not the bottom. A bottom toast lands exactly on top of
   the sheet's action button on mobile (the sheet is bottom-anchored and the
   toast outranks it in z-index), so the confirmation covered the very control
   that had just been pressed. The top edge is free in every layout here. */
.toast {
  position: fixed;
  left: 50%;
  top: calc(16px + env(safe-area-inset-top));
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--paper);
  font-size: .875rem;
  padding: 12px 20px;
  border-radius: 2px;
  z-index: 60;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
  max-width: calc(100vw - 40px);
  text-align: center;
}
.toast.show { opacity: 1; }
.toast--error { background: var(--accent); }

/* --- footer / misc -------------------------------------------------------- */

.foot {
  padding: 40px 0 56px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
  color: var(--ink-60);
  font-size: .8125rem;
}

.empty {
  padding: 96px 0;
  text-align: center;
  color: var(--ink-30);
  font-size: .875rem;
  letter-spacing: .04em;
}

.spinner {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 1px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin .7s linear infinite;
  vertical-align: -1px;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
