:root{--primary:#1BA94E;--primary-light:#2bc46a;--accent:#4FA8EA;--bg:#f0f9ff;--card:#fff;--text:#1a2e1f;--muted:#5a7a68;--border:#d0e8d8}
*{box-sizing:border-box;margin:0;padding:0}
html{font-size:100%;-webkit-text-size-adjust:100%;text-size-adjust:100%}
body{font-family:'Noto Sans TC','Noto Serif TC',sans-serif;background:var(--bg);color:var(--text);line-height:1.6}
.container{max-width:1200px;margin:0 auto;padding:0 1rem}
/* The browser paints this hero before main.js turns it into the event slider.
   Whatever the two disagree about, the visitor sees as a jolt the moment the
   script runs - so the plain hero is given the slider's own box: the same
   background, the same band for the pagination dots, the same frame shape and
   the same height cap. Change one and change the other; the pair is checked
   further down beside the slider's rules. */
header.hero{
  width:100%;
  padding:0 0 26px;
  margin:0;border:none;
  background-color:var(--bg);
  line-height:0;
  position:relative;
  z-index:0;
  cursor:pointer;
  overflow:hidden;
}
header.hero:focus-visible{outline:3px solid var(--primary);outline-offset:-3px}
header.hero img{
  width:100%;
  height:auto;
  display:block;
  vertical-align:top;
  transition:filter .25s ease;
}
/* Sized by class, never by `header.hero img`: the slider's own layers are
   images inside this same header, and a shared cap would squeeze them too. */
header.hero .hero-pc{
  display:block;
  aspect-ratio:1672 / 941;
  max-height:min(90vh,860px);
  object-fit:contain;
  object-position:center center;
}
header.hero .hero-mobile{display:none}
@media(hover:hover){header.hero:hover img{filter:brightness(.985)}}
/* The script chooses the artwork by orientation, not by width, so this must
   ask the same question - otherwise a portrait tablet is handed the landscape
   artwork for one frame and then swapped. */
@media (orientation:portrait){
  header.hero .hero-pc{display:none}
  header.hero .hero-mobile{
    display:block;
    aspect-ratio:839 / 1600;
    max-height:min(88vh,900px);
    object-fit:contain;
    object-position:center center;
  }
}
/* Matches the slider's rule for the welcome artwork on a phone: too tall to
   fit, so it fills the frame instead of sitting in from the sides. */
@media (orientation:portrait) and (max-width:560px){
  header.hero .hero-mobile{object-fit:cover}
}

/* Fullscreen video + text */
#heroFS{
  display:none;
  position:fixed;
  inset:0;
  z-index:10000;
  background:#07110b;
  flex-direction:column;
  align-items:center;
  justify-content:center;
}
#heroFS.show{display:flex}
#heroFS video{
  width:100%;
  height:100%;
  object-fit:contain;
  background:#07110b;
}
#heroFS .hero-fs-text{
  display:none;
  position:absolute;
  inset:0;
  align-items:center;
  justify-content:center;
  text-align:center;
  padding:1.5rem;
  /* cream background */
  background:#f5ead6;
}
#heroFS .hero-fs-text.show{display:flex}
#heroFS .hero-fs-inner{width:min(92%,900px)}
#heroFS .hero-fs-title{
  font-family:'Noto Serif TC','Noto Sans TC',serif;
  font-size:clamp(1.6rem,5.5vw,3rem);
  font-weight:700;
  letter-spacing:.08em;
  color:#1a2e1f;
  line-height:1.4;
}
#heroFS .hero-fs-divider{
  width:48px;height:2px;margin:.85rem auto;
  background:var(--primary);
  border-radius:999px;
}
#heroFS .hero-fs-subtitle{
  font-family:'Noto Sans TC',sans-serif;
  font-size:clamp(1.15rem,3.5vw,1.65rem);
  font-weight:500;
  letter-spacing:.12em;
  color:#1a2e1f;
  line-height:1.5;
}
#heroFS .hero-fs-close{
  position:absolute;
  top:16px;
  right:18px;
  z-index:3;
  background:rgba(26,46,31,.12);
  border:1px solid rgba(26,46,31,.35);
  color:#1a2e1f;
  width:42px;
  height:42px;
  border-radius:50%;
  font-size:1.5rem;
  cursor:pointer;
  display:flex;
  align-items:center;
  justify-content:center;
  line-height:1;
}
#heroFS .hero-fs-close:hover{background:rgba(27,169,78,.85);color:#fff;border-color:transparent}

/* ===== 崇拜程序表 =====
   Two cards, one per weekend service, sitting where someone who has just
   opened the page is already looking. Each says when the next one is, where
   it is, and hands over the order of service - and, the part that matters,
   whether the file it is handing over is this week's or last week's. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* ===== 每周崇拜資訊 =====
   Two cards inside 崇拜, one per weekend service, coloured the same way the
   事奉人員表 rota already tells its two lists apart (.rota-blue / .rota-green
   below) so the colour means the same thing everywhere on the page: blue is
   Saturday, green is Sunday.
   The two cards are mirrored rather than identical - the Saturday card reads
   calendar / service / QR left-to-right, the Sunday one is the same three
   parts in reverse - so that side by side on a desktop they read as a pair
   facing each other, not two copies of the same card. Reversed with `order`
   alone: the markup keeps the same reading order (when, what, get) in both,
   so a screen reader hears the same sequence either way. */
