/* Appearance ▸ Feel — the effects picker and the walkthrough that demonstrates it.

   Split out of styles.css on 2026-09-05, alongside looks.css and for the same reason: this is
   a screen whose whole job is to SHOW what a setting does, so it is redrawn far more often than
   the app around it, and it should not be a merge conflict with the file that holds everything
   else. The picker's rules came over from feel.css the same day, when the screen was rebuilt
   around live demos: feel.css is what the app does under your hand, this is where you choose it.

   Loaded from index.html right after styles.css and BEFORE theme-boot.js: a theme sheet still
   wins over anything in here, which is the source-order rule from themes/README.md.

   ONE THING TO KNOW BEFORE EDITING. This file is loaded BEFORE feel.css, so at equal specificity
   feel.css wins. The demo stages are full of the app's real components — `.row`, `.card`,
   `.btn`, `.nav-btn` — and feel.css styles those through `[data-feel] .row`, which is (0,2,0).
   Every rule here that has to reshape one of them therefore carries a class more than that:
   `.fx-stage.fx-rows .row`, not `.fx-rows .row`. Get it wrong and the tile looks right at first
   and wrong the moment the layer is switched on, which is the only time anybody looks at it. */

/* ============ the amplitude ============
   Three cards, and the demo on each is the thing itself: a live control you can put the pointer
   on, running at that card's own amplitude rather than the one currently chosen. A written
   description of a spring persuades nobody — the same reason the delete-animation picker next to
   it has a moving preview. */
.feel-seg { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.feel-seg button {
  display: grid; gap: 3px; text-align: left; padding: calc(10 * var(--sp)) calc(11 * var(--sp)); cursor: pointer;
  background: var(--glass); border: 1px solid var(--line); border-radius: var(--r-sm);
  color: var(--text); transition: border-color .14s, background .14s;
}
.feel-seg button:hover { background: var(--glass2); border-color: var(--line2); }
.feel-seg button.on { border-color: var(--accent); background: var(--accent-tint); box-shadow: inset 0 0 0 1px var(--accent-ring); }
.feel-seg b { font-size: var(--fs-base); font-weight: var(--fw-semi); }
.feel-seg em { font-style: normal; font-size: calc(var(--fs-sm) + var(--fs-half)); color: var(--mut); line-height: 1.4; }
.feel-prev {
  position: relative; display: grid; place-items: center; height: 54px; margin-bottom: 7px;
  border-radius: calc(var(--r-3xs) + var(--r-half)); overflow: hidden; background: var(--input);
}
.feel-prev > s {
  width: 62%; height: 22px; border-radius: var(--r-2xs); text-decoration: none;
  background: var(--line2); position: relative;
  transition: scale .34s var(--fe-spring, cubic-bezier(.34, 1.42, .32, 1));
}
.feel-seg button.on .feel-prev > s { background: color-mix(in srgb, var(--accent) 55%, var(--line2)); }
@media (max-width: 560px) { .feel-seg { grid-template-columns: 1fr; } }

/* ============ headings and the two lines of copy this screen gets ============
   Deliberately two. Everything else on the page is a demonstration, and a paragraph explaining a
   demonstration is a demonstration that failed. */
.fx-head { margin: 18px 0 8px; }
.fx-lede { margin: -4px 0 10px; font-size: calc(var(--fs-md) + var(--fs-half)); line-height: 1.5; color: var(--mut); }
.fx-note {
  margin: 10px 0 0; padding: calc(9 * var(--sp)) calc(12 * var(--sp)); border-radius: calc(var(--r-xs) + var(--r-half));
  font-size: calc(var(--fs-md) + var(--fs-half)); line-height: 1.5;
  color: var(--accent-text); background: var(--accent-tint);
  box-shadow: inset 0 0 0 1px var(--accent-ring);
}
.fx-beta-head { margin-top: 16px; }

/* The way in, and the way back. Below the tiles now rather than above them: the tiles answer
   "what does Row highlight mean" on the spot, so the full-size run is the second thing you want,
   not the first. */
.feel-acts { display: grid; grid-template-columns: 2fr 1fr; gap: 8px; margin-top: 14px; }
.feel-acts.fx-one { grid-template-columns: 1fr; }
.feel-acts .btn { height: 40px; font-weight: var(--fw-semi); }
.feel-reset { background: var(--glass); color: var(--mut); box-shadow: inset 0 0 0 1px var(--line); }
.feel-reset:hover { background: var(--glass2); color: var(--text); }
@media (max-width: 560px) { .feel-acts { grid-template-columns: 1fr; } }
.fw-open {
  height: 40px;
  background: var(--accent-tint); color: var(--accent-text);
  box-shadow: inset 0 0 0 1px var(--accent-ring);
}
.fw-open:hover { filter: brightness(1.12); }

/* ============ the tiles ============
   A stage, a name, three words of instruction, and the answer. In that order, because that is
   the order the question is actually asked in: you see it, you are told what to do to it, you
   say yes or no. */
.fx-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(228px, 1fr)); gap: 10px; }
.fx-tile {
  display: grid; gap: 8px; align-content: start;
  padding: calc(8 * var(--sp)); border: 1px solid var(--line); border-radius: var(--r-sm);
  transition: border-color .14s, background .14s;
}
.fx-tile:hover { border-color: var(--line2); background: var(--glass); }
/* Name over instruction rather than side by side: half the names wrap at this width, and a
   right-aligned hint sitting against a two-line name reads as a third column that is not there. */
