/*
  Social-Lite — reusable component styles
  Reference implementation, draft format. Every class is namespaced
  with `sl-` so it can be dropped into any site without collisions.
  This file must not contain demo-site branding — see demo.css for that.
*/

:root {
  --sl-content-width: 72rem;

  /* Narrower than the old panel-based layout's 48rem — closer to a
     Kindle/Medium reading column than a wide dashboard-style panel. */
  --sl-story-width: 36rem;

  /* The feed's own column width — narrower still than --sl-story-width,
     closer to Instagram's ~30rem single-column feed than a wide
     article-reading measure. The feed is for scanning large media
     quickly, not for reading long lines of text, so it stays narrow on
     desktop and only grows to fill the viewport (minus page gutters) on
     small screens. See .sl-feed below. */
  --sl-feed-width: 30rem;

  /* The Story viewport's own radius — the one visual frame in the
     whole Story, deliberately more generous than --sl-slide-radius
     below, which is reserved for smaller accents elsewhere so the two
     don't compete for attention. */
  --sl-stage-radius: 1.75rem;

  /* Smaller accent radius, currently only used by feed-item
     thumbnails — never the Story viewport, and no longer media inside
     a slide either, which now fills its slide edge-to-edge instead. */
  --sl-slide-radius: 1.25rem;

  --sl-space-1: 0.5rem;
  --sl-space-2: 1rem;
  --sl-space-3: 1.5rem;
  --sl-space-4: 2rem;
  --sl-transition-duration: 180ms;

  /* Consumed by the overlay Previous/Next controls, whether they're
     built by assets/js/social-lite.js or by the <social-lite-story>
     Web Component (assets/js/social-lite-story.js) — both generate the
     same .sl-story-nav__previous/__next markup, so both get these for
     free. rem-based rather than the plain 44px this replaces, so the
     touch target scales with a visitor's preferred text size instead
     of staying fixed. See docs/web-component.md for the full list of
     styling hooks the Web Component documents. */
  --sl-control-size: 2.75rem;
  --sl-control-opacity-idle: 0.18;
  --sl-control-opacity-active: 1;

  /* No gap by default — slides sit flush against each other edge to
     edge (see .sl-story__slides below). Exists as an overridable token
     for sites that want visible separation between slides. */
  --sl-slide-gap: 0;

  --sl-color-text: #221f1a;
  --sl-color-muted: #5c5648;
  --sl-color-surface: #ffffff;
  --sl-color-surface-alt: #f4f1ea;
  --sl-color-border: #ddd5c3;
  --sl-color-accent: #b0472c;
  --sl-color-focus: #1857b8;

  /* The Story reader's own palette — deliberately always dark,
     independent of the visitor's system light/dark preference. See
     CLAUDE.md's "Story Reader" principle: opening a Story is a shift
     in intent, not just another page load, so the reader recedes into
     the same dark background regardless of what theme the system
     happens to be in. Used only by .sl-story and its descendants;
     .sl-feed-item and everything else still use the adaptive
     --sl-color-* tokens above — the feed is for discovery and stays a
     normal, theme-adaptive webpage. */
  --sl-reader-bg: #0f0e0c;
  --sl-reader-stage-bg: #1b1915;
  --sl-reader-text: #f3efe6;
  --sl-reader-muted: #9c937f;
  --sl-reader-border: rgba(255, 255, 255, 0.08);
  --sl-reader-accent: #ef9271;
  --sl-reader-focus: #8fbaff;

  --sl-font-system:
    system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --sl-color-text: #f1ede3;
    --sl-color-muted: #b9b09a;
    --sl-color-surface: #26231d;
    --sl-color-surface-alt: #1b1913;
    --sl-color-border: #4a4434;
    --sl-color-accent: #ef8a68;
    --sl-color-focus: #7fb0ff;
  }
}

/* -------------------------------------------------------------------- */
/* <social-lite-story> — Web Component host element                     */
/*                                                                      */
/* Light DOM: this element wraps a Story's existing .sl-story markup    */
/* rather than replacing it — see docs/web-component.md for why. An     */
/* undefined custom element defaults to display: inline, and             */
/* social-lite-story.js never sets its own display style, so this rule   */
/* is what keeps the block-level .sl-story article laid out correctly    */
/* even on a page that never loads the script at all.                    */
/* -------------------------------------------------------------------- */

