/* Palette sampled from the home key visual (static/images/hero_720_2.png):
   the blues are the hero gradient read top to bottom, the night blues are that
   same hue darkened for dark sections, and the accents come from the island and
   the infinity loop. Every public page reads these, so sub pages stay in step
   with the hero. Contrast on white: blue 6.4:1, teal 4.2:1 (body text safe);
   on night blue: white 14.3:1, glow 11.7:1, mint 10.4:1. */
:root {
  /* Hero gradient, top to bottom */
  --gctc-blue: #0058cc;
  --gctc-blue-600: #1a6fd4;
  --gctc-sky: #69bde9;
  --gctc-air: #93d7f3;
  --gctc-ice: #e8f7ff;
  /* Same hue, darkened: dark sections */
  --gctc-night: #003a8c;
  --gctc-night-700: #00285f;
  --gctc-night-900: #001a45;
  /* Island and infinity loop accents */
  /* Darker than the sampled #2f8a6a: that value was chosen for 4.2:1 on plain
     white, but text sitting inside a .gctc-wave-host section also has to hold
     up against the glow wave's cyan tint (see .gctc-wave-host below), where it
     dropped to 2.8:1. This shade holds 5.8:1 at the wave's worst point. */
  --gctc-teal: #1c5340;
  --gctc-mint: #99eeb0;
  --gctc-glow: #98f8fe;
  --gctc-ink: #10213b;
  /* Retained for markup still naming the old poster tokens */
  --gctc-deep: var(--gctc-night-900);
  --gctc-navy: var(--gctc-night);
  --gctc-green: var(--gctc-teal);
  --gctc-leaf: var(--gctc-mint);
}

