/* Sidebar navigation -- design-handoffs/asportal-public/design-guide/
   Portal_Navigation.css, confirmed against its own .png. PC static
   visuals (colors/sizes/depth structure -- category header + indented
   children, Normal/Hover/Selected states) match admin's navigation.css
   closely; two colors are genuinely different, not just admin's values
   restated (see tokens.css's own note on --color-border-faint/
   --color-icon-chevron).

   The real difference is Tablet/Mobile, which admin has zero equivalent
   of (admin dropped mobile support app-wide) -- confirmed directly in
   Portal_Navigation.png's own "02. Tablet, Mobile > 메뉴버튼 누를 시"
   section: below 1200px the persistent sidebar disappears, replaced by a
   hamburger icon in the Top bar (see top.css); tapping it slides this
   same sidebar in as a full-height drawer that renders *on top of* the
   content with the content behind it dimmed, anchored below the topbar
   (the topbar itself stays visible/interactive above the dim). Tapping
   the dimmed area, or picking a menu item, closes it again. The PNG's own
   mobile mock also shows the account name ("gildong") and a logout icon
   anchored at the very bottom of this drawer -- content top.css drops
   from its own Mobile layout (see that file's note) since there's no
   room for it in the collapsed mobile Top bar; .sidebar-footer below is
   that slot. Drawer width (220px, same as the PC sidebar) is a
   reasonable default, not measured off an exact px in the guide -- the
   PNG doesn't carry an explicit width label the way Portal_Resolution.css
   does for its own breakpoints; revisit once a real full-bleed mobile
   screenshot is available to pixel-check against.

   Toggle behavior: static/js/nav-drawer.js (window.toggleNavDrawer/
   closeNavDrawer), wired into base.html via the hamburger button's
   onclick="toggleNavDrawer()". */

/* .top lives here, as a full-width sibling above .app-shell, not nested
   inside .main/.sidebar's row -- confirmed against the real screen export
   (`design-handoffs/asportal-public/license status/Portal_패키지 현황.png`,
   PC/no-prefix), which shows the dark Top masthead spanning the entire
   viewport width corner-to-corner (logo at the true top-left corner),
   with the sidebar sitting *below* it, not beside it starting from y:0.
   An earlier structure nested .top inside .main (a flex sibling of
   .sidebar), which worked fine at Tablet/Mobile (.sidebar leaves the flow
   there, position:fixed, so .main/.top already stretched full width) but
   left .top only spanning the content column's width on PC, visibly
   shorter than the design -- caught from a real PC-width screenshot where
   the sidebar appeared to render on top of/beside the Top bar's logo
   instead of below the full-width bar. */
.portal-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.app-shell {
  display: flex;
  flex: 1;
  /* .top (top.css) is now position:fixed, removed from normal flow --
     without this, .app-shell would start at the true viewport top and
     render underneath the fixed bar instead of below it. calc() here
     matches .top's own total height exactly (60px content + whatever
     safe-area-inset-top adds on a notched device, 0 on anything else). */
  margin-top: calc(60px + env(safe-area-inset-top));
}

/* .sidebar is a plain element at PC widths and a real popover
   (popover="manual", nav-drawer.js: .showPopover()/.hidePopover()) at
   Tablet/Mobile -- not a <dialog>, and the `popover` ATTRIBUTE ITSELF
   is added/removed by nav-drawer.js depending on breakpoint, not just
   toggled open/closed. That's a harder requirement than it sounds:
   ANY shown popover, any type, is forced into the top layer's own
   out-of-flow rendering model -- `position` there isn't a normal,
   CSS-cascade-beatable value (confirmed the hard way: neither a bare
   `.sidebar` selector NOR `.sidebar[popover]` at (0,0,2,0) NOR
   `!important` could make computed `position` anything other than
   `absolute` while genuinely shown -- a local Playwright check kept
   showing PC's sidebar at `top:0, height:900`, matching a fixed/
   top-layer box, no matter which selector/specificity was tried). A
   <dialog> has a genuine non-top-layer mode (.show(), modeless) that a
   plain `position:static` override could win back into being a real
   flex item; a popover has no equivalent -- every `showPopover()` call
   promotes to the top layer, unconditionally. Since PC's sidebar has to
   be a REAL flex item (always visible, stretching to .main's height,
   participating normally in .app-shell's layout), it can't be a shown
   popover there at all -- nav-drawer.js removes the `popover` attribute
   entirely at PC widths (making this element to CSS a completely
   ordinary always-visible div, no top-layer anything) and only adds it
   back at Tablet/Mobile, where the top-layer/overlay behavior is
   exactly what's wanted anyway. */