.bulletin-grid{display:grid;gap:1rem;grid-template-columns:1fr;margin:0 0 .75rem}
@media(min-width:900px){.bulletin-grid{grid-template-columns:1fr 1fr}}
@media (orientation:landscape) and (min-width:760px) and (max-width:899px){
  .bulletin-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .bcard{padding:.9rem;gap:.65rem}
}
.bcard{
  border-radius:16px;
  padding:1.1rem 1.2rem;
  display:grid;
  grid-template-columns:auto 1fr auto;
  align-items:center;
  gap:1rem;
  border:1px solid var(--border);
}
.bcard-sat{background:#f3f8fd;border-color:#cfe0f1}
.bcard-sun{background:#f3faf5;border-color:#cfe8d7}
@media(min-width:900px){
  /* The visual swap: Sunday's calendar block and QR trade places, so the
     card reads QR / service / calendar - a mirror image of Saturday's. */
  .bcard-sun .bcard-when{order:3}
  .bcard-sun .bcard-get{order:1}
  .bcard-sun .bcard-what{order:2}
}
/* On a phone the three parts stack and centre rather than squeezing into
   three columns that would each be too narrow to read - and both cards stack
   in the same calendar / service / QR order, since the mirroring is a desktop
   trick for reading two cards side by side that has nothing to say about a
   single column. */
@media(max-width:560px){
  .bcard{grid-template-columns:1fr;justify-items:center;text-align:center;gap:.85rem}
  .bcard-when,.bcard-what,.bcard-get{order:initial}
}
.bcard-when{display:flex;flex-direction:column;align-items:center;gap:.15rem;flex:none}
.bcard-cal{width:4.608rem;height:4.608rem}
.bcard-sat .bcard-cal{color:#2b6cb0}
.bcard-sun .bcard-cal{color:var(--primary)}
.bcard-when-label{font-size:.9rem;color:#000;font-weight:700}
.bcard-date{font-size:1.05rem;font-weight:800;font-variant-numeric:tabular-nums;color:#ee3690!important}
.bcard-sat .bcard-date{color:#2b6cb0}
.bcard-sun .bcard-date{color:var(--primary)}
.bcard-what{min-width:0}
@media(min-width:561px){.bcard-what{text-align:center}}
.bcard-what h4{margin:0;font-size:1.35rem;line-height:1.3}
.bcard-sat .bcard-what h4{color:#2b6cb0}
.bcard-sun .bcard-what h4{color:var(--primary)}
.bcard-time{margin:.15rem 0 0;font-size:1.1rem;font-weight:700;color:#ee3690!important}
.bcard-where{margin:.3rem 0 0;font-size:.9rem;color:var(--muted);display:flex;
  align-items:center;justify-content:center;gap:.25rem;flex-wrap:wrap}
.bcard-where svg{width:1em;height:1em;flex:none}
.bcard-get{display:flex;flex-direction:column;align-items:center;gap:.4rem;flex:none}
.bcard-qr{display:flex;flex-direction:column;align-items:center;gap:.25rem;
  text-decoration:none;color:inherit;border-radius:12px;padding:.4rem}
.bcard-sat .bcard-qr:hover,.bcard-sat .bcard-qr:focus-visible{background:#e4f0fa}
.bcard-sun .bcard-qr:hover,.bcard-sun .bcard-qr:focus-visible{background:#e3f5e8}
.bcard-pdf{width:57.4px;height:67.2px;display:block;overflow:visible}
.bcard-sat .bcard-pdf{color:#2b6cb0}
.bcard-sun .bcard-pdf{color:var(--primary)}
.bcard-get-label{font-size:.9rem;font-weight:800}
.bcard-sat .bcard-get-label{color:var(--primary)}
.bcard-sun .bcard-get-label{color:var(--primary)}
.bcard-get-hint{font-size:.74rem;color:var(--muted)}
/* Worship card hierarchy: blue/green identify the service and its icons;
   dates, times, day labels and the PDF action stay neutral for easier reading. */
.bcard-date,.bcard-time{color:var(--text)!important}
.bcard-get-label{color:#000!important}
.bcard-sat .bcard-what>div,.bcard-sun .bcard-what>div{color:var(--text)!important}

/* One line, for both cards - the address never changes, only the file behind
   it, so there is one thing to say rather than one per card. */
.bulletin-note{margin:0;font-size:.85rem;color:var(--muted);text-align:center}

/* 崇拜時間 table rows, tinted to match: 黃昏堂崇拜 is Saturday's blue, the
   three Sunday services are green. 聖餐主日 is dated by the month, not a day
   of the week, and stays neutral. */
.times-table tr[data-day="sat"]{color:#2b6cb0}
.times-table tr[data-day="sat"] td:first-child{color:#2b6cb0;font-weight:600}
.times-table tr[data-day="sun"]{color:var(--primary)}
.times-table tr[data-day="sun"] td:first-child{color:var(--primary);font-weight:600}

.notice{background:#e8f7ee;border-left:4px solid var(--primary);padding:0.85rem 0;margin:1.5rem 0;border-radius:0 8px 8px 0;overflow:hidden;white-space:nowrap}
/* 42s, down from the original 90s (and a first pass at 60s) - the
   anniversary notice that used to share this track is gone, so one pass
   is much shorter now and the old durations left it crawling. Still
   comfortably readable for a long Chinese sentence. */
.marquee-track{display:inline-block;padding-left:100%;animation:marquee 42s linear infinite;will-change:transform;backface-visibility:hidden;transform:translateZ(0)}
.notice:hover .marquee-track{animation-play-state:paused}
@keyframes marquee{0%{transform:translateX(0)}100%{transform:translateX(-100%)}}
section{background:var(--card);border-radius:12px;padding:1.5rem;margin-bottom:1.25rem;border:1px solid var(--border)}
section[id]{scroll-margin-top:72px}
section h2{
  color:var(--primary);
  margin-bottom:1rem;
  padding-bottom:.5rem;
  border-bottom:2px solid var(--border);
  overflow:visible;
}
section h2 svg{
  overflow:visible;
  flex-shrink:0;
  display:inline-block;
  vertical-align:-0.2em;
}
#donation h2{
  display:flex;
  align-items:center;
  gap:.35rem;
  overflow:visible;
}
#donation h2 .heart-icon{
  width:1.25em;
  height:1.25em;
  flex:0 0 auto;
  overflow:visible;
  display:block;
}
.info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1rem}
.info-item strong{display:block;color:var(--muted);font-size:.8rem;margin-bottom:.2rem}
.info-item a{color:var(--text);text-decoration:none}
.info-item a:hover{color:var(--primary-light);text-decoration:underline}
.times-table{width:100%;border-collapse:collapse}
.times-table:not(.wide-table){table-layout:fixed}
.times-table th,.times-table td{padding:.5rem .75rem;border-bottom:1px solid var(--border);text-align:left;word-break:break-word}
.times-table th{background:#e8f7ee;color:var(--primary)}
.times-table:not(.rota-table):not(.wide-table) td:first-child,.times-table:not(.rota-table):not(.wide-table) th:first-child{width:42%}
@media(max-width:480px){.times-table:not(.rota-table):not(.wide-table) td:first-child,.times-table:not(.rota-table):not(.wide-table) th:first-child{width:38%}}
.children-groups{display:grid;gap:.5rem;padding:.25rem .75rem 0}
.children-groups strong{color:var(--primary);margin-right:.5rem}
.children-groups .note-label{margin:0 0 .1rem;color:var(--muted);font-weight:700}
.section-separator{margin-top:2rem!important;padding-top:1.75rem}
.rota-grid{display:grid;grid-template-columns:1fr;gap:1rem;margin:.75rem 0}
@media(min-width:640px){.rota-grid{grid-template-columns:1fr 1fr}}
.rota-box{border-radius:10px;overflow:hidden;border:1px solid var(--border)}
.rota-box .times-table{margin:0}
.rota-box.rota-blue .times-table th{background:#dbeefc;color:#2b6cb0}
.rota-box.rota-blue .times-table td{background:#eef7fd}
.rota-box.rota-green .times-table th{background:#d9f0e3;color:var(--primary)}
.rota-box.rota-green .times-table td{background:#eef9f2}
.roster-tab{width:132px;min-height:44px;padding:.5rem .65rem;border:2px solid var(--primary);border-radius:8px;cursor:pointer;font-size:.95rem;background:#fff;color:var(--primary);font-family:inherit;display:inline-flex;align-items:center;justify-content:center;white-space:nowrap}
.roster-tab.active{background:var(--primary);color:#fff}
.roster-actions{display:flex;align-items:center;justify-content:center;gap:.5rem;flex-wrap:nowrap;margin-top:1rem;margin-bottom:.75rem;overflow-x:auto;padding-bottom:.1rem}
.roster-source-btn{min-width:132px;width:auto;min-height:44px;padding:.5rem .9rem;border:2px solid var(--accent);border-radius:8px;background:var(--accent);color:#fff;text-decoration:none;font-size:.9rem;font-weight:600;display:inline-flex;align-items:center;justify-content:center;white-space:nowrap;flex:0 0 auto}
.roster-source-btn:hover{background:#2f8fcf;border-color:#2f8fcf}
/* min-width, not width: the label must never wrap, so the button grows with
   the type instead. At the page's larger font sizes a fixed 170px broke
   "Google Play" across two lines. */
.app-btn{display:inline-flex;align-items:center;justify-content:center;gap:.65rem;background:#111;color:#fff;min-width:170px;min-height:52px;padding:.65rem 1rem;border-radius:10px;text-decoration:none;font-weight:600;margin:.25rem;white-space:nowrap}
.app-btn svg{width:22px;height:22px;display:block;flex:0 0 22px}
footer{text-align:center;padding:2rem 1rem;color:var(--muted);font-size:.85rem}
.top-nav{position:sticky;top:0;z-index:1000;background:rgba(255,255,255,.97);border-bottom:1px solid var(--border)}
.top-nav .nav-inner{max-width:1200px;margin:0 auto;padding:0 1rem;display:flex;align-items:center;justify-content:space-between;height:52px;position:relative}
.top-nav .nav-logo{font-weight:700;color:var(--primary);text-decoration:none;font-size:1.1rem;white-space:nowrap;display:inline-flex;align-items:center;gap:.5rem;order:1}
.top-nav .nav-links{display:flex;gap:.15rem;list-style:none;margin:0;padding:0;order:2;margin-left:auto}
.top-nav .nav-links a{display:flex;align-items:center;padding:.4rem .5rem;color:var(--text);text-decoration:none;font-size:.82rem;border-radius:6px;white-space:nowrap}
.top-nav .nav-links a:hover{background:#e8f7ee;color:var(--primary)}
/* ...and stays where it was on a phone, where the menu is a plain list with
   room to spare and an app link is the thing most worth reaching in one tap. */
@media(max-width:768px){
  .top-nav .nav-group-menu .nav-app-in-group{display:none}
}
@media(min-width:769px){
  /* The QR code belongs to the phone menu. On a wide screen the row is already
     at the width where it starts to crowd the logo, and the page carries a
     QR button of its own further down, so one more item here would cost more
     than it gives. */
  .top-nav .nav-links .nav-qr-item{display:none}
  /* App moves in under 資源 on a wide screen. The row was carrying eight
     items beside the logo and starting to crowd it; the app is a resource
     like the others in there, so it is the one that belongs in the group. */
  .top-nav .nav-links > .nav-app-item{display:none}
  .top-nav .nav-links{position:absolute;left:50%;transform:translateX(-50%);margin-left:0;gap:.35rem}
  .top-nav .nav-links a svg{display:block;width:1em!important;height:1em!important;flex:0 0 1em;margin-right:.25rem!important}
  .nav-right{margin-left:auto}
}

.nav-right{display:flex;align-items:center;gap:.35rem;flex-shrink:0;order:3}
.font-btn{
  background:#dbeefc;border:1px solid #a9d3ee;color:#1b4f72;
  width:36px;height:36px;border-radius:8px;cursor:pointer;
  font-family:'Noto Sans TC',sans-serif;font-weight:700;font-size:1rem;
  display:inline-flex;align-items:center;justify-content:center;
  line-height:1;padding:0;-webkit-tap-highlight-color:transparent
}
.font-btn:hover{background:#c3e4f8;color:#123b56}
.font-btn:focus-visible{outline:3px solid #2b6cb0;outline-offset:2px}
.font-btn:active{transform:scale(.95)}
.hamburger{display:none;background:none;border:none;cursor:pointer;padding:.45rem;flex-direction:column;gap:5px;min-width:40px;min-height:40px;align-items:center;justify-content:center;flex-shrink:0}
.hamburger span{display:block;width:22px;height:2.5px;background:var(--primary);border-radius:2px}

@media(max-width:768px){
  .hamburger{display:flex}
  #churchStoryCaption{font-size:1.3rem;padding:.5rem .75rem}
  .top-nav .nav-inner{height:56px;padding:0 .5rem;justify-content:space-between}
  .top-nav .nav-logo{
    position:absolute;left:50%;transform:translateX(-50%);
    font-size:1.18rem;max-width:calc(100% - 150px);overflow:hidden;
    justify-content:center
  }
  .top-nav .nav-logo span{overflow:hidden;text-overflow:ellipsis}
  .top-nav .nav-logo img{width:28px;height:28px}
  .nav-right{gap:.2rem;margin-left:auto}
  .font-btn{width:32px;height:32px;font-size:.9rem;border-radius:7px}
  .top-nav .nav-links{
    display:flex;position:absolute;top:56px;left:0;right:0;background:#fff;
    flex-direction:column;padding:.5rem 0;border-bottom:1px solid var(--border);
    box-shadow:0 6px 16px rgba(0,0,0,.08);margin-left:0;order:10;
    max-height:calc(100vh - 56px);overflow-y:auto;
    opacity:0;visibility:hidden;pointer-events:none;
    transform:translateY(-12px);
    clip-path:inset(0 0 100% 0);
    transform-origin:top center;
    will-change:transform,clip-path,opacity;
    transition:clip-path .34s cubic-bezier(.4,0,.2,1),
      transform .34s cubic-bezier(.4,0,.2,1),
      opacity .18s ease,
      visibility 0s linear .34s
  }
  .top-nav .nav-links.open{
    opacity:1;visibility:visible;pointer-events:auto;
    transform:translateY(0);
    clip-path:inset(0 0 0 0);
    transition:clip-path .36s cubic-bezier(.2,.8,.2,1),
      transform .36s cubic-bezier(.2,.8,.2,1),
      opacity .18s ease,
      visibility 0s
  }
  .top-nav .nav-links a{
    font-size:1.1rem;padding:.85rem 1.25rem;border-bottom:1px solid var(--border)
  }
  .top-nav .nav-links li:last-child a{border-bottom:none}
  .hamburger span{width:26px;height:3px}
  section[id]{scroll-margin-top:76px}
  .roster-actions{justify-content:flex-start}
  .roster-tab{width:112px;flex-basis:112px;font-size:.8rem;padding:.45rem .35rem}
  .roster-source-btn{min-width:112px;width:auto;flex-basis:auto;font-size:.8rem;padding:.45rem .6rem}
  .app-btn{min-width:160px}
}
#backToTop{position:fixed;bottom:24px;right:20px;width:44px;height:44px;border-radius:50%;background:var(--primary);color:#fff;border:none;font-size:1.3rem;cursor:pointer;opacity:0;visibility:hidden;z-index:900;display:flex;align-items:center;justify-content:center;box-shadow:0 3px 12px rgba(27,169,78,.35)}
#backToTop.show{opacity:1;visibility:visible}
#backToTop:hover{transform:translateY(-3px)}
#backToTop::after{content:"返回頂部";position:absolute;right:52px;top:50%;transform:translateY(-50%);background:#1a2e1f;color:#fff;padding:.35rem .7rem;border-radius:6px;font-size:.8rem;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .2s}
#backToTop:hover::after{opacity:1}

.roster-wrap{position:relative}
.roster-heading{position:relative;margin-bottom:1rem}
.roster-heading h2{margin-bottom:0;padding-right:8.75rem}
.roster-heading .roster-expand-btn{position:absolute;top:0;right:0}
.roster-expand-btn{position:absolute;top:8px;right:8px;z-index:5;background:var(--accent);color:#fff;border:none;padding:.4rem .75rem;border-radius:6px;cursor:pointer;font-size:.8rem;font-family:inherit;box-shadow:0 2px 8px rgba(0,0,0,.15);display:inline-flex;align-items:center;gap:.35rem}
.roster-expand-btn svg{width:1.05em;height:1.05em;flex:0 0 auto}
.roster-expand-btn:hover{background:#2f8fcf}
#rosterModal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:9998;flex-direction:column;padding:0}
#rosterModal.show{display:flex}
#rosterModal .modal-bar{display:flex;align-items:center;justify-content:space-between;padding:.6rem 1rem;background:#1a2e1f;color:#fff;flex-shrink:0}
#rosterModal .modal-bar button{background:transparent;border:none;color:#fff;font-size:1.6rem;cursor:pointer;line-height:1;padding:.2rem .5rem}
#rosterModal iframe{flex:1;width:100%;border:none;background:#fff}

#churchStoryWrap{position:relative;width:100%;max-width:720px;margin:0 auto;border-radius:12px;overflow:hidden;background:#000;box-shadow:0 4px 16px rgba(0,0,0,.15)}
#churchStoryCaption{position:absolute;left:0;right:0;bottom:0;padding:.6rem 1rem;text-align:center;color:#ffd400;font-size:2.3rem;font-weight:700;line-height:1.45;background:linear-gradient(transparent,rgba(0,0,0,.78));text-shadow:0 1px 3px rgba(0,0,0,.85);pointer-events:none;min-height:1.4em;white-space:pre-line}
#churchStoryCaption:empty{background:none}
#churchStoryClose{display:none;position:absolute;top:16px;right:18px;z-index:2;width:42px;height:42px;padding:0;border:1px solid rgba(255,255,255,.65);border-radius:50%;background:rgba(0,0,0,.45);color:#fff;cursor:pointer;align-items:center;justify-content:center}
#churchStoryClose svg{display:block;width:22px;height:22px;flex:0 0 22px}
#churchStoryClose:hover{background:rgba(0,0,0,.72)}
#churchStoryWrap:fullscreen,#churchStoryWrap:-webkit-full-screen{width:100vw;height:100vh;max-width:none;border-radius:0;display:flex;align-items:center;justify-content:center}
#churchStoryWrap:fullscreen video,#churchStoryWrap:-webkit-full-screen video{width:auto!important;height:100%!important;max-height:100vh!important;max-width:100vw!important}
#churchStoryWrap:fullscreen #churchStoryCaption,#churchStoryWrap:-webkit-full-screen #churchStoryCaption{font-size:2.3rem;padding:1.1rem 2rem}
#churchStoryWrap:fullscreen #churchStoryClose,#churchStoryWrap:-webkit-full-screen #churchStoryClose,#churchStoryWrap.is-fullscreen #churchStoryClose{display:flex}
@media(max-width:768px),(hover:none) and (pointer:coarse){
  #churchStoryCaption{font-size:1.3rem}
  #churchStoryWrap:fullscreen #churchStoryCaption,#churchStoryWrap:-webkit-full-screen #churchStoryCaption{font-size:2rem}
}

.gallery-trigger{display:block;width:100%;padding:0;border:0;background:transparent;cursor:zoom-in}
.gallery-trigger img{display:block;transition:filter .2s ease}
.gallery-trigger:hover img{filter:brightness(.92)}
.gallery-trigger:focus-visible{outline:3px solid var(--primary);outline-offset:3px}
#venueGallery{display:none;position:fixed;inset:0;z-index:10001;background:rgba(0,0,0,.9);padding:1rem;align-items:center;justify-content:center}
#venueGallery.show{display:flex}
#venueGallery .gallery-stage{position:relative;width:min(100%,1100px);height:100%;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;gap:.8rem;padding:1rem 0;align-items:center;touch-action:pan-y}
#venueGalleryImage{grid-row:1;display:block;width:100%;height:100%;max-width:100%;max-height:none;object-fit:contain}
#venueGalleryCaption{min-height:1.5em;margin:0;color:#fff;text-align:center;font-size:1rem;font-weight:600}
.gallery-caption-bar{grid-row:2;width:100%;display:grid;grid-template-columns:46px minmax(0,1fr) 46px;gap:.75rem;align-items:center}
.gallery-caption-bar .gallery-control{position:static;transform:none}
.gallery-control{position:absolute;z-index:2;width:46px;height:46px;padding:0;border:1px solid rgba(255,255,255,.65);border-radius:50%;background:rgba(0,0,0,.45);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center}
.gallery-control:hover{background:rgba(0,0,0,.75)}
.gallery-control:focus-visible{outline:3px solid #fff;outline-offset:3px}
.gallery-control svg{width:24px;height:24px;display:block}
#venueGalleryClose{top:10px;right:10px}
#venueGalleryPrev{left:10px;top:50%;transform:translateY(-50%)}
#venueGalleryNext{right:10px;top:50%;transform:translateY(-50%)}
.gallery-caption-bar #venueGalleryPrev,.gallery-caption-bar #venueGalleryNext{position:static;transform:none}
@media(max-width:768px){
  #venueGallery{padding:.75rem}
  .gallery-control{width:42px;height:42px}
  .gallery-caption-bar{grid-template-columns:42px minmax(0,1fr) 42px;gap:.5rem}
  #venueGalleryPrev{left:0}
  #venueGalleryNext{right:0}
}


/* ===== 共享空間相片 carousel =====
   Different source ratios (4:3 / 16:9) share one locked stage height.
   All slides are stacked and cross-fade; object-fit:contain keeps each photo
   whole and centred. */
.coffee-corner-cell{padding:.85rem .75rem 1rem!important}
.coffee-corner-carousel{
  position:relative;
  width:min(100%,760px);
  margin:0 auto;
}
.coffee-corner-stage{
  position:relative;
  height:280px;
  overflow:hidden;
  border:1px solid var(--border);
  border-radius:12px;
  background:rgba(0,0,0,.035);
}
.coffee-corner-slide{
  position:absolute;
  inset:0;
  display:block;
  width:100%;
  height:100%;
  margin:0;
  opacity:0;
  visibility:hidden;
  pointer-events:none;
  transition:opacity .7s ease-in-out,visibility 0s linear .7s;
}
.coffee-corner-slide.is-active{
  opacity:1;
  visibility:visible;
  pointer-events:auto;
  transition:opacity .7s ease-in-out,visibility 0s linear 0s;
}
.coffee-corner-slide .gallery-trigger{
  width:100%;
  height:100%;
  display:flex;
  align-items:center;
  justify-content:center;
}
.coffee-corner-slide img{
  display:block;
  width:100%;
  height:100%;
  object-fit:contain;
  object-position:center;
}
.coffee-corner-slide figcaption{
  position:absolute;
  width:1px;
  height:1px;
  padding:0;
  margin:-1px;
  overflow:hidden;
  clip:rect(0,0,0,0);
  white-space:nowrap;
  border:0;
}
.coffee-corner-arrow{
  position:absolute;
  top:50%;
  z-index:3;
  width:42px;
  height:42px;
  border:1px solid rgba(255,255,255,.72);
  border-radius:50%;
  background:rgba(0,0,0,.46);
  color:#fff;
  display:flex;
  align-items:center;
  justify-content:center;
  transform:translateY(-50%);
  cursor:pointer;
}
.coffee-corner-arrow:hover{background:rgba(0,0,0,.7)}
.coffee-corner-arrow:focus-visible{outline:3px solid var(--primary);outline-offset:2px}
.coffee-corner-arrow svg{width:23px;height:23px;display:block}
.coffee-corner-prev{left:10px}
.coffee-corner-next{right:10px}
.coffee-corner-dots{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:.5rem;
  margin-top:.65rem;
}
.coffee-corner-dot{
  width:10px;
  height:10px;
  padding:0;
  border:0;
  border-radius:50%;
  background:var(--border);
  cursor:pointer;
}
.coffee-corner-dot.is-active{background:var(--primary)}
.coffee-corner-count{
  margin:.45rem 0 0;
  text-align:center;
  color:var(--muted);
  font-size:.82rem;
}
.venue-location-carousel{margin-left:auto;margin-right:auto}
.venue-location-description{
  margin:.65rem 0 0;
  text-align:center;
  color:var(--text);
  font-size:.92rem;
  font-weight:600;
  min-height:1.45em;
  line-height:1.45;
}
@media(prefers-reduced-motion:reduce){
  .coffee-corner-slide{transition:none}
}
@media(max-width:768px){
  .coffee-corner-cell{padding:.7rem .45rem .85rem!important}
  .coffee-corner-stage{height:210px}
  .coffee-corner-arrow{width:38px;height:38px}
  .coffee-corner-prev{left:7px}
  .coffee-corner-next{right:7px}
}
@media(max-width:420px){
  .coffee-corner-stage{height:185px}
}

/* ===== Sandbox hero event slider =====
   Every slide ships two artworks: a horizontal (.h) one for landscape viewports
   and a vertical (.v) one for portrait viewports. The stage adopts the aspect
   ratio of whichever artwork is on screen, so a poster is shown whole - never
   cropped, and without letterbox bars. */
#heroWelcomeTrigger.hero-event-slider{
  min-height:0;
  padding:0 0 26px;
  background:var(--bg);
  overflow:hidden;
}
/* One fixed stage size per orientation, never per slide, so the hero keeps the
   same height all the way through the rotation and the page below it never
   shifts. Artwork is contained inside it, so a poster is never cropped. */
#heroWelcomeTrigger.hero-event-slider .hero-event-stage{
  position:relative;
  width:100%;
  aspect-ratio:1672 / 941;
  max-height:min(90vh,860px);
  background:var(--bg);
  overflow:hidden;
}
/* The two layers sit on top of each other: the incoming one fades up while the
   outgoing one fades down, so no frame of the transition shows bare stage. */
#heroWelcomeTrigger.hero-event-slider .hero-event-image{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  display:block;
  object-fit:contain;
  object-position:center center;
  opacity:0;
  transition:opacity .55s ease;
}
#heroWelcomeTrigger.hero-event-slider .hero-event-image.is-visible{opacity:1}
/* Labels that sit in the band above and below a poster. Placed from script,
   which measures the band first, so they only appear when there is room. */
#heroWelcomeTrigger.hero-event-slider .hero-event-caption{
  position:absolute;
  left:0;
  right:0;
  z-index:3;
  transform:translateY(-50%);
  padding:0 1rem;
  text-align:center;
  line-height:1.3;
  pointer-events:none;
}
/* The mark sits on the text's own baseline and scales with it, so enlarging
   the page type keeps the pair together. */
#heroWelcomeTrigger.hero-event-slider .hero-event-caption svg{
  width:1em;
  height:1em;
  display:inline-block;
  vertical-align:-.13em;
  margin-right:.3em;
  flex:none;
}
/* On a portrait phone, make only the video-camera mark above a video poster
   50% larger. Other poster/announcement icons keep their existing size. */
@media (max-width:768px) and (orientation:portrait){
  #heroWelcomeTrigger.hero-event-slider .hero-event-caption.is-top[data-icon="video"] svg{
    width:1.5em;
    height:1.5em;
    vertical-align:-.28em;
    margin-right:.35em;
  }
}
/* The page's own text colour: the label sits under the megaphone mark and
   needs no colour of its own to be read as one. The green it started with sat
   at 2.88:1 against the page, under the 3:1 that even large text needs; this
   is 12.6:1. */
#heroWelcomeTrigger.hero-event-slider .hero-event-caption.is-top{
  color:var(--text);
  font-weight:700;
  font-size:1.575rem;
  letter-spacing:.04em;
}
#heroWelcomeTrigger.hero-event-slider .hero-event-caption.is-bottom{
  color:var(--muted);
  font-size:1.32rem;
}
/* ===== 影片 =====
   A video plays inside the same stage the artwork uses, laid over it and cut
   to the same contain rule, so the hero's height never moves when one starts
   or stops. It takes no pointer events: a tap belongs to the stage, which
   opens the full-size player with sound. */
#heroWelcomeTrigger.hero-event-slider .hero-event-video{
  position:absolute;
  inset:0;
  z-index:2;
  /* One implicit row, which absorbs the whole stage height, so the video's
     height:100% has something definite to resolve against. Naming the rows
     here instead would give that height away to the named tracks and leave
     the frame at its content's intrinsic size - 300x150, in the middle of a
     1920px stage. Portrait names them, and does not need the height. */
  display:grid;
  justify-items:center;
  align-items:center;
  /* Its own ground, the stage's colour, across the whole frame. A 16:9 video
     over a poster cut for a phone would otherwise cover the middle and leave
     the ends of the poster showing - a hole, not a video. */
  background:var(--bg);
  opacity:0;
  transition:opacity .45s ease;
  pointer-events:none;
}
#heroWelcomeTrigger.hero-event-slider .hero-event-video.is-visible{opacity:1}
/* The rectangle the video occupies, fitted inside the stage exactly as
   object-fit:contain fits a picture - height from the stage, width from the
   height. Height first, not width: past about 1530px the stage stops growing
   taller, and anything sized by width alone then stands wider than the
   posters beside it and is cropped top and bottom.
   It must be a plain element rather than the frame or the video itself: a
   replaced element asked for a width of auto takes its own intrinsic width,
   and aspect-ratio then works the wrong way round.
   On a phone the stage is tall and this sits as a band across the middle of
   it, which is what a landscape video is there - so it is given a ground and
   a corner of its own and reads as a panel rather than as a picture that
   failed to fill the frame. */
#heroWelcomeTrigger.hero-event-slider .hero-event-video>.is-frame{
  position:relative;
  aspect-ratio:16 / 9;
  max-width:100%;
  max-height:100%;
  display:block;
  overflow:hidden;
  background:#0b1310;
  /* Landscape: the stage is about 16:9, so the height is what runs out first
     and the width follows from it. */
  height:100%;
  width:auto;
}
/* Portrait: the stage is far taller than any video, so the width runs out
   first and the height follows. The driving axis has to be chosen, not left
   to max-width and max-height - a clamp on one axis does not recompute the
   other, so whichever single rule is written alone overflows in the other
   orientation. This is the same question the stage itself answers with the
   same query, so the two always agree. */
@media (orientation:portrait){
  #heroWelcomeTrigger.hero-event-slider .hero-event-video>.is-frame{
    width:100%;
    height:auto;
    grid-row:2;
  }
  /* Three rows here, and only here: the empty page above the video, the
     video, the empty page below it. The outer two take half the leftover
     each, which turns the space under the video into a row the caption can
     be centred in rather than a gap that would have to be measured. */
  #heroWelcomeTrigger.hero-event-slider .hero-event-video{
    grid-template-rows:1fr auto 1fr;
  }
}
#heroWelcomeTrigger.hero-event-slider .hero-event-video>.is-frame>iframe,
#heroWelcomeTrigger.hero-event-slider .hero-event-video>.is-frame>video{
  width:100%;
  height:100%;
  border:0;
  display:block;
  object-fit:contain;
}
@media (orientation:portrait){
  #heroWelcomeTrigger.hero-event-slider .hero-event-video>.is-frame{
    border-radius:10px;
  }
}
/* The mark that says this one plays. Sized in em so it follows the page type,
   and drawn light-on-dark so it reads over any poster. */
#heroWelcomeTrigger.hero-event-slider .hero-event-play{
  position:absolute;
  top:50%;
  left:50%;
  transform:translate(-50%,-50%);
  z-index:4;
  width:3.6em;
  height:3.6em;
  border-radius:50%;
  display:grid;
  place-items:center;
  background:rgba(17,24,20,.62);
  border:2px solid rgba(255,255,255,.9);
  box-shadow:0 6px 24px rgba(0,0,0,.35);
  color:#fff;
  pointer-events:none;
}
#heroWelcomeTrigger.hero-event-slider .hero-event-play svg{
  width:1.7em;
  height:1.7em;
  margin-left:.12em;          /* a triangle looks off-centre when it is not */
}
#heroWelcomeTrigger.hero-event-slider .hero-event-play[hidden]{display:none}
/* The one sentence a silent moving picture needs: why there is no sound, and
   what to do about it.
   It is placed inside the video's own rectangle, not the stage's. The stage
   keeps one shape for the whole rotation, so its bottom corner is out on the
   page beside the picture whenever the video is narrower or shorter than the
   frame - which on a phone is always. */
#heroWelcomeTrigger.hero-event-slider .hero-event-pill{
  position:absolute;
  right:.9rem;
  /* Clear of the row of marks YouTube draws along its own bottom edge - the
     share and watch-later buttons, and 更多影片. In px rather than rem: what
     has to be cleared is YouTube's own chrome, which is drawn at a size of
     its own and pays no attention to this page's type scale. */
  bottom:70px;
  max-width:calc(100% - 1.8rem);
  z-index:5;
  display:flex;
  align-items:center;
  gap:.4em;
  padding:.4em .85em;
  border-radius:999px;
  background:rgba(17,24,20,.78);
  color:#fff;
  font-size:.95rem;
  font-weight:600;
  line-height:1.2;
  pointer-events:none;
  box-shadow:0 4px 16px rgba(0,0,0,.3);
}
#heroWelcomeTrigger.hero-event-slider .hero-event-pill svg{
  width:1.1em;
  height:1.1em;
  flex:none;
}
#heroWelcomeTrigger.hero-event-slider .hero-event-pill[hidden]{display:none}
/* Portrait: it is not on the video at all, it is on the page underneath,
   centred in the empty row the grid above makes for it. Plain words on the
   page background - a dark capsule out there would read as something stuck
   on the page rather than as a caption belonging to the video. */
@media (orientation:portrait){
  #heroWelcomeTrigger.hero-event-slider .hero-event-pill{
    grid-row:3;
    position:static;
    max-width:min(100%,32rem);
    padding:0 1rem;
    background:none;
    box-shadow:none;
    border-radius:0;
    color:var(--muted);
    /* Out on the page rather than over a picture, it is read at page size,
       not glanced at as an overlay - so it is set larger than the version
       that sits on the video. */
    font-size:1.4rem;
    text-align:center;
    justify-content:center;
    flex-wrap:wrap;
  }
}
@media (max-width:560px){
  #heroWelcomeTrigger.hero-event-slider .hero-event-pill{font-size:1.3rem}
  #heroWelcomeTrigger.hero-event-slider .hero-event-play{width:3em;height:3em}
}
/* Mobile 9:16: make the round play button over the poster 50% larger. */
@media (max-width:768px) and (orientation:portrait){
  #heroWelcomeTrigger.hero-event-slider .hero-event-play{width:4.5em;height:4.5em}
  #heroWelcomeTrigger.hero-event-slider .hero-event-play svg{width:2.55em;height:2.55em;margin-left:.18em}
}

/* First paint only: the hero should already be there, not fade in. */
#heroWelcomeTrigger.hero-event-slider .hero-event-stage.is-instant,
#heroWelcomeTrigger.hero-event-slider .hero-event-stage.is-instant .hero-event-image{
  transition:none;
}
@media (prefers-reduced-motion:reduce){
  #heroWelcomeTrigger.hero-event-slider .hero-event-stage,
  #heroWelcomeTrigger.hero-event-slider .hero-event-image{transition:none}
}
/* Portrait: the stage takes the welcome artwork's own shape, so slide 1 fills
   the frame edge to edge with nothing cropped. The posters are cut to a wider
   portrait shape, so they reach the same left and right edges and sit centred
   with matching bands above and below - identical to each other, and the same
   frame height throughout, so nothing below the hero moves as slides rotate.
   The cap only bites on portrait tablets, where the welcome shape would
   otherwise make the hero taller than the screen. */
@media (orientation:portrait){
  #heroWelcomeTrigger.hero-event-slider .hero-event-stage{
    aspect-ratio:839 / 1600;
    height:auto;
    max-height:min(88vh,900px);
  }
}
/* On a phone short enough that the welcome artwork's full height will not fit,
   let it fill the frame rather than sit in from the sides. It is a background
   photograph with generous sky and hills, so it gives up a little of those and
   never any wording. Posters are never treated this way - they stay contained,
   because cropping one would cut the details people need to read. */
@media (orientation:portrait) and (max-width:560px){
  #heroWelcomeTrigger.hero-event-slider .hero-event-image.is-welcome{
    object-fit:cover;
  }
}
/* Left/right arrows sit on the artwork itself, one to each side, vertically
   centred - the same dark translucent circle the play badge and muted pill
   already use elsewhere on this stage, so the chrome reads as one family. */
#heroWelcomeTrigger.hero-event-slider .hero-event-arrow{
  position:absolute;
  top:50%;
  transform:translateY(-50%);
  z-index:4;
  width:2.6em;
  height:2.6em;
  display:grid;
  place-items:center;
  padding:0;
  border-radius:50%;
  border:1px solid rgba(255,255,255,.7);
  background:rgba(17,24,20,.58);
  box-shadow:0 4px 14px rgba(0,0,0,.25);
  color:#fff;
  cursor:pointer;
}
#heroWelcomeTrigger.hero-event-slider .hero-event-arrow:hover{background:rgba(17,24,20,.78)}
#heroWelcomeTrigger.hero-event-slider .hero-event-arrow:focus-visible{
  outline:2px solid #fff;
  outline-offset:2px;
}
#heroWelcomeTrigger.hero-event-slider .hero-event-arrow svg{width:1.5em;height:1.5em}
/* The hero is full-bleed, but the nav bar above it (logo, resource menu)
   sits inside the site's own 1200px content column. Below that width the
   two edges coincide and the arrows sit at the plain rem inset; above it,
   the arrows fall in to line up with the logo and menu rather than
   floating further out in the hero's own margin than everything else on
   the page does. */
#heroWelcomeTrigger.hero-event-slider .hero-event-arrow-prev{left:max(1.5rem,calc((100% - 1200px)/2 + 1rem))}
#heroWelcomeTrigger.hero-event-slider .hero-event-arrow-next{right:max(1.5rem,calc((100% - 1200px)/2 + 1rem))}
@media(max-width:560px){
  #heroWelcomeTrigger.hero-event-slider .hero-event-arrow{width:2.2em;height:2.2em}
  #heroWelcomeTrigger.hero-event-slider .hero-event-arrow-prev{left:.7rem}
  #heroWelcomeTrigger.hero-event-slider .hero-event-arrow-next{right:.7rem}
}

/* ===== Navigation: the 資源 group =====
   On a wide screen it is a drop-down; on a phone the toggle is hidden and its
   items fall straight into the sliding menu as ordinary rows. */
.top-nav .nav-group{position:relative}
.top-nav .nav-group-menu{list-style:none;margin:0;padding:0}
.top-nav .nav-group-toggle{
  display:flex;
  align-items:center;
  gap:.15rem;
  font:inherit;
  font-size:.82rem;
  color:var(--text);
  background:none;
  border:0;
  border-radius:6px;
  padding:.4rem .5rem;
  white-space:nowrap;
  cursor:pointer;
}
.top-nav .nav-group-toggle:hover{background:#e8f7ee;color:var(--primary)}
.top-nav .nav-group-toggle:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
.top-nav .nav-group-caret{width:1em;height:1em;flex:0 0 1em;transition:transform .18s ease}
.top-nav .nav-group.is-open .nav-group-caret{transform:rotate(180deg)}

@media (min-width:769px){
  .top-nav .nav-group-menu{
    display:none;
    position:absolute;
    top:calc(100% + 6px);
    left:50%;
    transform:translateX(-50%);
    min-width:200px;
    padding:.3rem;
    background:#fff;
    border:1px solid var(--border);
    border-radius:10px;
    box-shadow:0 10px 26px rgba(0,0,0,.13);
    z-index:1001;
  }
  .top-nav .nav-group.is-open .nav-group-menu{display:block}
  .top-nav .nav-group-menu a{padding:.5rem .6rem}
}

@media (max-width:768px){
  .top-nav .nav-group{position:static}
  .top-nav .nav-group-toggle{display:none}
  /* 連結 ends the group but not the menu, so it keeps its divider. */
  .top-nav .nav-group-menu li:last-child a{border-bottom:1px solid var(--border)}
}

/* Poster lightbox */
#heroEventLightbox{
  display:none;
  position:fixed;
  inset:0;
  z-index:12000;
  background:rgba(0,0,0,.94);
  align-items:center;
  justify-content:center;
  padding:24px;
}
#heroEventLightbox.show{display:flex}
#heroEventLightbox .hero-lb-body{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:.85rem;
  max-width:100%;
  max-height:100%;
}
#heroEventLightbox img{
  max-width:100%;
  min-height:0;
  flex:1 1 auto;
  width:auto;
  height:auto;
  object-fit:contain;
  display:block;
}
/* The full-size player. It stands where the poster would, at the same size,
   so opening a video and opening a poster look like the same gesture. */
/* Sized against the window rather than against its parent. The body around it
   is a shrink-to-fit column, so a player asking for "100% of the parent" gets
   the width of the share row underneath it - a postage stamp in the middle of
   a desktop screen.
   The two numbers are this dialog's own: 48px of padding either side, and
   about 58px for the share row and the gap above it. Written that way the
   player gets the same box a poster gets, so opening a video and opening a
   poster fill the screen alike instead of one sitting visibly smaller. */
#heroEventLightbox .hero-lb-player{
  flex:0 0 auto;
  width:min(calc(100vw - 48px),calc((100vh - 106px) * 16 / 9));
  aspect-ratio:16 / 9;
  display:block;
}
#heroEventLightbox .hero-lb-player[hidden]{display:none}
#heroEventLightbox .hero-lb-player video,
#heroEventLightbox .hero-lb-player iframe{
  width:100%;
  height:100%;
  border:0;
  background:#000;
  display:block;
}
#heroEventLightbox img[hidden]{display:none}