social-lite-story {
  display: block;
}

/* A visible, consistent focus style for interactive elements outside
   the Story reader (currently just the feed, which keeps the adaptive
   --sl-color-* tokens — see .sl-story's rule below for why the reader
   itself needs a separate, non-adaptive one). */
.sl-feed-item a:focus-visible {
  outline: 3px solid var(--sl-color-focus);
  outline-offset: 3px;
}

/* The Story reader is always dark (see --sl-reader-* above), so its
   focus rings use --sl-reader-focus rather than --sl-color-focus —
   that token swaps based on the visitor's *system* theme, which has
   nothing to do with whether the Story itself is dark. Reusing it here
   would risk a dark-on-dark ring when the system is in light mode. */
.sl-story a:focus-visible,
.sl-story button:focus-visible,
.sl-story video:focus-visible {
  outline: 3px solid var(--sl-reader-focus);
  outline-offset: 3px;
}

/* The track's own focus ring needs a separate, inset rule: .sl-story__
   viewport clips with overflow: hidden, and the track fills that
   viewport edge to edge (see .sl-story__slides), so a ring drawn
   *outside* the track's border box the way every other control's ring
   is would be clipped away entirely — invisible to keyboard users. A
   negative offset draws it just inside the boundary instead. */
.sl-story__slides:focus-visible {
  outline: 3px solid var(--sl-reader-focus);
  outline-offset: -3px;
}

/* -------------------------------------------------------------------- */
/* Story                                                                 */
/*                                                                       */
/* Opening a Story is a shift in intent, not just another page load —   */
/* see CLAUDE.md's "Story Reader" principle. The whole component uses   */
/* its own always-dark palette (--sl-reader-*) regardless of the        */
/* visitor's system theme, and the surrounding page (see demo.css's     */
/* body.is-reader) recedes into the same dark background, so there's    */
/* no light-colored gutter around it breaking the effect.                */
/*                                                                       */
/* Still deliberately plain otherwise: no background, border, radius,   */
/* or shadow directly on .sl-story itself. .sl-story__viewport below is  */
/* the one framed object in a Story — title, description, metadata,     */
/* progress, and footer are just page content around it, not a second   */
/* panel it sits inside. "Page → Story → content," not "page → panel →  */
/* viewport → content."                                                  */
/* -------------------------------------------------------------------- */

.sl-story {
  max-width: var(--sl-story-width);
  margin-inline: auto;
  margin-block: clamp(1.5rem, 1rem + 2vw, 3rem) clamp(2rem, 1.5rem + 2vw, 4rem);
  color: var(--sl-reader-text);
  font-family: var(--sl-font-system);
}

/* A subtle arrival, not a flourish — the Story settles in rather than
   popping in instantly, reinforcing "entering" it. Only declared
   inside this reduced-motion-aware query, so a visitor who prefers
   reduced motion never has an animation to skip in the first place;
   .sl-story simply renders in its resting state immediately. */
@media (prefers-reduced-motion: no-preference) {
  .sl-story {
    animation: sl-story-rise 480ms ease both;
  }
}

@keyframes sl-story-rise {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.sl-story__header {
  text-align: center;
  padding-block-end: var(--sl-space-3);
}

.sl-story__title {
  font-size: clamp(1.35rem, 1.1rem + 1vw, 1.85rem);
  font-weight: 600;
  line-height: 1.25;
  margin: 0;
  color: var(--sl-reader-text);
}

.sl-story__meta {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--sl-space-1) var(--sl-space-2);
  color: var(--sl-reader-muted);
  font-size: 0.8125rem;
  text-align: center;
  margin: 0 0 var(--sl-space-3);
}

/* General link color for anything inside the Story — always the
   reader's own accent, never the page's adaptive one (see demo.css's
   plain `a` rule, which swaps with system theme and would mismatch
   the reader's always-dark background). .sl-story__footer a overrides
   this further down, for links that should read quieter than content
   links. */
.sl-story a {
  color: var(--sl-reader-accent);
}

/* -------------------------------------------------------------------- */
/* Slides — horizontal CSS Scroll Snap                                  */
/* -------------------------------------------------------------------- */