.sidebar {
  box-sizing: border-box;
  /* stretch (not flex-start) so header/item rows fill the sidebar's
     cross-axis width by default -- the margin-based inset (see
     .nav-category-header/.nav-item-link's own notes) then relies on the
     browser subtracting left/right margin from that stretched width to
     land on the pill's real size, rather than the row shrink-wrapping to
     its own text/icon content. */
  align-items: stretch;
  width: 220px;
  flex: none;
  /* Top padding replaces the removed .sidebar-logo block's own spacing --
     the Top bar's own logo (top.css's .top-logo) is the app's one
     authoritative logo now; showing it a second time at the top of the
     drawer was a duplicate (caught from a real drawer screenshot). */
  padding: 0;
  padding-top: 16px;
  /* Outer padding is intentionally limited to the 16px top inset. */
  gap: 4px;
  background: var(--color-nav-bg);
  border-right: 1px solid var(--color-border-faint);
}

/* PC: no `popover` attribute present at all (see the file-level note
   above), so none of the UA's popover chrome (position/border/margin/
   max-width/max-height/display:none-until-shown) ever applies in the
   first place -- this is a genuinely ordinary, always-visible flex
   child, same as the original pre-<dialog>, pre-popover <aside> this
   component started as. */
.sidebar:not([popover]) {
  display: flex;
  flex-direction: column;
}

/* Tablet/Mobile: `popover` IS present here, so its UA chrome does need
   resetting -- border/max-width/max-height, functionally the same set
   <dialog> needed resetting too. display:flex scoped to :popover-open
   specifically, not unconditional -- an unconditional rule would tie
   with the UA's own `[popover]:not(:popover-open) { display: none }`,
   and that kind of tie is confirmed (the hard way, fighting <dialog>'s
   equivalent rule, DEV_LOG 2026-07-21/22) to go the UA's way in WebKit
   regardless of author-stylesheet order -- scoping to :popover-open
   sidesteps the tie entirely (the two states are mutually exclusive by
   construction, so there's never an actual conflict to resolve). */
.sidebar[popover] {
  border: none;
  max-width: none;
  max-height: none;
  overflow: visible;
}

.sidebar[popover]:popover-open {
  display: flex;
  flex-direction: column;
}

/* Pairs with the autofocus attribute (base.html) -- same as modal.css's
   own dialog.modal:focus rule, without it WebKit draws a default blue
   focus ring around the entire dialog box the instant it opens. */
.sidebar:focus {
  outline: none;
}

.nav-category {
  width: 100%;
  box-sizing: border-box;
}

.nav-category-header {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  /* 8px horizontal / 4px vertical inset from the sidebar's own edge --
     Portal_Navigation.png's own dev-mode measurement on its "navi_text"
     example (a 220x40 total slot, the Hover/Selected pill itself 204x32,
     i.e. NOT filling the slot edge-to-edge). Done via margin, not padding
     + background-clip: content-box -- that first attempt technically
     inset the *paint*, but border-radius doesn't reliably re-derive an
     adjusted inner curve for a content-box clip across browsers, so the
     pill's corners came out looking flattened/"clipped" instead of
     rounded to match its siblings (caught from a real screenshot, see
     DEV_LOG 2026-07-21, later entry). Margin avoids the problem entirely
     -- this element's own (now smaller) border-box is what gets the
     background and border-radius both, no mismatch possible. Width is
     therefore `auto` (shrunk by the margin) rather than 100%. */
  width: auto;
  margin: 4px 8px;
  padding: 4px 8px;
  gap: 12px;
  min-height: 32px;
  border: none;
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text-muted);
  font-family: var(--font-family);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: var(--letter-spacing-tight);
  text-align: left;
  cursor: pointer;
}

