/*
 * This is a manifest file that'll be compiled into application.css.
 *
 * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
 * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
 * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
 * depending on specificity.
 *
 * Consider organizing styles into separate files for maintainability.
 */

:root {
  /* Brand color - 레드 계열 */
  --color-primary: #FF0000;
  --color-primary-dark: #DC2626;
  --color-primary-light: #FCA5A5;
  --color-primary-lighter: #FEE2E2;

  /* Neutral colors */
  --color-bg: #F9FAFB;
  --color-card: #FFFFFF;
  --color-border: #E5E7EB;
  --color-text: #111827;
  --color-text-sub: #6B7280;
}

/* Safari bounce scroll 방지 */
body {
  overscroll-behavior: none;
  /* Pretendard is loaded via CDN in the layout <head>. The Variable version
     covers Korean + English with consistent metrics. We list system-Korean and
     -apple-system as fallbacks for the moment Pretendard hasn't loaded yet. */
  font-family: 'Pretendard Variable', 'Pretendard', -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
}

/* Student Home / Explore */
.home-horizontal-scroll {
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.home-horizontal-scroll::-webkit-scrollbar {
  display: none;
}

.home-peek-scroll {
  scroll-snap-type: x proximity;
}

.home-peek-card {
  width: clamp(260px, 76vw, 316px);
  scroll-snap-align: start;
}

.home-company-card {
  height: 7.25rem;
  width: clamp(148px, 42vw, 190px);
  scroll-snap-align: start;
}

.home-club-card {
  height: 10rem;
  width: clamp(260px, 76vw, 316px);
  scroll-snap-align: start;
}

.home-sponsored-sticker {
  height: 3.75rem;
  left: 0;
  overflow: hidden;
  pointer-events: none;
  position: absolute;
  top: 0;
  width: 5.75rem;
  z-index: 2;
}

.home-sponsored-sticker::before {
  background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 62%, #f97316 100%);
  box-shadow: 0 0.3rem 0.85rem rgba(217, 119, 6, 0.2);
  clip-path: polygon(0 0, 100% 0, 0 100%);
  content: "";
  inset: 0;
  position: absolute;
}

.home-sponsored-sticker::after {
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.99) 0%, rgba(248, 250, 252, 0.98) 62%, rgba(226, 232, 240, 0.92) 100%);
  border-bottom-left-radius: 0.45rem;
  box-shadow:
    -0.08rem 0.1rem 0.18rem rgba(15, 23, 42, 0.12),
    -0.02rem 0.02rem 0.06rem rgba(255, 255, 255, 0.9) inset;
  clip-path: polygon(0 0, 100% 0, 100% 100%);
  content: "";
  height: 1.16rem;
  position: absolute;
  right: 0;
  top: 0;
  width: 1.16rem;
}

.home-sponsored-sticker span {
  color: white;
  font-size: 0.66rem;
  font-weight: 700;
  left: 0.58rem;
  letter-spacing: 0;
  line-height: 1;
  position: absolute;
  text-shadow: 0 1px 0 rgba(146, 64, 14, 0.18);
  top: 0.68rem;
}

.home-job-card {
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
}

.home-two-row-carousel {
  align-items: stretch;
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, minmax(0, 1fr));
  grid-auto-columns: clamp(260px, 76vw, 316px);
  gap: 0.75rem;
  margin: -0.125rem -0.125rem 0;
  overflow-x: auto;
  padding: 0.125rem 0.125rem 0.75rem;
  scroll-behavior: smooth;
  scroll-snap-type: x proximity;
}

.home-two-row-carousel > * {
  display: flex;
  min-width: 0;
  scroll-snap-align: start;
  width: 100%;
}

.home-two-row-carousel > * > * {
  width: 100%;
}

.home-carousel-control {
  align-items: center;
  background: rgba(17, 24, 39, 0.88);
  border: 1px solid rgba(255, 255, 255, 0.72);
  border-radius: 9999px;
  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.18);
  color: #ffffff;
  display: none;
  height: 2.75rem;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  transition: opacity 160ms ease, transform 160ms ease, background-color 160ms ease;
  width: 2.75rem;
  z-index: 10;
}

.home-carousel-control--prev {
  left: 0;
}

.home-carousel-control--next {
  right: 0;
}

.home-carousel-control svg {
  color: #ffffff;
  display: block;
  height: 1.25rem;
  stroke: currentColor;
  width: 1.25rem;
}