/* The viewport is the *only* visually framed object in a Story — the
   sole border, radius, shadow, and background in the whole component.
   Nothing wraps it in a second panel, and nothing inside it (slides,
   media) repeats its border/radius/shadow — see .sl-slide below for
   why individual slides carry none of that. overflow: hidden clips
   the scrolling content to this one boundary, so scrolling between
   slides never reveals a gap, a shadow seam, or the page background
   behind the track: one continuous surface, viewed through a single
   window, closer to a native photo viewer than a row of cards.

   It's also still the positioned anchor (position: relative) for the
   overlay Previous/Next buttons social-lite.js adds — see .sl-story-nav. */
.sl-story__viewport {
  position: relative;
  overflow: hidden;
  border-radius: var(--sl-stage-radius);
  background: var(--sl-reader-stage-bg);
  box-shadow:
    0 0 0 1px var(--sl-reader-border),
    0 30px 70px -25px rgba(0, 0, 0, 0.65);
}

.sl-story__slides {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;

  /* Tall on purpose: a Story here is a pane to spend time in, sized to
     the viewport itself rather than shrink-wrapped to whatever text
     happens to be in the shortest slide. */
  min-height: min(70vh, 40rem);

  /* --sl-slide-gap defaults to 0: each slide is exactly one track-width
     pane (see .sl-slide's flex-basis: 100%), so slides sit flush
     against each other with nothing between them for the page or
     panel background to show through while scrolling. */
  gap: var(--sl-slide-gap);

  /* The scrollbar is hidden — a deliberate design choice, not a lost
     affordance. CLAUDE.md's own rule is "don't hide a scrollbar unless
     another accessible position indicator exists," and .sl-story-outline
     below is exactly that: a persistent, always-present position
     indicator that isn't tied to whether JavaScript ran. Hiding the
     presentation here changes nothing about the underlying scroll
     container — overflow-x is still auto, so touch, trackpad, mouse
     wheel, and keyboard scrolling all keep working exactly as before. */
  scrollbar-width: none;
}

.sl-story__slides::-webkit-scrollbar {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .sl-story__slides {
    scroll-behavior: smooth;
  }
}

.sl-slide {
  /* Exactly one track-width pane per slide — see .sl-story__slides for
     why this (not a gap or margin) is what keeps slides seamless. */
  flex: 0 0 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  box-sizing: border-box;
  min-height: 0;

  /* Transparent, not its own surface: .sl-story__viewport owns the one
     background/border/radius/shadow for the whole canvas. A slide only
     gets its own background when intentionally styled to stand apart
     (see .sl-slide--quote below). Content centers vertically within
     the tall pane rather than starting flush at the top, so a short
     slide (a title, a single paragraph) gets generous surrounding
     space instead of stranding itself at the top of a mostly-empty box. */
  background: transparent;
  padding: clamp(1.5rem, 3vw, 3rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--sl-space-3);
}

.sl-slide__heading {
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.4rem);
  font-weight: 600;
  text-align: center;
  margin: 0;
}

.sl-slide__content {
  max-width: 34ch;
  margin-inline: auto;
  text-align: center;
  line-height: 1.65;
}

.sl-slide__content > * + * {
  margin-block-start: var(--sl-space-2);
}

/* -------------------------------------------------------------------- */
/* Slide presentation classes                                           */
/*                                                                      */
/* "The content determines the interface, not the other way around."   */
/* Each modifier below describes what *kind* of content a slide holds — */
/* the reader has no special-case logic for any of them; it just reads  */
/* a small, fixed set of custom properties generically (see             */
/* .sl-story-nav__previous further down) and lets each slide type set    */
/* whichever of those properties actually apply to it:                  */
/*                                                                      */
/*   --sl-slide-nav-bg          overlay button background (resting)     */
/*   --sl-slide-nav-bg-hover    overlay button background (hover)        */
/*   --sl-slide-nav-color       overlay button icon/text color           */
/*   --sl-slide-nav-border      overlay button border (rarely needed)    */
/*                                                                      */
/* The overlay buttons are siblings of the slide track, not descendants  */
/* of any one slide, so a property set on .sl-slide--photo can't cascade */
/* sideways to them on its own — social-lite.js's syncSlideTheme()       */
/* bridges that gap by copying whichever of these four properties the    */
/* *current* slide defines onto .sl-story__viewport once per slide       */
/* change. It only ever knows these four property names, never which     */
/* slide type is active, so a brand-new type — .sl-slide--map, say —    */
/* gets adaptive nav contrast for free just by setting the same four     */
/* properties; nothing in the reader itself needs to change.            */
/* -------------------------------------------------------------------- */

