/*
 * 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.
 */

/* maplibre-gl.css is auto-included as its own <link> by
 * `stylesheet_link_tag :app` (Rails 8 globs every top-level CSS file in
 * app/assets/stylesheets/). A plain `@import "vendor/maplibre-gl.css"`
 * used to live here, but Propshaft fingerprints the underlying file
 * without rewriting plain CSS `@import` URLs — so browsers fetched the
 * un-fingerprinted path and logged a 404 on every map page load. The
 * explicit link tag already loads the styles; no import needed. */

/* /map page: dim MapLibre's default white-on-black attribution bar so it
 * sits quietly under the heat field instead of punching a bright box
 * over it. Matches the now-card / scrubber translucent-dark aesthetic.
 *
 * Specificity note: vendor maplibre-gl.css ships
 *   `.maplibregl-ctrl.maplibregl-ctrl-attrib { background-color: rgba(255,255,255,.5); }`
 *   `.maplibregl-ctrl-attrib a { color: rgba(0,0,0,.75); }`
 * and is loaded AFTER application.css via Rails 8's stylesheet manifest,
 * so single-class selectors here lose the cascade. We chain
 * `.maplibregl-ctrl.maplibregl-ctrl-attrib` (two classes) plus the
 * controller attribute to land at specificity 0,2,1 — beating vendor's
 * 0,2,0 and also fixing the `a` rule that vendor's source-order tie was
 * winning. */
