/* ── RESET ── */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;}

/* ── TOKENS ── */
:root{
  --cream:#F3EAD3;
  --cream-dark:#E6D5B2;
  --cream-deeper:#D9C395;
  --forest:#1C4728;
  --forest-mid:#275E37;
  --forest-light:#3A7A4A;
  --ivory:#FAFAF7;
  --red:#7A1818;
  --red-mid:#9E1E1E;
  --red-light:#F5E4E4;
  --gold:#9A7209;
  --gold-light:#F0E3B0;
  --ink:#141008;
  --ink-mid:#332A1A;
  --ink-light:#6E5E42;
  --tile-white:#F7F3EB;
  --tile-green:#2A5438;
  --border:#BEA882;
  --border-light:#D8C9AD;
  /* Per-player identity colors for the table facilitator (fe/table-ui.js) —
     deliberately distinct from --forest/--gold/--red, which already carry
     UI meaning (primary action / highlight-state / error) elsewhere. Used
     on the name tag itself (.mj-name-tag) — the table's own felt (.mj-table)
     is a uniform green, not tinted per player. */
  --seat-0:#0F6B72;
  --seat-1:#8E3B7A;
  --seat-2:#3A5FA0;
  --seat-3:#C1631A;
}

/* ── BASE ── */
body{
  background:var(--cream);
  color:var(--ink);
  font-family:'Inter',sans-serif;
  font-size:15px;
  line-height:1.6;
  min-height:100vh;
  overflow-x:hidden;
}

/* FELT TABLE TEXTURE */
body::before{
  content:'';
  position:fixed;inset:0;z-index:0;
  background:
    radial-gradient(ellipse 120% 80% at 50% -10%,rgba(30,77,43,0.08) 0%,transparent 60%),
    radial-gradient(ellipse 80% 60% at 100% 100%,rgba(139,28,28,0.06) 0%,transparent 50%);
  pointer-events:none;
}

/* TILE PATTERN */
body::after{
  content:'';
  position:fixed;inset:0;z-index:0;
  background-image:url("data:image/svg+xml,%3Csvg width='80' height='80' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='8' y='8' width='26' height='32' rx='4' fill='none' stroke='%231E4D2B' stroke-width='0.6' opacity='0.07'/%3E%3Crect x='46' y='8' width='26' height='32' rx='4' fill='none' stroke='%231E4D2B' stroke-width='0.6' opacity='0.07'/%3E%3Crect x='8' y='46' width='26' height='32' rx='4' fill='none' stroke='%231E4D2B' stroke-width='0.6' opacity='0.07'/%3E%3Crect x='46' y='46' width='26' height='32' rx='4' fill='none' stroke='%231E4D2B' stroke-width='0.6' opacity='0.07'/%3E%3C/svg%3E");
  pointer-events:none;
  opacity:0.6;
}

.wrap{
  position:relative;z-index:1;
  max-width:620px;margin:0 auto;
  padding:0 1rem 5rem;
}
/* Same single-column flow, just less cramped once there's room for it —
   no restructuring into multi-column sections. */
@media(min-width:900px){
  .wrap{max-width:760px;}
}
.admin-wrap{
  position:relative;z-index:1;
  width:100%;box-sizing:border-box;
  padding:1.5rem 1.5rem 5rem;
}

/* ── ADMIN BAR ── */
.admin-bar{
  position:fixed;top:14px;right:14px;z-index:200;
}
.admin-btn{
  padding:6px 16px;
  border:1.5px solid var(--border-light);
  background:transparent;
  color:var(--ink-light);
  font-family:'Inter',sans-serif;
  font-size:12px;font-weight:500;
  border-radius:100px;cursor:pointer;
  transition:all 0.18s;
  letter-spacing:0.04em;
}
.admin-btn:hover{border-color:var(--forest);color:var(--forest);}
.admin-btn.logged-in{background:var(--forest);color:var(--ivory);border-color:var(--forest);}

/* ── HERO ── */
.hero{
  text-align:center;
  padding:2.5rem 1rem 1rem;
  position:relative;
}
.logo-wrap{
  width:225px;height:225px;
  margin:0 auto 1.2rem;
  display:flex;align-items:center;justify-content:center;
}
.logo-wrap img{width:100%;height:100%;object-fit:contain;}

.hero-kicker{
  font-size:10px;font-weight:500;
  letter-spacing:0.3em;text-transform:uppercase;
  color:var(--gold);margin-bottom:8px;
  font-family:'Inter',sans-serif;
}
.hero-title{
  font-family:'Cormorant Garamond',serif;
  font-size:clamp(3rem,9vw,4.8rem);
  font-weight:300;
  color:var(--ink);line-height:0.95;
  letter-spacing:-0.01em;
  margin-bottom:8px;
}
.hero-title em{color:var(--forest);font-style:italic;font-weight:400;}
.hero-subtitle{
  font-size:14px;color:var(--ink-light);
  margin-bottom:1.4rem;font-weight:300;
  letter-spacing:0.01em;
  line-height:1.7;
  max-width:480px;
  margin-left:auto;
  margin-right:auto;
}