/* Share row. A link carries the poster; /poster/<id> holds the preview tags. */
#heroEventLightbox .hero-lb-share{
  display:flex;
  flex-wrap:wrap;
  justify-content:center;
  align-items:center;
  gap:.55rem;
  flex:0 0 auto;
}
#heroEventLightbox .hero-lb-net{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:.35rem;
  width:44px;
  height:44px;
  padding:0;
  border-radius:50%;
  border:1px solid rgba(255,255,255,.45);
  background:rgba(255,255,255,.1);
  color:#fff;
  font:inherit;
  font-size:.9rem;
  text-decoration:none;
  cursor:pointer;
  transition:background .15s ease;
}
#heroEventLightbox .hero-lb-net.is-native{width:auto;padding:0 1rem;border-radius:999px}
#heroEventLightbox .hero-lb-net svg{width:22px;height:22px;flex:none}
#heroEventLightbox .hero-lb-net:hover{background:rgba(255,255,255,.24)}
#heroEventLightbox .hero-lb-net:focus-visible{outline:2px solid #fff;outline-offset:2px}
#heroEventLightbox .hero-lb-net.is-done{background:var(--primary);border-color:var(--primary)}
/* The close button alone is pinned to the corner. It used to be the only
   button in here; the share row's buttons must stay in the flow. */