[data-controller~="fishing-map-explore"] .maplibregl-ctrl.maplibregl-ctrl-attrib {
  background: rgba(13, 21, 33, 0.55);
  color: rgba(220, 230, 240, 0.7);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

[data-controller~="fishing-map-explore"] .maplibregl-ctrl.maplibregl-ctrl-attrib a {
  color: rgba(220, 230, 240, 0.85);
}

[data-controller~="fishing-map-explore"] .maplibregl-ctrl.maplibregl-ctrl-attrib .maplibregl-ctrl-attrib-button {
  background-color: rgba(13, 21, 33, 0.55);
}

/* Zoom +/- buttons on /map: dark translucent to match the chrome cards
 * rather than the bright-white default. Inverts the icon colour so the
 * +/- glyphs read against the dark background. */
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-group {
  background: rgba(13, 21, 33, 0.85);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border: 1px solid rgba(220, 230, 240, 0.12);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-group button {
  filter: invert(1) hue-rotate(180deg);
  background-color: transparent;
  /* Touch-target bump. Vendor maplibre-gl.css ships fixed 29x29 buttons
   * — well below the 44x44 WCAG / Apple HIG / Material guideline and
   * cramped to mis-tap on mobile. Bump to 40x40 (Windy's zoom group is
   * ~36 each, so this lands just above their target while staying
   * compact). The 29x29 +/- glyph is a background-image at
   * `background-position: 50%` with no `background-size`, so it stays
   * pixel-perfectly centred inside the larger button — no icon scaling
   * needed. Chained-class specificity (0,2,1 with the controller attr)
   * beats vendor's single-class `.maplibregl-ctrl-group button` rule. */
  width: 40px;
  height: 40px;
}
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-group button:hover {
  background-color: rgba(255, 87, 34, 0.15);
}
/* Suppress the vendor blue `box-shadow: 0 0 2px 2px #0096ff` halo on
 * the +/- zoom buttons. Vendor maplibre-gl.css ships three layered
 * rules:
 *   `button:focus`               -> blue shadow
 *   `button:focus:focus-visible` -> blue shadow
 *   `button:focus:not(:focus-visible)` -> none
 * On mouse-click, between focus and the browser's :focus-visible
 * evaluation, only `:focus` matches — so the blue ring flashes briefly
 * before vendor's `:not(:focus-visible)` rule strips it. Cover BOTH
 * the plain :focus and the :focus:focus-visible variant so the only
 * ring on this control is our accent-orange outline (already in place
 * via the `[data-controller~="fishing-map-explore"] button:focus-visible`
 * rule below). Specificity: 0,2,2 — beats vendor's 0,1,2/0,1,3 thanks
 * to the controller attribute selector. Finishes the focus-consistency
 * thread started in iter 31/32 — canvas, attribution, and zoom controls
 * now all share one accent-orange `:focus-visible` ring. */
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-group button:focus,
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-group button:focus:focus-visible {
  box-shadow: none;
}
/* Vendor maplibre-gl.css separates stacked control buttons with
 *   `button + button { border-top: 1px solid #ddd; }`.
 * Against our dark translucent group bg that hairline reads as bright
 * white and breaks the "single rounded pill" look (cf. Windy's
 * .rhbottom__map-tools zoom group, which has no visible divider at
 * all). Drop it back to the same subtle on-dark line we use for the
 * group's own border. The invert filter on the buttons would flip a
 * dark divider to light, so we set the colour pre-invert — i.e. light
 * → dark after invert. Specificity matches the chained group selector
 * above so we beat vendor's source-order tie. */
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-group button + button {
  border-top-color: rgba(35, 25, 15, 0.12);
}

/* Lift the bottom-right MapLibre controls (zoom +/-, attribution) above
 * the full-width bottom timebar at every width — the bar owns the bottom
 * edge now (inset-x-0 bottom-0), so the default bottom:0 ctrl stack would
 * sit underneath it and lose its pointer events. Offsets = bar height + a
 * small gap: the bar is one ~64px row on ≥md, plus the ~26px mobile
 * legend row below md. */
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-bottom-right {
  bottom: 74px;
}
@media (max-width: 767.98px) {
  [data-controller~="fishing-map-explore"] .maplibregl-ctrl-bottom-right {
    bottom: 100px;
  }
}

/* Loading-hint pulse — keeps the page from looking frozen when the
 * basemap takes more than a beat to paint. Fades the text between 30%
 * and 70% opacity over 1.4s. */
.fishing-map-loading {
  animation: fishing-map-loading-pulse 1.4s ease-in-out infinite;
}
@keyframes fishing-map-loading-pulse {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 0.7; }
}
@media (prefers-reduced-motion: reduce) {
  .fishing-map-loading { animation: none; opacity: 0.5; }
}

/* Hide the default disclosure triangle on the "Guide" panel summary.
 * Tailwind's `list-none` (list-style: none) drops the marker in
 * Chromium/Firefox, but Safari/iOS render it via this pseudo-element
 * which list-style doesn't reach — scope the rule to the disclosure
 * so we don't strip markers from any other <summary>. */
details[data-controller="map-disclosure"] > summary::-webkit-details-marker {
  display: none;
}

/* Spot-pin name tooltip on hover (desktop). Sits above the cursor with
 * a translucent dark backdrop matching the chrome cards. Hidden by
 * default; the layer toggles data-visible on hover. Pointer-events
 * disabled so the tooltip itself never steals the hover from the pin
 * underneath, which would cause an mouseleave/mouseenter flicker. */
.fishing-map-spot-name-tip {
  position: absolute;
  pointer-events: none;
  z-index: 30;
  padding: 2px 8px;
  background: rgba(13, 21, 33, 0.85);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border: 1px solid rgba(220, 230, 240, 0.12);
  color: rgba(220, 230, 240, 0.92);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
  font-size: 0.7rem;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 120ms ease-out, transform 120ms ease-out;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.fishing-map-spot-name-tip[data-visible="true"] {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .fishing-map-spot-name-tip { transition: none; transform: none; }
}

/* Status banner self-hides when its text content is empty so the
 * background box doesn't sit there as a ghost rectangle when there's
 * nothing to announce. The Stimulus controller toggles [hidden] in
 * _setStatus; we keep the element in flow with opacity so the banner
 * fades in/out cleanly.
 *
 * Cascade NB (see windTip/nowCard rule below): Tailwind v4 preflight
 * ships `[hidden]:where(:not([hidden=until-found])) { display: none
 * !important }` inside `@layer base`. Plain `display: revert` (unlayered,
 * no !important) loses to that, so the element drops out of the render
 * tree and the 160ms opacity fade never runs — "Loading forecast…"
 * popped abruptly instead of fading out when the scrubber fetch
 * resolved. Land the override in the same `@layer base` with !important
 * to win on layer-reversed-!important cascade. */
.fishing-map-status {
  transition: opacity 160ms ease-out;
}
@layer base {
  .fishing-map-status[hidden] {
    display: block !important;
    opacity: 0;
    pointer-events: none;
  }
}
.fishing-map-status:not([hidden]) {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .fishing-map-status { transition: none; }
}

/* Applied by `_onMapError` to interactive chrome (play button, scrubber,
 * wind toggle row, MapLibre zoom controls) when the basemap fails to load.
 * Those controls' wiring lives inside `map.on("load")`, which never fires
 * on a failed style fetch — so they appear interactive but are dead. The
 * dim + not-allowed cursor + pointer-events:none combination prevents
 * clicks (belt + braces alongside the real `disabled` attribute, since
 * the wind toggle is wrapped in a <label> that would otherwise still
 * forward clicks to its child input). Selectors not used in Tailwind
 * utility form because this class lives in a plain stylesheet. */
.fishing-map-disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Basemap-failure dim for the timebar shell. Softer than
 * .fishing-map-disabled and without pointer-events:none — the play
 * button / scrubber inside it already carry the real disabled state
 * (tests pin that flow); this just makes the bar's passive furniture (day
 * track, legend) read offline with them. */
.fishing-map-timebar--offline {
  opacity: 0.65;
}

/* Wind forecast scrubber — the native range input stretched invisibly
 * over the whole timebar strip. It keeps everything a native slider
 * gives us for free (keyboard stepping, aria-valuetext, focusability,
 * the disabled flow the basemap-error tests pin) while the visible
 * rendering — day cells, hour ticks, progress fill, cursor line, time
 * bubble — is painted by pointer-events-none layers in the same strip.
 * Clicking anywhere on the strip therefore jumps the value there and
 * starts a drag, exactly like Windy's timebar. */
.fishing-map-scrubber {
  -webkit-appearance: none;
  appearance: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background: transparent;
  outline: none;
  cursor: ew-resize;
}
.fishing-map-scrubber::-webkit-slider-runnable-track {
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  border: 0;
  height: 100%;
}
.fishing-map-scrubber::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 100%;
  background: transparent;
  border: 0;
  box-shadow: none;
  cursor: ew-resize;
}
.fishing-map-scrubber::-moz-range-track {
  background: transparent;
  border: 0;
  height: 100%;
}
.fishing-map-scrubber::-moz-range-thumb {
  width: 18px;
  height: 100%;
  border-radius: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
  cursor: ew-resize;
}