.home-carousel-row:hover .home-carousel-control--next:not(.is-disabled),
.home-carousel-control--next:not(.is-disabled):hover,
.home-carousel-row:hover .home-carousel-control--prev.is-visible:not(.is-disabled),
.home-carousel-control--prev.is-visible:not(.is-disabled):hover {
  opacity: 1;
  pointer-events: auto;
}

.home-carousel-control:hover {
  background: rgba(17, 24, 39, 0.96);
  transform: translateY(-50%) scale(1.03);
}

.home-carousel-control.is-disabled {
  opacity: 0 !important;
  pointer-events: none !important;
}

.home-carousel-fade {
  opacity: 0;
  transition: opacity 160ms ease;
}

.home-carousel-fade.is-visible {
  opacity: 1;
}

@media (min-width: 768px) {
  .home-peek-scroll {
    scroll-snap-type: none;
  }

  .home-peek-card {
    width: auto;
    scroll-snap-align: none;
  }

  .home-company-card {
    width: 184px;
  }

  .home-club-card {
    width: 300px;
  }

  .home-two-row-carousel {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: repeat(2, minmax(0, 1fr));
    grid-auto-columns: calc((100% - 1.5rem) / 3);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
  }

  .home-two-row-carousel > * {
    scroll-snap-align: start;
  }

  .home-carousel-control {
    display: inline-flex;
  }
}

@media (hover: hover) and (pointer: fine) {
  .home-job-card:hover {
    border-color: rgb(209 213 219);
    box-shadow: 0 8px 18px rgba(15, 23, 42, 0.08);
  }
}

/* Hotwire Native shell — native iOS UITabBar replaces the web bottom-nav,
   and a custom UINavigationController nav bar (configured in SceneDelegate)
   replaces the web header. CSS forces them off as a safety net even if any
   JS accidentally un-hides them. */
body[data-hotwire-native="true"] #main-header,
body[data-hotwire-native="true"] #student-bottom-nav,
body[data-hotwire-native="true"] #company-bottom-nav,
body[data-hotwire-native="true"] #ed-header,
body[data-hotwire-native="true"] #ed-bottom-nav,
body[data-hotwire-native="true"] #page-loading-overlay {
  display: none !important;
  pointer-events: none !important;
}

/* Every page on web puts top padding/margin on `<main>` purely to clear
   the fixed `<header>` (`pt-12`, `pt-16`, `mt-16`, `mt-24`, etc.). In the
   native shell that header is gone — Hotwire lays the WKWebView out under
   the iOS UINavigationBar so any extra top spacing becomes dead space.
   Collapse it for ALL `<main>` elements. Page-by-page allowlists turned
   into a constant maintenance tax (every new page repeats the bug). */
body[data-hotwire-native="true"] main {
  padding-top: 0 !important;
  margin-top: 0 !important;
}

/* List pages (events/projects/jobs/clubs/campaigns) pin a search + filter
   block at the top with `position: fixed; top: 48px` to clear the web header,
   then drop a hardcoded `h-44` (176px) spacer below to push the first card
   past the now-removed-from-flow block.

   In the native shell:
   1. The web header is gone, so we don't need the 48px offset.
   2. The block height is variable (category count, login state, admin role
      all change it), so a hardcoded spacer height will always be wrong on
      some configuration / device.

   Switch `fixed` → `sticky`. Sticky stays in normal flow, so it reserves
   exactly its own height — no spacer math needed at any screen size. The
   element still pins to `top: 0` once the user scrolls past it.

   Also tighten the block's vertical padding so the search input doesn't
   sit far below the iOS UINavigationBar. */
body[data-hotwire-native="true"] .md\:hidden.fixed.top-\[48px\] {
  position: sticky !important;
  top: 0 !important;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
  will-change: transform;
  overflow-anchor: none;
  /* Same horizontal padding on every list page (Events / Projects / Jobs /
     Clubs) so the search input + filter row align consistently across tabs.
     Matches the `px-4` baked into each page's bar markup. */
  padding: 0.5rem 1rem !important;
  /* Stretch full-bleed regardless of the parent wrapper's padding. Each list
     page has a different parent (Events/Projects/Jobs use `w-full px-2`,
     Clubs uses `container mx-auto px-0`), so a fixed negative margin can't
     cover all of them. The calc() trick centers a 100vw box on the viewport
     no matter how the parent is laid out — the bar's bg-white covers the
     full screen so cards behind it stay hidden as the list scrolls. */
  width: 100vw !important;
  margin-left: calc(50% - 50vw) !important;
}