#heroEventLightbox .hero-lb-close{
  position:fixed;
  top:18px;
  right:20px;
  z-index:2;
  width:44px;
  height:44px;
  border-radius:50%;
  border:1px solid rgba(255,255,255,.75);
  background:rgba(0,0,0,.45);
  color:#fff;
  font-size:1.8rem;
  line-height:1;
  display:flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
}


/* The nav entry carries the same wind-and-rain pair as the section heading,
   so the link and the section it opens read as the same thing. */
.nav-weather-icons{display:inline-flex;align-items:center;gap:.12em;margin-right:.25em;vertical-align:-.14em}
.nav-weather-icons svg{width:.95em;height:.95em;display:block;flex:none}
.nav-weather-icons i{font-style:normal;font-weight:800;font-size:.8em;line-height:1;opacity:.75}

/* ===== 颱風及暴雨安排 ===== */
#weather-arrangements .weather-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}#weather-arrangements .weather-panel{border:1px solid var(--border);border-radius:14px;padding:1rem;background:#fff}#weather-arrangements .weather-panel h3{text-align:center;color:var(--primary);margin:.1rem 0 1.1rem;padding-bottom:.6rem;border-bottom:2px solid var(--border)}#weather-arrangements .weather-title-icons{display:inline-flex;align-items:center;gap:.28rem;margin-right:.35rem;vertical-align:-.16em}#weather-arrangements .weather-title-icons svg{width:1.12em;height:1.12em;display:block}#weather-arrangements .weather-title-slash{font-weight:800;font-size:.95em;line-height:1}#weather-arrangements .weather-group{margin:0 0 1.5rem}#weather-arrangements .weather-signal-row{display:flex;align-items:flex-start;justify-content:center;gap:.7rem;flex-wrap:wrap;margin:0}#weather-arrangements .weather-signal{width:76px;text-align:center;font-size:.8rem;font-weight:700}#weather-arrangements .weather-signal img{display:block;width:58px;height:58px;object-fit:contain;margin:0 auto .35rem;background:#fff}#weather-arrangements .weather-signal.t8{width:68px}#weather-arrangements .weather-signal.t8 img{width:54px;height:54px}#weather-arrangements .weather-result{text-align:center;font-weight:800;margin:0 0 .7rem;font-size:1rem;line-height:1.6}#weather-arrangements .weather-note{font-weight:600}#weather-arrangements .weather-verdict{text-decoration:underline;text-underline-offset:3px;text-decoration-skip-ink:none}#weather-arrangements .weather-mark{width:1.2em;height:1.2em;vertical-align:-.25em;margin-right:.3rem}#weather-arrangements .weather-result.normal{color:#167a3f}#weather-arrangements .weather-result.cancel{color:#b42318}#weather-arrangements .weather-subtitle{text-align:center;font-weight:800;margin:0 0 .7rem;padding-top:1.1rem;border-top:1px solid var(--border)}#weather-arrangements .weather-timeline{width:100%;max-width:540px;margin:.4rem auto 0;border-collapse:collapse;line-height:1.6}#weather-arrangements .weather-timeline td{border:0;background:none;padding:.4rem .35rem;vertical-align:top;text-align:left}#weather-arrangements .weather-timeline td:first-child{padding-left:0}#weather-arrangements .weather-timeline td:last-child{padding-right:0}#weather-arrangements .weather-cell-icon{width:1px;white-space:nowrap}#weather-arrangements .weather-time{width:1px;white-space:nowrap;font-weight:400;font-variant-numeric:tabular-nums}#weather-arrangements .weather-clock{display:block;width:1.35rem;height:1.35rem;margin-top:.18rem;color:var(--primary)}#weather-arrangements .weather-tick{display:block;width:1.15rem;height:1.15rem;margin-top:.22rem;color:#167a3f}#weather-arrangements .weather-time{font-weight:400;text-decoration:none}#weather-arrangements .weather-outcome{font-weight:800;color:#167a3f}#weather-arrangements .weather-scope{text-decoration:underline;text-underline-offset:3px;text-decoration-skip-ink:none}#weather-arrangements .weather-cond{text-decoration:underline;text-underline-offset:3px;text-decoration-skip-ink:none}#weather-arrangements .weather-source{text-align:center;color:var(--muted);font-size:.86rem;margin-top:1rem}@media(max-width:768px){#weather-arrangements .weather-timeline,#weather-arrangements .weather-timeline tbody{display:block}#weather-arrangements .weather-timeline td{display:block;width:auto;padding:0}#weather-arrangements .weather-step{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:.45rem;row-gap:.1rem;padding:.5rem 0;font-size:.95rem}#weather-arrangements .weather-step>td:nth-child(1){grid-area:1/1}#weather-arrangements .weather-step>td:nth-child(2){grid-area:1/2}#weather-arrangements .weather-step>td:nth-child(3){grid-area:2/1}#weather-arrangements .weather-step>td:nth-child(4){grid-area:2/2}#weather-arrangements .weather-clock,#weather-arrangements .weather-tick{margin-top:.15rem}}@media(max-width:380px){#weather-arrangements .weather-panel{padding:.8rem .55rem}#weather-arrangements .weather-step{font-size:.88rem;column-gap:.35rem}#weather-arrangements .weather-clock{width:1.15rem;height:1.15rem}#weather-arrangements .weather-tick{width:1rem;height:1rem}}@media(max-width:768px){#weather-arrangements .weather-source-note{display:block}#weather-arrangements .weather-grid{grid-template-columns:1fr}#weather-arrangements .weather-panel{padding:.9rem .75rem}#weather-arrangements .weather-signal{width:68px}#weather-arrangements .weather-signal img{width:52px;height:52px}#weather-arrangements .weather-signal.t8{width:62px}#weather-arrangements .weather-signal.t8 img{width:48px;height:48px}}