/* === Timebar visual layers (all pointer-events-none, painted under the
 * transparent native input above) === */

/* Cursor — the 2px accent line marking the scrubbed hour in the strip. */
.fishing-map-timebar-cursor {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #ff5722;
  box-shadow: 0 0 8px rgba(255, 87, 34, 0.55);
  transform: translateX(-50%);
  pointer-events: none;
}

/* Time bubble — rides the cursor above the strip, Windy's amber pill
 * adapted to the accent token. The ::after triangle is the stem. The
 * controller clamps style.left so the pill never spills past the strip
 * edges; when that clamp engages, --stem-offset (set by
 * _positionScrubberVisuals, itself clamped to the pill's width minus
 * the rounded corners) slides the stem along the pill so it keeps
 * pointing at the accent cursor line instead of detaching from it. */
.fishing-map-timebar-bubble {
  position: absolute;
  bottom: calc(100% + 7px);
  transform: translateX(-50%);
  padding: 3px 10px 4px;
  border-radius: 3px;
  background: #ff5722;
  color: #16202e;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 2px 12px rgba(255, 87, 34, 0.45), 0 2px 6px rgba(0, 0, 0, 0.3);
}
.fishing-map-timebar-bubble::after {
  content: "";
  position: absolute;
  top: 100%;
  left: calc(50% + var(--stem-offset, 0px));
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: #ff5722;
  border-bottom-width: 0;
}

/* Day cells — one per local calendar day across the forecast span,
 * rendered by _renderDayTrack. Hairline left borders mark midnights. */