/* Slimmer, capsule-shaped search input in the native shell — matches iOS
   native search field styling and frees up vertical space. */
body[data-hotwire-native="true"] .md\:hidden.fixed.top-\[48px\] input[type="text"] {
  /* iOS WKWebView scrolls/zooms focused inputs below 16px, which makes the
     native sticky search bar jump upward or disappear while the keyboard is
     active. Keep the visual height compact with padding, but use the iOS-safe
     text size. */
  font-size: 16px !important;
  line-height: 1.25rem !important;
  padding-top: 0.5rem !important;
  padding-bottom: 0.5rem !important;
  border-radius: 9999px !important;
}

/* Keep icon-only controls visually locked to the native search field height.
   Tailwind's h-12/w-12 is comfortable on mobile web, but the native shell
   uses a slimmer iOS-style search field. */
body[data-hotwire-native="true"] .md\:hidden.fixed.top-\[48px\] button[aria-label] {
  width: 2.25rem !important;
  min-width: 2.25rem !important;
  height: 2.25rem !important;
  border-radius: 0.75rem !important;
}

body[data-hotwire-native="true"] .md\:hidden.fixed.top-\[48px\] button[aria-label] svg {
  width: 1rem !important;
  height: 1rem !important;
}

/* Spacer is redundant under sticky positioning. Use the adjacent-sibling
   selector so this catches every list page regardless of which fixed-height
   class it uses (h-44 on events/projects, h-18 on clubs, etc.) — keep a
   small gap so the first card doesn't sit flush against the filter pills. */
body[data-hotwire-native="true"] .md\:hidden.fixed.top-\[48px\] + .md\:hidden {
  height: 0.5rem !important; /* 8px breathing room */
}

/* Events / Projects / Saved place their mobile bottom sheet between the
   sticky search bar and spacer. Collapse that spacer in native too. */
body[data-hotwire-native="true"] .md\:hidden.fixed.top-\[48px\] + .md\:hidden.hidden.fixed.inset-0 + .md\:hidden {
  height: 0.5rem !important;
}

/* iOS WKWebView does not handle fixed-position text inputs reliably while
   the keyboard is visible: the visual viewport can move the fixed layer,
   making the search row appear to slide under the native navigation bar.
   These list pages therefore opt into the same sticky native header behavior
   Clubs uses instead of falling back to normal relative flow. */
body[data-hotwire-native="true"] .md\:hidden.fixed.top-\[48px\][data-native-stable-search] {
  position: sticky !important;
  top: 0 !important;
  left: auto !important;
  right: auto !important;
  flex: 0 0 auto;
  width: 100vw !important;
  margin-left: calc(50% - 50vw) !important;
  margin-right: 0 !important;
  z-index: 60 !important;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  -webkit-transform: translateZ(0) !important;
  transform: translateZ(0) !important;
  will-change: transform !important;
  overflow-anchor: none;
}

body[data-hotwire-native="true"] .md\:hidden.fixed.top-\[48px\][data-native-stable-search] + [data-native-stable-search-spacer] {
  flex: 0 0 0.5rem;
  height: 0.5rem !important;
  min-height: 0.5rem !important;
}

/* Search/list pages in the native shell keep the search row in normal flow
   at the top and scroll only the result list, mirroring Clubs. If the whole
   document scrolls, WKWebView tries to adjust window.scrollY when the search
   input focuses and the keyboard appears; if the input is fixed-position,
   that adjustment looks like the search row sliding under the native nav bar. */