html { scroll-behavior: smooth; }
body { color: var(--gctc-ink); background: #f7fbfc; }
::selection { color: white; background: var(--gctc-blue); }

/* Keep Korean words together in session headings, metadata and speaker profiles.
   Only break a word when it cannot fit on a line by itself. */
.session-detail { word-break: keep-all; overflow-wrap: break-word; }

/* Session details: roomy timeline cards on phones/tablets, table on desktop. */
.session-timeline { display: none; }
@media (max-width: 1023px) {
  .session-timetable-desktop { display: none; }
  .session-timeline { display: block; list-style: none; margin: 0; padding: 0 0 0 22px; }
  .session-timeline-item { position: relative; padding-bottom: 18px; min-width: 0; }
  .session-timeline-item::before {
    content: ""; position: absolute; left: -18px; top: 24px; bottom: -24px;
    width: 2px; background: #c8dfee;
  }
  .session-timeline-item:last-child { padding-bottom: 0; }
  .session-timeline-item:last-child::before { display: none; }
  .session-timeline-item::after {
    content: ""; position: absolute; left: -22px; top: 23px;
    width: 10px; height: 10px; border-radius: 50%; background: var(--gctc-blue);
    box-shadow: 0 0 0 4px #e8f3fc;
  }
  .session-timeline-card {
    overflow: hidden; border: 1px solid #d6e5f0; border-radius: 18px;
    background: #fff; box-shadow: 0 3px 12px rgba(0,56,118,.04);
    word-break: keep-all; overflow-wrap: anywhere;
  }
  .session-timeline-time {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
    padding: 13px 16px; background: #edf5fd; border-bottom: 1px solid #e0edf7;
    color: #003876; font-size: 15px; line-height: 1.5; font-variant-numeric: tabular-nums;
  }
  .session-timeline-time strong { font-weight: 800; }
  .session-timeline-duration {
    padding: 2px 8px; border: 1px solid #c3e2f0; border-radius: 6px;
    background: #fff; color: #08708b; font-size: 12px; font-weight: 700;
  }
  .session-timeline-content { padding: 16px; }
  .session-timeline-category {
    display: inline-block; max-width: 100%; padding: 4px 9px; margin-bottom: 10px;
    border-radius: 6px; background: #edf4ff; color: #0053b8;
    font-size: 12px; font-weight: 700; line-height: 1.5; white-space: pre-line;
  }
  .session-timeline-title { margin: 0; color: #10213b; font-size: 16px; line-height: 1.65; font-weight: 750; white-space: pre-line; }
  .session-timeline-presenters { margin-top: 14px; padding-top: 12px; border-top: 1px solid #edf1f5; }
  .session-timeline-presenters p + p { margin-top: 10px; }
  .session-timeline-presenters strong, .session-timeline-presenters span { display: block; white-space: pre-line; }
  .session-timeline-presenters strong { color: #253e5d; font-size: 14px; line-height: 1.6; }
  .session-timeline-presenters span { color: #5d6b7f; font-size: 13px; line-height: 1.6; }
  .session-timeline-note { display: flex; align-items: baseline; gap: 8px; margin-top: 14px; padding: 10px 12px; border-radius: 9px; background: #f3f6fa; color: #526279; font-size: 12px; line-height: 1.65; }
  .session-timeline-note span { min-width: 0; white-space: pre-line; }
  .session-timeline-note i { flex-shrink: 0; color: #6685a8; }
}
@media print {
  .session-timeline { display: none; }
  .session-timetable-desktop { display: block; }
}

/* Legacy sub-hero artwork layer, kept for CMS snapshots that still carry the
   old background-image div. The current sub hero uses an <img> with
   object-cover instead (see .gctc-subhero below). */
main [style*="hero_key_visual.png"] {
  background-size: contain !important;
  background-position: right center !important;
  background-repeat: no-repeat !important;
  mix-blend-mode: screen;
  opacity: .58 !important;
}

/* ---------------------------------------------------------------------------
   Sub-page hero: the key visual carried over from the home page. The artwork
   is the background (object-cover, so it is cropped rather than stretched at
   any viewport width) and the veil darkens the left half, where the copy sits.
   White text needs the veil: the artwork's own gradient passes #287fd7 partway
   down, which is only 4.1:1 against white on its own.
--------------------------------------------------------------------------- */
.gctc-subhero { background-color: var(--gctc-blue); }
.gctc-subhero-veil {
  background: linear-gradient(100deg,
    #00285f 0%, rgba(0,44,108,.95) 30%, rgba(0,58,140,.82) 50%,
    rgba(0,88,204,.45) 70%, rgba(0,88,204,.12) 84%, rgba(0,88,204,0) 94%);
}
/* The waves belong behind the copy but in front of the artwork. */
.gctc-subhero .gctc-waves { opacity: .3; }

/* Key-visual palette shared by every public page. */
.bg-brand-primary { background-color: var(--gctc-blue) !important; }
.text-brand-primary { color: var(--gctc-blue) !important; }
.border-brand-primary { border-color: var(--gctc-blue) !important; }
.hover\:text-brand-primary:hover { color: var(--gctc-blue) !important; }
.hover\:border-brand-primary:hover { border-color: var(--gctc-blue) !important; }
.focus\:ring-brand-primary:focus { --tw-ring-color: var(--gctc-air) !important; }

main .bg-slate-50 { background-color: #f2f9ff; }
main .bg-blue-50 { background-color: #eaf5ff; }
main .bg-cyan-50 { background-color: var(--gctc-ice); }
main .bg-emerald-50 { background-color: #ecf8f2; }
main .text-blue-600,
main .text-blue-700,
main .text-cyan-600,
main .text-cyan-700 { color: var(--gctc-blue); }
main .text-emerald-600,
main .text-emerald-700 { color: var(--gctc-teal); }
main .bg-blue-600,
main .bg-cyan-600 { background-color: var(--gctc-blue); }
main .bg-emerald-600 { background-color: var(--gctc-teal); }

/* Tables repeat the poster's blue title field and pale sky body. */
main table thead tr { background: var(--gctc-navy) !important; color: white !important; }
main table thead th { color: white !important; background: transparent !important; border-color: rgba(255,255,255,.2) !important; }
main table tbody tr { border-color: #d8e9ef; }
main table tbody tr:hover { background: #eef8fb !important; }

/* Shared editorial surface used by overview, program, meet-up and notice pages.
   Every sub page has exactly one of these, which makes it the one hook needed
   to carry the home page's drifting colour onto all of them - no per-page
   markup and so no CMS migration. Two pseudo-elements each hold a couple of
   soft radial blooms and drift on their own period; gradients are smooth by
   nature, so this needs no blur filter. Kept fainter than the home waves
   because these sections are dense with body copy. */
/* The blooms drift by animating background-position, not transform, and the
   layer sits exactly on the section box. That keeps them inside the section
   without `overflow: hidden`, which would have clipped the content itself:
   some of this stored CMS markup overflows horizontally below ~450px, and
   hiding the overflow turned a scrollable page into a cut-off one. */
main section.bg-slate-50 { position: relative; }
main section.bg-slate-50::before,
main section.bg-slate-50::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-repeat: no-repeat;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
main section.bg-slate-50::before {
  background-image:
    radial-gradient(closest-side at 50% 50%, rgba(152,248,254,.5), rgba(152,248,254,0) 100%),
    radial-gradient(closest-side at 50% 50%, rgba(153,238,176,.42), rgba(153,238,176,0) 100%);
  background-size: 62% 58%, 56% 54%;
  animation-name: gctc-surface-drift-a;
  animation-duration: 13s;
}
/* The blue bloom is held well back: these pages use brand-primary for links and
   labels, and blue text over a blue tint loses most of its separation. At .26
   it measured 4.0:1; at .14 the darkest point of the drift is #d0e2f7, where
   brand-primary holds 4.9:1 and body copy 5.7:1. */
main section.bg-slate-50::after {
  background-image:
    radial-gradient(closest-side at 50% 50%, rgba(0,88,204,.14), rgba(0,88,204,0) 100%);
  background-size: 72% 60%;
  animation-name: gctc-surface-drift-b;
  animation-duration: 19s;
}
main section.bg-slate-50 > * { position: relative; z-index: 1; }

/* background-position takes one pair per gradient layer, so the cyan and mint
   blooms travel independently inside the same pseudo-element. */
@keyframes gctc-surface-drift-a {
  0%   { background-position: 6% 8%, 92% 26%; }
  50%  { background-position: 34% 30%, 62% 6%; }
  100% { background-position: 18% 4%, 78% 40%; }
}
@keyframes gctc-surface-drift-b {
  0%   { background-position: 62% 92%; }
  50%  { background-position: 26% 70%; }
  100% { background-position: 74% 84%; }
}
@media (prefers-reduced-motion: reduce) {
  main section.bg-slate-50::before,
  main section.bg-slate-50::after { animation: none; }
}

/* ---------------------------------------------------------------------------
   Glow waves: the key visual's infinity-loop light carried down the page as
   slow drifting blurred bands, so the sections below the hero keep its motion.
   The layer sits behind everything (z-index 0 against a positioned parent) and
   never takes pointer events. Opacity stays low enough that body text keeps its
   contrast; the dark variant may sit higher because its text is white.
--------------------------------------------------------------------------- */
.gctc-waves {
  position: absolute;
  inset: -20% -10%;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
  /* Filters are expensive, so promote the layer once instead of per band. */
  will-change: transform;
}
/* Three things decide whether the drift actually reads as movement, and the
   first two versions of this got all of them wrong:

   - Blur. At 62px on a ~1200px band the whole shape was one smooth haze with
     no trackable edge, so there was nothing for the eye to follow. 34px still
     reads as a soft glow but keeps a discernible edge.
   - Falloff. Fading all the way out at 92% of the radius spread the colour so
     thin that the band had no core. Stopping at 72% leaves a defined centre
     that visibly arrives and leaves.
   - Speed. A 29-43s period over even a large travel works out to a few px per
     second - below the threshold where motion registers without staring at
     it. 11-17s covers the same distance fast enough to notice at a glance,
     and is still unhurried.

   Each band runs two animations at once - a drift across the section and a
   morph of its own outline - on periods that do not divide into each other,
   so the layer never repeats the same arrangement and reads as water moving
   rather than a shape sliding past. */
.gctc-waves__band {
  position: absolute;
  filter: blur(34px) saturate(1.3);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
/* Sizes are tuned against real desktop widths (1440-2560px), not a cropped
   component preview: the original 42-52% bands sat anchored near the edges and
   never reached the middle of a wide section, leaving the readable centre
   plain white. These still cross the centre, but are smaller than the previous
   pass so the shape stays legible instead of becoming a full-width wash. */
.gctc-waves__band--glow {
  left: -10%;
  top: -4%;
  width: 58%;
  height: 66%;
  border-radius: 52% 48% 57% 43% / 46% 54% 46% 54%;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(152,248,254,.95) 0%, rgba(140,240,254,.86) 22%, rgba(124,224,250,.6) 42%,
    rgba(110,206,244,.28) 58%, rgba(100,190,238,.08) 68%, rgba(96,184,236,0) 76%);
  animation-name: gctc-wave-drift-a, gctc-wave-morph-a;
  animation-duration: 11s, 7s;
}
.gctc-waves__band--mint {
  right: -8%;
  top: 14%;
  width: 54%;
  height: 62%;
  border-radius: 45% 55% 44% 56% / 55% 45% 58% 42%;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(153,238,176,.9) 0%, rgba(138,230,172,.8) 22%, rgba(120,218,172,.54) 42%,
    rgba(104,204,170,.25) 58%, rgba(92,192,168,.07) 68%, rgba(88,188,166,0) 76%);
  animation-name: gctc-wave-drift-b, gctc-wave-morph-b;
  animation-duration: 14s, 9s;
}
.gctc-waves__band--blue {
  left: 22%;
  bottom: -24%;
  width: 66%;
  height: 66%;
  border-radius: 56% 44% 48% 52% / 43% 57% 43% 57%;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(0,120,224,.62) 0%, rgba(0,108,216,.52) 22%, rgba(0,96,210,.32) 42%,
    rgba(0,88,204,.15) 58%, rgba(0,82,198,.04) 68%, rgba(0,80,196,0) 76%);
  animation-name: gctc-wave-drift-c, gctc-wave-morph-c;
  animation-duration: 17s, 8s;
}

/* Wind-like travel: each band takes a different path across the section, and
   stretches along one axis while it squashes along the other. That swell is
   what survives a heavy blur - a rolling outline alone would be smoothed away -
   so it carries most of the rippling.

   Percentages here are relative to the band's OWN size (CSS transform-percent
   rules), not the section, so the swing has to be large to read as movement on
   a big blurred shape: at this size a ±7% swing over 30s worked out to only a
   few px per second, invisible at a glance. These ranges are roughly 3x that -
   still a slow drift, but one a viewer actually sees shift up/down and
   side to side without staring at it. */
@keyframes gctc-wave-drift-a {
  0%   { transform: translate3d(-20%, 4%, 0) scale(1.02, .92); }
  25%  { transform: translate3d(-4%, -15%, 0) scale(.9, 1.12); }
  50%  { transform: translate3d(17%, -21%, 0) scale(1.18, .96); }
  75%  { transform: translate3d(30%, -4%, 0) scale(.95, 1.16); }
  100% { transform: translate3d(36%, 13%, 0) scale(1.08, .94); }
}
@keyframes gctc-wave-drift-b {
  0%   { transform: translate3d(19%, -10%, 0) scale(1.1, .95); }
  25%  { transform: translate3d(3%, 7%, 0) scale(.92, 1.14); }
  50%  { transform: translate3d(-25%, 17%, 0) scale(1.14, .9); }
  75%  { transform: translate3d(-19%, 0%, 0) scale(.94, 1.1); }
  100% { transform: translate3d(-10%, -16%, 0) scale(1.16, .98); }
}
@keyframes gctc-wave-drift-c {
  0%   { transform: translate3d(-13%, 13%, 0) scale(1.12, .94); }
  25%  { transform: translate3d(6%, 3%, 0) scale(.93, 1.1); }
  50%  { transform: translate3d(23%, -7%, 0) scale(1.06, .9); }
  75%  { transform: translate3d(-6%, 0%, 0) scale(.96, 1.14); }
  100% { transform: translate3d(-30%, 7%, 0) scale(1.14, .96); }
}

/* The swell: the outline itself rolls, so the light never holds one shape. */
@keyframes gctc-wave-morph-a {
  0%   { border-radius: 52% 48% 57% 43% / 46% 54% 46% 54%; }
  50%  { border-radius: 63% 37% 41% 59% / 58% 42% 61% 39%; }
  100% { border-radius: 43% 57% 62% 38% / 39% 63% 37% 61%; }
}
@keyframes gctc-wave-morph-b {
  0%   { border-radius: 45% 55% 44% 56% / 55% 45% 58% 42%; }
  50%  { border-radius: 59% 41% 63% 37% / 38% 62% 40% 60%; }
  100% { border-radius: 38% 62% 47% 53% / 61% 39% 56% 44%; }
}
@keyframes gctc-wave-morph-c {
  0%   { border-radius: 56% 44% 48% 52% / 43% 57% 43% 57%; }
  50%  { border-radius: 40% 60% 57% 43% / 60% 40% 59% 41%; }
  100% { border-radius: 61% 39% 38% 62% / 45% 55% 41% 59%; }
}

/* Light sections. Tightening the bands' falloff freed up contrast headroom, so
   this sits higher than the .38 the previous, more diffuse version needed -
   the colour reads without washing the text out. Measured at the worst point
   of the drift (the bands' brightest overlap): background peaks around
   #b8e6e9, where brand-primary holds 4.7:1, the darkened greys 5.5:1, the teal
   accent 6.5:1 and title text 13:1. .66 was the ceiling (brand-primary 4.52:1);
   this leaves margin. */
.gctc-wave-host { position: relative; overflow: hidden; }
.gctc-wave-host > *:not(.gctc-waves) { position: relative; z-index: 1; }
.gctc-wave-host .gctc-waves { opacity: .55; }
/* The blue band is the one that darkens a pale surface, so it is held back
   here; the light sections carry dark body copy that must keep its contrast. */
.gctc-wave-host .gctc-waves__band--blue { opacity: .38; }

/* Any tint over white costs contrast, and the muted greys were already close to
   the limit on a plain background (slate-500 is 4.76:1 on white). Over a wave
   they fall under it, so inside these sections the two palest greys are
   darkened one step: measured against the faintest point of the drift they then
   hold 6.3:1. Only colour changes; copy, size and weight are untouched. */
.gctc-wave-host .text-slate-400,
.gctc-wave-host .text-slate-500 { color: #475569; }

/* Dark sections carry white text, so the same waves can read more strongly.
   No section uses this today - the home page is light throughout - but the
   pairing is kept because the tokens and the measured strength belong together:
   at .22 the brightest point of the drift is #145294, which holds white at
   7.9:1 and the palest label colour at 5.0:1. */
.gctc-night {
  position: relative;
  overflow: hidden;
  background: linear-gradient(170deg, var(--gctc-night) 0%, var(--gctc-night-700) 55%, var(--gctc-night-900) 100%);
  color: #fff;
}
.gctc-night > *:not(.gctc-waves) { position: relative; z-index: 1; }
.gctc-night .gctc-waves { opacity: .22; }
/* Softer than the light variant: on a dark field the band's edge would read as
   a hard-edged blob rather than light. Re-measure before using this - the
   figure above predates the current band sizes. */
.gctc-night .gctc-waves__band { filter: blur(60px) saturate(1.3); }

/* The hero's key visual scrolls away; the waves below drift on independently,
   so the page keeps moving without the artwork having to stay in view. */
@media (prefers-reduced-motion: reduce) {
  .gctc-waves__band { animation: none; }
}
@media (max-width: 767px) {
  /* Blur is costly on phones, and at this width the bands already overlap. */
  .gctc-waves__band--blue { display: none; }
  .gctc-waves__band { filter: blur(26px) saturate(1.3); }
}

/* Page-specific composition keeps CMS copy intact while removing the repeated-card look. */
body[data-page="overview"] main .rounded-3xl {
  border-radius: 0 !important;
}
body[data-page="overview"] main .rounded-3xl.bg-white {
  border-top: 4px solid var(--gctc-blue) !important;
  box-shadow: 0 24px 70px rgba(2,54,115,.08) !important;
}
body[data-page="overview"] main .rounded-2xl.bg-white {
  border-radius: .5rem !important;
}
/* Map links also occur in saved CMS HTML, so match their destinations instead
   of requiring editors to replace the stored markup. */
@media (max-width: 767px) {
  body[data-page="overview"] main .bg-white:has(a[href*="maps.app.goo.gl"]) { padding: 20px; }
  body[data-page="overview"] main div:has(> a[href*="maps.app.goo.gl"]) {
    display: grid; width: 100%; max-width: 100%; gap: 8px;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(3.75rem + 16px)), 1fr));
  }
  body[data-page="overview"] main div:has(> a[href*="maps.app.goo.gl"]) > a {
    min-width: 0; justify-content: center;
  }
  body[data-page="overview"] main a:is(
    [href*="google.com/maps"], [href*="google.co.kr/maps"],
    [href*="maps.google."], [href*="maps.app.goo.gl"], [href*="goo.gl/maps"],
    [href*="map.kakao.com"], [href*="map.naver.com"]
  ) {
    padding-inline: 6px; gap: 4px; flex-shrink: 0; min-height: 44px; max-width: 100%;
    word-break: keep-all; overflow-wrap: normal;
  }
  body[data-page="overview"] main a:is(
    [href*="google.com/maps"], [href*="google.co.kr/maps"],
    [href*="maps.google."], [href*="maps.app.goo.gl"], [href*="goo.gl/maps"],
    [href*="map.kakao.com"], [href*="map.naver.com"]
  ) > i { width: 1em; flex-shrink: 0; margin: 0; text-align: center; }
  html:lang(ko) body[data-page="overview"] main div:has(> a[href*="maps.app.goo.gl"]) > a > span {
    flex: 0 0 4em; width: 4em; white-space: normal;
    word-break: keep-all; overflow-wrap: normal; line-height: 1.45; text-align: center;
  }
}
body[data-page="meetup"] main section.bg-white:first-of-type {
  background-image: radial-gradient(circle at 12% 50%, rgba(156,210,234,.28), transparent 25%);
}
body[data-page="meetup"] main section.bg-slate-50 .grid > .bg-white,
body[data-page="meetup"] main section.bg-slate-50 .grid > [class*="bg-"] {
  border-radius: 0 !important;
  box-shadow: none !important;
}
body[data-page="board"] #board-table-container {
  border-radius: 0 !important;
  border-top: 4px solid var(--gctc-navy) !important;
}

/* Board: keep the title prominent on narrow screens without squeezing six
   desktop columns into a phone. The same links open the existing detail modal. */
.board-mobile-list { display: none; }
.board-post-title, .notice-detail {
  min-width: 0;
  word-break: keep-all;
  overflow-wrap: anywhere;
}
.board-categories a { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; }
.board-search input { min-height: 44px; }
.board-search-submit { min-width: 44px; }
.board-post:focus-visible, .board-search-submit:focus-visible, .notice-modal-close:focus-visible {
  outline: 3px solid var(--gctc-blue); outline-offset: -3px;
}
.notice-detail .notice-detail-title { line-height: 1.5; }
.notice-detail .notice-detail-body { line-height: 1.85; }
.notice-detail-meta > * { min-width: 0; max-width: 100%; }
.notice-modal-panel {
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  min-width: 0; min-height: 0; overscroll-behavior: contain;
}
.notice-modal-toolbar {
  position: sticky; top: 0; z-index: 1; display: flex; justify-content: flex-end;
  padding: 4px 12px; background: #fff; border-bottom: 1px solid #edf1f5;
}
.notice-modal-close { display: grid; place-items: center; width: 44px; height: 44px; flex-shrink: 0; border-radius: 10px; }
@media (max-width: 1023px) {
  body[data-page="board"] .board-toolbar { position: static; }
  .board-toolbar > div > div { flex-direction: column; align-items: stretch; }
  .board-toolbar .board-search { max-width: none; }
  .board-desktop-table { display: none; }
  .board-mobile-list { display: block; list-style: none; padding: 0; margin: 0; }
  .board-mobile-list li + li { border-top: 1px solid #e2e8f0; }
  .board-post { display: block; padding: 20px; color: #10213b; word-break: keep-all; overflow-wrap: anywhere; }
  .board-post:hover { background: #f1f7fc; }
  .board-post-category {
    display: inline-block; max-width: 100%; padding: 4px 10px; margin-bottom: 10px;
    background: #edf4ff; border-radius: 6px; color: #0053b8;
    font-size: .75rem; font-weight: 750; word-break: keep-all; overflow-wrap: anywhere;
  }
  .board-post-title { display: block; font-size: 1rem; font-weight: 750; line-height: 1.65; }
  .board-post-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 12px; font-size: .75rem; line-height: 1.6; color: #64748b; }
  .board-post-meta > * { min-width: 0; overflow-wrap: anywhere; }
  .board-post-author { flex-basis: 100%; }
}
@media (max-width: 767px) {
  body[data-page="board"] .gctc-header-inner { min-height: 80px; height: auto; gap: 8px; padding-block: 10px; }
  body[data-page="board"] .gctc-header-brand { min-width: 0; flex: 1; gap: 8px; }
  body[data-page="board"] .gctc-header-mark,
  body[data-page="board"] .gctc-header-mark img { width: 40px; }
  body[data-page="board"] .gctc-header-title { min-width: 0; word-break: keep-all; overflow-wrap: anywhere; }
  body[data-page="board"] .gctc-header-title span { font-size: 14px; line-height: 1.4; }
  body[data-page="board"] .gctc-header-controls { flex-shrink: 0; gap: 4px; }
  body[data-page="board"] .gctc-header-language { font-size: 12px; }
  body[data-page="board"] .gctc-header-language button { min-width: 32px; min-height: 40px; padding: 4px 6px; }
  body[data-page="board"] .gctc-header-menu { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; }
  .board-search input { font-size: 16px; padding-left: 44px; }
  .board-post { padding: 18px 16px; }
  body[data-page="board"] .notice-detail-page { padding-top: 24px; padding-bottom: 40px; }
  .notice-detail .notice-detail-body { font-size: 1rem; }
}
@media (max-width: 359px) {
  body[data-page="board"] .gctc-header-brand { gap: 6px; }
  body[data-page="board"] .gctc-header-mark,
  body[data-page="board"] .gctc-header-mark img { width: 36px; }
  body[data-page="board"] .gctc-header-title span { font-size: 12px; }
}
body[data-page="program"] main section.bg-white.border-b h2,
body[data-page="overview"] main h2,
body[data-page="meetup"] main h2,
body[data-page="board"] main h2 {
  letter-spacing: -.045em;
}

/* ---------------------------------------------------------------------------
   Beam cards: a light runs the outline of each highlight card, one card at a
   time, the way the loop travels through the key visual. Built with offset-path
   so the light follows the rounded rectangle exactly; browsers without it fall
   back to a static gradient edge below.

   The cards sit on a pale surface, so the ray is the saturated end of the
   palette - glow into blue - rather than the mint used against a dark field.
--------------------------------------------------------------------------- */
.gctc-beam {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid transparent;
  pointer-events: none;
  /* No overflow clipping here: it would clip to the padding box, while the mask
     below reveals only the border ring, so the two together would paint nothing.
     The mask is what keeps the ray from spilling outside the card. */
  /* Punch out the middle so only the border ring paints. */
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  /* Each card lights up in turn, then hands over to the next. */
  animation: gctc-beam-show var(--beam-cycle, 12s) linear infinite;
  animation-delay: var(--beam-delay, 0s);
}
.gctc-beam__ray {
  position: absolute;
  width: 170px;
  aspect-ratio: 1;
  background: radial-gradient(circle at 50% 50%,
    var(--gctc-glow) 0%, #35b6e8 30%, var(--gctc-blue) 52%, rgba(0,88,204,0) 72%);
  offset-path: rect(0 auto auto 0 round 170px);
  offset-distance: 0;
  animation: gctc-beam-travel var(--beam-cycle, 12s) linear infinite;
  animation-delay: var(--beam-delay, 0s);
}
/* A card the pointer is on holds its own light instead of waiting its turn,
   and the ray laps continuously rather than parking between turns. */
.gctc-card:hover > .gctc-beam {
  opacity: 1;
  animation: none;
}
.gctc-card:hover > .gctc-beam > .gctc-beam__ray {
  animation: gctc-beam-lap 4s linear infinite;
}
@keyframes gctc-beam-lap {
  to { offset-distance: 100%; }
}

/* Stored CMS markup is re-sanitized on every render and inline style is dropped,
   so the per-card delay has to be a class rather than a variable in the markup.
   Thirds of the cycle: the light leaves one card as the next takes it up. */
.gctc-beam--1, .gctc-beam--1 .gctc-beam__ray { animation-delay: 0s; }
.gctc-beam--2, .gctc-beam--2 .gctc-beam__ray { animation-delay: 4s; }
.gctc-beam--3, .gctc-beam--3 .gctc-beam__ray { animation-delay: 8s; }

@keyframes gctc-beam-travel {
  /* One lap during this card's share of the cycle, then parked until its turn. */
  0%   { offset-distance: 0; }
  25%  { offset-distance: 100%; }
  100% { offset-distance: 100%; }
}
@keyframes gctc-beam-show {
  0%   { opacity: 0; }
  4%   { opacity: 1; }
  21%  { opacity: 1; }
  25%  { opacity: 0; }
  100% { opacity: 0; }
}

/* Without offset-path the ray cannot follow the border, so show a fixed edge. */
@supports not (offset-path: rect(0 auto auto 0 round 1px)) {
  .gctc-beam {
    opacity: 1;
    animation: none;
    border-color: rgba(0,88,204,.28);
  }
  .gctc-beam__ray { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .gctc-beam {
    opacity: 1;
    animation: none;
    border-color: rgba(0,88,204,.22);
  }
  .gctc-beam__ray { display: none; }
}

/* ---------------------------------------------------------------------------
   Glass card: the same frosted treatment as the notices list below it, so the
   two "one dominant surface per section" pieces of the home page read as one
   material instead of an opaque card next to a translucent list. A dedicated
   class rather than reusing .gctc-glass-panel here, so its own :hover can live
   in this stylesheet instead of racing Tailwind's hover utilities for the same
   border-color/box-shadow properties at equal specificity.
--------------------------------------------------------------------------- */
.gctc-glass-card {
  background: rgba(255,255,255,.55);
  backdrop-filter: blur(22px) saturate(1.4);
  -webkit-backdrop-filter: blur(22px) saturate(1.4);
  border: 1px solid rgba(255,255,255,.8);
  box-shadow: 0 20px 45px -22px rgba(0,58,140,.22), inset 0 1px 0 rgba(255,255,255,.9);
  transition: box-shadow .2s ease, border-color .2s ease, transform .2s ease;
}
.gctc-glass-card:hover {
  border-color: rgba(0,88,204,.35);
  box-shadow: 0 26px 55px -22px rgba(0,88,204,.32), inset 0 1px 0 rgba(255,255,255,.9);
  transform: translateY(-2px);
}
/* backdrop-filter's saturate(1.4) boosts whatever colour sits behind the glass
   before the semi-transparent white is blended on top, so raising the card's
   own opacity barely moves the worst case (measured .55->.80 opacity: 3.5:1
   -> 3.5:1, basically flat). Darkening the two text colours that actually sit
   on this card is what fixes it. Measured against the worst overlap behind a
   card (#8ec2db): brand-primary only held 3.5:1 and the wave-host grey 4.1:1,
   so both get one more step down specifically in this context; title
   (slate-900, 9.3:1) and the teal accent (4.6:1) already clear the bar. */
.gctc-glass-card .text-brand-primary,
.gctc-glass-card .border-brand-primary { color: var(--gctc-night) !important; border-color: var(--gctc-night) !important; }
.gctc-glass-card .text-slate-400,
.gctc-glass-card .text-slate-500 { color: #394454 !important; }

/* ---------------------------------------------------------------------------
   Glass list: a frosted, semi-transparent panel over the glow waves in place
   of a flat white sheet, so the drifting colour keeps showing through behind
   the notices. Rows take turns catching a brief heartbeat - a quick double
   pulse in background and left-edge ring - so the eye is drawn down the list
   the way the highlight cards' beam draws it across a row of cards.
--------------------------------------------------------------------------- */
.gctc-glass-panel {
  position: relative;
  background: rgba(255,255,255,.55);
  backdrop-filter: blur(22px) saturate(1.4);
  -webkit-backdrop-filter: blur(22px) saturate(1.4);
  border: 1px solid rgba(255,255,255,.8);
  box-shadow: 0 30px 60px -30px rgba(0,58,140,.28), inset 0 1px 0 rgba(255,255,255,.9);
}
.gctc-glass-row + .gctc-glass-row { border-top: 1px solid rgba(15,23,42,.08); }
.gctc-row-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  animation: gctc-row-heartbeat 9s ease-in-out infinite;
}
.gctc-glass-row > *:not(.gctc-row-glow) { position: relative; }

/* Two quick beats (lub-dub) at the start of each row's own cycle, then a long
   rest while - offset by a third of the cycle per row - the next one beats. */
@keyframes gctc-row-heartbeat {
  0%, 7%, 100% {
    background: rgba(0,88,204,0);
    box-shadow: inset 3px 0 0 0 rgba(0,88,204,0);
  }
  1.5% {
    background: rgba(0,88,204,.1);
    box-shadow: inset 3px 0 0 0 rgba(0,88,204,.6);
  }
  3.5% {
    background: rgba(0,88,204,.03);
    box-shadow: inset 3px 0 0 0 rgba(0,88,204,.25);
  }
  5% {
    background: rgba(0,88,204,.11);
    box-shadow: inset 3px 0 0 0 rgba(0,88,204,.65);
  }
}
.gctc-row--1 .gctc-row-glow { animation-delay: 0s; }
.gctc-row--2 .gctc-row-glow { animation-delay: 3s; }
.gctc-row--3 .gctc-row-glow { animation-delay: 6s; }
.gctc-row--4 .gctc-row-glow { animation-delay: 1.5s; }
.gctc-row--5 .gctc-row-glow { animation-delay: 4.5s; }

@media (prefers-reduced-motion: reduce) {
  .gctc-row-glow { animation: none; }
}

/* A restrained focus treatment based on the poster's pale energy ring. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible {
  outline: 3px solid var(--gctc-air);
  outline-offset: 3px;
}

@media (max-width: 767px) {
  main table thead tr { background: var(--gctc-navy) !important; }
}
