/*
 * 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 time-scrubber panel on narrow viewports.
 *
 * Bug: at 390×844 the scrubber panel is centred, 92vw wide, max-w 860px,
 * sitting at bottom-4 with ~95px height — so its right edge reaches
 * x≈367 and its top sits at y≈733. MapLibre's bottom-right ctrl group
 * defaults to bottom:0 with ~10px margin, putting the 60px-tall zoom
 * pill at y≈775–834. The scrubber sits over the top half of the zoom
 * group and intercepts pointer events, so mobile users can't tap +/-
 * at all (filed in this iteration's backlog).
 *
 * Fix: only when the scrubber actually overlaps horizontally (below the
 * `lg` breakpoint at 1024px — desktop scrubber tops out at 860px wide so
 * the zoom group already clears it), push the bottom-right ctrl stack
 * up clear of the scrubber. The scrubber is bottom-4 (16px) and ~107px
 * tall, so its top edge sits at y = vh - 123px. The 136px zoom group
 * therefore needs bottom >= 123 + a small gap; 128px gives a 5px clear
 * margin. (Iter 8 originally set 120px which left a ~3px corner overlap
 * — the rounded scrubber top-right punched through the zoom pill's
 * bottom-right.) Targets the MapLibre container, not individual buttons,
 * so attribution moves with it and the layout stays in one piece. */
@media (max-width: 1023.98px) {
  [data-controller~="fishing-map-explore"] .maplibregl-ctrl-bottom-right {
    bottom: 128px;
  }
}

/* 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; }
}

/* 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;
}

/* Wind forecast scrubber — dark Windy-style range slider. Browser-default
 * blue-thumb-on-grey-track clashes with the dark cards; replacing with a
 * thin track + accent-coloured thumb reads as part of the chrome. */
.fishing-map-scrubber {
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  border-radius: 2px;
  background: rgba(220, 230, 240, 0.18);
  outline: none;
  cursor: pointer;
}
.fishing-map-scrubber:focus-visible {
  box-shadow: 0 0 0 2px rgba(255, 87, 34, 0.4);
}
.fishing-map-scrubber::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #ff5722;
  border: 2px solid rgba(13, 21, 33, 0.85);
  box-shadow: 0 0 6px rgba(255, 87, 34, 0.5);
  cursor: grab;
}
.fishing-map-scrubber::-webkit-slider-thumb:active {
  cursor: grabbing;
  transform: scale(1.1);
}
.fishing-map-scrubber::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #ff5722;
  border: 2px solid rgba(13, 21, 33, 0.85);
  box-shadow: 0 0 6px rgba(255, 87, 34, 0.5);
  cursor: grab;
}
.fishing-map-scrubber::-moz-range-track {
  background: rgba(220, 230, 240, 0.18);
  height: 4px;
  border-radius: 2px;
}

/* Coarse-pointer (touch) devices get a larger thumb so it's easier to
 * grab and drag with a fingertip. Track stays the same height so the
 * visible "filled" portion doesn't change scale. */
@media (pointer: coarse) {
  .fishing-map-scrubber::-webkit-slider-thumb {
    width: 22px;
    height: 22px;
  }
  .fishing-map-scrubber::-moz-range-thumb {
    width: 22px;
    height: 22px;
  }
}

/* Play button "active/playing" state. Tailwind's aria-pressed variant
 * loses to bg-squid in the source-order cascade for v4 utilities, so
 * scope it as plain CSS with attribute specificity. */
.fishing-map-play-button[aria-pressed="true"] {
  background-color: rgba(255, 87, 34, 0.15) !important;
  border-color: #ff5722 !important;
  color: #ff5722 !important;
}

/* 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;
  }
}

/* The play button carries Tailwind's `transition-colors` utility for its
 * hover state, and v4 includes `outline-color` in that transition list.
 * Result: pressing Tab onto the play button shows the focus ring fading
 * from the inherited sandbar colour up to accent orange over 150 ms —
 * out of step with every other rail control (which snaps instantly).
 * Drop outline-color from the transition so the orange focus ring lands
 * immediately, while preserving the hover colour transitions. */
.fishing-map-play-button {
  transition-property: color, background-color, border-color;
}
@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) the now-card + mobile wind legend at
 * bottom-[8.5rem] collide with the scrubber card. Auxiliary info (the
 * sampled wind at map centre + the legend strip) is hidden so the primary
 * controls — drawer trigger, layer rail, scrubber + play button — stay
 * reachable without overlap. The legend remains accessible via the wind
 * heat-field tooltip; the now-card re-emerges as soon as the viewport is
 * tall enough to fit both.
 *
 * 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;
  }
}

/* Same WCAG 1.4.4 fix at md..xl widths (769–1535px) when the viewport is
 * short (e.g. small-laptop landscape, or a tablet zoomed to 200% in that
 * range). Iter 23 lifts the desktop legend to bottom-[8.5rem] for md..xl
 * but at <500h the legend's bottom (~h-134px) sits within ~13px of the
 * scrubber's top *and* its right edge crosses into the scrubber's left
 * edge (the scrubber is centred, ~92vw wide). Hide it here too —
 * symmetry with the ≤768px rule above. The wind tooltip still surfaces
 * the colour scale when the user hovers a cell. */
@media (max-height: 500px) and (min-width: 769px) and (max-width: 1535px) {
  aside[aria-label="Wind speed legend"] {
    display: none;
  }
}

/* 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;
  }
}