html:has(body[data-hotwire-native="true"] turbo-frame[data-page-type="events"]),
html:has(body[data-hotwire-native="true"] turbo-frame[data-page-type="projects"]),
html:has(body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"]),
html:has(body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"]) {
  height: 100%;
  overflow: hidden;
}

body[data-hotwire-native="true"]:has(turbo-frame[data-page-type="events"]),
body[data-hotwire-native="true"]:has(turbo-frame[data-page-type="projects"]),
body[data-hotwire-native="true"]:has(turbo-frame[data-page-type="jobs"]),
body[data-hotwire-native="true"]:has(turbo-frame[data-page-type="clubs"]) {
  height: 100vh;
  height: 100dvh;
  overflow: hidden !important;
  overscroll-behavior-y: none;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="events"],
body[data-hotwire-native="true"] turbo-frame[data-page-type="projects"],
body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"],
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="events"] main,
body[data-hotwire-native="true"] turbo-frame[data-page-type="projects"] main,
body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] main,
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] main {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  padding-bottom: 0 !important;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="events"] main > div,
body[data-hotwire-native="true"] turbo-frame[data-page-type="projects"] main > div,
body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] main > div,
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] main > div {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] [data-clubs-page-shell],
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] [data-clubs-layout],
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] [data-clubs-list-column],
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] [data-clubs-list-panel] {
  min-height: 0;
  overflow: hidden;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] [data-clubs-page-shell] {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  height: 100%;
  max-width: none;
  width: 100%;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] [data-clubs-layout],
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] [data-clubs-list-column],
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] [data-clubs-list-panel] {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="events"] [data-campaign-scroll-container],
body[data-hotwire-native="true"] turbo-frame[data-page-type="projects"] [data-campaign-scroll-container],
body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] [data-job-scroll-container],
body[data-hotwire-native="true"] turbo-frame[data-page-type="clubs"] #club-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 1rem);
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] #jobs-layout-wrapper,
body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] #jobs-content-wrapper {
  min-height: 0;
}

body[data-hotwire-native="true"] [data-native-pull-refresh] {
  overscroll-behavior-y: contain;
  position: relative;
  touch-action: pan-y;
}

body[data-hotwire-native="true"] [data-native-pull-refresh] > :not(.native-pull-refresh-indicator) {
  transition: transform 180ms ease;
}

body[data-hotwire-native="true"] [data-native-pull-refresh][data-native-pull-refresh-state="pulling"] > :not(.native-pull-refresh-indicator),
body[data-hotwire-native="true"] [data-native-pull-refresh][data-native-pull-refresh-state="refreshing"] > :not(.native-pull-refresh-indicator) {
  transform: translateY(var(--native-pull-refresh-distance, 0px));
  will-change: transform;
}

body[data-hotwire-native="true"] .native-pull-refresh-indicator {
  align-items: center;
  color: #ef4444;
  display: flex;
  height: 3.25rem;
  justify-content: center;
  left: 0;
  opacity: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  top: 0;
  transform: translateY(calc(var(--native-pull-refresh-distance, 0px) - 3.25rem));
  transition: opacity 160ms ease, transform 180ms ease;
  z-index: 8;
}

body[data-hotwire-native="true"] [data-native-pull-refresh][data-native-pull-refresh-state="pulling"] .native-pull-refresh-indicator,
body[data-hotwire-native="true"] [data-native-pull-refresh][data-native-pull-refresh-state="refreshing"] .native-pull-refresh-indicator {
  opacity: 1;
}

body[data-hotwire-native="true"] [data-native-pull-refresh][data-native-pull-refresh-state="refreshing"] .native-pull-refresh-indicator {
  transform: translateY(0);
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] main {
  position: relative;
  z-index: 1;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] #mobile-search-container,
body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] [data-job-scroll-container] {
  pointer-events: auto !important;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] [data-jobs-view-switcher] {
  align-items: center !important;
  display: flex !important;
  justify-content: center !important;
  left: 0 !important;
  position: fixed !important;
  right: 0 !important;
  width: 100vw !important;
  z-index: 90 !important;
  -webkit-transform: translate3d(0, 0, 0) !important;
  transform: translate3d(0, 0, 0) !important;
  will-change: transform;
  pointer-events: none !important;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] [data-jobs-view-switcher] [data-jobs-view-floating] {
  pointer-events: auto !important;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"]:has(#mobile-filter-sheet:not(.hidden)) [data-jobs-view-switcher] {
  opacity: 0 !important;
  pointer-events: none !important;
  visibility: hidden !important;
  z-index: 0 !important;
}

body[data-hotwire-native="true"] turbo-frame[data-page-type="jobs"] [data-jobs-mobile-filter-footer],
body[data-hotwire-native="true"] turbo-frame[data-page-type="job-company"] [data-jobs-mobile-filter-footer] {
  margin-bottom: 0.25rem !important;
  padding-bottom: 1rem !important;
}

body[data-hotwire-native="true"] #mobile-filter-sheet.hidden,
body[data-hotwire-native="true"] #advanced-filter-drawer.hidden,
body[data-hotwire-native="true"] #map-modal.hidden,
body[data-hotwire-native="true"] #campaign-card-delete-modal.hidden {
  display: none !important;
  pointer-events: none !important;
}