/* ===== YouTube card label ===== */
.sandbox-youtube-label{text-align:center}.sandbox-youtube-channel::before{content:" "}@media(max-width:768px){.sandbox-youtube-channel{display:block}.sandbox-youtube-channel::before{content:""}}

/* Bulletin text uses the shared rose action color. */
.bcard-sat .bcard-get-label,.bcard-sun .bcard-get-label{color:#ee3690!important}


/* 2026-09-17 compact portrait worship cards + four related links */
#links .links-3x2{grid-template-columns:repeat(4,minmax(0,1fr))}
@media (max-width:560px) and (orientation:portrait){
  .bcard{
    grid-template-columns:auto minmax(0,1fr) auto;
    justify-items:stretch;
    align-items:center;
    text-align:center;
    gap:.5rem;
    padding:.72rem .58rem;
  }
  .bcard-when,.bcard-what,.bcard-get{order:initial}
  .bcard-when{justify-self:start;gap:.08rem}
  .bcard-get{justify-self:end;gap:.18rem}
  .bcard-cal{width:3.25rem;height:3.25rem}
  .bcard-pdf{width:43px;height:50px}
  .bcard-when-label,.bcard-get-label{font-size:.76rem}
  .bcard-date{font-size:.9rem}
  .bcard-what>div{font-size:.9rem!important;margin-bottom:.08rem!important}
  .bcard-what h4{font-size:1.05rem;line-height:1.2}
  .bcard-time{font-size:.9rem;line-height:1.25;margin:.08rem 0 0}
  .bcard-where{font-size:.72rem;line-height:1.25;margin:.18rem 0 0;gap:.16rem}
  .bcard-get-hint{font-size:.62rem;line-height:1.2}
  #links .links-3x2{grid-template-columns:repeat(2,minmax(0,1fr));gap:.7rem}
}


/* Preserve the user-selected neutral bulletin label colour. */
.bcard-get-label{color:#000!important}

/* 2026-09-17 unified related-link cards + one-line mobile worship titles */
#links .links-3x2 a{
  background:#e8f7ee!important;
  color:var(--primary)!important;
  border:1px solid var(--border)!important;
  box-shadow:none!important;
}
#links .links-3x2 a:hover,#links .links-3x2 a:focus-visible{
  background:#dcf2e4!important;
  color:var(--primary)!important;
}
#links .links-3x2 a svg,#links .links-3x2 a strong{color:currentColor!important}
@media (max-width:560px) and (orientation:portrait){
  .bcard-sat .bcard-what h4,.bcard-sun .bcard-what h4{
    font-size:.9rem!important;
    line-height:1.15!important;
    white-space:nowrap!important;
    overflow:hidden;
    text-overflow:ellipsis;
    margin:0;
  }
}
.bcard-get-label{color:#000!important}

@media (max-width:560px) and (orientation:portrait){
  #links .sandbox-youtube-label{white-space:nowrap}
  #links .sandbox-youtube-channel{display:inline!important}
  #links .sandbox-youtube-channel::before{content:" "!important}
}


/* ===== Worship active mode visual styling =====
   Applied by the HKT timing engine in main.js. Saturday uses dark blue;
   Sunday uses dark green. Text and supporting details switch to high contrast,
   while the calendar/PDF icons use a lighter tint so the PDF badge remains
   readable instead of turning white-on-white. */
.bcard,.times-table tr[data-day] td{transition:background-color .25s ease,color .25s ease,border-color .25s ease}

.bcard-sat.is-worship-active{
  background:#123a63!important;
  border-color:#245f92!important;
  color:#fff!important;
}
.bcard-sun.is-worship-active{
  background:#14532d!important;
  border-color:#257548!important;
  color:#fff!important;
}
.bcard.is-worship-active .bcard-when-label,
.bcard.is-worship-active .bcard-date,
.bcard.is-worship-active .bcard-what h4,
.bcard.is-worship-active .bcard-what>div,
.bcard.is-worship-active .bcard-time,
.bcard.is-worship-active .bcard-where,
.bcard.is-worship-active .bcard-get-label,
.bcard.is-worship-active .bcard-get-hint{
  color:#fff!important;
}
.bcard-sat.is-worship-active .bcard-cal,
.bcard-sat.is-worship-active .bcard-pdf{color:#9fd3ff!important}
.bcard-sun.is-worship-active .bcard-cal,
.bcard-sun.is-worship-active .bcard-pdf{color:#a7f3c1!important}
.bcard.is-worship-active .bcard-qr:hover,
.bcard.is-worship-active .bcard-qr:focus-visible{background:rgba(255,255,255,.12)!important}

.times-table tr[data-day="sat"].is-worship-active td{
  background:#123a63!important;
  color:#fff!important;
  border-bottom-color:#315b82!important;
}
.times-table tr[data-day="sun"].is-worship-active td{
  background:#14532d!important;
  color:#fff!important;
  border-bottom-color:#2f7049!important;
}
.times-table tr[data-day].is-worship-active td:first-child{color:#fff!important;font-weight:700}


/* 2026-09-18 programme label: pink normally, black when active */
.bcard .bcard-get-label{color:#ee3690!important}
.bcard.is-worship-active .bcard-get-label{color:#000!important}
.bcard.is-worship-active .bcard-pdf text{fill:#000!important}


/* Final programme-label state: normal black, active white. */
.bcard-get-label{color:#000!important}
.bcard.is-worship-active .bcard-get-label{color:#fff!important}

/* Final programme label state: black normally, white on active worship cards. */
.bcard-sat .bcard-get-label,
.bcard-sun .bcard-get-label{color:#000!important}
.bcard-sat.is-worship-active .bcard-get-label,
.bcard-sun.is-worship-active .bcard-get-label{color:#fff!important}


/* ===== 地點與地圖：四個地點共用一張地圖 ===== */
.church-locations-map-wrap{
  margin:0 0 1.35rem;
  border:1px solid #c9ddd1;
  border-radius:18px;
  overflow:hidden;
  background:#fff;
  box-shadow:0 10px 28px rgba(23,59,42,.08);
  /* A flex column so the map and the note beneath it share whatever
     height the row (see .church-locations-row) gives this wrap, instead
     of the map claiming a flat height and the note adding more on top of
     it - which would either overflow (clipped by overflow:hidden above)
     or force the row taller than the fixed height it's meant to hold. */
  display:flex;
  flex-direction:column;
  min-height:0;
}
.church-locations-map{
  width:100%;
  flex:1;
  min-height:0;
  background:#eef3f0;
  z-index:0;
}
.church-locations-map-note{
  padding:.62rem .9rem;
  text-align:center;
  font-size:.82rem;
  color:var(--muted);
  background:linear-gradient(180deg,#fff,#f7fbf8);
  border-top:1px solid var(--border);
}
.church-locations-map .maplibregl-ctrl-bottom-left{left:8px;bottom:8px}
.church-locations-map .maplibregl-ctrl-bottom-left .maplibregl-ctrl{margin:0}
.church-locations-map .maplibregl-ctrl-group{
  overflow:hidden;
  border-radius:12px;
  box-shadow:0 5px 16px rgba(20,50,35,.2);
}
.church-locations-map .maplibregl-ctrl-group button{
  width:36px;
  height:36px;
}
/* MapLibre's own compact/open state for this control isn't reliable on
   first paint - it can render with the full credit text already showing,
   wide enough to sit over whichever markers happen to be near the bottom
   edge, before any tap. Clipping the control's own box to just the small
   "i" button's height at rest - and only letting it grow back out while
   it's actually being looked at - keeps it out of the markers' way
   regardless of which internal state caused it to open, and still lets a
   tap (which focuses the button) reveal the credit text same as before. */
.church-locations-map .maplibregl-ctrl-attrib{
  max-height:26px;
  overflow:hidden;
  transition:max-height .15s ease;
}
.church-locations-map .maplibregl-ctrl-attrib:hover,
.church-locations-map .maplibregl-ctrl-attrib:focus-within{
  max-height:160px;
}
/* NEVER set `position` on this element. MapLibre positions every marker
   through its own `.maplibregl-marker { position:absolute; top:0; left:0 }`
   rule, which lives in maplibre-gl.css - a stylesheet, not an inline
   style, and loaded BEFORE this file in index.html. Both selectors are a
   single class, so the specificity ties and source order decides: any
   `position` declared here silently beats MapLibre's and drops markers
   back into normal document flow. MapLibre then translates each one from
   wherever flow happened to put it rather than from the map's origin, so
   the first marker looks about right, the rest drift further and further
   off, some land outside the clipped box entirely, and because a flow
   offset is a fixed pixel amount that does not rescale with zoom while
   the translate does, the error changes at every zoom level.
   The ::before glow below does not need `position:relative` here -
   `absolute` is itself a positioned value, so it already anchors to this
   element correctly. */
.church-map-logo-marker-wrap{
  width:44px;
  height:44px;
  background:transparent!important;
  border:0!important;
  overflow:visible;
  cursor:pointer;
}
/* A soft ring behind the icon, only switched on for whichever place the
   panel is currently showing (is-current-place, set in main.js) - so a
   visitor can tell which marker matches the panel at any moment, not just
   in the instant right after picking it. Painted before the avatar span
   in the markup, so it sits behind it without needing its own z-index. */
.church-map-logo-marker-wrap::before{
  content:'';
  position:absolute;
  inset:-6px;
  border-radius:50%;
  background:var(--location-accent,#4FA8EA);
  opacity:0;
  pointer-events:none;
}
.church-map-logo-marker-wrap.is-current-place::before{
  animation:church-map-marker-glow 1.8s ease-in-out infinite;
}
@keyframes church-map-marker-glow{
  0%{transform:scale(1);opacity:.42}
  70%,100%{transform:scale(1.55);opacity:0}
}
.church-map-error{
  height:100%;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:1rem;
  text-align:center;
  color:var(--muted);
  font-weight:600;
}
.church-map-logo-marker{
  --location-accent:#4FA8EA;
  position:relative;
  width:44px;
  height:44px;
  display:block;
  border-radius:50%;
  border:3px solid var(--location-accent);
  background:#fff;
  box-shadow:0 5px 14px rgba(0,0,0,.26);
  transition:transform .18s ease,box-shadow .18s ease;
}
.church-map-logo-marker-wrap:hover .church-map-logo-marker{
  transform:translateY(-3px) scale(1.04);
  box-shadow:0 8px 20px rgba(0,0,0,.3);
}
/* A soft grow-and-dim on the whole icon - avatar and its number together,
   since the badge is a child of this element and moves with it - when
   this marker's own place is picked, by a click or by the auto-showcase
   passing through it. No separate ring drawn across the favicon; the
   icon itself is the thing that visibly changes. */
@keyframes church-map-marker-pulse{
  0%{transform:scale(1);opacity:1}
  40%{transform:scale(1.14);opacity:.78}
  100%{transform:scale(1);opacity:1}
}
.church-map-logo-marker.is-focus-pulse{animation:church-map-marker-pulse .7s ease-out}
.church-map-logo-marker img{
  width:100%;
  height:100%;
  display:block;
  object-fit:cover;
  border-radius:50%;
  border:2px solid #fff;
}
/* A separate badge clear of the avatar - at 135deg clockwise from the top
   (the 4:30 clock position), close to but not overlapping the favicon
   circle. translate/rotate/translate/counter-rotate is the standard way
   to place something at an angle around a centre while keeping its own
   content upright: centre on the avatar, rotate the axis 135deg, walk out
   along it past the avatar's own radius, then rotate the badge itself
   back so the number doesn't tilt. */
.church-map-marker-number{
  position:absolute;
  top:50%;
  left:50%;
  transform:translate(-50%,-50%) rotate(135deg) translate(0,-33px) rotate(-135deg);
  width:24px;
  height:24px;
  box-sizing:border-box;
  border-radius:50%;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:0;
  margin:0;
  text-align:center;
  background:var(--location-accent);
  color:#fff;
  border:2.5px solid #fff;
  box-shadow:0 2px 7px rgba(20,40,30,.32);
  font-family:system-ui,-apple-system,"Noto Sans TC",sans-serif;
  font-size:13px;
  font-weight:900;
  line-height:1;
  z-index:1;
}
/* Map on the left; on the right, tabs for the four places drive one
   fixed-height panel that carries both the address and that place's own
   photo - nothing here changes height when the place changes, so
   switching places never moves the rest of the page. */
.church-locations-row{
  display:flex;
  align-items:stretch;
  gap:1rem;
  /* Matches .church-locations-map's own height below. align-items:stretch
     alone isn't enough: percentage heights inside the tabs column (the
     photo's flex-basis) need a genuinely definite height to resolve
     against, or they fall back to sizing off the photo's own aspect ratio
     - which differs per place, and would otherwise grow the whole row
     (map included) to match whichever place's photo happens to be
     tallest. */
  height:430px;
}
.church-locations-row .church-locations-map-wrap{
  flex:1.35;
  min-width:0;
  margin:0;
}
.location-tabs-panel{
  flex:1;
  min-width:0;
  /* Without this, a flex item's cross-size floors at its content's natural
     size instead of the row's stretched height - so whichever place has
     the tallest photo would quietly grow the whole column past the map,
     exactly the per-tab height wobble this panel exists to prevent. */
  min-height:0;
  display:flex;
  flex-direction:column;
  gap:.6rem;
}
@media(max-width:980px){
  .church-locations-row{flex-direction:column;height:auto}
  .church-locations-row .church-locations-map-wrap{flex:none}
}

/* Always two columns, two rows - 1 2 / 3 4 - rather than however many
   happen to fit a row at a given width, so the four places read as one
   steady grid instead of reflowing unevenly. */
.location-tabs{
  display:grid;
  grid-template-columns:repeat(2,1fr);
  gap:.4rem;
}
.location-tab{
  --tab-accent:#1BA94E;
  display:flex;
  align-items:center;
  /* Centred rather than hard left. Left-aligned, the number and label sat
     11px from the left edge with 77-137px of dead space trailing them on
     a PC-width tab, which read as everything being crammed against the
     left. Centring splits whatever slack a tab actually has, so it
     rebalances the roomy tabs without touching the tight ones: the two
     long names on a phone only have ~10px spare and barely shift, while
     the short names finally sit in the middle of their pill. */
  justify-content:center;
  gap:.35rem;
  padding:.42rem .6rem;
  border:1.5px solid color-mix(in srgb,var(--tab-accent) 35%,#fff);
  border-radius:999px;
  background:#fff;
  color:var(--tab-accent);
  font-size:.76rem;
  font-weight:700;
  line-height:1.25;
  text-align:left;
  cursor:pointer;
  transition:background .16s ease,color .16s ease,transform .16s ease,box-shadow .16s ease;
  /* One name (鄱陽閣辦公室及聚會點) is long enough to wrap to two lines
     while its neighbours stay on one - without a shared floor, that tab
     alone grows taller and the 2x2 grid stops reading as even. */
  min-height:3rem;
}
.location-tab:hover:not(.is-active){background:color-mix(in srgb,var(--tab-accent) 8%,#fff)}
.location-tab.is-active{
  background:var(--tab-accent);
  color:#fff;
  transform:translateY(-1px);
  box-shadow:0 4px 10px rgba(20,40,30,.18);
}
.location-tab-num{
  width:18px;
  height:18px;
  flex:none;
  box-sizing:border-box;
  display:flex;
  align-items:center;
  justify-content:center;
  border-radius:50%;
  background:color-mix(in srgb,var(--tab-accent) 16%,#fff);
  color:inherit;
  font-size:.68rem;
  font-weight:900;
}
.location-tab.is-active .location-tab-num{background:rgba(255,255,255,.3)}

/* The panel itself: a fixed share of the row's height (stretched to match
   the map by the flex row above), so a photo appearing/disappearing or an
   address running one line versus three never changes how tall this is. */
.location-tab-panel{
  --card-accent:#1BA94E;
  flex:1;
  min-height:0;
  display:flex;
  flex-direction:column;
  border-radius:18px;
  overflow:hidden;
  background:#fff;
  box-shadow:0 8px 22px rgba(28,55,40,.11);
  border:1px solid color-mix(in srgb,var(--card-accent) 30%,#dfe7e3);
}
/* This is the static container buildLocationTabs() fills each time a tab
   is picked. The photo it builds (buildPhotoCarousel) is deliberately a
   different class, 'location-tab-photo-media' - not this one - so it
   never picks up this box's own flex sizing meant for its outer edge. */
.location-tab-photo{
  position:relative;
  /* Bigger than before - the title no longer repeats in the body when
     there's a photo, which freed up room this can use instead, so a 4:3
     picture (letterboxed under object-fit:contain on PC) reads larger
     rather than sitting small in the middle of extra empty space. */
  flex:0 0 58%;
  min-height:0;
  overflow:hidden;
  background:#eef1ef;
}
.location-tab-photo-media{
  position:relative;
  width:100%;
  height:100%;
}
/* .gallery-trigger already resets this button's border/padding/background
   and sets cursor:zoom-in - this just gives it the full box to fill, the
   same box the old bare <img> used to occupy. */
.location-tab-photo-media-trigger{
  width:100%;
  height:100%;
}
.location-tab-photo-media-trigger img{
  width:100%;
  height:100%;
  object-fit:cover;
}
/* One shared fade-IN, used both when a place's photos change (prev/next
   or a dot) and when the whole card switches to a different place. The
   content is swapped first and then fades in - there is deliberately no
   fade-out, so nothing waits on the outgoing picture before the new one
   appears. */
@keyframes location-tab-fade-in{
  from{opacity:0}
  to{opacity:1}
}
.location-tab-photo-media-trigger img.is-fading-in,
.location-tab-photo.is-fading-in,
.location-tab-body.is-fading-in{
  animation:location-tab-fade-in .45s ease-out;
}
/* Held invisible between swapping in a new photo and that photo actually
   being ready to paint. Without this the card would sit there as a
   full-opacity empty box for the length of the download and the picture
   would then appear all at once - the fade has to run on the real
   picture, not on the gap before it. */
.location-tab-photo-media-trigger img.is-fade-hold,
.location-tab-photo.is-fade-hold,
.location-tab-body.is-fade-hold{opacity:0}
/* On a wide screen there's room to show the whole picture rather than
   cropping it to fill the box - letterboxed on whichever side its own
   shape doesn't match. Mobile is unchanged; its narrower box makes a
   crop the better trade-off, and this site's photos are shot to read
   well cropped there already. */
@media(min-width:981px){
  .location-tab-photo-media-trigger img{object-fit:contain}
}
.location-tab-photo-media-nav{
  position:absolute;
  top:50%;
  transform:translateY(-50%);
  width:28px;
  height:28px;
  display:grid;
  place-items:center;
  padding:0;
  border:0;
  border-radius:50%;
  background:rgba(17,24,20,.5);
  color:#fff;
  cursor:pointer;
  z-index:1;
}
.location-tab-photo-media-nav:hover{background:rgba(17,24,20,.72)}
.location-tab-photo-media-nav svg{width:16px;height:16px}
.location-tab-photo-media-prev{left:.5rem}
.location-tab-photo-media-next{right:.5rem}
.location-tab-photo-media-dots{
  position:absolute;
  left:50%;
  bottom:.45rem;
  transform:translateX(-50%);
  display:flex;
  gap:5px;
  z-index:1;
}
.location-tab-photo-media-dot{
  width:6px;
  height:6px;
  border-radius:50%;
  background:rgba(255,255,255,.55);
  box-shadow:0 0 0 1px rgba(0,0,0,.18);
}
.location-tab-photo-media-dot.is-active{background:#fff}
/* Address pinned to the top of the card's text area, button pinned to the
   bottom - not the whole group centred as one block. Centring made both
   of them depend on how tall the address happened to be, so a one-line
   address sat 11px lower than the two-line one AND pushed the button 10px
   higher: every time the showcase rotated onto 東山閣/天山閣 (the only
   two-line address) the button visibly jumped. Pinning each end fixes the
   address just under the photo and holds the button still, so nothing
   shifts as the places cycle. */
.location-tab-body{
  flex:1;
  min-height:0;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:space-between;
  text-align:center;
  gap:.5rem;
  padding:1rem 1.15rem;
}
.location-tab-title{
  font-size:1.02rem;
  color:var(--card-accent);
  line-height:1.3;
}
.location-tab-address{
  margin:0;
  font-size:.85rem;
  line-height:1.55;
  color:#53655b;
}
.location-tab-link{
  display:inline-flex;
  align-items:center;
  gap:.3rem;
  padding:.5rem 1rem;
  border-radius:999px;
  background:color-mix(in srgb,var(--card-accent) 12%,#fff);
  color:var(--card-accent);
  border:1px solid color-mix(in srgb,var(--card-accent) 28%,#fff);
  text-decoration:none;
  font-size:.82rem;
  font-weight:800;
  box-shadow:0 3px 9px rgba(27,50,39,.08);
  transition:background .18s ease,transform .18s ease,box-shadow .18s ease;
}
.location-tab-link:hover,
.location-tab-link:focus-visible{
  background:color-mix(in srgb,var(--card-accent) 18%,#fff);
  transform:translateY(-1px);
  box-shadow:0 5px 12px rgba(27,50,39,.11);
}
@media(max-width:980px){
  .church-locations-map-wrap{height:340px}
  /* Overriding flex-basis, not just height - the base rule's flex:1 is
     shorthand for flex-basis:0%, which wins over a plain height on a
     flex item and would otherwise leave .location-tab-photo's
     flex-basis:44% with nothing definite to resolve against below
     980px, where the row no longer stretches this column for it (it's
     stacked, not a row, at this width). That collapsed the photo to
     height:0 - present in the DOM, invisible on screen. */
  .location-tab-panel{flex:0 0 260px}
}
@media(max-width:560px){
  .church-locations-map-wrap{height:300px}
  .location-tab-panel{flex:0 0 250px}
  .location-tab{font-size:.68rem;padding:.36rem .5rem;min-height:2.7rem}
  .location-tab-num{width:16px;height:16px;font-size:.62rem}
  .location-tab-title{font-size:.92rem}
  .location-tab-address{font-size:.78rem}
  /* Tighter top/bottom than the .8rem it used to be. On a phone the card
     is only 250px tall, which left ~78px for the text once padding was
     taken out - just under the ~86px the two-line 東山閣/天山閣 address
     plus the button actually need. That overflow pushed its button ~7px
     lower than the other places', so the button still twitched as the
     showcase cycled. Trimming the vertical padding gives that case the
     room it needs, and every place then lands identically. */
  .location-tab-body{padding:.5rem .9rem}
}