@media (hover: hover) {
  .nav-category-header:hover {
    background-color: var(--color-surface-muted);
  }
}

.nav-category-header .icon {
  color: var(--color-text-muted);
}

.nav-category-header .category-label {
  flex: 1;
}

.nav-category-chevron {
  flex: none;
  color: var(--color-icon-chevron);
  transition: transform 0.15s ease;
}

.nav-category.collapsed .nav-children {
  display: none;
}

.nav-category.collapsed .nav-category-chevron {
  transform: rotate(-90deg);
}

.nav-children {
  display: flex;
  flex-direction: column;
}

.nav-item-link {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  /* Margin, not padding + background-clip -- see .nav-category-header's
     own note above for why. Right/vertical match that same 8px/4px
     inset; left stays a larger 40px so an indented child still reads as
     nested under its parent category header. */
  width: auto;
  margin: 4px 8px 4px 40px;
  padding: 4px 8px;
  min-height: 32px;
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--color-text-muted);
  font-family: var(--font-family);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: var(--letter-spacing-tight);
}

/* Same reasoning as table.css's button.sort-link -- every existing
   .nav-item-link is an <a> (real page navigation), which has no default
   browser chrome to fight. .nav-training-mobile-only is the first plain
   <button> to use this class (교육신청 triggers a popup, not a page nav)
   -- without this reset it kept the browser's own default button border/
   background, showing as a visible outlined box around the label unlike
   every sibling link (caught from a real screenshot, see DEV_LOG
   2026-07-21, later entry). */
button.nav-item-link {
  border: none;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-align: left;
  cursor: pointer;
}

/* A top-level nav entry with no children of its own (Portal_Navigation.png's
   own real screens -- 패키지 현황/SDK 다운로드 -- render flat, icon+label at
   the same indentation as a .nav-category-header, no chevron, no nested
   .nav-children wrapper) -- distinct from a category's *child* link above,
   which sits indented under its own header. Carries an icon like a category
   header does, unlike a plain child link. */
.nav-item-link.nav-flat {
  /* Revised handoff: use the category-header inset and a 34px row. */
  margin: 4px 8px;
  height: 34px;
  min-height: 34px;
  padding: 4px 8px;
  gap: 12px;
}

.nav-item-link.nav-flat .icon {
  color: inherit;
}

.nav-item-link:active {
  background-color: var(--color-surface-muted);
  color: var(--color-text-secondary);
}

.nav-item-link.active {
  background-color: var(--color-surface-muted);
  color: var(--color-text-primary);
  font-weight: 700;
}

@media (hover: hover) {
  .nav-item-link:hover {
    background-color: var(--color-surface-muted);
    color: var(--color-text-secondary);
  }

  .nav-item-link.active:hover {
    color: var(--color-text-primary);
  }
}

/* Account name / logout / 교육신청, drawer-only -- see file-level note
   above. Hidden on PC *and Tablet*, where the Top bar itself still
   carries all three (base/top.css only drops them from Top at <=767px,
   not <=1199px) -- .sidebar-footer/.nav-training-mobile-only must use
   the SAME <=767px threshold, not the wider <=1199px the drawer itself
   opens at, or they render twice at once at Tablet (found from a real
   screenshot of the Tablet drawer that was captured but never actually
   reviewed at the time -- see DEV_LOG 2026-07-21, later entry). */
.sidebar-footer,
.nav-training-mobile-only {
  display: none;
}

/* Footer controls share the sidebar's light-surface Hover/Pressed state.
   The Mobile footer supplies the handoff's 8px 10px outer inset. */