body[data-hotwire-native="true"] #mobile-filter-sheet:has(#mobile-filter-sheet-panel.translate-y-full),
body[data-hotwire-native="true"] #advanced-filter-drawer:has(#advanced-filter-panel.translate-x-full),
body[data-hotwire-native="true"] #map-modal:has(#map-modal-content.opacity-0) {
  pointer-events: none !important;
}

body[data-hotwire-native="true"] .native-pull-refresh-spinner {
  align-items: center;
  background: rgba(255, 255, 255, 0.94);
  border: 1px solid rgba(229, 231, 235, 0.95);
  border-radius: 9999px;
  box-shadow: 0 8px 20px rgba(15, 23, 42, 0.1);
  display: inline-flex;
  height: 2rem;
  justify-content: center;
  width: 2rem;
}

body[data-hotwire-native="true"] .native-pull-refresh-spinner svg {
  height: 1.1rem;
  transform: rotate(calc(var(--native-pull-refresh-distance, 0px) * 2.2deg));
  width: 1.1rem;
}

body[data-hotwire-native="true"] [data-native-pull-refresh-ready] .native-pull-refresh-spinner svg,
body[data-hotwire-native="true"] [data-native-pull-refresh-state="refreshing"] .native-pull-refresh-spinner svg {
  animation: nativePullRefreshSpin 720ms linear infinite;
}

@keyframes nativePullRefreshSpin {
  to {
    transform: rotate(360deg);
  }
}

/* Detail pages (events/projects/campaigns show) have a fixed bottom action
   bar pinned at `bottom: 0` with the Apply button. The native shell hides
   the UITabBar on these pages via `hidesBottomBarWhenPushed`, so the bar
   needs to:
   1. Stay glued to the actual bottom of the screen (`bottom: 0`).
   2. Push its inner buttons above the home indicator via `padding-bottom`
      so the gray background extends all the way down with no floating gap.
   Setting `bottom: env(safe-area-inset-bottom)` instead would leave dead
   space below the bar that visually "floats" while scrolling. */
body[data-hotwire-native="true"] .lg\:hidden.fixed.bottom-0 {
  bottom: 0 !important;
  padding-bottom: env(safe-area-inset-bottom) !important;
}

/* Top-of-page in-page back links (chevron + "Back to X") are redundant in
   the native shell — the iOS UINavigationBar already shows a system back
   chevron. Hide these so the UI doesn't have two stacked back affordances.
   Add the `app-back-link` class to any such link in the views and it
   disappears in the native app while staying visible on the web. */
body[data-hotwire-native="true"] .app-back-link {
  display: none !important;
}

/* Floating action buttons positioned to sit above the web bottom-nav use
   `bottom: calc(5rem + env(safe-area-inset-bottom))` (or similar) on web.
   In the Hotwire native shell the web bottom-nav is gone and a native
   UITabBar takes its place; the web math leaves the FAB floating too high
   (when WKWebView is constrained above the tab bar) or buried behind the
   tab bar (when it extends underneath). Override to a single offset that
   clears the native tab bar consistently on both shell configurations.
   Tag the FAB container with `data-fab-above-nav`. */
body[data-hotwire-native="true"] [data-fab-above-nav] {
  /* In the KATCHUP iOS shell the WKWebView is constrained above the
     UITabBar, so env(safe-area-inset-bottom) is 0 inside the WebView and
     the WebView's bottom edge IS the tab bar top. Match the web feel
     (FAB ≈ 16px above the bottom-nav) by collapsing the offset to 1rem.
     env() stays in the formula so devices that pass a non-zero inset
     don't end up behind the tab bar. */
  bottom: calc(env(safe-area-inset-bottom, 0px) + 1rem) !important;
}

/* Generic "hide this element only in the native shell" hook. Useful for
   elements that duplicate something the iOS nav bar already shows
   (e.g. a page-top H1 that matches the URL-based header label). */
body[data-hotwire-native="true"] .app-hidden {
  display: none !important;
}

/* Admin pages: hide every page-title <h1> inside #adminMain in the native
   shell. The iOS UINavigationBar already shows the per-section label
   (mapped per URL in SceneDelegate.swift's `urlLabelRules`), so the inline
   H1 (e.g. "학생 관리", "Events & Projects", "동아리 관리", ...) would
   stack a second redundant title under the system nav bar. h2/h3 sub-
   headings inside the page stay visible — they're real section markers,
   not page titles. */