/* Cross-cutting background-luminance hint, independent of content type —
   combine with any .sl-slide--* type below once a slide's background
   luminance is actually known (e.g. .sl-slide--diagram.sl-slide--light
   for a diagram drawn on a pale background). */
.sl-slide--light {
  --sl-slide-nav-bg: rgba(0, 0, 0, 0.35);
  --sl-slide-nav-bg-hover: rgba(0, 0, 0, 0.5);
  --sl-slide-nav-color: #f6f3ec;
}

.sl-slide--dark {
  --sl-slide-nav-bg: rgba(255, 255, 255, 0.16);
  --sl-slide-nav-bg-hover: rgba(255, 255, 255, 0.26);
  --sl-slide-nav-color: var(--sl-reader-text);
}

/* Shared full-bleed layout for every media-forward slide type: no
   padding on the slide itself (the media fills it edge to edge), with
   the heading's own padding preserved so a *visible* heading (most of
   these are used with a visually-hidden one, but don't have to be)
   doesn't end up flush against the viewport's rounded corner. */
.sl-slide--media,
.sl-slide--photo,
.sl-slide--diagram,
.sl-slide--video {
  padding: 0;
  gap: 0;
}

.sl-slide--media .sl-slide__heading,
.sl-slide--photo .sl-slide__heading,
.sl-slide--diagram .sl-slide__heading,
.sl-slide--video .sl-slide__heading {
  padding: clamp(1.5rem, 3vw, 3rem) clamp(1.5rem, 3vw, 3rem) 0;
}

/* Photography: immersive edge-to-edge fill (object-fit: cover,
   cropping where necessary) rather than letterboxed contain — empty
   space inside the viewport undercuts the sense of immersion a photo
   is meant to create. The dark, moderately opaque scrim behind the nav
   buttons is a deliberately safe default for *arbitrary* photography
   without inspecting the actual image (no image analysis, by design —
   .sl-slide--light/--dark above exist for when more is known). */
.sl-slide--photo {
  --sl-slide-nav-bg: rgba(0, 0, 0, 0.32);
  --sl-slide-nav-bg-hover: rgba(0, 0, 0, 0.45);
  --sl-slide-nav-color: #ffffff;
}

.sl-slide--photo .sl-slide__media img,
.sl-slide--photo .sl-slide__media video {
  object-fit: cover;
}

/* Diagrams/illustrations and video: full-bleed like a photo slide, but
   cropping could cut off something meaningful (a label, a step, a UI
   element on screen), so both keep the base .sl-slide__media rule's
   object-fit: contain rather than overriding it to cover. Neither sets
   its own nav-theme properties — combine with .sl-slide--light or
   .sl-slide--dark once a particular diagram or video's background is
   known to need it. */

/* Media is the point of a media slide, so it's allowed to grow and
   fill the tall pane rather than sitting at its own small natural
   size. No background/radius/overflow of its own — .sl-story__viewport
   is still the only framed surface, so any letterboxing around a
   contained image just shows its dark stage-bg through, seamlessly. */
.sl-slide__media {
  flex: 1;
  min-height: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}