.fx-cap { display: grid; gap: 1px; min-width: 0; padding: 0 calc(2 * var(--sp)); }
.fx-cap b { font-size: var(--fs-base); font-weight: var(--fw-semi); line-height: 1.25; }
.fx-cap em { font-style: normal; font-size: calc(var(--fs-sm) + var(--fs-half)); line-height: 1.35; color: var(--mut); }
/* Ballistic-only, and you are in Tactile. Said where the instruction would have been, because
   the honest answer to "what does this do" here is "nothing, in the mode you are in". */
.fx-tile.fx-mute .fx-cap em { color: var(--dim); }
.fx-tile.fx-mute .fx-stage { opacity: .45; }

/* ---- the answer ----
   Two pills, 30px tall, and they are a radiogroup: what is stored is `.on`, and `.peek` is the
   dotted one, which says "this is what the stage is showing you at the moment" without a
   sentence saying it. They part company exactly when you point at a tile whose answer is Off —
   the stage demonstrates, the answer stays Off, and the outline is the thing that reconciles
   the two. */
.fx-pick { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 4px; padding: calc(3 * var(--sp)); border-radius: calc(var(--r-xs) + var(--r-half)); background: var(--input); }
.fx-p {
  height: 30px; min-width: 44px; border: 0; border-radius: calc(var(--r-2xs) + var(--r-half)); cursor: pointer;
  font: inherit; font-size: calc(var(--fs-md) + var(--fs-half)); font-weight: var(--fw-semi); color: var(--mut);
  background: transparent;
  transition: background .14s, color .14s, box-shadow .14s;
}
.fx-p:hover { color: var(--text); }
.fx-p.peek { box-shadow: inset 0 0 0 1px var(--line2); color: var(--text); }
.fx-p.on { color: var(--accent-text); background: var(--accent-tint); box-shadow: inset 0 0 0 1px var(--accent-ring); }
.fx-p:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- the two that are not effects ----
   Their own heading and no stage at all, because there is nothing to watch: they change the
   moment a press counts, not what it looks like. A demo of one would be a demo of nothing. */