.sidebar-footer-link {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
}

.sidebar-footer-link:active {
  background-color: var(--color-surface-muted);
}

@media (hover: hover) {
  .sidebar-footer-link:hover {
    background-color: var(--color-surface-muted);
  }
}

.sidebar-footer-logout {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: inherit;
  cursor: pointer;
}

.sidebar-footer-logout:active {
  background-color: var(--color-surface-muted);
}

@media (hover: hover) {
  .sidebar-footer-logout:hover {
    background-color: var(--color-surface-muted);
  }
}

@media (max-width: 1199px) {
  /* !important on position/inset/margin -- the UA stylesheet's own
     `[popover] { position: fixed; inset: 0; margin: auto; }` (an
     attribute selector, (0,0,1,0)) is at minimum tied with a plain
     `.sidebar` class selector ((0,0,1,0) too), and same-specificity ties
     are confirmed (the hard way, fighting the equivalent <dialog> rule,
     DEV_LOG 2026-07-21/22) to go the UA's way in WebKit regardless of
     author-stylesheet order -- without !important here the drawer fell
     back to the UA's own inset:0 + auto-margin centering-by-intrinsic-
     size instead of this app's real 220px-wide, full-height, left-
     anchored drawer (caught from a real screenshot when this was still
     a <dialog>: a small content-sized box floating near the top-left
     instead of the intended full-height panel) -- !important here is
     the deliberate, guaranteed fix rather than relying on matching the
     UA's exact specificity again.

     height: auto -- a second, separate UA default: the popover
     stylesheet sizes it to `fit-content` (shrink-wrap to its own
     content), not `auto`, same as <dialog>'s own equivalent default.
     `top` + `bottom` both set only stretches a box to fill that gap
     when `height` itself computes to `auto`, not `fit-content` -- so
     this needs to be forced back to `auto` explicitly, it's not
     redundant with the position/inset overrides above. */
  .sidebar {
    position: fixed !important;
    top: calc(60px + env(safe-area-inset-top)) !important;
    left: 0 !important;
    right: auto !important;
    height: auto !important;
    bottom: 0 !important;
    margin: 0 !important;
    z-index: 30;
    overflow-y: auto;
    transform: translateX(-100%);
    /* will-change: transform -- a reasonable hint regardless (this
       property is what actually animates on close/open), though it
       turned out NOT to be what fixed .sidebar-footer's own real
       stale-paint bug (gildong/로그아웃 flashing back into view after the
       rest of the drawer had already closed) -- that one needed a more
       direct fix, force-hiding the footer via JS the instant closing
       starts (nav-drawer.js's closeDrawerAnimated). See that function's
       own comment for the full diagnosis, including a real screen
       recording that caught the actual bug after this alone didn't fix
       it. */
    will-change: transform;
    box-shadow: 4px 0 16px rgba(0, 0, 0, 0.1);
    /* display/overlay: allow-discrete + @starting-style below fixes the
       OPEN side of the slide animation (confirmed via a local Playwright
       check: transform interpolates smoothly from ~-100% to 0 over the
       full 0.2s). showPopover() makes :popover-open true synchronously,
       so without @starting-style's explicit "before" frame, the very
       first paint already has transform:translateX(0) applied, with
       nothing for the transition to animate from. CLOSE, however, is
       handled entirely differently, in JS (popup.js's shared
       window.animatedCloseDialog) -- the same allow-discrete mechanism
       should in principle animate the reverse too, but a real check on
       WebKit showed an instant snap shut, zero interpolation, so
       `.closing` below is what actually drives the close animation for
       real, not this. */
    transition:
      transform 0.2s ease,
      display 0.2s allow-discrete,
      overlay 0.2s allow-discrete;
  }

  /* :popover-open -- true only while genuinely shown (nav-drawer.js's
     own showPopover()/hidePopover() calls at these widths). */
  .sidebar:popover-open {
    transform: translateX(0);
  }

  @starting-style {
    .sidebar:popover-open {
      transform: translateX(-100%);
    }
  }

  /* Close animation -- popup.js's shared window.animatedCloseDialog adds
     this class, waits for the transition below to actually finish
     (transitionend, with a fallback timer), THEN calls the real
     .hidePopover(). Needs to outrank `.sidebar:popover-open` above
     ((0,0,2,0) -- class + pseudo-class) while still technically shown
     during that wait, hence three selectors chained here instead of
     two. */
  .sidebar:popover-open.closing {
    transform: translateX(-100%);
  }
}