.sl-slide__media img,
.sl-slide__media svg,
.sl-slide__media video {
  flex: 1;
  min-height: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.sl-slide__caption {
  flex: 0 0 auto;
  color: var(--sl-reader-muted);
  font-size: 0.8125rem;
  text-align: center;
  padding: var(--sl-space-2) clamp(1.5rem, 3vw, 3rem) clamp(1.5rem, 3vw, 3rem);
}

/* EXPERIMENTAL — see visual-story.html. A slide that's *only* its
   media: no visible title, no caption, media filling the entire
   slide. Unlike .sl-slide--media above (full-bleed, no padding, no
   radius — the viewport is the only frame in the whole Story), this
   keeps the slide's ordinary padding and gives the media its own
   rounded corners, more like a photograph sitting in its own mat than
   an edge-to-edge canvas. Each slide still needs a real, accessible
   heading (see .sl-visually-hidden) even with nothing shown on
   screen — a Story remains a sequence of headed sections underneath,
   whether or not a sighted reader ever sees that heading text. */
.sl-slide--visual {
  padding: clamp(1.5rem, 3vw, 3rem);
}

.sl-slide--visual .sl-slide__media {
  border-radius: var(--sl-slide-radius);
  overflow: hidden;
}

.sl-slide--visual .sl-slide__media img,
.sl-slide--visual .sl-slide__media video {
  object-fit: cover;
}

/* Quote slides are the "intentionally styled" exception to every other
   slide's transparent background — a faint accent tint rather than a
   border, with a plain fallback for browsers without color-mix(). */
.sl-slide--quote {
  background: var(--sl-reader-stage-bg);
  background: color-mix(in srgb, var(--sl-reader-accent) 10%, var(--sl-reader-stage-bg));
}

.sl-slide--quote blockquote {
  margin: 0;
  font-size: clamp(1.1rem, 1rem + 0.6vw, 1.35rem);
  line-height: 1.5;
  font-style: italic;
}

.sl-slide--quote cite {
  display: block;
  margin-block-start: var(--sl-space-2);
  font-style: normal;
  color: var(--sl-reader-muted);
  font-size: 0.9375rem;
}

/* -------------------------------------------------------------------- */
/* Story navigation (progressive enhancement)                           */
/*                                                                      */
/* social-lite.js builds this entire nav and inserts it into            */
/* .sl-story__viewport as a sibling of .sl-story__slides — never a      */
/* child of the track — so it overlays the viewport, stays anchored     */
/* to fixed left/right edges while the track scrolls underneath it,     */
/* and is simply absent (no JS, no nav) when JavaScript doesn't run.    */
/* Without JavaScript, the slides remain fully navigable through        */
/* native scrolling and the progress list below, which is real,         */
/* always-present markup.                                               */
/*                                                                      */
/* On hover-capable pointers, the buttons rest at low opacity and only  */
/* become fully visible when the reader shows intent (moving the        */
/* pointer into the viewport, or focusing a button) — see the           */
/* `(hover: hover)` block below. That baseline is CSS-only; it works    */
/* with :hover and :focus-within alone, no JavaScript involved. Touch    */
/* devices get a separate, always-visible-but-subdued treatment in the  */
/* `(hover: none)` block, since there's no hover state to react to.      */
/* -------------------------------------------------------------------- */

.sl-story-nav {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;

  /* Enough inset that a focused button's outline (3px width, 3px
     offset — 6px total beyond its own edge) has room to render before
     reaching .sl-story__viewport's own overflow: hidden clip. */
  padding-inline: var(--sl-space-2);

  /* Let clicks fall through to the slide track everywhere except on
     the buttons themselves, which opt back in below. */
  pointer-events: none;
}

/* Narrow screens keep the same vertically centered, left/right-edge
   arrangement as desktop (align-items: center, inherited from above)
   so the interaction model matches — only the inset from the edges
   grows a little, for comfortable thumb reach and to clear notches
   or rounded corners in landscape. */
@media (max-width: 39.9375rem) {
  .sl-story-nav {
    padding-inline-start: max(1rem, env(safe-area-inset-left));
    padding-inline-end: max(1rem, env(safe-area-inset-right));
  }
}

.sl-story-nav__previous,
.sl-story-nav__next {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--sl-control-size, 44px);
  height: var(--sl-control-size, 44px);
  border-radius: 50%;

  /* Reads a small, fixed contract of theme custom properties rather
     than a hardcoded color — see the "Slide presentation classes"
     section above for what sets these and why. The fallback values
     (light, translucent discs against the dark stage) are exactly
     what this rule hardcoded before that system existed, so a slide
     that sets none of them looks identical to before. */
  border: var(--sl-slide-nav-border, none);
  background: var(--sl-slide-nav-bg, rgba(255, 255, 255, 0.12));
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);

  color: var(--sl-slide-nav-color, var(--sl-reader-text));
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;

  /* A single soft shadow rather than the slide's two-layer one: these
     are transient overlay controls, not another elevated surface
     competing with the Story itself. */
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);

  /* color/border-color included alongside the original background-
     color/opacity/transform, so a theme change on slide navigation
     fades smoothly rather than snapping — social-lite.js updates
     these custom properties once per slide change (see
     syncSlideTheme), and a plain CSS transition on the properties
     that read them is all "fade smoothly" needs. */
  transition: background-color var(--sl-transition-duration) ease,
    color var(--sl-transition-duration) ease,
    border-color var(--sl-transition-duration) ease,
    opacity var(--sl-transition-duration) ease,
    transform var(--sl-transition-duration) ease;
}