.fx-acts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.fx-act {
  display: flex; align-items: flex-start; gap: 10px;
  padding: calc(9 * var(--sp)) calc(11 * var(--sp)); border: 1px solid var(--line); border-radius: var(--r-sm);
  cursor: pointer; font-size: var(--fs-base); line-height: 1.35;
  transition: border-color .14s, background .14s;
}
.fx-act:hover { background: var(--glass); border-color: var(--line2); }
.fx-act:has(input:checked) { border-color: var(--accent-ring); background: var(--accent-tint); }
.fx-act input { accent-color: var(--accent); width: 16px; height: 16px; flex: none; margin-top: 1px; }
.fx-act b { font-weight: var(--fw-semi); }
.fx-act em { font-style: normal; color: var(--mut); font-size: calc(var(--fs-sm) + var(--fs-half)); display: block; margin-top: 2px; }
@media (max-width: 720px) { .fx-acts { grid-template-columns: 1fr; } }

/* ============ the stage ============
   Every one of these is real app markup — the classes are the contract feel.css and feel.js are
   written against — so what happens in here is the effect and not a drawing of it. What is
   restyled below is only the SIZE: a listing row is 22px-padded and full width in the app, and
   three of them at that size do not fit in a 228px tile. */
.fx-stage {
  position: relative; display: block; overflow: hidden;
  height: 108px; border-radius: var(--r-xs); background: var(--input);
  /* Scenery, and half the gestures on it are drags: a sweep across `combo` painted a text
     selection over every tile name it crossed. */
  user-select: none; -webkit-user-select: none;
  /* The demos are one part at a time by construction — see fxPreview in app.js — so they never
     accumulate; but a stage is still a stack of composited layers, and giving it its own one
     keeps a ripple from repainting the settings panel behind it. */
  contain: paint;
}

/* ---- three rows of a listing ---- */
.fx-stage.fx-rows { --cols: minmax(0, 1fr); --row-pad: 7px; --row-tile: 20px; --row-fs: .88; }
.fx-stage.fx-rows .row {
  padding: var(--row-pad) 12px;
  /* content-visibility skips layout for anything off screen, which inside a 108px box that is
     itself inside a scrolling dialog is a row that measures zero and then jumps. The real list
     wants it for a folder of three thousand; three rows do not. */
  content-visibility: visible; contain-intrinsic-size: auto;
  /* The listing's entry cascade would replay every time the panel repainted. */
  animation: none;
}
.fx-stage.fx-rows .row:last-child { border-bottom: 0; }
.fx-stage.fx-rows .row-name { gap: 9px; }

/* ---- buttons, and the icon strip ---- */
.fx-stage.fx-buttons, .fx-stage.fx-icons, .fx-stage.fx-mark, .fx-stage.fx-flick {
  display: flex; align-items: center; justify-content: center; gap: 12px;
}
.fx-stage.fx-buttons .btn { height: 34px; padding: 0 calc(14 * var(--sp)); font-size: calc(var(--fs-md) + var(--fs-half)); }
.fx-stage.fx-icons { gap: 14px; }

/* ---- a nav column ----
   12px of horizontal padding is not decoration: the glider is `left: 12px; right: 12px` inside
   whatever column it is in — .side's own padding, measured — so a demo column has to have the
   same or the pill sits proud of the buttons on both sides. See "the sidebar glider" in
   feel.css, and ensureGlide() in feel.js for how the pill came to belong to the column. */
.fx-side { position: relative; display: grid; gap: 2px; padding: calc(6 * var(--sp)) calc(12 * var(--sp)); width: 100%; }
.fx-stage.fx-nav .nav-btn { padding: calc(6 * var(--sp)) calc(10 * var(--sp)); font-size: calc(var(--fs-md) + var(--fs-half)); gap: 9px; }
.fx-stage.fx-nav .nav-btn > i { width: 15px; height: 15px; }

/* ---- a grid of tiles ---- */
.fx-stage.fx-cards, .fx-stage.fx-sweep { height: 122px; padding: calc(9 * var(--sp)); }
.fx-stage.fx-cards .grid { grid-template-columns: 1fr 1fr; gap: 8px; height: 100%; }
.fx-stage.fx-sweep .grid { grid-template-columns: repeat(3, 1fr); gap: 7px; height: 100%; }
/* The listing's entry cascade is off; the ring a taken tile throws is NOT, and `:not(.fe-took)`
   is what tells them apart. Written as a bare `animation: none` this beat feel.css's
   `[data-feel] .fe-took` on specificity, so the `combo` stage took tiles and showed nothing for
   it — the count on the pointer was the whole demonstration, and it rides off the stage's edge. */