/* .nav-training-mobile-only's own visibility threshold intentionally
   does NOT match .sidebar-footer's <=767px below -- see top.css's own
   <=249px note. 교육신청 stays in the Top bar through the whole Mobile
   tier (per the design's own Mobile PNG); only below the same
   pathologically-narrow <=249px does it drop from Top and need this
   drawer entry instead. */
@media (max-width: 249px) {
  .nav-training-mobile-only {
    display: flex;
  }
}

@media (max-width: 767px) {
  .sidebar-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    box-sizing: border-box;
    margin-top: auto;
    padding: 8px 10px;
    border-top: 1px solid var(--color-border-light);
    font-family: var(--font-family);
    font-size: 14px;
    letter-spacing: var(--letter-spacing-tight);
    color: var(--color-text-muted);
  }
}

/* Dimmed backdrop behind the open drawer -- .sidebar's own native
   ::backdrop (it's a real popover now, see the file-level note up top),
   not a separate .nav-backdrop div the way this used to work, and not
   the intermediate <dialog>-based version this went through either.
   Full history: a hand-rolled .nav-backdrop div went through two failed
   fix attempts (display none/block, then an opacity transition) for a
   real bug, iPhone 16 Pro only, never reproduced on any Simulator:
   mobile Safari's own bottom toolbar chrome tints dark when the drawer
   opens but never reliably tints back light on close. Root cause turned
   out to be architectural: native top-layer promotion (this app's
   modals already had it, via <dialog>) is what gets WebKit to correctly
   recompute that tint, which a plain div never participates in.
   Converting .sidebar to a real <dialog> (showModal()) inherited that
   fix -- but showModal() also made the Top bar's own buttons unclickable
   while the drawer was open (a real, separate regression: showModal()
   makes everything outside the dialog inert by spec). Converting again,
   this time to a real popover (popover="manual"), keeps the top-layer
   promotion (still fixes the toolbar tint) without that inert side
   effect -- popovers never block interaction with the rest of the page,
   by design, unlike a modal dialog.

   top offset matches .sidebar's own calc() (safe-area-aware Top bar
   height) so the dim starts below the topbar, not over it -- per the
   guide's own "누른 후 (하단 딤처리 / navi가 content 위로 나옴)"
   annotation, the topbar (and its hamburger button) stays visible/
   clickable above the dim -- now genuinely clickable too, not just
   visually above it. ::backdrop covers the full viewport by default
   (UA `position:fixed; inset:0`) regardless of top-layer stacking, so
   this still has to be set explicitly the same as before. */
/* No opacity transition here -- tried once, reverted. The dim needs to
   reach its real, final rgba(27, 27, 27, 0.6) the *instant* the drawer
   opens, not fade up to it: mobile Safari's own bottom-toolbar tint
   sampling (see the note above for the full history) turned out to
   sample early, catching the backdrop still mid-fade/near-transparent,
   and never tinted the toolbar at all while the drawer was open -- a
   real regression caught live on a Simulator right after adding this
   fade. `.sidebar` itself sliding in via `transform` was never the
   problem (that animation predates both the <dialog> and popover
   conversions, back when this was a plain div) -- only this backdrop's
   opacity was ever new, so only this reverts. */
.sidebar::backdrop {
  position: fixed;
  top: calc(60px + env(safe-area-inset-top));
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(27, 27, 27, 0.6);
}