.sl-story-nav__previous:hover,
.sl-story-nav__next:hover {
  background: var(--sl-slide-nav-bg-hover, rgba(255, 255, 255, 0.2));
}

.sl-story-nav__previous[aria-disabled="true"],
.sl-story-nav__next[aria-disabled="true"] {
  opacity: 0.35;
  pointer-events: none;
}

/* Mouse/trackpad: the buttons quietly step aside until the reader
   shows intent to navigate, the way QuickTime or Apple Photos'
   controls do. Scoped to (hover: hover) and (pointer: fine) so this
   never applies on touch, where there's no hover to reveal them with —
   see the (hover: none) block further down for that case instead. */
@media (hover: hover) and (pointer: fine) {
  .sl-story-nav__previous,
  .sl-story-nav__next {
    opacity: var(--sl-control-opacity-idle, 0.18);
    transform: scale(0.92);
  }

  .sl-story__viewport:hover .sl-story-nav__previous,
  .sl-story__viewport:hover .sl-story-nav__next,
  .sl-story__viewport:focus-within .sl-story-nav__previous,
  .sl-story__viewport:focus-within .sl-story-nav__next {
    opacity: var(--sl-control-opacity-active, 1);
    transform: scale(1);
  }

  /* A disabled button (first/last slide) stays subdued no matter what —
     hover or focus on the *nav* shouldn't imply this particular button
     is now usable. */
  .sl-story__viewport:hover .sl-story-nav__previous[aria-disabled="true"],
  .sl-story__viewport:hover .sl-story-nav__next[aria-disabled="true"],
  .sl-story__viewport:focus-within .sl-story-nav__previous[aria-disabled="true"],
  .sl-story__viewport:focus-within .sl-story-nav__next[aria-disabled="true"] {
    opacity: 0.35;
    transform: scale(0.92);
  }

  /* Progressive enhancement on top of the CSS-only rules above:
     social-lite.js adds .is-idle after ~2.5s with no pointer movement,
     so the buttons fade back out even while the pointer is still
     resting inside the viewport, not just when it leaves. Excludes
     :focus-within so a keyboard-focused button is never dimmed by
     mouse idling — the two forms of "attention" are independent. */
  .sl-story__viewport:hover:not(:focus-within) .sl-story-nav__previous.is-idle,
  .sl-story__viewport:hover:not(:focus-within) .sl-story-nav__next.is-idle {
    opacity: var(--sl-control-opacity-idle, 0.18);
    transform: scale(0.92);
  }
}

/* Touch: no hover to react to, so the buttons stay visible at all
   times, just with lower emphasis than the fully-revealed desktop
   state — obvious enough to use, without competing with the Story. */
@media (hover: none) {
  .sl-story-nav__previous,
  .sl-story-nav__next {
    opacity: 0.7;
  }
}

/* -------------------------------------------------------------------- */
/* Story description                                                    */
/*                                                                      */
/* Summarizes the whole Story, not any one slide — a sibling of the     */
/* viewport, not a caption inside it, and styled quietly (muted color,   */
/* smaller size) so it supports the Story after a reader has gone       */
/* through it rather than competing with it. Sits after the viewport    */
/* and before the byline/date, so a reader finishes the slides (and     */
/* the overlay controls stay behind, inside the viewport's own bounds)  */
/* before reaching any of this supporting text.                         */
/* -------------------------------------------------------------------- */

.sl-story__summary {
  max-width: 32ch;
  margin: var(--sl-space-4) auto var(--sl-space-2);
  color: var(--sl-reader-muted);
  font-size: 0.875rem;
  line-height: 1.6;
  text-align: center;
}