.fx-stage.fx-cards .card:not(.fe-took), .fx-stage.fx-sweep .card:not(.fe-took) { animation: none; }
.fx-stage.fx-cards .card-media, .fx-stage.fx-sweep .card-media { height: 52px; }
.fx-stage.fx-cards .card-name, .fx-stage.fx-sweep .card-name { font-size: var(--fs-sm); padding: calc(5 * var(--sp)) calc(7 * var(--sp)) 0; }
.fx-stage.fx-cards .card-meta, .fx-stage.fx-sweep .card-meta,
.fx-stage.fx-cards .card-check, .fx-stage.fx-sweep .card-check,
.fx-stage.fx-cards .card-more, .fx-stage.fx-sweep .card-more { display: none; }
.fx-stage.fx-cards .chip-mid, .fx-stage.fx-sweep .chip-mid { font-size: var(--fs-sm); }
/* The lift comes out of the plane toward you, so the box it is in cannot clip at the border. */
.fx-stage.fx-cards { overflow: visible; contain: none; }

/* ---- the mark ---- */
.fx-stage.fx-mark .logo-cloud { width: 42px; height: 42px; color: var(--accent); }

/* ---- an open space to flick across ----
   Nothing in it on purpose. The comet is one fixed canvas over the whole window, so what the
   trail actually looks like is not something a 228px box can contain — it only has to be
   somewhere to move the pointer fast. */
.fx-stage.fx-flick::after {
  content: ''; width: 56%; height: 1px; border-radius: calc(var(--r-4xs) / 2);
  background: linear-gradient(90deg, transparent, var(--line2), transparent);
}

/* ---- the drawn hand ----
   The stage demonstrates itself: fxDemo walks this along a script and dispatches real pointer
   events at each position, which is what makes the effect underneath the real effect. It is
   pointer-events: none because elementFromPoint is how the script finds what it is over, and a
   cursor that could be hit would find itself every frame. */
.fx-hand {
  position: absolute; left: 0; top: 0; z-index: 6;
  width: 15px; height: 19px; margin: -1px 0 0 -1px;
  pointer-events: none;
  background: var(--text); color: var(--bg);
  /* An arrow, cut out of a solid block rather than drawn as an SVG, so it takes the theme's ink
     and needs no second asset. */
  clip-path: polygon(0 0, 0 82%, 26% 63%, 44% 100%, 66% 89%, 48% 54%, 78% 50%);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .55));
  animation: fx-hand-in .18s ease-out backwards;
}
@keyframes fx-hand-in { from { opacity: 0; scale: .6; } }

/* ============ the walkthrough ============
   It happens in the app, at full size: the listing is invented, the sidebar and toolbar are the
   real ones, and the panel takes the details rail's place. Only two things here are new surfaces
   — the cover over the listing and the panel — and everything else is the app's own components
   being pointed at. */
html[data-fw] .rail,
html[data-fw] #rail-grip { visibility: hidden; }
/* The real listing stays in place, still scrolled where it was, and comes straight back —
   nothing had to be re-fetched to put it away. */
html[data-fw] #content > *:not(.fw-cover) { visibility: hidden; }

#content { position: relative; }
.fw-cover {
  position: absolute; inset: 0; z-index: 5;
  overflow: auto;
  background: var(--bg);
}

/* ---- the ring on the real thing ----
   What replaced "Point down the sidebar on the left." A sentence you have to read, act on and
   interpret is not an answer to "show me what this does"; a ring around the sidebar is. Fixed,
   and positioned by hand from a client-space rect divided by the UI zoom — the same correction
   openPop makes, and the one that is invisible at 100% and a third of a panel out at 140%. */