body[data-hotwire-native="true"] #adminMain h1 {
  display: none !important;
}

/* Adds breathing room between the iOS UINavigationBar and the first
   page-content element. Margin (not padding) so the gap sits outside
   the element — important for tab pills / cards whose background would
   otherwise extend upward. Web mobile already has the fixed web header
   margin baked into the page layout, so this is native-only. */
body[data-hotwire-native="true"] .app-top-gap {
  margin-top: 1rem;
}

/* Campaign detail lightbox media.
   Mobile: width fills viewport, height capped to 85vh — so the image
   maintains aspect AND vertically centers (justify-center on the wrap).
   Desktop keeps natural sizing within max-w 90vw / max-h 85vh. */
.lightbox-media-image,
.lightbox-media-video {
  display: block;
  width: 100vw;
  max-width: 100vw;
  height: auto;
  max-height: 85vh;
  object-fit: contain;
}

/* Tall portrait images on mobile — width fills viewport, height grows
   to natural size, and #attachment-lightbox (overflow-y-auto) lets the
   user scroll. lbCheckScrollable then surfaces the "Scroll to see more"
   hint. Detection (image ratio > viewport ratio) lives in detail.js
   img.onload — applied only when fitting by width would overflow. */
@media (max-width: 767px) {
  .lightbox-media-image--tall {
    max-height: none;
    object-fit: fill;
  }
}

@media (min-width: 768px) {
  .lightbox-media-image,
  .lightbox-media-video {
    width: auto;
    max-width: 90vw;
    max-height: 85vh;
    border-radius: 0.5rem;
  }

  /* Desktop tall mode — 600px 폭으로 column 형태 렌더(Instagram detail식)
     후 lightbox 자체를 세로 스크롤. fit 모드의 max-height: 85vh가 가로를
     좁게 만드는 portrait 이미지를 위해 사용. 600px = 본문 reading width
     기준의 안정적 폭이고 90vw로 작은 데스크탑/태블릿에서도 보호. */
  .lightbox-media-image--tall {
    width: min(90vw, 600px);
    max-width: 600px;
    max-height: none;
    object-fit: fill;
  }
}



/* 모바일 더블탭 줌 + 핀치 줌 방지 */
* {
  touch-action: pan-x pan-y;
}

/* Lightbox override: touch-action doesn't inherit, so the universal
   `pan-x pan-y` above lands on every descendant of #attachment-lightbox
   too — including the detail <img>, which silently blocks the browser
   from recognizing two-finger gestures as pinch-zoom on real devices.
   (DevTools mobile emulation synthesizes events that skip the check, so
   the bug only surfaces on physical phones.) Re-enable pinch inside the
   lightbox while keeping vertical pan for tall images. */
#attachment-lightbox,
#attachment-lightbox * {
  touch-action: pan-y pinch-zoom !important;
}

/* Primary/Brand color utilities - 로고, 강조 포인트용 */
.bg-primary { background-color: var(--color-primary) !important; }
.bg-primary-dark { background-color: var(--color-primary-dark) !important; }
.bg-primary-light { background-color: var(--color-primary-light) !important; }
.bg-primary-lighter { background-color: var(--color-primary-lighter) !important; }
.text-primary { color: var(--color-primary) !important; }
.text-primary-dark { color: var(--color-primary-dark) !important; }
.text-primary-light { color: var(--color-primary-light) !important; }
.border-primary { border-color: var(--color-primary) !important; }
.border-primary-light { border-color: var(--color-primary-light) !important; }
.stroke-primary { stroke: var(--color-primary) !important; }

/* Hover states — @media (hover: hover) 로 감싸서 마우스/트랙패드를 가진
   디바이스에서만 :hover 가 적용되게 한다. 안 그러면 터치 디바이스에서
   탭하면 :hover 가 sticky 하게 남아서, 카드 선택 후 뒤로 돌아왔을 때
   border 가 활성화 색으로 굳어있는 어색한 상태가 된다. (Tailwind v4 가
   생성하는 hover: 유틸은 이미 이 wrapper 안에 컴파일되지만, 여기 정의된
   커스텀 primary 컬러 유틸은 별도로 감싸야 함) */