.fishing-map-timebar-day {
  position: absolute;
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.fishing-map-timebar-day--bordered {
  border-left: 1px solid rgba(196, 165, 123, 0.16);
}
.fishing-map-timebar-day-label {
  font-family: 'JetBrains Mono', 'Courier New', monospace;
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(196, 165, 123, 0.7);
}
.fishing-map-timebar-day-label.is-today {
  color: #ff5722;
}
.fishing-map-timebar-day-label.is-weekend {
  color: var(--color-signal, #d9a44a);
}

/* Hovered day cell — set by the controller's ghost handler (the
 * transparent range input on top of the strip swallows pointer events,
 * so a CSS :hover on the cells can never fire). Brightens the CELL wash;
 * the label brighten is scoped via :not() to plain labels only, so the
 * is-today / is-weekend tints are never recoloured under hover. No
 * transition — the highlight snaps with the pointer, so there's nothing
 * to guard for prefers-reduced-motion. */
.fishing-map-timebar-day.is-hovered {
  background: rgba(196, 165, 123, 0.07);
}
.fishing-map-timebar-day.is-hovered
  .fishing-map-timebar-day-label:not(.is-today):not(.is-weekend) {
  color: rgba(232, 229, 223, 0.85);
}

/* Hour ticks along the strip's bottom edge — one per available forecast
 * hour, instrument-panel texture. */
.fishing-map-timebar-tick {
  position: absolute;
  bottom: 0;
  width: 1px;
  height: 5px;
  background: rgba(196, 165, 123, 0.28);
  transform: translateX(-50%);
}

/* Hover time-preview ghost — faint line + hour pill following a fine
 * pointer across the strip (created lazily by the controller; never
 * rendered on touch devices). Quieter than the accent bubble so the two
 * read as "preview" vs "selected". */
.fishing-map-timebar-ghost {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: rgba(232, 229, 223, 0.35);
  transform: translateX(-50%);
  pointer-events: none;
}
.fishing-map-timebar-ghost-label {
  position: absolute;
  bottom: calc(100% + 9px);
  left: 0;
  transform: translateX(-50%);
  font-family: 'JetBrains Mono', 'Courier New', monospace;
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  white-space: nowrap;
  color: rgba(232, 229, 223, 0.92);
  background: rgba(13, 21, 33, 0.92);
  border: 1px solid rgba(196, 165, 123, 0.25);
  border-radius: 3px;
  padding: 2px 7px;
}

/* Now marker — thin signal-gold line at wall-clock now. Gold = now,
 * accent orange = where you've scrubbed to; the two read independently
 * when they separate. */
.fishing-map-timebar-now {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 2px;
  background: var(--color-signal, #d9a44a);
  box-shadow: 0 0 6px rgba(217, 164, 74, 0.5);
  transform: translateX(-50%);
  opacity: 0.9;
}

/* While the forecast animates (controller sets data-playing on the bar),
 * glide the bubble/cursor/fill between hourly steps instead of teleporting.
 * Drag updates are continuous so they never enter this state. */
.fishing-map-timebar[data-playing="true"] .fishing-map-timebar-bubble,
.fishing-map-timebar[data-playing="true"] .fishing-map-timebar-cursor {
  transition: left 450ms linear;
}
.fishing-map-timebar[data-playing="true"] [data-fishing-map-explore-target="scrubberFill"] {
  transition: width 450ms linear;
}
@media (prefers-reduced-motion: reduce) {
  .fishing-map-timebar[data-playing="true"] .fishing-map-timebar-bubble,
  .fishing-map-timebar[data-playing="true"] .fishing-map-timebar-cursor,
  .fishing-map-timebar[data-playing="true"] [data-fishing-map-explore-target="scrubberFill"] {
    transition: none;
  }
}

/* Play button "active/playing" state — deepen the accent fill and pull
 * the glow in so the pressed state reads at a glance. Plain CSS with
 * attribute specificity because Tailwind's aria-pressed variant loses
 * to bg-accent in the v4 source-order cascade. */
.fishing-map-play-button[aria-pressed="true"] {
  background-color: #e64a19 !important;
  box-shadow: 0 1px 6px rgba(255, 87, 34, 0.5), inset 0 1px 3px rgba(0, 0, 0, 0.25) !important;
}
/* Hover/active feedback (mouse only — :not(:disabled) keeps the dimmed
 * offline state inert). */
.fishing-map-play-button:hover:not(:disabled) {
  background-color: #e64a19;
  transform: scale(1.05);
}
.fishing-map-play-button:active:not(:disabled) {
  transform: scale(0.96);
}
@media (prefers-reduced-motion: reduce) {
  .fishing-map-play-button:hover:not(:disabled),
  .fishing-map-play-button:active:not(:disabled) {
    transform: none;
  }
}

/* Keyboard focus rings on /map controls. Browser-default 1px sandbar
 * outline barely registers against the dark cards — switching to a
 * 2px accent ring with offset so it stands clear of the button's own
 * border. Only :focus-visible (not :focus) so mouse-clicks don't
 * leave the ring stuck on. */
[data-controller~="fishing-map-explore"] button:focus-visible,
[data-controller~="fishing-map-explore"] label:focus-within,
[data-controller~="fishing-map-explore"] .fishing-map-scrubber:focus-visible {
  outline: 2px solid #ff5722 !important;
  outline-offset: 2px !important;
}

/* MapLibre's <canvas class="maplibregl-canvas" tabindex="0"> is the
 * first focusable element on /map — MapKeyboardHandler needs the
 * tabindex so +/-/arrow keys can pan & zoom. The vendor stylesheet
 * leaves Chrome's default 1px UA outline (rgb(11,87,208) auto 1px) in
 * place, which is barely visible against the dark slate basemap — a
 * WCAG 2.4.7 focus-visibility failure on the page's primary surface.
 *
 * Match the rest of /map's chrome (2px solid accent orange) but use a
 * negative outline-offset so the ring sits INSIDE the canvas edges
 * instead of being clipped by the viewport — the canvas is sized to
 * fill the map container, so a positive offset would render off-screen
 * on most edges. -3px keeps the ring fully visible and clear of the
 * canvas border. */
[data-controller~="fishing-map-explore"] .maplibregl-canvas:focus-visible {
  outline: 2px solid #ff5722 !important;
  outline-offset: -3px !important;
}

/* Attribution toggle (the small "i" details/summary in the bottom-right)
 * focus ring. Vendor maplibre-gl.css ships
 *   `.maplibregl-ctrl-attrib-button:focus { box-shadow: 0 0 2px 2px #0096ff }`
 * — a bright blue 2px halo that fires on any focus (including
 * programmatic / mouse), and is inconsistent with the rest of /map's
 * chrome which uses accent orange via :focus-visible only. Bring it
 * in line with the canvas / zoom-button rule above.
 *
 * Two parts:
 *   1. Suppress the vendor blue box-shadow on plain :focus so mouse
 *      clicks don't leave a ring stuck on (matches the zoom-group
 *      treatment already in vendor, which the attribution rule was
 *      missing).
 *   2. Apply our 2px accent-orange outline on :focus-visible so
 *      keyboard users get a clear, on-brand ring.
 *
 * Specificity: chained controller-attr + class beats vendor's single
 * `.maplibregl-ctrl-attrib-button:focus` (0,2,0 vs 0,1,1). */
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-attrib summary:focus {
  box-shadow: none;
}
[data-controller~="fishing-map-explore"] .maplibregl-ctrl-attrib summary:focus-visible {
  outline: 2px solid #ff5722;
  outline-offset: 2px;
  border-radius: 12px;
}

/* Windows High Contrast Mode / forced-colors users have their system
 * theme remap author colours to system keywords. Our accent-orange
 * focus rings get remapped to a contrast-safe colour by the UA, but
 * vendor MapLibre only ships explicit forced-colors rules for
 * `.maplibregl-ctrl-group` — leaving the canvas, attribution toggle,
 * our own buttons, and the global share-sheet without a guaranteed
 * outline declaration when those rings are needed. Re-state the focus
 * outline using the `CanvasText` system colour so keyboard focus stays
 * unambiguous in HCM regardless of how the UA remaps author colours.
 *
 * `!important` mirrors the base /map focus-visible rule above — without
 * it the cascade tie goes to the older `!important` declaration and
 * the forced-colors override never takes effect. */
@media (forced-colors: active) {
  [data-controller~="fishing-map-explore"] .maplibregl-ctrl-attrib summary:focus-visible,
  [data-controller~="fishing-map-explore"] button:focus-visible,
  .share-sheet-panel :is(button, input, a):focus-visible {
    outline: 2px solid CanvasText !important;
    outline-offset: 2px !important;
  }
  /* Canvas fills the viewport — keep the negative offset so the ring
   * paints INSIDE the canvas edges (otherwise it's clipped off-screen,
   * see the base rule's comment above). */
  [data-controller~="fishing-map-explore"] .maplibregl-canvas:focus-visible {
    outline: 2px solid CanvasText !important;
    outline-offset: -3px !important;
  }
}

/* Play button transitions. Explicit property list (rather than Tailwind's
 * transition-colors / transition-all) for two reasons: v4's colour list
 * includes `outline-color`, which made the focus ring fade in over 150ms
 * instead of snapping like every other control; and we want transform +
 * box-shadow covered for the hover scale/glow without transitioning
 * layout properties. */
.fishing-map-play-button {
  transition-property: color, background-color, border-color, transform, box-shadow;
  transition-duration: 150ms;
  transition-timing-function: ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .fishing-map-play-button { transition: none; }
}

/* Wind-tip & now-card fade in/out instead of popping. The Stimulus
 * controller toggles the [hidden] attribute, so we keep the element in
 * layout and animate opacity. pointer-events: none while hidden so the
 * click-through passes to the canvas underneath.
 *
 * Cascade NB: Tailwind v4 preflight ships
 *   `[hidden]:where(:not([hidden=until-found])) { display: none !important }`
 * inside `@layer base`. For !important declarations the layer cascade
 * is REVERSED, so unlayered !important (and any later-layer !important)
 * still loses to that base-layer rule. We have to land our override in
 * the same `@layer base` with !important, then higher selector
 * specificity decides — `[data-…][hidden]` is 0,2,0 vs Tailwind's
 * `[hidden]:where(...)` at 0,1,0, so we win cleanly.
 * Without the override, `display: revert` resolved to the UA `[hidden]`
 * default of `display: none`, the element dropped out of the render
 * tree, and the opacity transition never ran — the now-card popped in
 * once data arrived, leaving the top-left slot blank under the drawer
 * trigger on slow loads. Keeping the chip in layout with opacity 0 and
 * the placeholder dashes ("— m/s", "—") gives it a skeleton state. */
[data-fishing-map-explore-target="windTip"],
[data-fishing-map-explore-target="nowCard"] {
  transition: opacity 160ms ease-out;
}
@layer base {
  [data-fishing-map-explore-target="windTip"][hidden],
  [data-fishing-map-explore-target="nowCard"][hidden] {
    display: block !important;
    opacity: 0;
    pointer-events: none;
  }
}
[data-fishing-map-explore-target="windTip"]:not([hidden]),
[data-fishing-map-explore-target="nowCard"]:not([hidden]) {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  [data-fishing-map-explore-target="windTip"],
  [data-fishing-map-explore-target="nowCard"] {
    transition: none;
  }
}

/* WCAG 1.4.4 — at very short viewports (mobile-landscape, or desktop zoomed
 * to ~200% on a small laptop) vertical space is the scarce resource.
 * Auxiliary info is shed: the now-card (top-left) and the mobile legend
 * row inside the timebar (its ~26px makes the bar fit at 500px heights).
 * The legend remains accessible via the wind heat-field tooltip; both
 * re-emerge as soon as the viewport is tall enough.
 *
 * The `!important` on the now-card overrides the [hidden] layer-base rule
 * above that intentionally keeps the card in layout at opacity 0. */
@media (max-height: 500px) and (max-width: 768px) {
  [data-fishing-map-explore-target="nowCard"] {
    display: none !important;
  }
  aside[aria-label="Wind speed legend"] {
    display: none;
  }
  /* The whole bar drops to single-row height — pull the controls and
   * guide chip down with it. */
  [data-controller~="fishing-map-explore"] .maplibregl-ctrl-bottom-right {
    bottom: 74px;
  }
}

/* Share sheet */
.share-sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(0, 0, 0, 0);
  transition: background 200ms ease;
  pointer-events: none;
}

.share-sheet-backdrop.open {
  background: rgba(0, 0, 0, 0.5);
  pointer-events: auto;
}

.share-sheet-panel {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 51;
  transition: transform 200ms ease;
  pointer-events: none;
  /* Mobile: slide up from bottom */
  bottom: 0;
  transform: translateY(100%);
}

.share-sheet-panel.open {
  transform: translateY(0);
  pointer-events: auto;
}

/* Desktop: center vertically instead of anchoring to bottom */
@media (min-width: 768px) {
  .share-sheet-panel {
    bottom: auto;
    top: 50%;
    transform: translateY(-50%) scale(0.95);
    opacity: 0;
    transition: transform 200ms ease, opacity 200ms ease;
  }

  .share-sheet-panel.open {
    transform: translateY(-50%) scale(1);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .share-sheet-backdrop,
  .share-sheet-panel {
    transition: none;
  }
}

/* Share-sheet focus rings — replace Chrome's default 1px blue with the
   site accent so keyboard nav matches the rest of /map (iter 30 thread).
   Scoped to the panel so the rule never bleeds into app chrome. */
.share-sheet-panel :is(button, input, a):focus-visible {
  outline: 2px solid #ff5722;
  outline-offset: 2px;
  box-shadow: none;
}

/* Article body — rendered markdown */
.article-body h1 {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: -0.03em;
  color: #e8e5df;
  margin-top: 0;
  margin-bottom: 1.25rem;
}

.article-body h2 {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-weight: 600;
  font-size: 1.15rem;
  color: #e8e5df;
  margin-top: 2rem;
  margin-bottom: 0.75rem;
}

.article-body h3 {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-weight: 600;
  font-size: 0.95rem;
  color: #e8e5df;
  margin-top: 1.5rem;
  margin-bottom: 0.5rem;
}

.article-body p {
  font-size: 0.9rem;
  line-height: 1.7;
  color: #c4a57b;
  margin-bottom: 1rem;
}

.article-body a {
  color: #ff5722;
  text-decoration: none;
}

.article-body a:hover {
  text-decoration: underline;
}

.article-body ul,
.article-body ol {
  font-size: 0.9rem;
  line-height: 1.7;
  color: #c4a57b;
  margin-bottom: 1rem;
  padding-left: 1.5rem;
}

.article-body li {
  margin-bottom: 0.25rem;
}

.article-body hr {
  border: none;
  border-top: 1px solid rgba(196, 165, 123, 0.12);
  margin: 2rem 0;
}

.article-body strong {
  color: #e8e5df;
  font-weight: 600;
}

/* Rig builder scene + card thumbnails (small for cross-links, larger for
 * /rigs index). All three wrap one inline SVG sized to its parent. */
.rig-scene { display: inline-block; width: 100%; max-width: 440px; }
.rig-card-preview { width: 72px; }
.rig-index-preview { width: 110px; }
.rig-scene > svg,
.rig-card-preview > svg,
.rig-index-preview > svg { display: block; width: 100%; height: auto; }

.rig-scene [data-component-type="hook"],
.rig-scene [data-component-type="bait"],
.rig-scene [data-component-type="leader"] {
  transform-origin: 50% 0%;
  animation: rig-sway 4s ease-in-out infinite;
}

@keyframes rig-sway {
  0%, 100% { transform: rotate(-0.5deg) translateX(-1px); }
  50%      { transform: rotate(0.5deg)  translateX(1px); }
}

.rig-scene [data-component-index][role="button"] { cursor: pointer; }

.rig-scene [data-component-index][role="button"]:hover {
  filter: drop-shadow(0 0 4px rgba(255, 200, 80, 0.45));
}

/* Only :focus-visible (not :focus) so mouse-clicks don't leave the
 * ring stuck on after the active-state animation finishes. Split
 * from the active-state rule below so the two paths can diverge. */
.rig-scene [data-component-index][tabindex]:focus-visible {
  outline: none;
  filter: drop-shadow(0 0 6px rgba(255, 200, 80, 0.85));
}

.rig-scene [data-component-index][data-active="true"] {
  outline: none;
  filter: drop-shadow(0 0 6px rgba(255, 200, 80, 0.85));
}

.rig-scene [data-component-index][data-active="true"]:not([data-component-type="hook"]):not([data-component-type="bait"]):not([data-component-type="leader"]) {
  transform: scale(1.08);
  transform-origin: 50% 50%;
  transition: transform 180ms ease-out;
}

.rig-scene[data-tap-hint="true"] [data-component-index][role="button"]:not([data-component-type="hook"]):not([data-component-type="bait"]):not([data-component-type="leader"]) {
  animation: rig-tap-hint 2.4s ease-in-out 3;
}

.rig-scene .rig-slot { cursor: pointer; opacity: 0.55; transition: opacity 160ms ease-out; }
.rig-scene .rig-slot:hover,
.rig-scene .rig-slot:focus-visible { opacity: 1; outline: none; }

[data-rig-builder-target="anatomyItem"] { transition: background 200ms ease-out, border-color 200ms ease-out; }
[data-rig-builder-target="anatomyItem"][data-active="true"] {
  border-left-color: rgba(255, 200, 80, 0.75) !important;
  background: rgba(255, 200, 80, 0.06);
}

@keyframes rig-tap-hint {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(255, 200, 80, 0)); }
  50%      { filter: drop-shadow(0 0 5px rgba(255, 200, 80, 0.55)); }
}

@media (prefers-reduced-motion: reduce) {
  .rig-scene [data-component-type] { animation: none !important; }
  .rig-scene[data-tap-hint="true"] [data-component-index][role="button"] { animation: none !important; }
  .rig-scene [data-component-index][data-active="true"],
  .rig-scene .rig-slot,
  [data-rig-builder-target="anatomyItem"] { transition: none !important; }
}

/* === /map print stylesheet ============================================
 * The /map page is a full-bleed WebGL canvas with floating chrome — neither
 * survives Ctrl+P intact. WebGL canvases typically render blank/black on
 * paper (the GPU buffer isn't part of the document's paint tree the print
 * pipeline reads), and the absolutely-positioned cards (menu pill, now
 * card, layer rail, legend, scrubber, attribution, drawer, share-sheet)
 * float over the empty rectangle as useless boxes if printed as-is.
 *
 * One coordinated rule: hide everything inside the fishing-map controller
 * container and replace it with a clean printed header (brand + "visit
 * the live map" message), so the printed page reads as a deliberate
 * fallback page instead of a broken screenshot. The site layout (header
 * rail, drawer, share-sheet, GA script) is positioned outside the
 * controller container, so we hide those siblings too. Zero markup
 * change — uses a ::before pseudo on the controller container itself.
 *
 * Scope: only triggers when the fishing-map controller is present, so
 * other pages keep their default browser print rendering. */
@media print {
  /* Reset the body and main wrapper to white-on-black ink defaults —
   * the screen `.bg-squid` (#1a2332) would otherwise burn the whole
   * page in dark ink, since most browsers honour `background-color`
   * in print when the user has "Background graphics" enabled. Scope
   * to body:has(...) so non-/map pages keep their default print
   * rendering. `:has()` is supported in all major print engines that
   * also support the rest of this block (Chromium 105+, Safari 15.4+,
   * Firefox 121+). */
  body:has([data-controller~="fishing-map-explore"]),
  body:has([data-controller~="fishing-map-explore"]) main {
    background: #fff !important;
    color: #000 !important;
  }
  /* Reset the controller container from 100vh to natural height and
   * remove its fixed sizing — print pages are paged, not viewport-sized.
   * Children get hidden via the descendant rule below; the container
   * itself stays visible to host the ::before fallback. */
  [data-controller~="fishing-map-explore"] {
    height: auto !important;
    min-height: 0 !important;
    background: #fff !important;
    color: #000 !important;
  }
  /* Inside @layer base because the iter-37 status-banner fix lives in
   * @layer base with `.fishing-map-status[hidden] { display: block
   * !important; }` (so the fade transition can run while [hidden] is
   * applied). For !important declarations the cascade reverses, so a
   * `base` layer rule beats an unlayered !important. Match the layer
   * to win against the iter-37 rule.
   *
   * Specificity NB: the iter-37 rule is (0,2,1) — class + attribute +
   * element. To beat it (or match + win on source order, which we do
   * since this block lives later in the file), I include `[hidden]`
   * variants explicitly. Without them, `[data-controller~="..."] > *`
   * is only (0,1,1) and loses to (0,2,1) regardless of layer. */
  @layer base {
    [data-controller~="fishing-map-explore"] > *,
    [data-controller~="fishing-map-explore"] > *[hidden] {
      display: none !important;
    }
  }
  /* Branded fallback header. Two-line layout: site name in heading
   * weight, then a one-line invitation back to the interactive page.
   * `attr(...)` would let us pull the URL dynamically but only Chromium
   * supports it for non-`content` properties; the explicit string is
   * future-proof across print engines. */
  [data-controller~="fishing-map-explore"]::before {
    content: "Bite Compass — Fishing map\A Visit bitecompass.com/map for the live, interactive forecast.";
    display: block;
    white-space: pre-line;
    padding: 24pt 0;
    font-family: ui-sans-serif, system-ui, sans-serif;
    font-size: 14pt;
    line-height: 1.6;
    color: #000;
    border-bottom: 1pt solid #000;
  }
  /* Hide the rest of the page chrome that lives outside the controller
   * (drawer rail is position:fixed off-canvas, share-sheet ditto, GA
   * script tags are display:none already but keep them out for clarity).
   * Scope to body > [data-controller~="drawer"] so we only fire this on
   * the /map fullscreen layout where the floating drawer is the wrapper. */
  body:has([data-controller~="fishing-map-explore"]) [data-controller~="drawer"] > :not(main) {
    display: none !important;
  }
  /* Tidy paper margins. Default Chromium prints with ~10mm margin and a
   * page-header/footer URL + date — leave those at user-agent defaults
   * so the user can toggle them in the print dialog. */
  @page {
    margin: 12mm;
  }
}