.fx-aim {
  position: fixed; z-index: 45; pointer-events: none;
  border-radius: var(--r);
  box-shadow: 0 0 0 2px var(--accent), 0 0 0 6px color-mix(in srgb, var(--accent) 22%, transparent);
  animation: fx-aim-in .3s cubic-bezier(.34, 1.42, .32, 1) backwards;
}
@keyframes fx-aim-in { from { opacity: 0; scale: 1.04; } }

/* ---- the panel ---- */
.fw-panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 46;
  width: min(360px, 92vw);
  display: flex; flex-direction: column; gap: 10px;
  padding: calc(16 * var(--sp)) calc(18 * var(--sp)) calc(18 * var(--sp));
  overflow: auto;
  background: var(--panel-solid, var(--panel));
  border-left: 1px solid var(--line);
  box-shadow: -20px 0 60px -30px rgba(0, 0, 0, .8);
  animation: fw-slide .28s cubic-bezier(.34, 1.42, .32, 1) backwards;
}
@keyframes fw-slide { from { transform: translateX(24px); opacity: 0; } }

/* The panel's own stage is bigger than a tile's, and it is the guarantee: whatever the part,
   something is doing the thing here, unprompted, for as long as the question is up. */
.fw-panel .fx-stage { height: 132px; flex: none; }
.fw-panel .fx-stage.fx-cards, .fw-panel .fx-stage.fx-sweep { height: 146px; }

.fw-top { display: flex; align-items: center; gap: 10px; }
.fw-count { font-family: var(--font-meta); font-size: calc(var(--fs-sm) + var(--fs-half)); color: var(--faint); letter-spacing: calc(.04em * var(--tracking)); }
.fw-beta {
  font-size: var(--fs-xs); font-weight: var(--fw-bold); letter-spacing: calc(.06em * var(--tracking)); text-transform: var(--label-case);
  padding: calc(2 * var(--sp)) calc(7 * var(--sp)); border-radius: var(--r-pill);
  color: var(--sun); background: color-mix(in srgb, var(--sun) 14%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sun) 36%, transparent);
}
.fw-close { margin-left: auto; }

.fw-name { font-size: calc(var(--fs-2xl) + var(--fs-half)); font-weight: var(--fw-bold); }
.fw-tag { margin: -4px 0 0; font-size: var(--fs-base); color: var(--mut); line-height: 1.5; }
.fw-gest { margin: 2px 0 0; font-size: calc(var(--fs-md) + var(--fs-half)); color: var(--accent-text); }
.fw-note { margin: 2px 0 0; font-size: var(--fs-md); line-height: 1.5; color: var(--mut); }
.fw-lab { margin: 6px 0 -4px; font-size: var(--fs-sm); letter-spacing: calc(.05em * var(--tracking)); text-transform: var(--label-case); color: var(--faint); }

/* ---- the live comparison ----
   The single best thing about this screen and the reason it beat the old switch list: nothing
   else is on to change at the same time, so what moves when you flip it is what you flipped. */
.fw-try { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; padding: calc(4 * var(--sp)); border-radius: calc(var(--r-sm) + var(--r-half)); background: var(--input); }
.fw-try-3 { grid-template-columns: repeat(3, 1fr); }
.fw-tryb {
  height: 34px; border-radius: var(--r-xs); cursor: pointer;
  font-size: var(--fs-base); font-weight: var(--fw-semi); color: var(--mut);
  background: transparent; border: 0;
  transition: background .14s, color .14s;
}
.fw-tryb:hover { color: var(--text); }
.fw-tryb.on { color: var(--accent-text); background: var(--accent-tint); box-shadow: inset 0 0 0 1px var(--accent-ring); }