/* -------------------------------------------------------------------- */
/* Story outline                                                        */
/*                                                                      */
/* Deliberately not called "progress": it isn't measuring how far a     */
/* reader has scrolled, it's showing the Story's own structure — one    */
/* segment per slide, however many that turns out to be. It also        */
/* replaces the track's now-hidden scrollbar as the position indicator  */
/* required by CLAUDE.md's "don't hide a scrollbar without one."        */
/*                                                                      */
/* Each segment is a real <a href="#slide-N"> link, same as the old     */
/* dot indicator — so, unlike the track's own scrollbar, this position  */
/* indicator is also a working no-JS navigation method: clicking a      */
/* segment jumps to that slide via native anchor scrolling with zero    */
/* JavaScript involved. JS only keeps aria-current in sync with         */
/* whichever slide is actually in view.                                 */
/* -------------------------------------------------------------------- */

.sl-story-outline ol {
  display: flex;
  gap: var(--sl-space-2);
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 16rem;
  margin-inline: auto;
}

.sl-story-outline li {
  flex: 1 1 0;
}

/* The link itself is the full touch target (44px tall) so it's easy to
   tap accurately; the ::after pseudo-element is the actual thin bar a
   reader sees, sized independently of the hit area around it. */
.sl-story-outline__segment {
  position: relative;
  display: block;
  min-height: 44px;
  text-indent: -9999px;
  overflow: hidden;
}

/* social-lite-story.js generates <button> segments when [data-sl-progress]
   is present but empty (see docs/web-component.md) — a <button> carries
   UA default border/background/padding/font that the <a>-based segments
   above never had, so it needs an explicit reset to render identically. */
.sl-story-outline__segment:where(button) {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  width: 100%;
  cursor: pointer;
}

.sl-story-outline__segment::after {
  content: "";
  position: absolute;
  top: 50%;
  inset-inline: 0;
  transform: translateY(-50%);
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  transition: background-color var(--sl-transition-duration) ease;
}

.sl-story-outline__segment[aria-current="step"]::after {
  background: var(--sl-reader-accent);
}

/* -------------------------------------------------------------------- */
/* Story footer                                                         */
/*                                                                       */
/* A single quiet way to leave the reader, not a restatement of the     */
/* site's normal navigation — see .reader-exit in demo.css, which       */
/* already replaces .site-header on Story pages for the same reason.    */
/* -------------------------------------------------------------------- */

.sl-story__footer {
  margin-block-start: var(--sl-space-4);
  padding-block-start: var(--sl-space-3);
  border-block-start: 1px solid var(--sl-reader-border);
  text-align: center;
}

/* Quieter than .sl-story a's general accent color — these are
   secondary, "where to go next" links, not part of the Story's own
   content. */
.sl-story__footer a {
  color: var(--sl-reader-muted);
}

.sl-story__footer a:hover {
  color: var(--sl-reader-text);
}

.sl-story__footer nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--sl-space-1) var(--sl-space-3);
}

/* -------------------------------------------------------------------- */
/* Story feed                                                           */
/*                                                                      */
/* The feed is for scanning, not reading — a visitor here is deciding   */
/* what to open next, not reading yet. So it borrows Instagram's        */
/* visual rhythm (one narrow column, large media, predictable           */
/* repetition, whitespace instead of bordered cards) without any of     */
/* its engagement mechanics: no likes, counts, or nested-card chrome.   */
/* Each .sl-feed-item is one <article>, and its media, title, and       */
/* "Open Story" link are three ordinary <a> elements pointing at the    */
/* same immersive Story URL — no JavaScript makes the whole item        */
/* clickable, and none is needed to.                                   */
/* -------------------------------------------------------------------- */

.sl-feed {
  max-width: var(--sl-feed-width);
  margin-inline: auto;
  display: flex;
  flex-direction: column;

  /* Generous, not incidental: whitespace is the only separator between
     Stories, standing in for the bordered/shadowed "card" this format
     deliberately avoids. */
  gap: clamp(3rem, 2rem + 4vw, 5rem);
  list-style: none;
  margin-block: 0;
  padding: 0;
}

.sl-feed-item {
  display: block;
}

.sl-feed-item__header {
  display: flex;
  align-items: center;
  gap: var(--sl-space-2);
  margin-block-end: var(--sl-space-2);
}