.promo-banner{
  background:linear-gradient(135deg,#fff3e0,#ffe0b2);
  border:1.5px solid #e65100;
  border-radius:12px;
  padding:10px 16px;
  margin-bottom:1rem;
  font-size:13px;font-weight:600;line-height:1.5;
  color:#bf360c;
}
.pill-row{
  display:flex;gap:7px;justify-content:center;flex-wrap:wrap;
  margin-bottom:0.5rem;
}
.pill{
  padding:4px 13px;border-radius:100px;
  font-size:12px;font-weight:500;letter-spacing:0.02em;
  border:1.5px solid;
}
.pill-forest{background:rgba(30,77,43,0.08);border-color:rgba(30,77,43,0.25);color:var(--forest);}
.pill-red{background:rgba(139,28,28,0.07);border-color:rgba(139,28,28,0.2);color:var(--red);}
.pill-gold{background:rgba(184,134,11,0.08);border-color:rgba(184,134,11,0.22);color:var(--gold);}

/* ORNAMENT */
.orn{
  text-align:center;
  color:var(--border);
  font-size:16px;letter-spacing:10px;
  margin:1.6rem 0;
  user-select:none;
  opacity:0.7;
}

/* ── SECTION ── */
.section{margin-bottom:1.8rem;}
.section-label{
  font-size:9.5px;font-weight:600;
  letter-spacing:0.28em;text-transform:uppercase;
  color:var(--ink-light);
  margin-bottom:12px;
  display:flex;align-items:center;gap:10px;
  font-family:'Inter',sans-serif;
}
.section-label::after{
  content:'';flex:1;height:1px;
  background:linear-gradient(90deg,var(--border),transparent);
  opacity:0.5;
}

/* ── INFO CARDS ── */
.info-grid{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:8px;
  margin-bottom:1.6rem;
}
.info-card{
  background:transparent;
  border:1.5px solid var(--border-light);
  border-radius:12px;
  padding:0.9rem 1rem;
  position:relative;overflow:hidden;
  pointer-events:none;
  user-select:none;
}
.info-card::before{
  content:'';
  position:absolute;top:0;left:0;right:0;height:2px;
  background:var(--border);
  border-radius:12px 12px 0 0;
}
.info-card .ic-icon{font-size:18px;margin-bottom:5px;display:block;}
.info-card .ic-label{font-size:10px;font-weight:600;letter-spacing:0.14em;text-transform:uppercase;color:var(--ink-light);margin-bottom:2px;}
.info-card .ic-val{font-size:14px;font-weight:600;color:var(--ink);line-height:1.3;}
.info-card .ic-sub{font-size:12px;color:var(--ink-light);margin-top:2px;}

/* ── BANNER ── */
.info-dropdown{border:1px solid var(--border-light);border-radius:6px;background:var(--ivory);overflow:hidden;}
.info-dropdown summary{padding:11px 14px;font-size:13px;font-weight:600;color:var(--ink-mid);cursor:pointer;list-style:none;display:flex;align-items:center;gap:4px;user-select:none;}
.info-dropdown summary::-webkit-details-marker{display:none;}
.info-dropdown summary::after{content:'›';margin-left:auto;font-size:16px;color:var(--ink-light);transition:transform 0.2s;display:inline-block;}
.info-dropdown[open] summary::after{transform:rotate(90deg);}
.info-dropdown-body{padding:10px 14px 12px;font-size:12.5px;color:var(--ink-light);line-height:1.7;border-top:1px solid var(--border-light);}
.banner{
  border-radius:8px;
  padding:1.1rem 1.2rem;
  margin-bottom:1.4rem;
  display:flex;gap:14px;align-items:flex-start;
}
.banner-forest{
  background:var(--forest);color:var(--ivory);
}
.banner-gold{
  background:var(--gold-light);
  border:1.5px solid rgba(184,134,11,0.25);
  color:var(--ink);
}
.banner-red{
  background:var(--red-light);
  border:1.5px solid rgba(139,28,28,0.18);
  color:var(--ink);
}
.banner-icon{font-size:22px;flex-shrink:0;margin-top:1px;}
.banner-body{}
.banner-title{font-weight:600;font-size:14px;margin-bottom:3px;}
.banner-forest .banner-title{color:var(--ivory);}
.banner-text{font-size:13px;line-height:1.6;opacity:0.88;}

/* ── DATE TABS ── */
.date-grid{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:8px;
  margin-bottom:1.4rem;
}
@media(max-width:380px){.date-grid{grid-template-columns:repeat(3,1fr);}}
.loading-state{grid-column:1/-1;display:flex;align-items:center;justify-content:center;gap:8px;padding:1.5rem 1rem;color:var(--ink-light);font-size:13px;}
.spinner{display:inline-block;width:14px;height:14px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:spin 0.7s linear infinite;flex-shrink:0;opacity:0.85;margin-right:6px;vertical-align:-2px;}
@keyframes spin{to{transform:rotate(360deg);}}
@media(prefers-reduced-motion:reduce){.spinner{animation:none;}}
.date-card{
  background:var(--ivory);
  border:1px solid var(--border-light);
  border-radius:8px;
  padding:0.9rem 0.7rem;
  cursor:pointer;
  transition:all 0.2s;
  text-align:center;
  position:relative;
}
.date-card:hover{border-color:var(--forest);transform:translateY(-2px);box-shadow:0 6px 20px rgba(28,71,40,0.12);}
.date-card.active{
  background:var(--forest);
  border:2px solid var(--card-text, var(--ivory));
  color:var(--ivory);
  box-shadow:0 4px 20px rgba(28,71,40,0.25);
}
/* Same treatment for the "Selected" checkmark (.dc-selected-tag) and the
   card's own border on selection: both read --card-text directly (the
   event's own admin-picked Text color — see cardColorVars, set inline on
   every card regardless of badge/theme) rather than the inherited
   currentColor, which for a themed card's active state resolves to
   --card-accent-text (a computed contrast color against the accent, not
   what the admin actually picked as "Text"). No new color either way —
   still one of the 3 colors an admin already chose for this event, just
   the right one of the three. */
/* Always in the markup, shown only via .active — selectDate() toggles that
   class directly (no re-render of the grid), so visibility has to be driven
   by CSS here rather than by whether the tag exists in the HTML at all. */
.date-card .dc-selected-tag{
  display:none;
  position:absolute;top:8px;right:8px;
  width:18px;height:18px;
  align-items:center;justify-content:center;
  font-size:10px;font-weight:700;
  border-radius:50%;
  border:1.2px solid var(--card-text, var(--ivory));
  color:var(--card-text, var(--ivory));
}
.date-card.active .dc-selected-tag{display:flex;}
.date-card .dc-day{font-size:9px;font-weight:600;letter-spacing:0.15em;text-transform:uppercase;opacity:0.7;margin-bottom:4px;font-family:'Montserrat',sans-serif;}
.date-card .dc-num{font-family:'Montserrat',sans-serif;font-size:1.7rem;line-height:1;font-weight:700;}
.date-card .dc-month{font-size:9px;font-weight:600;opacity:0.7;letter-spacing:0.1em;text-transform:uppercase;font-family:'Montserrat',sans-serif;}
.date-card .dc-time{font-size:11px;opacity:0.6;margin-top:4px;}
.date-card .dc-area{font-size:10px;font-weight:600;color:var(--ink-light);margin-top:4px;letter-spacing:0.04em;opacity:0.8;}
a.dc-area{display:block;text-decoration:none;cursor:pointer;}
a.dc-area .dc-area-text{text-decoration:underline;text-decoration-color:currentColor;text-underline-offset:2px;}
.date-card.active .dc-area{opacity:0.7;color:rgba(255,255,255,0.8);}
.date-card .dc-badge{
  font-size:8px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;
  margin-top:6px;padding:2px 7px;border-radius:3px;display:inline-block;
  font-family:'Inter',sans-serif;
}
.date-card .dc-status{
  font-size:8px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;
  margin-top:6px;padding:2px 7px;border-radius:3px;display:inline-block;
  font-family:'Inter',sans-serif;
  background:var(--red);color:var(--ivory);
}
.date-card-closed{opacity:0.6;}
.date-card-closed:hover{transform:none;box-shadow:none;}
.date-card-collab{
  border-color:var(--card-bg2,#c8a84b) !important;
  background:linear-gradient(135deg,var(--card-bg,#fffbef),var(--card-bg2,#fff5d6)) !important;
  color:var(--card-text,inherit) !important;
}
.date-card-collab .dc-day,.date-card-collab .dc-num,.date-card-collab .dc-month,.date-card-collab .dc-area{color:var(--card-text,inherit) !important;}
.date-card-collab .dc-badge{background:var(--card-accent,#c8a84b);color:var(--card-accent-text,white);}
.date-card-collab.active{background:var(--card-accent,#c8a84b) !important;border:2px solid var(--card-text,white) !important;color:var(--card-accent-text,white) !important;}
.date-card-collab.active .dc-day,.date-card-collab.active .dc-num,.date-card-collab.active .dc-month,.date-card-collab.active .dc-area{color:var(--card-accent-text,white) !important;}
.sc-price{font-size:13px;font-weight:700;color:var(--forest);margin-top:5px;}
.sc-perks{font-size:10px;color:var(--ink-light);margin-top:4px;line-height:1.4;border-top:1px solid var(--border-light);padding-top:5px;}

/* ── SESSION CARDS ── */
.session-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;
  margin-bottom:1.4rem;
}
@media(max-width:380px){.session-grid{grid-template-columns:repeat(auto-fit,minmax(100px,1fr));}}
@media(min-width:768px){.session-grid{grid-template-columns:repeat(3,1fr);}}
.exp-btn{
  flex:1;min-width:160px;
  background:var(--ivory);
  border:1.5px solid var(--border-light);
  border-radius:12px;
  padding:12px;
  cursor:pointer;
  text-align:left;
  transition:all 0.2s ease;
  color:var(--ink);
}
.exp-btn:hover{
  border-color:var(--forest);
  box-shadow:0 4px 12px rgba(34,79,53,0.1);
}
.exp-btn.sel{
  background:var(--forest);
  color:white;
  border-color:var(--forest);
}
.exp-btn.sel div:nth-child(2){
  color:rgba(255,255,255,0.85);
}
.session-card{
  background:var(--ivory);
  border:1px solid var(--border-light);
  border-radius:8px;padding:1rem;
  cursor:pointer;transition:all 0.2s;
  position:relative;
}
.session-card:hover{border-color:var(--forest);transform:translateY(-2px);box-shadow:0 6px 18px rgba(28,71,40,0.1);}
.session-card.active{border-color:var(--forest);box-shadow:0 0 0 2px rgba(30,77,43,0.15);}
/* A selected waitlist-only session gets the same gold the sc-tag itself
   already uses (var(--gold)) instead of staying forest-green — the border
   should match whichever color is already telling the guest "waitlist",
   not a fixed color regardless of state. */
.session-card.active.session-card-waitlist{border-color:var(--gold);box-shadow:0 0 0 2px rgba(154,114,9,0.15);}
.sc-tag{
  position:absolute;top:10px;right:10px;
  width:20px;height:20px;
  display:flex;align-items:center;justify-content:center;
  font-size:11px;font-weight:700;
  border-radius:50%;
}
.sc-tag-active{background:var(--forest);color:var(--ivory);}
.sc-tag-waitlist{background:var(--gold);color:#fff;}
.waitlist-note{
  font-size:12.5px;line-height:1.6;color:var(--ink-mid);
  background:var(--gold-light);border:1.5px solid rgba(184,134,11,0.25);
  border-radius:8px;padding:9px 12px;margin-top:10px;margin-bottom:4px;
}
.waitlist-note strong{color:var(--gold);}
.sc-num{font-size:9px;font-weight:500;letter-spacing:0.24em;text-transform:uppercase;color:var(--ink-light);margin-bottom:5px;font-family:'Inter',sans-serif;}
.sc-time{font-family:'Montserrat',sans-serif;font-size:1.6rem;font-weight:700;color:var(--ink);line-height:1;}
.sc-arrive{font-size:11px;color:var(--forest-mid);font-weight:600;margin-top:5px;letter-spacing:0.01em;}
.sc-bar-wrap{margin-top:10px;}
.sc-bar-bg{height:5px;background:var(--cream-dark);border-radius:3px;overflow:hidden;}
.sc-bar-fill{height:100%;border-radius:3px;transition:width 0.4s;background:var(--forest-light);}
.sc-bar-fill.warn{background:var(--gold);}
.sc-bar-fill.danger{background:var(--red-mid);}
.sc-spots{font-size:11px;color:var(--ink-light);margin-top:4px;font-weight:500;}
/* Closed/full status is called out with this small tag, not by recoloring
   the whole card or line — the card stays the same as any other session. */
.sc-status-tag{
  display:inline-block;
  background:var(--gold);color:#fff;
  font-size:9px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  padding:2px 7px;border-radius:100px;
  margin-right:5px;
  vertical-align:1px;
}

/* ── PAX ── */
.pax-row{display:flex;align-items:center;gap:14px;margin-bottom:8px;}
.pax-btn{
  width:36px;height:36px;border-radius:50%;
  border:1.5px solid var(--border);
  background:var(--ivory);color:var(--ink);
  font-size:1.2rem;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  transition:all 0.15s;
}
.pax-btn:hover:not(:disabled){border-color:var(--forest);color:var(--forest);}
.pax-btn:disabled{opacity:0.3;cursor:not-allowed;}
.pax-num{font-family:'Cormorant Garamond',serif;font-size:2.4rem;font-weight:300;color:var(--ink);min-width:36px;text-align:center;}
.pax-label{font-size:13px;color:var(--ink-light);}
.pax-note{
  font-size:12px;color:var(--ink-mid);line-height:1.5;
}
.pax-note.private{color:var(--forest);font-weight:600;}

/* ── FORM ── */
.form-block{
  background:var(--ivory);
  border:1px solid var(--border-light);
  border-radius:6px;
  padding:1.1rem 1.1rem;
  margin-bottom:1.4rem;
}
.field{margin-bottom:10px;}
.field:last-child{margin-bottom:0;}
.field label{
  display:block;
  font-size:11px;font-weight:600;
  letter-spacing:0.12em;text-transform:uppercase;
  color:var(--ink-light);margin-bottom:5px;
}
.field input{
  width:100%;padding:10px 12px;
  border:1.5px solid var(--border-light);
  border-radius:9px;
  font-family:'Inter',sans-serif;font-size:14px;
  color:var(--ink);background:var(--cream);
  transition:border-color 0.15s;
}
.field input:focus{outline:none;border-color:var(--forest-mid);}
.field input::placeholder{color:var(--ink-light);}

/* ── DETAILS (one line per attendee: name + drink dropdown) ── */
/* One block per attendee — name+drink on one line, then WhatsApp for just
   the booker below it. .drink-pax-label is the "Person N" heading (kept
   from the old drinks-only design — same bulleted style). */
.detail-block{margin-bottom:14px;}
.drink-pax-label{
  font-size:12px;font-weight:600;
  color:var(--ink-mid);margin-bottom:7px;
  display:flex;align-items:center;gap:6px;
}
.drink-pax-label::before{
  content:'';width:12px;height:12px;border-radius:50%;
  background:var(--forest);flex-shrink:0;
  display:inline-block;
}
.detail-name-input, .detail-wa-input, .detail-drink-select{
  padding:10px 12px;
  border:1.5px solid var(--border-light);
  border-radius:9px;
  font-family:'Inter',sans-serif;font-size:14px;
  color:var(--ink);background:var(--ivory);
  transition:border-color 0.15s;
}
.detail-name-input:focus, .detail-wa-input:focus, .detail-drink-select:focus{outline:none;border-color:var(--forest-mid);}
.detail-name-input::placeholder, .detail-wa-input::placeholder{color:var(--ink-light);}
.detail-line{display:flex;gap:8px;margin-bottom:8px;}
.detail-line .detail-name-input{flex:0 1 60%;min-width:0;}
/* Native <select> arrows render inconsistently across browsers/OSes and
   clash with the rounded pill look every other field here already has —
   strip it (appearance:none) and draw one deliberate chevron instead, same
   color as the field's other muted text (--ink-light). */
.detail-line .detail-drink-select{
  flex:0 1 40%;min-width:0;font-size:13px;
  appearance:none;-webkit-appearance:none;-moz-appearance:none;
  padding-left:8px;padding-right:24px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%236E5E42' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 10px center;
}
.detail-wa-input{width:100%;display:block;}

/* ── FOOD INFO ── */
.food-row{
  display:flex;align-items:center;gap:10px;
  background:var(--red-light);
  border:1.5px solid rgba(139,28,28,0.15);
  border-radius:12px;padding:0.8rem 1rem;
  margin-bottom:1.4rem;
  font-size:13px;color:var(--red);font-weight:500;
}

/* ── SUBMIT ── */
.submit-btn{
  width:100%;padding:17px;
  background:var(--forest);border:none;
  color:var(--ivory);
  font-family:'Cormorant Garamond',serif;
  font-size:1.2rem;font-weight:400;letter-spacing:0.06em;
  border-radius:6px;cursor:pointer;
  transition:all 0.2s;
  box-shadow:0 8px 24px rgba(28,71,40,0.22);
}
.submit-btn:hover{background:var(--forest-mid);transform:translateY(-2px);box-shadow:0 12px 28px rgba(28,71,40,0.28);}
.submit-btn:disabled{opacity:0.45;cursor:not-allowed;transform:none;box-shadow:none;}

/* NOTICE */
.notice{
  background:rgba(154,114,9,0.05);
  border:1px solid rgba(154,114,9,0.18);
  border-radius:6px;padding:0.9rem 1rem;
  margin-top:1rem;font-size:12.5px;
  color:var(--ink-mid);line-height:1.7;
}
.notice strong{color:var(--gold);font-size:10px;letter-spacing:0.14em;text-transform:uppercase;display:block;margin-bottom:3px;}

/* ── MODALS ── */
.modal-bg{
  display:none;position:fixed;inset:0;z-index:300;
  background:rgba(26,18,9,0.6);backdrop-filter:blur(6px);
  align-items:center;justify-content:center;padding:1rem;
}
.modal-bg.open{display:flex;}
.modal{
  position:relative; /* anchors .modal-close in the corner */
  background:var(--ivory);
  border:1px solid var(--border);
  border-radius:10px;
  padding:1.5rem 2rem 1.5rem 1.5rem;
  width:360px;max-width:100%;
  max-height:85vh;
  overflow-y:auto;
  scrollbar-gutter:stable;
  -webkit-overflow-scrolling:touch;
  box-shadow:0 24px 60px rgba(0,0,0,0.2);
}
.modal-wide{
  width:920px;
  /* This variant scrolls only #upsert-fields (below), not the whole modal —
     the footer needs to be a real, separate flex region, not an overlay,
     or the scrolled content can render a sliver past its edge. */
  display:flex;flex-direction:column;
  overflow-y:hidden;
}
.modal-wide #upsert-title{flex-shrink:0;}
.modal-wide #upsert-fields{
  flex:1;min-height:0;overflow-y:auto;
  scrollbar-gutter:stable;
}
.modal input.uf-title-input{
  display:block;width:100%;
  border:none;border-bottom:2px solid var(--border-light);
  border-radius:0;
  font-family:'Cormorant Garamond',serif;
  font-size:1.7rem;font-weight:600;color:var(--forest);
  padding:4px 0;margin-bottom:1.2rem;
  background:transparent;
}
.modal input.uf-title-input:focus{outline:none;border-bottom-color:var(--forest);}
/* A filled-in title and an empty one otherwise look nearly identical (same
   big serif style, placeholder text vs typed text) — this gives a required
   title a visibly different border specifically while it's still showing
   its placeholder (i.e. still empty), so "this needs filling in" doesn't
   depend on noticing the placeholder text itself. Gold, not red — this
   isn't a validation failure yet (see .uf-invalid), just a "still needs
   attention" cue that clears the moment something's typed. */
.modal input.uf-title-input.uf-title-required:placeholder-shown{border-bottom-color:var(--gold);}
.uf-section-title{
  font-size:11px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;
  color:var(--ink-light);
  border-top:1px solid var(--border-light);
  padding-top:14px;margin-top:14px;margin-bottom:8px;
}
/* Always occupies its line (min-height), even with no message — an error
   that only appears once validation fails would otherwise make the field's
   own row grow/shift at that moment, which is exactly what looked "wonky"
   about the listMode repeater form once several fields' errors differed in
   length or only some fields had one at all. Reserving the space up front
   keeps every row's height stable regardless of validation state. */
.uf-field-error{color:var(--red);font-size:11px;margin-top:3px;min-height:14px;}
.uf-footer{
  flex-shrink:0; /* a real flex item below the scroll area, not an overlay —
    nothing can render underneath it since it isn't sharing space with the
    scrolled content at all */
  background:var(--ivory);
  padding-top:24px;
}
.uf-form-error{
  display:none;
  background:var(--red-light);
  border:1px solid var(--red-mid);
  border-radius:6px;
  padding:0.7rem 0.9rem;
  margin-bottom:16px;
  font-size:12.5px;color:var(--red);line-height:1.6;
}
.uf-form-error div + div{margin-top:3px;}
.modal input.uf-invalid,.modal select.uf-invalid,.modal textarea.uf-invalid{border-color:var(--red) !important;}
.uf-checklist{border:1.5px solid var(--border-light);border-radius:8px;padding:6px 10px;background:var(--cream);}
.uf-checklist.uf-invalid{border-color:var(--red) !important;}
.uf-checklist label + label{border-top:1px solid var(--border-light);}
/* `.modal input` sets width:100%/padding/margin for text fields — that also
   hits these checkboxes, stretching each one across the row and pushing its
   label to the far edge. Reset them back to their intrinsic size. */
.uf-checklist input[type="checkbox"]{
  width:auto;flex-shrink:0;
  padding:0;margin:0;
}
/* Same cascade problem inside a repeater row: `.modal input`'s margin-bottom
   applies to the text inputs but there's no matching `.modal select` rule, so
   inputs sat 8px higher than the selects beside them (the row is
   align-items:flex-end), misaligning every box and label. Zero the margin and
   pin both control types to one height so a row reads as a single line.
   Needs `.modal` in the selector to out-specify `.modal input` (0-2-1 vs
   0-1-1) — a bare `.uf-repeater input` ties and loses on source order. */
.modal .uf-repeater input,
.modal .uf-repeater select{
  margin:0;
  height:32px;
}
/* A multiselect needs its multi-row height back — the 32px single-line rule
   above is for a normal <select>, and would otherwise clip a multiselect
   down to showing only its first option. */
.modal .uf-repeater select[multiple]{height:auto;}
/* Borderless ✕ for removing a repeater row — shared by every repeater
   (events' sessions/drinks, bookings' attendees) via renderRepeaterRows. */
.uf-rep-remove{
  height:32px;width:32px;flex-shrink:0;
  border:none;background:none;
  color:var(--ink-light);font-size:14px;line-height:1;
  cursor:pointer;border-radius:6px;
  transition:color 0.15s,background 0.15s;
}
.uf-rep-remove:hover{color:var(--red);background:var(--red-light);}
/* listMode repeater (renderListModeRepeater) — the always-visible add/edit
   form, then a real table (renderDataTable, so it looks like every other
   admin table) of committed rows below it, instead of every row being its
   own full inline form (events.html's sessions, once it grew a tables
   count and an experience-level picker). */
.uf-repeater-form{
  background:var(--cream);padding:10px;border-radius:8px;
}
/* Marks whichever row the add/edit form above is currently editing — a
   background is what actually renders reliably on a <tr> (a border only
   shows there under specific table border-collapse conditions the shared
   .data-table styling doesn't set up), same tint this app already uses
   elsewhere for an "active/selected" state. */
.uf-repeater-list-row-editing{background:rgba(30,77,43,0.08);}
.uf-toggle{
  display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none;
}
.uf-toggle input{display:none;}
.uf-toggle-track{
  position:relative;flex-shrink:0;
  width:40px;height:22px;border-radius:100px;
  background:var(--border-light);
  transition:background 0.18s;
}
.uf-toggle-thumb{
  position:absolute;top:2px;left:2px;
  width:18px;height:18px;border-radius:50%;
  background:var(--ivory);
  box-shadow:0 1px 3px rgba(0,0,0,0.25);
  transition:left 0.18s;
}
.uf-toggle.on .uf-toggle-track{background:var(--forest);}
.uf-toggle.on .uf-toggle-thumb{left:20px;}
.uf-toggle-label{font-size:13px;color:var(--ink-light);transition:color 0.18s,font-weight 0.18s;}
.uf-toggle-label-off{font-weight:600;color:var(--ink);}
.uf-toggle.on .uf-toggle-label-off{font-weight:400;color:var(--ink-light);}
.uf-toggle.on .uf-toggle-label-on{font-weight:600;color:var(--ink);}
.modal h3{
  font-family:'Cormorant Garamond',serif;
  font-size:1.5rem;font-weight:400;color:var(--ink);
  margin-bottom:1rem;
}
.modal input{
  width:100%;padding:10px 12px;
  border:1.5px solid var(--border-light);
  border-radius:9px;
  font-family:'Inter',sans-serif;font-size:14px;
  color:var(--ink);background:var(--cream);
  margin-bottom:8px;
}
.modal input:focus{outline:none;border-color:var(--forest);}
.modal .btn-row{display:flex;gap:8px;justify-content:flex-end;margin-top:12px;}
.uf-footer .btn-row{margin-top:0;}
.btn-ghost{
  padding:9px 18px;border-radius:100px;
  border:1.5px solid var(--border);background:transparent;
  font-family:'Inter',sans-serif;font-size:13px;font-weight:500;
  color:var(--ink-mid);cursor:pointer;transition:all 0.15s;
}
.btn-ghost:hover{border-color:var(--ink);}
.btn-solid{
  padding:9px 18px;border-radius:100px;
  border:none;background:var(--forest);
  font-family:'Inter',sans-serif;font-size:13px;font-weight:600;
  color:var(--ivory);cursor:pointer;transition:all 0.15s;
}
.btn-solid:hover{background:var(--forest-mid);}

/* ── ADMIN PANEL ── */
.admin-panel{
  display:none;
  margin-bottom:1.8rem;
}
.admin-nav-logout{
  margin-top:12px;padding-top:12px;
  border-top:1px solid var(--border-light);
  color:var(--red);
}
/* Hidden by default (not just .admin-panel inside it) — the sidebar used to
   render unconditionally, so a denied/pending sign-in briefly showed the
   whole admin shell before admin-auth.js reacted. Only .open (added by
   enterAdminPanel) reveals it. */
.admin-layout{display:none;gap:2rem;align-items:flex-start;}
.admin-layout.open{display:flex;}
.admin-sidebar{
  flex:0 0 190px;
  position:sticky;top:1.5rem;
  display:flex;flex-direction:column;gap:3px;
}
.admin-sidebar-title{display:block;font-family:'Cormorant Garamond',serif;font-weight:600;font-size:1.15rem;color:var(--forest);text-decoration:none;margin-bottom:.6rem;}
.admin-nav-item{
  display:block;padding:9px 12px;border-radius:8px;
  color:var(--ink-light);text-decoration:none;font-size:13px;font-weight:600;
}
.admin-nav-item.active{background:var(--cream);color:var(--forest);}
.admin-main{flex:1;min-width:0;}
.data-table{width:100%;border-collapse:collapse;font-size:13px;margin-top:1rem;}
.data-table th{text-align:left;padding:10px 12px;border-bottom:2px solid var(--border-light);color:var(--ink-light);font-size:11px;letter-spacing:0.06em;text-transform:uppercase;font-weight:600;}
.data-table td{padding:10px 12px;border-bottom:1px solid var(--border-light);}
/* Set when any column declares a width — makes sibling tables (the per-session
   groups) share one column geometry instead of each sizing to its own rows. */
.data-table.dt-fixed{table-layout:fixed;}
.data-table.dt-fixed td{overflow-wrap:anywhere;}
.data-table tbody tr{transition:background 0.15s;}
/* Only rows that actually open something on click look/behave clickable —
   set either by onRowClick (opens an edit form) or expand (toggles the
   accordion; bookings/events use this one, since their rows also carry
   their own edit/status controls that onRowClick would fight with). */
.data-table tbody tr.dt-clickable{cursor:pointer;}
.data-table tbody tr.dt-clickable:hover{background:var(--cream);}

/* Expandable detail rows */
.data-table th.dt-expand-col,.data-table td.dt-expand-col{width:30px;padding-right:0;}
/* In a fixed-layout table the <col> carries the width, not the cells. */
.data-table col.dt-expand-col{width:30px;}
/* A single static "›" that rotates 90° open, rather than swapping between
   two different triangle characters — the standard modern chevron. */
.dt-expand{
  border:none;background:none;cursor:pointer;
  color:var(--ink-light);font-size:13px;line-height:1;
  padding:4px;border-radius:4px;
  display:inline-flex;align-items:center;justify-content:center;
  transition:color 0.15s,background 0.15s,transform 0.15s;
}
.dt-expand:hover{color:var(--forest);background:var(--cream);}
.dt-expand.dt-expand-open{transform:rotate(90deg);}
.data-table tr.dt-detail > td{background:var(--cream);padding:0 12px 12px 42px;}
.dt-detail-empty{font-size:12px;color:var(--ink-light);font-style:italic;padding:10px 0;}
/* Fixed layout so the <colgroup> widths are authoritative — otherwise each
   expanded row's table sizes to its own content and no two line up. */
.dt-subtable{width:100%;border-collapse:collapse;font-size:12px;table-layout:fixed;}
.dt-subtable td{overflow-wrap:anywhere;}
.dt-subtable th{
  text-align:left;padding:6px 10px;
  color:var(--ink-light);font-size:10px;letter-spacing:0.06em;
  text-transform:uppercase;font-weight:600;
  border-bottom:1px solid var(--border-light);
}
.dt-subtable td{padding:6px 10px;border-bottom:1px solid var(--border-light);}
.dt-subtable tr:last-child td{border-bottom:none;}
.dt-booker-tag{
  font-size:9px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  background:var(--forest);color:var(--ivory);
  padding:1px 6px;border-radius:100px;margin-left:4px;
}

/* Per-row controls: edit button + inline status dropdown */
.dt-action-btn{
  border:none;background:none;cursor:pointer;
  color:var(--ink-light);font-size:15px;line-height:1;
  padding:4px 8px;border-radius:6px;
  transition:color 0.15s,background 0.15s;
}
.dt-action-btn:hover{color:var(--forest);background:var(--cream-dark);}
/* Shape/typography only — background and text color come from the shared
   .status-* classes the render() also puts on it, so the dropdown keeps the
   same color language as the read-only badges elsewhere. */
/* The status pill is an editable dropdown, so it needs to *look* like one —
   a bare pill reads as a static badge. The chevron lives on this wrapper
   rather than the <select> (which can't have pseudo-elements) and is drawn
   in currentColor, so it picks up whichever .status-* text color the pill
   carries instead of needing one rule per status. */
.dt-status{
  position:relative;display:inline-block;
  border-radius:100px;
  padding-right:18px;
  transition:box-shadow 0.15s;
}
.dt-status::after{
  content:'';
  position:absolute;right:10px;top:50%;
  width:6px;height:6px;
  border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;
  transform:translateY(-70%) rotate(45deg);
  opacity:0.8;pointer-events:none;
}
.dt-status:hover{box-shadow:0 0 0 1.5px currentColor;}
.dt-status-select{
  font-size:10px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  font-family:'DM Sans',sans-serif;
  /* A <select> sizes itself to its longest option, so a short label like
     "Pending" would otherwise leave a gap of dead space after it. Pin the
     width so every pill is the same deliberate size instead of ragged. */
  width:88px;
  padding:4px 0 4px 10px;
  border:none;background:none;color:inherit;
  cursor:pointer;appearance:none;-webkit-appearance:none;
}
.dt-status-select:focus{outline:none;}
.dt-status-select:disabled{opacity:0.6;cursor:default;}
/* Event statuses run longer ("Booking Closed") than booking/session ones
   ("Confirmed", "Concluded") — widen just this table's pills rather than
   the shared width everyone else uses. */
#events-table-wrap .dt-status-select{width:128px;}

/* Copiable plain-text event summary. Monospace so the aligned columns in
   the generated report line up wherever it's pasted. */
/* Floating ✕ in the modal's top-right corner, in place of a footer button */
.modal-close{
  position:absolute;top:12px;right:12px;
  width:30px;height:30px;
  display:flex;align-items:center;justify-content:center;
  border:none;background:none;
  color:var(--ink-light);font-size:15px;line-height:1;
  cursor:pointer;border-radius:6px;
  transition:color 0.15s,background 0.15s;
}
.modal-close:hover{color:var(--ink);background:var(--cream-dark);}

/* Title row: heading with the copy action sitting right beside it */
.summary-heading{
  display:flex;align-items:center;gap:10px;
  flex-shrink:0;
}
.summary-copy-btn{
  cursor:pointer;
  font-family:'Inter',sans-serif;
  font-size:11px;font-weight:600;letter-spacing:0.04em;
  color:var(--ink-mid);
  background:none;border:1.5px solid var(--border);
  padding:4px 12px;border-radius:100px;
  transition:color 0.15s,background 0.15s,border-color 0.15s;
}
.summary-copy-btn:hover{color:var(--forest);border-color:var(--forest);}
.summary-copy-btn.copied{
  color:var(--ivory);background:var(--forest);border-color:var(--forest);
}

/* Table Facilitator Link modal (#table-link-modal, table-link-ui.js) */
#table-link-body{display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center;}
.table-link-qr{border-radius:6px;border:1px solid var(--border-light);}
.table-link-url{
  font-family:'Inter',sans-serif;font-size:12px;color:var(--ink-mid);
  word-break:break-all;background:var(--cream);border-radius:6px;padding:8px 12px;width:100%;
}
.table-link-hint{font-size:12px;color:var(--ink-light);line-height:1.5;margin:0;}

/* Booking change log */
.log-body{
  flex:1;min-height:0;overflow-y:auto;
  scrollbar-gutter:stable;
}
.log-entry{
  padding:12px 0;
  border-bottom:1px solid var(--border-light);
}
.log-entry:last-child{border-bottom:none;}
.log-entry-head{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  font-size:13px;color:var(--ink);
}
.log-badge{
  font-size:9px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  padding:2px 8px;border-radius:100px;
}
.log-public{background:var(--cream-dark);color:var(--ink-mid);}
.log-admin{background:rgba(30,77,43,0.12);color:var(--forest);}
.log-time{margin-left:auto;font-size:11px;color:var(--ink-light);white-space:nowrap;}
.log-actor{font-size:11px;color:var(--ink-light);margin-top:2px;}
.log-changes{list-style:none;margin:8px 0 0;padding:0;}
.log-changes li{
  font-size:12px;color:var(--ink-mid);
  padding:3px 0 3px 12px;
  border-left:2px solid var(--border-light);
  margin-bottom:2px;
}
.log-field{
  font-weight:600;color:var(--ink);
  text-transform:capitalize;margin-right:4px;
}
.log-from{color:var(--ink-light);text-decoration:line-through;}
.log-to{color:var(--forest);font-weight:600;}

/* Section toggles above the report */
.summary-options{
  flex-shrink:0;
  display:flex;flex-wrap:wrap;gap:6px 16px;
  padding-bottom:12px;margin-bottom:12px;
  border-bottom:1px solid var(--border-light);
}
.summary-opt{
  display:flex;align-items:center;gap:7px;
  font-size:13px;color:var(--ink);cursor:pointer;user-select:none;
}
/* `.modal input` sets width:100%/margin for text fields, which would
   stretch these checkboxes across the row — reset to intrinsic size. */
.modal .summary-opt input[type="checkbox"]{
  width:auto;flex-shrink:0;padding:0;margin:0;cursor:pointer;
}

.summary-text{
  width:100%;
  /* `.modal-wide` is a flex column whose grow rule names #upsert-fields —
     this modal has no such element, so the textarea takes that role. */
  flex:1;min-height:260px;
  padding:14px 16px;
  border:1.5px solid var(--border-light);border-radius:8px;
  background:var(--cream);color:var(--ink);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:12.5px;line-height:1.65;
  resize:none;white-space:pre;
}
.summary-text:focus{outline:none;border-color:var(--forest);}

/* Per-session groups in the bookings page's grouped views */
.view-group{margin-top:1.5rem;}
.view-group:first-child{margin-top:0.5rem;}
/* The header is a one-row table sharing its colgroup with the session's own
   data table below (see renderSessionGroups) — that's what lands the status
   pill in the same x-position as that table's Status column. */
.view-group-head td{
  border-bottom:2px solid var(--forest);
  vertical-align:middle;
}
.view-group-head:has(.view-group-title.collapsed) td{border-bottom-color:var(--border-light);}
/* A button so the whole header cell is a keyboard-reachable collapse toggle */
.view-group-title{
  width:100%;
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  font-family:'Montserrat',sans-serif;
  font-size:12px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  color:var(--forest);text-align:left;
  background:none;border:none;
  padding:0;
  cursor:pointer;
}
.view-group-title:hover{color:var(--forest-mid);}
.view-group-title.collapsed{
  color:var(--ink-light);
}
.view-group-chevron{font-size:10px;line-height:1;}
.view-group-count{
  font-family:'Inter',sans-serif;
  font-size:11px;font-weight:500;letter-spacing:0;text-transform:none;
  color:var(--ink-light);
}
.view-group .data-table{margin-top:0;}

/* Floating toast, pinned to the bottom center of the viewport — it sits
   above the page instead of in the flow, so showing/hiding it never shifts
   the table underneath. */
.table-msg{
  position:fixed;
  bottom:24px;left:50%;
  transform:translateX(-50%);
  z-index:400; /* over the admin panel; modals (300) sit below, but a toast
                  fires from the table, never while a modal is open */
  max-width:min(90vw,420px);
  padding:10px 18px;border-radius:100px;
  font-size:12.5px;text-align:center;
  background:var(--forest);color:var(--ivory);
  box-shadow:0 8px 24px rgba(0,0,0,0.18);
  animation:toast-in 0.18s ease-out;
}
.table-msg-error{background:var(--red);color:#fff;}
@keyframes toast-in{
  from{opacity:0;transform:translateX(-50%) translateY(8px);}
  to{opacity:1;transform:translateX(-50%) translateY(0);}
}
@media(prefers-reduced-motion:reduce){.table-msg{animation:none;}}

/* Standalone select outside the modal (the bookings event picker). The
   native arrow sits flush against the right edge with no padding of its
   own — replaced with a drawn chevron that can be positioned properly,
   with matching room reserved for it on the right. */
.admin-select{
  width:100%;
  padding:9px 34px 9px 12px;
  border:1.5px solid var(--border-light);
  border-radius:8px;
  font-family:'DM Sans',sans-serif;font-size:14px;
  color:var(--ink);
  cursor:pointer;
  appearance:none;-webkit-appearance:none;
  background-color:var(--cream);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236E5E42' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 12px center;
  background-size:10px 6px;
}
.admin-select:focus{outline:none;border-color:var(--forest);}
.admin-panel.open{display:block;}
.dash-stats{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:1rem;}
@media(min-width:640px){.dash-stats{grid-template-columns:repeat(4,1fr);}}
.dash-stat{background:var(--cream);border:1.5px solid var(--border-light);border-radius:10px;padding:10px 12px;text-align:center;}
.dash-stat-value{font-family:'Montserrat',sans-serif;font-size:1.4rem;font-weight:700;color:var(--forest);line-height:1.2;}
.dash-stat-label{font-size:10px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:var(--ink-light);margin-top:2px;}
.dash-filters{display:flex;gap:6px;margin-bottom:1rem;}
.dash-filter-btn{flex:1;padding:8px;border:1.5px solid var(--border-light);border-radius:8px;background:var(--ivory);color:var(--ink-light);font-family:'DM Sans',sans-serif;font-size:12px;font-weight:600;cursor:pointer;}
.dash-filter-btn.active{background:var(--forest);border-color:var(--forest);color:var(--ivory);}
.admin-panel h3{
  font-family:'Cormorant Garamond',serif;
  font-size:1.3rem;font-weight:400;color:var(--ink);
  margin-bottom:1rem;
  position:sticky;
  top:0;
  background:var(--ivory);
  padding-bottom:0.5rem;
}
/* For a page whose header row has more than just the h3 (e.g. events.html's
   title + filter chip + New button) — the sticky/background/padding moves to
   the row wrapper instead, so the whole row stays together while scrolling. */
.admin-panel-header-row{
  position:sticky;top:0;z-index:2;
  background:var(--ivory);
  padding-bottom:0.5rem;margin-bottom:1rem;
}
.admin-panel-header-row h3{position:static;background:none;padding-bottom:0;margin-bottom:0;}
.status-badge{
  font-size:10px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  padding:3px 10px;border-radius:100px;display:inline-block;white-space:nowrap;
}
.status-draft{background:var(--border-light);color:var(--ink-light);}
.status-booking_open{background:rgba(30,77,43,0.1);color:var(--forest);}
.status-booking_closed{background:var(--red-light);color:var(--red);}
.status-ongoing{background:var(--gold-light);color:var(--gold);}
.status-completed{background:var(--tile-green);color:var(--ivory);}
.status-cancelled{background:var(--ink-light);color:var(--ivory);}
.status-pending{background:var(--gold-light);color:var(--gold);}
.status-confirmed{background:rgba(30,77,43,0.1);color:var(--forest);}
/* Session status — same palette as its event/booking equivalents
   (open ~ confirmed/booking_open, closed ~ booking_closed); status-cancelled
   and status-ongoing above already cover those two session cases too. */
.status-open{background:rgba(30,77,43,0.1);color:var(--forest);}
.status-closed{background:var(--red-light);color:var(--red);}
.status-concluded{background:var(--tile-green);color:var(--ivory);}
.filter-chip{
  display:inline-flex;align-items:center;gap:5px;
  padding:6px 14px;border-radius:100px;
  font-size:12px;font-weight:600;font-family:'DM Sans',sans-serif;
  border:1.5px solid var(--border-light);
  background:var(--ivory);color:var(--ink-light);
  cursor:pointer;transition:background 0.15s,border-color 0.15s,color 0.15s;
}
.filter-chip:hover{border-color:var(--forest-mid);}
.filter-chip.active{
  background:rgba(30,77,43,0.09);border-color:var(--forest);color:var(--forest);
}
.no-bk{text-align:center;padding:1.5rem;color:var(--ink-light);font-size:13px;font-style:italic;}

/* ── SEATING ARRANGEMENT ── */
/* Same entry-point look wherever it's triggered from (bookings' session
   header, events' session subtable) — a plain action button, no
   precomputed arranged/not-arranged state (matches how the edit/log
   buttons elsewhere don't precompute anything either). */
.seat-btn{
  border:none;background:none;cursor:pointer;
  color:var(--ink-light);font-size:12px;font-weight:600;line-height:1;
  padding:4px 10px;border-radius:100px;
  border:1.5px solid var(--border-light);
  transition:color 0.15s,background 0.15s,border-color 0.15s;
}
.seat-btn:hover{color:var(--forest);border-color:var(--forest-mid);background:var(--cream);}
.seat-board{
  display:flex;flex-wrap:wrap;gap:14px;
  padding-bottom:4px;
}
.seat-table{
  width:260px;flex-shrink:0;
  border:1.5px solid var(--border-light);border-radius:10px;
  background:var(--cream);
  padding:10px;
  transition:border-color 0.15s,background 0.15s;
}
.seat-table.seat-dragover{border-color:var(--forest);background:var(--cream-dark);}
.seat-table-head{
  display:flex;justify-content:space-between;align-items:center;
  margin-bottom:8px;
}
.seat-table-title{font-size:12px;font-weight:700;letter-spacing:0.04em;text-transform:uppercase;color:var(--ink-mid);}
.seat-table-count{font-size:11px;color:var(--ink-light);}
.seat-slots{display:flex;flex-direction:column;gap:6px;min-height:40px;}
.seat-chip{
  display:flex;align-items:center;gap:7px;
  background:var(--ivory);border:1.5px solid var(--border-light);border-radius:8px;
  padding:6px 9px;
  font-size:12.5px;color:var(--ink);
  cursor:grab;user-select:none;
}
.seat-chip:active{cursor:grabbing;}
.seat-chip.seat-dragging{opacity:0.4;}
.seat-chip-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
/* One dot color per booking (not per person) — chips from the same booking
   share a dot so the admin can see at a glance which people came in
   together, without needing the booker tag this replaced. */
.seat-group-dot{
  width:8px;height:8px;border-radius:50%;flex-shrink:0;
}
.seat-exp{
  font-size:9px;font-weight:700;letter-spacing:0.04em;text-transform:uppercase;
  padding:2px 7px;border-radius:100px;white-space:nowrap;flex-shrink:0;
}
.seat-exp-never{background:var(--border-light);color:var(--ink-light);}
.seat-exp-beginner{background:var(--gold-light);color:var(--gold);}
.seat-exp-experienced{background:rgba(30,77,43,0.1);color:var(--forest);}
/* Any experience level added later (fe/settings.html) beyond the 3 above
   falls back to this neutral style rather than needing a new rule per name. */
.seat-exp-other{background:var(--border-light);color:var(--ink-light);}
.seat-empty-slot{
  border:1.5px dashed var(--border-light);border-radius:8px;
  padding:6px 9px;font-size:11px;color:var(--ink-light);font-style:italic;
  text-align:center;
}
.seat-pool{
  width:100%;
  border:1.5px dashed var(--border);border-radius:10px;
  background:var(--ivory);
  padding:10px;margin-top:14px;
}
.seat-pool .seat-table-title{color:var(--ink-light);}
.seat-pool .seat-slots{flex-direction:row;flex-wrap:wrap;}
.seat-pool .seat-chip{width:auto;}
.seat-lock-note{font-size:11.5px;color:var(--ink-light);}

/* ── TABLE FACILITATOR (fe/table.html, fe/table-ui.js) ── */
/* Anonymous, phone-first page — one screen at a time, centered, no
   sidebar/layout chrome (unlike the admin pages this design system also
   serves). */
.table-app{
  min-height:100vh;
  display:flex;align-items:flex-start;justify-content:center;
  padding:1.5rem 1rem 3rem;
}
.table-screen{
  width:100%;max-width:480px;
  display:flex;flex-direction:column;align-items:center;gap:14px;
  text-align:center;
}
.table-title{
  font-family:'Cormorant Garamond',serif;
  font-size:1.6rem;font-weight:500;color:var(--ink);
  margin:0;
}
.table-sub{font-size:13px;color:var(--ink-light);margin:0;line-height:1.5;}
.table-input{
  width:100%;max-width:200px;
  padding:12px;text-align:center;
  border:1.5px solid var(--border-light);border-radius:9px;
  font-family:'Inter',sans-serif;font-size:18px;
  background:var(--cream);color:var(--ink);
}
.table-input:focus{outline:none;border-color:var(--forest);}
.table-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;}
.table-picker-list{display:flex;flex-direction:column;gap:8px;width:100%;max-width:280px;}
.table-picker-btn{width:100%;}
/* Small ghost/solid links that sit ABOVE a screen's .mj-table (History,
   the dice screen's Back, the wall-result screen's Continue) — every
   action lives either above the table or inside its center, never below. */
.table-history-link{align-self:center;justify-self:end;}
.table-history-list{width:100%;display:flex;flex-direction:column;gap:6px;}
.table-history-row{
  display:flex;align-items:center;gap:10px;justify-content:space-between;
  padding:8px 12px;border:1px solid var(--border-light);border-radius:8px;
  font-size:12.5px;background:var(--ivory);
}

/* Back to the previous step, on whichever screens actually have one (game
   home doesn't — it's the hub). */
.table-back-btn{
  border:none;background:none;cursor:pointer;
  font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:var(--ink-light);
  padding:4px 0;
}
.table-back-btn:hover{color:var(--forest);}
/* 3 equal-ish sections — back (left), Home (dead center regardless of how
   wide back/history's own text is, thanks to the two 1fr columns being
   symmetric), History (right). Never below the .mj-table. */
.table-header-row{
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  width:100%;
}
.table-header-back{justify-self:start;}
.table-home-btn{
  justify-self:center;
  border:1.5px solid var(--border-light);border-radius:50%;background:none;cursor:pointer;
  width:30px;height:30px;font-size:14px;line-height:1;
  display:flex;align-items:center;justify-content:center;
  color:var(--ink-light);
}
.table-home-btn:hover{border-color:var(--forest);color:var(--forest);}

/* The table primitive: a phone laid flat in the middle of a physical
   mahjong table has 4 EDGES, one per seated player — not 4 corners, which
   is what a plain 2×2 grid gives you. A 3×3 grid with a wide center band
   turns this into a cross/plus: N/S get the full-width middle column, E/W
   get the full-height middle row, and each one's content is rotated to
   face outward so whoever's sitting there reads it right-side up without
   picking up the device. The shared center cell (dice, the wall diagram)
   never rotates — there's no single "right side up" for shared content.
   Forcing the container square, and using the same 1:3:1 ratio on both
   axes, is what keeps every edge symmetric and keeps rotated content from
   clipping regardless of the phone's real aspect ratio. Sized off vw/vh
   rather than a fixed px, so the whole thing scales with the actual
   screen. No animations — state just swaps directly. */
/* Real mahjong tables are green felt, uniformly — the whole table (gutters,
   all 4 seats, and the center) shares one green, not just the middle.
   Player identity moved onto the name itself (.mj-name-tag) instead of
   tinting each seat's own background, so the felt can stay one continuous
   surface. */
.mj-table{
  width:min(94vw,94vh);aspect-ratio:1/1;margin:0 auto;
  display:grid;grid-template-columns:1fr 3fr 1fr;grid-template-rows:1fr 3fr 1fr;gap:3px;
  background:var(--tile-green);border-radius:14px;overflow:hidden;
}
.mj-seat{background:var(--tile-green);display:flex;align-items:center;justify-content:center;overflow:hidden;padding:6px;}
.mj-seat-n{grid-column:2;grid-row:1;}
.mj-seat-w{grid-column:1;grid-row:2;}
.mj-seat-c{grid-column:2;grid-row:2;}
.mj-seat-e{grid-column:3;grid-row:2;}
.mj-seat-s{grid-column:2;grid-row:3;}
/* flex-shrink:0 is the fix that makes E/W seats work at all: .mj-seat is a
   flex container only ~1/5 as wide as the table before rotation, and
   without this, that narrow track would squeeze .mj-seat-inner and wrap
   names onto 2 lines. flex-shrink:0 lets it render at its natural
   (un-squeezed) width instead — the SAME trick .mj-wall-row's stacks
   already relied on. Rotation happens around the element's own center, and
   centering happens against that same point on both axes, so a wide
   un-rotated box still ends up fully inside the cell once rotated — the
   cell's OTHER axis (the wide 3fr band) is what actually has to fit it,
   not the narrow one. */
.mj-seat-inner{display:flex;flex-direction:column;align-items:center;gap:5px;flex-shrink:0;}
.mj-seat-n .mj-seat-inner{transform:rotate(180deg);}
.mj-seat-w .mj-seat-inner{transform:rotate(90deg);}
.mj-seat-e .mj-seat-inner{transform:rotate(270deg);}
.mj-seat-s .mj-seat-inner{transform:rotate(0deg);}
/* The center seat has no rotation, so it gets none of the "overflow, then
   rotate back into bounds" benefit above — it needs to actually fit its
   own cell. Without this override, the center wall diagram (which wants to
   render at its full natural size) would overflow .mj-seat-c symmetrically
   on every side and get clipped by its overflow:hidden, hiding everything
   except whatever sat exactly in the middle. justify-content:center is
   what actually centers content vertically within that full-height box —
   flex columns default to packing from the top, so without this, a single
   button (much shorter than the cell) would sit at the top instead of
   dead center. */
.mj-seat-c .mj-seat-inner{flex-shrink:1;width:100%;height:100%;justify-content:center;}
/* No gold ring around the highlighted seat — the mj-tag itself (東 Dealer /
   ⚡ Break here / 🏆 Wins) already says what state that seat is in, so a
   second ring saying the same thing was redundant. .mj-seat-highlight is
   still passed into renderMjTable's opts and still marked on the DOM (in
   case a future screen wants it), it just doesn't paint anything today. */

.mj-name-row{display:flex;align-items:center;gap:6px;flex-shrink:0;}
/* The player-identity pill — a slim colored tag (background set inline per
   seat via var(--seat-0..3)) wherever a name appears, replacing both the
   old dot-plus-plain-text and the whole-seat background tint. */
.mj-name-tag{
  display:inline-flex;align-items:center;flex-shrink:0;
  font-family:'Cormorant Garamond',serif;font-weight:600;color:var(--ivory);
  font-size:clamp(0.75rem,3.2vw,0.95rem);white-space:nowrap;
  padding:2px 10px;border-radius:100px;
}
/* Persistent small "who's dealer" marker — shown next to a name on every
   screen, not just game home, so it's never ambiguous mid-session. Distinct
   from .mj-tag (the bigger gold pill used for the *current* screen's own
   state, e.g. game home's own dealer callout, or "wins"). */
.mj-dealer-mark{
  font-size:clamp(9px,3vw,11px);font-weight:700;color:var(--gold);
  border:1.5px solid var(--gold);border-radius:50%;
  width:1.5em;height:1.5em;display:inline-flex;align-items:center;justify-content:center;
  flex-shrink:0;
}
.mj-tag{
  font-size:10px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  background:var(--gold);color:var(--ivory);
  padding:3px 10px;border-radius:100px;white-space:nowrap;
}
.mj-tag.mj-dealer-tag{cursor:grab;touch-action:none;}
.mj-tag.mj-dealer-tag.mj-armed{outline:2px dashed var(--ivory);outline-offset:2px;}
/* Full-color tiles standing in for "these are now in your hand" — in this
   player's own color, sitting outside their name (further from center), in
   the strip between the name and the table's own outer edge. Grows to the
   player's real final count (13, or 14 for the dealer — see
   computeSeatTileDelays), not a fixed decorative number.

   Each reveal step is its OWN block (.mj-play-group) of same-size tiles —
   never merged into fewer, taller ones — and blocks run left to right, new
   ones appearing to the right of the last, mirroring how the wall's own
   stacks run sideways along an edge rather than piling up. */
.mj-play-tiles{display:flex;flex-direction:row;flex-wrap:wrap;gap:3px;margin-top:3px;}
/* 2 fixed row tracks, columns generated as needed (grid-auto-flow:column)
   — a 4-tile step fills column 1 then column 2 (2 across, 2 down); a
   2-tile step (the dealer's merged final tile pair) fills just column 1
   (2 down, "2 vertical tiles"); a 1-tile step is a lone tile. auto (not
   1fr) rows so an unused second row doesn't reserve empty space. */
.mj-play-group{display:grid;grid-template-rows:repeat(2,auto);grid-auto-flow:column;gap:2px;}
.mj-play-tile{
  width:7px;height:13px;border-radius:1px;
  opacity:0;animation:wall-count-reveal 0.3s ease-out forwards;
}
@media (prefers-reduced-motion: reduce){
  .mj-play-tile{animation:none;opacity:1;}
}
/* Dragged onto a seat to award the win (recordGame('win', ...)), same
   drag-or-tap-twice pattern as the seat-swap chip / dealer tag above —
   not tapped directly on a seat, since a stray tap during a real game
   shouldn't be able to end it. */
.mj-trophy{
  font-size:2rem;line-height:1;cursor:grab;touch-action:none;
  padding:4px;border-radius:50%;border:1.5px solid transparent;
}
.mj-trophy.mj-armed{border-color:var(--ivory);box-shadow:0 0 0 2px rgba(250,250,247,0.35);}

/* Every seat's name is a draggable, slim colored "chip", right on game
   home — drag one chip onto another to swap the two people, or (since a
   precise drag can be fiddly on a real table) tap one chip then the other
   to do the same swap (see beginSeatInteraction in table-ui.js). Saves
   straight to seating_tables on every swap — no separate confirm step.
   Same visual language as .mj-name-tag (it IS that tag, just draggable —
   color set inline per seat), just slimmer than the old neutral pill. */
.mj-seat-chip{
  display:flex;align-items:center;cursor:grab;touch-action:none;flex-shrink:0;
  font-family:'Cormorant Garamond',serif;font-weight:600;color:var(--ivory);
  font-size:clamp(0.75rem,3.2vw,0.95rem);white-space:nowrap;
  padding:3px 12px;border-radius:100px;
  border:1.5px solid transparent;
}
.mj-seat-chip.mj-armed{border-color:var(--ivory);box-shadow:0 0 0 2px rgba(250,250,247,0.35);}
.mj-drag-ghost{
  position:fixed;top:0;left:0;transform:translate(-50%,-50%);
  pointer-events:none;z-index:999;
  display:flex;align-items:center;gap:6px;
  padding:3px 12px;border-radius:100px;
  background:var(--forest);color:var(--ivory);border:1.5px solid var(--forest);
  font-family:'Cormorant Garamond',serif;font-size:0.9rem;font-weight:600;
  box-shadow:0 10px 24px rgba(0,0,0,0.28);
}
[data-seat].mj-drop-target{outline:3px dashed var(--forest);outline-offset:-3px;}

/* Real dice — drawn with CSS dots on a 3×3 grid (.dice-face/.dice-pip),
   not Unicode dice glyphs: those render inconsistently across fonts/OSes,
   some carrying their own baked-in border that doubled up with the
   button's own, and can't be forced to a clean square. 3-per-row (not 6)
   because the wall now shares this same center column (see .mj-center-
   wall) — narrower available width, so fewer/bigger buttons per row reads
   better than 6 tiny squares. Sits with "Break Wall" in the table's
   shared center, nested inside the (blank, uncolored) wall — see
   renderDiceTable — same as game home always showing the wall too. */
.dice-picker{display:flex;flex-direction:column;align-items:center;gap:clamp(3px,1.2vw,6px);}
/* Die 1's row of 6 above Die 2's row of 6 ("123456" over "123456") — not
   the earlier 3-per-row/2-dice-side-by-side layout, which read as
   "123123"/"456456" across the two dice instead of each die counting
   1-6 in order. Still only 2 rows total (not the 4 a fully stacked 3-per-
   row layout would need), so it stays within the vertical space reserved
   for the center on a phone-sized table — see the .dice-row note below for
   why that mattered. No "Die 1"/"Die 2" labels — the dice are big enough
   now to read as two dice on their own. */
.dice-cols{display:flex;flex-direction:column;gap:6px;align-items:center;}
.dice-col{display:flex;flex-direction:column;align-items:center;gap:clamp(3px,1.2vw,6px);}
/* Everything in here sits on the green felt (.mj-seat-c), not the usual
   cream background, so text/buttons use light-on-green tokens instead of
   the ink-on-cream ones the rest of the app uses. */
/* width is the clamp directly (not width:100%;max-width:clamp(...)) —
   every ancestor up to .wall-mid (.dice-col, .dice-cols, .dice-picker) is
   shrink-to-fit with no concrete width of its own, so a plain 100% here
   had nothing real to resolve against and collapsed to auto — the
   max-width clamp was capping a width that never actually reached it,
   which is why raising the cap earlier changed nothing visible. A vw-based
   width sidesteps that entirely by computing straight from the viewport
   instead of an indeterminate parent. Viewport-relative (not a flat px)
   so the dice read bigger on a roomier screen without risking the row
   overflowing past the W/E walls on a narrow phone — where 40vw is
   already close to the low end of the clamp; .wall-mid's own max-height/
   overflow-y:auto is still the safety net if this ever runs out of
   vertical room. 6 columns (1-6 in order, not 3+3 split across the two
   dice) — width doubled from the old 3-column version to keep each
   button roughly the same size now that a row holds twice as many. */
.dice-row{display:grid;grid-template-columns:repeat(6,1fr);gap:clamp(3px,1.2vw,5px);width:clamp(180px,40vw,320px);}
/* width:100% + aspect-ratio (not a hand-tuned clamp) is what keeps these
   square — the grid's own 3 equal columns decide the actual size, the
   button just fills whatever that cell turns out to be. One border only
   (the button's own) — the dots inside carry no border of their own. */
.dice-btn{
  width:100%;aspect-ratio:1/1;padding:14%;
  border:1.5px solid var(--border-light);border-radius:22%;background:var(--ivory);cursor:pointer;
  transition:border-color 0.15s,background 0.15s;
}
.dice-btn:hover{border-color:var(--forest-mid);}
.dice-btn.picked{background:var(--gold);border-color:var(--gold);}
.dice-btn.picked .dice-pip.on{background:var(--ivory);}
.dice-total{font-size:clamp(9px,2.4vw,11px);color:var(--ivory);opacity:0.85;}
.dice-face{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);width:100%;height:100%;}
.dice-pip{justify-self:center;align-self:center;width:46%;height:46%;border-radius:50%;background:transparent;}
.dice-pip.on{background:var(--ink);}
/* Ivory chip on green felt, gold-bordered — reads clearly against the felt
   in a way a forest-green button (too close to the felt's own color)
   wouldn't. Sized down from its original clamp — freeing up room for the
   dice themselves (see .dice-row) to read bigger, rather than the button
   crowding them out. */
.mj-center-btn{
  margin-top:4px;border:1.5px solid var(--gold);border-radius:100px;cursor:pointer;
  background:var(--gold);color:var(--ivory);font-family:'Inter',sans-serif;
  font-weight:600;font-size:clamp(10px,2.8vw,12px);padding:clamp(5px,1.8vw,8px) clamp(12px,3.6vw,16px);
}
.mj-center-btn:hover{background:var(--gold-light);color:var(--ink);}
.mj-center-btn:disabled{background:var(--border-light);border-color:var(--border);color:var(--ink-light);cursor:not-allowed;}

/* The wall lives in the table's shared CENTER, not each player's own edge
   — a real mahjong wall is built by all 4 players together into one square
   in the middle, not stacked in front of any single seat. Each of the 4
   strips (2 tiles/stack × 18 stacks) sits on the same compass side as its
   owner's outer seat — no per-strip color (redundant with the tile fill
   colors below), and no static H/T end labels either — the center legend
   (see .wall-legend-mini) explains what actually matters instead of just
   marking positions.

   Finding WHICH WALL breaks counts counter-clockwise from the dealer, but
   counting stacks along that wall and distributing tiles from the break
   goes clockwise (n→e→s→w→n — see wallRingFromBreak in table-ui.js) — the
   opposite direction, per explicit correction. Each wall still needs to
   read in the direction that makes that clockwise walk continuous (N and E
   read stack-1-to-18 left-to-right/top-to-bottom "naturally"; S and W run
   in reverse — row-reverse/column-reverse — so their own stack 1 lands on
   the corner the previous wall's count just arrived at).

   Each wall-group is pinned to its own full edge (top/right/bottom/left:0)
   via absolute positioning, not confined to a middle grid track — that's
   what lets adjacent walls actually reach and touch at the 4 corners, like
   a real physical mahjong wall, instead of leaving a gap where a fixed-
   size corner cell used to sit empty. .wall-mid stays a normal (non-
   absolute) flex child, so it centers itself in whatever's left in the
   middle, capped by max-width/height so it doesn't run under the walls.

   The group also stacks the seat-count label (see seatCountLabelHtml)
   against the OUTER side of the actual tile strip — the side nearer that
   seat's own player, not the center — using the same normal/reversed
   direction pairing as the tile-ordering above, just one level up: N/W put
   the label first in DOM order (which a plain row/column already renders
   on the outer side), S/E need it reversed to land the label on their own
   outer side instead. */
.mj-center-wall{
  position:relative;width:100%;height:100%;
  display:flex;align-items:center;justify-content:center;
  /* Every tile's two fixed dimensions — long side (--wall-thick, also how
     far a strip juts in from its own edge) and short side (--tile-short,
     the axis running along the wall). Both are capped, independently of
     viewport, so a tile is always the same rectangle whether the table is
     phone-sized or filling a huge desktop window — letting either one
     scale with the *table's* size (rather than staying a fixed real-world-
     ish tile size) is what previously turned tiles into thin wide bars on
     a big screen (width uncapped, height still capped) or let them
     overshoot into a neighboring wall at a corner (width sized to exactly
     match a stretched container instead of its own natural size). */
  --wall-thick:clamp(20px,7.8vw,30px);
  --tile-short:clamp(8px,3vw,12px);
}
.wall-group{position:absolute;display:flex;gap:2px;}
/* Each wall is inset from the two edges it doesn't run along by exactly
   the perpendicular wall's own thickness (--wall-thick) — e.g. N is
   pulled in from left/right by W's and E's thickness. That reserves each
   corner for whichever wall actually owns it, so N's and E's strips can
   never both reach into the same corner pixels and overlap. Combined with
   the strip staying centered at its own natural (capped) size rather than
   stretching to fill this box (see .wall-strip below), the wall simply
   doesn't span the full edge on a big screen — same as a real wall built
   from a fixed number of real-sized tiles wouldn't grow to fill an
   oversized table — while still centering itself in whatever's inset. */
.wall-group.wall-n{top:0;left:var(--wall-thick);right:var(--wall-thick);flex-direction:column;align-items:center;}
.wall-group.wall-s{bottom:0;left:var(--wall-thick);right:var(--wall-thick);flex-direction:column-reverse;align-items:center;}
.wall-group.wall-w{left:0;top:var(--wall-thick);bottom:var(--wall-thick);flex-direction:row;align-items:center;}
.wall-group.wall-e{right:0;top:var(--wall-thick);bottom:var(--wall-thick);flex-direction:row-reverse;align-items:center;}
/* gap:0 — real wall stacks sit flush against each other; each stack's own
   1px border is what still reads as a seam between them. Direction here is
   purely about stack-1-to-18 ordering (see the note above) — positioning
   is the group's job now, not the strip's. max-width/max-height (100% of
   the group's own already-inset box) is a shrink safety net only, for a
   viewport narrow enough that even --tile-short's own minimum would
   overflow the inset space — see .mj-stack's flex-shrink below. */
.wall-strip{display:flex;gap:0;border-radius:3px;max-width:100%;max-height:100%;}
.wall-strip.wall-s{flex-direction:row-reverse;}
.wall-strip.wall-w{flex-direction:column-reverse;}
.wall-strip.wall-e{flex-direction:column;}
/* Tile fill color comes entirely from the inline style table-ui.js sets per
   stack (solid = a whole stack is one player's; half-and-half gradient =
   a stack split between two players' single final tile — that gradient
   split IS the visual cue for "top tile"/"bottom tile"). The break marker
   is a border only, never a background, so it can never get hidden behind
   whichever fill color a stack has. */
/* Each stack is its own fixed rectangle (--tile-short × --wall-thick, or
   swapped for W/E below) — normally flex-shrink:0 like a real tile, only
   allowed to shrink (flex-shrink:1) as the strip's max-width/max-height
   safety net above actually kicks in, on a viewport too narrow to fit 18
   of them at their natural size. */
.mj-stack{
  flex:0 1 auto;min-width:0;min-height:0;
  width:var(--tile-short);height:var(--wall-thick);
  background:var(--tile-white);border:1px solid var(--border);border-radius:1px;
  position:relative;
}
.wall-strip.wall-w .mj-stack,.wall-strip.wall-e .mj-stack{
  width:var(--wall-thick);height:var(--tile-short);
}
.mj-stack::after{content:'';position:absolute;left:0;right:0;top:50%;height:1px;background:var(--border);}
.wall-strip.wall-w .mj-stack::after,.wall-strip.wall-e .mj-stack::after{
  left:50%;right:auto;top:0;bottom:0;width:1px;height:auto;
}
/* The gold break-marker fades in too, rather than showing from the moment
   the screen renders — it only means something once the tile-counting wave
   has actually landed on this stack, so it appears right at that boundary
   (revealTiming.colorStart, set via inline animation-delay in
   stackCellHtml) instead of before the count even gets there. Animating
   border-color/box-shadow directly (no separate overlay needed, unlike the
   fill) since there's nothing underneath that needs to stay visible through
   it — the border IS the marker. */
@keyframes mj-stack-break-reveal{
  from{border-color:var(--border);border-width:1px;box-shadow:none;}
  to{border-color:var(--gold);border-width:2px;box-shadow:0 0 0 1px var(--gold);}
}
.mj-stack.mj-stack-break{
  animation:mj-stack-break-reveal 0.3s ease-out forwards;
}
@media (prefers-reduced-motion: reduce){
  .mj-stack.mj-stack-break{animation:none;border-color:var(--gold);border-width:2px;box-shadow:0 0 0 1px var(--gold);}
}
/* The recipient color, as a separate overlay rather than the stack's own
   background — that's what lets it fade in on its own delay (dealing
   order, see stackCellHtml/revealTimingFor) while the stack and its gold
   break-marker border stay put underneath. Reuses wall-count-reveal
   (defined just below) for the same fade/pop language as the two count
   waves that lead into this one. Defaults to full coverage (a stack fully
   claimed by one player); -top/-bottom below narrow it to half a stack,
   for the single-tile round where two recipients' halves can now land in
   two different reveal steps (see computeInitialDealing's revealGroup) and
   so need to fade in independently rather than as one shared gradient. */
.mj-stack-fill{
  position:absolute;inset:0;border-radius:1px;
  opacity:0;animation:wall-count-reveal 0.3s ease-out forwards;
}
/* "top"/"bottom" name WHO gets which half (see computeInitialDealing), not
   a fixed screen direction — on a horizontal (N/S) strip that's literally
   upper/lower, but on a vertical (W/E) strip the stack itself is drawn on
   its side, so "top" reads as the near-center half (left for W, right for
   E — see .wall-strip.wall-w/e's own reversed stack-ordering) and "bottom"
   the far half, keeping the same physical assignment the un-rotated N/S
   case uses, just turned 90°. */
.mj-stack-fill-top{height:50%;}
.mj-stack-fill-bottom{top:auto;height:50%;}
.wall-strip.wall-w .mj-stack-fill-top,.wall-strip.wall-e .mj-stack-fill-top{
  height:auto;width:50%;right:auto;
}
.wall-strip.wall-w .mj-stack-fill-bottom,.wall-strip.wall-e .mj-stack-fill-bottom{
  top:0;height:auto;width:50%;left:auto;
}
/* The 1..diceTotal seat count — the count (from the dealer, counter-
   clockwise) that decides WHICH wall breaks, one step before the tile
   count decides WHERE on it. Sits right against that seat's own wall
   strip now, not the outer seat. A seat can show more than one number if
   the count wraps past it more than once; the final number (the one that
   actually lands, on the breaking seat) is gold, matching the "this is
   the state that matters" convention used everywhere else. Each number
   fades/pops in on its own delay (see seatCountLabelHtml) so the reveal
   visibly counts around the table in order, 1 first, rather than
   appearing all at once. */
.wall-count-label{display:flex;gap:4px;flex-shrink:0;}
@keyframes wall-count-reveal{from{opacity:0;transform:scale(0.4);}to{opacity:1;transform:scale(1);}}
.wall-count-num{
  font-family:'Inter',sans-serif;font-size:11px;font-weight:700;color:var(--ivory);opacity:0;
  animation:wall-count-reveal 0.3s ease-out forwards;
}
.wall-count-num.final{color:var(--gold);font-size:14px;}
@media (prefers-reduced-motion: reduce){
  .wall-count-num{animation:none;opacity:1;}
}
/* The 1-2-3... count that lands on the break point, on the counted stacks
   of the wall that actually breaks only — not every stack, and not the
   dealing order that follows (that was tried and reverted). Plain dark
   text is enough here since these stacks stay neutral/uncolored right up
   to the break itself. */
.mj-stack-count{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:clamp(6px,2vw,7.5px);font-weight:700;color:var(--ink);line-height:1;
  pointer-events:none;
  /* Second reveal wave — fades in 1 by 1 right after the player-count wave
     finishes (see revealTimingFor/stackCellHtml for the delay math). */
  opacity:0;animation:wall-count-reveal 0.3s ease-out forwards;
}
@media (prefers-reduced-motion: reduce){
  .mj-stack-fill,.mj-stack-count{animation:none;opacity:1;}
}
/* The center legend — what the gold border and the coloring actually mean,
   replacing the old decorative "中" glyph with something functional. A
   normal (non-absolute) flex child of .mj-center-wall, so it just centers
   itself in the middle automatically. No hard size cap — the dice picker
   uses this same slot for a much bigger interactive UI (renderDiceTable),
   so this has to stay free to grow; each screen's own content is already
   sized to fit reasonably via its own clamp()s. */
.wall-mid{
  display:flex;flex-direction:column;align-items:center;gap:5px;
  max-width:100%;padding:0 4px;
  /* Guards against a repeat of the dice-picker overflow bug: .wall-mid is
     normal-flow while the 4 wall-groups are position:absolute, so an
     absolute element paints ON TOP of normal-flow content wherever they
     overlap regardless of DOM order — meaning content here that's too
     tall for the reserved center doesn't get pushed below the wall, it
     renders BEHIND it (invisible, unclickable). Capping the height and
     scrolling internally keeps oversized content contained and reachable
     instead, on any screen that ends up using this slot. */
  max-height:calc(100% - 2 * var(--wall-thick) - 8px);
  overflow-y:auto;
}
/* The actual rolled dice, shown for real in the table's shared center —
   not just mentioned as text in the screen title. */
.wall-dice-display{display:flex;gap:8px;}
/* Same real dot-face square as .dice-btn, just bigger and non-interactive
   — only 2 of these here, not 12, so there's room to size up. */
.wall-die{width:clamp(28px,10vw,38px);aspect-ratio:1/1;padding:14%;background:var(--ivory);border-radius:22%;}
/* Game home's how-to, moved into the table's own center instead of a
   caption above it — same light-on-felt treatment as everything else that
   lives in .wall-mid. */
.wall-mid-title{
  font-size:10px;font-weight:700;letter-spacing:0.04em;text-transform:uppercase;
  color:var(--ivory);text-align:center;
}
.wall-mid-hint{font-size:9.5px;color:var(--ivory);opacity:0.8;text-align:center;line-height:1.4;max-width:150px;margin:0;}
.wall-legend-mini{display:flex;flex-direction:column;gap:5px;}
.wall-legend-row{
  display:flex;align-items:center;gap:5px;
  font-size:clamp(8px,2.6vw,9.5px);line-height:1.3;color:var(--ivory);
  text-align:left;
}
.wall-legend-swatch{width:13px;height:13px;border-radius:3px;flex-shrink:0;background:var(--tile-white);border:1px solid var(--border);}
.wall-legend-swatch.wall-legend-gold{background:var(--gold-light);border-color:var(--gold);border-width:2px;}

/* ── SUCCESS ── */
.success-overlay{
  display:none;position:fixed;inset:0;z-index:400;
  background:rgba(26,18,9,0.7);backdrop-filter:blur(8px);
  align-items:center;justify-content:center;padding:1rem;
}
.success-overlay.open{display:flex;}
.success-box{
  background:var(--ivory);border-radius:22px;
  padding:2rem;text-align:center;max-width:320px;width:100%;
  border:1.5px solid var(--border);
  box-shadow:0 30px 60px rgba(0,0,0,0.2);
}
.s-tile{
  width:64px;height:64px;background:var(--forest);
  border-radius:10px;margin:0 auto 1rem;
  display:flex;align-items:center;justify-content:center;
  font-size:2rem;color:var(--ivory);
}
.s-title{font-family:'DM Serif Display',serif;font-size:1.5rem;color:var(--ink);margin-bottom:6px;}
.s-text{font-size:13px;color:var(--ink-light);line-height:1.6;margin-bottom:1.2rem;}

/* ── MAHJONG TILE DECO ── */
.tile-deco{
  display:flex;gap:8px;justify-content:center;align-items:center;
  margin:1.6rem 0 0.4rem;opacity:0.22;
}
.tile-deco-item{
  width:22px;height:28px;
  background:var(--tile-white);
  border:1.5px solid var(--ink);
  border-radius:3px;
  display:flex;align-items:center;justify-content:center;
  font-size:11px;color:var(--red);
  font-family:'DM Serif Display',serif;
}

/* ── TAB NAV ── */
/* The frosted bar itself spans the full row edge-to-edge; the buttons inside
   (.tab-nav-inner) stay capped and centered to line up with .wrap below. */
.tab-nav{
  background:var(--ivory);
  position:sticky;
  top:0;
  z-index:50;
}
.tab-nav-inner{
  display:flex;
  gap:0;
  padding:0 1.2rem;
  max-width:620px;margin:0 auto;
}
@media(min-width:900px){.tab-nav-inner{max-width:760px;}}
/* Below 620px the inner row is un-capped (there's no room to center it away
   from the edges) and really does reach the real screen edge, where
   .admin-bar's fixed button floats — reserve space for it here instead of
   overlapping. */
@media(max-width:619px){.tab-nav-inner{padding-right:96px;}}
.tab-btn{
  flex:1;
  padding:14px 0;
  background:none;
  border:none;
  border-bottom:2.5px solid transparent;
  font-family:'Inter',sans-serif;
  font-size:13px;
  font-weight:300; /* matches .hero-subtitle's light weight — bold only kicks in on .active below */
  letter-spacing:0.01em;
  color:var(--ink-light);
  cursor:pointer;
  transition:all 0.2s;
}
.tab-btn.active{
  color:var(--forest);
  border-bottom-color:var(--forest);
  font-weight:600;
}

/* ── STORY PAGE ── */
.story-wrap{
  max-width:560px;
  margin:0 auto;
  padding:0 1.4rem 3rem;
}
.story-hero{
  text-align:center;
  padding:2rem 0 1.5rem;
  border-bottom:1px solid var(--border-light);
  margin-bottom:2rem;
}
.story-kicker{
  font-size:11px;
  letter-spacing:0.12em;
  text-transform:uppercase;
  color:var(--ink-light);
  margin-bottom:0.4rem;
}
.story-title{
  font-family:'DM Serif Display',serif;
  font-size:1.7rem;
  color:var(--ink);
  line-height:1.2;
  margin:0 0 0.8rem;
}
.story-title em{color:var(--forest);font-style:italic;}
.story-tile-deco{font-size:1.4rem;margin-top:0.4rem;}
.story-body{
  font-family:'Inter',sans-serif;
  font-size:15px;
  color:var(--ink);
  line-height:1.75;
}
.story-body p{margin:0 0 1.2rem;}
.story-lead{
  font-size:17px;
  font-weight:600;
  color:var(--ink);
  margin-bottom:1rem !important;
}
.story-pull{
  border-left:3px solid var(--forest);
  padding:0.6rem 1rem;
  margin:1.4rem 0;
  font-size:16px;
  font-style:italic;
  color:var(--forest);
  background:rgba(34,79,53,0.05);
  border-radius:0 10px 10px 0;
}
.story-tiles-row{
  display:flex;
  gap:10px;
  margin:1.2rem 0;
}
.story-tile-card{
  flex:1;
  background:var(--ivory);
  border:1.5px solid var(--border-light);
  border-radius:12px;
  padding:12px 8px;
  text-align:center;
  font-size:22px;
  font-family:'Inter',sans-serif;
}
.story-tile-card div{
  font-size:11px;
  color:var(--ink-light);
  letter-spacing:0.06em;
  text-transform:uppercase;
  margin-top:4px;
  font-weight:600;
}
.story-name-block{
  background:var(--forest);
  color:white;
  border-radius:16px;
  padding:1.4rem;
  text-align:center;
  margin:1.6rem 0;
}
.story-name-title{font-size:12px;letter-spacing:0.1em;text-transform:uppercase;opacity:0.7;margin-bottom:8px;}
.story-name-word{font-family:'DM Serif Display',serif;font-size:2.2rem;font-style:italic;margin-bottom:8px;}
.story-name-def{font-size:14px;opacity:0.9;line-height:1.5;}
.story-cta{
  background:var(--ivory);
  border:1.5px solid var(--border-light);
  border-radius:16px;
  padding:1.8rem;
  text-align:center;
  margin-top:2rem;
}
.story-cta-text{font-size:14px;color:var(--ink-light);margin-bottom:1rem;line-height:1.6;}
.story-cta-big{font-family:'DM Serif Display',serif;font-size:2rem;color:var(--ink);}
.story-cta-sub{font-size:14px;color:var(--ink-light);margin-top:4px;}
.gallery-section{
  padding:2rem 1.2rem 1.5rem;
  max-width:680px;
  margin:0 auto;
}
.carousel-wrap{
  overflow:hidden;
  border-radius:14px;
  position:relative;
}
.carousel-track{
  display:flex;
  gap:10px;
  animation:carousel-scroll 18s linear infinite;
  width:max-content;
}
.carousel-track:hover{
  animation-play-state:paused;
}
.carousel-slide{
  width:220px;
  height:160px;
  border-radius:12px;
  flex-shrink:0;
  overflow:hidden;
  display:flex;
  align-items:center;
  justify-content:center;
}
.carousel-slide img{
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}
.slide-placeholder{
  text-align:center;
  font-size:13px;
  color:rgba(255,255,255,0.8);
  font-family:'Inter',sans-serif;
  line-height:1.6;
}
@keyframes carousel-scroll{
  0%{transform:translateX(0);}
  100%{transform:translateX(calc(-230px * 6));}
}
@media(max-width:480px){
  .carousel-slide{width:170px;height:130px;}
  @keyframes carousel-scroll{
    0%{transform:translateX(0);}
    100%{transform:translateX(calc(-180px * 6));}
  }
}

/* ── MOBILE ── */
@media(max-width:480px){
  .info-grid{grid-template-columns:repeat(3,1fr);}
  .date-grid{grid-template-columns:repeat(3,1fr);}
  .session-grid{grid-template-columns:1fr 1fr;}
  .hero-title{font-size:2.4rem;}
  .admin-bar{top:10px;right:10px;}
}




/* ── SITE FOOTER ── */
/* A slim row, not a card — a thin top border is the only thing marking it
   off from the page content above, so it reads as "the footer" rather than
   another content section in the scroll. */
.site-footer{
  margin-top:1.4rem;
  padding-top:1.2rem;
  border-top:1px solid var(--border-light);
  text-align:center;
}
.sf-links{
  display:flex;flex-wrap:wrap;
  justify-content:center;
  gap:6px 18px;
  margin-bottom:10px;
}
.sf-links a{
  font-size:12px;color:var(--ink-light);
  text-decoration:none;
}
.sf-links a:hover{color:var(--forest);text-decoration:underline;text-underline-offset:2px;}
.sf-copyright{font-size:11px;color:var(--ink-light);opacity:0.6;letter-spacing:0.04em;}