/* ---- the answer ---- */
.fw-ans { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.fw-ans button, .fw-keep {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 46px; border-radius: calc(var(--r-sm) + var(--r-half)); cursor: pointer;
  font-size: calc(var(--fs-lg) + var(--fs-half)); font-weight: var(--fw-bold); border: 0;
  transition: background .14s, color .14s;
}
.fw-ans svg, .fw-keep svg { width: 17px; height: 17px; }
.fw-no {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger) 38%, transparent);
}
.fw-no:hover { background: color-mix(in srgb, var(--danger) 22%, transparent); }
.fw-yes {
  color: var(--ok);
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ok) 38%, transparent);
}
.fw-yes:hover { background: color-mix(in srgb, var(--ok) 22%, transparent); }
.fw-keep {
  width: 100%;
  color: var(--ok);
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ok) 38%, transparent);
}
.fw-keep:hover { background: color-mix(in srgb, var(--ok) 22%, transparent); }
.fw-hear { align-self: start; height: 34px; }

.fw-nav { display: flex; align-items: center; gap: 10px; margin-top: auto; padding-top: calc(12 * var(--sp)); }
.fw-prev, .fw-skip {
  display: flex; align-items: center; gap: 4px;
  background: none; border: 0; cursor: pointer;
  font-size: calc(var(--fs-md) + var(--fs-half)); color: var(--mut); padding: calc(6 * var(--sp)) calc(4 * var(--sp));
}
.fw-prev:hover:not(:disabled), .fw-skip:hover { color: var(--text); }
.fw-prev:disabled { opacity: .35; cursor: default; }
.fw-prev svg, .fw-skip svg { width: 14px; height: 14px; }
.fw-skip { margin-left: auto; }

.fw-dots { display: flex; gap: 4px; }
.fw-dots i {
  width: 5px; height: 5px; border-radius: 50%; display: block; background: var(--line2);
  transition: background .2s, width .2s;
}
.fw-dots i.on { background: color-mix(in srgb, var(--accent) 55%, transparent); }
.fw-dots i.at { background: var(--accent); width: 14px; border-radius: calc(var(--r-4xs) + var(--r-half)); }

.fw-end { display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; margin: auto 0; }
.fw-end b { font-size: var(--fs-3xl); }
.fw-end .btn { width: 100%; }
.fw-tick {
  width: 52px; height: 52px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--accent-ink); background: var(--accent);
  animation: fw-tick .45s cubic-bezier(.34, 1.42, .32, 1) backwards;
}
.fw-tick svg { width: 26px; height: 26px; stroke-width: 2.6; }
@keyframes fw-tick { from { transform: scale(.3); opacity: 0; } }

/* On a narrow screen there is no rail to take over, so the panel becomes a sheet along the
   bottom. The listing is NOT shortened to make room — the cover is `inset: 0` inside #content
   and shrinking it would shrink the very thing being judged — it is padded instead, so it
   scrolls clear of the sheet and every row is still full width and full height. */
@media (max-width: 720px) {
  .fw-panel {
    width: 100%; left: 0; right: 0; top: auto; height: min(62vh, 500px);
    border-left: 0; border-top: 1px solid var(--line);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    box-shadow: 0 -20px 60px -30px rgba(0, 0, 0, .8);
    animation-name: fw-sheet;
  }
  @keyframes fw-sheet { from { transform: translateY(24px); opacity: 0; } }
  html[data-fw] .fw-cover { padding-bottom: min(62vh, 500px); }
  .fw-panel .fx-stage { height: 104px; }
  .fw-nav { padding-top: calc(8 * var(--sp)); }
}

/* Half of these answer a pointer, and a finger is not one. The stage keeps demonstrating either
   way — the drawn hand is a script, not a mouse — so what is missing on a touch screen is only
   your half of it, and that is what this says. */
@media (hover: none) {
  .fx-lede::after {
    content: ' On a touch screen the demos still run, but most of these answer a mouse.';
    color: var(--mut);
  }
}

/* The OS setting outranks the picker entirely, exactly as it outranks the delete animation.
   feel.js already forces the layer off when reduce is asked for and the picker drops the stages
   altogether rather than drawing thirteen boxes that cannot move — this is the second lock, for
   anything that reaches the page another way. */
@media (prefers-reduced-motion: reduce) {
  .fw-panel, .fw-tick, .fx-aim, .fx-hand { animation: none !important; }
  .fx-tile .fx-stage { display: none; }
}