/* A simple lettered mark, not a photo — "optional small avatar or site
   mark" per the feed-item structure, without requiring a real avatar
   asset. Decorative: the author name right next to it already carries
   the same information, so it's hidden from assistive technology
   rather than announced twice. */
.sl-feed-item__avatar {
  flex-shrink: 0;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--sl-color-accent);
  color: #fff;
  font-size: 0.8125rem;
  font-weight: 600;
}

.sl-feed-item__byline {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  line-height: 1.3;
  font-size: 0.875rem;
  margin: 0;
}

.sl-feed-item__author {
  font-weight: 600;
  color: var(--sl-color-text);
}

.sl-feed-item__date {
  color: var(--sl-color-muted);
}

/* Large and visually dominant, per the feed goals — this is the one
   element in a feed item meant to command attention, so it comes right
   after the (deliberately small/quiet) header. A real link, not a div
   with a click handler: title, media, and the "Open Story" action all
   point at the same immersive URL as ordinary <a> elements. Its own
   accessible name comes from the visually-hidden text inside (see
   .sl-visually-hidden usage in feed.html) rather than the image, since
   the placeholder thumbnails are decorative (alt="") the same way they
   are inside the Story reader itself. */
.sl-feed-item__media {
  display: block;
  margin: 0 0 var(--sl-space-3);
  aspect-ratio: 4 / 5;
  border-radius: var(--sl-slide-radius);
  overflow: hidden;
  background: var(--sl-color-surface-alt);
}

.sl-feed-item__media img,
.sl-feed-item__media svg {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Explicit opt-in per CLAUDE.md's feed-goals wording ("allow contain
   for diagrams when explicitly configured") — cropping a diagram risks
   cutting off the part that actually explains something, so this
   modifier trades the edge-to-edge fill above for letterboxing instead. */
.sl-feed-item__media--contain img,
.sl-feed-item__media--contain svg {
  object-fit: contain;
}

/* A quiet play-icon hint for video previews — purely decorative
   (::after, no markup, nothing for assistive tech to trip over), since
   the title and summary already say "video" in words. No engagement
   chrome, just the same affordance any native video thumbnail has. */
.sl-feed-item__media--video {
  position: relative;
}

.sl-feed-item__media--video::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-sizing: border-box;
}

/* A classic CSS border-triangle, not clip-path: a 0×0 box centers
   cleanly with inset: 0 / margin: auto the same way the circle above
   does, then the borders alone draw a right-pointing triangle. A small
   translateX nudge corrects for the triangle's visual center sitting
   left of its bounding box's center. */
.sl-feed-item__media--video::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 0.65rem 0 0.65rem 1.05rem;
  border-color: transparent transparent transparent #fff;
  transform: translateX(0.15rem);
}

.sl-feed-item__title {
  font-size: clamp(1.2rem, 1.1rem + 0.5vw, 1.5rem);
  font-weight: 600;
  margin: 0 0 var(--sl-space-1);
}

.sl-feed-item__title a {
  color: inherit;
  text-decoration: none;
}

.sl-feed-item__title a:hover {
  text-decoration: underline;
}

.sl-feed-item__summary {
  margin: 0 0 var(--sl-space-2);
  max-width: 60ch;
  color: var(--sl-color-text);
}

/* The one clear call to action per feed goals — deliberately styled
   like a real, visible link (not a button) since it's still ordinary
   in-page navigation, just the most prominent instance of it in the
   item. */
.sl-feed-item__cta {
  display: inline-block;
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--sl-color-accent);
  text-decoration: none;
}

.sl-feed-item__cta:hover {
  text-decoration: underline;
}

/* Visually hidden but still available to assistive technology — used
   for the live-updating "slide X of N" announcement. */
.sl-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* -------------------------------------------------------------------- */
/* Reduced motion                                                       */
/* -------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  /* Belt-and-suspenders with the animation only being declared inside
     the no-preference query above: makes the intent explicit even
     though it's technically redundant given that gating. */
  .sl-story {
    animation: none;
  }

  .sl-story__slides {
    scroll-behavior: auto;
  }

  .sl-story-nav__previous,
  .sl-story-nav__next,
  .sl-story-outline__segment::after,
  .sl-feed-item__title a {
    transition: none;
  }
}