@media (hover: hover) {
  .hover\:bg-primary-dark:hover { background-color: var(--color-primary-dark) !important; }
  .hover\:bg-primary-light:hover { background-color: var(--color-primary-light) !important; }
  .hover\:text-primary:hover { color: var(--color-primary) !important; }
  .hover\:text-primary-dark:hover { color: var(--color-primary-dark) !important; }
  .hover\:border-primary:hover { border-color: var(--color-primary) !important; }
  .hover\:border-primary-light:hover { border-color: var(--color-primary-light) !important; }
}

/* Focus ring */
.focus\:ring-primary:focus { --tw-ring-color: var(--color-primary-light) !important; }
.focus\:border-primary:focus { border-color: var(--color-primary) !important; }

/* Gradient utilities - 빨간색 계열 그라데이션 */
.bg-gradient-primary {
  background: linear-gradient(135deg, #FF0000 0%, #DC2626 100%) !important;
}
.bg-gradient-primary-light {
  background: linear-gradient(135deg, #FEE2E2 0%, #FECACA 100%) !important;
}
.bg-gradient-primary-soft {
  background: linear-gradient(135deg, #FFF5F5 0%, #FEE2E2 100%) !important;
}
.bg-gradient-primary-to-orange {
  background: linear-gradient(135deg, #FF0000 0%, #F97316 100%) !important;
}
.bg-gradient-primary-radial {
  background: radial-gradient(circle at top right, #FCA5A5 0%, #FEE2E2 50%, #FFFFFF 100%) !important;
}

/* Rich text preview (description_only fields) */
.field-rich-preview a { color: #2563eb; text-decoration: underline; }
.field-rich-preview ul { list-style-type: disc; padding-left: 1.5em; margin: 0.25em 0; }
.field-rich-preview ol { list-style-type: decimal; padding-left: 1.5em; margin: 0.25em 0; }

/* Campaign description rich text (replaces @tailwindcss/typography prose) */
.campaign-description ul { list-style-type: disc; padding-left: 1.5em; margin: 0.5em 0; }
.campaign-description ol { list-style-type: decimal; padding-left: 1.5em; margin: 0.5em 0; }
.campaign-description li { margin: 0.25em 0; }
.campaign-description li ul { list-style-type: circle; margin: 0.25em 0; }
.campaign-description li ol { margin: 0.25em 0; }
.campaign-description a { color: #2563eb; text-decoration: underline; }
.campaign-description strong, .campaign-description b { font-weight: 700; }
.campaign-description em, .campaign-description i { font-style: italic; }
.campaign-description u { text-decoration: underline; }
.campaign-description p { margin: 0.5em 0; }
.campaign-description h1, .campaign-description h2, .campaign-description h3 { font-weight: 700; margin: 0.75em 0 0.25em; }
.campaign-description h1 { font-size: 1.5em; }
.campaign-description h2 { font-size: 1.25em; }
.campaign-description h3 { font-size: 1.1em; }

/* Safe area for mobile (iPhone notch/dynamic island) */
.safe-area-bottom {
  padding-bottom: env(safe-area-inset-bottom, 0);
}
.safe-area-top {
  padding-top: env(safe-area-inset-top, 0);
}

/* In the iOS native shell the UITabBar is outside the WKWebView, so the
   Clubs filter sheet does not need the same large web safe-area footer gap.
   Keep mobile web unchanged while making the app sheet feel attached to the
   bottom edge. */
body[data-hotwire-native="true"] #mobile-filter-footer {
  margin-bottom: 0.25rem !important;
  padding-bottom: 1rem !important;
}

/* Job cards grid — 420px 하한 floor 는 md+ 에서만 적용한다. 모바일에서 이
   floor 가 그리드의 intrinsic min-width 로 새어나가 min-width:auto 인 flex
   조상(main, turbo-frame)을 420px+ 로 늘려 카드가 뷰포트 밖으로 빠져나갔음.
   모바일은 1열 고정이므로 1fr 하나로 충분. */
.jobs-card-grid {
  /* minmax(0, 1fr) — 1fr 단독은 트랙 최소가 auto 라서 카드 내부의 nowrap
     (truncate) 텍스트 min-width 가 트랙→그리드→flex 조상으로 전파돼 페이지를
     늘림. 최소 0 으로 강제해 truncate 가 정상 동작하도록. */
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 768px) {
  .jobs-card-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, max(calc(33.33% - 16px), 420px)), 1fr));
  }
}
